Prhub

#24931 feat(mimo-v2): add EPD disaggregation support

原始 PR 作者 Abatom 合并时间 2026-05-18 10:33 文件变更 7 提交数 6 评论 14 代码增减 +961 / -289

执行摘要

MiMo-V2 添加 EPD 编码器分离支持

MiMo-V2 的视觉/音频编码器与 LLM 骨干计算特性差异大,物理部署在同一节点会导致 TP/DP 尺寸一刀切,浪费空闲组件 GPU 内存。EPD 让运营商在成本较低的 GPU 上运行编码器(自有 TP),在首选 TP/DP 拓扑上运行语言模型,并根据请求混合独立扩展两层。

PR 设计合理,关键讨论已解决,CI 通过。建议合并,并后续补充自动化测试以覆盖 EPD 端到端流程。值得关注的设计点包括编码服务器钩子模式和按模型类型属性拆分,可作为其他模型接入 EPD 的范例。

讨论亮点
  • 视频元数据属性拆分:ShangmingCai 指出原 _VIDEO_META_ATTRS 注释误导非 MiMo 模型,Abatom 将其拆分为通用和 MiMo 特有组,通过 video_meta_attrs_for 动态选择。
  • 编码服务器中视频音频编码抽象:ShangmingCai 建议将视频中音频编码逻辑抽取为模型侧钩子,Abatom 实现 encode_video_audio 方法并重命名以符合现有约定。
  • Embedding 拼接设备匹配:ShangmingCai 询问移除 .cuda().to("cpu") 是否引入设备问题,Abatom 解释所有 tensor 均为 CPU,直接拼接安全,原操作为性能优化非必需。

实现拆解

  1. 模型分离:在 MiMoV2ForCausalLM.__init__ 中根据 config.encoder_only / config.language_only 跳过语言模型(modellm_headLogitsProcessor),但始终保留视觉/音频编码器作为本地 fallback。添加权重路由前缀常量 _LANGUAGE_WEIGHT_PREFIXES_VISION_AUDIO_WEIGHT_PREFIXESload_weights 按角色过滤权重。
  2. 编码器预处理MiMoProcessor 增加 from_hf_config 类方法(无需 tokenizer)和 preprocess_for_encoder 方法,处理图像/视频/音频到模型输入张量。新增 _decode_frames_and_timestamps_ffprobe_has_audio 等共享工具。
  3. 编码服务器重构:将原先单一 _process_mm_items 拆分为 _process_image_items_process_video_items_process_audio_items 三个助手。若模型定义了 preprocess_mm_for_encoder 则委托模型预处理,否则回退到原有 per-modality 逻辑。新增 encode_video_audio 钩子处理视频中的音频轨道。
  4. 视频元数据扩展encode_receiver.py 中拆分通用(_GENERAL_VIDEO_META_ATTRS)和 MiMo 特有(_MIMO_VIDEO_AUDIO_META_ATTRS)视频元数据属性,通过 video_meta_attrs_formodel_type 动态解析,避免非 MiMo 模型引入额外字段。
  5. SHM 包装器泛化mm_utils.py 中提取 _wrap_tensor_or_list_unwrap_tensor_or_list 以支持 precomputed_embeddings 的共享内存传输,使 EPD 数据流兼容既有 SHM 机制。
  6. 启动配置调整server_args.py 中跳过 MiMoV2 encoder_only 模式的 fused-QKV TP 检查,并将 MiMoV2ForCausalLM 加入编码器分离允许列表。
    测试方面,PR 未包含自动化测试,但列出了详细的测试计划(单节点回归、端到端 EPD 验证等)。
文件 模块 状态 重要度
python/sglang/srt/models/mimo_v2.py 模型定义 modified 8.93
python/sglang/srt/multimodal/processors/mimo_v2.py 预处理 modified 8.65
python/sglang/srt/disaggregation/encode_server.py 编码服务 modified 8.64
python/sglang/srt/disaggregation/encode_receiver.py 接收逻辑 modified 7.52
python/sglang/srt/managers/mm_utils.py 工具函数 modified 7.19
python/sglang/srt/server_args.py 启动配置 modified 4.87
python/sglang/srt/managers/tokenizer_manager.py 管理服务 modified 4.33

关键符号

preprocess_mm_for_encoder encode_video_audio from_hf_config preprocess_for_encoder video_meta_attrs_for _process_image_items _process_video_items _process_audio_items _wrap_tensor_or_list _unwrap_tensor_or_list

关键源码片段

python/sglang/srt/disaggregation/encode_receiver.py core-logic

拆分通用和 MiMo 视频元数据属性,动态按 model_type 解析,确保非 MiMo 模型不引入额外字段

def video_meta_attrs_for(model_type: Optional[str]) -> tuple:
    # 根据 model_type 返回视频元数据属性元组
    # 所有模型共有的属性
    general = ("video_timestamps", "second_per_grid_ts")
    if model_type and "mimo" in model_type.lower():
        # MiMo 模型额外包含音频 - 视频对齐属性
        general += (
            "video_audio_feature_lens",
            "video_audio_segment_lens_flat",
            "video_audio_per_video_num_units",
            "video_audio_embedding",
        )
    return general
python/sglang/srt/managers/mm_utils.py core-logic

提取 _wrap_tensor_or_list 和 _unwrap_tensor_or_list 以支持 precomputed_embeddings 的 SHM 传输,为 EPD 提供必要的数据传递工具

def _wrap_tensor_or_list(value):
    # 包装 CPU tensor 或 tensor 列表为 ShmPointerMMData
    # 用于跨进程共享内存传输
    if isinstance(value, torch.Tensor) and value.is_cpu:
        return ShmPointerMMData(value)
    elif isinstance(value, (list, tuple)):
        wrapped = [
            (ShmPointerMMData(t) if isinstance(t, torch.Tensor) and t.is_cpu else t)
            for t in value
        ]
        return type(value)(wrapped) if isinstance(value, tuple) else wrapped
    return valuedef _unwrap_tensor_or_list(value):
    # 从 ShmPointerMMData 恢复为普通 tensor
    if isinstance(value, ShmPointerMMData):
        return value.materialize()
    elif isinstance(value, (list, tuple)):
        unwrapped = [
            t.materialize() if isinstance(t, ShmPointerMMData) else t
            for t in value
        ]
        return type(value)(unwrapped) if isinstance(value, tuple) else unwrapped
    return value

评论区精华

视频元数据属性命名与跨模型兼容性 设计

ShangmingCai 指出新加的 VIDEO_META_ATTRS 包含 MiMo 特有字段,注释提到必须匹配 MiMoProcessor,对其他模型具有误导性。

结论:Abatom 将属性拆分为 _GENERAL_VIDEO_META_ATTRS 和 _MIMO_VIDEO_AUDIO_META_ATTRS,并通过 video_meta_attrs_for 函数按 model_type 动态拼接,解决混淆。 · 已解决

视频音频编码应抽取为模型侧钩子 设计

ShangmingCai 建议将 encode_server.py 中 video_audio_features 的处理部分抽象成独立模块,避免模型逻辑侵入编码服务器。

结论:Abatom 将该部分抽出为 model-side 的 encode_video_audio 方法,服务器仅做调度,并重命名以符合现有约定。 · 已解决

Embedding 拼接时的设备匹配问题 正确性

ShangmingCai 询问移除 .cuda() 和 .to("cpu") 是否会导致设备不匹配。

结论:Abatom 解释所有 embedding 均为 CPU tensor(来自 ZMQ/Mooncake 链路),因此直接 cat 安全;之前 .cuda() 是性能优化,非必要。 · 已解决

风险与影响

权重路由可能因前缀匹配不完整导致加载错误,但已通过 is_vision_audio_weight 等辅助方法防御。MiMo-V2 特定视频元数据字段(video_audio*)若未在 decode 端正确重构,可能引发维度不匹配,动态属性机制降低了跨模型污染。编码服务器中 fallback 路径(Qwen2-VL、Kimi 等)依赖重构后的 per-modality 助手,回归风险较小但需验证。mm_utils.py 中 SHM 包装新增对 precomputed_embeddings 的处理,可能与其他模型交互时产生意料外的包装,但已有显式类型检查。整体缺少自动化测试覆盖,依赖手动验证计划。

用户启用 --encoder-only--language-only 即可部署 MiMo-V2 EPD,实现灵活资源配置。编码服务器新增模型侧钩子接口,未来模型无需修改编码服务器即可支持 EPD;但需确保模型实现 preprocess_mm_for_encoderencode_video_audio 等契约。团队需维护和文档化钩子契约及 MiMo-V2 特有视频元数据属性。

核心路径变更 缺少测试覆盖 模型兼容性 数据契约变更

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论