Prhub

#2251 Internalize mbridge and remove megatron-bridge

原始 PR 作者 zhuzilin 合并时间 2026-08-04 15:19 文件变更 119 提交数 2 评论 0 代码增减 +1975 / -9452

执行摘要

内部化 mbridge,移除 megatron-bridge 依赖

PR body 明确指出:在 slime 当前发展阶段,无法及时支持每个模型,实验性 Megatron-Bridge 集成仅部分实现、达不到代码质量和可维护性标准;独立 MBridge 项目已弃用并迁移至 NVIDIA-NeMo/Megatron-Bridge,因此将 slime 所需功能直接内部化,以获得清晰的所有权和按需维护能力。

值得精读。这是 slime 一次重要的架构收敛:从外部桥接模型转换切换到自研的轻量直接转换器,同时明确了模型支持的边界。建议重点阅读 slime/backends/megatron_utils/hf_to_megatron/qwen.py 的映射逻辑和 slime_plugins/models/qwen3_5_vl.py 的原生 VLM 实现,理解新的模型接入成本和约束。

讨论亮点

该 PR 没有公开的 review 评论和讨论线程。PR body 中作者直接陈述了决策理由:移除实验性 Megatron-Bridge、内部化 mbridge 相关能力,并推荐需要更广模型覆盖的用户转向 radixark/miles。由于没有 reviewer 的交锋记录,本文档不臆造讨论内容。

实现拆解

  1. 移除 mbridge 与 megatron-bridge 插件层:删除 slime_plugins/mbridge/ 下的 qwen3_5.pygemma4.pyqwen3_next.pygpt_oss.pyglm4moe.pymimo.pyglm4.pydeepseek_v32.pyglm4moe_lite.py 等 Bridge 子类,以及 slime_plugins/megatron_bridge/glm4v_moe.py;这些文件承担了从 HF 权重到 Megatron 格式的动态映射,删除后不再依赖 mbridge 包和 megatron.bridge
  2. 内部化权重转换:新增 slime/backends/megatron_utils/hf_to_megatron/common.py(定义 SafetensorReadermerge_qkvmerge_gate_upstrip_mcore_wrappers)、qwen.pyqwen_hf_tensorqwen_moe_hf_tensormimo_hf_tensorminimax_m2_hf_tensor)和 deepseek.pydeepseek_hf_tensor),以声明式映射函数逐张量读取 HF safetensors,取代 mbridge 的类级映射配置。
  3. 删除不再维护的模型与工具:移除 Gemma4 原生实现(slime_plugins/models/gemma4.pygemma4_provider.py)及其转换器(hf_to_megatron/gemma4.pymegatron_to_hf/gemma4.py),删除 tools/preprocess_gpt_oss.py(GPT-OSS MXFP4 反量化工具),并移除 update_weight/hf_weight_iterator_bridge.py 桥接迭代器。
  4. 新增原生 Qwen3.5-VL 支持:新增 slime_plugins/models/qwen3_5_vl.pyqwen3_5_vl_utils.py,在 Megatron GPTModel 基础上叠加 HF Vision Model,实现 packed sequence 下的 MRoPE、CP 分片视觉 token 注入,替代原先依赖桥接器的 VLM 路径。
  5. 配套更新:同步更新 docs/endocs/zhexamples/geo3k_vlm_multi_turn,并为新的转换器和 VLM 逻辑补充/调整 20 余个测试文件,覆盖 tests/test_*tests/utils/ 等测试目录。
文件 模块 状态 重要度
slime/plugins/models/gemma4.py 模型层 removed 7.92
slime/plugins/models/qwen3_5_vl.py VLM 模型 added 7.44
slime/plugins/mbridge/qwen3_5.py 桥接层 removed 6.72
slime/plugins/megatron_bridge/glm4v_moe.py 桥接层 removed 6.72
slime/backends/megatron_utils/hf_to_megatron/qwen.py 权重转换 added 8.81
slime/backends/megatron_utils/hf_to_megatron/common.py 权重转换 added 8.82

关键符号

qwen_hf_tensor qwen_moe_hf_tensor mimo_hf_tensor minimax_m2_hf_tensor deepseek_hf_tensor merge_qkv merge_gate_up SafetensorReader.get_tensor Qwen3_5VLModel.forward Qwen3_5VLModel._inject_vision_embeddings build_packed_mrope_position_ids gather_packed_input_ids get_packed_cp_local_indices

