Prhub

#30904 feat: unify multimodal feature transport

原始 PR 作者 mickqian 合并时间 2026-07-15 17:42 文件变更 7 提交数 3 评论 3 代码增减 +240 / -51

执行摘要

统一多模态特征传输,新增 --mm-feature-transport 参数

PR 描述指出,需要将多模态特征传输的配置统一到一个参数,避免分散的环境变量和命令行标志导致混淆。CUDA IPC 是有意选择加入的,因为其固定池会减少 KV 缓存空间,不应成为无条件的默认值。新增参数后,旧标志和环境变量被映射到新策略并给出弃用警告。

建议技术管理者关注此 PR 中的配置迁移模式,特别是 _handle_multimodal_feature_transport 方法中向后兼容的处理逻辑,可作为类似场景的参考。工程师应重点阅读 server_args.py 中的解析逻辑和 base_processor.py 中从全局变量到实例属性的转变,理解如何安全地废弃旧配置。

讨论亮点

该 PR 的 Review 没有产生实质性讨论。PR 描述中提到了对 #30902 的依赖,该 PR 实现了 SGLANG_MM_FEATURE_CACHE_MB 作为跨 tokenizer worker 的硬预算,应先于本 PR 合并。

实现拆解

  1. 添加新配置字段:在 python/sglang/srt/server_args.pyServerArgs 类中新增 mm_feature_transport 字段,类型为 Optional[Literal['cpu', 'cuda_ipc']],默认 None,同时将 keep_mm_feature_on_device 的描述标记为已弃用。

  2. 实现解析逻辑:新增 _handle_multimodal_feature_transport 方法,负责解析新旧配置的优先级:如果指定了 --keep-mm-feature-on-device,则将其映射为 cuda_ipc;如果 mm_feature_transport 未指定,则回退到环境变量 SGLANG_USE_CUDA_IPC_TRANSPORT,否则默认 cpu;如果两者冲突则记录警告。对于 cuda_ipc 模式,校验平台必须为 NVIDIA CUDA 且为单节点,并记录池大小、base GPU、tokenizer worker 数量等信息。

  3. 基类处理器适配:在 python/sglang/srt/multimodal/processors/base_processor.pyBaseMultimodalProcessor.__init__ 中,从 server_args 读取 mm_feature_transport 并设置 self.use_cuda_ipcself.mm_feature_transport 实例属性,替换原来的全局变量 SGL_USE_CUDA_IPC。同时,在需要分配 IPC 池的地方根据 self.use_cuda_ipc 决定。

  4. 具体处理器清理moss_vl.pyernie45_vl.pymidashenglm.py 中移除对全局 SGL_USE_CUDA_IPC 的引用,改用实例属性 self.use_cuda_ipc,并提取公共的 _wrap_tensor_for_cuda_ipc 方法以减少重复代码。

  5. 单元测试覆盖:在 test/registered/unit/server_args/test_server_args.py 中新增 TestMultimodalFeatureTransport 类,测试显式指定 CUDA IPC、旧标志映射、显式 CPU 覆盖环境变量、默认 CPU、非 NVIDIA 拒绝、多节点拒绝六种场景。在 test/registered/unit/managers/test_mm_process_config.py 中新增 TestMultimodalFeatureTransportRuntime 类,验证运行时根据 mm_feature_transport 正确分配或不分配 IPC 池。

文件 模块 状态 重要度
python/sglang/srt/server_args.py 服务参数 modified 7.58
test/registered/unit/server_args/test_server_args.py 服务参数测试 modified 6.82
python/sglang/srt/multimodal/processors/base_processor.py 多模态处理器 modified 6.06
python/sglang/srt/multimodal/processors/moss_vl.py 多模态处理器 modified 6.18
test/registered/unit/managers/test_mm_process_config.py 多模态配置测试 modified 6.37
python/sglang/srt/multimodal/processors/ernie45_vl.py 多模态处理器 modified 5.17
python/sglang/srt/multimodal/processors/midashenglm.py 多模态处理器 modified 4.09

关键符号

_handle_multimodal_feature_transport BaseMultimodalProcessor.__init__ BaseMultimodalProcessor._wrap_tensor_for_cuda_ipc

关键源码片段

python/sglang/srt/server_args.py core-logic

核心变更文件,新增了 `mm_feature_transport` 字段和 `_handle_multimodal_feature_transport` 方法,实现了新旧配置的统一解析

def _handle_multimodal_feature_transport(self):
    """Resolve multimodal feature transport before tokenizer workers start.    CUDA IPC is deliberately opt-in: its fixed pool lives on ``base_gpu_id``
    and reduces the memory left for model/KV-cache allocations. The legacy
    flag and environment variable remain supported so existing deployments
    continue to work, but both map to this single policy.
    """
    requested_transport = self.mm_feature_transport
    legacy_ipc_is_set = envs.SGLANG_USE_CUDA_IPC_TRANSPORT.is_set()
    legacy_ipc_enabled = envs.SGLANG_USE_CUDA_IPC_TRANSPORT.get()
​
    # 如果旧标志 keep_mm_feature_on_device 被设置,则映射为 cuda_ipc
    if self.keep_mm_feature_on_device:
        if requested_transport == "cpu":
            raise ValueError(
                "--keep-mm-feature-on-device conflicts with "
                "--mm-feature-transport=cpu. Use only "
                "--mm-feature-transport=cuda_ipc."
            )
        requested_transport = "cuda_ipc"
        logger.warning(
            "--keep-mm-feature-on-device is deprecated; using "
            "--mm-feature-transport=cuda_ipc instead."
        )
