首页
/ 在 🤗 Transformers 中集成 Ovis2:多模态视觉-语言大模型的架构解析与图像文本生成实践

在 🤗 Transformers 中集成 Ovis2:多模态视觉-语言大模型的架构解析与图像文本生成实践

2026-09-07 12:20:05作者:秋阔奎Evelyn

Ovis2 是阿里巴巴国际数字商业集团 AIDC-AI 团队在其 Ovis 系列基础上推出的多模态大语言模型(MLLM),延续了"对齐视觉与文本嵌入"的核心设计思路。本文以 docs/source/en/model_doc/ovis2.md 为主线,结合当前仓库中 src/transformers/models/ovis2/ 的源码实现,系统讲解 Ovis2 在 Transformers 中的模块组成、关键配置项、Processor 的图像分块策略,并给出可直接运行的图像描述(Image Captioning)推理代码。读完本文,你将掌握如何加载官方转换后的 Ovis2 权重做端到端多模态生成,并理解底层 placeholder 替换、视觉 indicator token 等机制。

Ovis2 概览:从 Ovis 到 Ovis2

根据官方文档,Ovis2 是 AIDC-AI 团队在阿里巴巴国际数字商业集团开发的 Ovis 模型的更新版本,作为 Ovis1.6 的继任者,Ovis2 是多模态大语言模型(MLLM)领域的最新进展之一。它继承了 Ovis 系列"聚焦于对齐视觉与文本嵌入"的架构设计,同时在数据整理(data curation)与训练方法上引入了重大改进。

从模型历史看,Ovis 系列的研究成果于 2024-05-31 发表在 Hugging Face 论文平台,而 Ovis2 的模型实现由社区成员 thisisiron 于 2025-08-18 贡献并合入 Transformers。当前仓库中已包含完整的 Ovis2 实现文件,集中在:

代码中的模型与配置均已注册到 Auto 体系(见 auto_mappings.py 等自动映射文件),因此既可以直接使用 Ovis2ForConditionalGeneration 这样的显式类,也可以通过 AutoModelForImageTextToText / AutoProcessor 这类通用入口加载。

Ovis2 的架构设计:从源码理解三大组成模块

打开 modeling_ovis2.py 可以看到,Ovis2Model(第 467 行起)由三部分拼接而成:

class Ovis2Model(Ovis2PreTrainedModel):
    def __init__(self, config: Ovis2Config):
        super().__init__(config)
        self.vision_tower = Ovis2VisionModel(config.vision_config)
        self.language_model = AutoModel.from_config(config.text_config)
        self.visual_embeddings_table = Ovis2VisualEmbeddingTable(config.vision_config.vocab_size, config.hidden_size)
  1. 视觉塔(vision_tower)Ovis2VisionModel,一个自带"视觉分词头"的 ViT,负责把图片编码成视觉 token 的概率分布;
  2. 语言模型(language_model):由 text_config 指定,默认回落到 Qwen2 架构(详见配置小节),承担全部文本理解与自回归生成;
  3. 视觉嵌入表(visual_embeddings_table):充当"视觉词汇表 → LLM 嵌入空间"的投影桥,其独特之处在于继承了 nn.Embedding 却重写了前向逻辑——当输入是整数索引时走标准查表;当输入是连续的视觉概率分布向量时,则退化为一次矩阵乘法 torch.matmul(visual_tokens, self.weight)(见 Ovis2VisualEmbeddingTable 第 359 行起)。这一设计正是 Ovis 系列"把视觉信号软化、与文本 embedding 空间对齐"思想的具体实现。

视觉编码器的"软分词":visual indicator tokens

与多数将图像 patch 直接映射为 embedding 的 MLLM 不同,Ovis2 的视觉塔做了一层显式的 token 化。Ovis2VisionTransformer 由 patch 嵌入(nn.Conv2d,kernel/stride 等于 patch_size)、若干 Ovis2VisionEncoderLayer 以及尾部 RMSNorm 组成;每层编码器都采用 pre-norm 的 RMSNorm + 多头注意力 + 带 gate/up/down 三个线性层与 SiLU 激活的 FFN(结构与 Ovis2VisionMLP 相同)。

