Revert "[DSpark] Support pipeline-parallel targets in aggregated serving (#56956)" (#58484)

Nick Hill committed Sep 24, 2026 at 08:48 UTC 09fe178dba08e6de4448ade1e37d14ae1dab3acf
11 files changed +28 -629
tests/kernels/moe/test_topk_softplus_sqrt.py
-130
@@ -339,136 +339,6 @@ def test_dsv4_fast_topk(
339 )
340
341
342 -@pytest.mark.skipif(
343 - not current_platform.is_cuda(),
344 - reason="The DeepSeek V4 fast path is CUDA-only.",
345 -)
346 -def test_dsv4_fast_topk_padding_uint32_falls_back(monkeypatch: pytest.MonkeyPatch):
347 - """Padded rows need the -1 sentinel, which uint32 cannot represent: the
348 - router must skip the dsv4 fast path and still route the real rows."""
349 - torch.manual_seed(0)
350 - num_tokens = 17
351 - num_experts = 256
352 - hidden_states = torch.randn((num_tokens, 64), dtype=torch.float32, device="cuda")
353 - gating_output = torch.randn(
354 - (num_tokens, num_experts), dtype=torch.float32, device="cuda"
355 - )
356 - correction_bias = torch.randn(num_experts, dtype=torch.float32, device="cuda")
357 - is_padding = torch.zeros(num_tokens, dtype=torch.bool, device="cuda")
358 - is_padding[1::2] = True
359 - gating_output[is_padding] = float("nan")
360 -
361 - monkeypatch.setattr(
362 - "vllm.model_executor.layers.fused_moe.router."
363 - "fused_topk_bias_router._get_padding_mask",
364 - lambda _: is_padding,
365 - )
366 - # uint32 + padding would trip dsv4_topk's signed-indices assertion; the
367 - # generic path must take over instead.
368 - topk_weights, topk_ids = fused_topk_bias(
369 - hidden_states=hidden_states,
370 - gating_output=gating_output,
371 - scoring_func="sqrtsoftplus",
372 - e_score_correction_bias=correction_bias,
373 - topk=6,
374 - renormalize=True,
375 - indices_type=torch.uint32,
376 - routed_scaling_factor=1.5,
377 - )
378 -
379 - assert topk_ids.dtype == torch.uint32
380 - topk_weights_ref, topk_ids_ref = _torch_topk_softplus_sqrt(
381 - gating_output=gating_output[~is_padding],
382 - topk=6,
383 - renormalize=True,
384 - routed_scaling_factor=1.5,
385 - e_score_correction_bias=correction_bias,
386 - )
387 - # uint32 CUDA tensors do not support boolean-mask indexing; widen first.
388 - torch.testing.assert_close(
389 - topk_ids.to(torch.int64)[~is_padding],
390 - topk_ids_ref.to(torch.int64),
391 - atol=0,
392 - rtol=0,
393 - )
394 - torch.testing.assert_close(
395 - topk_weights[~is_padding], topk_weights_ref, atol=2e-5, rtol=2e-5
396 - )
397 -
398 -
399 -@pytest.mark.skipif(
400 - not current_platform.is_cuda(),
401 - reason="The DeepSeek V4 fast path is CUDA-only.",
402 -)
403 -def test_dsv4_fast_topk_padding(monkeypatch: pytest.MonkeyPatch):
404 - """Verify the DSV4 fast path removes graph-padding rows from routing."""
405 - torch.manual_seed(0)
406 - num_tokens = 17
407 - num_experts = 256
408 - hidden_states = torch.randn((num_tokens, 64), dtype=torch.float32, device="cuda")
409 - gating_output = torch.randn(
410 - (num_tokens, num_experts), dtype=torch.float32, device="cuda"
411 - )
412 - correction_bias = torch.randn(num_experts, dtype=torch.float32, device="cuda")
413 - is_padding = torch.zeros(num_tokens, dtype=torch.bool, device="cuda")
414 - is_padding[1::2] = True
415 - gating_output[is_padding] = float("nan")
416 -
417 - monkeypatch.setattr(
418 - "vllm.model_executor.layers.fused_moe.router."
419 - "fused_topk_bias_router._get_padding_mask",
420 - lambda _: is_padding,
421 - )
422 - topk_weights, topk_ids = fused_topk_bias(
423 - hidden_states=hidden_states,
424 - gating_output=gating_output,
425 - scoring_func="sqrtsoftplus",
426 - e_score_correction_bias=correction_bias,
427 - topk=6,
428 - renormalize=True,
429 - routed_scaling_factor=1.5,
430 - )
431 -
432 - assert torch.equal(topk_ids[is_padding], torch.full_like(topk_ids[is_padding], -1))
433 - assert torch.equal(
434 - topk_weights[is_padding], torch.zeros_like(topk_weights[is_padding])
435 - )
436 -
437 - topk_weights_ref, topk_ids_ref = _torch_topk_softplus_sqrt(
438 - gating_output=gating_output[~is_padding],
439 - topk=6,
440 - renormalize=True,
441 - routed_scaling_factor=1.5,
442 - e_score_correction_bias=correction_bias,
443 - )
444 - torch.testing.assert_close(topk_ids[~is_padding], topk_ids_ref, atol=0, rtol=0)
445 - torch.testing.assert_close(
446 - topk_weights[~is_padding], topk_weights_ref, atol=2e-5, rtol=2e-5
447 - )
448 -
449 - # The mask buffer is persistent under CUDA graph replay, but its contents
450 - # change with every batch. Verify that the kernel reads those contents at
451 - # runtime rather than specializing on the mask captured above.
452 - graph = torch.cuda.CUDAGraph()
453 - with torch.cuda.graph(graph):
454 - graph_weights, graph_ids = dsv4_topk(
455 - gating_output,
456 - correction_bias,
457 - torch.int32,
458 - 1.5,
459 - is_padding=is_padding,
460 - )
461 -
462 - is_padding.logical_not_()
463 - graph.replay()
464 - assert torch.equal(
465 - graph_ids[is_padding], torch.full_like(graph_ids[is_padding], -1)
466 - )
467 - assert torch.equal(
468 - graph_weights[is_padding], torch.zeros_like(graph_weights[is_padding])
469 - )
470 -
471 -
342 @pytest.mark.skipif(
343 not current_platform.is_cuda_alike(),
344 reason="This test is skipped on non-CUDA platform.",
tests/v1/spec_decode/test_dflash_prepare_inputs.py
-149
@@ -1,7 +1,6 @@
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 -from contextlib import contextmanager
4 from types import SimpleNamespace
5
6 import numpy as np
@@ -9,9 +8,7 @@ import pytest
8 import torch
9
10 from vllm.v1.attention.backends.utils import PAD_SLOT_ID
12 -from vllm.v1.worker.gpu.spec_decode.dflash import speculator as dflash_speculator
11 from vllm.v1.worker.gpu.spec_decode.dflash.speculator import (
14 - DFlashSpeculator,
12 prepare_dflash_inputs,
13 )
14
@@ -36,7 +33,6 @@ def _run_prepare(
33 input_buffers = SimpleNamespace(
34 input_ids=torch.full((max_num_tokens,), -1, dtype=torch.int32, device=device),
35 positions=torch.full((max_num_tokens,), -1, dtype=torch.int64, device=device),
39 - is_padding=torch.zeros(max_num_tokens, dtype=torch.bool, device=device),
36 query_start_loc=torch.full(
37 (max_num_reqs + 1,), -1, dtype=torch.int32, device=device
38 ),
@@ -144,114 +140,6 @@ def test_prepare_dflash_inputs_excludes_rejected_context_suffix():
140 assert out.temperature[2].item() == 1.0
141 assert out.seeds[2].item() == 17
142
147 - assert not out.input_buffers.is_padding[:3].any()
148 - assert out.input_buffers.is_padding[3:].all()
149 - assert out.input_buffers.input_ids[3:].cpu().tolist() == [0] * 13
150 - assert out.input_buffers.positions[3:].cpu().tolist() == [0] * 13
151 -
152 -
153 -def test_prepare_dflash_inputs_compacts_noncontiguous_request_slots():
154 - device = torch.device("cuda")
155 - max_num_reqs = 4
156 - max_num_tokens = 16
157 - num_speculative_steps = 3
158 - input_buffers = SimpleNamespace(
159 - input_ids=torch.full((max_num_tokens,), -1, dtype=torch.int32, device=device),
160 - positions=torch.full((max_num_tokens,), -1, dtype=torch.int64, device=device),
161 - is_padding=torch.zeros(max_num_tokens, dtype=torch.bool, device=device),
162 - query_start_loc=torch.full(
163 - (max_num_reqs + 1,), -1, dtype=torch.int32, device=device
164 - ),
165 - seq_lens=torch.full((max_num_reqs,), -1, dtype=torch.int32, device=device),
166 - )
167 - input_batch = SimpleNamespace(
168 - num_reqs=2,
169 - num_scheduled_tokens=np.array([4, 4], dtype=np.int32),
170 - positions=torch.tensor(
171 - [10, 11, 12, 13, 20, 21, 22, 23],
172 - dtype=torch.int64,
173 - device=device,
174 - ),
175 - query_start_loc=torch.tensor([0, 4, 8], dtype=torch.int32, device=device),
176 - # Active batch rows are compact, while request state remains in slots 3 and 1.
177 - idx_mapping=torch.tensor([3, 1], dtype=torch.int32, device=device),
178 - )
179 - query_slot_mapping = torch.full(
180 - (max_num_tokens,), -2, dtype=torch.int64, device=device
181 - )
182 - context_positions = torch.full(
183 - (max_num_tokens,), -1, dtype=torch.int64, device=device
184 - )
185 - context_slot_mapping = torch.full(
186 - (max_num_tokens,), -2, dtype=torch.int64, device=device
187 - )
188 - sample_indices = torch.full(
189 - (max_num_reqs * num_speculative_steps,),
190 - -1,
191 - dtype=torch.int64,
192 - device=device,
193 - )
194 - sample_pos = torch.full_like(sample_indices, -1)
195 - sample_idx_mapping = torch.full(
196 - sample_indices.shape, -1, dtype=torch.int32, device=device
197 - )
198 - temperature = torch.zeros(max_num_reqs, dtype=torch.float32, device=device)
199 - seeds = torch.zeros(max_num_reqs, dtype=torch.int64, device=device)
200 - input_temperature = torch.tensor(
201 - [0.0, 0.5, 0.0, 1.0], dtype=torch.float32, device=device
202 - )
203 - input_seeds = torch.tensor([0, 11, 0, 33], dtype=torch.int64, device=device)
204 - last_sampled = torch.tensor([0, 77, 0, 99], dtype=torch.int64, device=device)
205 - next_prefill_tokens = torch.zeros_like(last_sampled)
206 - block_table = torch.tensor(
207 - [[0, 0, 7, 8, 9, 10, 11, 12], [0, 0, 13, 14, 15, 16, 17, 18]],
208 - dtype=torch.int32,
209 - device=device,
210 - )
211 -
212 - prepare_dflash_inputs(
213 - input_buffers,
214 - query_slot_mapping,
215 - context_positions,
216 - context_slot_mapping,
217 - sample_indices,
218 - sample_pos,
219 - sample_idx_mapping,
220 - temperature,
221 - seeds,
222 - input_batch,
223 - torch.tensor([1, 1], dtype=torch.int32, device=device),
224 - torch.tensor([2, 1], dtype=torch.int32, device=device),
225 - last_sampled,
226 - next_prefill_tokens,
227 - input_temperature,
228 - input_seeds,
229 - block_table,
230 - 4,
231 - 0,
232 - 1,
233 - 1,
234 - 123,
235 - num_speculative_steps,
236 - num_speculative_steps,
237 - max_num_reqs,
238 - max_num_tokens,
239 - 128,
240 - sample_from_anchor=True,
241 - )
242 - torch.accelerator.synchronize()
243 -
244 - # Query rows follow compact batch order, but every persistent state lookup
245 - # follows idx_mapping instead of accidentally using the compact row index.
246 - assert input_buffers.input_ids[:6].cpu().tolist() == [99, 123, 123, 77, 123, 123]
247 - assert input_buffers.positions[:6].cpu().tolist() == [12, 13, 14, 23, 24, 25]
248 - assert sample_indices[:6].cpu().tolist() == [0, 1, 2, 3, 4, 5]
249 - assert sample_idx_mapping[:6].cpu().tolist() == [3, 3, 3, 1, 1, 1]
250 - assert temperature.cpu().tolist() == [0.0, 0.5, 0.0, 1.0]
251 - assert seeds.cpu().tolist() == [0, 11, 0, 33]
252 - assert not input_buffers.is_padding[:6].any()
253 - assert input_buffers.is_padding[6:].all()
254 -
143
144 def test_prepare_dflash_inputs_excludes_rejected_context_suffix_with_dcp():
145 out = _run_prepare(
@@ -286,40 +174,3 @@ def test_prepare_dflash_inputs_never_writes_the_null_block():
174 PAD_SLOT_ID,
175 PAD_SLOT_ID,
176 ]
289 -
290 -
291 -def test_dflash_forward_context_receives_draft_padding_mask(monkeypatch):
292 - device = torch.device("cuda")
293 - input_buffers = SimpleNamespace(
294 - input_ids=torch.tensor([11, 12, 0, 0], dtype=torch.int32, device=device),
295 - positions=torch.tensor([7, 8, 0, 0], dtype=torch.int64, device=device),
296 - is_padding=torch.tensor([False, False, True, True], device=device),
297 - )
298 - observed = None
299 -
300 - @contextmanager
301 - def fake_set_forward_context(*args, **kwargs):
302 - nonlocal observed
303 - observed = kwargs["is_padding"].clone()
304 - yield
305 -
306 - monkeypatch.setattr(
307 - dflash_speculator, "set_forward_context", fake_set_forward_context
308 - )
309 - speculator = SimpleNamespace(
310 - input_buffers=input_buffers,
311 - vllm_config=SimpleNamespace(),
312 - model=lambda **kwargs: kwargs["input_ids"],
313 - )
314 -
315 - result = DFlashSpeculator._run_model(
316 - speculator,
317 - num_tokens=4,
318 - attn_metadata=None,
319 - slot_mappings=None,
320 - num_tokens_across_dp=None,
321 - )
322 -
323 - assert result.tolist() == [11, 12, 0, 0]
324 - assert observed is not None
325 - assert observed.tolist() == [False, False, True, True]
tests/v1/worker/test_pp_utils.py
+3 -149
@@ -5,35 +5,16 @@
5 from unittest.mock import Mock
6
7 import numpy as np
8 -import pytest
9 -import torch
8
11 -from vllm.v1.worker.gpu import model_runner, pp_utils
9 +from vllm.v1.worker.gpu import pp_utils
10
11
14 -def _cuda_handler(max_sample_len=6):
15 - handler = object.__new__(pp_utils.PPHandler)
16 - handler.is_last_rank = True
17 - handler.disabled = False
18 - handler.max_sample_len = max_sample_len
19 - handler.last_rank = 1
20 - handler.broadcast_group = Mock()
21 - handler.device = torch.device("cuda")
22 - handler.main_stream = torch.cuda.current_stream()
23 - handler.broadcast_stream = torch.cuda.Stream()
24 - return handler
25 -
26 -
27 -def _batch(num_computed, prefill_len, num_scheduled, idx_mapping=None):
28 - num_reqs = len(num_computed)
29 - if idx_mapping is None:
30 - idx_mapping = list(range(num_reqs))
12 +def _batch(num_computed, prefill_len, num_scheduled):
13 return Mock(
32 - num_reqs=num_reqs,
14 + num_reqs=len(num_computed),
15 num_computed_tokens_np=np.array(num_computed, dtype=np.int32),
16 prefill_len_np=np.array(prefill_len, dtype=np.int32),
17 num_scheduled_tokens=np.array(num_scheduled, dtype=np.int32),
36 - idx_mapping=torch.tensor(idx_mapping, dtype=torch.int64),
18 )
19
20
@@ -100,130 +81,3 @@ def test_decode_row_ahead_of_a_prefill_chunk():
81
82 assert mask is not None
83 assert mask.tolist() == [True, False]
103 -
104 -
105 -def test_disabled_handler_skips_broadcast_and_receive(monkeypatch):
106 - """While disabled (warmup), neither side enqueues a broadcast op."""
107 - sent = []
108 - monkeypatch.setattr(
109 - pp_utils.torch.distributed,
110 - "broadcast",
111 - lambda *args, **kwargs: sent.append((args, kwargs)),
112 - )
113 -
114 - handler = object.__new__(pp_utils.PPHandler)
115 - handler.set_disabled(True)
116 -
117 - handler.is_last_rank = False
118 - assert handler.receive(Mock()) is False
119 -
120 - handler.is_last_rank = True
121 - assert handler.broadcast(Mock(), Mock(), Mock(), Mock()) is None
122 -
123 - assert sent == []
124 -
125 - handler.set_disabled(False)
126 - assert handler.disabled is False
127 -
128 -
129 -def test_alloc_combined_keeps_unbind_views_16_byte_aligned():
130 - """Triton specializes on pointer alignment: an unaligned `num_rejected`
131 - would compile a second `_post_update_kernel` variant at serving time,
132 - where the in-flight broadcast NCCL kernel can block the module load."""
133 - for num_reqs in range(1, 9):
134 - combined = pp_utils._alloc_combined(num_reqs, torch.device("cpu"))
135 - num_sampled, num_rejected = combined.unbind(dim=0)
136 - assert num_sampled.data_ptr() % 16 == 0
137 - assert num_rejected.data_ptr() % 16 == 0
138 - assert combined.shape[1] >= num_reqs
139 -
140 -
141 -def test_warmup_pp_decode_update_matches_serving_specialization(monkeypatch):
142 - """The warmup launch must hit the same triton specialization as serving.
143 -
144 - A mismatch means the first real ``update_pp_decode_requests`` recompiles
145 - mid-serving, where the in-flight broadcast NCCL kernel blocks the CUDA
146 - module load and deadlocks the pipeline.
147 - """
148 - calls = []
149 - monkeypatch.setattr(model_runner, "post_update", lambda *args: calls.append(args))
150 -
151 - runner = object.__new__(model_runner.GPUModelRunner)
152 - runner.device = torch.device("cpu")
153 - runner.pp_handler = Mock(max_sample_len=3)
154 - runner.req_states = Mock()
155 -
156 - runner.warmup_pp_decode_update()
157 -
158 - assert len(calls) == 1
159 - args = calls[0]
160 - idx_mapping, _, _, output_bin_counts = args[:4]
161 - sampled_tokens, num_sampled, num_rejected, query_start_loc = args[4:8]
162 - broadcast_drafts, draft_tokens_out = args[10:12]
163 - assert idx_mapping.tolist() == [-1] and idx_mapping.dtype == torch.int64
164 - assert output_bin_counts is None
165 - assert query_start_loc is None
166 - assert sampled_tokens.shape == (1, 3) and sampled_tokens.dtype == torch.int64
167 - assert num_sampled.dtype == torch.int32
168 - assert num_rejected.dtype == torch.int32
169 - # Spec-enabled PP handlers receive drafts over the broadcast; the warmup
170 - # must compile that specialization (non-None draft pointers) too.
171 - assert broadcast_drafts.shape == (1, 2) and broadcast_drafts.dtype == torch.int64
172 - assert draft_tokens_out is runner.req_states.draft_tokens
173 -
174 -
175 -@pytest.mark.skipif(not torch.cuda.is_available(), reason="needs a CUDA stream")
176 -def test_broadcast_pads_plain_sampler_rows_to_max_sample_len(monkeypatch):
177 - """The wire shape must not depend on whether the batch carried drafts:
178 - the receiver always allocates [num_reqs, max_sample_len], and a NCCL
179 - broadcast with mismatched counts hangs the receiver."""
180 - sent = []
181 - monkeypatch.setattr(
182 - pp_utils.torch.distributed,
183 - "broadcast",
184 - lambda tensor, **kw: sent.append(tensor),
185 - )
186 - handler = _cuda_handler()
187 - batch = _batch(num_computed=[10], prefill_len=[8], num_scheduled=[1])
188 -
189 - handler.broadcast(
190 - torch.zeros(1, 1, dtype=torch.int64, device="cuda"), # plain sampler
191 - torch.ones(1, dtype=torch.int32, device="cuda"),
192 - torch.zeros(1, dtype=torch.int32, device="cuda"),
193 - batch,
194 - )
195 -
196 - assert sent[0].shape == (1, 6)
197 - assert sent[1].shape == (2, 4)
198 - torch.accelerator.synchronize()
199 -
200 -
201 -@pytest.mark.skipif(not torch.cuda.is_available(), reason="needs a CUDA stream")
202 -def test_broadcast_drafts_gathers_fresh_rows_from_the_table(monkeypatch):
203 - sent = []
204 - monkeypatch.setattr(
205 - pp_utils.torch.distributed,
206 - "broadcast",
207 - lambda tensor, **kw: sent.append(tensor),
208 - )
209 - handler = _cuda_handler()
210 - # The batch's single row maps to request-state row 2.
211 - batch = _batch(num_computed=[10], prefill_len=[8], num_scheduled=[1])
212 - batch.idx_mapping = torch.tensor([2], dtype=torch.int64, device="cuda")
213 - table = torch.arange(20, dtype=torch.int64, device="cuda").view(4, 5)
214 -
215 - handler.broadcast_drafts(table, batch)
216 -
217 - assert sent[0].shape == (1, 5)
218 - # The payload is a gather into a fresh tensor: propose() overwrites its
219 - # persistent buffer on the next step, possibly before this send completes.
220 - assert sent[0].data_ptr() != table.data_ptr()
221 - assert sent[0].cpu().tolist() == [table[2].cpu().tolist()]
222 -
223 - # An all-prefill batch sends nothing (receive() enqueues nothing either).
224 - sent.clear()
225 - prefill_batch = _batch(num_computed=[0], prefill_len=[4096], num_scheduled=[448])
226 - prefill_batch.idx_mapping = prefill_batch.idx_mapping.cuda()
227 - handler.broadcast_drafts(table, prefill_batch)
228 - assert sent == []
229 - torch.accelerator.synchronize()
vllm/model_executor/layers/fused_moe/router/dsv4_topk.py
-18
@@ -41,7 +41,6 @@ if current_platform.is_cuda():
41 def _dsv4_topk_kernel(
42 gating_output_ptr,
43 correction_bias_ptr,
44 - is_padding_ptr,
44 topk_weights_ptr,
45 topk_ids_ptr,
46 routed_scaling_factor,
@@ -50,7 +49,6 @@ if current_platform.is_cuda():
49 image_sentinel_lo,
50 NUM_EXPERTS: tl.constexpr,
51 BLOCK_N: tl.constexpr,
53 - HAS_PADDING: tl.constexpr,
52 HAS_VL: tl.constexpr,
53 launch_pdl: tl.constexpr,
54 ):
@@ -108,11 +106,6 @@ if current_platform.is_cuda():
106 output_mask = topk_offsets < 6
107 output_offsets = row * 6 + topk_offsets
108
111 - if HAS_PADDING:
112 - is_padding = tl.load(is_padding_ptr + row)
113 - selected_weights = tl.where(is_padding, 0.0, selected_weights)
114 - selected_ids = tl.where(is_padding, -1, selected_ids)
115 -
109 if launch_pdl:
110 tl.extra.cuda.gdc_launch_dependents()
111
@@ -125,20 +118,11 @@ def dsv4_topk(
118 correction_bias: torch.Tensor,
119 indices_dtype: torch.dtype,
120 routed_scaling_factor: float,
128 - is_padding: torch.Tensor | None = None,
121 input_ids: torch.Tensor | None = None,
122 bias_vl: torch.Tensor | None = None,
123 image_sentinel_lo: int = 0,
124 ) -> tuple[torch.Tensor, torch.Tensor]:
125 num_tokens, num_experts = gating_output.shape
134 - if is_padding is not None:
135 - assert is_padding.dtype == torch.bool
136 - assert is_padding.shape == (num_tokens,)
137 - assert is_padding.device == gating_output.device
138 - assert is_padding.is_contiguous()
139 - assert indices_dtype in (torch.int32, torch.int64), (
140 - "Padding requires a signed indices dtype for the -1 sentinel."
141 - )
126 has_vl = bias_vl is not None and image_sentinel_lo > 0
127 if bias_vl is not None:
128 assert input_ids is not None, "bias_vl routing requires input_ids"
@@ -152,7 +136,6 @@ def dsv4_topk(
136 _dsv4_topk_kernel[(num_tokens,)](
137 gating_output,
138 correction_bias,
155 - is_padding,
139 topk_weights,
140 topk_ids,
141 routed_scaling_factor,
@@ -161,7 +144,6 @@ def dsv4_topk(
144 image_sentinel_lo,
145 NUM_EXPERTS=num_experts,
146 BLOCK_N=triton.next_power_of_2(num_experts),
164 - HAS_PADDING=is_padding is not None,
147 HAS_VL=has_vl,
148 num_warps=1,
149 launch_pdl=current_platform.is_arch_support_pdl(),
vllm/model_executor/layers/fused_moe/router/fused_topk_bias_router.py
-5
@@ -148,13 +148,9 @@ def fused_topk_bias(
148 )
149
150 output_indices_dtype = torch.int32 if indices_type is None else indices_type
151 - padding_mask = _get_padding_mask(gating_output.shape[0])
151 if (
152 scoring_func == "sqrtsoftplus"
153 and hash_indices_table is None
155 - # dsv4_topk marks padded rows with a -1 sentinel, which unsigned
156 - # indices cannot represent; keep the generic path for that pair.
157 - and (padding_mask is None or output_indices_dtype != torch.uint32)
154 and can_use_dsv4_topk(
155 gating_output,
156 e_score_correction_bias,
@@ -169,7 +165,6 @@ def fused_topk_bias(
165 e_score_correction_bias,
166 output_indices_dtype,
167 routed_scaling_factor,
172 - is_padding=padding_mask,
168 input_ids=input_tokens,
169 bias_vl=bias_vl,
170 image_sentinel_lo=image_sentinel_lo,
vllm/v1/worker/gpu/input_batch.py
-26
@@ -555,11 +555,6 @@ def _post_update_kernel(
555 all_token_ids_ptr,
556 all_token_ids_stride,
557 total_len_ptr,
558 - broadcast_drafts_ptr,
559 - broadcast_drafts_stride,
560 - draft_tokens_ptr,
561 - draft_tokens_stride,
562 - num_spec,
558 ):
559 req_id = tl.program_id(0)
560 req_state_idx = tl.load(idx_mapping_ptr + req_id)
@@ -567,17 +562,6 @@ def _post_update_kernel(
562 # Filter rows with negative index entries.
563 return
564
570 - if broadcast_drafts_ptr is not None:
571 - # PP path: adopt the draft tokens proposed by the last rank's
572 - # speculator so the next verification step embeds the real drafts.
573 - for i in range(num_spec):
574 - token_id = tl.load(
575 - broadcast_drafts_ptr + req_id * broadcast_drafts_stride + i
576 - )
577 - tl.store(
578 - draft_tokens_ptr + req_state_idx * draft_tokens_stride + i, token_id
579 - )
580 -
565 total_len = tl.load(total_len_ptr + req_state_idx)
566 num_sampled = tl.load(num_sampled_ptr + req_id)
567 if num_sampled > 0:
@@ -638,11 +622,6 @@ def post_update(
622 all_token_ids: torch.Tensor,
623 # [max_num_reqs]
624 total_len: torch.Tensor,
641 - # [num_reqs, num_spec]; drafts broadcast from the last PP rank. Only
642 - # passed on non-last PP ranks, which never run the speculator.
643 - broadcast_drafts: torch.Tensor | None = None,
644 - # [max_num_reqs, num_spec]
645 - draft_tokens_out: torch.Tensor | None = None,
625 ) -> None:
626 num_reqs = idx_mapping.shape[0]
627 _post_update_kernel[(num_reqs,)](
@@ -659,11 +638,6 @@ def post_update(
638 all_token_ids,
639 all_token_ids.stride(0),
640 total_len,
662 - broadcast_drafts,
663 - broadcast_drafts.stride(0) if broadcast_drafts is not None else 0,
664 - draft_tokens_out,
665 - draft_tokens_out.stride(0) if draft_tokens_out is not None else 0,
666 - broadcast_drafts.shape[1] if broadcast_drafts is not None else 0,
641 num_warps=1,
642 )
643
vllm/v1/worker/gpu/model_runner.py
+5 -63
@@ -862,7 +862,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
862 # dummy run the eagle speculator's propose to ensure DP/EP sync.
863 if self.speculator is not None:
864 assert self.sampler is not None
865 - assert hidden_states is not None
865 self.step_timing.drafter_start()
866 mm_inputs: tuple[list[torch.Tensor], torch.Tensor] | None = None
867 if self.speculator.supports_mm_inputs:
@@ -878,8 +877,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
877 spec_hidden_states = hidden_states
878 if hasattr(self.model, "get_mtp_target_hidden_states"):
879 pre_hc_hidden_states = self.model.get_mtp_target_hidden_states()
881 - if pre_hc_hidden_states is not None:
882 - spec_hidden_states = pre_hc_hidden_states[: hidden_states.shape[0]]
880 + spec_hidden_states = pre_hc_hidden_states[: hidden_states.shape[0]] # type: ignore[union-attr]
881 if isinstance(self.sampler, GPUWatermarkSampler):
882 self.speculator.prepare_watermarking(
883 self.sampler._get_contexts(input_batch.idx_mapping),
@@ -1111,44 +1109,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1109 if outputs is not None:
1110 self.postprocess_sampled(**outputs)
1111
1114 - def warmup_pp_decode_update(self) -> None:
1115 - """JIT-compile the kernel behind ``update_pp_decode_requests``.
1116 -
1117 - That path only runs on real steps, so the warmup steps never reach it
1118 - on non-last PP ranks. Its first triton compile must not happen
1119 - mid-serving: the in-flight sampled-token broadcast keeps a NCCL kernel
1120 - spinning on this device, which blocks the CUDA module load and
1121 - deadlocks the pipeline. An all -1 idx_mapping makes this a no-op.
1122 - The freshly allocated int32 tensors are 16-byte aligned, matching the
1123 - padded views `PPHandler` produces at serving time (triton specializes
1124 - on pointer alignment).
1125 - """
1126 - assert self.pp_handler is not None
1127 - num_spec = self.pp_handler.max_sample_len - 1
1128 - broadcast_drafts = (
1129 - torch.zeros((1, num_spec), dtype=torch.int64, device=self.device)
1130 - if num_spec > 0
1131 - else None
1132 - )
1133 - post_update(
1134 - torch.full((1,), -1, dtype=torch.int64, device=self.device),
1135 - self.req_states.num_computed_tokens.gpu,
1136 - self.req_states.last_sampled_tokens,
1137 - None,
1138 - torch.zeros(
1139 - (1, self.pp_handler.max_sample_len),
1140 - dtype=torch.int64,
1141 - device=self.device,
1142 - ),
1143 - torch.zeros(1, dtype=torch.int32, device=self.device),
1144 - torch.zeros(1, dtype=torch.int32, device=self.device),
1145 - None,
1146 - self.req_states.all_token_ids.gpu,
1147 - self.req_states.total_len.gpu,
1148 - broadcast_drafts,
1149 - self.req_states.draft_tokens if broadcast_drafts is not None else None,
1150 - )
1151 -
1112 def add_requests(self, scheduler_output: SchedulerOutput) -> None:
1113 for new_req_data in scheduler_output.scheduled_new_reqs:
1114 assert new_req_data.prefill_token_ids is not None
@@ -1630,7 +1590,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1590 num_sampled: torch.Tensor,
1591 num_rejected: torch.Tensor,
1592 query_start_loc: torch.Tensor | None = None,
1633 - broadcast_drafts: torch.Tensor | None = None,
1593 ) -> None:
1594 # Update the number of computed tokens.
1595 output_bin_counts = None
@@ -1648,8 +1607,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1607 query_start_loc,
1608 self.req_states.all_token_ids.gpu,
1609 self.req_states.total_len.gpu,
1651 - broadcast_drafts,
1652 - self.req_states.draft_tokens if broadcast_drafts is not None else None,
1610 )
1611
1612 self.model_state.postprocess_state(
@@ -2170,10 +2127,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
2127 spec_hidden_states = draft_hidden_states
2128 if hasattr(self.model, "get_mtp_target_hidden_states"):
2129 pre_hc_hidden_states = self.model.get_mtp_target_hidden_states()
2173 - if pre_hc_hidden_states is not None:
2174 - spec_hidden_states = pre_hc_hidden_states[
2175 - : draft_hidden_states.size(0)
2176 - ]
2130 + spec_hidden_states = pre_hc_hidden_states[: draft_hidden_states.size(0)]
2131 if isinstance(self.sampler, GPUWatermarkSampler):
2132 self.speculator.prepare_watermarking(
2133 self.sampler._get_contexts(input_batch.idx_mapping),
@@ -2195,16 +2149,8 @@ class GPUModelRunner(LoRAModelRunnerMixin):
2149 dp_sync=dp_sync,
2150 mm_inputs=mm_inputs,
2151 )
2198 - if draft_tokens is not None:
2199 - self.req_states.draft_tokens[input_batch.idx_mapping] = draft_tokens
2200 - if self.pp_handler is not None:
2201 - # Earlier stages never run the speculator; ship the
2202 - # drafts so their next verification step embeds the real
2203 - # draft tokens instead of stale buffer contents.
2204 - self.pp_handler.broadcast_drafts(
2205 - self.req_states.draft_tokens, input_batch
2206 - )
2207 - if draft_tokens is not None and self.adaptive_verification is not None:
2152 + self.req_states.draft_tokens[input_batch.idx_mapping] = draft_tokens
2153 + if self.adaptive_verification is not None:
2154 self.adaptive_verification.record_confidences(
2155 self.speculator.draft_token_confidence_probs, input_batch
2156 )
@@ -2216,11 +2162,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
2162 input_batch,
2163 self.req_states.draft_tokens[input_batch.idx_mapping],
2164 )
2219 - if self.pp_handler is not None and self.speculator is None:
2220 - # When a speculator ran, the propose() path above already
2221 - # broadcast the fresh drafts. Broadcasting here as well would
2222 - # double-post on the pp_broadcast group and misalign the
2223 - # recv FIFO on earlier stages, hanging the pipeline.
2165 + if self.pp_handler is not None:
2166 self.pp_handler.broadcast_drafts(
2167 self.req_states.draft_tokens, input_batch
2168 )
vllm/v1/worker/gpu/pp_utils.py
+20 -65
@@ -46,18 +46,6 @@ def compute_need_sampled_mask(input_batch: InputBatch) -> np.ndarray | None:
46 return produces_sample if produces_sample.any() else None
47
48
49 -def _alloc_combined(num_reqs: int, device: torch.device) -> torch.Tensor:
50 - """Allocate the (2, N) int32 buffer broadcast alongside sampled tokens.
51 -
52 - The inner dim is padded to a multiple of 4 so that both `unbind` views
53 - stay 16-byte aligned for any `num_reqs`: triton specializes on pointer
54 - alignment, and a misaligned `num_rejected` would JIT-compile a second
55 - `_post_update_kernel` variant at serving time. The padding is broadcast
56 - but never read. Sender and receiver must both use this allocation.
57 - """
58 - return torch.empty(2, -(-num_reqs // 4) * 4, dtype=torch.int32, device=device)
59 -
60 -
49 class PPHandler:
50 """Runs the PP sampled-token broadcast/recv on a side stream so the
51 default stream isn't gated by the matching peer call. Step T's recv is
@@ -98,14 +86,6 @@ class PPHandler:
86 )
87 self.aux_hidden_state_relay_keys: tuple[str, ...] = ()
88
101 - # Warmup steps run the pipeline with synthetic batches whose outputs are
102 - # discarded; the sampled-token broadcast is disabled there so its
103 - # side-stream NCCL ops cannot overlap the next step's activation p2p.
104 - self.disabled = False
105 -
106 - def set_disabled(self, disabled: bool) -> None:
107 - self.disabled = disabled
108 -
89 def on_req_idx_freed(self, req_idx: int) -> None:
90 self.req_idx_gen_np[req_idx] += 1
91
@@ -177,15 +157,29 @@ class PPHandler:
157 num_sampled=slot.num_sampled,
158 num_rejected=slot.num_rejected,
159 idx_mapping=idx_mapping,
180 - broadcast_drafts=slot.draft_tokens,
160 )
161
162 + def broadcast_drafts(
163 + self, draft_tokens: torch.Tensor, input_batch: InputBatch
164 + ) -> None:
165 + """Broadcast draft proposals so non-last ranks can embed real token ids."""
166 + assert self.is_last_rank
167 + if compute_need_sampled_mask(input_batch) is None:
168 + return
169 + with torch.cuda.stream(self.broadcast_stream):
170 + self.broadcast_stream.wait_stream(self.main_stream)
171 + send = draft_tokens[input_batch.idx_mapping].contiguous()
172 + # Must record the idx_mapping tensor since it was allocated
173 + # on the main stream.
174 + input_batch.idx_mapping.record_stream(self.broadcast_stream)
175 + torch.distributed.broadcast(
176 + send, src=self.last_rank, group=self.broadcast_group
177 + )
178 +
179 def receive(self, input_batch: InputBatch) -> bool:
180 """Returns True iff sampled tokens need to be gathered from *all*
181 requests in the batch."""
182 assert not self.is_last_rank
187 - if self.disabled:
188 - return False
183 need_sampled_mask = compute_need_sampled_mask(input_batch)
184 if need_sampled_mask is None:
185 # Leave this step's reserved slot as None.
@@ -201,7 +195,7 @@ class PPHandler:
195 sampled_tokens = torch.empty(
196 num_reqs, self.max_sample_len, dtype=torch.int64, device=self.device
197 )
204 - combined = _alloc_combined(num_reqs, self.device)
198 + combined = torch.empty(2, num_reqs, dtype=torch.int32, device=self.device)
199 torch.distributed.broadcast(
200 sampled_tokens, src=self.last_rank, group=self.broadcast_group
201 )
@@ -219,7 +213,6 @@ class PPHandler:
213 torch.distributed.broadcast(
214 draft_tokens, src=self.last_rank, group=self.broadcast_group
215 )
222 -
216 event = self.broadcast_stream.record_event()
217 num_sampled, num_rejected = combined.unbind(dim=0)
218 # Must record_stream since these were allocated on broadcast stream but
@@ -249,16 +242,11 @@ class PPHandler:
242 input_batch: InputBatch,
243 ) -> None:
244 assert self.is_last_rank
252 - if self.disabled:
253 - return
254 - mask = compute_need_sampled_mask(input_batch)
255 - if mask is None:
245 + if compute_need_sampled_mask(input_batch) is None:
246 # No request needs sampled outputs for a subsequent decode step.
247 return
248
249 assert sampled_token_ids.dtype == torch.int64
260 - assert num_sampled.dtype == torch.int32
261 - assert num_rejected.dtype == torch.int32
250
251 if current_platform.is_xpu():
252 self.main_stream.synchronize()
@@ -274,42 +262,9 @@ class PPHandler:
262 src=self.last_rank,
263 group=self.broadcast_group,
264 )
277 - combined = _alloc_combined(num_sampled.shape[0], self.device)
278 - combined[0, : num_sampled.shape[0]] = num_sampled
279 - combined[1, : num_sampled.shape[0]] = num_rejected
265 + combined = torch.stack((num_sampled, num_rejected), dim=0)
266 torch.distributed.broadcast(
267 combined, src=self.last_rank, group=self.broadcast_group
268 )
269 for tensor in (sampled_token_ids, num_sampled, num_rejected):
270 tensor.record_stream(self.broadcast_stream)
285 -
286 - def broadcast_drafts(
287 - self, draft_token_table: torch.Tensor, input_batch: InputBatch
288 - ) -> None:
289 - """Broadcast the speculator's freshly proposed draft tokens.
290 -
291 - Runs after propose() on the last rank; the send is stream-ordered
292 - after broadcast()'s sends, matching receive()'s enqueue order. The
293 - payload is gathered from the runner's draft table (just updated from
294 - propose()'s output) into a fresh compact tensor: the speculator
295 - overwrites its own persistent buffer on the next step, possibly
296 - before this async send completes.
297 - """
298 - assert self.is_last_rank
299 - if self.disabled or self.max_sample_len == 1:
300 - return
301 - if compute_need_sampled_mask(input_batch) is None:
302 - return
303 - assert draft_token_table.dtype == torch.int64
304 - assert draft_token_table.shape[1] == self.max_sample_len - 1
305 -
306 - # Gather on the main stream so the payload is ordered after this
307 - # step's table update and before any later one.
308 - drafts = draft_token_table[input_batch.idx_mapping]
309 - assert drafts.shape[0] == input_batch.num_reqs
310 - with torch.cuda.stream(self.broadcast_stream):
311 - self.broadcast_stream.wait_stream(self.main_stream)
312 - torch.distributed.broadcast(
313 - drafts, src=self.last_rank, group=self.broadcast_group
314 - )
315 - drafts.record_stream(self.broadcast_stream)
vllm/v1/worker/gpu/spec_decode/dflash/speculator.py
-7
@@ -256,7 +256,6 @@ class DFlashSpeculator(DraftModelSpeculator):
256 num_tokens_across_dp=num_tokens_across_dp,
257 slot_mapping=slot_mappings,
258 batch_descriptor=batch_descriptor,
259 - is_padding=self.input_buffers.is_padding[:num_tokens],
259 ):
260 last_hidden_states = self.model(
261 input_ids=self.input_buffers.input_ids[:num_tokens],
@@ -488,7 +487,6 @@ def _prepare_dflash_inputs_kernel(
487 # Outputs
488 out_input_ids_ptr,
489 out_query_positions_ptr,
491 - out_is_padding_ptr,
490 out_query_start_loc_ptr,
491 out_seq_lens_ptr,
492 out_query_slot_mapping_ptr,
@@ -619,7 +617,6 @@ def _prepare_dflash_inputs_kernel(
617 )
618
619 tl.store(out_input_ids_ptr + query_idx, input_id, mask=is_query)
622 - tl.store(out_is_padding_ptr + query_idx, False, mask=is_query)
620 clamped_query_pos = tl.minimum(query_pos, max_model_len - 1)
621 tl.store(out_query_positions_ptr + query_idx, clamped_query_pos, mask=is_query)
622 tl.store(out_query_slot_mapping_ptr + query_idx, q_slot, mask=is_query)
@@ -682,9 +679,6 @@ def _prepare_dflash_inputs_kernel(
679 for i in range(q_pad_start, max_num_tokens, BLOCK_SIZE):
680 block = i + tl.arange(0, BLOCK_SIZE)
681 mask = block < max_num_tokens
685 - tl.store(out_input_ids_ptr + block, 0, mask=mask)
686 - tl.store(out_query_positions_ptr + block, 0, mask=mask)
687 - tl.store(out_is_padding_ptr + block, True, mask=mask)
682 tl.store(out_query_slot_mapping_ptr + block, PAD_SLOT_ID, mask=mask)
683
684
@@ -736,7 +730,6 @@ def prepare_dflash_inputs(
730 _prepare_dflash_inputs_kernel[(num_reqs, num_blocks)](
731 input_buffers.input_ids,
732 input_buffers.positions,
739 - input_buffers.is_padding,
733 input_buffers.query_start_loc,
734 input_buffers.seq_lens,
735 query_slot_mapping,
vllm/v1/worker/gpu/warmup.py
-5
@@ -445,11 +445,6 @@ def _warmup_kernels(
445 for step_indices, step_spec_flags in decode_steps:
446 _run_decode_step(step_indices, step_spec_flags)
447
448 - # The deferred PP post-update path only runs on real steps, so the steps
449 - # above never JIT-compile its kernel on non-last ranks.
450 - if not model_runner.is_last_pp_rank and model_runner.pp_handler is not None:
451 - model_runner.warmup_pp_decode_update()
452 -
448 # Clean up - process finish_req_ids.
449 cleanup_output = SchedulerOutput.make_empty()
450 cleanup_output.finished_req_ids = set(req_ids)
vllm/v1/worker/gpu_worker.py
-12
@@ -804,15 +804,6 @@ class Worker(WorkerBase):
804
805 @instrument(span_name="Warmup (GPU)")
806 def compile_or_warm_up_model(self) -> CompilationTimes:
807 - # All warmup phases below run synthetic steps whose sampled outputs are
808 - # discarded. The PP sampled-token broadcast would carry no payload, and
809 - # its side-stream NCCL ops can overlap the next step's activation p2p
810 - # and deadlock the pipeline, so keep it disabled for the whole warmup
811 - # window and restore it before serving.
812 - pp_handler = getattr(self.model_runner, "pp_handler", None)
813 - if pp_handler is not None:
814 - pp_handler.set_disabled(True)
815 -
807 warmup_sizes: list[int] = []
808
809 if (
@@ -991,9 +982,6 @@ class Worker(WorkerBase):
982 # gate so subsequent `execute_model` / `sample_tokens` calls enforce it.
983 enable_gpu_sync_check()
984
994 - if pp_handler is not None:
995 - pp_handler.set_disabled(False)
996 -
985 return CompilationTimes(
986 language_model=self.compilation_config.compilation_time,
987 encoder=self.compilation_config.encoder_compilation_time,