Skip to content

Qwen quantized KV cache at 65K (TG512), and a correction to 872ff3f

Correction

The commit message of 872ff3f ("Add FP8 dequant-once, INT8 per-token-head, and TQ4NC quantizers", PR #83) contains two throughput claims that were not backed by a run record:

  • "65K prefill goes 57.9 -> 1138.1 tok/s" for the FP8 path;
  • "Prefill reuses the dequant-once path and lands at 1142.3 tok/s" for INT8.

Neither number came from a 65536-token run of the format it was attached to. 1138.1 and the neighbouring TurboQuant/FP16 figures circulating in notes at the time were 32768-token measurements that had been relabelled as 65536, and every long-context record on disk predating this document used --max-new-tokens 4, which cannot measure decode at all. No run record for turboquant_k8v4 or int8_per_token_head above 32768 tokens existed when that commit was written.

The commit is merged, so its message stands as written. This file is the correction of record. The table below replaces those claims. The architectural claims in 872ff3f (dequant-once removes the O(rows x context) redundancy; prefill reaches FP16 parity; DP4A cannot amortize a per-position scale; the Triton kernel matches the PyTorch reference to 8e-6 but is not wired into cpp_engine) are unaffected and still hold.

Measured results

Qwen3.8-27B-FP8, 4x RTX 2080 Ti, TP4, NCCL-enabled build, rev 3aa1484 (tree-identical to 872ff3f). Four cases run serially, each generating 512 tokens, all with 4-rank token parity PASS.

python scripts/bench_qwen_long_context.py \
  --ckpt /mnt/data2/Qwen3.8-27B-FP8 \
  --binary build/pocketllm_engine \
  --lengths 65536 --max-new-tokens 512 \
  --prefill-chunk-tokens 4096 \
  --kv-cache-dtype <fp16|fp8|turboquant_k8v4|int8_per_token_head> \
  --tp-world 4
KV dtype prefill s prefill tok/s decode s decode tok/s KV MiB/rank pf vs fp16 dc vs fp16
fp16 56.46 1160.7 25.41 20.11 1032 1.000 1.000
fp8 56.29 1164.3 204.22 2.50 532 1.003 0.124
turboquant_k8v4 56.39 1162.1 68.57 7.45 391 1.001 0.370
int8_per_token_head 56.32 1163.6 487.87 1.05 520 1.002 0.052

decode_token_count is 511 in every case (the first token belongs to prefill, per docs/guides/benchmarking.md). TQ4NC is absent because it has no CUDA kernel and is not wired into attention, so it cannot run.

Reading of the results

Prefill parity is real: all three quantized formats land within 0.3% of FP16, confirming dequant-once works. Prefill is also insensitive to the KV format, which is what a single bulk dequant into a dense FP16 workspace predicts.

Decode is where the gap lives, and it is large. Ranking by decode throughput, turboquant_k8v4 (0.370x FP16) is the only quantized format that is practical, and it is also the most memory-efficient at 391 MiB/rank, a 62% saving. FP8 at 0.124x and int8_per_token_head at 0.052x are opt-in curiosities at this context length.

Against vLLM-2080Ti

vLLM-2080Ti's own PP65536/TG512 sweep (docs/qwen36-kv-throughput-sweep.zh-CN.md in that tree) reports, for Qwen3.6 27B FP8 TP2 without MTP: FP16 1303.9/29.1, INT8 1274.8/33.7, TQK8V4 1277.9/20.7, TQ4NC 1273.9/19.6.

This is a different model (Qwen3.6 vs 3.8) at a different TP width (2 vs 4), so the ratios below are indicative, not a controlled comparison:

  • prefill: 0.89x (fp16), 0.91x (tqk8v4), 0.91x (int8)
  • decode: 0.69x (fp16), 0.36x (tqk8v4), 0.031x (int8)

The FP16 and TQK8V4 decode ratios are plausible as TP4 communication overhead plus model differences. The INT8 ratio is not: on vLLM, INT8 decode is faster than FP16 (33.7 vs 29.1), while ours is 1/19th of our own FP16. The sign is reversed, which points at the implementation rather than at hardware or model differences. The suspect is Phase 3 of cpp_engine/backends/cuda/kernels/qwen_int8_per_token_head_ops.cu, which walks all 65536 positions serially per output channel, dequantizing one value at a time. Wiring in the already-validated Triton kernel (src/kernels/int8_per_token_head_triton.py) is the open follow-up; whether it reaches vLLM's 33.7 tok/s is untested.