Prhub

#48594 [Model] Enable LoRA support for tower and connector in LlavaNextVideo

原始 PR 作者 gangula-karthik 合并时间 2026-07-14 20:09 文件变更 3 提交数 1 评论 2 代码增减 +149 / -3

执行摘要

LlavaNextVideo 新增 LoRA 支持(含视觉塔与连接器)

实现 Issue #31479 中关于更多多模态模型支持视觉塔和连接器 LoRA 的需求。LlavaNextVideo 之前完全没有 LoRA 支持,即使语言模型部分也不支持。需要实现 get_num_mm_encoder_tokens 和 get_num_mm_connector_tokens 来桥接 vision tower 与 connector 之间的 token 计数差异(空间池化导致)。

该 PR 值得精读,尤其是 get_num_mm_encoder_tokens 和 get_num_mm_connector_tokens 的实现方式——通过存储网格长度来反推各阶段 token 数量,为其他具有空间池化的多模态模型(如 LLaVA-NeXT、LLaVA-Onevision)的 LoRA 支持提供了可复用的设计模式。

讨论亮点

本 PR 无实质性 review 审查讨论;仅收到 claude bot 自动评论(fork 项目无法自动审查)和合并者 noooop 的批准("thanks!")。

实现拆解

  1. 添加 LoRA 接口混入和模块映射:在 LlavaNextVideoForConditionalGeneration 类继承中加入 SupportsLoRA,并定义 packed_modules_mapping(qkv_proj、gate_up_proj),使得 LoRA 框架能够识别模型并正确绑定目标模块。
  2. 初始化时计算网格长度:在 __init__ 中通过 get_vision_encoder_info 获取视觉编码器的 patch_grid_length,并根据 spatial_pool_stride 向上取整得到 pooled_grid_length,作为实例属性存储。这是后续 token 计数反转的基础。
  3. 实现 get_mm_mapping:返回 MultiModelKeys 对象,分别指定 language_model、connector(multi_modal_projector)和 tower_model(vision_tower)的模块前缀,供 LoRA 框架定位子模块。
  4. 实现 token 计数 helper:get_num_mm_encoder_tokens 将 LLM 侧的占位 token 数(pooled_grid_length² per frame)反算为 vision tower 实际输出的 token 数(patch_grid_length² per frame);get_num_mm_connector_tokens 进行逆操作。注意两者均以帧为单位,并处理非正整数边界。
  5. 新增单元测试:tests/models/multimodal/processing/test_llava_next_video.py 使用 _StubModel 模拟属性,测试真实配置(LLaVA-NeXT-Video-7B-hf)下的双向转换正确性,以及非 2 的幂池化比率和零 token 边界情况。
  6. 更新文档:docs/models/supported_models.md 中该模型的 LoRA 支持列标记为 ✅。
文件 模块 状态 重要度
vllm/model_executor/models/llava_next_video.py 模型定义 modified 8.57
tests/models/multimodal/processing/test_llava_next_video.py 测试 added 7.94
docs/models/supported_models.md 文档 modified 1.89

关键符号

__init__ get_mm_mapping get_num_mm_encoder_tokens get_num_mm_connector_tokens

关键源码片段

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

核心实现文件,添加 SupportsLoRA、packed_modules_mapping、get_mm_mapping、get_num_mm_encoder_tokens、get_num_mm_connector_tokens。

def __init__(self, *, vllm_config: VllmConfig, prefix: str = "") -> None:
    super().__init__()
    config = vllm_config.model_config.hf_config
    # ...
    vision_encoder_info = get_vision_encoder_info(config)
    self.patch_grid_length = vision_encoder_info.get_patch_grid_length()
    # 池化后的网格长度由步长决定,向上取整
    self.pooled_grid_length = math.ceil(
        self.patch_grid_length / config.spatial_pool_stride
    )
    # ... 初始化 vision_tower, vision_resampler, multi_modal_projector 等
​
def get_mm_mapping(self) -> MultiModelKeys:
    """返回多模态模型的模块前缀,供 LoRA 框架定位子模块"""
    return MultiModelKeys.from_string_field(
        language_model="language_model",
        connector="multi_modal_projector",
        tower_model="vision_tower",
    )
​
def get_num_mm_encoder_tokens(self, num_video_tokens: int) -> int:
    """从 LLM 侧的占位 token 数 (pooled_grid_length^2 per frame)
    反算 vision tower 实际输出的 token 数 (patch_grid_length^2 per frame)。
    """
    pooled_tokens_per_frame = self.pooled_grid_length ** 2
    if num_video_tokens <= 0 or pooled_tokens_per_frame <= 0:
        return 0
    num_frames = num_video_tokens // pooled_tokens_per_frame
    return num_frames * (self.patch_grid_length ** 2)
​
def get_num_mm_connector_tokens(self, num_vision_tokens: int) -> int:
    """从 vision tower 输出 token 数反算 connector 实际输入的 token 数
    (即 pooled_grid_length^2 per frame,因为 projector 保持长度不变)。
    """
    patch_tokens_per_frame = self.patch_grid_length ** 2
    if num_vision_tokens <= 0 or patch_tokens_per_frame <= 0:
        return 0
    num_frames = num_vision_tokens // patch_tokens_per_frame
    return num_frames * (self.pooled_grid_length ** 2)

评论区精华

设计权衡:累加器 token 计数与池化反推 设计

PR body 解释了为何需要独立的 `get_num_mm_encoder_tokens` 和 `get_num_mm_connector_tokens`:由于 `LlavaNextVideoPooler` 对每帧进行空间池化(`patch_grid_length^2` -> `pooled_grid_length^2`),LLM 侧的 token 计数不能直接用于 vision tower 或 connector 的 LoRA 层。作者选择在初始化时从配置计算并存储两个网格长度,而非实时推断,以简化逻辑。

结论:采用存储网格长度的方式实现 token 计数反转,并通过测试覆盖真实配置和非标准配置,确认设计正确。 · 已解决

风险与影响

token 计数逻辑正确性:get_num_mm_encoder_tokens 和 get_num_mm_connector_tokens 依赖于 patch_grid_length 和 pooled_grid_length 的准确推导。如果 get_vision_encoder_info 返回错误值,或 spatial_pool_stride 配置异常,可能导致 LoRA 层输入尺寸不匹配。测试覆盖了真实配置和多种网格组合,降低了风险。无回归风险:变更仅在该模型类内添加新方法和属性,不影响其他模型或模块。性能影响:仅增加两次整除运算,无显著开销。兼容性:新增方法遵循已有接口约定(SupportsLoRA、MultiModelKeys),对旧版本无影响。

用户影响:现在可以对 LLaVA-NeXT-Video 系列模型(如 llava-hf/LLaVA-NeXT-Video-7B-hf)使用 LoRA 微调,包括视觉塔和连接器,提升多模态任务的适配灵活性。系统影响:仅影响单个模型类,无跨模块副作用。团队影响:遵循了已有的模式(如 LLaVA #31513),降低了后续维护和扩展相似模型 LoRA 支持的难度。

新 token 计数逻辑 LoRA 接口首次用于该模型 依赖 get_vision_encoder_info

关联 Issue

#31479 [Feature]: Enable LoRA support for tower and connector in more MM models

完整报告

参与讨论