Break-Even Math for Self-Hosted GLM-5.1: GPU Utilization, Ops Overhead, and Claude API Rates

Break-Even Math for Self-Hosted GLM-5.1: GPU Utilization, Ops Overhead, and Claude API Rates

pr0h0••23 min read•ai
AI Usage (76%)
On this page

Introduction: The 10x Claim and the Number That Decides It

On 2026-11-04, several outlets reported that Revolut cut its AI coding costs by roughly 10x by self-hosting a GLM model — GLM-5.1 in the headlines — instead of paying Claude API rates. The same day, IT Brief UK reported that llm-d 1.0 is aimed at cutting enterprise inference costs, and VentureBeat ran a piece on how the AI race is driving up GPU, memory, and software bills.

This post is the break-even math behind that claim. It compares the cost per million output tokens of a self-hosted GLM-5.1 node against Claude API list rates, then shows how GPU utilization, batch depth, the prefill/decode split, KV cache memory, and ops overhead each move the answer. The deliverable is a TypeScript cost model you can point at your own token mix and your own hardware prices instead of mine.

Those three stories tend to get read as one: open weights plus better serving stacks beating the API. I think that's the easy reading, and it skips the only number that actually decides the outcome.

My position, stated up front: a 10x self-hosting saving is not a hardware claim and not a model-quality claim. It's a utilization claim in a costume. Per-token economics on a self-hosted node really are better than Sonnet-class list prices — by 6x to 15x depending on workload shape — but only while the node is saturated. Divide by real sustained utilization and the multiple collapses. Below roughly 7–11% utilization it inverts, and the API wins.

I couldn't read the underlying Revolut engineering write-up. What I had was headline-and-snippet reporting from shattered.io and Pasquale Pillitteri, plus the llm-d item. So I treat "10x" as a reported figure, not a verified one, and I haven't benchmarked GLM-5.1 on any hardware. Everything throughput-related below is a stated assumption, labelled as such. The arithmetic that turns those assumptions into dollars is exact, though, and that's the part worth reusing with your own numbers.

What the Revolut Self-Hosting Report Says — and What It Leaves Out

The Confirmed Surface of the Reporting

Stripped down to what the public reporting actually supports:

ClaimStatus
Revolut self-hosted a GLM-family model for coding workloadsReported 2026-11-04 by shattered.io and Pasquale Pillitteri
Reported cost reduction of ~10x versus Claude API spendReported; methodology not visible in the snippets
llm-d 1.0 positioned around enterprise inference cost reductionReported 2026-11-04 by IT Brief UK
GPU, memory, and software line items are rising industry-wideReported 2026-11-04 by VentureBeat

That's the whole confirmed surface. It is not very much.

The Seven Numbers You Would Need to Audit the 10x

"10x cheaper" isn't a falsifiable statement until you know the denominator. If a vendor or an internal team tells you they cut inference cost 10x, these seven numbers let you check it:

  1. Baseline model tier. 10x against Opus-tier list rates and 10x against Haiku-tier rates are different claims about the world. Coding agents usually run a mid tier.
  2. Token mix. Median and p95 input tokens, median and p95 output tokens, per request.
  3. Prompt cache hit rate on the API side. Often 70–90% for agent loops, and cache reads are priced at a fraction of fresh input. Ignore caching and you overstate the baseline by 2–3x.
  4. Sustained aggregate decode throughput on the self-hosted node, at the concurrency you actually run — not the peak from a vllm bench run.
  5. Sustained utilization across the measurement window, including nights, weekends, and the two weeks after a model release when everyone was on the API.
  6. What the ops labor costs, in loaded dollars, and whether it was already on payroll.
  7. Quality delta and the resulting retry rate.

If someone shows you a 10x and can't produce items 2, 3, and 5, the number is probably real — it's just measuring a point-in-time peak, not a steady state.

Two Cost Curves: Per-Token Claude API Pricing vs Per-GPU-Hour Self-Hosting

How Claude API Pricing Actually Scales

Per-token pricing is linear in tokens and zero in everything else. Three properties matter for this comparison:

  • Marginal cost of scale is zero. Ten requests a day and ten million a day cost the same per token. Nothing to amortize.
  • Output tokens cost ~5x input tokens. On the Sonnet tier this has historically been $3 / $15 per million tokens for input / output, with cache reads at roughly a tenth of input. Check Anthropic's pricing page before you hardcode anything; the ratio is stable, the absolute numbers are not.
  • Caching is a first-class lever. For an agent loop that re-sends a 40k-token repository context every turn, cache reads dominate the bill.

There's also the Batch API, historically priced at a discount for asynchronous work. If your workload is batch-shaped, that discount applies to the entire comparison and roughly doubles the break-even utilization computed below.

What a Self-Hosted GPU-Hour Really Costs

A GPU-hour isn't a price, it's a bundle of depreciation, power, cooling, floor space, and network. Two ways to buy it:

Rented. H100/H200-class capacity has been quoted in the $1.50–$3.00 per GPU-hour range depending on contract length and provider. Call it $2.50/GPU-hour for an 8-GPU node → $20.00/node-hour.

Owned. An 8-GPU node at ~$30k/GPU plus chassis and networking, say $300k all-in, depreciated over 3 years (26,280 hours), plus power and colo:

capex:  $300,000 / 26,280 h          = $11.42 / node-hour
power:  5.6 kW × PUE 1.3 × $0.10/kWh = $ 0.73 / node-hour
colo/network                          = $ 1.00 / node-hour
                                        ────────────────
                                        $13.15 / node-hour

$13.15/node-hour looks 34% cheaper than renting. It isn't, unless the node is busy, because you pay that $13.15 for every one of the 8,760 hours in a year whether or not a single token comes out. The rental price only bills you for hours you hold. The owned price bills you for hours you own. That asymmetry is the entire article.

The Break-Even Equation

Normalizing to Cost per Million Output Tokens

The API is priced per token; the node is priced per hour. Convert the node to per-token terms by measuring how many node-seconds one request consumes.

Per request, define:

  • I = prompt tokens, C = fraction served from cache
  • O = output tokens
  • p = sustained prefill rate, cached-miss prompt tokens per second, saturated node
  • d = sustained decode rate, output tokens per second, saturated node
  • H = node cost in dollars per hour
  • U = sustained utilization, 0–1

Node-seconds per request:

t = (I × (1 − C)) / p  +  O / d

Dollars per request:

cost = t × (H / 3600) / U

That / U is doing all the work. At U = 1 you're buying exactly the node-hours you consume. At U = 0.05 you're buying twenty node-hours for every one you use.

Why Utilization Is the Denominator

Utilization is not simply "hours when a request was in flight divided by 24." There's a second, mechanical reason it stays low: throughput is a function of batch size. Decode saturates memory bandwidth, so a node at batch size 1 emits maybe 80 tokens/second, while the same node at batch size 64 emits 2,000 tokens/second. Cost per output token differs by 25x between those two states.

Which means the saturation assumption inside d and p only holds when enough concurrent requests exist to fill the batch. Below that concurrency, effective d collapses and cost per token rises even though nothing about the hardware changed.

Solve for the aggregate decode rate at which the node's output-token cost merely matches the API's output price — ignoring input entirely, and assuming perfect utilization:

(1 / d) × (20.00 / 3600) × 1e6 = 15.00   →   d ≈ 370 tok/s

At 30% utilization you need about 1,240 tok/s of sustained aggregate decode just to tie on output tokens alone. That's not a small number, and it requires sustained concurrency to reach. Low traffic doesn't just waste a fraction of the hardware — it changes the per-token cost of the hardware.

Worked Example: One 8-GPU Node at 5%, 30%, and 70% Utilization

Assumptions, all explicitly labelled as assumptions:

Hardware:   8× H200-class, rented at $2.50/GPU-hour → $20.00/node-hour
Prefill p:  25,000 tok/s sustained   (ASSUMPTION, not measured on GLM-5.1)
Decode d:    2,000 tok/s sustained   (ASSUMPTION, not measured on GLM-5.1)
Workload:   40,000 input, 1,500 output, 80% cache hit
API rates:  $3.00 input / $0.30 cache read / $15.00 output per Mtok

