执行摘要
- 一句话:GPT-OSS 支持按专家流式加载,供 RL 引擎逐个同步权重
- 推荐动作:值得精读。重点关注
GptOssRoutedExperts.weight_loader 的 return_success 契约设计(让调用方知道专家是否真正属于本 rank)、与 LoRA 包装层及 MXFP4 后处理的交互;meta.py 的恢复逻辑修复对所有 layerwise reload 模型都有参考价值。若团队在做 RL 权重同步,可与 #43375 的 P2P RDT 权重同步结合阅读。
功能与动机
PR body 明确说明动机:让 verl 等 RL 引擎 "sync experts one by one instead of requiring all-gathering experts during actor-rollout sync"。GPT-OSS 这类大 MoE 模型在 RL 训练中 actor 与 rollout 实例需频繁同步权重,全量 all-gather 专家权重的通信与显存开销很大;按专家粒度同步可显著降低成本。作者在评论中补充:实际 RL 训练使用 unsloth 的 BF16 版本,mxfp4/nvfp4 量化路径是按 reviewer 要求补上,并做了"逐专家回放 + 推理输出逐 token 一致"的验证。
实现拆解
- 新增流式专家加载器 GptOssRoutedExperts(
vllm/model_executor/models/gpt_oss.py,+197):继承 RoutedExperts,复用其 _map_global_expert_id_to_local_expert_id 把全局专家 ID 映射到本地 slot,非本 rank 专家返回 False 并配合 return_success 语义;weight_loader 只接管 gpt_oss_w13/gpt_oss_w2 两个 shard,其余走基类,并按权重类型分派到 _load_expert_bias(w2 bias 仅 tp_rank 0 持有,其余 rank 填零)、_load_unquantized_expert(转置后按列切分)、_load_packed_expert(量化打包布局:reshape 后按块切分)。
- 接入模型加载主流程:
GptOssModel 新增 _get_streamed_expert_info 解析 checkpoint 键 ...experts.<expert_id>.<fused_param>,映射到 params_dict 中 routed_experts.* 融合参数,并兼容 LoRA 包装层的 base_layer. 前缀;_try_load_streamed_expert 调用参数上的 weight_loader,成功后把参数名记入 loaded_params;在 _load_weights_mxfp4 与 _load_weights_other 的权重循环中插入钩子,命中即加载并跳过后续分支,Quark 路径显式标注不支持;MLPBlock 通过 routed_experts_cls=GptOssRoutedExperts 注入新加载器。
- 修复 layerwise reload 恢复逻辑(
vllm/model_executor/model_loader/reload/meta.py,+6/-3):restore_layer_on_meta 原来只删除 get_layer_tensors 返回的已注册参数/缓冲区;MXFP4 后处理会把注册专家参数替换为未注册的 Triton 属性,导致恢复 checkpoint 参数时 KeyError: attribute already exists。修复为删除「当前张量 ∪ 恢复元数据」的并集,并用 hasattr 保护。
- 测试配套:新增
tests/model_executor/model_loader/test_gpt_oss_weight_loading.py(152 行),用 _TestExperts 桩覆盖 wrapped parameter 解析(含 base_layer. 前缀)、非本地专家跳过、非量化专家 TP 切分(w13/w2)、w2 bias 归属 tp_rank 0、量化打包布局(mxfp4/nvfp4)切分;test_reload.py 新增 test_restore_layer_replaces_postprocessed_tensor_attribute 模拟属性被替换后 restore_layer_on_meta 能正确重建 meta 参数。
关键文件:
vllm/model_executor/models/gpt_oss.py(模块 模型加载;类别 source;类型 core-logic;符号 GptOssRoutedExperts, weight_loader, _narrow_for_rank, _copy_to_expert): 核心实现文件:新增 GptOssRoutedExperts 流式专家加载器、_get_streamed_expert_info/_try_load_streamed_expert 解析与钩子,并在 _load_weights_mxfp4/_load_weights_other 中接入;所有 TP 切分与量化分派逻辑都在此。
tests/model_executor/model_loader/test_gpt_oss_weight_loading.py(模块 测试配套;类别 test;类型 test-coverage;符号 _TestExperts, _map_global_expert_id_to_local_expert_id, _load, test_streamed_expert_resolves_wrapped_parameter): 新增 152 行单元测试,用 _TestExperts 桩覆盖流式加载的关键分支:wrapped parameter 解析(含 base_layer. 前缀)、非本地专家跳过、TP 切分、bias 归属、量化打包布局。
vllm/model_executor/model_loader/reload/meta.py(模块 重载机制;类别 source;类型 bugfix;符号 restore_layer_on_meta): 共享 layerwise reload 恢复逻辑的配套修复:restore_layer_on_meta 现在会清理量化后处理生成的未注册属性,解决 MXFP4 场景下恢复 checkpoint 参数的 KeyError。影响所有 layerwise reload + 量化后处理的模型。
tests/model_executor/model_loader/test_reload.py(模块 测试配套;类别 test;类型 test-coverage;符号 test_restore_layer_replaces_postprocessed_tensor_attribute): 新增 test_restore_layer_replaces_postprocessed_tensor_attribute,验证 restore_layer_on_meta 能正确处理属性被替换(post-processed)的场景,是 meta.py 修复的直接回归测试。
关键符号:GptOssRoutedExperts.weight_loader, GptOssRoutedExperts._narrow_for_rank, GptOssRoutedExperts._copy_to_expert, GptOssRoutedExperts._load_expert_bias, GptOssRoutedExperts._load_unquantized_expert, GptOssRoutedExperts._load_packed_expert, GptOssModel._get_streamed_expert_info, GptOssModel._try_load_streamed_expert, restore_layer_on_meta
关键源码片段
vllm/model_executor/models/gpt_oss.py
核心实现文件:新增 GptOssRoutedExperts 流式专家加载器、_get_streamed_expert_info/_try_load_streamed_expert 解析与钩子,并在 _load_weights_mxfp4/_load_weights_other 中接入;所有 TP 切分与量化分派逻辑都在此。
# gpt_oss.py:GptOssRoutedExperts 的核心分派逻辑
class GptOssRoutedExperts(RoutedExperts):
"""Load one GPT-OSS expert at a time without assembling the global stack."""
@staticmethod
def _narrow_for_rank(loaded_weight, dim, rank, size):
# 按 TP rank 均匀切分,尾部不足时截断到可用长度
start = rank * size
available = loaded_weight.shape[dim] - start
return loaded_weight.narrow(dim, start, min(size, max(available, 0)))
@staticmethod
def _copy_to_expert(expert_data, loaded_weight):
# 兼容单元素标量(bias)与尾维为 1 的 checkpoint 布局
if expert_data.numel() == loaded_weight.numel() == 1:
expert_data.copy_(loaded_weight.reshape(()))
return
while loaded_weight.ndim > expert_data.ndim and loaded_weight.shape[-1] == 1:
loaded_weight = loaded_weight.squeeze(-1)
slices = tuple(slice(0, size) for size in loaded_weight.shape)
expert_data[slices].copy_(loaded_weight)
def weight_loader(self, param, loaded_weight, weight_name, shard_id,
expert_id, return_success=False):
# 仅接管 w13/w2 两类 shard,其余交给基类
if shard_id not in ("gpt_oss_w13", "gpt_oss_w2"):
return super().weight_loader(param, loaded_weight, weight_name,
shard_id, expert_id, return_success)
# 非本 rank 专家直接跳过,return_success 供调用方记录已加载参数
expert_id = self._map_global_expert_id_to_local_expert_id(expert_id)
if expert_id == -1:
return False if return_success else None
expert_data = param.data[expert_id]
weight_dtype = getattr(self.quant_method, "weight_dtype", "")
quant_method_name = self.quant_method.__class__.__name__
if weight_name.endswith("_bias"):
self._load_expert_bias(expert_data, loaded_weight, shard_id)
elif weight_dtype in ("gpt_oss_mxfp4", "mxfp4") or quant_method_name in (
"CompressedTensorsW4A4Nvfp4MoEMethod",
"Nvfp4OnlineMoEMethod",
):
# 量化权重走打包布局(块缩放)切分
self._load_packed_expert(expert_data, loaded_weight, weight_name, shard_id)
else:
self._load_unquantized_expert(expert_data, loaded_weight, shard_id)
return True if return_success else None
vllm/model_executor/model_loader/reload/meta.py
共享 layerwise reload 恢复逻辑的配套修复:restore_layer_on_meta 现在会清理量化后处理生成的未注册属性,解决 MXFP4 场景下恢复 checkpoint 参数的 KeyError。影响所有 layerwise reload + 量化后处理的模型。
# meta.py:修复 restore_layer_on_meta 未清理未注册属性的问题
def restore_layer_on_meta(layer, info):
"""Restore a layer to model format with tensors on the meta device."""
if layer.__class__.__name__ in SKIP_MODULES:
return
non_persistent = set(layer._non_persistent_buffers_set)
restore_params, restore_buffers = info.restore_metadata
# 需要同时清理后处理(如 MXFP4)生成的未注册属性,
# 否则重新注册 checkpoint 参数会抛 "attribute already exists"
tensor_names = (
get_layer_tensors(layer).keys() | restore_params.keys() | restore_buffers.keys()
)
for name in tensor_names:
if name not in SKIP_TENSORS and hasattr(layer, name):
delattr(layer, name)
for name, param in restore_params.items():
if name not in SKIP_TENSORS:
param = restore_layer_refs(param, layer)
layer.register_parameter(name, param)
for name, buffer in restore_buffers.items():
if name not in SKIP_TENSORS:
buffer = restore_layer_refs(buffer, layer)
layer.register_buffer(name, buffer, persistent=name not in non_persistent)
评论区精华
量化方法支持范围:aoshen02 追问 "Will it support other mxfp4 and quack?",并质疑 "Isn't this model is mxfp4? Does the other two load function have the same problem?"。作者最初回答只支持 BF16(RL 训练用 unsloth BF16 版),被追问后补齐量化路径,并给出"逐专家回放 + 推理 token 一致"的验证结果。
LoRA 包装层兼容性(Codex P1):指出 LoRA 启用时 mlp.experts 被替换为 FusedMoEWithLoRA,不暴露 _map_global_expert_id_to_local_expert_id,参数名也变为 experts.base_layer.routed_experts.*,直接调用会抛 AttributeError;作者回复 "followed existing convention where we add the name to the parameter so lora is dispatched correctly",并在 _get_streamed_expert_info 中兼容 base_layer. 前缀。
meta.py 修改原因:wyettzeng 解释 "native MXFP4 post-processing replaces registered expert parameters with unregistered Triton tensor attributes. During layerwise reload, the old code only removed registered parameters buffers, so restoring checkpoint-format parameters failed with KeyError: attribute already exists"。
风格与范围收束:aoshen02 建议简化 _narrow_for_rank 并消除 _map_global_expert_id_to_local_expert_id 的重复调用;Isotr0py 要求简化 _GPT_OSS_STREAMED_EXPERT_SUFFIXES 常量表、撤销不必要的 hf_to_vllm 映射删除、并把测试移到 tests/model_executor/model_loader 目录,均已按意见修改。
- 量化方法(mxfp4/quark)是否同样支持流式加载 (question): 最终支持 BF16 与 mxfp4/nvfp4 打包布局,Quark 路径显式注释不支持;作者补充了量化方法逐专家回放的推理一致性验证。
- LoRA 包装层下流式加载路径解析(Codex P1) (correctness): 作者按现有约定将名称挂到参数上以支持 LoRA 分发,并在 _get_streamed_expert_info 中兼容 base_layer. 前缀。
- restore_layer_on_meta 需清理后处理生成的未注册属性 (correctness): 改为删除「当前张量 ∪ 恢复元数据」并集并加 hasattr 保护,附带新增回归测试。
- _map_global_expert_id_to_local_expert_id 重复调用 (style): 作者回复 "good catch, removed the outer one",移除外层调用。
- _narrow_for_rank 切分代码可读性 (style): 作者重构为当前的 _narrow_for_rank 静态方法并统一各加载路径。
- 常量表结构与 hf_to_vllm 映射是否应保留 (design): 作者简化常量表并还原 hf_to_vllm 映射("reverted")。
- 测试文件位置 (testing): 作者按意见移动("done")。
风险与影响
- 风险:共享恢复路径回归风险:
restore_layer_on_meta 是所有 layerwise reload 模型的公共路径,删除范围从注册张量扩大到「当前张量 ∪ 恢复元数据」并集,虽有 hasattr 保护,属性名冲突时仍可能影响其他使用量化后处理的模型,需要非 gpt-oss 模型回归验证。LoRA 兼容性依赖约定:流式加载对 LoRA 包装层的解析依赖"参数名挂载"的既有约定,若 FusedMoEWithLoRA 结构变化,_get_streamed_expert_info 的 base_layer. 探测可能失效。量化覆盖有限:Quark 路径显式不支持流式加载,_load_packed_expert 假设统一打包布局,新增量化格式需扩展分派。切分边界假设:_narrow_for_rank 依赖 TP 均匀切分假设,尾部不足靠 max(available, 0) 截断,极端形状下可能出现拷贝形状不匹配。验证范围:仓库内 CI 主要是单元测试,多卡 TP/EP + LoRA 的 e2e 回归依赖 verl 等 RL 工作流后续验证。
- 影响:对 verl 等 RL 训练工作流直接受益:actor-rollout 同步可从全量 all-gather 降为按专家粒度传输;对 GPT-OSS 模型加载路径是主要变更点,不影响其他 MoE 模型;
meta.py 的修复影响所有使用 layerwise reload + 量化后处理的模型;新增 152 行单元测试覆盖了 TP 切分、bias 归属、量化布局等关键分支,但缺少多卡 e2e。整体影响面为模型级 + 共享工具函数的小幅改动。
- 风险标记:共享重载逻辑变更, LoRA 兼容性依赖约定, 量化覆盖有限, 模型专属路径, 多卡 e2e 待验证
关联脉络
- PR #43375 [RL] P2P RDT weight sync: 同属 RL 权重同步基础设施:该 PR 为权重传输提供 sharded_rdt 拉取式后端,本 PR 为其提供按专家粒度加载能力,两者互补,未来可让权重传输按专家细粒度推送。
参与讨论