Prhub

#39869 [Model] Use mm_features for Keye-VL and Keye-1.5-VL M-RoPE

vllm-project/vllm · 作者 lalit10 · 合并时间 2026-04-16 17:16

分析状态 已生成
文件变更 4提交数 1 · 评论 3
代码增减 +425 / -158
model v1 refactor multi-modality

执行摘要

为 Keye-VL 和 Keye-1.5-VL 模型重构 M-RoPE 位置计算,切换到 mm_features 驱动。

根据 PR body,目的是实现 Keye-VL 和 Keye-1.5-VL 的 M-RoPE 计算重构,以便从 mm_feature.mm_position 和网格元数据派生多模态跨度,而不是从 token ID 重建图像和视频区域。这有助于统一接口,简化逻辑,并为后续多模态特性开发铺平道路。

此 PR 值得精读,特别是 iter_mm_grid_thw 的设计展示了如何从传统 token 处理过渡到基于元数据的多模态接口。关注视频拆分逻辑和测试用例的构造,以理解 M-RoPE 计算的关键细节。

讨论亮点
  • HACK 注释问题:gemini-code-assist[bot] 指出测试文件中的 # HACK. 注释可能隐藏维护问题,但此问题未在 PR 中解决。
  • 死代码移除:同一评论者建议移除 keye.py 中处理非 Tensor 图像网格的 else 块,认为它是死代码,但 PR 中未采纳。
  • Keye-1.5-VL 扩展:DarkLight1337 最初要求将此重构扩展到 Keye-1.5-VL,作者随后添加并验证,使用 lm-eval 显示性能无显著变化,最终批准 PR。

实现拆解

  1. 入口方法重构:在 keye.pykeye_vl1_5.py 中,get_mrope_input_positions 方法不再调用 MultiModalFeatureSpec.gather_kwargs,而是通过新增的 iter_mm_grid_thw 遍历排序后的 mm_features,获取每个多模态块的偏移和网格尺寸。
  2. 核心迭代器实现:新增 iter_mm_grid_thw 方法,接受 mm_features 列表,根据 mm_position.offset 排序,为图像和视频模态生成 (offset, t, h, w) 元组。图像直接提取网格尺寸;视频使用 _split_video_grid_thw 拆分为帧级行。
  3. 视频网格拆分:在 keye.py 中新增静态方法 _split_video_grid_thw,将视频网格沿时间维度拆分为 [1, h, w] 的行列表,以保持 Keye 当前的帧级 M-RoPE 行为。
  4. 测试配套:新增 tests/model_executor/test_keye_mrope.pytest_keye_vl1_5_mrope.py,使用虚拟配置和特征模拟文本、单图像、图像视频交错等场景,通过断言验证位置计算和 delta 值的正确性。
  5. 导入和结构调整:在两个模型文件中添加 Iterator 导入,并调整控制流以处理新的数据契约,确保代码可读性和维护性。
文件 模块 状态 重要度
vllm/model_executor/models/keye.py 模型层 modified 8.19
vllm/model_executor/models/keye_vl1_5.py 模型层 modified 7.91
tests/model_executor/test_keye_mrope.py 测试模块 added 7.52
tests/model_executor/test_keye_vl1_5_mrope.py 测试模块 added 7.52
vllm/model_executor/models/keye.py core-logic

Keye-VL 模型的核心文件,重构了 M-RoPE 位置计算方法,新增视频拆分和多模态迭代器。

def iter_mm_grid_thw(
    self, mm_features: list[MultiModalFeatureSpec]
) -> Iterator[tuple[int, int, int, int]]:
    spatial_merge_size = self.config.vision_config.spatial_merge_size # 获取空间合并尺寸以调整网格
​
    for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset): # 按偏移排序确保顺序
        if mm_feature.data is None:
            raise ValueError("M-RoPE calculation requires multimodal feature data") # 数据缺失则报错