Per request: t = (40,000 × 0.2) / 25,000 + 1,500 / 2,000 = 0.32 + 0.75 = 1.07 node-seconds.

UtilizationNode-hours paid per useful hourSelf-host $/reqAPI $/reqAPI ÷ self-hostVerdict
5%20.0$0.1189$0.05610.47xAPI cheaper by 2.1x
10%10.0$0.0594$0.05610.94xAPI ahead, barely
30%3.3$0.0198$0.05612.8xSelf-host wins
50%2.0$0.0119$0.05614.7xSelf-host wins
70%1.4$0.0085$0.05616.6xSelf-host wins
100%1.0$0.0059$0.05619.4xSelf-host wins

Break-even utilization: 10.6%.

Same math against owned hardware at $13.15/node-hour and break-even drops to 7.0%. Against the Batch API (half price on the API side) it climbs back above 20%.

Ten percent sounds easy. Two and a half hours a day of a fully saturated 8-GPU node is not a trivial operating target for a team whose traffic arrives in bursty, interactive patterns, and "saturated" means batch depth in the tens, not one request in flight.

Throughput Is Not a Single Number

Prefill vs Decode and Why Your Token Mix Changes Everything

Prefill is compute-bound: you push a large matrix of prompt tokens through the transformer and attention is quadratic in sequence length. Decode is memory-bandwidth-bound: you read the entire weight matrix to produce one token, then do it again. Different bottlenecks, and they contend for the same silicon.

Turning the assumptions into marginal cost per token type puts the mix in focus:

PhaseNode-seconds per tokenMarginal $/Mtok at U=1API equivalentHeadroom
Prefill (cache miss)1 / 25,000$0.22$3.00 input13.6x
Decode1 / 2,000$2.78$15.00 output5.4x

Self-hosting has nearly three times more headroom on input tokens than on output tokens. That inverts the intuition most cost models start with:

Workload shapeNode-sec/reqAPI $/reqSelf-host $/req @U=1MultipleBreak-even U
RAG: 100k in / 800 out / 85% hit1.00$0.0825$0.0055614.9x6.7%
Coding: 40k in / 1.5k out / 80% hit1.07$0.0561$0.005949.4x10.6%
Chat: 8k in / 600 out / 60% hit0.43$0.0200$0.002388.4x11.9%
Agentic: 40k in / 6k out / 80% hit3.32$0.1236$0.018446.7x14.9%

The chat row is the one that gets misread: its break-even is 11.9%, not the 10.6% the coding row suggests, and its multiple is 8.4x.

That's uncomfortable news for anyone planning around agentic coding: long-output agent loops are the worst case for self-hosting, not the best. Every extra output token costs you 12x more on the node than an input token does, relative to what the API charges for it. The workloads that benefit most are input-heavy and cache-friendly — analysis over large documents, retrieval-heavy question answering, classification at scale.

⚠️

The 100k-context RAG row is optimistic in a specific way: prefill throughput degrades with context length because attention is quadratic. My 25,000 tok/s figure would have to be measured at 4k context, not 100k. Treat the 14.9x row as an upper bound on the real advantage until you've measured prefill at your actual p95 context length.

KV Cache Memory: The Constraint Nobody Budgets For

Throughput isn't the only thing concurrency buys you. Every in-flight request holds a KV cache, and its size is pure arithmetic:

bytes per token = 2 × layers × kv_heads × head_dim × bytes_per_element

For an illustrative configuration of 64 layers, 8 KV heads (GQA), head_dim 128:

KV dtypeBytes/tokenOne 32k request64 concurrent128 concurrent
FP8 (1 B)128 KiB4.0 GiB256 GiB512 GiB
FP16 (2 B)256 KiB8.0 GiB512 GiB1024 GiB

Replace the layer and head counts with the real numbers from the GLM-5.1 model card; I don't have them, and guessing would be worse than telling you to look them up. The shape of the result is what matters.

