Prhub

#56385 [Bugfix][MM] Fix swapped H/W in dummy video profiling inputs

原始 PR 作者 hungnnvidia 合并时间 2026-09-11 13:25 文件变更 1 提交数 1 评论 2 代码增减 +1 / -1

执行摘要

修复多模态 dummy video 宽高转置

PR body 明确指出动机:BaseDummyInputsBuilder._get_dummy_videos 构造 dummy profiling video 时把宽度和高度写反了,"for any non-square dummy size, the profiling video has H and W transposed, feeding memory profiling / max-token estimation the wrong aspect ratio",且 "Square sizes hide the bug"。仓库内其它视频路径(如 vllm/multimodal/video.py 的 rescale_video_size 用 _, height, width, _ = frames.shape)都按 (T, H, W, C) 处理,兄弟方法 _get_dummy_images 也已使用正确顺序(Image.new("RGB", (width, height))),因此这是一处与全局约定背离的隐性错误,需要在进入真实 profiling 计算前修正。

不必逐行精读,但值得花 5 分钟了解这处"形状约定不一致"的经典案例:真正有价值的是认识到 profiling dummy 输入与真实数据处理路径之间存在隐式契约,正方形默认值会长期掩盖 H/W 转置。阅读时重点关注三点:(1) 为什么以 vllm/multimodal/video.py 的 rescale_video_size 作为约定基准;(2) dummy 形状变化会经由 token 估算传导到 KV cache 与 max_num_seqs;(3) 该修复缺少单测,若后续要动 _get_dummy_videos 或各模型的 VideoDummyOptions,建议先补一条 shape == (T, H, W, C) 的断言测试。

讨论亮点

本次讨论非常轻量,几乎没有技术交锋:claude[bot] 仅说明 fork PR 的自动 review 被禁用,需要 maintainer 手动 @claude review 才会跑一次性评审,实际未触发;maintainer DarkLight1337 只做了 /ci run 与一句 "Thanks" 的 approve,没有对形状约定或影响面提出质疑。作者则在 PR body 中承担了主要论证工作:用 rescale_video_size 的 _, height, width, _ = frames.shape 和 _get_dummy_images 的 Image.new("RGB", (width, height)) 两条证据说明 (T, H, W, C) 是既定约定,并附上可运行的复现脚本与前后对照输出(shape 由 (4, 1920, 1080, 3) 变为 (4, 1080, 1920, 3))。整体结论是无争议的一行修复,但评审过程未产生关于 profiling token 数变化幅度的实测数据,也没有要求补测试。

实现拆解

  1. 定位入口:变更集中在 vllm/multimodal/processing/dummy_inputs.py 的 BaseDummyInputsBuilder._get_dummy_videos,该方法是多模态模型启动 profiling 时生成 dummy 视频样本的唯一入口,约在函数末尾用 np.full(...) 一次性构造整段视频张量。
  2. 对齐布局约定:全仓库视频数组统一为 (T, H, W, C),证据是 vllm/multimodal/video.py 的 rescale_video_size 按 _, height, width, _ = frames.shape 解析高与宽;_get_dummy_images 采用 Image.new("RGB", (width, height)) 的 PIL 顺序(W, H)也等价于 height 在前的语义。因此修复只改数组形状元组中的 H/W 位置,不改动 overrides 对 num_frames / width / height 的 min 收缩与 warning 分支。
  3. 影响链路:dummy video 会经模型 processor 的 resize 与 patchify 流程折算为视觉 token 数,参与 memory profiling 的峰值激活估算,进而决定 KV cache 预算与 max_num_seqs 等启动期参数。宽高转置会改变 smart-resize 后的实际形状,使估算结果偏离真实请求的形状分布;修复后估算与实际推理路径的输入布局一致。
  4. 测试与配套:本 PR 只改 1 个文件、+1/-1,未新增任何单元测试或测试数据;作者在 PR body 中提供了使用真实代码路径的最小复现脚本(断言 v.shape == (4, 1080, 1920, 3),main 上失败、修复后通过),并执行了 pre-commit run ruff-check --files vllm/multimodal/processing/dummy_inputs.py。
  5. CI 与合入流程:maintainer DarkLight1337 评论 /ci run 触发 Buildkite CI #88244,随后直接 approve(回复 "Thanks"),无额外 review 评论;PR body 注明 "AI assistance was used to locate, reproduce, and fix this bug; a human has reviewed the change",commit 带 Co-authored-by: Cursor Agent 标记。
文件 模块 状态 重要度
vllm/multimodal/processing/dummy_inputs.py 多模态输入 modified 4.89

关键符号

BaseDummyInputsBuilder._get_dummy_videos

关键源码片段

vllm/multimodal/processing/dummy_inputs.py core-logic

本 PR 唯一改动文件。`BaseDummyInputsBuilder._get_dummy_videos` 生成的 dummy video 布局由 `(T, W, H, C)` 修正为 `(T, H, W, C)`,直接决定多模态模型 profiling 时的视频 token 估算与显存预算是否与真实输入一致。

# vllm/multimodal/processing/dummy_inputs.py
# BaseDummyInputsBuilder._get_dummy_videos:为 profiling 生成占位视频张量。
# 这里的形状语义必须与 vllm/multimodal/video.py 中 rescale_video_size 的
# `_, height, width, _ = frames.shape` 完全一致,否则下游会拿到转置后的宽高比。
def _get_dummy_videos(
    self,
    width: int,
    height: int,
    num_frames: int,
    num_videos: int,
    overrides: VideoDummyOptions | None = None,
) -> list[npt.NDArray]:
    if num_videos == 0:
        return []
