Throughput economics
The unit economics of serving reduce to one metric — tokens/sec/GPU — turned into $/1M tokens. Batching lowers it to a knee, utilization silently multiplies it, and the self-host vs API decision is a break-even volume you plan under a latency SLO.
Learning objectives
- Compute the one metric that governs serving cost: tokens/sec/GPU.
- Convert a GPU-hour price into $/1M tokens, and compare it to an API's list price.
- Explain why batching lowers $/token up to the memory/latency knee — and why past it you pay for latency.
- Treat utilization as the hidden cost: an idle reserved GPU bills at 100%.
- Find the self-host vs API break-even volume and plan capacity under a p99 latency SLO.
1 · The only cost metric that matters: tokens/sec/GPU advanced
Start with the meeting you've either sat in or will. A team ships a self-hosted model, the demo screams, everyone's thrilled — then the first cloud bill lands and it's five times what the spreadsheet promised. Nobody lied; the model didn't change, the GPU price didn't change. What broke the estimate was that the spreadsheet quoted a single glossy number ("$0.20/1M tokens!") without asking the one question that actually decides the bill: how many tokens does this GPU really produce per second, and how much of the hour is it actually producing them? This whole chapter exists to make that question reflexive.
Here's the analogy to hold onto: a GPU is a taxi with the meter always running. You pay by the hour the car is on the road, not by the passenger. A taxi that carries one rider per trip and a taxi that carries four cost the same per hour — but the four-rider taxi's cost per passenger is a quarter as much. Your GPU is identical: it bills a fixed dollars-per-hour whether it emits 10 tokens/sec or 10,000, so the only way to cheapen each token is to cram more tokens into every paid second. That "tokens carried per paid hour" is exactly what tokens/sec/GPU measures.
Now the precise definition: tokens/sec/GPU (also called goodput when you count only useful output) is the aggregate output tokens one accelerator generates per second, summed across every request it's serving at once. It is the pivot on which the entire cost model turns, because $/token = GPU-hour price ÷ tokens produced that hour — the price is fixed, so tokens/sec/GPU is the only variable you control. That reframing is the mental unlock of this chapter: every optimization you'll ever read about — quantization (IC2), the KV-cache (IC3), batching (IC4), tensor/pipeline parallelism (MS4) — is worth exactly the tokens/sec/GPU it buys and not a cent more. Quote a speedup in "20% faster" and you can't compare it to anything; quote it in tokens/sec/GPU and it converts straight to dollars.
The common mistake is confusing tokens/sec/GPU with raw single-stream speed — how fast one user's response streams. Those pull in opposite directions: batching many users together makes each individual response a little slower but drives aggregate tokens/sec/GPU (and thus cost) dramatically better. A card that feels "slow" to one user can be the cheapest per token in the fleet. Serving economics cares about the aggregate, not the single stream — keep the two straight and half the confusing benchmarks you'll read suddenly make sense.
Everything in serving economics reduces to one number: tokens/sec/GPU (also written tok/s/GPU or the aggregate goodput). A GPU costs a fixed number of dollars per hour whether it emits 10 tokens/sec or 10,000. So $/token is just the GPU-hour price divided by how many tokens that GPU produces in an hour. Every other lever in this track — quantization (MS/IC2), the KV-cache (IC3), batching (IC4), tensor/pipeline parallel (MS4) — is worth exactly the tokens/sec/GPU it buys you. Learn to quote everything in this unit.
Order-of-magnitude anchors you should carry in your head (2025-era, single accelerator): an H100 rents for roughly $2–4/GPU-hour on-demand from the big clouds and specialist providers; an A100 is cheaper (~$1–2). A well-batched 7–8B model on one H100 does thousands of output tokens/sec aggregate; a 70B model sharded across GPUs does far fewer per GPU. These are ballparks to reason with — always re-measure your own stack (IC1).
Read it top-to-bottom: one fixed hourly price at the top fans down to two GPUs doing different amounts of work, and each lands on a very different cost per token at the bottom.
- The top purple bar is the fixed GPU-hour price — the "taxi meter" that charges the same $3/hour no matter what the card is doing.
- The middle row is goodput: the red box is a GPU squeezing out only ~250 tok/s, the green box one running well-batched at ~2,500 tok/s.
- The bottom row is the payoff — the identical hourly bill spread over those tokens gives ~$3.33/1M (red, expensive) versus ~$0.33/1M (green, cheap). Same hardware, same price, 10× the tokens, 1/10th the cost.
In short: Because the price is fixed, $/token is decided entirely by tokens/sec/GPU — which is why every optimization in this track is quoted in that one unit.
2 · From GPU-hour to $/1M tokens advanced
You have tokens/sec/GPU. Finance has a budget in dollars. Section 2 is the bridge between them — and it's a bridge people cross badly all the time. The classic failure: someone divides the GPU-hour price by the datasheet's peak throughput, gets a beautiful number, and quotes it as gospel. Six months later the real bill is triple that, and nobody can explain why because the arithmetic was "correct." The arithmetic was correct; the inputs were fantasies. This section teaches you to do the conversion honestly, which mostly means being honest about one sneaky input.
Think of it like converting a car's fuel economy into a cost-per-mile. You don't just take the sticker MPG and the fuel price — you take the MPG you actually get in your real driving (city traffic, hills, the AC on), and only that number turns into a cost you can trust. Serving is the same: you take your measured tokens/sec (not the datasheet peak), the GPU-hour price you actually pay, and — the input everyone forgets — the fraction of paid hours the GPU is genuinely working. Multiply those honestly and you get a $/1M-tokens figure you can defend.
The conversion itself is grade-school arithmetic, which is exactly why it lulls people into trusting it. One GPU-hour buys tokens_per_sec × 3600 tokens, but only if the GPU is busy every one of those 3600 seconds. So you multiply by utilization — the fraction of paid time actually spent generating billable tokens — to get the tokens you truly bought. Divide the hourly price by that, scale to a million, and you have $/1M tokens. Written out: $/1M = GPU-hour price ÷ (tokens/sec × 3600 × utilization) × 1,000,000. The three boxes in the diagram below — price, throughput, utilization — are the only inputs, and the last one is the one that quietly wrecks estimates.
Why bother, when the API just posts a price? Because that's the whole point of the conversion: it puts your self-hosted cost in the same unit as the API's published $/1M so you can lay them side by side. If your honest number sits below the API's price at your real volume, self-hosting is worth investigating; if it sits above, the API is simply cheaper and you can stop the analysis right here and save yourself the ops burden. The mistake to avoid is comparing an API's real, utilization-inclusive price against your own 100%-utilization fantasy — an apples-to-fantasy comparison that has launched a thousand regretted GPU clusters.
Here is the whole cost model on one line. Three inputs — the GPU-hour price, the tokens/sec/GPU it sustains, and your utilization (the fraction of paid time the GPU is actually generating billable tokens) — collapse into a single $/1M tokens, which you then hold up against the API's published price.
This diagram is the entire cost model of self-hosting in one row. Read it left to right: three measurable inputs on the left combine into a single $/token number, which you then hold up against the API's list price on the right.
- GPU-hour $ — the fixed price you pay for the accelerator every hour (~$2–4 for an H100), whether it's busy or idle.
- tokens/sec/GPU — the aggregate output tokens that one GPU actually produces per second on your model and batch size. This is measured goodput, not the datasheet.
- × utilization — the fraction of paid time the GPU is really generating billable tokens. Idle hours still cost the full GPU-hour, so this quietly multiplies your $/token.
- $/1M tokens — the three inputs collapse to one number: GPU-hour price ÷ (tokens/sec × 3600 × utilization) × 1,000,000. This is your true unit cost.
- vs API price — the crossover. If your $/1M sits below the API's published price at your real volume, self-host is in the running; if above, the API is simply cheaper.
In short: Everything else in this track — batching, quantization, parallelism — is worth exactly the tokens/sec/GPU (the middle box) it buys. Quote every optimization in this one unit and the comparisons become apples-to-apples.
The arithmetic: one GPU-hour buys tokens_per_sec × 3600 tokens at full utilization. Divide the hourly price by that token count and scale to a million. Then divide by utilization, because tokens you didn't serve still cost you the full hour. If your measured cost sits below the API's $/1M price at your real volume, self-hosting is in the running; if it sits above, the API is simply cheaper and you should stop here.
3 · The $/1M-tokens calculator advanced
The best way to feel how brutal utilization is on your wallet is to hold everything else fixed and watch one number move. That's what this tiny calculator is for. It's tempting to skip past a five-line function as too trivial to matter — but the triviality is the lesson. If the formula were complicated, you'd blame the math when the bill surprised you. It isn't. The formula is one division; the surprises all live in the inputs you feed it.
Picture the same physical GPU on a bench, running the identical model at an identical 2,500 tokens/sec, at an identical $3/hour. Nothing about the hardware or the workload changes. The only thing we vary is how many of the paid hours it's actually busy. Run it flat-out and the tokens are cheap; let it idle two-thirds of the day and each token you did serve has to carry the cost of the two you didn't. The calculator makes that carry-cost visible: at 100% utilization the H100 lands near $0.33/1M tokens; at 50% it doubles; at 20% it more than quintuples — all with the sticker price frozen.
Why does halving utilization exactly double the cost? Because utilization sits in the denominator: $/1M ∝ 1 ÷ utilization. Cut the busy fraction in half and you've halved the tokens that same fixed hourly bill buys, so the cost per token doubles. This inverse relationship is the single most important intuition in the chapter, and it's why a "cheap on paper" self-host quote so often turns into an expensive reality — the paper assumed 100% and reality delivered 30%. The guard clause in the code (rejecting a utilization outside 0–1 or a non-positive token rate) exists for the same honesty reason: a typo shouldn't be able to hand you a comforting garbage number.
Read the code as three moves: convert the hourly price into tokens-per-hour at your real utilization, divide to get dollars-per-token, then scale by a million to reach the industry-standard $/1M quote everyone speaks in. The loop deliberately changes only utilization so you can attribute the entire cost swing to that one lever — the discipline you want when you later argue a budget.
This is the model above as code. It is deliberately tiny — the point of throughput economics is that the formula is trivial and the inputs (measured goodput, honest utilization) are where all the judgement lives.
Scan the bars left-to-right: same GPU at three utilization levels, with cost per million tokens shown as bar height (taller = more expensive).
- The green bar (100% utilization) is the floor — ~$0.33/1M when the GPU is busy every paid second.
- The amber bar (50%) is exactly twice as tall — halving the busy fraction doubles the cost, because you halved the tokens the same hourly bill buys.
- The red bar (20%) towers over both at ~$1.67/1M — the formula
$/1M ∝ 1 ÷ utilizationmeans low utilization multiplies cost fast. Nothing about the hardware or throughput changed.
In short: Utilization is a divisor, so idle time is a multiplier on cost — the single most important intuition in the chapter.
cost_per_mtok.pydef cost_per_million_tokens(gpu_hourly, tokens_per_sec, utilization):
"""$/1M output tokens for a self-hosted GPU.
gpu_hourly : $ per GPU-hour (e.g. 3.00 for an H100 on-demand)
tokens_per_sec : measured aggregate output tokens/sec on that GPU
utilization : fraction of paid time actually generating (0<u<=1)
"""
if tokens_per_sec <= 0 or not (0 < utilization <= 1):
raise ValueError("need tokens_per_sec>0 and 0<utilization<=1")
tokens_per_hour = tokens_per_sec * 3600 * utilization
dollars_per_token = gpu_hourly / tokens_per_hour
return dollars_per_token * 1_000_000
for u in (1.0, 0.5, 0.2):
c = cost_per_million_tokens(gpu_hourly=3.00, tokens_per_sec=2500, utilization=u)
print(f"util {u:>4.0%} -> ${c:6.2f} / 1M tokens")
util 100% -> $ 0.33 / 1M tokens
util 50% -> $ 0.67 / 1M tokens
util 20% -> $ 1.67 / 1M tokens
This is the cost model as code — deliberately tiny. The formula is trivial; the judgement is entirely in the inputs you feed it (measured throughput, honest utilization).
- The guard rejects nonsense inputs — you need a positive token rate and a utilization strictly between 0 and 1 — so a typo can't silently produce a garbage cost.
tokens_per_hour = tokens_per_sec * 3600 * utilizationis the key line: one paid hour buystokens_per_sec × 3600tokens only at full utilization. Multiplying byutilizationdiscounts to what the GPU actually generates.- Dividing the hourly price by those tokens gives $/token; scaling by 1,000,000 gives the industry-standard $/1M-tokens quote.
- The loop runs the same 2,500 tok/s H100 at 100%, 50%, and 20% utilization to isolate one variable: utilization alone.
What the output means: Three lines. At 100% utilization the H100 costs ~$0.33/1M tokens; halve utilization and it doubles to $0.67; drop to 20% and it's $1.67 — the GPU-hour price never changed.
Try this: Change gpu_hourly to 2.00 (a cheaper A100-ish rate) and re-run. Notice the cost scales linearly with the hourly price but is dominated by how full you keep the GPU.
At full utilization an H100 doing 2,500 tok/s costs about $0.33 per million output tokens — cheaper than most frontier APIs. But halve utilization and the cost doubles; drop to 20% and you're at $1.67. The GPU-hour price never changed. Utilization is the entire story, which is why it gets its own section.
4 · Batching: the throughput→cost curve and its knee expert
Here's the seductive trap that catches good engineers. You've internalized "utilization is everything" and "cram more tokens into each paid second," so batching looks like free money — bigger batch, more tokens/sec, lower $/token. Someone plots the first few points, sees cost falling fast, and reasons: if batch 4 is cheaper than batch 1, batch 128 must be cheaper still, so max the batch and cut the bill. Then p99 latency detonates, users start timing out, and the "savings" past a certain point turn out to have been pennies bought with seconds. This section is about finding the exact point where batching stops being a gift.
The analogy is a shared shuttle bus versus a private car. One passenger in a car pays for the whole trip; put four in the shuttle and they split the cost of the same fuel — that's batching amortizing the model's weight-read across many requests, and it's genuinely powerful. The reason it works: token generation (decode) is memory-bound, meaning each token spends most of its time re-reading the model's weights from GPU memory, not doing math. Batch several requests and they all ride on one weight-read, so aggregate tokens/sec climbs steeply and $/token falls in lockstep. So far, so good.
But a shuttle only seats so many, and a fuller shuttle takes longer as it waits for everyone to board — and that's where the free lunch ends. Two hard ceilings bite as the batch grows. First, the KV-cache runs out of GPU memory: every concurrent sequence needs its own cache (IC3), and once the card is full you physically cannot add another rider. Second, per-token latency (TPOT) rises as more requests compete for the same compute, so each user waits longer. The result is a curve that climbs steeply, then bends over and flattens — the knee. Before the knee, each doubling of the batch is a big cost win; after it, throughput barely moves while latency keeps climbing.
The mistake, then, is optimizing for the ceiling instead of the knee. Past the knee you're paying real, SLO-threatening latency to shave fractions of a cent off each million tokens — a terrible trade for an interactive service. The right rule is batch to the knee, not to the ceiling: find where the marginal savings go flat, park your operating point just below it, and when you need more capacity, add GPUs (scale out) rather than stuffing the batch deeper. The lab below prints the marginal saving per doubling precisely so you can see the knee arrive rather than guess at it.
Decode is memory-bound: every token re-reads the model's weights (IC1/IC3). Batching amortizes that one weight-read across many concurrent requests (IC4), so aggregate tokens/sec/GPU climbs steeply with batch size — and $/token falls in lockstep. But it does not climb forever. Two ceilings bite: the KV-cache runs out of GPU memory (you can't hold more sequences), and per-request latency (TPOT) rises as the batch competes for compute. The knee is where throughput flattens; pushing past it buys almost no cost savings while blowing your latency SLO.
Scan left-to-right as the batch size grows along the bottom axis; watch the two curves and the dashed line where they bend.
- The green curve is aggregate throughput (tok/s). It climbs steeply at small batches — one weight-read amortized across many requests — then flattens as the KV-cache and compute cap the GPU.
- The blue curve is $/1M tokens, the mirror image: it falls fast while throughput climbs, then bottoms out. Cheaper tokens are the whole reason to batch.
- The amber dashed line and dot mark the knee (~batch 32 here). To its left every doubling is a big win; to its right (amber arrow) throughput barely moves, cost barely drops, but per-token latency keeps rising — a bad trade for an interactive service.
In short: Park your operating point just below the knee; when you need more capacity, add GPUs rather than batching deeper.
batch_curve.pydef throughput_at_batch(batch, per_stream_toks=55, knee=32, ceiling=3200):
"""Toy model of aggregate tokens/sec vs batch size.
Near-linear early (weight-read amortized), saturating past the knee
where KV-cache/compute cap the GPU. Illustrative shape, not a spec."""
ideal = per_stream_toks * batch # if it scaled forever
saturating = ceiling * (batch / (batch + knee)) # diminishing returns
return round(min(ideal, saturating))
def cost_per_mtok(gpu_hourly, tps):
return gpu_hourly / (tps * 3600) * 1_000_000
print("batch tok/s $/1M marginal $/1M saved")
prev = None
for b in (1, 4, 8, 16, 32, 64, 128):
tps = throughput_at_batch(b)
c = cost_per_mtok(3.00, tps)
delta = "" if prev is None else f"{prev - c:+.3f}"
print(f"{b:>4} {tps:>5} ${c:6.3f} {delta}")
prev = c
batch tok/s $/1M marginal $/1M saved
1 55 $15.152
4 220 $ 3.788 +11.364
8 440 $ 1.894 +1.894
16 880 $ 0.947 +0.947
32 1600 $ 0.521 +0.426
64 2133 $ 0.391 +0.130
128 2560 $ 0.326 +0.065
This models why batching lowers $/token — but only up to a point. Aggregate throughput climbs nearly linearly at first (one weight-read amortized across many requests), then saturates as the KV-cache and compute cap the GPU.
ideal = per_stream_toks * batchis the fantasy where throughput scales forever — each extra request adds a full stream's worth of tokens.saturating = ceiling * (batch / (batch + knee))is the reality: a curve that approaches a hardceilingand bends over near thekneebatch size.min(ideal, saturating)takes whichever is smaller, so early batches are linear and later ones flatten.cost_per_mtokturns each throughput number into $/1M tokens at a $3/hr GPU — higher throughput, lower cost.- The
marginalcolumn subtracts each row's cost from the previous, showing how much each doubling of the batch actually saves.
What the output means: A table from batch 1 to 128. Cost falls fast early (1→4 saves ~$11/1M) then crawls (64→128 saves ~6¢), while every step up the batch adds latency.
Try this: Find the knee in the marginal column — the batch where extra batching saves less than a cent per 1M tokens. That's where you stop batching and start scaling out (adding GPUs) instead.
Read the last column. Going from batch 1→4 saves $11/1M tokens; 32→64 saves 13¢; 64→128 saves 6¢ — and every step up the batch adds queueing and TPOT latency. The economically interesting region is the early, steep part; beyond the knee (~32 here) you are trading real latency for pennies. Batch to the knee, not to the ceiling.
5 · Utilization — the hidden cost expert
Two teams, same model, same GPU, same $3/hour — and one team's tokens cost five times the other's. That's not a benchmark error or a magic kernel; it's the most common and most preventable cost blowout in serving, and the culprit has been lurking in every formula so far: utilization. It's earned its own section because it's the input people omit — not because they can't compute it, but because it doesn't feel like a cost. An idle GPU isn't doing anything wrong; it's just sitting there. But sitting there is precisely the expense.
The mental model is a gym membership. You pay the same monthly fee whether you go every day or twice a year — the fee is for access, not for workouts. Your "cost per workout" is entirely determined by how often you show up. A reserved or on-demand GPU is identical: it bills for wall-clock time, every hour of it, whether it's generating tokens or staring at an empty queue at 3 a.m. The GPU-hour price is the membership fee; utilization is how often you actually show up; and your true $/token is the fee spread across only the tokens you served.
This is why the same hardware yields wildly different costs. Real traffic is diurnal and bursty — busy during working hours, dead overnight, spiky within the day — so a GPU sized to handle the peak sits mostly idle the rest of the time. Left un-managed, diurnal patterns alone can cap realistic average utilization around 30–40%, which (by the inverse law from section 3) roughly triples your cost versus the 100% fantasy. The levers to claw it back are the practical payoff of this section: autoscale down at night, batch offline work into the troughs so the GPU is never truly idle, or rent by the token (an API) and let someone else solve the packing problem.
And that last point reveals why APIs can undercut a naive self-host quote. A frontier API is effectively a giant shared GPU pool aggregating thousands of customers' bursty traffic into one smooth, near-100%-utilized load — the pooled efficiency is baked into its per-token price. When you self-host, you inherit the utilization problem yourself, and your single-tenant traffic is far lumpier than a pool's. The mistake to never make: trusting a self-host cost that assumed 100% utilization. Measure your real busy fraction first; the purchasing-mode table below then shows how commitment level (on-demand, reserved, spot) interacts with that number to set your effective price.
A reserved or on-demand GPU bills for wall-clock time, not for tokens. Every second it sits idle — overnight, between traffic bursts, waiting on a slow upstream, or over-provisioned "just in case" — is billed at 100% and produces zero tokens. This is why two teams running the same model on the same GPU can have a 5× difference in $/token: one runs it hot, the other runs it at 20% average utilization. Diurnal traffic (busy 9–5, dead at night) alone can cap your realistic average utilization near 30–40% unless you autoscale, batch offline work into the troughs, or rent by the token.
Read the two horizontal bands top-to-bottom: the top band is what you pay for, the bottom band is what you actually get.
- The amber top bar is the full 24 paid hours — the "gym membership" fee that charges the same whether or not you show up.
- The bottom bar splits into a small green slice (hours the GPU is genuinely generating tokens, e.g. the 9–5 rush) and a wide red slice (idle overnight and between bursts — still billed, zero tokens).
- The red band is the payoff element: because you divide the same fixed bill by only the green tokens, an average utilization of ~30–40% multiplies your $/token roughly 3× — the levers below (autoscale, trough-batching, rent-by-token) all work by shrinking that red band.
In short: You pay for wall-clock time, not tokens; the fraction of paid hours that are truly busy is your utilization, and it is a divisor on cost.
| Purchasing mode | Rel. price | Commitment | Use for |
|---|---|---|---|
| On-demand | 1.0× (baseline) | none | spiky/unknown load, dev |
| Reserved / committed (1–3yr) | ~0.4–0.6× | you pay even when idle | steady 24/7 baseline |
| Spot / preemptible | ~0.2–0.4× | can be reclaimed anytime | batch, fault-tolerant, checkpointed |
The purchasing mode is a second utilization lever. Reserved capacity is cheap per hour only if you keep it busy — an idle reserved GPU is the worst of both worlds. Spot is the cheapest raw price but can vanish mid-request, so it fits batch/offline (AP1-style) work, not your latency-SLO online path. A common pattern: reserved for the predictable baseline, on-demand to absorb peaks, spot for overnight batch.
6 · Self-host vs API: the break-even volume expert
"Self-hosting is cheaper" and "the API is cheaper" are both true — the honest question is at what volume? Engineers argue this in absolutes and talk past each other for hours, because each is picturing a different traffic level. A weekend side-project and a company serving a billion tokens a month live on opposite sides of a line, and the entire debate collapses once you find that line. This section turns a religious war into a single number you can compute.
The cleanest way to see it is two phone plans. One is pay-as-you-go: no monthly fee, you pay per minute — that's the API, a straight line through the origin whose slope is the per-token price. The other is a fixed monthly plan: a chunky bill every month whether you call or not, plus almost nothing per minute — that's self-hosting, a line with a high y-intercept (the GPUs you rent 24/7) and a gentle slope (your tiny marginal per-token cost). Two lines with different intercepts and slopes cross at exactly one point. Below that crossover the pay-as-you-go plan is cheaper; above it the fixed plan wins. That crossover is your break-even volume.
The arithmetic follows straight from the picture. Self-host beats the API by (api_price − self_host_marginal) on every million tokens — call that the per-million saving. To justify self-hosting you must save enough to pay off the fixed GPU bill, so the break-even is simply fixed_monthly_cost ÷ per-million-saving. Feed in a reserved GPU's monthly cost and a realistic marginal rate and the crossover pops out in millions of tokens per month. Below it, don't self-host; above it, self-hosting can win.
Now the two mistakes that make this number lie. First, that gentle self-host slope assumes the utilization your section-3 marginal cost was computed at — leave the GPU 30% utilized and your real marginal cost triples, tilting the line up and shoving break-even far to the right. Break-even and utilization are the same conversation wearing different clothes. Second, the fixed cost in the formula is not just GPUs: a self-hosted stack needs a human to patch, autoscale, and answer pages at 2 a.m. Fold even a fraction of an engineer's salary into the fixed cost and the break-even jumps by hundreds of millions of tokens. The calculator below computes the naive crossover; the "try this" then makes you add the salary and watch it move — because that's the version finance actually lives with.
Self-hosting converts a per-token cost (the API bill, which scales with usage) into a fixed monthly cost (GPUs you rent whether busy or not) plus a small marginal per-token cost. The API is a flat line through the origin; self-host is a line with a big y-intercept and a gentle slope. They cross at one volume — your break-even. Below it the API wins on cost; above it self-host can win, if you actually reach the utilization your fixed cost assumed. This is the GPU-serving version of the general build-vs-buy from LM5.
Read left-to-right along the volume axis; the cheaper option at any volume is whichever line sits lower there.
- The red line is the API: it starts at the origin (no volume, no bill) and rises steeply — every token costs the same per-token price, so cost is proportional to usage.
- The green line is self-host: it starts high on the y-axis (the fixed GPU bill you owe even at zero tokens, marked by the dashed
fixed GPU $stub) but rises only gently, because your marginal per-token cost is tiny. - The blue dot where they cross is the break-even volume (~492M/month in the lab). Left of it the red line is lower → the API wins; right of it the green line is lower → self-host wins.
In short: Break-even = fixed monthly cost ÷ the per-million saving; low utilization steepens the green line and shoves the crossover to the right.
breakeven.pydef breakeven_volume(api_price_per_mtok, gpu_monthly_fixed, self_host_per_mtok):
"""Monthly volume (in millions of tokens) where self-host total cost
equals the API bill. api = api_price * V ; self = fixed + shp * V.
Returns the crossover V in M-tokens/month, or None if self-host never wins."""
slope_gap = api_price_per_mtok - self_host_per_mtok
if slope_gap <= 0:
return None # API's per-token price already <= our marginal cost
return gpu_monthly_fixed / slope_gap
def monthly(api, fixed, shp, V):
return api * V, fixed + shp * V # (api_bill, self_host_bill) at V M-tokens
# One reserved H100 ~ $1.80/hr committed -> ~$1,314/month fixed.
# Self-host marginal cost ~ $0.33/1M (from section 3). API list ~ $3.00/1M.
be = breakeven_volume(api_price_per_mtok=3.00, gpu_monthly_fixed=1314,
self_host_per_mtok=0.33)
print(f"break-even at ~{be:,.0f} M tokens/month")
for V in (100, be, 1000):
api_bill, sh_bill = monthly(3.00, 1314, 0.33, V)
winner = "API" if api_bill < sh_bill else "self-host"
print(f" V={V:>7,.0f}M API=${api_bill:>8,.0f} self=${sh_bill:>8,.0f} -> {winner}")
break-even at ~492 M tokens/month
V= 100M API=$ 300 self=$ 1,347 -> API
V= 492M API=$ 1,476 self=$ 1,476 -> self-host
V= 1,000M API=$ 3,000 self=$ 1,644 -> self-host
This finds the monthly volume where self-hosting stops being more expensive than the API. The API is a pure per-token cost (a line through the origin); self-host is a big fixed cost plus a small per-token cost (a line with a high y-intercept and gentle slope). They cross once.
slope_gap = api_price_per_mtok - self_host_per_mtokis how much cheaper each million tokens is once you're self-hosting. If it's zero or negative (if slope_gap <= 0), the API's per-token price already beats your marginal cost and self-host can never win — returnNone.- Otherwise the break-even volume is
gpu_monthly_fixed / slope_gap: the fixed GPU cost divided by the per-million savings tells you how many millions of tokens it takes to pay off the fixed cost. monthly(...)returns both bills at a given volume so you can see who wins where.- The example uses a reserved H100 (~$1,314/month fixed), a $0.33/1M marginal cost, and a $3.00/1M API price.
What the output means: Break-even ~492M tokens/month. Below it the API is cheaper (100M: $300 vs $1,347); above it self-host wins (1,000M: $3,000 vs $1,644).
Try this: Add half an engineer's salary (say +$6,000) to gpu_monthly_fixed and re-run — watch the break-even volume jump far to the right. Ops cost is the thing spreadsheets forget.
Below ~492M tokens/month the single reserved GPU's fixed cost isn't amortized and the API is cheaper; above it self-host pulls ahead, and at 1B tokens/month it's nearly 2× cheaper. But note the trap: that $1,314 fixed cost assumes the GPU is busy enough to serve 492M tokens. If diurnal traffic leaves it 30% utilized, your effective marginal cost triples and the break-even volume moves far to the right. Break-even and utilization are the same conversation.
7 · Capacity planning under a latency SLO professional
This is where the demo number goes to die. Every prior section handed you a clean cost — $0.33/1M at full tilt — and here we watch that number balloon to five times bigger the moment it meets real traffic and a real latency promise. If you've ever presented a serving budget that finance later called "off by 5×," this is the section that explains the gap and, more importantly, closes it before the meeting instead of after.
Two forces you've already met now collide. From section 4 you know you can't just max the batch, because p99 latency is a hard promise to users, not a knob — push the batch past the SLO-safe point and you've broken the product to save pennies. So the throughput you're actually allowed to use per GPU is the SLO-safe rate, deliberately below the raw ceiling. From section 5 you know you must size the fleet to the peak (you can't drop requests at the busy hour), yet the fleet then sits mostly idle at the average hour — so you buy for the peak but run at the average.
Think of it like staffing a restaurant for the Friday-night rush. You hire enough servers that no table waits too long at peak (that's the SLO, and it caps how many tables each server can cover). But those same servers are on the clock — and paid — through the dead Tuesday lunch. Your labor cost per meal isn't set by the busy night; it's set by all the quiet hours you're still paying for. A GPU fleet is identical: sized to peak, billed 24×7, and its true $/token driven by the ratio of average load to peak load.
So the planner does three honest things in order: divide peak demand by the SLO-safe per-GPU throughput (plus a headroom margin) to get the fleet size; compute the real average utilization from the average-to-peak ratio, which is low precisely because you sized to peak; then feed that utilization — not 100% — into the section-3 cost formula. The result is a $/token that already includes the idle Tuesday hours. The mistake this kills is quoting the peak-throughput cost as if the GPU ran hot all day; the planner instead surfaces the ugly-but-true number, and the levers to shrink it (autoscale down at night, drain batch work into the troughs, serve peaks on on-demand/spot rather than reserving for them) are exactly the ones you can defend to a finance team.
You cannot simply max out the batch to minimize cost, because p99 latency is a hard constraint (IC4). Capacity planning is: given a peak request rate and a per-GPU throughput that is only valid while you stay under the SLO, how many GPUs do you need — and how much does that fleet cost per million tokens at the utilization the peak/average ratio implies? The planner below sizes the fleet to peak, then reports the honest cost at the resulting average utilization.
Read the top row left-to-right, then drop down: the top row sizes the fleet, the bands below show how full it actually runs, and the bottom box is the honest cost that falls out.
- The top row is the sizing arithmetic: peak demand (9000 tok/s) divided by the SLO-safe per-GPU throughput (1500 tok/s/GPU, deliberately below the batch ceiling) gives the fleet — 8 GPUs. You must size to the peak because you cannot drop requests at the busy hour.
- The wide red band is that fleet, billed for all 24×7 hours; the small green slice is the fraction it is actually busy — average load is only 40% of peak, so it averages ~30% utilization.
- The bottom red box is the payoff: feeding that real ~30% utilization into the §3 cost formula yields ≈ $1.85/1M — more than 5× the $0.33 the full-utilization demo promised, purely because you buy for the peak but run at the average.
In short: Fleet size comes from the peak and the SLO; the honest $/token comes from the low utilization that sizing-to-peak forces on you.
capacity.pyimport math
def plan_capacity(peak_tokens_per_sec, per_gpu_tps_at_slo, avg_to_peak_ratio,
gpu_hourly, headroom=1.2):
"""Size a GPU fleet to a peak load while respecting a latency SLO.
per_gpu_tps_at_slo : tokens/sec/GPU you can sustain *without* breaching p99
(well below the throughput ceiling from batch_curve).
avg_to_peak_ratio : average load / peak load (drives real utilization).
headroom : over-provision factor for safety (1.2 = 20% slack).
"""
needed = peak_tokens_per_sec / per_gpu_tps_at_slo * headroom
gpus = math.ceil(needed)
# Average utilization = (avg load) / (provisioned capacity)
provisioned_tps = gpus * per_gpu_tps_at_slo
avg_tps = peak_tokens_per_sec * avg_to_peak_ratio
utilization = avg_tps / provisioned_tps
monthly_fixed = gpus * gpu_hourly * 24 * 30
cost_per_mtok = gpu_hourly / (per_gpu_tps_at_slo * 3600 * utilization) * 1_000_000
return {"gpus": gpus, "avg_util": round(utilization, 3),
"monthly_$": round(monthly_fixed), "$_per_mtok": round(cost_per_mtok, 2)}
plan = plan_capacity(peak_tokens_per_sec=9000, per_gpu_tps_at_slo=1500,
avg_to_peak_ratio=0.4, gpu_hourly=3.00)
print(plan)
{'gpus': 8, 'avg_util': 0.3, 'monthly_$': 17280, '$_per_mtok': 1.85}
This sizes a GPU fleet to a traffic peak while respecting a latency SLO, then reports the honest cost at the utilization that peak-vs-average traffic actually produces — usually far worse than the 100%-utilization demo number.
needed = peak / per_gpu_tps_at_slo * headroomdivides the peak load by the throughput each GPU can sustain without breaching p99 (well below the raw ceiling), times a safety headroom.math.ceilrounds up to whole GPUs.provisioned_tps = gpus * per_gpu_tps_at_slois the capacity you're paying for. Average load is onlyavg_to_peak_ratioof the peak, so realutilization = avg_tps / provisioned_tpsis low — you bought for the peak but mostly run below it.monthly_fixedbills every GPU 24×30 hours regardless of load.cost_per_mtokreuses the section-3 formula but with the real utilization, so the returned $/token reflects the idle hours you're paying for.
What the output means: 8 GPUs, ~30% average utilization, ~$17,280/month, and $1.85/1M tokens — over 5× the $0.33 the full-utilization estimate promised, purely because you size to peak but run at average.
Try this: Raise avg_to_peak_ratio toward 1.0 (flatter traffic) and watch cost fall toward the $0.33 floor. That's the payoff of autoscaling, draining batch work into troughs, or serving peaks on on-demand/spot instead of reserving for them.
Sizing to a 9,000 tok/s peak at a SLO-safe 1,500 tok/s/GPU (well below the batch ceiling) needs 8 GPUs with 20% headroom. But because average load is only 40% of peak and we hold 20% headroom, the fleet averages just 30% utilization — so the real cost is $1.85/1M tokens, more than 5× the $0.33 the full-utilization spreadsheet promised. That gap between the demo number and the SLO-and-diurnal number is exactly what separates a capacity plan that survives contact with production from one that doesn't. To close it: autoscale down at night, drain batch work into the troughs, or serve the peak on on-demand/spot instead of reserving for it.
8 · The tech-lead's serving-cost model tech-lead
Everything so far has been a piece; a lead's job is to hold all the pieces at once and hand finance a single number they can trust. The failure mode here isn't a wrong formula — you've mastered the formulas — it's presenting a $/token that quietly assumed the best case, then owning the fallout when reality delivers the worst. A tech-lead is the person in the room who states the assumption out loud before anyone commits budget to it. That habit, more than any equation, is what this section is really teaching.
Think of it as the difference between a mechanic and a foreman. Each earlier section taught you a tool: measure tokens/sec/GPU, convert to $/1M, find the batching knee, expose utilization, compute break-even, plan capacity under the SLO. A foreman doesn't just own each tool — they own the sequence and the caveats: measure real throughput and real utilization (never the datasheet); quote every optimization in $/1M so batching, quantization, and parallelism compare apples-to-apples; compute the API break-even with ops cost folded in; size to the p99 SLO rather than the throughput ceiling; and pick the reserved/on-demand/spot mix that matches the traffic shape. String those together and the output is a defensible build-vs-buy recommendation, not a spreadsheet cell.
The one non-negotiable running through all of it is stating the utilization assumption out loud, because — as every section has hammered — that is the number that silently breaks naive estimates. A cost of "$0.33/1M" is a lie by omission; "$0.33/1M at 100% utilization, which we'll actually hit ~35% of, so budget ~$0.95" is a number a business can plan around. The habit is cheap and the credibility it buys is enormous.
One practical honesty note the code that follows makes concrete: in the real world you don't hard-code the GPU-hour price, you pull it live from the cloud's pricing API so your model tracks the market. That snippet is genuine infrastructure code — it needs cloud credentials and is explicitly not run by this lesson's offline labs — but it's the shape of how a production cost model stays honest as prices drift. Treat it as the seam where the teaching model meets the real billing system.
A lead owns the whole model end to end: measure real tokens/sec/GPU and real utilization (never trust the datasheet), quote everything in $/1M tokens so batching, quantization, and parallelism can be compared apples-to-apples, compute the API break-even honestly with ops cost folded in, size capacity to the p99 SLO rather than the throughput ceiling, and pick the reserved/on-demand/spot mix that matches the traffic shape. The output is a defensible build-vs-buy recommendation and a $/token you can put in front of finance — with the utilization assumption stated out loud, because that is the number that quietly breaks every naive estimate.
Read left-to-right: the stacked tools on the left flow into a decision gate in the middle, which branches to one of two outcomes on the right.
- The left column of blue boxes is every tool this chapter built — tokens/sec/GPU, the $/1M conversion, the batching knee, utilization, break-even, and SLO capacity. A lead holds all six at once, each quoted in the same $/1M unit so they compare apples-to-apples.
- The purple gate in the middle is the one non-negotiable: is the utilization assumption stated out loud?
- The two right-hand boxes are the payoff branch — skip the gate (red) and you quote a best-case "$0.33/1M" that reality triples; pass it (green) and you hand finance a number like the amber caption below, which they can actually budget around.
In short: The formulas are the easy part; the lead's real job is the sequence plus stating the utilization assumption before anyone commits budget.
For live pricing you would pull GPU-hour rates from the cloud pricing API rather than hard-coding them. The snippet below sketches that; it is real infrastructure code and is not part of the offline labs above.
live_pricing.py# ▶ needs cloud creds — real infra, NOT run by this lesson's offline labs.
# Requires: pip install boto3, configured AWS credentials, pricing:GetProducts.
import boto3, json
def ondemand_gpu_price(instance_type="p5.48xlarge", region="US East (N. Virginia)"):
pricing = boto3.client("pricing", region_name="us-east-1") # Pricing API lives in us-east-1
resp = pricing.get_products(
ServiceCode="AmazonEC2",
Filters=[
{"Type": "TERM_MATCH", "Field": "instanceType", "Value": instance_type},
{"Type": "TERM_MATCH", "Field": "location", "Value": region},
{"Type": "TERM_MATCH", "Field": "tenancy", "Value": "Shared"},
{"Type": "TERM_MATCH", "Field": "operatingSystem", "Value": "Linux"},
{"Type": "TERM_MATCH", "Field": "preInstalledSw", "Value": "NA"},
],
MaxResults=1)
product = json.loads(resp["PriceList"][0])
terms = product["terms"]["OnDemand"]
dim = next(iter(next(iter(terms.values()))["priceDimensions"].values()))
hourly = float(dim["pricePerUnit"]["USD"])
gpus_per_instance = 8 # p5.48xlarge = 8x H100
return hourly / gpus_per_instance # -> feed into cost_per_million_tokens()
# print(round(ondemand_gpu_price(), 2)) # ~ per-GPU-hour, then run the offline model above
Exercise MS5.1 — Quote your stack in $/1M tokens
Context: Quoting a stack honestly means quoting it at the utilization you will actually run at, and marking the batch past which more batching buys almost nothing.
Your task: Measure aggregate output tokens/sec/GPU at a batch that stays under a p99 TPOT SLO, compute $/1M at 100%, 50%, and your honest average utilization, and mark the batching knee.
Requirements:
- Choose a batch that holds a p99 TPOT SLO you set
- Report $/1M at 100%, 50%, and honest-average utilization
- Find the knee: the batch past which batching saves less than a cent per 1M
- Report the three cost numbers and the knee batch
💡 Hint: The three utilization figures show the true cost band; the knee is where the throughput curve flattens.
Exercise MS5.2 — Break-even and a capacity plan
Context: The self-host-vs-API decision moves once you fold in the people cost, and the final fleet size falls out of your peak rate, an SLO-safe throughput, and an honest avg/peak ratio.
Your task: Find the self-host vs API break-even, re-run it with half an engineer's monthly salary folded into the fixed GPU cost, size the fleet from your peak rate, and explain why your $/1M exceeds the 100%-utilization number.
Requirements:
- Compute the break-even volume, then re-compute it with salary in the fixed cost
- Note how far the break-even moves once people cost is included
- Size the fleet from peak rate, SLO-safe per-GPU throughput, and an avg/peak ratio
- Explain why real $/1M beats the 100%-util figure and name the lever you'd pull
💡 Hint: Folding fixed cost in raises the break-even; the gap to the 100%-util price is utilization, and autoscaling / spot / trough-batching are the levers.
An H100 rents for $3/GPU-hour and sustains 2,500 output tokens/sec. Your teammate quotes $0.33/1M tokens. Six months later finance says the real bill is ~$1/1M tokens even though the GPU-hour price and the model never changed. What single number explains the whole gap, and why?
Show answer
You're at batch 32 (near the knee: ~1,600 tok/s, $0.52/1M). A PM asks you to push to batch 128 "to cut cost." Using the curve from section 4, is that a good trade? What does it actually buy and cost?
Show answer
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Serving cost tracks throughput per GPU, not raw speed, so the metric that turns into dollars is tokens/sec/GPU — and by it, a bigger cluster can be the worse deal.
Your task: Write tokens_per_sec_per_gpu(total_tps, gpus) and rank three configs by it.
Requirements:
- Normalize total throughput by GPU count
- Rank the configs best-first on the normalized number
- Show more total tok/s can still be worse per GPU
- Frame tok/s/GPU as the number that becomes cost
💡 Hint: Raw tokens/sec flatters big clusters; divide by GPUs before comparing.
Show solution
Normalize throughput by GPU count so configs are comparable. Runnable:
def tps_per_gpu(total_tps, gpus):
return total_tps / gpus
configs = [("A: 1 GPU", 1200, 1), ("B: 4 GPU", 4000, 4), ("C: 8 GPU", 9600, 8)]
ranked = sorted(configs, key=lambda c: tps_per_gpu(c[1], c[2]), reverse=True)
for name, tps, g in ranked:
print(f"{name:>10}: {tps_per_gpu(tps, g):7.1f} tok/s/GPU")
# C (1200) = A (1200) > B (1000): more total speed can still be worse per GPU
Raw tokens/sec flatters big clusters; tokens/sec/GPU is what turns into dollars.
Context: Self-host economics collapses to one conversion line, and everything else — batching, utilization — just moves the tokens/sec that goes into it.
Your task: Build the $/1M calculator (= gpu_hour / 3600 / tokens_per_sec × 1e6) and compare a self-host config to a $3/1M API price.
Requirements:
- Cost per token = price per GPU-second ÷ tokens per second
- Scale to a million tokens for the quoted figure
- Compare at least one self-host config against the $3/1M API
- Report whether self-host is cheaper or pricier at that throughput
💡 Hint: Only tokens_per_sec varies with your engineering choices; the rest of the line is fixed by the GPU price.
Show solution
Cost per token = price per GPU-second ÷ tokens per second; scale to 1M. Runnable:
def cost_per_million(gpu_hour, tokens_per_sec):
per_sec = gpu_hour / 3600.0
per_token = per_sec / tokens_per_sec
return per_token * 1_000_000
for name, hr, tps in [("A100 @ 900 tok/s", 2.0, 900),
("H100 @ 2500 tok/s", 4.0, 2500)]:
c = cost_per_million(hr, tps)
print(f"{name:>18}: ${c:5.2f}/1M vs API $3.00 -> "
f"{'cheaper' if c < 3 else 'pricier'} to self-host")
The whole self-host economics collapses to this one line — everything else (batching, utilization) just moves tokens_per_sec.
Context: Batching raises throughput by amortizing weight loads, but with diminishing returns up to a memory/latency knee — and the knee, not the max batch, is the operating point.
Your task: Model tokens/sec as a saturating function of batch size, convert to $/1M, and show cost falls then flattens as batch grows.
Requirements:
- Throughput saturates toward a peak as batch grows
- $/1M is inversely proportional to throughput
- Tabulate batch, tok/s, $/1M, and the marginal saving per doubling
- Show the marginal saving shrinks each doubling — that is the knee
💡 Hint: Past the knee you pay latency (bigger batches wait longer) for pennies of savings.
Show solution
Model throughput as saturating in batch; cost is inversely proportional. Runnable:
def throughput_at_batch(b, peak=4000.0, half=8.0):
# saturating curve: approaches `peak` tok/s, half-saturation at batch=half
return peak * b / (b + half)
def cost_per_million(gpu_hour, tps):
return (gpu_hour / 3600.0 / tps) * 1_000_000
print("batch tok/s $/1M marginal $/1M saved")
prev = None
for b in (1, 4, 8, 16, 32, 64, 128):
tps = throughput_at_batch(b)
c = cost_per_million(4.0, tps)
delta = "" if prev is None else f"{prev - c:6.3f}"
print(f"{b:>5} {tps:8.0f} {c:7.3f} {delta}")
prev = c
# savings shrink each doubling -- the knee is where marginal savings stop mattering
Past the knee you pay latency (bigger batches wait longer) for pennies of savings — the knee, not max batch, is the operating point.
Context: A GPU bills for every hour whether or not it is serving tokens, so utilization is the hidden multiplier: effective cost is the raw cost divided by how busy the card actually is.
Your task: Model effective $/1M as raw $/1M ÷ utilization and show that 40% utilization nearly triples the true cost.
Requirements:
- Utilization divides throughput, so it multiplies cost
- Effective cost =
raw_cost / utilization - Tabulate several utilizations from 100% down
- Show ~40% utilization is roughly 2.5× the sticker cost
💡 Hint: Idle GPU-hours are pure loss; this is the number that lets an API's pooled utilization beat a self-host quote.
Show solution
Utilization divides throughput and so multiplies cost. Runnable:
def effective_cost(raw_cost_per_m, utilization):
return raw_cost_per_m / utilization
raw = 1.78 # $/1M at full tilt
for u in (1.0, 0.7, 0.4, 0.2):
print(f"utilization {u:>4.0%}: true cost = ${effective_cost(raw, u):5.2f}/1M")
# 40% util -> 2.5x the sticker cost -- idle GPUs are the silent budget killer
This is why a "cheap on paper" self-host loses to an API: the API bills only used tokens, while your idle GPU-hours are pure loss.
Context: Self-host carries a fixed monthly GPU cost regardless of volume while an API is pure per-token, so there is a break-even volume above which self-host amortizes and below which the API wins.
Your task: Find the monthly token volume where self-host becomes cheaper than a per-token API price.
Requirements:
- Fixed monthly cost =
gpus × gpu_hour × 24 × 30 - Break-even volume =
fixed_cost / api_price_per_token - API is cheaper below break-even, self-host above
- Note low utilization pushes break-even higher (idle hours buy fewer real tokens)
💡 Hint: It is fixed cost divided by API price-per-token; utilization erodes the tokens the fixed bill actually buys.
Show solution
Break-even = fixed monthly GPU cost ÷ API price/token: above that volume the API bill would exceed the fixed fleet cost. Runnable:
def break_even_tokens(gpus, gpu_hour, api_per_million):
# self-host bills a fixed monthly GPU cost regardless of volume;
# the API bills per token. Break-even is where API cost = fixed cost.
fixed_month = gpus * gpu_hour * 24 * 30
api_per_tok = api_per_million / 1_000_000
return fixed_month / api_per_tok, fixed_month
be, fixed = break_even_tokens(gpus=2, gpu_hour=4.0, api_per_million=3.0)
print(f"fixed self-host: ${fixed:,.0f}/mo")
print(f"break-even: ~{be/1e6:,.1f}M tokens/mo (API cheaper below, self-host above)")
Below break-even, the API's pay-per-use wins; above it, the self-host's fixed cost amortizes. Low utilization pushes break-even higher, because idle GPU-hours make the fixed bill buy fewer real tokens.
Context: A real capacity plan puts it all together: a latency SLO caps the batch size, which fixes throughput per GPU, which fixes fleet size and budget — with the curves themselves only trustworthy when measured on the target GPU.
Your task: Build a planner that serves X tokens/day under a p95 latency SLO on the cheapest fleet, using the SLO to cap batch size, and label the real measurement 'needs GPU'.
Requirements:
- Find the largest batch whose modeled latency stays under the p95 SLO
- That batch fixes throughput/GPU and therefore daily capacity per GPU
- GPUs =
ceil(tokens_per_day / per_GPU_daily_capacity); report $/1M and $/mo - Note p95 latency and tok/s must be measured on the real GPU under load (needs GPU)
💡 Hint: The SLO is the binding constraint: it picks the batch, and everything downstream — fleet and bill — follows from it.
Show solution
The SLO caps batch (bigger batch = higher latency); that fixes throughput/GPU and thus fleet size and cost. Runnable:
import math
def throughput_at_batch(b, peak=4000.0, half=8.0):
return peak * b / (b + half)
def latency_ms(b, base=40.0, per_item=6.0):
return base + per_item * b # simple linear batch-latency model
def plan(tokens_per_day, p95_ms=800.0, gpu_hour=4.0):
# largest batch under the SLO
b = 1
while latency_ms(b + 1) <= p95_ms:
b += 1
tps = throughput_at_batch(b)
day_capacity = tps * 86400
gpus = math.ceil(tokens_per_day / day_capacity)
cost_m = (gpu_hour / 3600.0 / tps) * 1_000_000
return (f"SLO-capped batch={b}, {tps:.0f} tok/s/GPU, "
f"gpus={gpus}, ${cost_m:.2f}/1M, ${gpus*gpu_hour*24*30:,.0f}/mo")
print(plan(tokens_per_day=5_000_000_000))
# real p95 & tok/s MUST be measured on the target GPU under load -- needs GPU
The planner picks batch, fleet, and budget from the SLO; the latency and throughput curves themselves are only trustworthy when measured on the real GPU under production load (needs GPU).
✓ Checkpoint — you can move on when you can…
- Quote any serving optimization in tokens/sec/GPU and convert a GPU-hour price to $/1M tokens.
- Explain why batching lowers $/token to a knee, and why pushing past it just buys latency.
- State why utilization is the hidden cost and how purchasing mode (reserved/on-demand/spot) plays in.
- Compute the self-host vs API break-even volume and reason about how ops cost moves it.
- Size a GPU fleet to a p99 latency SLO and report the honest $/1M tokens at real utilization.