​
        if mm_feature.modality == "image":
            grid_thw = mm_feature.data["image_grid_thw"].data # 提取图像网格数据
            if isinstance(grid_thw, torch.Tensor):
                if grid_thw.ndim == 2:
                    assert grid_thw.shape[0] == 1 # 确保为单图像
                    t, h, w = grid_thw[0].tolist() # 转换为列表格式
                else:
                    t, h, w = grid_thw.tolist()
            else:
                # 处理非 Tensor 情况,当前为死代码,可能为历史遗留
                if isinstance(grid_thw[0], list):
                    assert len(grid_thw) == 1
                    t, h, w = grid_thw[0]
                else:
                    t, h, w = grid_thw
​
            yield (
                mm_feature.mm_position.offset, # 多模态块在输入序列中的起始偏移
                t,
                h // spatial_merge_size, # 调整高度以适应 LLM 网格
                w // spatial_merge_size, # 调整宽度以适应 LLM 网格
            )
        elif mm_feature.modality == "video":
            current_offset = mm_feature.mm_position.offset
            for t, h, w in self._split_video_grid_thw( # 拆分视频网格为帧级
                mm_feature.data["video_grid_thw"].data
            ):
                llm_grid_h = h // spatial_merge_size
                llm_grid_w = w // spatial_merge_size
                yield (current_offset, t, llm_grid_h, llm_grid_w) # 为每帧生成元组
                current_offset += t * llm_grid_h * llm_grid_w # 更新偏移以处理连续帧
        else:
            raise ValueError(f"Unsupported modality: {mm_feature.modality}") # 不支持其他模态

关键符号

iter_mm_grid_thw _split_video_grid_thw get_mrope_input_positions

评论区精华

测试文件中的 HACK 注释 style

gemini-code-assist[bot] 指出 `tests/model_executor/test_keye_mrope.py` 中的 `# HACK.` 注释可能隐藏维护问题,建议更彻底地处理或移除。

结论:未在 PR 中解决,遗留为潜在技术债。 · unresolved

keye.py 中的死代码 正确性

同一评论者认为 `keye.py` 中处理非 Tensor 图像网格的 `else` 块是死代码,因为 `make_mm_feature` 测试函数确保数据为 Tensor,建议移除以提高代码清晰度。

结论:PR 中未采纳,代码保留但可能不影响功能。 · unresolved

扩展 Keye-1.5-VL 支持 设计

DarkLight1337 最初要求将此重构扩展到 Keye-1.5-VL,作者随后在 PR 中添加并验证,使用 lm-eval 显示性能无显著变化。

结论:成功扩展,验证通过后 PR 获批准。 · 已解决

风险与影响

  • 回归风险:M-RoPE 计算逻辑变更可能导致位置编码错误,影响模型输出质量,尤其是在视频处理中,需要确保 iter_mm_grid_thw 生成的偏移和网格尺寸准确。
  • 兼容性风险:新代码强依赖 mm_features 的特定结构(如 mm_position.offsetdata["image_grid_thw"]),如果上游接口变化,可能导致运行时错误。
  • 性能风险:新增的迭代和拆分操作可能引入轻微计算开销,但鉴于是重构且测试覆盖 CPU,实际推理影响应可控。
  • 代码质量风险:review 中提到的 HACK 注释和死代码未处理,可能增加长期维护难度和潜在 bug。
  • 用户影响:使用 Keye-VL 或 Keye-1.5-VL 模型的开发者无需主动更改代码,但内部 M-RoPE 计算方式更新,应通过测试确保行为一致。
  • 系统影响:代码更模块化,减少了基于 token 搜索的复杂性,使多模态特征处理更统一,有助于未来扩展。
  • 团队影响:为基于 mm_features 的多模态模型开发提供了参考模式,促进代码标准化和团队协作效率。
核心路径变更 数据契约依赖 未处理代码问题

关联 Issue

未识别关联 Issue

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

完整报告

