Skip to content

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

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

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

Conversation

@haregali

@haregali haregali commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

With int8_per_token_head (or fp8/int4 per-token-head) KV cache on KV-sharing models (Gemma-4, Gemma-3n), the first max_num_seqs requests after engine start generate corrupted leading tokens. Found while investigating #50702 and #50749; the reporter has since verified on their rig that this defect is real but distinct from the primary failures those issues report (see the issue threads). Supersedes #51061, closed while we finished verification.

TritonAttentionImpl._ensure_scale_caches fills the cache's inline scale bytes with 1.0 when an impl first builds its scale views. KV-sharing layers alias the target layer's cache view, and dummy runs skip their attention (attn_metadata is None early return), so their first touch happens during the first real forward pass, after the target layers have already written per-token scales for the prompt. The fill resets those scales to 1.0 while leaving the quantized data intact; K dequantizes ~200x too large and the first decode steps of the entire first batch go bad. Later batches are unaffected, so under multiprocess serving the failure looks stochastic and spuriously correlated with prefix caching, prompt length, and concurrency — matching the confusing observations in both issues.

The fix initializes each cache view's scale region exactly once, keyed by view data_ptr with a storage weakref for liveness. Keying by view rather than storage keeps packed layouts correct (multiple layers in one storage each own their region). Skipping the fill for kv-sharing layers alone would not cover cross-group tensor sharing, where the same wipe can occur.

Evidence

Deterministic repro: google/gemma-4-E2B-it, TRITON_ATTN, in-process engine, greedy, 48 number-sequence prompts whose correct continuation is checkable, 4 copies each.

  • Before: exactly the first max_num_seqs requests fail, every run (mns=4 → requests 0–3, 6 → 0–5, 8 → 0–7). bf16 passes.
  • Instrumentation: 20 late fills, all on the aliased storage, none elsewhere; wiped scales read back as exactly 1.0; quantized codes byte-identical to a healthy run.
  • Independent confirmation: pre-creating all scale views at init on unfixed code also eliminates the failure.
  • After: zero failures across prefix caching on/off, prompt-length residues, mns 4/6/8, and multiprocess mode; first-vs-later-batch KV bytes identical; bf16 unchanged.

Test Plan

pytest tests/v1/attention/test_triton_attn_scale_init.py   # new, 5 tests, red on main
pre-commit ruff-check / ruff-format / mypy-3.12            # pass
vllm bench latency (gemma-4-E2B, int8_per_token_head): 3.127s vs 3.138s on main

Notes

No open PR touches this path (searched 2026-08-05). The reporters of #50702/#50749 run v0.26.0, which has this code; their confirmation on a patched build would establish how much of their production failure this explains. AI assistance was used; every changed line was human-reviewed and all experiments ran locally on one RTX 4090.

@haregali
haregali requested a review from tdoublep as a code owner August 5, 2026 00:42

@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.

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 in a class-level registry and fill each storage's
scale regions exactly once, keyed by data_ptr with a weakref to the storage so freed-and-reallocated
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>
@haregali
haregali force-pushed the fix/int8-kv-scale-init-once branch from 3100149 to 103ea62 Compare August 5, 2026 00:46
@haregali

haregali commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

gsm8k, 250 samples, 5-shot, gemma-4-E2B-it, TRITON_ATTN, RTX 4090:

config strict-match flexible-extract
this PR, int8_per_token_head 0.108 ± 0.020 0.132 ± 0.022
main, int8_per_token_head 0.108 ± 0.020 0.132 ± 0.022
main, bf16 0.124 ± 0.021 0.152 ± 0.023

No accuracy change. The bug only affects the first batch per engine start (~6 of 250 samples), below eval sensitivity; the deterministic repro in the description is the sensitive measurement. This confirms no regression.

@mergify

mergify Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @haregali.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working mrv1-only Issues/PRs which apply only to Model Runner V1 (not applicable to Model Runner V2) needs-rebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants