Prhub

#44282 [Bugfix] Vendor MiniCPMV/MiniCPMO processors to unblock Transformers v5

原始 PR 作者 wjinxu 合并时间 2026-06-02 22:14 文件变更 7 提交数 6 评论 6 代码增减 +965 / -16

执行摘要

Vendor MiniCPMV/MiniCPMO 处理器以解锁 Transformers v5 升级

Transformers v5 升级后,MiniCPMV 和 MiniCPMO 的原始处理器因使用了已删除的 API(如 tokenizer.im_start_id)而失败,触发 AttributeError。此 PR 通过将处理器代码内部化,解除了升级阻塞,解决了 Issue #38385 和 #30566 中的相关 skip 标记。

建议开发者关注 vendor 处理器与上游的差异,确保后续 Transformers 升级时及时同步更新。此 PR 采用的 vendor 策略(直接复制关键依赖)适用于其他类似场景,但需评估长期维护成本。同时,建议增加更多端到端测试以覆盖新处理器的各种输入组合。

讨论亮点

在 Review 中,@DarkLight1337 要求彻底删除 tests/models/multimodal/generation/test_common.py 中注释掉的 marks=[pytest.mark.skip(...)] 行,而非仅注释。作者 @wjinxu 已按请求完整删除,并最终获得批准。其余讨论涉及测试通过和 CI 问题,作者指出一个无关的 CI 失败(tracing 测试 segfault),@DarkLight1337 强制合并。

实现拆解

实现分为以下步骤:

  1. 新增 vendor 处理器文件:在 vllm/transformers_utils/processors/ 下新建 minicpmv.py(+314 行)和 minicpmo.py(+603 行),分别实现 MiniCPMVProcessorMiniCPMOProcessor 类。它们继承自 transformers.ProcessorMixin,并提供 __call__batch_decodedecode 等方法,与上游 API 保持一致。特别注意对音频、图像等多模态输入的处理逻辑。
  2. 修改模型代码以使用 vendor 处理器:在 vllm/model_executor/models/minicpmv.pyminicpmo.pyget_hf_processor 方法中,导入并使用 vendor 处理器替换原有的 HF 处理器。同时保留对 numpy 数组序列化的兼容处理(将 mean/std 等属性从 numpy 数组转换为列表)。
  3. 注册 vendor 处理器:在 vllm/transformers_utils/processors/__init__.py 中将新处理器类添加到 _import_structure 字典中,确保可通过名称查找。
  4. 移除测试跳过标记:删除 tests/lora/test_minicpmv_tp.py 中因 Transformers v5 而设置的 pytest.mark.skipif 条件,以及 tests/models/multimodal/generation/test_common.py 中对 minicpmv_25minicpmo_26 的跳过标记(从注释改为完整删除)。现在相关测试可在 Transformers v5 上正常执行。
  5. 处理 Review 反馈:修复了 MiniCPMVProcessor 中当 imagesNone 时可能出现的 UnboundLocalError,调整了 docstring 以满足 mkdocs strict 模式,并修复了类型检查问题。
文件 模块 状态 重要度
vllm/transformers_utils/processors/minicpmo.py 处理器 added 9.08
vllm/transformers_utils/processors/minicpmv.py 处理器 added 9.08
vllm/model_executor/models/minicpmo.py 模型层 modified 7.24
vllm/model_executor/models/minicpmv.py 模型层 modified 6.05
vllm/transformers_utils/processors/__init__.py 配置 modified 5.07
tests/lora/test_minicpmv_tp.py 测试 modified 4.47
tests/models/multimodal/generation/test_common.py 测试 modified 4.2

关键符号

MiniCPMVProcessor.__init__ MiniCPMVProcessor.__call__ MiniCPMVProcessor.batch_decode MiniCPMVProcessor.decode MiniCPMOProcessor.__init__ MiniCPMOProcessor.__call__ MiniCPMOProcessor.get_audio_placeholder MiniCPMOProcessor.audio_feature_extract MiniCPMOProcessingInfo.get_hf_processor MiniCPMVProcessingInfo.get_hf_processor

关键源码片段

vllm/transformers_utils/processors/minicpmo.py dependency-wiring

MiniCPMO 处理器核心实现,包含音频特征提取和多模态输入处理

class MiniCPMOProcessor(ProcessorMixin):
    """MiniCPMO 处理器,包装图像处理器、特征提取器和分词器。"""
    def __init__(self, image_processor=None, feature_extractor=None, tokenizer=None, pool_step=2):
        # 调用父类初始化,设置 component
        super().__init__(image_processor, feature_extractor, tokenizer)
        self.version = image_processor.version
        self.pool_step = pool_step # 音频 pooling 步长
​
    def __call__(self, text, images=None, audios=None, audio_parts=None,
                 max_length=None, do_pad=True, max_slice_nums=None,
                 use_image_id=True, chunk_input=False, return_tensors=TensorType.PYTORCH,
                 sampling_rate=16000, **kwargs):
        # 处理图像:若 images 不为 None,调用 image_processor 生成图像输入
        if images is not None:
            image_inputs = self.image_processor(images, do_pad=do_pad,
                                                max_slice_nums=max_slice_nums,
                                                return_tensors=return_tensors)
        else:
            image_inputs = None
​
        # 处理音频:调用音频特征提取方法
        if audios is not None:
            audio_features, audio_feature_lens, audio_phs = self.audio_feature_extract(
                audios, audio_parts, chunk_input, sampling_rate)
        else:
            audio_features, audio_feature_lens, audio_phs = [], [], []
​
        # 合并图像、音频占位符和文本到模型输入
        model_inputs = self._convert_omni_to_inputs(
            image_inputs, audio_phs, text,
            max_slice_nums=max_slice_nums, use_image_id=use_image_id,
            max_length=max_length, **kwargs)
​
        model_inputs["audio_features"] = audio_features
        model_inputs["audio_feature_lens"] = audio_feature_lens
​
        return MiniCPMOBatchFeature(data={**model_inputs})

评论区精华

删除测试中的跳过标记 测试

DarkLight1337 要求完全删除 test_common.py 中注释的 skip 行,而不是仅注释。

结论:已删除并批准合并。 · 已解决

风险与影响

  • vendor 代码同步成本:将上游代码复制到内部后,需要与 Hugging Face 的更新保持同步,否则可能错过重要修复或功能改进。
  • 潜在的兼容性问题:新处理器虽然复制自上游,但可能未覆盖所有边界情况,特别在音频处理和图像分片方面。
  • 测试覆盖:虽然移除了跳过标记,但当前测试用例可能不足以覆盖所有功能路径。建议后续增加更多针对性测试。
  • 用户影响:使用 MiniCPMV 和 MiniCPMO 模型的用户可以正常升级到 Transformers v5,不再受阻塞。
  • 系统影响:vLLM 不再依赖 Hugging Face Transformers 中这两个处理器的特定版本,提高了向后兼容性。但需要额外维护约 917 行 vendor 代码。
  • 团队影响:解除了 Transformers v5 升级的一个关键障碍,推动项目整体升级。
vendor 代码同步 潜在兼容性问题 测试覆盖不足

关联 Issue

#30566 Update to transformers v5
#38385 [Transformers v5] MiniCPMV cannot apply processor

完整报告

参与讨论