Skip to content

[Bugfix] Initialize per-token-head KV scale regions once per storage - #51061

Closed
haregali wants to merge 1 commit into
vllm-project:mainfrom
haregali:fix/int8-kv-scale-init-once
Closed

haregali wants to merge 1 commit into
vllm-project:mainfrom
haregali:fix/int8-kv-scale-init-once

Conversation

@haregali

@haregali haregali commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

With int8_per_token_head/fp8_per_token_head KV cache on KV-sharing models (Gemma-4 family), the first scheduled batch after engine start emits corrupted leading tokens. Likely the root cause behind #50702 and #50749.

TritonAttentionImpl._ensure_scale_caches builds its inline-scale views lazily per impl and unconditionally fill_(1.0)s the storage's scale regions on first touch. KV-sharing layers alias their target layer's cache tensor; when warmup didn't exercise them, their first touch happens mid-way through the first real forward pass — after the target layers already wrote real per-token scales for the prompt. The fill wipes the scales (int8 codes stay intact), so prompt K/V dequantize against 1.0 instead of ~0.005: K inflated ~200×, first decode steps of every request in the first batch corrupted. Later batches are clean (views exist), which made the bug look stochastic under multiprocess serving and spuriously dependent on batch geometry / prefix caching / prompt-length residue — matching the confusing conditionals reported in both issues.

Fix: initialize each storage's scale regions exactly once (class-level registry keyed by (data_ptr, nbytes), so caches re-allocated by sleep/wake_up are re-initialized).

Evidence

Deterministic single-GPU repro: google/gemma-4-E2B-it, TRITON_ATTN, MRV1, in-process engine, 48 arithmetic-continuation prompts × 4 copies, greedy — output objectively checkable against the true sequence.

  • Pre-fix: exactly the first max_num_seqs requests damaged, every run (mns=4 → requests 0–3, mns=6 → 0–5, mns=8 → 0–7); int8-only (bf16 clean).
  • Instrumented _ensure_scale_caches: 33 fills wipe nothing (pre-write), 2 late fills wipe 22,384 / 13,394 written non-default scales on an aliased storage mid-first-batch.
  • Scale forensics: first-batch vs second-batch KV for identical prompts — int8 codes byte-identical, K scales differ ×221, V ×24 = exactly 1.0 / true_scale.
  • Post-fix: zero damaged requests across prefix-caching on/off, residues 4/12, mns 4/6/8, in-process and multiprocess modes; first-vs-later-batch KV bytes become identical; bf16 unchanged.

Test Plan

pytest tests/v1/attention/test_triton_attn_scale_init.py   # 3 passed (new; red on main)
pre-commit: ruff-check, ruff-format, mypy-3.12             # passed

Repro harness (~100 s/run on one RTX 4090) available on request; happy to contribute it as an integration test if desired.

Notes

With int8/fp8_per_token_head KV cache on KV-sharing models (Gemma-4),
the first scheduled batch after engine start emits corrupted leading
tokens. TritonAttentionImpl._ensure_scale_caches builds its scale views
lazily per impl and unconditionally fills the storage's inline scale
regions with 1.0 on first touch. KV-sharing layers alias their target
layer's cache tensor and, when warmup did not exercise them, first touch
it mid-way through the first real forward pass - after the target layer
already wrote real per-token scales for the prompt. The fill wipes those
scales (leaving the int8 codes intact), so the prompt K/V dequantize
against 1.0 instead of ~0.005, inflating K by ~200x and corrupting the
first decode steps of every request in the first batch. Subsequent
batches are clean because the views exist afterwards, which made the bug
look stochastic under multiprocess serving and dependent on batch
geometry, prefix caching, and prompt-length residue (vllm-project#50702, vllm-project#50749).

Fix: track initialized storages on the class and fill each storage's
scale regions exactly once, keyed by (data_ptr, nbytes) so re-allocated
caches (sleep/wake_up) are re-initialized.

Verified on a deterministic single-GPU reproduction (gemma-4-E2B-it,
TRITON_ATTN, int8_per_token_head, in-process engine, 48 arithmetic
continuation prompts x 4 copies, greedy): before the fix the entire
first batch (exactly max_num_seqs requests) produces objectively wrong
continuations in every run; after the fix zero requests are damaged
across prefix-caching on/off, prompt-length residues, max_num_seqs
4/6/8, multiprocess mode, and first-vs-later-batch KV bytes become
identical. bf16 outputs are unaffected.

Signed-off-by: Hareesh <hgsata@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rsg3Q3x8FCVCZd1fUFED5U
@haregali
haregali requested a review from tdoublep as a code owner August 4, 2026 17:37

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added the bug Something isn't working label Aug 4, 2026
@haregali haregali closed this Aug 4, 2026
@haregali
haregali deleted the fix/int8-kv-scale-init-once branch August 4, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant