KV pool: kv_cache_memory_bytes vs gpu_memory_utilization

The KV pool is a number of tokens, not a number of gigabytes. vLLM divides the bytes you give it by what one token costs in every attention layer. Bytes per token range from 5,987 (modeled) to 55,011 (measured) across the models on this panel, so the same 10 GB can hold 181,777 tokens or 1.67 million.

Two ways to size it

gpu_memory_utilizationkv_cache_memory_bytes (a pin)
What you statefraction of the card vLLM may use in totalthe KV pool itself, in bytes, per GPU
KV poolwhat is left of the fraction after weights, activations, CUDA graphsexactly the pin
Changes whenweights, the prefill budget, graph sizes or seat count changenever, unless you change it
Start checkvLLM refuses a start if fraction × card > free memorythe same check still runs on the fraction

The pin wins on a shared or unified-memory box: the pool stays the same size from one start to the next, and the rest of the budget is the operator’s to read.

The fraction still matters with a pin. On the RTX 5090 node vLLM refuses any start where fraction × 31.36 GiB exceeds the 30.85 GiB free at start, so 0.99 and 1.0 can never start; 0.9837 is the ceiling (measured 2026-10-11/12). That node runs a 7.92 GiB pin (8,501,810,176 B) beside a fraction of 0.98.

Reading the startup line

vLLM prints two figures:

GPU KV cache size: 181,777 tokens
Maximum concurrency for 131,072 tokens per request: 1.39x

A second case, 2026-09-30: Qwen3.8 Flash-Next on the same pair, 28e9 B pin, 262,144 context, 4 speculative tokens. vLLM reported 1,708,119 tokens and 6.52x. At 3 and 2 speculative tokens the same pin gave 1,752,822 and 1,791,558: the drafter’s KV comes out of the same pool.

Bytes per token, by model

KV bytes per token per rank: DeepSeek-V4-Flash 55,011 measured; Qwen3.8 27B dense on vLLM 36,860; Qwen3.8 27B dense on SGLang 21,502; Qwen3.8 Flash-Next 16,392; GLM-5.3-Flash 6,973 from a published run; DeepSeek-V4-Flash compressed layout 5,987 modeled KV bytes per token, per rank (7 px per 1,000 B) DeepSeek-V4-Flash · fp8_ds_mla 55,011 Qwen3.8 27B dense · vLLM bf16 36,860 Qwen3.8 27B dense · SGLang fp8 21,502 Qwen3.8 Flash-Next · vLLM bf16 16,392 GLM-5.3-Flash · fp8 MLA 6,973 published run DeepSeek-V4-Flash · compressed 5,987 modeled Solid: measured on 2x DGX Spark (GB10), TP=2. Grey: not measured here.
Each model in the KV type it launches with. Qwen3.8 rows are hybrid models: 16 of 64 layers (27B) keep a per-token KV entry.
Model, engine, KV typeB / token / rankTokens in 10 GBSource
DeepSeek-V4-Flash NVFP4, vLLM, fp8_ds_mla55,011181,777measured, gen 234/235, 2026-10-24
Qwen3.8 27B dense NVFP4, vLLM, bf1636,860271,297measured, gen 103/104, 2026-10-02
Qwen3.8 27B dense NVFP4, SGLang, fp8 (target + drafter pools)21,502465,073measured, gen 123, 2026-10-03
Qwen3.8 Flash-Next MoE NVFP4, vLLM, bf1616,392610,054measured, 2026-09-15 / 09-30
GLM-5.3-Flash NVFP4, vLLM, fp86,9731,434,103a published 2x DGX Spark run, not measured here
DeepSeek-V4-Flash, compressed layout~5,987~1,670,000modeled, not run

"Tokens in 10 GB" = 10e9 ÷ bytes per token, arithmetic. Only the DeepSeek row was run at 10e9.

DeepSeek-V4-Flash on 2x DGX Spark

nvidia’s NVFP4 checkpoint, vLLM nightly 2026-10-15, TP=2, 8 seats, 131,072 context, prefill budget 8,192, drafter off.

GenPinPool, tokensFull 131,072 contextsOutcome
23416e9 B (14.90 GiB)290,8582.22killed in warmup: 7.9 GiB available, under the 8 GiB floor
23510e9 B (9.31 GiB)181,7771.39serving; init 357 s; 28.8 tok/s at 1 stream
23710e9 B181,7771.39serving

The plan, written before the first start, expected 13,952 B/token, read off two published 2x DGX Spark pools of the same model. At that figure 16e9 B would have held ~1.15 M tokens, enough for all 8 seats at full length. The measured figure is 3.9x larger.

Where the memory went, per rank

DeepSeek-V4-Flash per-rank memory on a DGX Spark, 117.2 GiB available before the start. 10e9 pin: weights 79.29, KV 9.31, overhead 13.4, 15.2 GiB left at idle. 16e9 pin: weights 79.29, KV 14.90, overhead and warmup 15.1, 7.9 GiB left, under the 8 GiB guard floor, start killed GiB per rank, of 117.2 available before the start (4.5 px per GiB) 10e9 pin, idle weights 79.29 KV 9.31 other 13.4 15.2 free 16e9 pin, warmup weights 79.29 KV 14.90 other 15.1 7.9 8 GiB floor Row 1 measured (gen 235). Row 2: weights and KV are the start's own; 7.9 GiB is the guard's reading; "other" is the remainder, assuming the same 117.2 GiB before the start (gen 234's own pre-start figure is not recorded).
Per rank, 2026-10-24. "Other" = b12x workspaces, CUDA graphs up to 48, engine overhead.

On a DGX Spark the GPU and the OS share one 128 GB LPDDR5X pool. A start that allocates a bigger KV pool takes it from the same memory the OS, the page cache and the warmup kernels need. The start’s memory guard kills a launch if available memory stays under 8 GiB for 3 polls 2 s apart, before the engine serves; a healthy serving pair settles near 4.8 GiB, so the floor applies only during the start. Gen 234 hit 7.9 GiB in the b12x warmup and was killed before it could serve.

With the drafter on, the start estimate needs 115.1 GiB at a 10e9 pin against ~114.5 GiB free: the pin has to drop to 9e9 or less (8e9 leaves a margin). The fuller budget is on memory budget.

Why 55,011 bytes

Analysis, not a log line. DeepSeek-V4’s attention is designed to keep most of its KV compressed along the sequence. The measured 55,011 B/token matches caches allocated at full length and uncompressed for every layer. With the design’s hybrid, compressed layout the same model would need about 5,987 B/token, which at the same 10e9 B pin is about 1.67 M tokens, 12.7 full 131,072 contexts. That figure is modeled from the architecture; no build here has run it. Gen 237 logs no hybrid-allocator or padding line at INFO, so the log cannot confirm the cache groups either way.

DeepSeek-V4-Flash KV pool tokens per rank: 181,777 at a 10e9 pin measured; 290,858 at 16e9 measured but killed; 1,048,576 needed for 8 seats at 131,072; about 1,670,000 at 10e9 with the compressed layout, modeled KV pool, tokens per rank (1 px per 4,000 tokens) 10e9 pin, measured 181,777 1.39 contexts 16e9 pin, killed 290,858 2.22 needed: 8 seats × 131,072 1,048,576 10e9 pin, compressed layout ~1.67 M Dashed: modeled (10e9 ÷ ~5,987 B), not run. Context cap 131,072 is the operator's.
Gen 234/235, 2026-10-24, from vLLM's own startup line.

The pool is the lever with the largest headroom on this model, and it is a layout change in the engine, not a flag. A smaller KV type saves at most 40 % per token here (KV type).

Sizing it

  1. Find bytes per token: the startup line’s tokens ÷ the pool bytes. Expect the published figure to be wrong for your build (3.9x off here).
  2. Tokens needed = seats × typical context, not seats × max_model_len, unless every seat really runs full length. Prefix-cache hits also live in this pool (prefix caching).
  3. On unified memory, size from available memory during warmup, not at idle. On this pair idle and loaded differ: ~11 GiB free just after a start vs ~4.8 GiB under a saturating load (Qwen3.8 Flash-Next, 2026-09).
  4. A pool larger than one full context is the floor: below it a single max_model_len request cannot be admitted (context length).

More settings: vLLM settings, explained. Overflow beyond the pool: KV offload.

Questions? Let's Chat :)

vllm settings