NInfer-3090 · RTX 3090 · 24 GiB

Fit Calculator

Pick a model, a KV format and a context length; this says whether it fits in 24 GiB, and what it costs you in decode speed and perplexity. Every constant is measured on this fork, not estimated from parameter counts — see the method notes at the foot.

Configure

Result

Memory budget

012 GiB24 GiB
Largest context that fits
—
Decode at this depth
—
Perplexity cost
—

KV formats, measured

FormatBytes / tokenLargest context Perplexityvs bf16Decode @ 32k

Decode speed against cache depth

Decode tokens/second, no speculation. Attention re-reads the whole cache every step, so a format's cost only appears at depth — measuring at a shallow cache would show almost no difference between any of these. Each figure is the mean of three timed repetitions after one warm-up.

What speculation costs and buys

ModelModeWeightsDecodeAcceptance
Measured at an 8,192-token context with INT8 KV. Speculation is only worth its weight when the draft is accepted often: on the 35B, MTP3 with the draft head accepts 59.4% and pays back handsomely, while DFlash2 on the 27B accepts 10.8% and is a net loss against no speculation at all.

How these numbers were taken

All measurements are from one RTX 3090 (24 GiB, sm_86, CUDA 12.8) on this fork, with ninfer_bench and ninfer-perplexity.

The model is checked against the engine, not just against itself. Asking for a 262,144-token INT8 context on the 27B is refused with minimum Engine runtime reservation requires 9197389568 bytes in addition to 1073741824 bytes of automatic headroom. Adding this page's own terms — 262,144 × 33,792 for the cache, 166,438,656 fixed, 16,384 for the per-token remainder, 159,981,568 of workspace and 12,582,912 of graph allowance — gives 9,197,389,568. An exact match, so the arithmetic here is the arithmetic the engine actually applies.

One difference worth knowing: --kv-capacity auto holds back a further 1 GiB of sizing headroom on top of everything above. The largest context this page reports is what physically fits; the largest the engine will auto-size to is whatever fits with that extra gibibyte also spoken for. Ask for a context explicitly and you get the former.

What this does not model

Single-lane serving only. Concurrency changes the picture: --kv-capacity is a shared pool while --max-context is a per-request cap, so a second lane does not cost twice the memory unless you also want twice the per-request context. Vision in overlay residency costs no resident capacity. Prefill throughput, time-to-first-token and multi-GPU expert offload are all out of scope here — see docs/performance.md.

Speculative modes are now modelled per mode, and cross-checked against the engine. Every term — resident weights, CUDA graph allowance, the fixed and per-token parts of the non-KV sequence block, and the extra cache each backend reserves — is measured separately for each speculation option by scripts/sweeps/speculative-memory-terms.ps1. Two configurations are pinned against the engine's own refusal arithmetic: MTP3 with the draft head on the 27B at a 262,144-token context agrees to 0.044%, and DFlash-3 on the 35B agrees exactly. Before this, the page reused the 12 MiB no-speculation graph allowance and added no extra cache for any mode, which understated that DFlash-3 case by 1,289 MiB.

The two backends behave differently and the page reflects that. MTP buffers extra draft tokens in the KV format, so its cost is a multiplier — +6.30% per token on the 27B, +10.08% on the 35B — identical across storage formats. DFlash v1 instead reserves a fixed 4,096 bytes per token regardless of format, which as a ratio would read as +38.8% on int8 and +71.1% on nvfp4. DFlash2 costs nothing per token and pays entirely in a graph allowance that is itself context-dependent: 288 MiB up to 8,192 tokens and 384 MiB beyond, against 12 MiB with speculation off.

Weight profile. Every model here is measured against the one groupwise-int-quantized weight artifact its download script fetches. The runtime also accepts other weight profiles for the same model family with different resident sizes (see src/runtime/engine/context_cost_defaults.cpp); these figures do not apply to a differently-quantized weight artifact.

Estimates between measured depths are interpolated, and anything past the deepest measured point is extrapolated and marked as such. Both are done in seconds-per-token rather than tokens-per-second, because step time is what grows linearly with cache depth; extrapolating the rate directly would have it fall through zero a few hundred thousand tokens out. Treat an extrapolated figure as the shape of the curve, not a promise.