真正关键的是 Ovis2VisionModel 顶部的两个线性层(modeling_ovis2.py 第 410-416 行):

self.head_linear = nn.Linear(
    config.hidden_size * config.hidden_stride * config.hidden_stride,
    self.vocab_size - self.num_visual_indicator_tokens,
    bias=False,
)
self.head_norm = nn.LayerNorm(self.vocab_size - self.num_visual_indicator_tokens)

它把视觉塔最后一层隐藏状态映射到一个"视觉词汇表"上(默认 vocab_size=16384,其中末尾 5 个类别被保留给 visual indicator tokens),随后按 tokenize_function 归一化:

  • softmax(默认):直接对 logits 做 softmax,产生连续的概率分布作为软视觉 token;
  • st_argmax:straight-through 式的硬 argmax,训练时梯度可穿透;
  • gumbel_argmax:Gumbel-Softmax 的硬采样变体。

从该模块的实现(见 configuration_ovis2.py 第 25-53 行)可以看到 num_visual_indicator_tokensvocab_size 共同定义了软词表的边界。此外当 hidden_stride > 1 时,视觉塔会把空间相邻的 hidden_stride × hidden_stride 个 patch 合并拼接后再送进 head_linear,用于在分辨率与 token 数之间做取舍(代码中对非整除情况会先做 zero-padding 使 token 序列长度保持完全平方)。

前向融合:placeholder 的精确替换

Ovis2Model.forward(第 537 行起)是理解图像"如何进入文本流"的最佳入口,其逻辑可以概括为三步:

  1. 图像特征提取:调用 get_image_features(pixel_values=...),视觉塔输出每个视觉 token 的软概率,随后拼接宽度为 num_visual_indicator_tokens 的零向量并经过 visual_embeddings_table 投影,得到真正可被 LLM 消费的连续向量 image_features(见第 483-509 行);
  2. 图像占位符替换:通过 get_placeholder_mask(第 511 行起)在 input_ids/inputs_embeds 中定位所有等于 config.image_token_id(默认 151665)的占位 token,并用 inputs_embeds.masked_scatter(...) 将图像特征"铺"进这些位置,同时校验图像特征数量与占位符数量严格一致,否则直接报错;
  3. indicator token 注入:遍历 visual_indicator_token_ids(默认 [151666, 151667, 151668, 151669, 151670]),凡是文本中出现这些 id 的位置,都会被替换成相应的 visual_indicator_features(第 569-579 行),从而把"图像提示符"的语义也带入 LLM embedding 空间。

完成上述融合后,inputs_embeds 连同 attention_maskposition_ids 等一起交给 language_modelOvis2ForConditionalGeneration(第 601 行起)在此之上套了不带 bias 的 lm_head 输出 logits,并借助 logits_to_keep 只计算必要位置的 logits 以降低生成开销。整条链路最终返回 Ovis2CausalLMOutputWithPast,其中额外的 image_hidden_states 携带了视觉编码器产生的中间图像表征,方便研究者做二次分析与调试。

modeling_ovis2.py 第 366-379 行的类属性可以看出,Ovis2 原生支持 gradient checkpointing、KV Cache(_supports_cache_class)、Flash Attention、Flex Attention 与 SDPA 等注意力后端(_supports_flash_attn / _supports_flex_attn / _supports_sdpa 均为 True),并声明了多模态输入 input_modalities = ("image", "text")

配置项详解:Ovis2Config 与 Ovis2VisionConfig

Ovis2 的配置由顶层 Ovis2Config 与嵌套的 Ovis2VisionConfig(同时实现了 AutoConfig 子配置映射,见 configuration_ovis2.py 第 35 行 base_config_key = "vision_config")构成。

