Prhub

#49015 [Bugfix] Qwen3-VL/Qwen-Omni: honor max_pixels/min_pixels for video prompts

原始 PR 作者 lishunyang12 合并时间 2026-07-19 01:29 文件变更 2 提交数 1 评论 1 代码增减 +30 / -0

执行摘要

修复 Qwen 视频 max_pixels 被丢弃的问题

Fixes #48733. 用户报告 Qwen3-Omni 在线推理的 prompt_tokens 比 HF 处理器语义高出约 65%,原因是 mm_processor_kwargs 中的 max_pixels 和 min_pixels 被静默丢弃,导致视频使用未限制的原生分辨率。PR body 详细说明了两个根因:HF 视频处理器只从 size.{longest,shortest}_edge 读取帧像素预算;引擎的 get_allowed_kwarg_only_overrides 过滤掉了不在 HF 签名中的 max_pixels。

该 PR 值得精读,尤其适用于理解 vLLM 多模态处理器的 mm_kwargs 传递与 HF 处理器之间的适配层机制。其设计决策——在 _call_hf_processor 内部通过 ctx.get_merged_mm_kwargs 统一读取引擎保存的配置,而非修改外部过滤逻辑——值得作为类似问题的修复模式参考。

讨论亮点

无 review 评论。仅有一条来自 github-actions 的自动欢迎消息和一条来自 claude[bot] 的自动回复(提及 fork PR 无法自动审查)。最终由 ywang96 直接批准合并。

实现拆解

  1. Qwen3-VL 视频处理分支(vllm/model_executor/models/qwen3_vl.py):在 _call_hf_processor 的视频循环中,新增 merged = self.info.ctx.get_merged_mm_kwargs(mm_kwargs),当 merged 包含 size/min_pixels/max_pixels 时,读取 self.info.get_video_processor().size 作为基准,将 min_pixels 映射到 shortest_edge,max_pixels 映射到 longest_edge,合并后写入 video_mm_kwargs["size"],使 HF 处理器能够正确识别像素预算。

  2. Qwen2.5-Omni/Qwen3-Omni 视频处理分支(vllm/model_executor/models/qwen2_5_omni_thinker.py):在 _call_hf_processor 中,新增相同的合并逻辑,但先检查 mm_data.get("videos") 非空,然后通过 self.info.get_hf_processor().video_processor.size 获取处理器基准尺寸,同样将 min_pixels/max_pixels 映射到 shortest_edge/longest_edge。

  3. 无变更时的行为:当 mm_processor_kwargs 中未传递 size/min_pixels/max_pixels 时,两处逻辑均不触发,mm_kwargs 保持不变,保持 uncapped 行为不变,无回归风险。

文件 模块 状态 重要度
vllm/model_executor/models/qwen2_5_omni_thinker.py 模型执行器 modified 6.43
vllm/model_executor/models/qwen3_vl.py 模型执行器 modified 6.3

关键符号

Qwen2_5OmniThinkerMultiModalProcessor._call_hf_processor Qwen3VLMultiModalProcessor._call_hf_processor

关键源码片段

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

修复 Qwen2.5-Omni 和 Qwen3-Omni 视频 max_pixels 被丢弃的核心变更文件。在 _call_hf_processor 中新增从 ctx.get_merged_mm_kwargs 读取并翻译 size/min_pixels/max_pixels 的逻辑。

# vllm/model_executor/models/qwen2_5_omni_thinker.py
# 在 _call_hf_processor 的音频处理后、调用 super() 之前插入
​
merged = self.info.ctx.get_merged_mm_kwargs(mm_kwargs)
# 仅当有视频数据且用户指定了 size/min_pixels/max_pixels 才处理
if mm_data.get("videos") and (
    merged.keys() & {"size", "min_pixels", "max_pixels"}
):
    mm_kwargs = dict(mm_kwargs)
    # 从 HF 处理器获取默认的 video_processor.size 字典
    video_size = dict(self.info.get_hf_processor().video_processor.size)
    size_override = merged.get("size")
    if size_override is not None:
        video_size = video_size | size_override # 合并用户传递的 size 覆盖
    min_pixels = merged.get("min_pixels")
    if min_pixels is not None:
        video_size["shortest_edge"] = min_pixels # 映射 min_pixels -> shortest_edge
    max_pixels = merged.get("max_pixels")
    if max_pixels is not None:
        video_size["longest_edge"] = max_pixels # 映射 max_pixels -> longest_edge
    mm_kwargs["size"] = video_size # 替换 size 使 HF 处理器能识别像素预算
vllm/model_executor/models/qwen3_vl.py data-contract

修复 Qwen3-VL 视频 max_pixels 被丢弃的核心变更文件。在 _call_hf_processor 的视频循环中新增使用 ctx.get_merged_mm_kwargs 的像素预算处理逻辑。

# vllm/model_executor/models/qwen3_vl.py
# 在 _call_hf_processor 的视频循环中创建 video_mm_kwargs 后、设置 fps 前插入
​
video_mm_kwargs = dict(**mm_kwargs)
merged = self.info.ctx.get_merged_mm_kwargs(mm_kwargs)
# 当用户指定了 size/min_pixels/max_pixels 中的任一键时处理
if merged.keys() & {"size", "min_pixels", "max_pixels"}:
    # 从 HF 视频处理器获取默认 size(包含 shortest_edge 和 longest_edge)
    video_size = dict(self.info.get_video_processor().size)
    size_override = merged.get("size")
    if size_override is not None:
        video_size = video_size | size_override # 合并用户传递的 size 覆盖
    min_pixels = merged.get("min_pixels")
    if min_pixels is not None:
        video_size["shortest_edge"] = min_pixels # min_pixels -> shortest_edge
    max_pixels = merged.get("max_pixels")
    if max_pixels is not None:
        video_size["longest_edge"] = max_pixels # max_pixels -> longest_edge
    video_mm_kwargs["size"] = video_size # 替换 size 使 HF 处理器正确限制帧分辨率

评论区精华

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

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

风险与影响

风险较低。变更仅在视频分支且显式检查 merged.keys() & {"size", "min_pixels", "max_pixels"},非破坏性。但需注意:min_pixels 和 max_pixels 映射到 shortest_edge/longest_edge 是语义等价假设,若 HF 处理器未来改变这些键的含义可能失效。另外,video_size 从默认处理器尺寸构建,若用户同时传递了 size 和 max_pixels,size 先被合并,然后 max_pixels 会覆盖 longest_edge,行为符合预期。

影响范围集中在 Qwen3-VL、Qwen2.5-Omni 和 Qwen3-Omni 三个模型系列。用户侧效果显著:PR body 中的 E2E 测试显示,对于 1280x720 视频,max_pixels=393216 时 prompt_tokens 从 12044 降至 324;对于 480x832 视频,max_pixels=50176 时从 4086 降至 486。这直接降低 TTFT 和显存占用,提升系统吞吐量。对不传递 max_pixels 的用户无影响。

需要模型行为验证

关联 Issue

#48733 [Bug]: Qwen3-Omni video prompt_tokens ~65% higher than HF processor semantics for identical sampling config (online path)

完整报告

参与讨论