Prhub

#52881 [BugFix] Revert incorrect MM keep_on_cpu=True changes

原始 PR 作者 njhill 合并时间 2026-08-19 15:07 文件变更 7 提交数 1 评论 2 代码增减 +17 / -12

执行摘要

回滚错误的 MM keep_on_cpu=True 变更,修复 CI 回归

PR #52827 在修改 vllm launch 参数校验时,错误地给多个多模态模型的字段配置添加了 keep_on_cpu=True,导致 CI 回归。该 PR 的目的是回滚这些不正确的变更,恢复原有行为,保证 CI 稳定。PR body 明确说明 "Fix CI regression introduced by #52827"。

该 PR 是紧急修复,改动直接,建议快速合入以稳定 CI。值得关注的是回滚时对 non_blocking=True 的补充,这是一个性能折中策略,可以借鉴。但需在合入前确认相关模型的测试覆盖。

讨论亮点

无实质性 review 讨论,仅有一个来自 claude[bot] 的自动提示(由于来自 fork 而禁用自动审查)和 AndreasKaratzas 的批准。没有内容需要提炼。

实现拆解

回滚操作分两步:

  1. MultiModalFieldConfigkeep_on_cpu=True 参数进行还原,涉及文件:vllm/model_executor/models/minicpmv4_6.pytgt_sizesvideo_tgt_sizes)、llava_onevision2.pypatch_positionspatch_positions_videos)、fireredlid.pyspeech_lengths)、qwen2_audio.pyfeature_attention_mask)。
  2. 对部分张量 .to() 调用添加 non_blocking=True 以补偿移除 keep_on_cpu 带来的潜在同步开销,涉及文件:ernie45_vl.pyencoder_eager_forwardpostprocess_encoder_output,以及 isaac.py_process_image_input
  3. 删除 moss_transcribe_diarize.py 中错误的 keep_on_cpu=True 配置。
文件 模块 状态 重要度
vllm/model_executor/models/ernie45_vl.py 模型执行器 modified 6.01
vllm/model_executor/models/isaac.py 模型执行器 modified 5.28
vllm/model_executor/models/minicpmv4_6.py 模型执行器 modified 5.28
vllm/model_executor/models/llava_onevision2.py 模型执行器 modified 5.11
vllm/model_executor/models/fireredlid.py 模型执行器 modified 5.1
vllm/model_executor/models/qwen2_audio.py 模型执行器 modified 5.1
vllm/model_executor/models/moss_transcribe_diarize.py 模型执行器 modified 4.56

关键符号

encoder_eager_forward postprocess_encoder_output _process_image_input _minicpmv4_6_field_config _field_config _get_mm_fields_config _qwen2audio_field_config

关键源码片段

vllm/model_executor/models/ernie45_vl.py data-contract

修改了 eager forward 和 postprocess 中的张量传输方式,添加 non_blocking=True,属于需要验证的性能补偿改动。

# vllm/model_executor/models/ernie45_vl.py ( 部分 )
def encoder_eager_forward(
    self, mm_kwargs: dict[str, Any], path: str = "default"
) -> torch.Tensor:
    # 异步拷贝 grid_thw,避免阻塞,提升 eager 路径性能。
    pixel_values = mm_kwargs["pixel_values"].type(self.vision_model.dtype)
    grid_thw = mm_kwargs["image_grid_thw"].to(
        self.vision_model.device, non_blocking=True
    )
    image_features = self.vision_model(pixel_values, grid_thw)
    return self.resampler_model(image_features, grid_thw)def postprocess_encoder_output(
    self,
    outputs: dict[str, torch.Tensor],
    indices: list[int],
    per_item_out_tokens: list[int],
    dest,
    clone: bool = False,
    batch_mm_kwargs: dict[str, Any] | None = None,
) -> None:
    # 利用 CPU 上的 grid_thw 直接计算有效 token 数,再异步拷贝到 GPU。
    output = outputs["default"]
    grid_thw_cpu = batch_mm_kwargs["image_grid_thw"]
    grid_thw = grid_thw_cpu.to(output.device, non_blocking=True)
    num_valid = int(
        (grid_thw_cpu[:, 0] * grid_thw_cpu[:, 1] * grid_thw_cpu[:, 2]).sum()
    )
    image_embeds = self.resampler_model(output[:num_valid], grid_thw)
    scatter_output_slices(image_embeds, indices, per_item_out_tokens, dest, clone)