Ovis2VisionConfig

控制视觉塔(ViT 风格编码器 + 软分词头),关键字段及其默认值如下表所示(取自源码类定义):

字段 默认值 说明
hidden_size 1024 视觉塔隐藏维度
intermediate_size 2816 FFN 中间层维度
num_hidden_layers 24 编码器层数
num_attention_heads 8 注意力头数
num_channels 3 输入图像通道数
image_size 224 输入图像边长(也支持 list / (h, w))
patch_size 14 patch 边长(支持 list / (h, w))
rms_norm_eps 1e-5 RMSNorm 的 epsilon
attention_dropout 0.0 注意力 dropout
qkv_bias / mlp_bias False 是否使用 bias
hidden_act "silu" FFN 激活函数
vocab_size 16384 视觉"软词表"大小
hidden_stride 1 相邻 patch 合并步长(>1 时压缩视觉 token)
num_visual_indicator_tokens 5 视觉 indicator token 数量
initializer_range 0.02 参数初始化范围
tokenize_function "softmax" 软分词方式:softmax / st_argmax / gumbel_argmax

Ovis2Config

顶层配置控制整体模型,关键字段如下(见 configuration_ovis2.py 第 58-99 行):

字段 默认值 说明
model_type "ovis2" 模型类型标识,用于 Auto 注册
vision_config None 视觉子配置(dict 或 Ovis2VisionConfig,缺省自动生成且 num_visual_indicator_tokens 取 indicator 数量)
text_config None 文本子配置(dict 或 PreTrainedConfig,缺省回落为 qwen2,即语言模型默认基于 Qwen2 架构,见第 94-97 行)
image_token_id 151665 图像占位符 token id
visual_indicator_token_ids (151666, 151667, 151668, 151669, 151670) 编码图像提示的 indicator token id 序列
vocab_size 151643 文本词表大小
hidden_size 1536 LLM 隐藏维度
tie_word_embeddings True 是否共享输入/输出词嵌入

值得注意的是 __post_init__ 的处理方式(第 88-99 行):若传入 dict 形式的 vision_config/text_config 会自动实例化对应配置类;text_config 为空时直接构造 CONFIG_MAPPING["qwen2"]()vision_config 为空时会用 len(visual_indicator_token_ids) 自动对齐 indicator token 个数。这意味着最小配置只需 Ovis2Config(),即可得到一个"Ovis2 骨架 + Qwen2 语言模型"的完整模型。

配置文件中还给出了从零初始化模型的示例(configuration_ovis2.py 第 63-74 行):

>>> from transformers import Ovis2ForConditionalGeneration, Ovis2Config

>>> # Initializing a Ovis2 style configuration
>>> configuration = Ovis2Config()

>>> # Initializing a model from the Ovis2-2B style configuration
>>> model = Ovis2ForConditionalGeneration(configuration)

>>> # Accessing the model configuration
>>> configuration = model.config

Processor 与动态分块:多分辨率图像如何被切分

Ovis2Processor:模板对话与占位符展开

Ovis2Processorprocessing_ovis2.py)把图像处理器与分词器粘合在一起,支持基于 Chat Template 的对话式输入。其 __init__ 接受 image_token="<image>"image_seq_length=256 两个关键参数:前者是对话内容中标记图像位置的 token,后者是每张图(默认单一 tile 时)展开的图像 token 数量。

Processor 通过 replace_image_token 方法(第 53-66 行)依据图像预处理产出的 grids 信息动态构造占位文本。伪结构为:

<IMG_START> <IMG_ATOM>×256 <IMG_GRID>
<(若网格大于 1×1)每行每列重复 <IMG_ATOM>×256,并用 <IMG_COL> / <IMG_ROW> 分隔>
<IMG_END>

也就是说,当图像被切成多个 tile 时,每个 tile 都会获得独立的图像 token 块,token 数量由 image_seq_length × 网格数 决定,网格内部用 <IMG_COL>(列分隔)与 <IMG_ROW>(行分隔)标记空间布局。unused_input_names = ["grids"] 表明 grids 只是处理时的中间信息,最终不会传给模型。

Ovis2ImageProcessor:自适应 tile 画布

image_processing_ovis2.py 实现了对任意长宽比图像的自适应切块。其内部的核心函数包括:

  • get_all_supported_aspect_ratios(min_image_tiles, max_image_tiles):枚举所有允许的(宽 × 高)tile 网格组合;
  • get_optimal_tiled_canvas(...):从中挑出与原图长宽比最接近的画布;
  • get_min_tile_covering_grid(...):以 covering_threshold = 0.9 为界,寻找"patch 覆盖面积占比"达标前提下 tile 数最少的网格;
  • split_image_into_grid(...)compute_patch_covering_area(...):负责把图像切格并计算有效覆盖面积。

这些函数共同支撑两类可配置行为(定义在 Ovis2ImageProcessorKwargs 中):

参数 默认值 说明
crop_to_patches False 是否启用"按 patch 裁剪/分块"流程
min_patches 1 crop_to_patches=True 时的最小 patch(tile)数
max_patches 12 crop_to_patches=True 时的最大 patch(tile)数
use_covering_area_grid True 是否用"覆盖面积"启发式决定 tile 网格

预处理遵循 CLIP 的统计口径(源码导入了 OPENAI_CLIP_MEANOPENAI_CLIP_STD),并基于 Torchvision 后端执行缩放与归一化;对应的 Ovis2ImageProcessorPil 则是面向 PIL 输入、逐图处理的封装版本(见 image_processing_pil_ovis2.py)。默认 max_image_tiles=12 使模型可以在保持底层 patch 分辨率的前提下兼容从竖图、方图到超宽图的多种输入。

开箱即用:图像描述推理实战

下面以文档中的官方用法示例为基础(见 docs/source/en/model_doc/ovis2.md),做少量加固(显式指定 torch_dtypedevice_map),并改用仓库测试图片目录中的真实样例图作为输入。你完全可以把它替换为任意本地图片:

import torch
from PIL import Image

from transformers import AutoModelForImageTextToText, AutoProcessor

# 加载已转换为 HF 格式的官方 checkpoint(约 2B 参数)
model = AutoModelForImageTextToText.from_pretrained(
    "thisisiron/Ovis2-2B-hf",
    torch_dtype=torch.bfloat16,
    device_map="auto",
).eval()
processor = AutoProcessor.from_pretrained("thisisiron/Ovis2-2B-hf")

# 组装对话式消息:注意 content 中使用 {"type": "image"} 声明图像位置
messages = [
    {
        "role": "user",
        "content": [
            {"type": "image"},
            {"type": "text", "text": "Describe the image."},
        ],
    },
]

# 读取本地测试图片(也可以用你自己的图片路径替代)
image = Image.open("tests/fixtures/tests_samples/COCO/000000039769.png")

# 先通过 chat template 展开为纯文本 prompt,便于检查
messages = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
print(messages)

# 统一做多模态编码:text -> input_ids,image -> pixel_values
inputs = processor(images=[image], text=messages, return_tensors="pt")
inputs = inputs.to(model.device)
# 视觉塔通常在 float32 下精度更好/内存更大,官方示例将其显式转成 bf16 以省显存
inputs["pixel_values"] = inputs["pixel_values"].to(torch.bfloat16)

with torch.inference_mode():
    output_ids = model.generate(**inputs, max_new_tokens=128, do_sample=False)
    # 仅截取新生成的 token(去除输入提示部分)
    generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(inputs.input_ids, output_ids)]
    output_text = processor.batch_decode(generated_ids, skip_special_tokens=True)
    print(output_text)

该流程的几个要点:

  • 模型与处理器配套使用AutoModelForImageTextToTextAutoProcessor 都会读取远端仓库的 config.jsonpreprocessor_config.json,自动实例化 Ovis2ForConditionalGeneration、对应的图像处理器与分词器;
  • apply_chat_template:把 messages 结构转成带 <|im_start|>/<|im_end|> 角色标记的指令串(processor 内部再替换成 <IMG_START> 等图像占位结构);
  • 精度选择:示例将 pixel_values 显式转成 bfloat16。若 GPU 显存充足也可保持更高精度,但需与 torch_dtype 的选择保持一致,避免视觉塔输入输出 dtype 不匹配;
  • 截取生成段output_ids[len(input_ids):] 剔除了输入提示部分,只保留真正生成的图像描述文本。

如果希望绕开 Chat Template、直接用原始指令字符串,模型 docstring 也给出了等价写法(modeling_ovis2.py 第 640-662 行),例如把 prompt 构造为:

<|im_start|>user\n<image>\nDescribe the image.<|im_end|>\n<|im_start|>assistant\n

然后同样走 processor(images=image, text=prompt, return_tensors="pt")model.generate(...) 即可。

需要说明的是:图像与文本分支均较大,2B 级 checkpoint 建议在具备 CUDA 且显存充足的 GPU 上以 bfloat16 运行;CPU 推理耗时较长,仅适合功能验证。文档与测试中出现的 HF 转换后 checkpoint 名称包括 thisisiron/Ovis2-1B-hfthisisiron/Ovis2-2B-hf(如配置类 docstring 与 test_processing_ovis2.py 均引用了 Ovis2-1B-hf,模型测试则以 Ovis2-2B-hf 为例),加载时请按实际可用模型名替换。

API 参考速览

除上面剖析的机制外,官方文档还登记了以下公开 API,均可在 model_doc/ovis2.md 中通过 autodoc 索引到完整 docstring:

类/方法 作用
Ovis2Config 顶层配置类(文本 + 视觉双子配置)
Ovis2VisionConfig 视觉塔配置类
Ovis2Model 无语言模型头的整体骨干:视觉塔 + 语言模型 + 视觉嵌入表
Ovis2ForConditionalGenerationforwardget_image_features lm_head 的条件生成模型;get_image_features 可单独抽取投影后的图像特征
Ovis2ImageProcessorpreprocess 基于 Torchvision 的图像预处理,含动态分块
Ovis2ImageProcessorPilpreprocess 面向 PIL 输入的图像预处理
Ovis2Processor__call__ 图像处理器 + 分词器 + Chat Template 的统一入口

质量保障与扩展阅读

  • 测试覆盖:当前仓库在 tests/models/ovis2/ 下提供了 test_modeling_ovis2.py(模型前向/生成/图像特征)、test_processing_ovis2.py(Processor 与 chat template 行为)以及 test_image_processing_ovis2.py(分块与预处理),可作为理解 Ovis2 预期行为的参考用例;
  • 自动注册:Ovis2 的模型类、配置类已写入 Auto 映射(见 src/transformers/models/auto/auto_mappings.pymodeling_auto.py),因此前述 AutoModelForImageTextToText / AutoConfig / AutoModel 均可直接解析 ovis2 类型;
  • 权重转换convert_ovis2_weights_to_hf.py 提供了将 Ovis2 原始权重整理为 Transformers 兼容格式的转换入口,对从源头复现或二次训练很有价值;
  • 代码生成说明:模型主体文件头部注明它由 modular_ovis2.py 自动生成,若希望研究该架构的模块化拆分方式,可对比阅读 modular 源文件与生成文件。

综上,Ovis2 在 Transformers 中的落地融合了三条关键设计:以"软视觉词表 + visual indicator tokens"承载图像语义、以可查表亦可矩阵乘的视觉嵌入表完成跨模态投影、以"占位符 masked_scatter + 动态 tile 预处理"实现灵活的任意分辨率输入。理解这三条主线后,无论是直接部署推理还是基于该实现做二次开发,都能有的放矢。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388