An 8×H200 node has roughly 1,128 GB of HBM. If weights of a large MoE at FP8 occupy, say, 355 GB, you have ~770 GB left for KV cache and activations. FP8 KV at 128 concurrent 32k-token requests needs 512 GiB — it fits, but it's two thirds of your remaining headroom, and it means your advertised 2,000 tok/s decode rate is only reachable in a regime where you're also burning most of your memory. FP16 KV at the same concurrency doesn't fit at all.

That's the mechanism behind the utilization trap. The concurrency that makes the hardware cheap is the same concurrency that makes the memory scarce. When you run out you either cap concurrency (throughput drops, cost rises) or preempt and recompute (throughput drops, cost rises). Both are invisible in a $/GPU-hour spreadsheet.

Ops Overhead You Cannot Amortize Away

Serving Stack Choices: vLLM, SGLang, TensorRT-LLM

StackWhere it earns its keepCost-relevant knob
vLLMBroadest model coverage, PagedAttention, mature prefix cachingAutomatic prefix caching and chunked prefill both cut the prefill term directly
SGLangRadixAttention prefix reuse, strong structured-output pathVery high cache reuse for agent loops that re-send the same context
TensorRT-LLMHighest kernel-level ceiling, in-flight batchingBest throughput, highest build cost and longest path to a working deployment

These are characterizations from the projects' documentation, not benchmarks I ran. Ordering by raw throughput at equal effort is roughly TensorRT-LLM > SGLang ≈ vLLM in my reading, but the gap in cost per token is usually smaller than the gap in engineering hours to get there.

Note that prefix caching shows up on both sides of the ledger. It cuts your prefill term on self-hosted hardware and it cuts your input bill on the API. It's a shared lever, not an advantage — see the sensitivity section below.

llm-d 1.0 and Disaggregated Serving

llm-d 1.0 was reported on 2026-11-04 as targeting enterprise inference costs. The architectural idea it's built around — the part worth understanding regardless of the release notes — is disaggregated prefill and decode.

Because prefill is compute-bound and decode is memory-bandwidth-bound, running both on the same GPU means one phase steals from the other. Disaggregation puts prefill workers and decode workers in separate pools, sizes each independently for the actual token mix, and streams the KV cache between them over a fast interconnect. It can be a large win, and it's exactly the right optimization for output-heavy agentic traffic — the shape I showed above to be the worst case for naive self-hosting.

But be precise about what it buys. Disaggregation lowers the numerator: fewer node-seconds per request. It does nothing for the denominator. A 40% faster serving stack doesn't move your break-even utilization by 40%; it moves it by 40% only if you were already saturated. At 5% utilization, a 40% throughput improvement takes you to 5% utilization with a shorter queue. llm-d is a reason self-hosting becomes viable for teams already committed to filling a cluster. It isn't a reason to buy the cluster.

On-Call, Upgrades, and the Cost of Being Your Own Vendor

The fixed costs that never show up in the per-token model:

  • Platform labor. Half an FTE at a $220k loaded rate is $110k/year. That's fixed, so it sets a floor on workload size. If you're saving 80% on an $80k/year API bill, the engineer eats 170% of the savings. Rough rule, and this is inference not measurement: below roughly $300–500k/year of API spend, a dedicated half-FTE doesn't amortize.
  • Upgrade cadence. A new open-weights model lands every few months. Each one means re-quantizing, re-tuning the serving config, re-running the eval suite, and re-validating the throughput numbers the business case was built on.
  • Failure modes. ECC errors, NCCL hangs, NVLink degrades, a node drops out of the pool mid-batch. These aren't application on-call, and your existing runbooks don't cover them.
  • Security surface. You now operate a long-context model endpoint inside your network. Prompt injection against a coding agent with repository access is an infrastructure problem now, not a vendor's problem.

A Reproducible Break-Even Cost Model in TypeScript

Inputs and Assumptions

Every number below is an input you're expected to replace. The rates are illustrative; the arithmetic is the deliverable.

The Model

cost-model.ts
// Run: npx tsx cost-model.ts
const SECONDS_PER_HOUR = 3600;
const HOURS_PER_YEAR = 8760;

export interface SelfHost {
gpuCount: number;
usdPerGpuHour: number;   // all-in amortized: capex + power + colo + network
utilization: number;     // 0..1, billable fraction of wall clock
prefillTokPerSec: number; // sustained aggregate, saturated node
decodeTokPerSec: number;  // sustained aggregate, saturated node
}

export interface Workload {
inputTokens: number;
outputTokens: number;
cacheHitRate: number;    // 0..1, prompt served from prefix cache
}

export interface ApiRates {
inputPerMTok: number;
cacheReadPerMTok: number;
outputPerMTok: number;
}

export const usdPerNodeSecond = (c: SelfHost): number =>
(c.usdPerGpuHour * c.gpuCount) / SECONDS_PER_HOUR;

export function nodeSecondsPerRequest(c: SelfHost, w: Workload): number {
const uncachedPrompt = w.inputTokens * (1 - w.cacheHitRate);
return uncachedPrompt / c.prefillTokPerSec + w.outputTokens / c.decodeTokPerSec;
}

export function selfHostUsdPerRequest(c: SelfHost, w: Workload): number {
return (nodeSecondsPerRequest(c, w) * usdPerNodeSecond(c)) / c.utilization;
}

export function apiUsdPerRequest(p: ApiRates, w: Workload): number {
const cached = w.inputTokens * w.cacheHitRate;
const fresh = w.inputTokens - cached;
return (
  (fresh / 1e6) * p.inputPerMTok +
  (cached / 1e6) * p.cacheReadPerMTok +
  (w.outputTokens / 1e6) * p.outputPerMTok
);
}

/** Cost scales as 1/U, so break-even U = cost@U=1 / apiCost. */
export function breakEvenUtilization(
c: SelfHost,
w: Workload,
p: ApiRates,
): number {
const atFullUtil = selfHostUsdPerRequest({ ...c, utilization: 1 }, w);
return atFullUtil / apiUsdPerRequest(p, w);
}

export function ownedNodeCostPerHour(o: {
nodeCapexUsd: number;
depreciationYears: number;
powerKw: number;
pue: number;
usdPerKwh: number;
coloUsdPerHour: number;
}): number {
const capexHourly = o.nodeCapexUsd / (o.depreciationYears * HOURS_PER_YEAR);
const powerHourly = o.powerKw * o.pue * o.usdPerKwh;
return capexHourly + powerHourly + o.coloUsdPerHour;
}

// ---- inputs (replace every one of these with your own) ----
const rented: SelfHost = {
gpuCount: 8,
usdPerGpuHour: 2.5,
utilization: 1,
prefillTokPerSec: 25_000,
decodeTokPerSec: 2_000,
};

const workload: Workload = {
inputTokens: 40_000,
outputTokens: 1_500,
cacheHitRate: 0.8,
};

const api: ApiRates = {
inputPerMTok: 3,
cacheReadPerMTok: 0.3,
outputPerMTok: 15,
};

// ---- report ----
const nodeHour = usdPerNodeSecond(rented) * SECONDS_PER_HOUR;
const t = nodeSecondsPerRequest(rented, workload);
const apiCost = apiUsdPerRequest(api, workload);

console.log("node $/hr        " + nodeHour.toFixed(2));
console.log("node-sec/request " + t.toFixed(2));
console.log("");
console.log("util | self-host $/req | API $/req | API/self | verdict");

for (const u of [0.05, 0.1, 0.3, 0.5, 0.7, 1.0]) {
const self = selfHostUsdPerRequest({ ...rented, utilization: u }, workload);
const ratio = apiCost / self;
console.log(
  (u * 100).toFixed(0).padStart(3) + "% |" +
  self.toFixed(4).padStart(17) + " |" +
  apiCost.toFixed(4).padStart(9) + " |" +
  ratio.toFixed(2).padStart(8) + "x |" +
  (ratio >= 1 ? " self-host" : " API"),
);
}

console.log("");
console.log(
"break-even utilization: " +
(breakEvenUtilization(rented, workload, api) * 100).toFixed(1) + "%",
);

console.log(
"owned node $/hr: " +
ownedNodeCostPerHour({
  nodeCapexUsd: 300_000,
  depreciationYears: 3,
  powerKw: 5.6,
  pue: 1.3,
  usdPerKwh: 0.1,
  coloUsdPerHour: 1.0,
}).toFixed(2),
);