vllm/model_executor/models/isaac.py data-contract

修改了 spatial_grids 的传输方式,同样添加 non_blocking=True 作为性能补偿。

# vllm/model_executor/models/isaac.py ( 部分 )
def _process_image_input(
    self,
    image_input: IsaacImagePixelInputs,
) -> tuple[torch.Tensor, ...]:
    pixel_values = image_input["pixel_values"]
    image_grid_thw = image_input["image_grid_thw"]
    if pixel_values.numel() == 0:
        return ()
    device = next(self.language_model.parameters()).device
    dtype = self.vision_embedding.linear_fc1.weight.dtype
    pixel_values = pixel_values.to(device=device, dtype=dtype)
    # 非阻塞传输,减少 H2D 同步开销。
    spatial_grids = image_grid_thw[:, 1:3].to(
        device, dtype=torch.int32, non_blocking=True
    )
    vision_embeddings = self.vision_embedding((pixel_values, spatial_grids))
    merge_size = self.config.vision_config.pixel_shuffle_scale_factor
    sizes = spatial_grids.prod(-1) // (merge_size * merge_size)
    return tuple(vision_embeddings.split(sizes.tolist()))
vllm/model_executor/models/minicpmv4_6.py data-contract

回滚了 tgt_sizes 和 video_tgt_sizes 字段的 keep_on_cpu=True 设置,是回滚的核心文件之一。

# vllm/model_executor/models/minicpmv4_6.py ( 部分 )
def _minicpmv4_6_field_config(hf_inputs: Mapping[str, torch.Tensor]):
    fields = dict(
        pixel_values=MultiModalFieldConfig.batched("image"),
        tgt_sizes=MultiModalFieldConfig.batched("image"), # 回滚:不再 keep_on_cpu
        image_embeds=MultiModalFieldConfig.batched("image"),
        video_pixel_values=MultiModalFieldConfig.batched("video"),
        video_image_sizes=MultiModalFieldConfig.batched("video", keep_on_cpu=True),
        video_tgt_sizes=MultiModalFieldConfig.batched("video"), # 回滚:不再 keep_on_cpu
        video_embeds=MultiModalFieldConfig.batched("video"),
    )
    if "use_vit_merger" in hf_inputs:
        fields["use_vit_merger"] = MultiModalFieldConfig.batched(
            "image", keep_on_cpu=True
        )
    return fields

评论区精华

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

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

风险与影响

回滚本身风险较低,但需要注意:

  1. keep_on_cpu 的移除意味着这些字段将保留在 GPU 上,可能增加显存占用或改变数据流路径,对于 MiniCPM-V 和 Qwen2-Audio 等模型可能影响性能,需要回归测试验证。
  2. 添加 non_blocking=True 的改动(ernie45_vl.pyisaac.py)属于新增改动,需要确保相关张量在异步传输时没有被提前修改,否则可能引入并发问题。
  3. 回滚未包含测试变更,可能缺乏对回滚后行为的自动化验证,存在回归风险。

影响范围:涉及 6 个多模态模型处理器(MiniCPM-V4.6、LLaVA-OneVision2、FireRedLID、Qwen2-Audio、Ernie45-VL、Isaac、Moss),影响这些模型的输入处理逻辑,尤其是字段的 CPU/GPU 放置。对用户而言,如果这些模型在 CI 中受回归影响,回滚将修复其功能;对系统而言,消除了 CI 的不稳定因素。影响程度中等,但由于改动分散,需确保各模型回归测试通过。

回滚未包含测试 异步传输引入并发风险 显存占用可能增加

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论