​
    # overrides 只做「向下收缩」:显式值超过模型上限时打 warning 并忽略,
    # 因此越界场景下 width / height / num_frames 仍保持模型侧默认值。
    # 这段逻辑本次未改动,但它决定了最终进入 np.full 的 height / width。
    if overrides:
        if overrides.num_frames:
            if overrides.num_frames > num_frames:
                logger.warning(
                    "video.num_frames override (%d) exceeds model's "
                    "maximum number of frames (%d), will be ignored",
                    overrides.num_frames,
                    num_frames,
                )
            num_frames = min(num_frames, overrides.num_frames)
        if overrides.width:
            if overrides.width > width:
                logger.warning(
                    "video.width override (%d) exceeds model's "
                    "maximum width (%d), will be ignored",
                    overrides.width,
                    width,
                )
            width = min(width, overrides.width)
        if overrides.height:
            if overrides.height > height:
                logger.warning(
                    "video.height override (%d) exceeds model's "
                    "maximum height (%d), will be ignored",
                    overrides.height,
                    height,
                )
            height = min(height, overrides.height)
​
    # 本次唯一改动:np.full 的形状从 (T, W, H, C) 变成 (T, H, W, C)。
    # 旧写法在非正方形 dummy 尺寸下会把高与宽转置(例如 1920x1080 被建成
    # (4, 1920, 1080, 3)),使 profiling 与 max-token 估算拿到错误宽高比;
    # 正方形尺寸会掩盖该问题。修改后与 rescale_video_size 的解析顺序一致。
    video = np.full((num_frames, height, width, 3), 255, dtype=np.uint8)
​
    # 注意:num_videos 份 dummy 共享同一个 NDArray 对象,下游若有原地写需谨慎。
    return [video] * num_videos

评论区精华

fork PR 的自动化 review 被禁用 other

claude[bot] 在该 PR 上只留下说明:"This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review." 实际未触发一次性评审。

结论:自动化静态 / 语义评审缺席,正确性判断主要依赖作者在 PR body 提供的复现脚本以及 maintainer 的人工确认。 · 已了解,未跟进

(T, H, W, C) 布局约定的一致性确认 正确性

作者在 PR body 用两条证据论证 height 在前的约定:`vllm/multimodal/video.py` 的 `rescale_video_size` 读取 `_, height, width, _ = frames.shape`,以及 `_get_dummy_images` 使用 `Image.new("RGB", (width, height))`。maintainer DarkLight1337 未提出异议,直接 approve 并回复 "Thanks"。

结论:确认 `(T, H, W, C)` 为仓库内视频数组统一约定,修复方向与范围均被接受,无争议点。 · 已解决

CI 验证范围与测试缺口 测试

DarkLight1337 评论 "/ci run" 后由 github-actions[bot] 触发 Buildkite CI #88244;讨论中没有人要求补充针对 `_get_dummy_videos` 形状的单元测试。

结论:合入仅依赖端到端 CI 与人工判断,仓库内没有直接断言 dummy video 形状的测试用例,回归防护存在空缺。 · 未覆盖(遗留)

风险与影响

  1. profiling 结果变化面:只有 dummy video 尺寸非正方形(width ≠ height,如 16:9 配置)的多模态模型受影响,正方形配置行为完全不变;修复后 token 数与激活峰值估算可能较旧值下降,原先"偏保守"的显存预算会变得紧凑,若下游还存在其它低估因素,理论上增加 OOM 或 max_num_seqs 需回退的风险。
  2. 缺少测试覆盖:改动落在 vllm/multimodal/processing/dummy_inputs.py,但 PR 未在 tests/multimodal/ 下补任何形状断言,回归只能依赖 CI 端到端视频用例,后续重构很容易再次写反 H/W 而不被发现。
  3. 依赖隐式约定:正确性依赖 vllm/multimodal/video.py 等下游模块持续按 (T, H, W, C) 解释张量,而 dummy 张量本身不带元数据,约定一旦漂移就会静默错算,这类"形状语义靠注释与惯例维持"的结构性风险未被本 PR 处理。
  4. 既有行为未变更点:return [video] * num_videos 让多个 dummy 视频共享同一个 NDArray 对象,若下游有原地写操作会互相污染;本次未改动该逻辑,属于既有隐患,仅作提示。
  5. 无真实推理回归风险:dummy 输入只用于 profiling,不进入线上推理路径,因此不会改变实际请求的输出质量或吞吐,风险集中在启动期的显存与并发参数估算。

用户侧:使用视频输入的多模态模型(视频理解类 VLM)在非正方形 dummy video 配置下,启动 profiling 得到的显存/并发参数会更贴近真实输入形状,降低因估算偏差导致的启动即 OOM 或并发被过度压低的概率。系统侧:影响 memory profiling → KV cache 预算 → max_num_seqs 这条启动关键链路,但改动是单点、单文件、+1/-1,不会扩散到运行时调度或注意力后端。团队侧:给多模态维护者一个明确信号——dummy 输入布局必须与模型 processor 的 (T, H, W, C) 约定保持一致,后续新增视频模型(自定义 VideoDummyOptions)时应显式检查 width/height 顺序。整体影响范围窄、程度中等,属于低扩散但落在启动路径上的正确性修复。

profiling 路径变更 缺少单元测试覆盖 隐式形状约定依赖 非方形 dummy 尺寸行为变化

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论