Sample Output

node $/hr        20.00
node-sec/request 1.07

util | self-host $/req | API $/req | API/self | verdict
  5% |           0.1189 |    0.0561 |    0.47x | API
 10% |           0.0594 |    0.0561 |    0.94x | API
 30% |           0.0198 |    0.0561 |    2.83x | self-host
 50% |           0.0119 |    0.0561 |    4.72x | self-host
 70% |           0.0085 |    0.0561 |    6.60x | self-host
100% |           0.0059 |    0.0561 |    9.44x | self-host

break-even utilization: 10.6%
owned node $/hr: 13.15

That 9.44x at 100% utilization is almost certainly where a "10x" headline comes from. It's a real number. It just assumes you never had a quiet afternoon.

Sensitivity Analysis: Which Knob Moves the Answer

Utilization: The Dominant Term

The dominant term, and it's a hyperbola, not a line. Going from 5% to 30% is a 6x improvement in cost per token. Going from 70% to 100% is a 1.4x improvement. The first 30 points of utilization are worth more than the last 70, which means effort spent on queueing, batching, and traffic shaping beats effort spent on kernel optimization for most teams.

Prompt Cache Hit Rate Cuts Both Sides

Counter-intuitive result: cache hit rate changes absolute costs a lot but barely moves break-even utilization, because it reduces both sides.

Cache hitAPI $/reqSelf-host $/req @U=1MultipleBreak-even U
0%$0.1425$0.0130610.9x9.2%
50%$0.0885$0.0086110.3x9.7%
80%$0.0561$0.005949.4x10.6%
95%$0.0399$0.004618.7x11.6%

So don't let anyone sell self-hosting on the basis of "our cache hit rate is high." A high cache hit rate makes the API cheaper too. It's a shared lever, and if anything it slightly favours the API: cache reads are priced at a tenth of input, while your prefix cache saves you prefill compute that was already the cheap phase.

Token Mix: Output-Heavy Work Needs More Utilization

Tabled above, but the direction deserves restating: the more output-heavy your workload, the higher the utilization you need. Agentic coding loops with 5k+ output tokens per turn push break-even utilization toward 15%, and that's before quality regression and before the prefill degradation at long context.

Hardware Price and Depreciation Window

ScenarioNode $/hrSelf-host $/req @U=1Break-even U
Rented @ $2.50/GPU-hr$20.00$0.0059410.6%
Rented @ $1.50/GPU-hr$12.00$0.003576.4%
Owned, $300k, 3 yr$13.15$0.003917.0%
Owned, $300k, 5 yr$8.58$0.002554.5%
Owned, $400k, 3 yr$16.95$0.005049.0%

The 5-year depreciation row is a trap worth naming. GPUs don't stay competitive inference hardware for five years; a 3-year window is already generous, and stretching it to make the spreadsheet work books a future write-down as a present saving. Treat the 3-year owned row as the honest one, and notice it only beats renting if you actually fill the node.

The Line Item Spreadsheets Miss: Quality Regression

Cost per token is the wrong unit. Cost per completed task is the right one, and that folds in the retry rate.

The eval you need is boring and specific. Same tasks, same criterion, both models, blind judging, counts reported next to the claim. Table shape below uses placeholder values — I haven't run this:

Task:      120 tickets from the internal backlog, Python.
Criterion: tests pass AND reviewer rating >= 4/5, blind.
Result A:  baseline model — 94/120 pass, mean rating 4.1
Result B:  self-hosted GLM-5.1 — 89/120 pass, mean rating 3.8

A 5-point pass-rate drop means roughly 6% more retries. If a retry costs one extra request, that's noise against a 6x cost advantage. But if a failed attempt costs a developer fifteen minutes of unwinding a bad patch, the human time dwarfs the entire compute line item — and it lands on a different budget, which is why it never makes it into the comparison.

There's a second-order effect too. Lower-confidence output produces more exploratory turns, which means more input tokens per completed task. That raises the API bill and the self-hosted prefill load at the same time. The token-mix assumption in your cost model isn't independent of the model-quality assumption.

