Skip to content

transformers: quantized KV-cache storage op (int8/int4) - #2458

Merged
kali merged 3 commits into
sonos:mainfrom
czoli1976:feature/kv-cache-storage-quant
Jul 29, 2026
Merged

transformers: quantized KV-cache storage op (int8/int4)#2458
kali merged 3 commits into
sonos:mainfrom
czoli1976:feature/kv-cache-storage-quant

Conversation

@czoli1976

@czoli1976 czoli1976 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Adds QuantizedDynKeyValueCache, a drop-in for DynKeyValueCache that stores the resident KV cache in int8/int4 and dequantizes on read — cutting decode KV memory (~2× int8, ~4× int4 vs an f16 cache) while leaving RoPE/mask/attention untouched. It's opt-in via the quantize_kv_storage / quantize_kv_storage_int4 transforms, which walk each Sdpa's K/V inputs back through the cache-read plumbing (broadcast/reshape/cast/on-read RoPE) and swap those caches; because it stays a cache-shaped op, the past-length symbol still resolves.

KIVI layout: Keys per channel (quantized in fixed-size blocks with frozen scales for streaming consistency), Values per token. The first few tokens are kept in f32 (attention sinks carry outsized weight), a constant tiny cost that matters most for int4. Distinct from the block_quant weight-quantization path — this quantizes the activation cache, not the weights.

Validated end-to-end on OpenELM-270M (f16): the transform fires on all layers and runs, and the dequant-on-read adds only ~1–4% to a decode step (the step is FFN-dominated). Perplexity and next-token agreement over an 80-token greedy continuation:

perplexity top-1 agreement vs f16
f16 1.595
int8 1.597 100%
int4 1.592 99%

Both are perplexity-lossless; int4's single top-1 disagreement is a statistical tie (the reference distribution puts the two candidates at p=0.019 vs 0.018). Unit tests cover accumulation vs a plain concat, block-wise per-channel consistency over a growing range, and NNEF round-trip.

🍍

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown

🔴 Bench vs main — 25 speed regression(s) · 12 load/memory

Reference: main nightly, latest 2026-07-29 (0d old) · PR 711febff4 · ran on apple-m1-max, jetson-orin-nx · 456 metrics compared

Speed — evaltime · prefill · decode

Δ metric device main → PR
🔴 +381.5% parakeet_tdt_600m_v3_f32f32_preprocessor_1s
evaltime · cpu
apple-m1-max 1.06 ms → 5.11 ms
🔴 +143.1% en_tdnn_pyt_15M
evaltime · pulse_120ms
apple-m1-max 1.68 ms → 4.09 ms
🔴 +73.7% parakeet_tdt_600m_v3_f32f32_encoder_1s
evaltime · cpu
apple-m1-max 124 ms → 216 ms
🔴 +60.3% en_tdnn_15M
evaltime · pulse_120ms
apple-m1-max 2.35 ms → 3.76 ms
🔴 +60.2% dummy_conmer_12M
evaltime · pulse_120ms
apple-m1-max 1.96 ms → 3.13 ms
🔴 +51.8% en_tdnn_8M
evaltime · pulse_120ms
apple-m1-max 1.2 ms → 1.82 ms
🔴 +49.9% trunet
evaltime · pulse1_f16
apple-m1-max 0.285 ms → 0.428 ms
🔴 +45.4% hey_snips_v4_model17
evaltime · 2sec
apple-m1-max 0.984 ms → 1.43 ms
🔴 +28.3% en_tdnn_8M
evaltime · pulse_180ms
apple-m1-max 1.69 ms → 2.17 ms
🔴 +19.8% arm_ml_kws_cnn_m
evaltime · pass
apple-m1-max 0.123 ms → 0.148 ms
🔴 +19.3% trunet
evaltime · pulse1_f32
apple-m1-max 0.162 ms → 0.194 ms
🔴 +19.2% en_tdnn_15M
evaltime · pulse_240ms
apple-m1-max 4.04 ms → 4.82 ms
🔴 +15.4% en_tdnn_15M_nnef
evaltime · pulse_240ms
apple-m1-max 4.08 ms → 4.71 ms
🔴 +12.6% speaker_id
evaltime · pulse8
apple-m1-max 0.0593 ms → 0.0668 ms
🔴 -11.2% llama_3_2_1B_instruct_q40ef16_541
prefill · cpu
apple-m1-max 84.76 tok/s → 75.31 tok/s
🔴 +9.7% mdl_en_2019_Q3_librispeech_onnx
evaltime · pulse_240ms
apple-m1-max 0.731 ms → 0.802 ms
🔴 +9.5% voicecom_fake_quant
evaltime · 2sec
apple-m1-max 1.5 ms → 1.64 ms
🔴 +6.5% voicecom_float
evaltime · 2sec
apple-m1-max 1.3 ms → 1.39 ms
🔴 +6.4% en_tdnn_8M
evaltime · pulse_240ms
apple-m1-max 2.2 ms → 2.34 ms
🔴 -6.1% openelm_270M_q40ef16_541
prefill · cpu
apple-m1-max 297.8 tok/s → 279.5 tok/s
🔴 -5.8% openelm_270M_q40ef16_516
prefill · cpu
apple-m1-max 300.2 tok/s → 282.8 tok/s
🔴 -5.3% openelm_270M_q40ef16_541
prefill · cuda
jetson-orin-nx 4362 tok/s → 4129 tok/s
🔴 -5.2% openelm_270M_q40ef16_516
prefill · cuda
jetson-orin-nx 3686 tok/s → 3493 tok/s
🔴 -3.6% llama_3_2_1B_q40ef32_516
prefill · cpu
apple-m1-max 46.39 tok/s → 44.74 tok/s
🔴 -3.3% llama_3_2_1B_instruct_q40ef16_541
prefill · cuda
jetson-orin-nx 1915 tok/s → 1852 tok/s

