[Bugfix][Refactor] Keep Qwen3Next layer boundaries sequence parallel (#50685)
Signed-off-by: kunzh <RimeK20@outlook.com> Signed-off-by: zjy0516 <riverclouds.zhu@qq.com> Co-authored-by: OpenAI Codex <codex@openai.com> Co-authored-by: zjy0516 <riverclouds.zhu@qq.com>
kzwrime committed
Aug 14, 2026 at 11:58 UTC
c05d75aaa95cf89f547503044c1921625905085d
4 files changed
+75
-111
vllm/model_executor/models/interns2_mobius.py
+22
-35
@@ -43,7 +43,7 @@ from .qwen3_5 import (
43
Qwen3_5RMSNorm,
44
)
45
from .qwen3_5_mtp import Qwen3_5MoeMTP
46
-from .qwen3_next import Qwen3NextAttention, _all_gather_hidden_and_residual
46
+from .qwen3_next import Qwen3NextAttention
47
from .qwen3_vl import (
48
Qwen3_VisionTransformer,
49
Qwen3VLDummyInputsBuilder,
@@ -233,11 +233,6 @@ class InternS2MobiusDecoderLayer(nn.Module):
233
meta_mlp: nn.ModuleList,
234
) -> tuple[torch.Tensor, torch.Tensor]:
235
full_num_tokens = positions.shape[-1]
236
- input_is_sequence_parallel = (
237
- self.use_attn_reduce_scatter_for_moe
238
- and residual is not None
239
- and hidden_states.shape[0] != full_num_tokens
240
- )
236
237
if residual is None:
238
residual = hidden_states
@@ -245,7 +240,7 @@ class InternS2MobiusDecoderLayer(nn.Module):
240
else:
241
hidden_states, residual = self.input_layernorm(hidden_states, residual)
242
248
- if input_is_sequence_parallel:
243
+ if self.use_attn_reduce_scatter_for_moe:
244
hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
245
hidden_states = hidden_states[:full_num_tokens]
246
@@ -266,8 +261,6 @@ class InternS2MobiusDecoderLayer(nn.Module):
261
sp_pad = (-hidden_states.shape[0]) % tp_world_size
262
hidden_states = torch.nn.functional.pad(hidden_states, (0, 0, 0, sp_pad))
263
hidden_states = tensor_model_parallel_reduce_scatter(hidden_states, 0)
269
- if not input_is_sequence_parallel:
270
- residual = sequence_parallel_chunk(residual)
264
265
hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
266
block_idx = self.layer_idx % self.num_blocks
@@ -343,6 +336,10 @@ class InternS2MobiusModel(nn.Module, EagleModelMixin):
336
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
337
return self.embed_tokens(input_ids)
338
339
+ @property
340
+ def use_sequence_parallel(self) -> bool:
341
+ return self.layers[self.start_layer].use_attn_reduce_scatter_for_moe
342
+
343
def forward(
344
self,
345
input_ids: torch.Tensor | None,
@@ -363,36 +360,21 @@ class InternS2MobiusModel(nn.Module, EagleModelMixin):
360
residual = intermediate_tensors["residual"]
361
362
full_num_tokens = positions.shape[-1]
363
+ if self.use_sequence_parallel:
364
+ hidden_states = sequence_parallel_chunk(hidden_states)
365
+ assert residual is None
366
+
367
aux_hidden_states = self._maybe_add_hidden_state([], 0, hidden_states, residual)
368
for layer_idx, layer in enumerate(
369
islice(self.layers, self.start_layer, self.end_layer),
370
start=self.start_layer,
371
):
371
- if (
372
- hidden_states.shape[0] != full_num_tokens
373
- and not layer.use_attn_reduce_scatter_for_moe
374
- ):
375
- hidden_states, residual = _all_gather_hidden_and_residual(
376
- hidden_states,
377
- residual,
378
- full_num_tokens,
379
- self.config.hidden_size,
380
- )
372
hidden_states, residual = layer(
373
positions=positions,
374
hidden_states=hidden_states,
375
residual=residual,
376
meta_mlp=self.meta_mlp,
377
)
387
- if (layer_idx + 1) in self.aux_hidden_state_layers and hidden_states.shape[
388
- 0
389
- ] != full_num_tokens:
390
- hidden_states, residual = _all_gather_hidden_and_residual(
391
- hidden_states,
392
- residual,
393
- full_num_tokens,
394
- self.config.hidden_size,
395
- )
378
self._maybe_add_hidden_state(
379
aux_hidden_states,
380
layer_idx + 1,
@@ -404,14 +386,19 @@ class InternS2MobiusModel(nn.Module, EagleModelMixin):
386
return IntermediateTensors(
387
{"hidden_states": hidden_states, "residual": residual}
388
)
407
- if hidden_states.shape[0] != full_num_tokens:
408
- hidden_states, residual = _all_gather_hidden_and_residual(
409
- hidden_states,
410
- residual,
411
- full_num_tokens,
412
- self.config.hidden_size,
413
- )
389
hidden_states, _ = self.norm(hidden_states, residual)
390
+ if self.use_sequence_parallel:
391
+ if aux_hidden_states:
392
+ hidden_size = hidden_states.shape[-1]
393
+ hidden_states = torch.cat([hidden_states, *aux_hidden_states], dim=-1)
394
+ hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
395
+ hidden_states = hidden_states[:full_num_tokens]
396
+ hidden_states, *aux_hidden_states = hidden_states.split(
397
+ hidden_size, dim=-1
398
+ )
399
+ else:
400
+ hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
401
+ hidden_states = hidden_states[:full_num_tokens]
402
if aux_hidden_states:
403
return hidden_states, aux_hidden_states
404
return hidden_states
vllm/model_executor/models/qwen3_5_mtp.py
+9
-10
@@ -10,7 +10,7 @@ from torch import nn
10
from vllm._aiter_ops import rocm_aiter_ops
11
from vllm.compilation.decorators import support_torch_compile
12
from vllm.config import VllmConfig, get_current_vllm_config
13
-from vllm.distributed.parallel_state import get_pp_group
13
+from vllm.distributed import get_pp_group, tensor_model_parallel_all_gather
14
from vllm.logger import init_logger
15
from vllm.model_executor.layers.linear import ColumnParallelLinear
16
from vllm.model_executor.layers.logits_processor import LogitsProcessor
@@ -26,9 +26,9 @@ from vllm.model_executor.models.qwen3_5 import (
26
)
27
from vllm.model_executor.models.qwen3_next import (
28
QwenNextMixtureOfExperts,
29
- _all_gather_hidden_and_residual,
29
_is_shared_expert_fse_compatible,
30
)
31
+from vllm.model_executor.models.utils import sequence_parallel_chunk
32
from vllm.sequence import IntermediateTensors
33
from vllm.transformers_utils.configs.qwen3_5 import Qwen3_5TextConfig
34
from vllm.transformers_utils.configs.qwen3_5_moe import Qwen3_5MoeTextConfig
@@ -161,6 +161,10 @@ class Qwen3_5MultiTokenPredictor(nn.Module):
161
162
current_step_idx = spec_step_idx % self.num_mtp_layers
163
mtp_layer = self.layers[current_step_idx]
164
+ if mtp_layer.use_attn_reduce_scatter_for_moe:
165
+ assert hidden_states.shape[0] == positions.shape[-1]
166
+ hidden_states = sequence_parallel_chunk(hidden_states)
167
+ assert residual is None
168
hidden_states, residual = mtp_layer(
169
positions=positions,
170
hidden_states=hidden_states,
@@ -172,15 +176,10 @@ class Qwen3_5MultiTokenPredictor(nn.Module):
176
{"hidden_states": hidden_states, "residual": residual}
177
)
178
175
- if mtp_layer.use_attn_reduce_scatter_for_moe:
176
- hidden_states, residual = _all_gather_hidden_and_residual(
177
- hidden_states,
178
- residual,
179
- positions.shape[-1],
180
- self.config.hidden_size,
181
- )
182
-
179
hidden_states, _ = self.norm(hidden_states, residual)
180
+ if mtp_layer.use_attn_reduce_scatter_for_moe:
181
+ hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
182
+ hidden_states = hidden_states[: positions.shape[-1]]
183
return hidden_states
184
185
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
vllm/model_executor/models/qwen3_next.py
+35
-57
@@ -78,6 +78,18 @@ logger = init_logger(__name__)
78
KVCache = tuple[torch.Tensor, torch.Tensor]
79
80
81
+def _should_use_sequence_parallel(vllm_config: VllmConfig) -> bool:
82
+ config = vllm_config.model_config.hf_text_config
83
+ parallel_config = vllm_config.parallel_config
84
+ return (
85
+ parallel_config.use_sequence_parallel_moe
86
+ and parallel_config.pipeline_parallel_size == 1
87
+ and getattr(config, "num_experts", 0) > 0
88
+ and not getattr(config, "mlp_only_layers", [])
89
+ and getattr(config, "decoder_sparse_step", 1) == 1
90
+ )
91
+
92
+
93
def _is_shared_expert_fse_compatible(quant_config) -> bool:
94
"""Check if shared expert can be fused with routed experts.
95
@@ -397,7 +409,6 @@ class Qwen3NextDecoderLayer(nn.Module):
409
model_config = vllm_config.model_config
410
cache_config = vllm_config.cache_config
411
quant_config = vllm_config.quant_config
400
- parallel_config = vllm_config.parallel_config
412
413
self.layer_type = layer_type
414
self.layer_idx = extract_layer_index(prefix)
@@ -409,10 +420,8 @@ class Qwen3NextDecoderLayer(nn.Module):
420
config.num_experts > 0
421
and (self.layer_idx + 1) % config.decoder_sparse_step == 0
422
)
412
- self.use_attn_reduce_scatter_for_moe = (
413
- parallel_config.use_sequence_parallel_moe
414
- and parallel_config.pipeline_parallel_size == 1
415
- and is_moe_layer
423
+ self.use_attn_reduce_scatter_for_moe = _should_use_sequence_parallel(
424
+ vllm_config
425
)
426
427
if self.layer_type == "linear_attention":
@@ -481,11 +490,6 @@ class Qwen3NextDecoderLayer(nn.Module):
490
**kwargs: object,
491
):
492
full_num_tokens = positions.shape[-1]
484
- input_is_sequence_parallel = (
485
- self.use_attn_reduce_scatter_for_moe
486
- and residual is not None
487
- and hidden_states.shape[0] != full_num_tokens
488
- )
493
494
if residual is None:
495
residual = hidden_states
@@ -493,7 +497,7 @@ class Qwen3NextDecoderLayer(nn.Module):
497
else:
498
hidden_states, residual = self.input_layernorm(hidden_states, residual)
499
496
- if input_is_sequence_parallel:
500
+ if self.use_attn_reduce_scatter_for_moe:
501
hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
502
hidden_states = hidden_states[:full_num_tokens]
503
@@ -524,8 +528,6 @@ class Qwen3NextDecoderLayer(nn.Module):
528
# pad if not divisible by world size
529
hidden_states = torch.nn.functional.pad(hidden_states, (0, 0, 0, sp_pad))
530
hidden_states = tensor_model_parallel_reduce_scatter(hidden_states, 0)
527
- if not input_is_sequence_parallel:
528
- residual = sequence_parallel_chunk(residual)
531
532
# Fully Connected
533
hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
@@ -554,24 +556,6 @@ class Qwen3NextDecoderLayer(nn.Module):
556
return hidden_states, residual
557
558
557
-def _all_gather_hidden_and_residual(
558
- hidden_states: torch.Tensor,
559
- residual: torch.Tensor | None,
560
- full_num_tokens: int,
561
- hidden_size: int,
562
-) -> tuple[torch.Tensor, torch.Tensor | None]:
563
- if residual is None:
564
- hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
565
- hidden_states = hidden_states[:full_num_tokens]
566
- return hidden_states, None
567
-
568
- combined_states = torch.cat([hidden_states, residual], dim=-1)
569
- combined_states = tensor_model_parallel_all_gather(combined_states, 0)
570
- combined_states = combined_states[:full_num_tokens]
571
- hidden_states, residual = combined_states.split([hidden_size, hidden_size], dim=-1)
572
- return hidden_states, residual
573
-
574
-
559
@support_torch_compile
560
class Qwen3NextModel(nn.Module, EagleModelMixin):
561
hf_to_vllm_mapper = WeightsMapper(
@@ -629,6 +613,10 @@ class Qwen3NextModel(nn.Module, EagleModelMixin):
613
def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
614
return self.embed_tokens(input_ids)
615
616
+ @property
617
+ def use_sequence_parallel(self) -> bool:
618
+ return self.layers[self.start_layer].use_attn_reduce_scatter_for_moe
619
+
620
def forward(
621
self,
622
input_ids: torch.Tensor | None,
@@ -648,35 +636,20 @@ class Qwen3NextModel(nn.Module, EagleModelMixin):
636
residual = intermediate_tensors["residual"]
637
638
full_num_tokens = positions.shape[-1]
639
+ if self.use_sequence_parallel:
640
+ hidden_states = sequence_parallel_chunk(hidden_states)
641
+ assert residual is None
642
+
643
aux_hidden_states = self._maybe_add_hidden_state([], 0, hidden_states, residual)
644
for layer_idx, layer in enumerate(
645
islice(self.layers, self.start_layer, self.end_layer),
646
start=self.start_layer,
647
):
656
- if (
657
- hidden_states.shape[0] != full_num_tokens
658
- and not layer.use_attn_reduce_scatter_for_moe
659
- ):
660
- hidden_states, residual = _all_gather_hidden_and_residual(
661
- hidden_states,
662
- residual,
663
- full_num_tokens,
664
- self.config.hidden_size,
665
- )
648
hidden_states, residual = layer(
649
positions=positions,
650
hidden_states=hidden_states,
651
residual=residual,
652
)
671
- if (layer_idx + 1) in self.aux_hidden_state_layers and hidden_states.shape[
672
- 0
673
- ] != full_num_tokens:
674
- hidden_states, residual = _all_gather_hidden_and_residual(
675
- hidden_states,
676
- residual,
677
- full_num_tokens,
678
- self.config.hidden_size,
679
- )
653
self._maybe_add_hidden_state(
654
aux_hidden_states, layer_idx + 1, hidden_states, residual
655
)
@@ -685,14 +658,19 @@ class Qwen3NextModel(nn.Module, EagleModelMixin):
658
return IntermediateTensors(
659
{"hidden_states": hidden_states, "residual": residual}
660
)
688
- if hidden_states.shape[0] != full_num_tokens:
689
- hidden_states, residual = _all_gather_hidden_and_residual(
690
- hidden_states,
691
- residual,
692
- full_num_tokens,
693
- self.config.hidden_size,
694
- )
661
hidden_states, _ = self.norm(hidden_states, residual)
662
+ if self.use_sequence_parallel:
663
+ if aux_hidden_states:
664
+ hidden_size = hidden_states.shape[-1]
665
+ hidden_states = torch.cat([hidden_states, *aux_hidden_states], dim=-1)
666
+ hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
667
+ hidden_states = hidden_states[:full_num_tokens]
668
+ hidden_states, *aux_hidden_states = hidden_states.split(
669
+ hidden_size, dim=-1
670
+ )
671
+ else:
672
+ hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
673
+ hidden_states = hidden_states[:full_num_tokens]
674
if aux_hidden_states:
675
return hidden_states, aux_hidden_states
676
return hidden_states
vllm/model_executor/models/qwen3_next_mtp.py
+9
-9
@@ -9,7 +9,7 @@ from torch import nn
9
10
from vllm.compilation.decorators import support_torch_compile
11
from vllm.config import VllmConfig
12
-from vllm.distributed.parallel_state import get_pp_group
12
+from vllm.distributed import get_pp_group, tensor_model_parallel_all_gather
13
from vllm.logger import init_logger
14
from vllm.model_executor.layers.linear import ColumnParallelLinear
15
from vllm.model_executor.layers.logits_processor import LogitsProcessor
@@ -22,8 +22,8 @@ from vllm.model_executor.models.qwen3_next import (
22
Qwen3NextModel,
23
Qwen3NextRMSNorm,
24
QwenNextMixtureOfExperts,
25
- _all_gather_hidden_and_residual,
25
)
26
+from vllm.model_executor.models.utils import sequence_parallel_chunk
27
from vllm.sequence import IntermediateTensors
28
from vllm.transformers_utils.configs.qwen3_next import Qwen3NextConfig
29
@@ -131,6 +131,10 @@ class Qwen3NextMultiTokenPredictor(nn.Module):
131
132
current_step_idx = spec_step_idx % self.num_mtp_layers
133
mtp_layer = self.layers[current_step_idx]
134
+ if mtp_layer.use_attn_reduce_scatter_for_moe:
135
+ assert hidden_states.shape[0] == positions.shape[-1]
136
+ hidden_states = sequence_parallel_chunk(hidden_states)
137
+ assert residual is None
138
hidden_states, residual = mtp_layer(
139
positions=positions,
140
hidden_states=hidden_states,
@@ -142,14 +146,10 @@ class Qwen3NextMultiTokenPredictor(nn.Module):
146
{"hidden_states": hidden_states, "residual": residual}
147
)
148
145
- if mtp_layer.use_attn_reduce_scatter_for_moe:
146
- hidden_states, residual = _all_gather_hidden_and_residual(
147
- hidden_states,
148
- residual,
149
- positions.shape[-1],
150
- self.config.hidden_size,
151
- )
149
hidden_states, _ = self.norm(hidden_states, residual)
150
+ if mtp_layer.use_attn_reduce_scatter_for_moe:
151
+ hidden_states = tensor_model_parallel_all_gather(hidden_states, 0)
152
+ hidden_states = hidden_states[: positions.shape[-1]]
153
return hidden_states
154
155
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]: