Skip to content

Commit bff0a54

Browse files
authored
[BugFix]fix qwen3.5+pcp+chunkprefill accuracy error (vllm-project#11508)
### What this PR does / why we need it? Fix the precision issue in qwen3.5 with pcp and chunkprefill stacking. Original code: updated_state[i] = all_final_state[i] + matmul(all_final_h_update[i], updated_state[i-1]) Missing - s0 Expanding all_final_state[i] = Φ_i·s0 + p_i: Original code = (Φ_i·s0 + p_i) + Φ_i·correct_{i-1} = Φ_i·s0 + p_i + Φ_i·correct_{i-1} Correct value: correct_i = p_i + Φ_i·correct_{i-1} The error is Φ_i·s0—s0's contribution is counted twice (once in all_final_state[i], and once indirectly through Φ_i·updated_state[i-1]). Why does this only trigger in chunked prefill? - First chunk: s0 = 0 (new prompt) → - s0 = - 0 = no-op → the original code happens to be correct. - Subsequent chunks: s0 ≠ 0 (carrying state from the previous chunk) → Φ_i·s0 is recalculated → ssm_state writeback error → precision divergence. Thus, short prompts with a single chunk (in memory, non-MTP PCP=2 "correct") cannot detect this; it only becomes apparent when --max-num-batched-tokens is small enough to split the prompt into multiple chunks. ### Does this PR introduce _any_ user-facing change? ### How was this patch tested? - vLLM version: v0.23.0 - vLLM main: vllm-project/vllm@1f486d9 --------- Signed-off-by: weiguihua2 <weiguihua2@huawei.com>
1 parent 0958c6e commit bff0a54

2 files changed

Lines changed: 102 additions & 1 deletion

File tree

tests/ut/ops/a2/test_gdn_chunk_meta.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ def __setitem__(self, item, value):
6161
def __add__(self, other):
6262
return self
6363

64+
def __sub__(self, other):
65+
return self
66+
6467
def transpose(self, dim0, dim1):
6568
return self
6669

@@ -398,3 +401,100 @@ def test_build_final_chunk_indices_falls_back_without_triton_kernel(
398401
)
399402

400403
assert torch.equal(out_final_chunk_indices, torch.tensor([2, 3, 7], dtype=torch.int32))
404+
405+
406+
def test_chunk_gated_delta_rule_fwd_pcp_chaining_subtracts_initial_state(
407+
monkeypatch: pytest.MonkeyPatch,
408+
):
409+
"""PCP chaining uses (updated_state[i-1] - initial_state), not updated_state[i-1].
410+
411+
With s0 != 0 (subsequent prefill chunk), the fix subtracts s0 to avoid
412+
double-counting Φ_i·s0. Verified by checking the returned final_state
413+
matches the sequential result Φ_1·(Φ_0·s0+p_0)+p_1.
414+
"""
415+
torch.manual_seed(42)
416+
N, H, K, V = 1, 2, 4, 4
417+
s0 = torch.randn(N, H, K, V)
418+
phi_0 = torch.randn(N, H, K, K)
419+
phi_1 = torch.randn(N, H, K, K)
420+
p_0 = torch.randn(N, H, K, V)
421+
p_1 = torch.randn(N, H, K, V)
422+
423+
# Each rank computes final_state = Φ_i · s0 + p_i (from shared s0)
424+
rank0_fs = torch.matmul(phi_0, s0) + p_0
425+
rank1_fs = torch.matmul(phi_1, s0) + p_1
426+
# h_update shape [1, N, H, K, K]; after [:, [0], :, :, :] → [1, N, H, K, K]
427+
h_update_tensor = phi_0.unsqueeze(0)
428+
429+
prebuilt_meta = type(
430+
"PrebuiltMeta",
431+
(),
432+
{
433+
"block_indices_cumsum": None,
434+
"cu_seqlens_host": (0, N),
435+
"chunk_indices_chunk64_host": (0, 0),
436+
"chunk_indices_chunk64": None,
437+
"chunk_offsets_chunk64": torch.tensor([0, 1], dtype=torch.int32),
438+
"update_chunk_offsets_chunk64": torch.tensor([0, 2], dtype=torch.int32),
439+
"final_chunk_indices_chunk64": torch.tensor([0], dtype=torch.int32),
440+
"chunk_indices_large_block": None,
441+
"num_decodes": 0,
442+
},
443+
)()
444+
445+
all_gather_returns = [
446+
torch.stack([rank0_fs, rank1_fs]), # all_final_state: [2, N, H, K, V]
447+
torch.stack([phi_0, phi_1]), # all_final_h_update: [2, N, H, K, K]
448+
]
449+
450+
group = type(
451+
"Group",
452+
(),
453+
{
454+
"world_size": 2,
455+
"rank_in_group": 0,
456+
"all_gather": lambda self, value, dim: all_gather_returns.pop(0),
457+
},
458+
)()
459+
460+
monkeypatch.setattr(chunk, "get_forward_context", lambda: type("Ctx", (), {"attn_metadata": None})())
461+
monkeypatch.setattr(chunk, "get_pcp_group", lambda: group)
462+
monkeypatch.setattr(chunk, "chunk_local_cumsum", lambda *a, **kw: _DummyTensor("g_cumsum"))
463+
monkeypatch.setattr(chunk, "chunk_scaled_dot_kkt_fwd", lambda *a, **kw: _DummyTensor("A"))
464+
monkeypatch.setattr(chunk, "solve_tril", lambda *a, **kw: _DummyTensor("A_solved"))
465+
monkeypatch.setattr(chunk, "recompute_w_u_fwd", lambda *a, **kw: (_DummyTensor("w"), _DummyTensor("u")))
466+
monkeypatch.setattr(
467+
torch.ops._C_ascend,
468+
"chunk_gated_delta_rule_fwd_h",
469+
lambda *a, **kw: (_DummyTensor("h"), _DummyTensor("v_new"), rank0_fs),
470+
raising=False,
471+
)
472+
monkeypatch.setattr(
473+
chunk,
474+
"chunk_gated_delta_rule_fwd_hupdate",
475+
lambda *a, **kw: h_update_tensor,
476+
)
477+
monkeypatch.setattr(
478+
torch.ops._C_ascend,
479+
"chunk_fwd_o",
480+
lambda *a, **kw: _DummyTensor("o_ascendc"),
481+
raising=False,
482+
)
483+
484+
result = chunk.chunk_gated_delta_rule_fwd(
485+
q=_DummyTensor("q"),
486+
k=_DummyTensor("k"),
487+
v=_DummyTensor("v"),
488+
g=_DummyTensor("g"),
489+
beta=_DummyTensor("beta"),
490+
scale=1.0,
491+
initial_state=s0,
492+
output_final_state=False,
493+
cu_seqlens=torch.tensor([0, N], dtype=torch.int32),
494+
prebuilt_meta=prebuilt_meta,
495+
)
496+
497+
final_state = result[3]
498+
# Sequential: Φ_1·(Φ_0·s0 + p_0) + p_1
499+
expected = torch.matmul(phi_1, torch.matmul(phi_0, s0) + p_0) + p_1
500+
torch.testing.assert_close(final_state, expected, rtol=1e-4, atol=1e-4)

vllm_ascend/ops/triton/fla/chunk.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ def chunk_gated_delta_rule_fwd(
142142
updated_state = final_state.new_empty(get_pcp_group().world_size, *final_state.shape)
143143
updated_state[0, ...] = all_final_state[0]
144144
for i in range(1, get_pcp_group().world_size):
145+
# correct_i = all_final_state[i] + Φ_i · (correct_{i-1} - s0) = Φ_i · correct_{i-1} + p_i
145146
updated_final_state = all_final_state[i] + torch.matmul(
146-
all_final_h_update[i, ...], updated_state[i - 1, ...]
147+
all_final_h_update[i, ...], updated_state[i - 1, ...] - initial_state
147148
)
148149
updated_state[i, ...] = updated_final_state
149150

0 commit comments

Comments
 (0)