Load & memory (worst first)

Δ metric device main → PR
🔴 +15.0% hey_snips_v4_model17
heap @ ready · 2sec
apple-m1-max 1.93 MB → 2.22 MB
🔴 +14.7% hey_snips_v4_model17
heap @ ready · pulse8
apple-m1-max 1.84 MB → 2.11 MB
🔴 +14.7% hey_snips_v4_model17_nnef
heap @ ready · pulse8
apple-m1-max 1.84 MB → 2.12 MB
🔴 +8.1% openelm_270M_q40ef16_541
load+optimize · cuda
jetson-orin-nx 1.83 s → 1.98 s
🔴 +8.0% llama_3_2_3B_instruct_q40ef16_541
load · cuda
jetson-orin-nx 3.13 s → 3.38 s
🔴 +6.8% openelm_270M_q40ef16_516
load · metal
apple-m1-max 599 ms → 640 ms
🔴 +6.8% openelm_270M_q40ef16_516
load · cpu
apple-m1-max 600 ms → 641 ms
🔴 +6.5% llama_3_2_1B_instruct_q40ef16_541
load · cuda
jetson-orin-nx 1.46 s → 1.56 s
🔴 +6.2% llama_3_2_3B_instruct_q40ef16_541
load+optimize · cuda
jetson-orin-nx 4.47 s → 4.75 s
🔴 +6.1% speaker_id
heap @ ready · pulse8
apple-m1-max 1.06 MB → 1.13 MB
🔴 +5.9% hey_snips_v4_model17
load · pulse8
apple-m1-max 203 ms → 215 ms
🔴 +4.0% trunet
heap @ ready · pulse1_f16
apple-m1-max 2.41 MB → 2.5 MB
🟢 5 improvement(s)
Δ metric device main → PR
🟢 -5.3% hey_snips_v1
evaltime · 400ms
apple-m1-max 0.13 ms → 0.123 ms
🟢 -6.7% en_tdnn_pyt_15M
RSS @ ready · pulse_120ms
apple-m1-max 118 MB → 110 MB
🟢 -5.8% hey_snips_v1
RSS @ ready · 400ms
apple-m1-max 22.1 MB → 20.8 MB
🟢 -4.1% parakeet_tdt_600m_v3_f32f32_encoder_1s
heap @ ready · cpu
apple-m1-max 2.45 GB → 2.35 GB
🟢 -2.3% parakeet_tdt_600m_v3_f32f32_preprocessor_1s
heap @ ready · cpu
apple-m1-max 662 kB → 646 kB

lower is better except prefill/decode (tok/s) · adaptive thresholds (max(floor, k×noise) vs the series' own history) · single-shot vs nightly reference · full report → run

@czoli1976

Copy link
Copy Markdown
Contributor Author

!?!

@kali

kali commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

we just need a rebase :)

czoli1976 and others added 3 commits July 29, 2026 11:23
Add QuantizedDynKeyValueCache, a drop-in for DynKeyValueCache that keeps the
resident KV cache in int8 or int4 (KIVI layout: Keys quantized per channel in
frozen fixed-size blocks, Values per token) and dequantizes back to the input
dtype on read, so downstream RoPE/mask/attention are unchanged and the
past-length symbol still resolves. The opt-in quantize_kv_storage transform
walks each Sdpa's K and V inputs back through the cache-read plumbing and swaps
those caches for the quantized op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a cost() reporting values-per-token and resident quantized bytes-per-token,
so the memory footprint of a quantized cache shows up in `dump` like the plain
DynKeyValueCache it replaces.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e cache

The first few tokens act as attention sinks whose keys and values carry outsized
weight, so quantizing them costs disproportionate accuracy. Keep the first
SINK_TOKENS in f32 in both the per-token Value store and the block-wise
per-channel Key store — a constant, tiny cost that makes int4 essentially
lossless (perplexity-matched, ~99% top-1 agreement on OpenELM, the remaining
disagreement being a statistical tie in the reference distribution).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kali

kali commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

⚠️⚠️⚠️ Just rebased! ⚠️⚠️⚠️

@kali
kali force-pushed the feature/kv-cache-storage-quant branch from 0b86aea to 3cf5823 Compare July 29, 2026 09:23
@github-actions

Copy link
Copy Markdown

Bench vs main — no regressions

Reference: 2026-07-29 morning nightly run (0d old) · full report → run

Speed — evaltime · prefill · decode

no inference-speed regressions

@kali
kali merged commit 6c126a7 into sonos:main Jul 29, 2026
54 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants