Skip to content

Commit 36a7037

Browse files
maeehartcursoragent
andcommitted
[ROCm][DSv4] Zero prefill-attn KV workspace before gather
``_forward_prefill_attn`` allocates the prefill KV buffer via ``current_workspace_manager().get_simultaneous`` which returns a ``torch.empty`` view -- uninitialized memory shared across requests and across earlier layers in the same forward pass. For each chunk row only the compressed-K prefix (rows [0, seq_len / compress_ratio)) and the SWA window (rows [N, N + gather_lens)) get written by ``dequantize_and_gather_k_cache``. The rest of the M dimension stays at whatever bytes the workspace held last. ``flash_mla_sparse_fwd`` then reads ``kv.view(-1, 1, head_dim)`` using ``combined_indices`` that can address those holes when a query token's effective context is shorter than M, so the attention output becomes data-dependent on prior workspace residents. Concrete symptom: on MI300X with FNUZ FP8 + cudagraphs, the same temperature=0 deterministic prompt produces 10 distinct first tokens across 10 back-to-back ``/v1/completions`` calls. Disabling AITER MoE and MLA did not help; the variance comes from this DSv4-specific workspace read. Zero ``kv`` once after ``get_simultaneous`` so any unread slot deterministically contributes zero. The cost is one bf16 fill of ``PREFILL_CHUNK_SIZE * M * head_dim`` bytes per attention layer call, which is dwarfed by the FP8 dequant + sparse FlashMLA themselves. Signed-off-by: Markus Hartikainen <markus.hartikainen@amd.com> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent b605d0d commit 36a7037

1 file changed

Lines changed: 12 additions & 0 deletions

File tree

vllm/model_executor/layers/deepseek_v4_attention.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,18 @@ def _forward_prefill(
961961
kv = workspace_manager.get_simultaneous(
962962
((PREFILL_CHUNK_SIZE, M, q.shape[-1]), torch.bfloat16),
963963
)[0]
964+
# The workspace allocator returns uninitialized memory and is shared
965+
# across requests + other layers. dequantize_and_gather_k_cache only
966+
# writes the compressed-K prefix (rows [0, seq_len/compress_ratio))
967+
# and the SWA window (rows [N, N+gather_lens)) for each chunk row,
968+
# leaving holes in the M dimension. flash_mla_sparse_fwd then reads
969+
# `kv.view(-1, 1, head_dim)` using top-k indices that can reference
970+
# those holes for very short sequences, causing data-dependent
971+
# non-determinism across otherwise-identical temperature=0 requests.
972+
# Zero once per call so the holes are deterministic (and harmless if
973+
# ever indexed). The cost is one bf16 zero per chunk worth of
974+
# workspace, which is dwarfed by the gather + attention themselves.
975+
kv.zero_()
964976
for chunk_idx in range(num_chunks):
965977
chunk_start = chunk_idx * PREFILL_CHUNK_SIZE
966978
chunk_end = min(chunk_start + PREFILL_CHUNK_SIZE, num_prefills)

0 commit comments

Comments
 (0)