Prhub

#40136 [Model] support Qianfan-OCR model

原始 PR 作者 marvinzh 合并时间 2026-05-05 18:51 文件变更 9 提交数 13 评论 23 代码增减 +312 / -0

执行摘要

新增 QianfanOCR 多模态模型支持

根据 PR 描述,目的是为 QianfanOCR 模型添加支持。在 Review 讨论中,作者表示希望将配置直接包含在 vLLM 中,使用户无需指定 trust_remote_code 即可加载模型,与其他开源模型的使用体验保持一致。

此 PR 适合作为多模态新模型集成的参考范例,特别是如何基于现有架构(InternVL)快速实现。Review 中关于 trust_remote_code 的设计权衡也值得学习。建议开发者关注其配置管理和测试组织方式。

讨论亮点
  1. trust_remote_code 权衡:DarkLight1337 建议使用 trust_remote_code 以减少维护负担,作者表示愿意承担维护责任,最终保留内置配置。
  2. 测试冗余性:DarkLight1337 指出单独的处理器测试与 InternVL 测试重叠,且模型初始化测试已覆盖配置加载,作者同意后移除了 test_qianfan_ocr.py。
  3. 排序问题:DarkLight1337 要求将新增条目按字母顺序排列,作者已修正。
  4. 硬编码层数:gemini-code-assist[bot] 建议将视觉层数硬编码 24 改为动态读取配置,已被采纳(最终代码使用 config.vision_config.num_hidden_layers)。

实现拆解

  1. 配置文件定义(vllm/transformers_utils/configs/qianfan_ocr.py):新增 QianfanOCRVisionConfig 和 QianfanOCRConfig,分别对应视觉编码器和全模型配置。QianfanOCRConfig 兼容 dict 或 PretrainedConfig 输入,并自动使用 Qwen3 作为默认文本配置。
  2. 模型执行器(vllm/model_executor/models/qianfan_ocr.py):定义 QianfanOCRProcessingInfo(仅图像处理)和 QianfanOCRForConditionalGeneration,后者直接继承 InternVLChatModel,仅重写 _patch_quant_config 以在 FP8 量化时排除视觉层。通过 MULTIMODAL_REGISTRY 注册了处理器和 DummyInputs 生成器。
  3. 模型和配置注册:在 vllm/model_executor/models/registry.py 中添加架构映射;在 vllm/transformers_utils/configs/__init__.py 和 vllm/transformers_utils/config.py 中添加配置类映射,使 vLLM 能自动识别并加载。
  4. 测试支持:在 tests/models/multimodal/generation/vlm_utils/model_utils.py 中添加辅助函数;在 test_common.py 中添加 VLMTestInfo 条目;处理器测试因与 InternVL 共享,经 review 后删除;由于需要 transformers 5.6.0,设置了 min_transformers_version 跳过测试。
  5. 文档更新:在 docs/models/supported_models.md 中新增条目。
文件 模块 状态 重要度
vllm/model_executor/models/qianfan_ocr.py 模型执行 added 8.82
vllm/transformers_utils/configs/qianfan_ocr.py 配置类 added 8.15
tests/models/multimodal/generation/vlm_utils/model_utils.py 测试工具 modified 7.04
vllm/model_executor/models/registry.py 模型注册 modified 4.88
vllm/transformers_utils/configs/__init__.py 配置注册 modified 4.67

关键符号

QianfanOCRProcessingInfo.get_hf_processor QianfanOCRForConditionalGeneration._patch_quant_config QianfanOCRVisionConfig.__init__ QianfanOCRConfig.__init__ qianfan_ocr_hf_model_kwargs qianfan_ocr_patch_hf_runner QianfanOCRProcessor.__init__ QianfanOCRProcessor.__call__

关键源码片段

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

核心模型执行器文件,定义 QianfanOCRProcessingInfo 和 QianfanOCRForConditionalGeneration,重用 InternVL 的模型结构和多模态处理逻辑,仅覆盖量化配置补丁和处理器参数。

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
​
# QianfanOCR 基于 InternVL 构建,使用 Qwen3 作为语言骨干。
# 模型架构和权重与 InternVLChatModel 完全兼容,仅 config.model_type 和 architectures 不同。
​
from transformers import PretrainedConfig
​
from vllm.model_executor.layers.quantization import QuantizationConfig
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
from vllm.multimodal import MULTIMODAL_REGISTRY
from vllm.transformers_utils.processors.internvl import (
    InternVLImageProcessor,
    InternVLProcessor,
)
​
from .internvl import (
    BaseInternVLDummyInputsBuilder,
    BaseInternVLMultiModalProcessor,
    BaseInternVLProcessingInfo,
    InternVLChatModel,
)
​
​
class QianfanOCRProcessingInfo(BaseInternVLProcessingInfo):
    """仅支持图片的 ProcessingInfo,不支持视频。"""
​
    def get_hf_processor(self, **kwargs: object) -> InternVLProcessor:
        config = self.get_hf_config()
        vision_config = config.vision_config
​
        # 合并多模态上下文中的额外参数,并从配置中提取默认值
        kwargs = self.ctx.get_merged_mm_kwargs(kwargs)
        kwargs.setdefault("image_size", vision_config.image_size)
        kwargs.setdefault("min_dynamic_patch", config.min_dynamic_patch)
        kwargs.setdefault("max_dynamic_patch", config.max_dynamic_patch)
        kwargs.setdefault("dynamic_image_size", config.dynamic_image_size)
        kwargs.setdefault("use_thumbnail", config.use_thumbnail)
​
        image_processor = InternVLImageProcessor(**kwargs)
        image_size = image_processor.image_size
        patch_size = vision_config.patch_size
        downsample_ratio = config.downsample_ratio
        # 计算每个图像对应的 token 数
        image_seq_length = int((image_size // patch_size) ** 2 * (downsample_ratio**2))
​
        return InternVLProcessor(
            tokenizer=self.get_tokenizer(),
            image_processor=image_processor,
            video_processor=None, # 不支持视频
            image_seq_length=image_seq_length,
            ctx_video_token=None,
        )
​
​
@MULTIMODAL_REGISTRY.register_processor(
    BaseInternVLMultiModalProcessor,
    info=QianfanOCRProcessingInfo,
    dummy_inputs=BaseInternVLDummyInputsBuilder,
)
class QianfanOCRForConditionalGeneration(InternVLChatModel):
    """
    QianfanOCR 多模态模型。
​
    结构与 InternVLChatModel 完全相同(InternViT 视觉编码器 +
    pixel-shuffle MLP 连接器 + Qwen3 语言模型)。此类仅用于
    注册出现在模型 config.json 中的架构名称。
    """
​
    def _patch_quant_config(
        self, config: PretrainedConfig, quant_config: QuantizationConfig
    ) -> None:
        super()._patch_quant_config(config, quant_config)
        # 在 FP8 量化时忽略视觉层以保持模型性能
        if isinstance(quant_config, Fp8Config):
            # 动态获取 vision encoder 层数,支持模型变体
            _FP8_IGNORED_LAYERS = [
                *(
                    layer
                    for i in range(config.vision_config.num_hidden_layers)
                    for layer in [
                        f"vision_model.encoder.layers.{i}.attn.qkv",
                        f"vision_model.encoder.layers.{i}.attn.proj",
                        f"vision_model.encoder.layers.{i}.mlp.fc1",
                        f"vision_model.encoder.layers.{i}.mlp.fc2",
                    ]
                ),
                "language_model.lm_head",
                "mlp1.1",
                "mlp1.3",
            ]
            for layer in _FP8_IGNORED_LAYERS:
                if layer not in quant_config.ignored_layers:
                    quant_config.ignored_layers.append(layer)

评论区精华

是否将配置文件内置到 vLLM 还是使用 trust_remote_code 设计

DarkLight1337 建议使用 trust_remote_code 以减少维护负担,marvinzh 表示希望避免用户指定 trust_remote_code 以改善体验,并愿意承担维护责任。

结论:双方达成一致,保留配置文件并作者负责维护。 · 已解决

测试文件冗余 测试

DarkLight1337 指出 test_qianfan_ocr.py 中的处理器测试和配置测试与 InternVL 测试重叠,且模型初始化测试已覆盖配置加载。

结论:作者移除了处理器测试和冗余配置测试文件。 · 已解决

代码组织规范性 style

DarkLight1337 要求将新添加的条目按字母顺序排列。

结论:作者更新了排序。 · 已解决

硬编码 vision encoder 层数 正确性

gemini-code-assist[bot] 建议将硬编码的 24 改为动态读取配置,以支持不同层数的变体。

结论:已采纳,最终代码使用 config.vision_config.num_hidden_layers。 · 已解决

风险与影响

  1. 配置类需要持续维护,若上游模型配置变更可能产生不一致,但作者承诺维护。
  2. 依赖 transformers 5.6.0,vLLM 当前默认 5.5.3,测试已通过 min_transformers_version 跳过,但用户需要手动升级 transformers。
  3. 模型完全继承 InternVL,若 InternVL 出现 bug 修复,QianfanOCR 也需要同步更新。

用户可以直接使用 baidu/QianfanOCR 模型,无需 trust_remote_code,降低使用门槛。新增约 300 行源码,对系统影响局部化。团队维护负担由作者承担,但减少了其他用户的接入成本。

新模型集成 外部维护依赖 transformers 版本兼容 继承架构同步风险

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论