Skip to content

Commit 886e88a

Browse files
[Spec Decode][PP] Fix aux-tap packing under compile and outside a worker
Two CI failures on this branch, both from the aux-tap plumbing running in contexts the feature could not reach before. The compile wrapper rejects any forward whose bytecode names `update`, its heuristic for buffer mutation under cudagraphs. Packing the taps through dict.update tripped it, so the engine failed to start for Llama, Qwen2/3, Kimi-K3 and DeepSeek-V4. Stock CUDA hid this because AOT compile bypasses the hook; it fired on ROCm and wherever the compile cache is off. Merge the taps by unpacking instead, and pin the rule with a unit test. The mixin also resolved the PP group while packing, receiving and caching the slot layout. Every caller already gates on is_last_rank, so the first two lookups were redundant, and they made model construction fail wherever no PP group exists, which is what the Kimi-K3 eagle3 unit tests do. Drop them, and skip the layout cache when the parallel state is not initialized. Signed-off-by: Yongqin Wang <yongqinwang@roblox.com>
1 parent e7859f5 commit 886e88a

6 files changed

Lines changed: 54 additions & 20 deletions

File tree

tests/v1/worker/test_eagle3_aux_hidden_states_pp.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from vllm.distributed.utils import get_pp_indices
1212
from vllm.model_executor.models.interfaces import EagleModelMixin
13+
from vllm.model_executor.models.llama import LlamaModel
14+
from vllm.model_executor.models.qwen2 import Qwen2Model
1315

1416
# Kimi-K3 / DSpark: 93 layers, target_layer_ids [2, 23, 47, 71, 89].
1517
# get_eagle3_aux_layers_from_config maps target_layer_ids -> +1.
@@ -65,3 +67,14 @@ def test_middle_stage_sends_only_local_taps():
6567
upstream = [a for a in AUX_IDS if a <= start]
6668
assert not set(local) & set(upstream)
6769
assert local == [a for a in AUX_IDS if start < a <= end]
70+
71+
72+
@pytest.mark.parametrize("model_cls", [LlamaModel, Qwen2Model])
73+
def test_forward_does_not_name_update(model_cls):
74+
"""Packing taps must not go through dict.update.
75+
76+
TorchDynamoWrapper.bytecode_hook refuses any compiled forward whose
77+
bytecode names `update`, so a model that packs its taps that way cannot
78+
start under cudagraphs. The same holds for the other opted-in models.
79+
"""
80+
assert "update" not in model_cls.forward.__code__.co_names

vllm/model_executor/models/interfaces.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1510,8 +1510,15 @@ def _set_aux_hidden_state_layers(self, layers: tuple[int, ...]) -> None:
15101510

15111511
def _cache_aux_pp_layout(self) -> None:
15121512
"""Resolve this rank's slot layout, off the forward path."""
1513-
from vllm.distributed.parallel_state import get_pp_group
1513+
from vllm.distributed.parallel_state import (
1514+
get_pp_group,
1515+
model_parallel_is_initialized,
1516+
)
15141517

1518+
# Models are also built outside a worker, where there is no PP group
1519+
# and so nothing to forward.
1520+
if not model_parallel_is_initialized():
1521+
return
15151522
pp = get_pp_group()
15161523
if pp.world_size < 2:
15171524
return
@@ -1583,12 +1590,10 @@ def pack_local_aux_for_last(
15831590
"""Expose this stage's own aux taps to the runner, keyed by global slot.
15841591
15851592
Pure packing, so the forward stays capturable by full CUDA graphs; the
1586-
taps then ride the ``IntermediateTensors`` handoff.
1593+
taps then ride the ``IntermediateTensors`` handoff. Callers reach this
1594+
only off the last rank, which implies a PP world size above one.
15871595
"""
1588-
from vllm.distributed.parallel_state import get_pp_group
1589-
1590-
pp = get_pp_group()
1591-
if pp.world_size == 1 or pp.is_last_rank or not aux_hidden_states:
1596+
if not aux_hidden_states:
15921597
return {}
15931598
base = self._aux_slot_base_cached
15941599
return {
@@ -1602,13 +1607,10 @@ def recv_remote_aux_from_producers(
16021607
"""Collect earlier stages' aux taps on the last rank, in tap order.
16031608
16041609
The handoff has already landed them in the persistent buffer, so reading
1605-
fixed slots keeps the forward capturable by full CUDA graphs.
1610+
fixed slots keeps the forward capturable by full CUDA graphs. Callers
1611+
gate on the last rank; the count is zero unless a split left taps
1612+
upstream, which cannot happen at a PP world size of one.
16061613
"""
1607-
from vllm.distributed.parallel_state import get_pp_group
1608-
1609-
pp = get_pp_group()
1610-
if not pp.is_last_rank or pp.world_size == 1:
1611-
return []
16121614
total = self._aux_upstream_total_cached
16131615
if total == 0:
16141616
return []

vllm/model_executor/models/llama.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,13 @@ def forward(
442442
)
443443

444444
if not get_pp_group().is_last_rank:
445-
tensors = {"hidden_states": hidden_states, "residual": residual}
446-
tensors.update(self.pack_local_aux_for_last(aux_hidden_states))
445+
# Merged by unpacking rather than dict.update: the compile wrapper
446+
# rejects a forward whose bytecode names `update`.
447+
tensors = {
448+
"hidden_states": hidden_states,
449+
"residual": residual,
450+
**self.pack_local_aux_for_last(aux_hidden_states),
451+
}
447452
return IntermediateTensors(tensors)
448453

449454
hidden_states, _ = self.norm(hidden_states, residual)

vllm/model_executor/models/qwen2.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,13 @@ def forward(
435435
)
436436

437437
if not get_pp_group().is_last_rank:
438-
tensors = {"hidden_states": hidden_states, "residual": residual}
439-
tensors.update(self.pack_local_aux_for_last(aux_hidden_states))
438+
# Merged by unpacking rather than dict.update: the compile wrapper
439+
# rejects a forward whose bytecode names `update`.
440+
tensors = {
441+
"hidden_states": hidden_states,
442+
"residual": residual,
443+
**self.pack_local_aux_for_last(aux_hidden_states),
444+
}
440445
return IntermediateTensors(tensors)
441446

442447
hidden_states, _ = self.norm(hidden_states, residual)

vllm/models/deepseek_v4/nvidia/model.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,8 +1197,12 @@ def forward(
11971197
)
11981198

11991199
if not get_pp_group().is_last_rank:
1200-
tensors = {"hidden_states": hidden_states}
1201-
tensors.update(self.pack_local_aux_for_last(aux_hidden_states))
1200+
# Merged by unpacking rather than dict.update: the compile wrapper
1201+
# rejects a forward whose bytecode names `update`.
1202+
tensors = {
1203+
"hidden_states": hidden_states,
1204+
**self.pack_local_aux_for_last(aux_hidden_states),
1205+
}
12021206
return IntermediateTensors(tensors)
12031207

12041208
if self.use_sequence_parallel:

vllm/models/kimi_k3/nvidia/model.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,8 +1374,13 @@ def forward(
13741374
)
13751375
if prefix_sum is not None:
13761376
hidden_states = hidden_states + prefix_sum
1377-
tensors = {"hidden_states": hidden_states, "residual": residual}
1378-
tensors.update(self.pack_local_aux_for_last(aux_hidden_states))
1377+
# Merged by unpacking rather than dict.update: the compile wrapper
1378+
# rejects a forward whose bytecode names `update`.
1379+
tensors = {
1380+
"hidden_states": hidden_states,
1381+
"residual": residual,
1382+
**self.pack_local_aux_for_last(aux_hidden_states),
1383+
}
13791384
return IntermediateTensors(tensors)
13801385

13811386
if self.use_attn_res:

0 commit comments

Comments
 (0)