Prhub

#55448 [Bugfix][Multimodal] Bound renderer warmup to the prefill token budget

原始 PR 作者 lucamotz 合并时间 2026-09-06 00:43 文件变更 4 提交数 3 评论 10 代码增减 +59 / -8

执行摘要

修复多模态渲染器热身内存占用过高问题,将热身预算限制在 prefill token 上限内。

为解决 GLM-5.3-Flash 模型在 GB10 平台上迁移至最新主线后 KV 缓存容量下降的问题(Issue #55435)。该 Issue 指出,持久化执行器中,渲染器热身阶段的虚拟多模态预处理会占用 CPU 分配器内存,限制了 KV 缓存容量。具体而言,Issue 描述中提到 “Bound renderer warmup to the prefill token budget; full-context dummy preprocessing retains CPU allocator memory”。本 PR 旨在通过将热身序列长度约束到实际分块预填充的最大批处理 token 数,来释放这部分被占用的内存。

该 PR 值得精读,特别是 MultiModalRegistry.get_dummy_mm_inputs 方法中如何利用 SchedulerConfig 来调整行为,这是一个将系统配置与模块内部逻辑解耦的清晰示例。对于关注 vLLM 内存优化、多模态模型支持或启动性能的工程师,这个变更展示了如何通过细粒度的配置感知来改善资源使用。建议关注实现中“约束”与“兼容”的平衡设计。

讨论亮点

维护者 DarkLight1337 首先建议直接调用 MultiModalRegistry.get_dummy_mm_inputs 而非内部逻辑,并指出应仅在启用分块预填充时使用 max_num_batched_tokens。作者 lucamotz 随后提出为 get_dummy_mm_inputs 添加可选 seq_len 参数的方案。最终,DarkLight1337 建议在 get_dummy_mm_inputs 内部直接根据 max_num_batched_tokens 设置序列长度,这简化了 API 并将决策封装在注册表内部,避免了为单个调用者添加特定参数。讨论共识是将预算约束逻辑内聚到 MultiModalRegistry 中,并确保现有性能分析(profiling)调用者行为不变。

实现拆解

  1. 核心逻辑改造:在 vllm/multimodal/registry.py 的 MultiModalRegistry.get_dummy_mm_inputs 方法中,新增可选的 scheduler_config 参数。当该参数不为 None 且其 enable_chunked_prefill 为 True 时,将用于生成虚拟输入的 seq_len 从 model_config.max_model_len 缩小为 min(max_model_len, scheduler_config.max_num_batched_tokens)。此修改集中了序列长度计算逻辑。
  2. 热身调用点重构:修改 vllm/renderers/base.py 中 BaseRenderer._warmup_mm_processor 方法的实现。原实现直接调用 processor.dummy_inputs.get_dummy_processor_inputs 并传入 max_model_len;新实现改为调用 mm_registry.get_dummy_mm_inputs,并传入 processor 和 self.config.scheduler_config,从而将热身逻辑委托给注册表,确保序列长度受到预算约束。
  3. 测试配套:
    • 在 tests/multimodal/test_registry.py 中新增 test_dummy_inputs_scheduler_budget 测试用例,使用参数化覆盖无 scheduler_config、启用/禁用 chunked prefill 以及不同 max_model_len 的组合场景,验证 get_dummy_mm_inputs 生成的虚拟输入序列长度符合预期。
    • 在 tests/renderers/test_warmup.py 中新增 test_warmup_sequence_length_is_bounded 测试用例,并调整 _make_renderer_mock 辅助函数以模拟 scheduler_config,验证完整的渲染器热身流程确实应用了序列长度约束。
文件 模块 状态 重要度
vllm/multimodal/registry.py 多模态注册表 modified 6.37
vllm/renderers/base.py 渲染器基类 modified 5.94
tests/multimodal/test_registry.py 多模态测试 modified 5.43
tests/renderers/test_warmup.py 渲染器测试 modified 4.82

关键符号

vllm.multimodal.registry.MultiModalRegistry.get_dummy_mm_inputs vllm.renderers.base.BaseRenderer._warmup_mm_processor

关键源码片段

vllm/multimodal/registry.py dependency-wiring

核心变更文件。修改了 `get_dummy_mm_inputs` 方法,引入基于 `SchedulerConfig` 的序列长度约束逻辑,是解决内存问题的关键。

    def get_dummy_mm_inputs(
        self,
        model_config: "ModelConfig",
        mm_counts: Mapping[str, int],
        *,
        cache: BaseMultiModalProcessorCache | None = None,
        processor: BaseMultiModalProcessor | None = None,
        scheduler_config: "SchedulerConfig | None" = None,
    ) -> MultiModalInput:
        """
        Create dummy data for profiling the memory usage of a model.
​
        The model is identified by `model_config`.
        """
        # 默认使用模型的最大序列长度进行分析
        seq_len = model_config.max_model_len
        # 当传入 scheduler_config 且启用了分块预填充时,
        # 将热身预算限制为每次处理的最大 token 数,以避免不必要的内存占用。
        # 这是修复渲染器 warmup 内存过高的核心逻辑。
        if scheduler_config is not None and scheduler_config.enable_chunked_prefill:
            seq_len = min(seq_len, scheduler_config.max_num_batched_tokens)
​
        if processor is None:
            processor = self.create_processor(model_config, cache=cache)
​
        mm_config = model_config.get_multimodal_config()
        processor_inputs = processor.dummy_inputs.get_dummy_processor_inputs(
            seq_len=seq_len,
            mm_counts=mm_counts,
            mm_options=mm_config.limit_per_prompt,
        )
        mm_inputs = processor.apply(
            processor_inputs,
            timing_ctx=TimingContext(enabled=False),
        )
​
        prompt_token_ids = mm_inputs["prompt_token_ids"]
        total_len = len(prompt_token_ids)
        if total_len < seq_len:
            prompt_token_ids.extend([0] * (seq_len - total_len))
​
        return mm_inputs
vllm/renderers/base.py dependency-wiring

热身调用点。将 warmup 逻辑重构为委托给 `MultiModalRegistry.get_dummy_mm_inputs`,从而应用新的预算约束,并复用公共代码。

    def _warmup_mm_processor(
        self,
        processor: "BaseMultiModalProcessor",
        *,
        log_prefix: str,
    ) -> None:
        model_config = self.model_config
        # 从允许的模态限制中过滤出数量 > 0 的模态
        mm_limits = {k: v for k, v in processor.info.allowed_mm_limits.items() if v > 0}
​
        start_time = time.perf_counter()
        # 直接调用注册表的统一接口来生成虚拟输入。
        # 通过传递 `scheduler_config`,内部逻辑会自动将序列长度
        # 约束为 `max_num_batched_tokens`(当 chunked prefill 启用时),
        # 从而避免占用不必要的内存。
        _ = mm_registry.get_dummy_mm_inputs(
            model_config,
            mm_counts=dict.fromkeys(mm_limits, 1),
            processor=processor,
            scheduler_config=self.config.scheduler_config,
        )
​
        elapsed = time.perf_counter() - start_time
        logger.info("%s warmup completed in %.3fs", log_prefix, elapsed)

评论区精华

热身预算计算位置与 API 设计 设计

DarkLight1337 建议直接在 `get_dummy_mm_inputs` 中设置预算,而非添加 `seq_len` 参数。lucamotz 最初提议添加 `seq_len` 参数以保持 API 明确,但最终采纳了 DarkLight1337 的建议,将逻辑内聚到注册表方法中。

结论:决定在 `MultiModalRegistry.get_dummy_mm_inputs` 内部根据 `max_num_batched_tokens` 计算序列长度,通过新增 `scheduler_config` 参数传入配置。这简化了 API,将决策封装,且不影响现有调用者。 · 已解决

风险与影响

  1. 正确性风险:主要风险在于 scheduler_config 参数未正确传递或判断,导致序列长度约束未生效。但新的参数是可选的且默认为 None,保证了向后兼容性;同时,全面的单元测试覆盖了各种配置组合,风险较低。
  2. 兼容性风险:get_dummy_mm_inputs 方法的 API 发生了变化(新增可选参数),但这是非破坏性变更。所有现有的性能分析(profiling)调用者因未传入 scheduler_config,其行为(使用完整上下文长度)得以保留,符合设计意图。
  3. 性能影响:这是一个正面性能优化。减少了启动时的内存分配,理论上能提升可加载模型的 KV 缓存容量和吞吐量。没有引入额外的运行时开销。

对用户和系统的影响主要体现在资源效率和稳定性上。对于部署长上下文多模态模型(如 GLM-5.3-Flash)的用户,引擎启动时的内存占用将显著降低(Issue #55435 中报告可减少约 2 GiB CPU 内存),从而允许分配更多的 KV 缓存,提升服务容量和长文本处理能力。此变更仅影响引擎初始化阶段的 warmup 过程,不影响在线服务的请求处理路径。对于团队而言,该修复通过代码复用(利用注册表方法)和清晰的测试,维护了相关代码的长期可维护性。

API 变更 内存行为变更

关联 Issue

#55435 [Bugfix] Restore GLM-5.3-Flash startup memory and KV capacity

完整报告

参与讨论