执行摘要

  • 一句话:为 Keye-VL 和 Keye-1.5-VL 模型重构 M-RoPE 位置计算,切换到 mm_features 驱动。
  • 推荐动作:此 PR 值得精读,特别是 iter_mm_grid_thw 的设计展示了如何从传统 token 处理过渡到基于元数据的多模态接口。关注视频拆分逻辑和测试用例的构造,以理解 M-RoPE 计算的关键细节。

功能与动机

根据 PR body,目的是实现 Keye-VL 和 Keye-1.5-VL 的 M-RoPE 计算重构,以便从 mm_feature.mm_position 和网格元数据派生多模态跨度,而不是从 token ID 重建图像和视频区域。这有助于统一接口,简化逻辑,并为后续多模态特性开发铺平道路。

实现拆解

  1. 入口方法重构:在 keye.pykeye_vl1_5.py 中,get_mrope_input_positions 方法不再调用 MultiModalFeatureSpec.gather_kwargs,而是通过新增的 iter_mm_grid_thw 遍历排序后的 mm_features,获取每个多模态块的偏移和网格尺寸。
  2. 核心迭代器实现:新增 iter_mm_grid_thw 方法,接受 mm_features 列表,根据 mm_position.offset 排序,为图像和视频模态生成 (offset, t, h, w) 元组。图像直接提取网格尺寸;视频使用 _split_video_grid_thw 拆分为帧级行。
  3. 视频网格拆分:在 keye.py 中新增静态方法 _split_video_grid_thw,将视频网格沿时间维度拆分为 [1, h, w] 的行列表,以保持 Keye 当前的帧级 M-RoPE 行为。
  4. 测试配套:新增 tests/model_executor/test_keye_mrope.pytest_keye_vl1_5_mrope.py,使用虚拟配置和特征模拟文本、单图像、图像视频交错等场景,通过断言验证位置计算和 delta 值的正确性。
  5. 导入和结构调整:在两个模型文件中添加 Iterator 导入,并调整控制流以处理新的数据契约,确保代码可读性和维护性。

关键文件:

  • vllm/model_executor/models/keye.py(模块 模型层;类别 source;类型 core-logic;符号 _split_video_grid_thw, iter_mm_grid_thw, get_mrope_input_positions): Keye-VL 模型的核心文件,重构了 M-RoPE 位置计算方法,新增视频拆分和多模态迭代器。
  • vllm/model_executor/models/keye_vl1_5.py(模块 模型层;类别 source;类型 core-logic;符号 iter_mm_grid_thw, get_mrope_input_positions): Keye-1.5-VL 模型的核心文件,类似重构 M-RoPE 计算,特别处理了嵌入范围以保持视频行为。
  • tests/model_executor/test_keye_mrope.py(模块 测试模块;类别 test;类型 test-coverage;符号 _force_cpu_default_device, DummyVisionConfig, DummyConfig, make_model): 新增 Keye-VL 的 CPU 单元测试,验证重构后 M-RoPE 位置计算的正确性。
  • tests/model_executor/test_keye_vl1_5_mrope.py(模块 测试模块;类别 test;类型 test-coverage;符号 _force_cpu_default_device, DummyVisionConfig, DummyConfig, make_model): 新增 Keye-1.5-VL 的 CPU 单元测试,特别验证视频嵌入范围的使用。

关键符号:iter_mm_grid_thw, _split_video_grid_thw, get_mrope_input_positions

关键源码片段

vllm/model_executor/models/keye.py

Keye-VL 模型的核心文件,重构了 M-RoPE 位置计算方法,新增视频拆分和多模态迭代器。

def iter_mm_grid_thw(
    self, mm_features: list[MultiModalFeatureSpec]
) -> Iterator[tuple[int, int, int, int]]:
    spatial_merge_size = self.config.vision_config.spatial_merge_size # 获取空间合并尺寸以调整网格
​
    for mm_feature in sorted(mm_features, key=lambda f: f.mm_position.offset): # 按偏移排序确保顺序
        if mm_feature.data is None:
            raise ValueError("M-RoPE calculation requires multimodal feature data") # 数据缺失则报错
​
        if mm_feature.modality == "image":
            grid_thw = mm_feature.data["image_grid_thw"].data # 提取图像网格数据
            if isinstance(grid_thw, torch.Tensor):
                if grid_thw.ndim == 2:
                    assert grid_thw.shape[0] == 1 # 确保为单图像
                    t, h, w = grid_thw[0].tolist() # 转换为列表格式
                else:
                    t, h, w = grid_thw.tolist()
            else:
                # 处理非 Tensor 情况,当前为死代码,可能为历史遗留
                if isinstance(grid_thw[0], list):
                    assert len(grid_thw) == 1
                    t, h, w = grid_thw[0]
                else:
                    t, h, w = grid_thw
​
            yield (
                mm_feature.mm_position.offset, # 多模态块在输入序列中的起始偏移
                t,
                h // spatial_merge_size, # 调整高度以适应 LLM 网格
                w // spatial_merge_size, # 调整宽度以适应 LLM 网格
            )
        elif mm_feature.modality == "video":
            current_offset = mm_feature.mm_position.offset
            for t, h, w in self._split_video_grid_thw( # 拆分视频网格为帧级
                mm_feature.data["video_grid_thw"].data
            ):
                llm_grid_h = h // spatial_merge_size
                llm_grid_w = w // spatial_merge_size
                yield (current_offset, t, llm_grid_h, llm_grid_w) # 为每帧生成元组
                current_offset += t * llm_grid_h * llm_grid_w # 更新偏移以处理连续帧
        else:
            raise ValueError(f"Unsupported modality: {mm_feature.modality}") # 不支持其他模态

评论区精华

  • HACK 注释问题:gemini-code-assist[bot] 指出测试文件中的 # HACK. 注释可能隐藏维护问题,但此问题未在 PR 中解决。
  • 死代码移除:同一评论者建议移除 keye.py 中处理非 Tensor 图像网格的 else 块,认为它是死代码,但 PR 中未采纳。
  • Keye-1.5-VL 扩展:DarkLight1337 最初要求将此重构扩展到 Keye-1.5-VL,作者随后添加并验证,使用 lm-eval 显示性能无显著变化,最终批准 PR。

    • 测试文件中的 HACK 注释 (style): 未在 PR 中解决,遗留为潜在技术债。
    • keye.py 中的死代码 (correctness): PR 中未采纳,代码保留但可能不影响功能。
    • 扩展 Keye-1.5-VL 支持 (design): 成功扩展,验证通过后 PR 获批准。

风险与影响

  • 风险:- 回归风险:M-RoPE 计算逻辑变更可能导致位置编码错误,影响模型输出质量,尤其是在视频处理中,需要确保 iter_mm_grid_thw 生成的偏移和网格尺寸准确。
  • 兼容性风险:新代码强依赖 mm_features 的特定结构(如 mm_position.offsetdata["image_grid_thw"]),如果上游接口变化,可能导致运行时错误。
  • 性能风险:新增的迭代和拆分操作可能引入轻微计算开销,但鉴于是重构且测试覆盖 CPU,实际推理影响应可控。
  • 代码质量风险:review 中提到的 HACK 注释和死代码未处理,可能增加长期维护难度和潜在 bug。
  • 影响:- 用户影响:使用 Keye-VL 或 Keye-1.5-VL 模型的开发者无需主动更改代码,但内部 M-RoPE 计算方式更新,应通过测试确保行为一致。
  • 系统影响:代码更模块化,减少了基于 token 搜索的复杂性,使多模态特征处理更统一,有助于未来扩展。
  • 团队影响:为基于 mm_features 的多模态模型开发提供了参考模式,促进代码标准化和团队协作效率。
  • 风险标记:核心路径变更, 数据契约依赖, 未处理代码问题

关联脉络

  • 暂无明显关联 PR

参与讨论