InternVL2_5-78B架构深度解析:ViT-MLP-LLM范式与pixel_shuffle如何压缩视觉Token
InternVL2_5-78B架构深度解析:ViT-MLP-LLM范式与pixel_shuffle如何压缩视觉Token
🔍 InternVL2_5-78B 是 OpenGVLab 开源的旗舰多模态大语言模型(MLLM),总参数量约 780 亿。它采用经典的 ViT-MLP-LLM 三段式架构:InternViT-6B 视觉编码器 + MLP 投影层 + Qwen2.5-72B 语言模型,并通过 pixel_shuffle(像素重排) 将视觉 Token 压缩至原来的 1/4,让"看得清"与"算得动"兼得。本文将带你快速吃透它的架构设计与视觉 Token 压缩原理。
ViT-MLP-LLM 三大组件:各司其职
InternVL2_5-78B 的架构由三个独立预训练的模块拼接而成,中间用一层随机初始化的 MLP 做"翻译官":
| 组件 | 型号 | 关键参数 |
|---|---|---|
| 🖼️ 视觉编码器 ViT | InternViT-6B | hidden_size=3200,patch_size=14,45 层,25 个注意力头 |
| 🔗 MLP 投影层 | 两层全连接 | 3200×4 → 8192 → 8192 |
| 💬 语言模型 LLM | Qwen2.5-72B | hidden_size=8192,80 层,最大上下文 32768 |
三者的配置分别定义在 configuration_intern_vit.py、configuration_internvl_chat.py 中,统一入口模型类 InternVLChatModel 位于 modeling_internvl_chat.py。
ViT 视觉编码器:448px 输入下的细粒度感知
视觉塔基于 InternViT-6B(模型类型 intern_vit_6b),有几个值得注意的设计:
- RMSNorm + QK Normalization:用
qk_normalization=true对查询/键向量做归一化,提升训练稳定性,这在 configuration_intern_vit.py 中有明确定义; - Flash Attention 加速:通过
use_flash_attn开关启用,实现见 modeling_intern_vit.py 的FlashAttention类; - 固定 448×448 输入:
force_image_size=448,与 patch_size=14 整除得到 32×32 个 patch,是后续 Token 计算的基础。
MLP 投影层:维度对齐的"翻译官"
ViT 输出维度(3200)与 LLM 输入维度(8192)并不一致,且经过 pixel_shuffle 后单个 Token 的通道数变成 3200×4=12800。mlp1 就是负责这次对齐的关键模块:
LayerNorm(12800) → Linear(12800, 8192) → GELU → Linear(8192, 8192)
对应源码在 modeling_internvl_chat.py。训练时该 MLP 随机初始化,先单独预热(MLP Warmup 阶段),冻结 ViT 和 LLM,让跨模态对齐先稳定下来。
LLM:Qwen2.5-72B 提供语言底座
语言部分直接复用 Qwen2.5-72B-Instruct(见 config.json),具备 GQA 分组注意力(64 个 Q 头 / 8 个 KV 头)和 32K 长上下文,bfloat16 精度运行。多模态信息最终以 <IMG_CONTEXT> 占位 Token 的形式被"替换"进输入嵌入序列,在 modeling_internvl_chat.py 的 forward 中完成注入:
input_embeds[selected] = input_embeds[selected] * 0.0 + vit_embeds.reshape(-1, C)
pixel_shuffle 如何把视觉 Token 压缩到 1/4?🧩
这是 InternVL 系列最巧妙的工程设计。核心问题:一张 448×448 的图片经过 ViT 后会产生 32×32=1024 个视觉 Token,若原样送入 LLM,多张图、视频帧一叠加,注意力开销将爆炸。
压缩三步走:以单张 448×448 图为例
- ViT 输出:
1024 × 3200的特征序列(每个 patch 一个 Token); - pixel_shuffle:按 2×2 邻域重排,把空间分辨率减半、通道数放大 4 倍 →
256 × 12800; - MLP 对齐:投影到 LLM 维度 →
256 × 8192。
也就是说,1024 个 Token 被无损地"打包"成 256 个更宽(12800 维)的 Token——4 个相邻 patch 的特征沿通道维拼接,空间信息没有丢失,只是密度更高了。
pixel_shuffle 的完整实现位于 modeling_internvl_chat.py,调用点在 extract_feature 方法中(modeling_internvl_chat.py)。每个 448×448 tile 最终贡献的 Token 数由这一行公式给出:
num_image_token = (image_size // patch_size)² × downsample_ratio² = 32² × 0.5² = 256
见 modeling_internvl_chat.py,其中 downsample_ratio=0.5 定义在 config.json。
ps_version v1 → v2:修复转置 Bug 的演进
细看源码会发现版本差异:v1 版 pixel_shuffle 少做了一次高宽交换,导致重排后的特征图被转置;v2 版在末尾补上 x = x.permute(0, 2, 1, 3) 修正了该问题(modeling_internvl_chat.py)。本仓库配置为 "ps_version": "v2"(config.json),即使用修正后的空间顺序。
动态分辨率与缩略图:Token 预算的灵活分配
固定 448px 输入限制了复杂图像的感知上限,InternVL2_5-78B 采用动态高分辨率策略解决:
- 448×448 网格切块:原始图像被切成 n 个 tile,每个 tile 独立过 ViT;
- 动态分配:
min_dynamic_patch=1、max_dynamic_patch=12(config.json),简单图用 1 块(256 Token),复杂图最多 12 块(3072 Token); - 缩略图机制:
use_thumbnail=true(config.json)会为图像附加一张全局缩略图,让模型兼顾"局部细节 + 全局布局"; - 多模态统一:多张图片、视频帧同样以 tile 为单位处理,视频文件可参考 examples/red-panda.mp4,图片示例见 examples/image1.jpg。
核心文件速查清单 📁
| 文件 | 作用 |
|---|---|
| config.json | 全局配置:downsample_ratio、ps_version、动态 patch 范围 |
| configuration_internvl_chat.py | InternVLChatConfig,组合 ViT + LLM 双配置 |
| configuration_intern_vit.py | InternVisionConfig,视觉编码器超参 |
| modeling_intern_vit.py | InternVisionModel 及 FlashAttention 实现 |
| modeling_internvl_chat.py | 主模型类:pixel_shuffle、mlp1、chat/generate |
| conversation.py | 对话模板(internvl2_5 template) |
| model.safetensors.index.json | 33 个权重分片的映射索引 |
总结:为什么这套架构值得学习?
InternVL2_5-78B 展示了多模态大模型工程化的精髓:
- 解耦设计:ViT、LLM 各自独立预训练,MLP 低成本对齐,任何一端都可替换升级;
- pixel_shuffle 压缩:用"空间换通道"的零参数操作,将视觉 Token 降至 1/4,显存与注意力成本随之大幅下降;
- 动态分辨率:按图像复杂度弹性分配 Token 预算,细节与成本兼得。
理解这三个要点,你就掌握了 InternVL 系列——乃至大多数"编码器 + 投影层 + LLM"架构 MLLM 的核心思想。
