[fsdp] fix: Fix Qwen3 MoE FSDP weight sync for vLLM rollout in Transformers 5#6863
[fsdp] fix: Fix Qwen3 MoE FSDP weight sync for vLLM rollout in Transformers 5#6863lxb007981 wants to merge 1 commit into
Conversation
Transformers 5 stores Qwen-style MoE expert weights as packed 3D `mlp.experts.gate_up_proj` and `mlp.experts.down_proj` tensors. During live FSDP-to-vLLM rollout weight sync, those packed keys were sent directly, but vLLM's Qwen3 MoE reload path expects the original per-expert checkpoint keys. Expand packed MoE expert tensors during FSDP parameter streaming so vLLM receives per-expert `gate_proj`, `up_proj`, and `down_proj` weights. Dense models and non-packed tensors continue to pass through unchanged.
There was a problem hiding this comment.
Code Review
This pull request introduces iter_vllm_compatible_moe_params to expand Transformers 5 packed MoE expert tensors into vLLM-compatible checkpoint keys during live weight sync, and integrates it into the FSDP transformer implementation. The reviewer suggested stripping the .weight suffix from parameter names before matching to improve robustness, and removing redundant .contiguous() calls on sliced tensors to avoid unnecessary overhead.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| def iter_vllm_compatible_moe_params(name: str, tensor: torch.Tensor) -> Iterable[tuple[str, torch.Tensor]]: | ||
| """Expand Transformers 5 packed MoE expert tensors to vLLM checkpoint keys. | ||
|
|
||
| Transformers 5 stores Qwen-style MoE experts as packed 3D parameters: | ||
| ``mlp.experts.gate_up_proj`` with shape | ||
| ``[num_experts, 2 * intermediate_size, hidden_size]`` and | ||
| ``mlp.experts.down_proj`` with shape | ||
| ``[num_experts, hidden_size, intermediate_size]``. vLLM's Qwen MoE reload | ||
| path still accepts the original per-expert checkpoint keys during live | ||
| weight sync, so stream those keys without materializing a full dict. | ||
| """ | ||
| if name.endswith(".mlp.experts.gate_up_proj") and tensor.dim() == 3: | ||
| gate, up = tensor.chunk(2, dim=1) | ||
| base = name.removesuffix(".gate_up_proj") | ||
| for expert_id in range(tensor.size(0)): | ||
| yield f"{base}.{expert_id}.gate_proj.weight", gate[expert_id].contiguous() | ||
| yield f"{base}.{expert_id}.up_proj.weight", up[expert_id].contiguous() | ||
| return | ||
|
|
||
| if name.endswith(".mlp.experts.down_proj") and tensor.dim() == 3: | ||
| base = name.removesuffix(".down_proj") | ||
| for expert_id in range(tensor.size(0)): | ||
| yield f"{base}.{expert_id}.down_proj.weight", tensor[expert_id].contiguous() | ||
| return | ||
|
|
||
| yield name, tensor |
There was a problem hiding this comment.
Robustness and Performance Improvements
- Robustness of Parameter Name Matching: The current implementation checks
name.endswith(".mlp.experts.gate_up_proj"). Depending on how the model is loaded or wrapped, the parameter name in the state dict might have a.weightsuffix (e.g.,...mlp.experts.gate_up_proj.weight). Stripping.weightfirst makes the matching much more robust. - Redundant
.contiguous()Calls: Sincetensoris gathered from FSDP or is a model parameter, it is contiguous. Slicing it along the first dimension (e.g.,gate[expert_id]) produces a slice that is also contiguous because the remaining dimensions have contiguous strides. Therefore, calling.contiguous()is redundant and can be omitted to avoid unnecessary overhead.
def iter_vllm_compatible_moe_params(name: str, tensor: torch.Tensor) -> Iterable[tuple[str, torch.Tensor]]:
"""Expand Transformers 5 packed MoE expert tensors to vLLM checkpoint keys.
Transformers 5 stores Qwen-style MoE experts as packed 3D parameters:
mlp.experts.gate_up_proj with shape
[num_experts, 2 * intermediate_size, hidden_size] and
mlp.experts.down_proj with shape
[num_experts, hidden_size, intermediate_size]. vLLM's Qwen MoE reload
path still accepts the original per-expert checkpoint keys during live
weight sync, so stream those keys without materializing a full dict.
"""
name_stripped = name.removesuffix(".weight")
if name_stripped.endswith(".mlp.experts.gate_up_proj") and tensor.dim() == 3:
gate, up = tensor.chunk(2, dim=1)
base = name_stripped.removesuffix(".gate_up_proj")
for expert_id in range(tensor.size(0)):
yield f"{base}.{expert_id}.gate_proj.weight", gate[expert_id]
yield f"{base}.{expert_id}.up_proj.weight", up[expert_id]
return
if name_stripped.endswith(".mlp.experts.down_proj") and tensor.dim() == 3:
base = name_stripped.removesuffix(".down_proj")
for expert_id in range(tensor.size(0)):
yield f"{base}.{expert_id}.down_proj.weight", tensor[expert_id]
return
yield name, tensor
What does this PR do?
Transformers 5 stores Qwen-style MoE expert weights as packed 3D
mlp.experts.gate_up_projandmlp.experts.down_projtensors. During live FSDP-to-vLLM rollout weight sync, those packed keys were sent directly, but vLLM's Qwen3 MoE reload path expects the original per-expert checkpoint keys.Expand packed MoE expert tensors during FSDP parameter streaming so vLLM receives per-expert
gate_proj,up_proj, anddown_projweights. Dense models and non-packed tensors continue to pass through unchanged.Checklist Before Starting
[{modules}] {type}: {description}(This will be checked by the CI){modules}includefsdp,megatron,veomni,sglang,vllm,rollout,trainer,ci,training_utils,recipe,hardware,deployment,ray,worker,single_controller,misc,perf,model,algo,env,tool,ckpt,doc,data,cfg,reward,fully_async,one_step_off,like[megatron, fsdp, doc]{type}is infeat,fix,refactor,chore,test[BREAKING]to the beginning of the title.[BREAKING][fsdp, megatron] feat: dynamic batchingTest
API and Usage Example
# Add code snippet or script demonstrating how to use thisDesign & Code Changes
Checklist Before Submitting
Important
Please check all the following items before requesting a review, otherwise the reviewer might deprioritize this PR for review.
pre-commit install && pre-commit run --all-files --show-diff-on-failure --color=alwaysci-requestchannel in theverlSlack workspace. (If not accessible, please try the Feishu group (飞书群).)recipesubmodule, please also update the reference to the submodule commit viagit submodule update --remoteorcd recipe && git pull origin main.