​
    # 如果新参数未指定,则检查环境变量,否则默认 cpu
    if requested_transport is None:
        if legacy_ipc_is_set:
            requested_transport = "cuda_ipc" if legacy_ipc_enabled else "cpu"
            logger.warning(
                "SGLANG_USE_CUDA_IPC_TRANSPORT is deprecated; use "
                "--mm-feature-transport=%s instead.",
                requested_transport,
            )
        else:
            requested_transport = "cpu"
    elif legacy_ipc_is_set and legacy_ipc_enabled != (requested_transport == "cuda_ipc"):
        # 新参数与环境变量冲突时,新参数优先
        logger.warning(
            "--mm-feature-transport=%s overrides the conflicting legacy "
            "SGLANG_USE_CUDA_IPC_TRANSPORT=%s setting.",
            requested_transport,
            int(legacy_ipc_enabled),
        )
​
    # 校验 CUDA IPC 的平台要求
    if requested_transport == "cuda_ipc":
        if not is_cuda():
            raise ValueError("--mm-feature-transport=cuda_ipc requires NVIDIA CUDA.")
        if self.nnodes != 1:
            raise ValueError("--mm-feature-transport=cuda_ipc only supports a single node.")
​
        pool_budget_mb = envs.SGLANG_MM_FEATURE_CACHE_MB.get()
        logger.info(
            "Using CUDA IPC for multimodal features: reserving up to %d MiB "
            "on base GPU %d across %d tokenizer worker(s). This reduces KV "
            "cache headroom; a full pool falls back to CPU transport.",
            pool_budget_mb,
            self.base_gpu_id,
            self.tokenizer_worker_num,
        )
​
    self.mm_feature_transport = requested_transport
    # 将解析结果写回环境变量,供后续 tokenizer worker 使用
    envs.SGLANG_USE_CUDA_IPC_TRANSPORT.set(
        "1" if requested_transport == "cuda_ipc" else "0"
    )
test/registered/unit/server_args/test_server_args.py test-coverage

新增了 TestMultimodalFeatureTransport 测试类,覆盖六种配置组合场景,是配置逻辑的主要验证

class TestMultimodalFeatureTransport(CustomTestCase):
    @patch("sglang.srt.server_args.is_cuda", return_value=True)
    def test_cuda_ipc_is_explicit_and_bounded(self, _mock_is_cuda):
        # 显式指定 cuda_ipc,环境变量冲突时仍采用新参数
        server_args = ServerArgs(
            model_path="dummy",
            mm_feature_transport="cuda_ipc",
            tokenizer_worker_num=4,
            base_gpu_id=2,
        )
        with patch.dict(os.environ, {"SGLANG_USE_CUDA_IPC_TRANSPORT": "0"}):
            with self.assertLogs(server_args_module.logger, level="INFO") as logs:
                server_args._handle_multimodal_feature_transport()
            self.assertEqual(server_args.mm_feature_transport, "cuda_ipc")
            self.assertTrue(envs.SGLANG_USE_CUDA_IPC_TRANSPORT.get())
        output = "\n".join(logs.output)
        self.assertIn("base GPU 2", output)
        self.assertIn("4 tokenizer worker", output)
​
    @patch("sglang.srt.server_args.is_cuda", return_value=True)
    def test_legacy_keep_flag_maps_to_cuda_ipc(self, _mock_is_cuda):
        # 旧标志 keep_mm_feature_on_device 会被映射为 cuda_ipc
        server_args = ServerArgs(model_path="dummy", keep_mm_feature_on_device=True)
        with patch.dict(os.environ, {"SGLANG_USE_CUDA_IPC_TRANSPORT": "0"}):
            with self.assertLogs(server_args_module.logger, level="WARNING") as logs:
                server_args._handle_multimodal_feature_transport()
            self.assertEqual(server_args.mm_feature_transport, "cuda_ipc")
            self.assertFalse(server_args.keep_mm_feature_on_device)
            self.assertTrue(envs.SGLANG_USE_CUDA_IPC_TRANSPORT.get())
        self.assertIn("deprecated", logs.output[0])

评论区精华

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

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

风险与影响

  1. 向后兼容风险:旧标志 --keep-mm-feature-on-device 和环境变量 SGLANG_USE_CUDA_IPC_TRANSPORT 仍被支持,但会发出弃用警告。若用户脚本依赖旧行为且未指定新参数,默认 cpu 可能改变之前隐式启用 CUDA IPC 的行为(之前环境变量未设置时可能默认 false?但实际默认可能就是 false,所以默认 CPU 一致)。需要确认默认值一致。
  2. CUDA IPC 内存占用cuda_ipc 模式会在 GPU 0 上分配池,可能减少 KV 缓存可用内存,若用户误开启可能导致 OOM。PR 强调这是 opt-in,日志会记录池大小。
  3. 平台限制:非 NVIDIA 平台和多节点配置下使用 cuda_ipc 会抛出 ValueError,需要用户调整配置。
  4. 测试覆盖:新增了单元测试但未涉及集成测试,可能在真实多节点或非 NVIDIA 环境下行为未充分验证。

用户:提供更清晰的配置接口,弃用旧标志,过渡期用户仍可用旧方式但会收到警告。系统:统一了多模态特征传输的配置路径,减少了全局变量的使用,使传输策略可实例级别控制。团队:后续维护更简单,添加新的传输模式只需扩展 mm_feature_transport 的取值。

配置迁移兼容性 CUDA IPC 内存占用 平台限制硬检查 缺少集成测试

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论