Theoretical Upper Bounds for LLM Throughput
Note: This post is AI-assisted. I developed and checked the formulation. AI tools helped draft parts of the prose from my working notes, but I reviewed and edited the equations, technical claims, and conclusions. The prose reads as AI-written in places, and I am aware of it. The post is still a work in progress and may contain errors. If you find one, please email me at [email protected].
How fast can a machine serve a large language model? I built Our Models, a database of open models, AI hardware, and audited model profiles, to answer that question for any hardware and model pair. This post derives, from first principles, the math the calculator runs.
The math gives a ballpark ceiling on how many tokens per second a given hardware and model pair can serve, a roofline the pair cannot exceed under a stated set of assumptions. Nothing in it is specific to local machines. The decode bound needs only two numbers from the hardware, memory capacity and memory bandwidth, and the prefill bound adds compute rates for the arithmetic formats used by the runtime, so the same formulas cover a MacBook and a datacenter GPU node. The post focuses on local hardware because local machines are the weak ones, and the weaker the machine, the harder good throughput is to get and the more the ceiling matters. A real implementation lands somewhere below these ceilings.
The argument builds in layers, each created by a problem the previous layer cannot solve. A memory system is two numbers, capacity and bandwidth, and their product turns out to be a natural measure of what the system can fund. That product yields a clean but loose throughput ceiling for batched decoding. The loose ceiling ignores per-session context traffic, so it is too generous for real serving, and repairing it gives the bound the calculator actually uses. The repaired bound is then filled in per architecture by small adapters and checked against real hardware. That covers decode, the phase that generates tokens. A second part runs the same program for prefill, the phase that processes the prompt, where the limiting resource can switch from memory bandwidth to compute.
Interactive figures accompany the derivation. They all use the same toy setup, a 32B-class dense model at 4-bit (18 GB of weights, 6 GB of runtime overhead, 0.26 GB of KV per thousand tokens of context) on a 128 GB machine with 800 GB/s of sustained bandwidth, so the numbers stay comparable from figure to figure.
Four questions
Serving an LLM raises four separate questions, and mixing them up is an easy way to be wrong about a machine.
| Question | What it asks |
|---|---|
| Resident fit | Can the model plus runtime overhead be held in memory at all? |
| Single-session speed | What is the memory-side ceiling for one active conversation? |
| Useful serving throughput | Across many active sessions, how many tokens per second can the device produce while each session stays above a minimum useful rate? |
| Prompt wait | How long does a user wait for the first token while their prompt is processed? |
The third question is the hard one for decode. A machine can fit many sessions in memory and still be too slow per session at that concurrency, so the calculator must report both whether sessions fit and whether the fitting sessions are fast enough to be worth running. The fourth question belongs to prefill, also considers compute, and gets its own part of the post.
The formulas give upper bounds when resource rates are upper limits and memory and compute costs are lower limits. Expected expert routing or measured sustained rates give estimates instead. A real system can fall below a bound for reasons the model deliberately ignores, among them kernel quality, quantization overhead, scheduling, CPU involvement, paging, interconnects, and thermal throttling. The decode bounds also ignore compute, which is safe because dropping the compute constraint only loosens the bound. Prefill is usually compute-bound, so its bounds have to include it. The value of a clean upper bound is that it tells you the best case you are allowed to hope for, and therefore how much room an implementation still has.
Memory power
Before any model-specific detail, ask what a memory system fundamentally offers. When people compare accelerators for local inference they list many specs, from capacity and bandwidth through compute throughput, cache hierarchy, PCIe lanes, and thermals. For the decode phase of autoregressive generation, two of these recur in almost every bound. How much state can the memory hold, and how fast can it move that state? We start there.
Two numbers and their product
Model an idealized memory system by exactly two quantities.
Capacity is a stock, the amount of resident state that can exist at once. Bandwidth is a flow, the amount of state that can be moved per second. They have different units and answer different questions, so any single product of them needs justifying before we rely on it.
Define memory power as
If is in GB and is in GB/s, then is in . Power is meant in the colloquial sense of capability, as in computing power, and no watts appear anywhere in this model. The units look strange, so the rest of this section explains why this particular combination of and is the right scalar.
For a feel of the scale, a 24 GB GPU at 1000 GB/s has . One thing to be careful with throughout is that every memory quantity in a given calculation must use the same unit system. The equations are identical in bytes, GB, or bits, and only the numeric value of changes.
Feasibility theorem
Here is the toy problem that justifies . The models in this formulation are deliberately simple, and they are meant as rules of thumb, useful when deciding on hardware for a model or when checking whether an existing setup is getting the most out of the hardware it runs on.
A pure memory workload is a pair , where is the resident information it must keep alive and is the information flow rate it must sustain. The workload is feasible on the system when the memory can both hold the state and carry the traffic. There are exactly two ways to fail.
The first is a capacity failure. If a workload needs more resident state than the memory can store, it cannot fit, and no scheduling trick repairs that.
The second is a flux failure. If a workload needs more traffic per second than the interface can deliver, it cannot be sustained, and no surplus capacity repairs that.
In the idealized model these two conditions are also sufficient. If and , allocate of state and stream at rate . Therefore the feasible set is precisely the rectangle
whose area is
So memory power has a concrete meaning. It is the measure of the feasible workload region. A workload lives at a point in the plane, the system can serve every workload inside its rectangle and none outside, and the size of that rectangle is .
A caution before building on this. The rectangle is a toy model, and the world is messier in both directions. Real hardware delivers only a fraction of its catalog bandwidth. Even a perfectly sequential read falls short of the spec number, and the achieved fraction depends on the access pattern, on whether enough parallel work is in flight to hide memory latency, and on the hardware itself. The sufficiency claim is idealized too, because the rate a machine reaches depends on which bytes are read and in what order. The bounds below survive this, since real inefficiency only pushes a machine further below its ceiling. The cost lands on comparisons between machines. If one machine sustains 85% of its spec bandwidth and another 60%, ceilings computed from spec numbers make the second machine look better than it really is. Read as the hardware bandwidth limit for a ceiling, or as measured sustained bandwidth for an estimate, and read every comparison in this post as approximate.
A caveat on the metric
One misreading is worth heading off. The product measures the workload-feasibility region, the set of jobs the device can support at an instant. A different question, “how much data can this device hold and transfer over a time ”, has a different answer, namely the resident information plus the information streamed over that window,
This volume can count the same bytes repeatedly. It does not count distinct histories or new information. The feasibility-region reading is the one an inference-throughput bound will need, because serving means keeping sessions resident in memory and streaming data for them.
A first decode bound
The feasibility theorem describes static workloads. Decoding is dynamic, emitting tokens over time. This section turns memory power into a throughput ceiling for batched decoding, and in doing so shows where the product enters a real throughput bound.
A toy decoder
Consider the decode phase of an autoregressive transformer, simplified to the memory system alone. Let
let be the batch size (number of concurrent sequences), and let be the number of decode steps per second. Each decode step emits one token per active sequence, so aggregate output throughput is
Throughput is a product of two things, how many sequences run in parallel and how fast the shared model can be swept, and the memory system caps each factor separately.
Capacity limit
The model weights must be resident once, and every active sequence needs its own KV cache, the per-conversation attention state that grows with context length. So the resident state is , and it must fit.
This is the capacity limit, and it sets the maximum parallelism. It is exactly the condition of the feasibility theorem, with .
Bandwidth limit
In a dense transformer, each decode step must apply the model weights. In the memory-bound idealization, assume applying the weights means streaming of data per step, equal to the resident footprint in this first model. With bandwidth , the step rate obeys
This is the bandwidth limit, and it sets the maximum step rate. It is the condition, with the per-step traffic playing the role of the flux.
Memory-power decode bound
Multiply the two caps. Throughput is parallelism times step rate, and each is separately bounded, so
Substituting and factoring out exposes the memory-power term.
When the model is much smaller than memory, , the correction vanishes and the bound collapses to the memorable form
This is the memory-power decode bound. The product appears because maximum throughput genuinely factors into maximum parallelism times maximum step rate.
The numerator is the machine. The denominator is the workload, the per-session state times the model sweep size. The bound reads as throughput is memory power divided by memory cost per active model-token.
Scope of the bound
The memory-power decode bound governs batched throughput. Set and it degenerates to
so for a single session only bandwidth matters and capacity is merely a fit constraint. That is the correct behavior, and it makes clear what each metric is for.
| Use case | The metric that governs it |
|---|---|
| Single-user local chat | bandwidth , with capacity as a fit gate |
| Largest model that fits | capacity first, then bandwidth |
| Maximum batched decode throughput | memory power |
Memory power is the right scalar precisely for the third row. The next section explains why even that bound is too optimistic.
Missing traffic
The memory-power decode bound assumes the only per-token memory traffic worth counting is the model sweep , shared across the batch. Real decoding also reads each session’s growing KV cache, and that traffic is private, so it does not amortize over the batch. Ignoring it makes the bound promise throughput that long-context serving can never reach. This section introduces the correct per-token accounting and shows the memory-power bound falls out of it as a loose corollary.
Universal resource bound
Step back to the most general statement, which holds regardless of architecture. If each output token costs at least of unavoidable memory traffic and at least of unavoidable compute, and the device delivers at most bytes/s and FLOP/s, then over all setups that fit,
Two rooflines, and the workload lives under the lower of them. For decode I keep only the memory half of the minimum, which gives a looser bound even when compute is the limiting resource,
where is the memory traffic per output token. Everything in the decode part now reduces to estimating honestly. The compute half of the minimum is not discarded, though. It returns as a possible limit in the prefill part, which computes where the two rooflines cross. Dropping it here is also practical. A hardware catalog can collect capacity and bandwidth consistently across consumer and workstation devices, while comparable sustained-compute numbers are much harder to obtain, a price the prefill bound will have to pay.
Bytes per token
The following decode formulas use and private caches without cross-session prefix sharing. Speculation uses below. Split the per-token traffic into the two kinds that behave differently under batching. The model (or active-expert) weights are shared, since one sweep serves the whole batch, so their per-token cost is divided by the batch. The KV/context read is private, since each session reads its own cache, so its per-token cost is not divided at all. With the shared weight traffic per iteration and the private context traffic per output token at active context length ,
The first term shrinks as the batch grows, because more sessions share each weight sweep. The second term does not move. A larger batch does not make any session’s context cheaper to read, and this asymmetry is the main reason long-context serving behaves differently from short-context serving.
Interactive figure: bytes per output token as the batch and the read context change. Enable JavaScript to explore it.
Memory-power bound as a corollary
Treat as an optimistic lower estimate of the true bytes per token, . Dividing by a smaller denominator gives a larger quotient, so substituting keeps the result an upper bound.
Here the maximization runs over batches that fit in memory, with the resident model footprint, the KV memory reserved per session, and the runtime overhead. This is the honest simple bound, bandwidth divided by shared-per-token cost plus private-per-token cost, maximized over fitting batches.
Now recover the previous section’s bound by deliberately throwing information away. Since , dropping it only loosens the denominator.
The capacity constraint caps the batch at , so
This is the memory-power decode bound again. It is what you get by discarding the private context term and using the largest batch that fits. That is why it can sit far above achievable throughput while remaining a true ceiling. The ordering is
The memory-power bound is the orientation line. The KV-aware bound is the one to serve from, and the next section develops it into the operational calculator.
KV-aware bound
This section turns the simple bytes-per-token bound into the model the calculator runs. Three refinements are needed. Context must be split into the part that controls memory and the part that controls speed. The shared weight traffic must be allowed to grow with the batch, which matters for mixture-of-experts (MoE) models, models that route each token through a small subset of their weights. And the batch must be filtered so that we never count concurrency at which every session has become uselessly slow.
Two context lengths
A serving system usually reserves KV space for a long maximum context but reads, on average, a shorter active context. These two lengths drive different parts of the bound, so we keep them separate.
The allocation length controls how much KV memory each session reserves, and therefore how many sessions fit. The read length controls how much context each output token must stream, and therefore per-token cost. Collapsing them into one number either overcharges memory or overcharges speed.
Model quantities
A model contributes five quantities. Two are about fitting, two are about speed, and one is about decoding style.
| Symbol | Role |
|---|---|
| Full resident footprint (for MoE, all resident weights, including the inactive experts) | |
| Shared weight traffic for one iteration that routes tokens in total, evaluated at in ordinary decode | |
| KV/cache memory reserved per session, which controls concurrency | |
| Private context traffic per output token, which controls decode cost | |
| Tokens emitted per session per iteration ( ordinary, speculative) |
The split between and mirrors the split between the two context lengths. Allocation controls how many sessions fit, read controls how fast each one decodes. Note that takes the iteration’s total token routings as its argument, for an ordinary decode iteration, for a reason the adapter section explains. Prefill will later feed the same function far larger token counts.
Memory-fit batch
The first gate is whether sessions fit. Load the model, reserve overhead, and divide the remainder by the per-session allocation.
This is memory-fit concurrency only. It is necessary but not sufficient, and it is exactly the trap that makes a machine look like it can serve a hundred sessions when it cannot serve them usefully.
Interactive figure: how many sessions fit in memory as capacity and reserved context change. Enable JavaScript to explore it.
Aggregate and per-session ceilings
The per-token traffic is the shared weight sweep amortized over the emitted tokens, plus the private context read.
Then the memory roofline gives the aggregate ceiling at batch ,
and dividing by the batch gives the per-session rate,
As grows, the aggregate rises but the per-session falls. That tension is the whole serving tradeoff, and it is why a fit-only bound is not enough.
Usable-batch correction
The fix is to refuse batches at which a session would crawl. Impose a per-session floor , the minimum useful tokens/s/session, and solve for . Replacing the batch-dependent by its shared lower bound keeps a closed form. Because , the substitution only weakens the condition, so the implication runs one way, and the closed form is a necessary condition on the admissible batch rather than a sufficient one.
which defines a rate-limited batch
The usable batch is whichever gate binds first.
Because comes from a necessary condition, is itself an upper bound on the truly admissible batch, and the calculator applies the exact floor test with the true in the next step. This is what stops the “hundred sessions” illusion. As context grows, grows, so falls quickly even while stays large. The KV slots fit while the useful rate does not.
Interactive figure: aggregate and per-session ceilings against batch size, with the memory gate and the per-session floor. Enable JavaScript to explore it.
The bound the calculator uses
Collecting the pieces, define the usable batch set as the fitting batches that also clear the floor,
and take the best aggregate over that set.
This is the KV-aware bound, the main practical formulation. In words, try every batch that fits, reject the ones too slow per session, and for the rest take bandwidth divided by bytes per output token, keeping the best.
The looser memory-power bound is its corollary, obtained as before by dropping the private term and using the largest fitting batch.
The two stand in a fixed relation, which is the main result of the derivation, written first by name and then in full.
The gap across these terms is the point of the whole derivation. The KV-aware line is the tight, practical bound. The memory-power line shows the memory system’s large theoretical capacity-bandwidth product, and the distance between them comes from private context traffic, expert diversity, and the per-session floor. The final term drops the resident-model factor as well, so the right-hand side is exactly the simplified from the memory-power decode bound, now with and . It is the loosest, most optimistic reading, since the resident model and overhead always claim a real share of .
Single session
Set to recover the latency-style bound for one conversation. There is no batch to amortize the weight sweep over.
and capacity has dropped out except as the gate that decides whether the model fits at all, consistent with the earlier observation that bandwidth governs single-session speed.
Speculative decoding
Speculative decoding lets . A draft model proposes tokens, the target verifies all of them in one forward pass, and is the expected number of tokens emitted, including the correction or bonus token, per session per iteration, bounded as . If the target accepts each draft token with the same conditional probability given all earlier acceptances, the expected count is , with at . A measured marginal acceptance rate alone need not satisfy this assumption. The formulas of the previous sections apply only at , and this section gives its honest accounting, which changes the earlier formulas in both directions.1
The first change charges costs the earlier formulas skip. The draft must be resident, so its footprint and its own cache allocation join the capacity gate, . For a sequential autoregressive draft, proposing tokens sweeps its weights times and reads its own cache times, and every iteration carries that traffic. The sweep of the target grows too, since all candidates route through the model whether or not they survive verification, so the MoE expert count takes routings rather than .
The second change runs the other way and removes an overcharge. Keeping the ordinary cache term at charges once per emitted token, so times per iteration. A verification pass does less work than that, because it scores all candidates against the session’s cache in one sweep, the same way a prefill chunk reads a prefix once for many tokens. The honest private charge is one read per iteration, or per emitted token. The overcharge matters because it pushes the computed ceiling below what a clean speculative implementation can reach at long context, and a bound an implementation can beat has stopped being an upper bound.
Collecting both changes, the traffic per emitted token is
where is the draft’s weight sweep, shared across the batch like the target’s, and is the draft’s per-step cache read, private like the target’s. At there is no draft, , and the formula collapses to the ordinary . For fixed draft costs and length, whether speculation improves this memory-side estimate depends on the acceptance rate, since rises with it while every -proportional cost stays fixed. One more limit applies at large batches. A verification pass pushes tokens through the model at once, which is as much work as a small prefill chunk. The compute roofline of the prefill part below therefore applies to the pass, and the same crossover test decides whether memory or compute limits it. Parallel draft methods such as DFlash need their own draft cost model instead of sequential sweeps.
The two bounds side by side
The gap between the memory-power bound and the KV-aware bound is easiest to see as memory traffic. Below, two copies of the same machine decode side by side. Each board is the machine’s usable memory, with the weights packed into an orange container of equal-sized cells and each session’s blue KV cells packed into a small container of its own.
Each decode iteration must move every byte that its accounting charges, at the same bandwidth on both machines, so the charged cells light up one by one and the board resets when the iteration completes. The left board charges only the shared weight cells, so it resets quickly and its token counter races ahead. The right board also charges the read cells of every session’s context, so its iterations stretch as the batch and the context grow. A real iteration takes milliseconds, so time runs in slow motion here.
Interactive animation: memory-power accounting and KV-aware accounting decoding side by side on the same machine. Enable JavaScript to watch it.
Both boards run on the same silicon at the same bandwidth, and only the bookkeeping differs between them. The left counter is the memory-power accounting at the chosen batch, and the right one is what the KV-aware bound admits once private context reads are charged.
The sliders show the two context lengths at work. The reserved context sets how many cells each session’s container holds and can push the machine past its capacity, so raising it eventually makes the containers stop fitting. The read context sets how many of those cells light up every iteration, and the longer it gets the smaller the weights’ share of each iteration becomes. It follows the reservation at an adjustable fraction, 90 percent by default, with the unread rest capped at 32k, and the sliders for all of this sit under Advanced. Growing the model itself slows both boards down in step, while the orange container eats the room the blue ones need.
The full memory-power bound goes one step further than the left board. It grows the batch until memory is completely full of KV cells, which is exactly what the default maximize batch mode does, and the line under the left board reports that number. Shrinking the reserved context makes it explode.
At a 4k reservation about a hundred sessions fit and the bound climbs past 4,000 tok/s on this toy machine, and at a 1k reservation it would pass 17,000. Those numbers are true ceilings and useless forecasts at the same time. What stops a real machine long before then is reading each session’s context, which is exactly the traffic the right board charges.
Prefill
Everything so far bounds decode, which produces tokens one iteration at a time. Before decode can start, the machine has to push the whole prompt through the model once to build the KV cache. That phase is prefill, and its limits work differently. A decode iteration re-reads the weights for every batch of tokens it emits, while one-shot prefill reads them once and serves every prompt token in the batch with that single sweep. The weight term that dominated the decode denominator nearly vanishes for large prompts, and the limit can move to the compute roofline that the decode part dropped. The common compute rate is , in FLOP/s, and prefill quantities carry a pf subscript throughout this part.
Prefill workload
A prefill batch holds prompts of tokens each, so one iteration processes input tokens. The prompt length is a third length next to the decode part’s two. counted the average context a decode step reads back, while counts the tokens of the prompt being pushed through now. Write for aggregate prefill throughput in prompt tokens per second, matching the decode part’s use of . If the batch takes seconds, then
and is the prompt-processing time. Dividing by the aggregate rate would understate that time by a factor of , since all prompts share the machine, and the two readings agree only at . Queueing, tokenization, sampling, and delivery also contribute to client-observed time to first token and sit outside this model.
Prefill capacity gate
Everything the batch needs has to fit in memory before speed matters. The gate is the decode gate plus a workspace term,
where is the temporary activation memory prefill holds in flight. A runtime that grows the KV cache on demand instead of reserving it replaces with , the cache actually written for an -token prompt. For a conventional KV cache each prompt token writes
bytes, so . This is the per-token form of the KV-allocation formula in the adapter section below. This theoretical bound sets temporary workspace to zero for every batch and chunk size. Leaving it out of the gate is ceiling-safe, because a looser gate only admits more batches and a maximum over more batches can only rise.
Traffic per prompt token
The decode part defined as the shared weight traffic of an iteration with token routings. A one-shot prefill iteration routes tokens, so its sweep is , and each token’s share is . For a dense model the sweep is one full read of the active weights however large grows, which is why prefill costs so much less memory traffic per token than decode. Adding the KV bytes each token writes and the kernel-dependent terms gives a minimal traffic model,
where is the attention kernel’s memory traffic and the traffic from activations. The memory-side ceiling is .
One warning applies to the attention term. In decode, every output token reads the whole cache, so the decode bound charges per token. Prefill does not work that way. All the tokens of one prompt share the same keys and values, and how many bytes actually move depends on the kernel. FlashAttention never writes the full attention matrix out to memory, so this traffic stays small even though the amount of math stays quadratic. The safest loose ceiling drops the two kernel-dependent terms entirely, which only raises and keeps it a true ceiling.
Mixture-of-experts models need one correction. The adapter section below derives the expected distinct-expert count , which applies with , the token routings of the prefill iteration, where ordinary decode fed it . Prefill routes so many tokens at once that approaches under independent uniform routing, so the expected prefill sweep is approximately a full-expert read, excluding parameters in the untied input embedding (zero when tied),
whereas decode at modest batch sizes touches far fewer in expectation. This is an estimate, not a guaranteed minimum sweep.
Compute per prompt token
Every prompt token has to be multiplied through the model’s weight matrices. Let count the parameters in those multiplications, meaning the attention projections and whichever MLP or expert matrices are active, but leaving out embedding lookups and any output projection that runs once per request. This is deliberately narrower than the MoE section’s , which was introduced for weight accounting rather than as a FLOP count. Counting one multiply-add as two FLOPs, the linear layers cost FLOPs per batch.
Attention adds the part that grows with context. In a causal sequence of length , each token attends to itself and everything before it, which makes query-key pairs per layer, and computing both and costs about FLOPs per pair, where is the query-side hidden width and the query-head count. Summing over layers and adding a remainder term for softmax, normalization, rotary embeddings, routing, dequantization, and logits,
and dividing by gives FLOPs per prompt token, with ,
The compute-side ceiling is . For short and medium prompts the weight multiplications dominate and the ceiling is roughly . For long prompts the attention work takes over, growing with per token and with per request, and layers that only attend to a sliding window stop growing once passes the window width , the same split the adapter section below uses for .
One refinement matters when precisions mix, because is really several numbers on one chip. Let be the hardware’s compute rate limit in FLOP/s for arithmetic mode . Each mode specifies the operand and accumulation formats and whether the rate assumes dense or sparse arithmetic. The model and runtime settings select modes and for the linear layers and attention. Weight and cache storage formats alone do not determine these modes. The two compute rates are lookups,
This two-rate form assumes one arithmetic mode for each path. If a path uses several modes, split its FLOP count by mode and charge each part at its matching rate. If a required rate is unknown, report the compute result as unavailable. The time sum below assumes both paths share the same execution units and cannot overlap. A tighter compute floor charges each term at its own rate,
Here is the batch’s attention FLOP count, and and are the rates looked up for the arithmetic used by the linear layers and attention. The calculator adds the two processing times and omits . The single-rate expressions below are the special case . Charging everything at the fastest path’s rate stays a valid ceiling, only a looser one, and the gap opens exactly where attention dominates, at long context. The worked example below charges attention at the BF16 rate for this reason.
Combined ceiling and crossover
Putting the two sides together, prefill at a fixed batch and prompt length obeys
for any batch that passes the capacity gate. In time terms, with total traffic and separate compute totals and , the batch cannot finish faster than the slower of its two jobs,
Which side is the limit comes down to arithmetic intensity, the ratio of math to bytes. At short context, one weight sweep of bytes serves tokens costing FLOPs each, so the two rooflines cross where the sweep time equals the math time, using in this approximation that omits attention,
For a dense model the sweep is the active parameters themselves, so the parameters cancel and . At TFLOP/s, GB/s, and 4-bit weights that is 31.25 tokens, or about 30, so dense prefill turns compute-bound almost immediately. A sparse MoE does not cancel. Prefill sweeps every expert while each token only computes through its active parameters, so
the dense figure times the sparsity ratio. A model with 14 times as many swept as active linear parameters crosses at tokens with those hardware rates, and the worked example below uses different rates and lands between about 2,100 and 8,300 tokens per sweep. So compute is the usual limit for dense prefill, while a sparse MoE processed in modest chunks on bandwidth-poor hardware can stay memory-bound. Large decode batches and speculative verification can also cross this threshold. Dropping compute still gives a looser upper bound.
One formula for both phases
Real servers rarely run a prompt in one piece. Let be how much of the prompt is already cached and how many new tokens this iteration processes, so one iteration handles tokens and the weight sweep , including the MoE expert count , amortizes over all of them. Each new token attends to the cached tokens plus the new ones before it, which makes attention pairs per request, so the compute per new token generalizes to
and an optimistic memory model to
The term assumes the cache of the old prefix is read about once per chunk and shared by the chunk’s new tokens. For full attention, averaged over a prompt split into equal chunks that divide , the prefixes run , so the average prefix is , while the compute average sums to however the prompt is divided, since chunking changes the rereads and the scheduling but never the total attention arithmetic. For sliding windows, average the individual capped prefix reads instead of capping the average prefix. For unequal chunks, sum traffic and FLOPs over chunks and divide by the total prompt-token count.
The earlier one-shot forms are the case , where the reread term vanishes because there is no earlier chunk. At the formula reproduces the decode bound of the first part, up to the KV write term , which the decode formulation drops as negligible next to (a tenth of a megabyte against gigabytes at long context, and dropping it only loosens that ceiling). Speculation has no counterpart before the first output token, but its verification pass is itself a chunk, positions yielding emitted tokens on average, and that is where the speculative section’s division of the cache read by comes from. One formula covers both phases, and the chunk width is what separates them.
Chunk size belongs to the serving software, so a ceiling for a hardware and model pair must range over it. At fixed batch with workspace omitted, the answer is simple. Shrinking the chunk only adds rereads and shrinks the weight amortization, while the total attention arithmetic never changes, so the combined ceiling is largest at and the one-shot case is the true ceiling. A number computed at a fixed smaller chunk is a policy ceiling, the best a scheduler that caps its chunks at can reach, and a one-shot implementation may legitimately exceed it. What pushes real systems toward smaller chunks sits outside the bound, in the workspace and in the waiting decode requests that Sarathi-Serve style scheduling lets cut in between chunks.
TTFT floor
The decode bound rejects batches where a session falls below tokens per second. The prefill counterpart is a budget on the wait for the first token. A setting is now a batch and a chunk size, and the settings that fit in memory and finish within the budget form the set
and the prefill ceiling at prompt length is the best throughput over the admissible settings,
The budget is optional. Without one, the set is simply every setting that fits, and a caller who supplies tightens it without hiding the unrestricted result. At fixed batch with workspace omitted, one-shot prefill maximizes throughput and minimizes the wait at the same time, so the maximum lands at whenever it fits. As with the decode floor, passing the test does not guarantee the budget is met, since scheduling and kernel overhead can still push the real wait past .
Ceilings across prompt lengths
is a ceiling for one prompt length, and prefill has no length-independent speed. Short prompts amortize the weight sweep over fewer tokens, long prompts pay more attention work, and the fastest ceiling can sit at an intermediate length. A summary that avoids asking for takes the range over every prompt length that fits,
where is the model’s context limit, and reports the two ends of the curve,
Both extremes must carry the prompt length where they occur, and is still an upper ceiling on throughput at its length, never a lower bound on what real software achieves.
Ordering of the prefill bounds
The decode part arranged its bounds in one chain, each looser than the last, because every bound lived on the same resource and each step dropped a positive term from the same denominator. Prefill cannot be arranged that way. The memory ceiling and the compute ceiling are incomparable, since neither is smaller everywhere and which one is the limit flips at the crossover . What survives is a chain within each resource. Dropping the kernel-dependent traffic terms loosens the memory side toward a memory-power analogue,
obtained by multiplying the capacity cap on with the sweep-rate cap , and dropping the attention term loosens the compute side toward . The full ordering is
on the memory side and
on the compute side, two independent chains hanging off a shared minimum, a lattice rather than a line. The practical consequence is that prefill has no single loosest bound to serve as an orientation line, and a loose bound from the wrong chain can sit orders of magnitude above the true ceiling. The memory-power analogue above is exactly such a bound. The decode part kept only the memory resource, which collapsed the lattice into a chain.
Model adapters
The KV-aware bound is architecture-agnostic, and an architecture enters only through a short list of quantities. So each model family is captured by a small adapter that supplies them, and the prefill bound extends the same adapter with two compute entries described at the end of this section.
Here means the model with the selected weight storage, cache, and decoding settings. Runtime overhead is supplied separately and is not a hardware property. Capacity is measured before subtracting , so the memory gate subtracts it only once.
Three adapters cover the catalog. They handle dense transformers, mixture-of-experts, and hybrid/sliding/recurrent attention.
Dense transformers
For a dense model with parameters at bytes each, the resident footprint and the per-step sweep need not coincide: an untied input embedding reads selected rows, not the full table. Let count the parameters in that untied input embedding, or zero when it is tied to the output projection. With uniform weight storage and one sweep, omitting the selected embedding rows gives
The resident footprint is approximate because quantization metadata, padding, and mixed tensor precision can change the stored bytes.
With layers, key/value heads, head dimension , and KV byte widths and , full-context attention reserves and reads
where the factor counts keys and values. Weight precision and KV precision are independent settings. NVFP4 weights (NVIDIA’s 4-bit floating-point format) do not imply an NVFP4 cache, so and are tracked separately.
Mixture-of-experts
An MoE model is where the constant- assumption breaks, and fixing it is the single most important adapter correction. Let be total parameters, the active parameters per token, the number of routed experts, and the experts selected per token. For this decomposition, both parameter counts include the full input embedding. Assuming uniformly sized routed experts, each routed expert holds
and the always-on remainder (dense trunk, shared experts, embeddings, attention) is
The naive model assumes a batch touches the same active experts every session, keeping constant. That is false. Independent sessions route to different experts, so a larger batch touches more distinct experts. With token routings, each independently missing a given expert with probability , the expected number of distinct experts touched is
and the per-iteration shared traffic is the fixed part minus the untied input embedding, plus the touched experts. The embedding stays in the resident footprint.
At this reduces to the active-parameter footprint minus the untied input embedding, , and as it saturates at all experts. This rising is why MoE batching does not amortize for free, and why the MoE rows in the worked table reach their throughput optimum at modest batch sizes.
One caveat applies here. Every other traffic term in the bound is a deliberate under-estimate of real traffic, which is what makes a true ceiling. The expert count is different. It is an expectation under independent, uniform routing rather than a lower bound. Real routing is correlated, since load-balancing losses push toward uniform while hot experts and topically similar sessions pull the other way, and correlated routing can touch fewer distinct experts than the formula predicts. In that case the modeled traffic overstates the actual traffic, and the computed ceiling can sit below the true one. When a guaranteed ceiling is required, replace by its minimum , which replaces by . The expectation form gives estimates, including during prefill. The floor form is the safe bound when the active-weight bytes are a valid traffic lower bound.
Hybrid, sliding, and recurrent attention
Models with local or sliding-window attention, compressed or latent attention, or linear/recurrent state must not use the full-KV formula blindly, because their cache does not grow linearly in everywhere. Split both KV terms into global, local, and fixed-state parts.
A simple read approximation with sliding-window width is
Full-attention layers pay for the whole context, sliding-window layers pay only up to the window, and recurrent or latent state adds a fixed or slowly growing term. The same shape covers Gemma-style local/global attention and DeepSeek-style compressed/sparse attention, with only the coefficients changing.
Prefill adapter fields
The prefill bound reuses almost the whole decode adapter. and serve the same capacity gate, is the same function fed the iteration’s token count, reappears as the chunked prefix reread, and the write coefficient is derivable from the allocation fields. Two entries are new. The linear FLOP count prices the weight multiplications, and a layered attention-FLOP function prices the context work for chunk width and prefix length , with the window including the current token,
with the average attended context of the iteration. Each global or local coefficient sums FLOPs per pair over the layers in that group, using each layer’s query width, so this is the FLOP mirror of above, and the same hybrid, sliding, and recurrent splits carry over, with each local length capped before averaging and the state cost supplied by the architecture. One decode caveat still applies here. The MoE adapter had to distinguish the expected expert count from its worst-case floor, and large prefill token counts do not make the expectation a guaranteed minimum. All tokens can still select the same experts. The decode field with no prefill counterpart is . The extended adapter is
Calculator procedure
The ordinary-decode bound () is now ready to compute. Because the same computation runs for every hardware-and-model pair, it is worth stating once as a procedure.
The inputs are a hardware row , runtime overhead , a model adapter , and workload assumptions , plus a prompt length and an optional TTFT budget for prefill. For prefill, the model and runtime also select arithmetic modes for the hardware-rate lookups. Ordinary decode needs no compute rate.
- Compute the resident margin . If it is negative, the model does not fit, so stop.
- Compute and the memory-fit batch .
- For each integer batch , compute and .
- Compute the aggregate ceiling and the per-session ceiling at each batch.
- Keep the batches whose per-session ceiling is at least .
- Among the kept batches, choose the one with the largest aggregate ceiling, and report it as the KV-aware result with its batch as .
- Separately compute the memory-power ceiling for orientation.
For speculation, use and its draft-inclusive capacity gate instead. The output is a stack of gates, and the right phrasing depends on which gate bound.
| State | Meaning |
|---|---|
| Resident fit | The model plus overhead fits in memory |
| Session fit | At least one reserved-context session fits |
| Floor fit | Some fitting batch clears |
| No floor | Sessions fit, but no batch clears |
The common invalid reading is that fitting in memory implies serving usefully. A model can pass resident fit and session fit and still have an empty usable batch set, because every fitting batch is below the floor. The honest report for that case is “fits, but no batch satisfies the floor”, a distinct verdict from a true fit failure. Keeping the two apart is the reason the floor gate exists.
For prefill, first look up and . The prefill computation runs the same way over batch and chunk settings, with in every capacity check. For each fitting , sum traffic over the prompt’s chunks to get , set , and sum over the chunks to get , using the actual width of any shorter final chunk. Compute the time and throughput as
Report the largest with its batch, chunk, both resource ceilings, and the latency floor. At fixed batch with workspace omitted, the search over collapses, since the optimum sits at . The central formula is
the prefill counterpart of the KV-aware decode bound, with a supplied restricting the settings inside the maximum.
Worked examples
Now check the theory against real hardware. Consider two 128 GB machines, one bandwidth-rich and one bandwidth-poor, which isolate the effect of at fixed .
Two machines
NVIDIA’s DGX Spark, a small desktop AI machine, carries 128 GB of LPDDR5x unified memory at 273 GB/s. An Apple M5 Max with a 40-core GPU reaches 614 GB/s and is configurable to 128 GB of unified memory. At equal capacity their memory powers are
| Hardware | |||
|---|---|---|---|
| DGX Spark | 128 GB | 273 GB/s | 34,944 |
| Apple M5 Max 128GB | 128 GB | 614 GB/s | 78,592 |
Both bandwidth numbers are catalog figures rather than measured sustained rates, so the earlier caveat applies. If one machine sustains a larger share of its spec than the other, the comparison will make the other machine look better than it really is.
Three models
Three MoE models, modeled with simplified adapter parameters, without re-measurement.
- Qwen3.6-35B-A3B, with 35B total / 3B active parameters, routed experts, routed (plus one shared) per token, and weights quantized NVFP4.
- Gemma 4 26B-A4B-it, with 26B total / 4B active, , top-8 routing, hybrid local/global attention, and NVFP4 weights.
- DeepSeek V4 Flash (DS4), with 284B total / 13B active, routed plus one shared, per token, million-token context via compressed/sparse attention, and weights at a Q2-style mixed quantization.
All rows use the original Our Models defaults, namely reserved context , active context , per-session floor tok/s/session, ordinary decoding , runtime overhead GB, and the memory roofline only. The numbers are memory-side estimates from the simplified adapters and expected expert routing, not guaranteed hardware ceilings.
The following inputs complete the calculation at those context lengths. They are the original illustrative adapter values, not audited tensor sizes or cache measurements. Use the parameter and expert counts above in the MoE formula, which includes shared experts in . These three illustrative adapters set as an approximation, since their rounded inputs do not separate the embedding. Memory values use decimal GB. Results are rounded only after choosing the batch.
| Model | (bytes/parameter) | (GB) | (GB/session) | (GB/token) |
|---|---|---|---|---|
| Qwen3.6-35B-A3B | 0.5 | 17.5 | 1.025 | 0.3268992 |
| Gemma 4 26B-A4B-it | 0.5 | 13 | 0.618497 | 0.269504 |
| DeepSeek V4 Flash (Q2) | 0.25 | 71 | 0.31410256 | 0.016 |
Results
| Hardware | Model | Single-session estimate | KV-aware aggregate estimate | Memory-power estimate | |
|---|---|---|---|---|---|
| DGX Spark | Qwen3.6-35B-A3B | 149 tok/s | 17 | 346 tok/s | 18.2k tok/s |
| DGX Spark | Gemma 4 26B-A4B-it | 120 tok/s | 16 | 333 tok/s | 23.6k tok/s |
| DGX Spark | DeepSeek V4 Flash (Q2) | 84 tok/s | 7 | 155 tok/s | 13.1k tok/s |
| Apple M5 Max 128GB | Qwen3.6-35B-A3B | 336 tok/s | 50 | 1,008 tok/s | 40.9k tok/s |
| Apple M5 Max 128GB | Gemma 4 26B-A4B-it | 271 tok/s | 66 | 1,323 tok/s | 53.1k tok/s |
| Apple M5 Max 128GB | DeepSeek V4 Flash (Q2) | 188 tok/s | 22 | 448 tok/s | 29.5k tok/s |
Two things stand out. First, with capacity held equal, the higher M5 Max bandwidth lifts the single-session estimates in proportion to , and the batched estimates by even more, because the extra bandwidth also lets more sessions clear the per-session floor. The bandwidth-rich machine wins exactly where the theory says it should, in batched throughput. Second, the memory-power column sits one to two orders of magnitude above the KV-aware column. That gap is the cost of private context traffic and expert diversity, and showing it is the point of the derivation.
Forced concurrency
What if concurrency is fixed by policy rather than chosen at the floor-satisfying optimum? On DGX Spark, pushing past buys aggregate throughput at the cost of per-session rate.
| Model | Batch | Aggregate estimate | Per-session estimate |
|---|---|---|---|
| Qwen3.6-35B-A3B | 32 | 397 tok/s | 12.4 tok/s/session |
| Qwen3.6-35B-A3B | 64 | 482 tok/s | 7.5 tok/s/session |
| Gemma 4 26B-A4B-it | 32 | 434 tok/s | 13.6 tok/s/session |
| Gemma 4 26B-A4B-it | 64 | 581 tok/s | 9.1 tok/s/session |
This is the serving tradeoff in numbers. For DGX Spark under these assumptions, 32 and 64 sessions are estimated to be too high if the goal is around 20 tok/s/session, exactly the regime the usable-batch correction is built to reject, and the reason for these models settles near 16.
A sanity check against a real run
A reported but unverified DGX Spark run served Gemma at concurrency 16 at roughly 16 to 18 tok/s/session, an aggregate of to tok/s. The KV-aware aggregate estimate for Gemma at this batch is 333 tok/s, so the reported throughput is
of the simplified estimate. That ratio alone does not establish proximity to the memory-side roofline. It does not prove the quantization is optimal. The bound omits compute, scheduler behavior, kernel details, and exact cache traffic, and proving optimality would require profiler evidence of bandwidth saturation with no compute, scheduler, or CPU stalls.
Prefill on real hardware
The prefill example uses the DGX Spark again, paired with Poolside’s Laguna S 2.1 NVFP4 checkpoint, because a real prefill benchmark exists for the pair. For , NVIDIA quotes the GB10 chip at 1 PFLOP of FP4 with sparsity, and this example assumes ideal rates of 500 TFLOP/s of dense FP4 and 125 TFLOP/s of dense BF16. The attention math is assumed to run on the BF16 path, so it is charged at 125 TFLOP/s throughout, and only the linear term ranges over the 125-to-500 bracket, since it is not obvious how much of that math runs on the fast path.
The bound needs three things from the model, its sweep size, its linear FLOP count, and its KV write coefficient. The model card, configuration, and tensor index at revision b482b5d57fda6e4e562a652869bde24ba2a57c92 supply the inputs. Laguna S 2.1 has 117.6B total and 8.5B active parameters, 48 layers of which 12 use full attention and 36 use a 512-token sliding window, 48 query heads in full-attention layers and 72 in sliding-window layers, each of dimension 128 (so and , respectively), 256 routed experts with 10 picked per token, and an FP8 KV cache. From those numbers, the 71.9 GB NVFP4 checkpoint carries a swept weight payload of 71.28 GB (71.8987 GB of tensor data minus the 0.6166 GB input embedding), subtracting the embedding and output head from the active parameters leaves B, and the FP8 cache takes 98,304 bytes of writes per prefilled token.
The expert formula decides the memory side. The expected distinct experts already reach 254 of 256 at a 128-token chunk, so the expected-routing model approximates realistic chunks as full sweeps of 71.28 GB, and at 273 GB/s the model gives about 3.8 sweeps per second. A scheduler that caps chunks at 2048 tokens is therefore estimated near tokens per second before prefix rereads. One-shot prefill sweeps once per prompt instead and lifts the memory estimate to 31k tok/s at an 8k prompt, 120k at 32k, and 425k at 128k. The crossover confirms the regime, since for this pair sits between about 2,100 and 8,300 tokens per sweep depending on the compute bracket, so a 2048-token chunk is still memory-bound at the FP4 bracket while a one-shot prompt can pass the crossover.
The compute side uses the layered attention form with 12 full and 36 sliding-window layers. The tables use B and omit . At , the attention cost is GFLOP per token, giving GFLOP per token. The estimate, at batch 1 with one-shot prefill:
| Prompt | per token | Compute estimate | Memory estimate | Combined estimate | Modeled prefill time |
|---|---|---|---|---|---|
| 8,192 | 17.7 GFLOP | 7.1k to 21.5k tok/s | 31k tok/s | 7.1k to 21.5k tok/s | 0.4 to 1.2 s |
| 32,768 | 21.3 GFLOP | 5.9k to 13.2k tok/s | 120k tok/s | 5.9k to 13.2k tok/s | 2.5 to 5.6 s |
| 131,072 | 35.8 GFLOP | 3.5k to 5.2k tok/s | 425k tok/s | 3.5k to 5.2k tok/s | 25 to 38 s |
The 2048-token-chunk policy is the same formula with pinned:
| Prompt | Memory estimate | Combined estimate | Modeled prefill time |
|---|---|---|---|
| 8,192 | 7.8k tok/s | 7.1k to 7.8k tok/s | 1.0 to 1.2 s |
| 32,768 | 7.8k tok/s | 5.9k to 7.8k tok/s | 4.2 to 5.6 s |
| 131,072 | 7.6k tok/s | 3.5k to 5.2k tok/s | 25 to 38 s |
The weight multiplications, 15.8 GFLOP per token, dominate the compute cost until the context gets long. At 128k the attention work reaches 20.0 GFLOP per token and overtakes them, and under the assumed BF16 path it pulls even the high bracket down to 5.2k, where compute is the limit under either policy.
A measured run gives a comparison. A vLLM 0.25.1 NVFP4 benchmark on the GB10 reported 2,348 prompt tokens per second by dividing about 8,192 input tokens by the median 3.49-second time to first token. This includes work outside the prefill kernels. It used DFlash with 15 draft tokens and max_num_seqs=32. With a 2048-token batch budget, vLLM’s scheduler rule reserves draft slots, giving max_num_scheduled_tokens=1600 for chunked prefill. Neither table matches that policy exactly. The earlier 77% to 86% decode comparison used Gemma, not this Laguna workload. These ratios do not measure idle hardware or identify the cause of the gap.
Omitted rooflines
The memory and compute rooflines are now both explicit, and real throughput is still the minimum over further limits,
where the terms this post computes can be undercut by dequantization kernels, attention kernels and KV layout, prefill/decode phase mixing, scheduler overhead and request churn, CPU and PCIe involvement, multi-GPU communication, allocator fragmentation, thermal and power limits, tokenization and sampling, and speculative rejection rates. Each belongs as its own limit term. Prefix sharing can instead reduce traffic and storage. The private-KV formulas here assume no cross-session sharing. The two derived rooflines remain useful because they make the first unavoidable ceilings explicit and cheap to compute.
The compute roofline also carries a weakness the memory one does not. The memory bound remains a true ceiling only when its traffic terms are lower bounds. Expected expert routing gives an estimate, while a ceiling computed from spec-sheet peak FLOP/s is far too high, because real kernels on quantized weights often reach less than half of peak and the fraction changes with kernel and precision. For bandwidth this post could note the sustained-versus-spec gap and move on. For it decides whether the number means anything, and measuring believable sustained FLOP/s across consumer devices is the hard open problem of the prefill extension. Apple silicon illustrates it, since Apple publishes no comparable dense BF16 or FP16 GPU rate, and Neural Engine TOPS or estimates from GPU core counts are no substitute.
One recurring caution applies to models with recurrent or linear state. A model with tiny fixed state and tiny private read traffic produces an enormous memory-side aggregate at high concurrency, because almost nothing in the denominator grows with the batch. That is precisely the signal that compute, kernel, scheduler, and recurrent-state details must be added before the aggregate number is treated as realistic. The memory bound describes the best case the hardware allows, and reaching it is the implementation’s job.
Cheat sheet
This section collects the whole formulation in one place, so it can be read on its own. A machine is two memory numbers and a table of compute rates, a model adapter is five decode quantities plus two prefill ones, and the workload adds its assumptions.
| Symbol | Meaning |
|---|---|
| , | Hardware memory capacity before subtracting overhead, and memory bandwidth |
| Hardware compute rate limit in FLOP/s for mode , including operand and accumulation formats and dense or sparse arithmetic | |
| Runtime memory overhead, subtracted from once | |
| Full resident weight footprint, which must fit in memory | |
| Shared weight traffic for an iteration of token routings, in ordinary decode, equal to for dense models and growing with for MoE | |
| Active-weight sweep size, the lower bound | |
| KV memory reserved per session | |
| Private context traffic per output token | |
| Tokens emitted per session per iteration, one for ordinary decoding | |
| , , , | Draft length, draft weight sweep, draft per-step cache read, and draft cache allocation for speculative decoding |
| , | Reserved and average read context, |
| Minimum useful tokens/s per session | |
| , | Arithmetic modes selected by the model and runtime for linear layers and attention |
| , | Derived rates and in FLOP/s |
| Common compute rate in the single-rate derivation | |
| Temporary prefill workspace, set to zero in this theoretical bound | |
| , , | Prompt length, chunk width, and cached prefix length in prefill |
| KV bytes written per prompt token | |
| , | Active linear parameters, and the query-side hidden width |
| Transformer layer count | |
| Optional TTFT budget for the prefill floor |
Everything descends from the two rooflines, for a token costing bytes of traffic and FLOPs. These are bounds when resource rates are upper limits and costs are lower limits. Expected expert routing or measured sustained rates give estimates instead. The ordinary-decode formulas here use and keep only the memory constraint,
The first gate is whether sessions fit. Load the weights, reserve the overhead, and divide what is left by the per-session KV allocation.
Per-token traffic is the shared weight sweep amortized over the batch plus the private context read, which no batch size amortizes.
The roofline gives the aggregate ceiling at batch , and dividing by the batch gives the per-session rate. The aggregate rises with while the per-session rate falls.
Imposing the floor rejects the batches where every session crawls, and the usable batch is whichever gate binds first. The closed form below uses as a lower bound and requires positive . For MoE it is a necessary condition, and the full batch test below is still required.
The usable batch set holds the batches that fit and clear the floor, and the KV-aware bound is the best aggregate over it.
Setting in the same formula gives the single-session ceiling. Dropping the private context term and taking the largest fitting batch gives the looser memory-power ceiling,
With speculation the private read divides by , because one verification pass reads the cache for all emitted tokens, and the draft adds its own costs,
with the draft’s residency and cache joining the memory gate. For ordinary decoding with upper resource limits and lower costs, the three levels order the same way.
Prefill runs on the same adapter plus and , looked up from the hardware table using the selected arithmetic modes, with in the capacity gate. If a required rate is unknown, the compute result is unavailable. Per prompt token, an iteration of chunk width over a cached prefix moves
bytes of memory traffic and spends
FLOPs of compute. For sliding and recurrent layers, replace the full-attention term with above. Sum traffic over the prompt’s chunks to get , set , and sum over the chunks to get , using the actual width of any shorter final chunk. Assuming one arithmetic mode per path and that both paths share the same execution units and cannot overlap, charge each compute total at its own rate and add the times. Split a path’s FLOPs by mode if it uses several modes.
When both compute rates equal , the sum of the processing times is , with . The ceiling at prompt length maximizes throughput over settings that pass the same memory gate,
with the maximum at one-shot at fixed batch when workspace is omitted, fixed-chunk evaluations serving as policy ceilings, and a supplied TTFT budget restricting the settings through the wait . Ordinary decode uses the memory formula with the KV write term omitted, the rooflines cross near tokens per sweep when attention is omitted, and a range over prompt lengths reports and with the length at each extreme. With both resources included, decode and prefill’s loosened bounds form two chains hanging off the shared minimum, one per resource, so no single orientation line exists.
When resource rates are bounded above and per-token costs below, these formulas give an upper bound built from memory capacity, memory bandwidth, and for prefill compute throughput. Expected-routing calculations are estimates. Real implementations under the stated assumptions land below a valid bound, and kernel quality, software overhead, and interconnects can only lower the ceiling further.
When reporting a result, always state the assumptions that move it, namely , , , , , the weight precision, and the KV-cache precision or attention adapter, and for prefill also the prompt length, the chunk mode, , , their hardware rate entries and sources, and . State whether the calculation uses resource limits or measured rates and minimum or expected expert traffic. Without them, a single tok/s number is not reproducible.
To see these bounds computed live for hundreds of audited model profiles against a catalog of local hardware, try Our Models. An independent implementation of the decode bound runs at the Localmaxxing decode calculator.
-
This section was updated based on feedback from Lotto’s Localmaxxing decode calculator. His calculator charges the draft model’s costs explicitly, and comparing against it surfaced both the missing draft terms and the cache-read overcharge fixed here. ↩
Theoretical Upper Bounds for LLM Throughput
July 14, 2026
How fast can a given machine serve a large language model? This post derives upper bounds on inference throughput from hardware specs and model parameters. It turns out that decode is usually limited by memory. Namely, the product of memory capacity and memory bandwidth yields a loose bound on the maximum possible throughput. Taking the KV cache into account then brings the bound much closer to measured values. Whereas decode is usually limited by memory, prefill can be limited by memory and/or compute. We provide closed-form formulas for all of these bounds. We also introduce architecture-specific formulations that serve as model adapters, enabling efficient, ballpark prediction of inference throughput at scale.