My position: run the eval before the procurement, on your own backlog, with the criterion written down in advance. An unmeasured quality delta is the single largest unquantified risk in any self-hosting business case, and it's the one nobody puts a number on.

Decision Table: When Self-Hosting GLM-5.1 Wins

ConditionSelf-hostAPI
Sustained utilization ≥ 40% with real concurrency✅
Sustained utilization < 15% or bursty < 8 h/day✅
Input-heavy, cache-friendly workloads (RAG, analysis, classification)✅
Output-heavy agent loops (> 5k output tokens/turn)✅
Annual API spend under ~$300k✅
Hard data-residency or air-gap requirement✅
Team already runs GPU infrastructure and has on-call✅
Need frontier quality within a quarter of release✅
Quality delta unmeasured on your own tasks✅ (until measured)

Honest summary of that table: self-hosting wins on steady, input-heavy, high-volume workloads owned by a team that already operates accelerators. It loses on bursty, output-heavy, low-volume workloads owned by an application team. The 10x figure describes the first case.

Pre-Commitment Checklist: Verify These Before the Purchase Order

Write these down and sign them before the purchase order, not after:

  1. Measure prefill and decode on the actual model, quantization, and p95 context length. Publish the numbers internally. Don't accept vendor or blog throughput figures.
  2. Extract your real token mix from API logs. p50 and p95 input and output tokens per request, plus the actual cache hit rate — not the theoretical one.
  3. Compute break-even utilization from those numbers. It's one division once you have the model above.
  4. Compare it against the p20 of your last 90 days of demand, not the peak. If break-even is 11% and your p20 is 6%, you have your answer before you spend anything.
  5. Price the ops labor explicitly. Half an FTE plus 20% of an SRE, in loaded dollars, on the same spreadsheet.
  6. Run the quality eval on your own backlog with the criterion fixed in advance, and convert the delta into a retry rate.
  7. Write the exit trigger now. Something like: "If sustained utilization is below 20% for two consecutive quarters, or quality delta exceeds 8 points, we return to the API." A migration you can't reverse isn't a cost optimization, it's a commitment.

Conclusion: Buy the Cluster When You Can Fill It

The reported 10x is plausible. My own arithmetic lands at 9.4x for a coding-shaped workload at 100% utilization against Sonnet-class list rates, close enough to the headline that I don't doubt the shape of the result. What I doubt is the denominator.

Three things actually decide this decision:

Utilization is the whole argument. At 5% you're paying 2.1x more than the API. At 10.6% you break even. Everything above that is profit, and the first thirty points are worth more than the next seventy. If your break-even is 11% and your realistic sustained load is 15%, you have a 4-point margin on a hyperbola — that's not a business case, that's a bet.

Throughput is not a constant. Below the concurrency needed to fill a batch, decode throughput collapses and cost per token rises 25x. A node at 100% wall-clock "utilization" with batch size 1 is more expensive than the API. Utilization and throughput are the same variable, measured two ways.

Workload shape matters more than hardware. Input-heavy, cache-friendly work has 13.6x of headroom over API input pricing. Output-heavy agent loops have 5.4x. Self-hosting is most attractive on exactly the input-heavy work that agentic traffic is drifting away from, which makes the 10x a snapshot of a workload mix quietly changing underneath it.

llm-d 1.0 and disaggregated serving are real improvements and they shrink the numerator. They don't touch the denominator. Buy the cluster when you can fill it, not because the serving stack got faster.

Further Reading

  • Anthropic API pricing — the input, cache-read, output, and batch rates you should plug into the model rather than trusting any figure in this post
  • vLLM documentation — automatic prefix caching, chunked prefill, and the paged KV cache implementation behind the memory arithmetic above
  • SGLang — RadixAttention prefix reuse, the strongest cache-reuse story for agent loops
  • NVIDIA TensorRT-LLM — in-flight batching and the highest throughput ceiling, at the highest integration cost
  • llm-d — the project reported on 2026-11-04 as targeting enterprise inference cost, built around disaggregated prefill and decode

Share this post

More posts

Loading comments…