Prhub

#48413 [Bugfix][MM] Fix MiniCPM-V placeholder replacement and image processor loading on Transformers v5

原始 PR 作者 YunzhuLu 合并时间 2026-08-06 12:12 文件变更 4 提交数 5 评论 15 代码增减 +218 / -10

执行摘要

修复 MiniCPM-V 在 Transformers v5 下的占位符替换与图像处理器复用

PR body 说明两个独立但相关的阻塞问题:其一,升级 Transformers v5 后 vllm serve 初始化即报 RuntimeError: Expected there to be 1 prompt placeholders ... but instead found 0 prompt placeholders!,根因是 MiniCPM-V 占位符为普通文本而非 special token,BPE 下单独编码与在完整 prompt 上下文中编码的 token 边界不同;其二,多个 MiniCPM-V repo 在 preprocessor_config.json 中声明相同的 MiniCPMVImageProcessor 类名但实现各异,同进程串行加载时后加载模型复用先前模型的处理器类,报 TypeError: ... got an unexpected keyword argument 'max_slice_nums'。作者同时移除 max_transformers_version="4.57" 上限,因为 #44282 已 vendor MiniCPMV processor 并将 MiniCPMVBatchFeature 别名到 BatchFeature。

值得精读。三个设计决策有借鉴价值:一是用"先 token 匹配、失败后分段文本编码"的双路径策略兼容 BPE 边界差异,并把 workaround 下沉为公共层工具而非堆在模型文件里;二是通过 processor_cls_overrides 绕过 AutoImageProcessor 按类名共享缓存的机制,解决多 checkpoint 类名冲突;三是在 review 中把"复现原始失败"作为测试验收标准,test_image_processor_for_dif_model 和 test_prompt_has_dif_BPE_boundaries_in_context 在 main 上失败、分支上通过,是高质量回归测试的范例。阅读时可重点关注 _apply_prompt_updates 覆写与公共实现之间的重复代码边界,以及上游 transformers 修复合入后的清理机会。

讨论亮点

审阅人 tc-mb 与作者 YunzhuLu 围绕缓存设计、测试质量与安全边界进行了多轮交锋:

  • 缓存忽略 per-call kwargs:tc-mb 实测 get_hf_processor(max_slice_nums=1) 后再调用 max_slice_nums=2 返回同一实例,要求按 normalized kwargs 键控或直接移除缓存。作者先改为按 _merge_mm_kwargs 结果键控,随后在下一轮 review 中彻底移除该缓存。
  • 无界缓存与既有 LRU 重复:tc-mb 指出长运行服务中 per-request kwargs 会让缓存无限增长,而 cached_get_image_processor 已有有界 LRU、cached_property 已记忆类解析,额外缓存和对象同一性测试均不必要。作者最终只保留 @cached_property 记忆类解析。
  • 测试应复现原始失败:tc-mb 要求测试覆盖 2.5 后接 4.0 的类错用和 BPE 边界差异 prompt,作者用 test_image_processor_for_dif_model 与 test_prompt_has_dif_BPE_boundaries_in_context 替换了基于对象同一性的断言,两者在 main 上失败、分支上通过。
  • 公共层复用与安全:tc-mb 指出 workaround 复制了公共处理器私有逻辑、类引用应来自 auto_map.AutoImageProcessor 而非硬编码,且 get_class_from_dynamic_module 会执行仓库代码,必须显式强制 trust_remote_code。作者将逻辑下沉至 processor.py::apply_text_matches_as_segmented_tokens,类引用改为从配置读取并强制信任校验,还顺带发现 transformers 上游 get_image_processor_config 丢失 auto_map 的 bug(已提 upstream PR#47628)。

实现拆解

1. 占位符替换修复:覆写 _apply_prompt_updates

在 vllm/model_executor/models/minicpmv.py 的 MiniCPMVMultiModalProcessor 中覆写 _apply_prompt_updates(+111/-3,本 PR 源码主改动)。流程为先按 token 匹配(_apply_token_matches),若任一占位符未匹配到,则回退到 _apply_text_matches_as_segmented_tokens:把 token 序列解码为文本、按段做字符串匹配,再逐段编码拼回 token ID,避免整段重编码改变占位符的 BPE 边界。该覆写复用 _find_mm_placeholders 与 _seq2text 等公共工具,最终返回新 token 序列与 PlaceholderFeaturesInfo 列表。

2. 公共层新增分段编码工具

在 vllm/multimodal/processing/processor.py 中新增模块级函数 apply_text_matches_as_segmented_tokens 与 BaseMultiModalProcessor._apply_text_matches_as_segmented_tokens(+33/-0)。核心思想:先 _apply_matches 得到切分后的文本段,再对每一段独立 _seq2tokens 编码后 flatten_2d_lists 拼接,避免整段重编码时 BPE 跨段合并改变占位符 token。此举把 MiniCPM-V 的 workaround 下沉为公共能力,其他文本占位符模型可复用。

3. 图像处理器按 repo 解析

在 MiniCPMVProcessingInfo 中新增 @cached_property _image_processor_cls:通过 ImageProcessingMixin.get_image_processor_dict 读取仓库配置中的 auto_map['AutoImageProcessor'],先 resolve_trust_remote_code 显式校验信任开关,再 get_class_from_dynamic_module 加载类。get_hf_processor 改为:tokenizer 仍走 ctx.get_hf_processor,image processor 则通过 cached_get_image_processor(..., processor_cls_overrides=processor_cls) 按 repo 特定类实例化,并用 _merge_mm_kwargs 归一化 kwargs——既避免 AutoImageProcessor 跨版本类名共享错配,也保证每次调用的 max_slice_nums 等参数生效。

4. 测试与注册表配套

新增 tests/models/multimodal/processing/test_minicpmv.py(+74 行)三个回归测试:test_image_processor_for_dif_model 复现 2.5 后接 4.0 的类错用;test_prompt_has_dif_BPE_boundaries_in_context 复现 BPE 边界差异;test_get_hf_processor_for_same_model_different_kwargs 验证不同 kwargs 不会复用陈旧实例。tests/models/registry.py 删除 MiniCPM-V 的 max_transformers_version="4.57" 与对应 reason(-7 行),使处理测试在 Transformers v5 下运行;2.6 因 gated repo 被跳过。

文件 模块 状态 重要度
vllm/model_executor/models/minicpmv.py 模型层 modified 7.85
vllm/multimodal/processing/processor.py 多模态处理 modified 6.93
tests/models/multimodal/processing/test_minicpmv.py 回归测试 added 6.97
tests/models/registry.py 测试配置 modified 3.97

关键符号

_image_processor_cls get_hf_processor _apply_prompt_updates apply_text_matches_as_segmented_tokens _apply_text_matches_as_segmented_tokens test_get_hf_processor_for_same_model_different_kwargs test_image_processor_for_dif_model test_prompt_has_dif_BPE_boundaries_in_context

分析完成后,这里会展示 LLM 生成的相对完整源码片段和详细注释。

评论区精华

新增 _minicpmv_hf_processor_cache 忽略 per-call mm_processor_kwargs 正确性

tc-mb 实测 MiniCPM-V-4 先以 max_slice_nums=1 调用再以 max_slice_nums=2 调用 get_hf_processor,返回同一实例,第二次仍为 1,质疑缓存未按参数归一。

结论:作者先改为按 _merge_mm_kwargs 归一化后的 kwargs 键控,后续 review 中彻底移除该缓存,仅以 cached_property 记忆类解析。 · 已解决

无界缓存与既有 LRU 缓存重复 设计

tc-mb 指出 per-request kwargs 会让缓存无限增长,动态类解析已被 cached_property 记忆,cached_get_image_processor 也已有有界 LRU,额外缓存和对象同一性测试均不必要。

结论:删除 _minicpmv_hf_processor_cache,保留 cached_get_image_processor 的 LRU 与 cached_property 类解析记忆,避免长运行服务缓存膨胀。 · 已解决

测试应复现原始失败场景 测试

tc-mb 要求测试覆盖 2.5 后接 V-4 的处理器类错用,以及 BPE 边界导致单独 / 上下文 tokenization 不同的 prompt,而非仅断言对象同一性。

结论:新增 test_image_processor_for_dif_model 与 test_prompt_has_dif_BPE_boundaries_in_context,作者确认两者在 main 上失败、分支上通过。 · 已解决

占位符 workaround 应下沉到公共处理器层 设计

tc-mb 指出 _apply_prompt_updates_by_text_locate 复制了公共处理器的私有逻辑并导入多个私有 helper,增加维护风险,建议在公共层提供可复用 helper。

结论:移除模型内私有实现,逻辑提炼为 processor.py 的 apply_text_matches_as_segmented_tokens,MiniCPM-V 只做覆写调用。 · 已解决

image processor 类来源与 trust_remote_code 强制 安全

tc-mb 要求类引用来自 auto_map.AutoImageProcessor 而非硬编码,并强调 get_class_from_dynamic_module 会直接执行仓库代码,路径上必须显式强制 trust_remote_code。

结论:类引用改为从 ImageProcessingMixin.get_image_processor_dict 读取 auto_map,resolve_trust_remote_code 显式校验;过程中发现上游 get_image_processor_config 丢 auto_map 的 bug,已提 transformers#47628。 · 已解决

风险与影响

  • 公共层新增函数影响面:apply_text_matches_as_segmented_tokens 是对 processor.py 的纯增量(+33),目前仅 MiniCPM-V 使用;但 MiniCPMVMultiModalProcessor._apply_prompt_updates 仍复制了公共实现中 matched_updates 组装与 _find_mm_placeholders 的代码,未来公共 _apply_prompt_updates 演进时需同步维护。
  • BPE 回退路径行为变化:失败回退从"整段文本匹配后整体重编码"变为"分段编码",对于同样有文本占位符的其他模型(若未来复用该工具),token 序列可能与旧行为不同,需回归验证。
  • 依赖 transformers 内部/半私有 API:ImageProcessingMixin.get_image_processor_dict、resolve_trust_remote_code 与 vLLM 侧 _merge_mm_kwargs、cached_get_image_processor 的 kwargs 归一化行为直接耦合;上游已确认 get_image_processor_config 存在丢 auto_map 的 bug(README 中 transformers#47628 修复中),若上游接口再变动可能回归。
  • 安全与信任边界:get_class_from_dynamic_module 会执行仓库代码,虽然已用 resolve_trust_remote_code 强制校验,但 MiniCPM-V 本身要求 trust_remote_code=True;企业环境若误配信任开关,动态加载即执行第三方代码。
  • 测试覆盖缺口:MiniCPM-V-2_6 是 gated repo 被跳过;BPE 边界测试依赖特定 tokenizer 行为,若未来 tokenizer 或 chat template 变更可能失去复现力。
  • 用户侧:MiniCPM-V 2.5 / 2.6 / 4.0 / 4.5 / 4.6 在 Transformers v5 下可正常 vllm serve 并处理图文/视频请求;同进程多 checkpoint(串行 pytest、多模型服务)不再错用图像处理器。
  • 系统侧:processor.py 公共层新增分段编码工具,为后续其他非 special-token 占位符模型的 prompt 替换提供通用解法;tests/models/registry.py 解除版本上限后,MiniCPM-V 处理测试会随 CI 在 v5 下持续运行,提前暴露兼容性回归。
  • 团队侧:需要持续跟踪上游 transformers#47628 的合入状态,并维护 vendored MiniCPMVProcessor 与上游 autoclass 解析行为的对齐。
远程代码动态加载 依赖 transformers 内部 API BPE 回退路径改变 tokenization gated 模型测试覆盖缺口 公共处理器层新增分支

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论