关键源码片段

slime/plugins/models/qwen3_5_vl.py core-logic

新增的原生 Qwen3.5-VL 模型提供程序,以 Megatron GPTModel 叠加 HF ViT,标志着不依赖 mbridge 的 VLM 训练新路径。

# 文件 : slime_plugins/models/qwen3_5_vl.py
# 在 packed sequence 下把 HF 视觉模型的输出注入 Megatron embedding 序列。
def _inject_vision_embeddings(self, input_ids, full_input_ids, cu_seqlens, cp_group, pixel_values, pixel_values_videos, image_grid_thw, video_grid_thw):
    # 先用语言模型的 embedding 生成基础 token embedding,再在视觉 token 位置替换为视觉特征。
    embeddings = self.language_model.embedding(input_ids=input_ids, position_ids=None).clone()
    embeddings_bsh = embeddings.transpose(0, 1).contiguous()
    # 根据 THD 格式的 cu_seqlens 和 CP 分片策略,把本地 token 映射回完整 packed 序列的下标。
    local_indices = get_packed_cp_local_indices(
        cu_seqlens,
        cp_group.size() if cp_group is not None else 1,
        cp_group.rank() if cp_group is not None else 0,
        input_ids.device,
    )
    # 分别处理 image 和 video 两种模态,grid_thw 与 token 数必须匹配。
    for values, grids, token_id in (
        (pixel_values, image_grid_thw, self.image_token_id),
        (pixel_values_videos, video_grid_thw, self.video_token_id),
    ):
        if values is None:
            continue
        if grids is None:
            raise ValueError("Qwen3.5-VL pixel values require matching grid_thw")
        vision_output = self.model.visual(values.to(dtype=self.model.visual.dtype), grid_thw=grids)
        vision_embeddings = vision_output.pooler_output.to(device=embeddings.device, dtype=embeddings.dtype)
        # 视觉特征数量必须等于完整序列中对应 token 的数量。
        full_vision_positions = (full_input_ids[0] == token_id).nonzero(as_tuple=False).flatten()
        if full_vision_positions.numel() != vision_embeddings.shape[0]:
            raise ValueError(
                f"Qwen3.5-VL token/features mismatch: {full_vision_positions.numel()} tokens, "
                f"{vision_embeddings.shape[0]} features"
            )
        # 构建完整序列的 feature 下标,再用 CP 本地下标取出本 rank 需要替换的向量。
        feature_indices = torch.full((full_input_ids.shape[1],), -1, dtype=torch.long, device=input_ids.device)
        feature_indices[full_vision_positions] = torch.arange(vision_embeddings.shape[0], device=input_ids.device)
        local_feature_indices = feature_indices[local_indices]
        local_vision_mask = local_feature_indices >= 0
        # 严格校验:本地视觉掩码必须与 token id 匹配,否则说明 CP 布局或特征数与预期不一致。
        if not torch.equal(local_vision_mask, input_ids[0] == token_id):
            raise ValueError("Qwen3.5-VL CP token layout does not match its full packed sequence")
        embeddings_bsh[0, local_vision_mask] = vision_embeddings[local_feature_indices[local_vision_mask]]
    embeddings = embeddings_bsh.transpose(0, 1).contiguous()
    if self.config.sequence_parallel:
        embeddings = tensor_parallel.scatter_to_sequence_parallel_region(embeddings).contiguous()
    return embeddings
slime/backends/megatron_utils/hf_to_megatron/qwen.py core-logic

新增的内部化 Qwen/Qwen MoE/MiMo/MiniMax-M2 HF 权重读取器,取代旧桥接器的核心转换逻辑。

# 文件 : slime/backends/megatron_utils/hf_to_megatron/qwen.py
# 内部化的 MoE 权重读取:把 Megatron 参数名解析为 HF safetensors 中的张量。
def qwen_moe_hf_tensor(name: str, reader: SafetensorReader, config) -> torch.Tensor:
    # 剥离 _extra_state 等 Megatron 内部包装后缀。
    name = strip_mcore_wrappers(name)
    # 顶层权重(embedding、layernorm、output_layer)直接映射。
    if (tensor := _direct_tensor(name, reader, config)) is not None:
        return tensor
​
    layer, rest = _layer(name)
    prefix = f"model.layers.{layer}"
    # 注意力部分(含 linear_qkv 的 qkv 融合)。
    if (tensor := _attention_tensor(rest, prefix, reader, config)) is not None:
        return tensor
    # MoE 专属部分:router、shared experts、per-expert 张量。
    if (tensor := _qwen_moe_layer_tensor(rest, prefix, reader)) is not None:
        return tensor
    # 回退到 dense 分支处理共享结构。
    return qwen_hf_tensor(name, reader, config)# 解析 MoE 层的专家张量:
# "mlp.experts.linear_fc1.weight{expert_id}" -> HF 侧 gate_up_proj / down_proj。
def _qwen_moe_layer_tensor(rest: str, prefix: str, reader: SafetensorReader) -> torch.Tensor | None:
    mapping = {
        "pre_mlp_layernorm.weight": "post_attention_layernorm.weight",
        "mlp.linear_fc1.layer_norm_weight": "post_attention_layernorm.weight",
        "mlp.router.weight": "mlp.gate.weight",
        "mlp.router.expert_bias": "mlp.gate.e_score_correction_bias",
        "mlp.shared_experts.linear_fc2.weight": "mlp.shared_expert.down_proj.weight",
        "mlp.shared_experts.gate_weight": "mlp.shared_expert_gate.weight",
    }
    if rest in mapping:
        return reader.get_tensor(f"{prefix}.{mapping[rest]}")
    if rest in {"mlp.shared_experts.linear_fc1.weight", "shared_experts.linear_fc1.weight"}:
        # HF 侧 gate_proj 与 up_proj 合并为 linear_fc1。
        return merge_gate_up(
            reader.get_tensor(f"{prefix}.mlp.shared_expert.gate_proj.weight"),
            reader.get_tensor(f"{prefix}.mlp.shared_expert.up_proj.weight"),
        )
    # 每个专家的线性层以 "weight{expert_id}" 结尾。
    match = re.fullmatch(r"mlp\.experts\.linear_fc([12])\.(weight|bias)(\d+)", rest)
    if match:
        projection, kind, expert = match.groups()
        if projection == "1":
            # 专家 fc1 由 gate_proj + up_proj 融合而成。
            return merge_gate_up(
                reader.get_tensor(f"{prefix}.mlp.experts.{expert}.gate_proj.{kind}"),
                reader.get_tensor(f"{prefix}.mlp.experts.{expert}.up_proj.{kind}"),
            )
        return reader.get_tensor(f"{prefix}.mlp.experts.{expert}.down_proj.{kind}")
    return None

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

  1. 破坏性变更:移除 mbridge 和 megatron-bridge 依赖后,依赖 mbridge 外部包的旧配置和旧脚本将直接失效;删除 Gemma4 模型支持会对使用 Gemma4 的现有用户造成明显回归。
  2. 权重转换正确性hf_to_megatron/qwen.pydeepseek.py 等新转换器是手工映射,若 HF 侧权重命名或张量布局变动(如 lm_head.weight 缺失、MTP 层结构变化),可能静默产生错误映射;merge_qkv 的 head 分组和 TP 切分逻辑风险较高。
  3. Qwen3.5-VL 新路径Qwen3_5VLModel.forward 强制要求 packed sequences,且 _inject_vision_embeddingslocal_vision_maskinput_ids[0] == token_id 的一致性做严格校验,如果 CP 布局或 vision features 数量不匹配会直接报错,属于新的运行时风险点。
  4. 文档与示例滞后:119 个文件的大改动可能遗漏个别引用(如 examples/geo3k_vlm_multi_turn 之外的示例脚本),需要关注 CI 和用户反馈。

影响范围非常大:对外部依赖模型支持(Gemma4、GLM-4V-MoE、GPT-OSS)的用户是破坏性升级;对使用 Qwen3.5、Qwen3.5-VL、DeepSeek-V3.2、MiMo、MiniMax-M2 等模型的用户,转换路径从桥接器切换为内部直接转换,行为可能略有差异。同时,仓库去掉了两个外部运行时依赖(mbridge、megatron.bridge),降低安装复杂度和维护成本;团队不再需要为第三方桥接层打补丁。

破坏性变更 权重转换重构 模型支持移除 外部依赖移除 新 VLM 路径

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论