Skip to content

[Bugfix][NPU] fix MTP training weight sync for MindSpeed GroupedGemm - #375

Open
CalvinXKY wants to merge 1 commit into
vllm-project:ascendfrom
CalvinXKY:fix/npu-mtp-training-integration
Open

[Bugfix][NPU] fix MTP training weight sync for MindSpeed GroupedGemm#375
CalvinXKY wants to merge 1 commit into
vllm-project:ascendfrom
CalvinXKY:fix/npu-mtp-training-integration

Conversation

@CalvinXKY

@CalvinXKY CalvinXKY commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Details

Fix NPU MTP acceptance collapsing to 0% in disaggregated RL training.

NPU-only (is_npu() gated):

  1. MindSpeed GroupedGemm 3D expert weight conversion in glm4moe.py (wrong view() corrupted experts)
  2. Disable NPU layerwise_reload in disaggregated weight update (is_checkpoint_format=not is_npu())
  3. GroupedGemm EP concat for MindSpeed packed experts

GPU path unchanged.

Companion vllm-ascend fix: vllm-project/vllm-ascend#13209

Testing

GLM-4.7-Flash (30B-A3B MoE + MTP), 8x Ascend 910B1, disaggregated 4 train + 4 rollout, TP=4, EP=4 (together with vllm-ascend#13209):

Metric Before After
MTP acceptance rate 0% ~30%
rollout_time 1289.6s (garbage) 847.4s
tokens/gpu/sec 32.0 74.13
grad_norm 6.95 0.128
update_weights_time 91.3s 9.3s

Pure-inference MTP baseline (same machine): +82.5% throughput (49.6 → 90.6 tok/s).

Correct 3D expert weight conversion and disable NPU layerwise_reload in
disaggregated update so MTP acceptance recovers during train/infer split.
NPU-only paths are gated by is_npu(); GPU behavior is unchanged.

Signed-off-by: kaiyuan <kyxiezju@163.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds NPU-specific support for GLM4MoE weight conversion, including handling MindSpeed GroupedGemm and MLA attention mappings, and disables checkpoint format during NPU weight updates to prevent corruption. The review feedback highlights critical issues where directly calling .view() on 2D expert weights would scramble them due to memory layout, suggesting a .reshape().permute() sequence instead. Additionally, the reviewer recommends using .reshape() instead of .view() elsewhere to safely handle potentially non-contiguous tensors.

Comment on lines +187 to +188
gate_up = param.view(num_experts, args.hidden_size, -1)
gate_weight, up_weight = gate_up.chunk(2, dim=2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In the 2D case, param has the shape [hidden_size, fc1_output * num_experts]. Directly calling .view(num_experts, args.hidden_size, -1) will scramble the weights because the expert dimension is interleaved in memory. To correctly reshape and split the expert dimension, you should first reshape to [hidden_size, num_experts, fc1_output] and then permute to [num_experts, hidden_size, fc1_output]. Additionally, using .reshape() is safer than .view() to avoid potential runtime errors on non-contiguous tensors.

Suggested change
gate_up = param.view(num_experts, args.hidden_size, -1)
gate_weight, up_weight = gate_up.chunk(2, dim=2)
gate_up = param.reshape(args.hidden_size, num_experts, -1).permute(1, 0, 2)
gate_weight, up_weight = gate_up.chunk(2, dim=2)

Comment on lines +231 to +232
gate_up = param.view(num_experts, args.hidden_size, -1)
gate_weight, up_weight = gate_up.chunk(2, dim=2)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the 2D case in linear_fc1, param here has the shape [hidden_size, fc1_output * num_experts]. Directly calling .view(num_experts, args.hidden_size, -1) will scramble the weights. You should reshape to [hidden_size, num_experts, fc1_output] and then permute to [num_experts, hidden_size, fc1_output]. Using .reshape() is also safer than .view() here.

Suggested change
gate_up = param.view(num_experts, args.hidden_size, -1)
gate_weight, up_weight = gate_up.chunk(2, dim=2)
gate_up = param.reshape(args.hidden_size, num_experts, -1).permute(1, 0, 2)
gate_weight, up_weight = gate_up.chunk(2, dim=2)

outputs.append((f"model.layers.{layer_idx}.mlp.experts.{i}.down_proj.weight", param[i]))
else:
# 2D: [fc2_input * num_experts, hidden_size] - old format
down = param.view(num_experts, -1, args.hidden_size)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using .reshape() is safer than .view() here to prevent potential runtime errors if the tensor is non-contiguous.

Suggested change
down = param.view(num_experts, -1, args.hidden_size)
down = param.reshape(num_experts, -1, args.hidden_size)

for i in range(num_experts):
outputs.append((f"model.layers.{layer_idx}.mlp.experts.{i}.down_proj.weight", param[i]))
else:
down = param.view(num_experts, -1, args.hidden_size)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using .reshape() is safer than .view() here to prevent potential runtime errors if the tensor is non-contiguous.

Suggested change
down = param.view(num_experts, -1, args.hidden_size)
down = param.reshape(num_experts, -1, args.hidden_size)

@CalvinXKY

Copy link
Copy Markdown
Collaborator Author
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant