[Bugfix] DeepEP-V2: expert_tokens_meta must be None on the decode/cudagraph path (empty recv_expert_num_tokens) (#52632)

Signed-off-by: Anton Alexander <dmvevents@gmail.com> Signed-off-by: Roger Wang <hey@rogerw.io> Co-authored-by: Roger Wang <hey@rogerw.io>

Anton A committed Aug 20, 2026 at 04:11 UTC 14617c2b6c1257ac0d6c7b5e05b195ca30013827
1 file changed +13 -4
vllm/model_executor/layers/fused_moe/prepare_finalize/deepep_v2.py
+13 -4
@@ -210,14 +210,23 @@ class DeepEPV2PrepareAndFinalize(mk.FusedMoEPrepareAndFinalizeModular):
210 else:
211 expert_x, expert_x_scale = recv_x, None
212
213 - expert_tokens_meta = mk.ExpertTokensMetadata.make_from_list(
214 - recv_expert_num_tokens,
215 - device=expert_x.device,
216 - )
213 + if recv_expert_num_tokens:
214 + expert_tokens_meta = mk.ExpertTokensMetadata.make_from_list(
215 + recv_expert_num_tokens,
216 + device=expert_x.device,
217 + )
218 + else:
219 + # Decode/cudagraph path (do_cpu_sync=False) skips the CPU sync and
220 + # leaves recv_expert_num_tokens empty. A present-but-empty
221 + # ExpertTokensMetadata violates the decode-mode contract above
222 + # (expert_tokens_meta must be None) and crashes DeepEP combine
223 + # during profile_run when CUDA graphs are enabled.
224 + expert_tokens_meta = None
225
226 if recv_topk_idx is None:
227 # do_expand=True (prefill mode): build topk_ids from
228 # per-expert token counts.
229 + assert expert_tokens_meta is not None
230 total_tokens = sum(recv_expert_num_tokens)
231 if total_tokens > 0:
232 recv_topk_idx = torch.repeat_interleave(