Hugging Face Transformers 中的 Aria 多模态 MoE 模型:架构剖析、图像问答推理与量化部署实战
Aria 是 Rhymes AI 开源的多模态 Mixture-of-Experts(MoE)模型,采用「轻量视觉编码器 + MoE 文本解码器 + 交叉注意力投影器」的组合,自 2024-12-06 起已正式集成进本仓库(关联文档),并以 AutoModel/AutoProcessor/pipeline 全链路可用。本文以该模型文档为主线,结合仓库内 src/transformers/models/aria/ 的配置、预处理、处理器与建模源码,系统讲解 Aria 的模型背景、图像问答的快速上手、torchao 权重量化部署、核心架构模块与前向融合流程、配置与图像预处理参数,让读者既能直接跑通推理,也能理解每个环节背后的源码实现。
Aria 是什么:多模态原生 MoE 模型
Aria 的目标是开源一套「从零训练多模态原生模型」的配方。其核心设计要点如下(来自模型文档):
- 多模态 MoE 架构:文本由 MoE 解码器处理,视觉输入由一个轻量视觉编码器处理;
- 按需激活:每个视觉 token 激活约 3.9B 参数,每个文本 token 激活约 3.5B 参数——即「总参数量更大,但每次推理只激活其中一小部分」;
- 四阶段训练:语言预训练 → 多模态预训练 → 多模态长上下文预训练 → 多模态后训练,最终覆盖常见图文理解能力。
在模型文档右侧列出的可用算子上,Aria 同时支持 FlashAttention 与 SDPA 两种注意力实现。从源码看,AriaTextPreTrainedModel 在 modeling_aria.py 中声明了 _supports_flash_attn = True 与 _supports_sdpa = True,AriaPreTrainedModel 则继承了完整的 attention-backend 支持——这也是为什么下面示例可以在 attn_implementation="sdpa" 下运行。
快速上手:基于图片的文本生成
模型文档给出了两种等价的上手方式:Pipeline 一行调用,或 AutoModel + AutoProcessor 显式控制。两者底层走的是同一套聊天模板与生成逻辑。
方式一:Pipeline(极简入口)
image-to-text pipeline 会自动加载 rhymes-ai/Aria 并完成全部预处理:
from transformers import pipeline
pipeline = pipeline(
"image-to-text",
model="rhymes-ai/Aria",
device=0,
)
pipeline(
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg",
text="What is shown in this image?"
)
pipeline(...) 的第一个位置参数即图像(URL、本地路径或 PIL 图像均可),第二个 text= 为提问。适合快速验证模型效果与搭建最小 demo。
方式二:AutoModel + AutoProcessor(推荐用于生产)
通过 AutoModelForCausalLM 与 AutoProcessor 加载模型,显式调用 apply_chat_template 构造多模态对话输入,再调用 generate:
import torch
from transformers import AutoModelForCausalLM, AutoProcessor
model = AutoModelForCausalLM.from_pretrained(
"rhymes-ai/Aria",
device_map="auto",
attn_implementation="sdpa",
)
processor = AutoProcessor.from_pretrained("rhymes-ai/Aria")
messages = [
{
"role": "user", "content": [
# url 字段也可替换为本地图片路径或 PIL 图像
{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"},
{"type": "text", "text": "What is shown in this image?"},
]
},
]
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(model.device)
inputs = inputs.to(model.device, torch.bfloat16)
output = model.generate(
**inputs,
max_new_tokens=15,
stop_strings=["<|im_end|>"],
tokenizer=processor.tokenizer,
do_sample=True,
temperature=0.9,
)
output_ids = output[0][inputs["input_ids"].shape[1]:]
response = processor.decode(output_ids, skip_special_tokens=True)
print(response)
这段示例里值得注意的工程细节:
apply_chat_template(..., tokenize=True, return_dict=True, return_tensors="pt")一次性完成「模板展开 + 多模态 token 占位 + tokenize」,返回可直接送入模型的input_ids、pixel_values、pixel_mask等字段;- 输入被显式转成
torch.bfloat16后再送入生成; stop_strings=["<|im_end|>"]配合tokenizer=processor.tokenizer,让生成在 Aria 的对话结束符处自然停止;- 解码时通过
output[0][inputs["input_ids"].shape[1]:]截掉输入部分,仅对新增 token 调用processor.decode(..., skip_special_tokens=True)。
多图 / 多轮批量问答
在 modeling_aria.py 的 forward 示例中,展示了更完整的「多消息 + 多图」用法:先逐个消息调用 apply_chat_template 得到纯文本 prompt,再通过 processor(text=prompts, images=images, padding=True, return_tensors="pt") 把按消息分组的图片列表(如 [[image1, image2], [image3]])与文本对齐;generate(max_new_tokens=256) 之后用 batch_decode 批量还原答案。图片既可直接传入 PIL 图像,也可用 load_image 从 URL/本地路径加载。
用 torchao 把权重量化到 int4
量化通过低精度权重显著降低显存占用。仓库文档的 Quantization 总览 罗列了各后端;对 Aria 而言,文档推荐使用 torchao 做 int4_weight_only 量化,并搭配专为量化设计的 rhymes-ai/Aria-sequential_mlp 权重:
# pip install torchao
import torch
from transformers import AutoModelForCausalLM, AutoProcessor, TorchAoConfig
quantization_config = TorchAoConfig("int4_weight_only", group_size=128)
model = AutoModelForCausalLM.from_pretrained(
"rhymes-ai/Aria-sequential_mlp",
device_map="auto",
quantization_config=quantization_config,
)
processor = AutoProcessor.from_pretrained(
"rhymes-ai/Aria-sequential_mlp",
)
messages = [
{
"role": "user", "content": [
{"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"},
{"type": "text", "text": "What is shown in this image?"},
]
},
]
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(model.device)
inputs = inputs.to(model.device, torch.bfloat16)
output = model.generate(
**inputs,
max_new_tokens=15,
stop_strings=["<|im_end|>"],
tokenizer=processor.tokenizer,
do_sample=True,
temperature=0.9,
)
output_ids = output[0][inputs["input_ids"].shape[1]:]
response = processor.decode(output_ids, skip_special_tokens=True)
print(response)
为什么推荐 Aria-sequential_mlp 权重:普通 Aria 的 MoE 专家使用 grouped GEMM 计算,形状为三维 (num_experts, in_features, out_features) 的权重张量;而 modeling_aria.py 中 AriaGroupedExpertsGemm 注释明确指出,量化工具难以直接处理 grouped GEMM。sequential_mlp checkpoint 将 grouped GEMM 替换为标准 torch.nn.Linear(对应模块化源文件 modular_aria.py 中的 sequential_experts_gemm 循环逐专家矩阵乘),从而让 int4 量化可以按普通线性层逐层生效。
源码视角:三大子模块如何拼成 Aria
Aria 的整体装配在 modeling_aria.py 的 AriaModel 中一目了然:
self.vision_tower = AutoModel.from_config(config.vision_config) # 视觉编码器
self.multi_modal_projector = AriaProjector(config) # 多模态投影器
self.language_model = AutoModel.from_config(config.text_config) # MoE 语言解码器
三个子模块职责清晰:vision_tower 把图片变成视觉 patch 特征;multi_modal_projector 把这些特征「压缩 + 投影」进语言模型的嵌入空间;language_model(AriaText)完成最终的自回归文本生成。
视觉塔:复用 Idefics3 视觉主干
一个值得注意的细节是:Aria 的视觉塔并没有新造架构,而是在 configuration_aria.py 的 AriaConfig.__post_init__ 里通过 CONFIG_MAPPING["idefics3_vision"] 直接复用 Idefics3 的视觉配置,属于该模型视觉编码器的实际实现。
视觉特征抽取路径见 modeling_aria.py 的 get_image_features:
- 用
_create_patch_attention_mask(pixel_mask)把图像处理器产出的、分辨率为max_image_size × max_image_size的pixel_mask按视觉塔的patch_size划分网格,每个 patch 只要包含任一真实像素即为有效; - 视觉塔接收
patch_attention_mask并强制output_hidden_states=True,返回各层 hidden states; - 按
vision_feature_layer(配置默认-1,即最后一层)选取视觉特征; - 把选出的特征交给
multi_modal_projector,得到可供文本侧使用的 pooler 输出。
多模态投影器:交叉注意力把「patch 数」映射为「query 数」
AriaProjector(modeling_aria.py)包含三部分:可学习的 query 参数、一层 AriaCrossAttention 交叉注意力,以及一个两层的 AriaProjectorMLP(gelu_new 激活 + 无 bias 线性层)。
它的核心巧妙之处在于 patch 数与 query 数的映射表 patch_to_query_dict。由于 Aria 会把大图切分/缩放到不同 crop 数,不同输入会有不同 patch 总数;投影器针对每种 patch 数量预置了不同长度的 query:
- 默认映射见 configuration_aria.py:
{1225: 128, 4900: 256},即 1225 个 patch 对应 128 个 query,4900 个 patch 对应 256 个 query; AriaProjector从config.max_value_projector_patch_to_query_dict(映射表最大值 256)分配 query 参数,再在 forward 时按query_num = self.patch_to_query_dict[num_patches]截取前若干个 query 参与交叉注意力;- 若
num_patches不在映射表中会直接抛出KeyError,提示当前 patch 数与支持值(见 modeling_aria.py)。
这一机制保证了:无论输入分辨率如何变化,语言模型最终拿到的是固定维度、与 query 数一致的视觉 token 序列,可无缝拼接进文本嵌入。
文本侧:Llama 骨架 + MoE 前馈
AriaTextModel/AriaTextForCausalLM 在源码中大量继承 Llama 的实现:AriaTextConfig(LlamaConfig)、注意力直接复用 Llama 的 RoPE 与注意力,唯一被替换的是每一层 decoder 的 MLP:
AriaTextDecoderLayer的self.mlp被换成AriaTextMoELayer(modeling_aria.py);- MoE 层内含一个无 bias 的 router 线性层(
hidden_size → moe_num_experts)、一组路由专家AriaExperts和一组共享专家AriaSharedExpertsMLP; - 前向时每个 token 由 router 选出 top-k 专家(
torch.topk+ softmax 加权),专家输出与共享专家输出逐元素相加作为该层 FFN 结果; - 路由专家的激活函数是 SwiGLU(SiLU 门控),权重形状为三维 grouped GEMM 或逐专家 Linear,取决于加载的是普通权重还是
sequential_mlp权重。
与模型「3.5B 文本 token 激活参数」对应,上述 top-2-of-8 路由正是「大而稀疏」的来源:单层有 8 个专家(默认),每个 token 只激活 2 个,再叠加 2 个总被激活的共享专家。
前向融合:placeholder 占位与 masked_scatter
当 pixel_values 存在时,AriaModel.forward(modeling_aria.py)完成图文融合:
- 先把
input_ids查表得到文本inputs_embeds; - 视觉特征经投影器得到
image_features,转成与文本嵌入相同的 device/dtype; get_placeholder_mask定位input_ids中等于config.image_token_index(默认 9)的占位位置,并校验「图像 token 数 × 隐藏维度 == 图像特征元素总数」,不一致即报错;- 用
inputs_embeds.masked_scatter(special_image_mask, image_features)把视觉特征逐位置填充进文本嵌入流; - 合并后的序列送入
language_model做标准的因果建模,输出AriaModelOutputWithPast(含可选image_hidden_states)。
这套「占位 + 就地填充」的模式与 LLaVA 家族(LlavaModel/LlavaForConditionalGeneration)一脉相承——事实上 AriaModel 与 AriaForConditionalGeneration 正是以 Llava 的相关类为父类再覆写视觉/投影部分实现的(可对照模块化源 modular_aria.py)。
配置参数详解
Aria 的配置类与实现一致采用「文本 + 视觉 + 总配置」的分层结构,定义在 configuration_aria.py。
AriaConfig(总配置)
model_type = "aria",聚合子配置并在初始化时完成归一化:
| 参数 | 默认值 | 说明 |
|---|---|---|
text_config |
None |
文本子配置,可为 dict(需含 model_type)或 AriaTextConfig,为空时自动创建 |
vision_config |
None |
视觉子配置,dict 会被强制写入 model_type="idefics3_vision" 后实例化,为空时用默认 Idefics3 视觉配置 |
vision_feature_layer |
-1 |
视觉塔取哪一层特征,-1 表示最后一层,也支持列表取多层 |
projector_patch_to_query_dict |
{1225: 128, 4900: 256} |
patch 数 → 投影器 query 数的映射;键值统一转 int,并自动计算 max_value_projector_patch_to_query_dict |
image_token_index |
9 |
图片占位 token id,模型中用于定位并替换为视觉特征 |
initializer_range |
0.02 |
参数初始化标准差 |
tie_word_embeddings |
False |
是否共享输入/输出词嵌入 |
另外 attribute_map = {"image_token_id": "image_token_index"} 保证了旧字段名 image_token_id 仍可兼容读取。
AriaTextConfig(MoE 语言模型配置)
文本子配置继承自 Llama 系配置(model_type = "aria_text"),MoE 专属参数及典型默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
moe_num_experts |
8 |
MoE 层中路由专家总数 |
moe_topk |
2 |
每个 token 激活的 top 专家数 |
moe_num_shared_experts |
2 |
共享专家数,处理所有 token、不参与路由 |
intermediate_size |
4096 |
专家 FFN 的中间维度(共享专家的实际中间维度为 intermediate_size × moe_num_shared_experts,见 modeling_aria.py) |
hidden_size |
4096 |
隐藏维度 |
num_hidden_layers |
32 |
解码器层数 |
num_attention_heads |
32 |
注意力头数 |
num_key_value_heads |
None |
KV 头数,缺省时等于注意力头数(无 GQA) |
head_dim |
None |
每头维度,缺省时由 hidden_size / num_attention_heads 自动推导 |
hidden_act |
"silu" |
FFN 激活 |
rms_norm_eps |
1e-6 |
RMSNorm epsilon |
max_position_embeddings |
2048 |
最大位置编码 |
vocab_size |
32000 |
词表大小 |
pad/bos/eos_token_id |
2 / 1 / 2 |
特殊 token id |
use_cache |
True |
是否启用 KV cache |
attention_bias |
False |
注意力是否带 bias |
rope_parameters |
None |
RoPE 参数(继承 Llama 的 RoPE 体系) |
AriaTextConfig 还内置了架构校验:若 hidden_size 不能被 num_attention_heads 整除会直接抛错(见 configuration_aria.py);同时声明了 tensor parallelism / pipeline parallelism 的分片计划,以及 keys_to_ignore_at_inference = ["past_key_values"]。
图像预处理:让不同分辨率的图都能进模型
Aria 需要支持任意分辨率图像,因此图像处理器包含「选分辨率 → 缩放 → 切分/对齐 → 生成真实像素 mask」的完整管线。仓库提供了两套等价实现:基于 torchvision 后端的 AriaImageProcessor(image_processing_aria.py)与基于 Pillow 后端的 AriaImageProcessorPil(image_processing_pil_aria.py),二者预处理逻辑一致、仅底层张量形态不同(Tensor vs NumPy)。
两者的模型输入名为 ["pixel_values", "pixel_mask", "num_crops"],类级默认参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
max_image_size |
980 |
单张 crop 的最大边长,只允许 490 或 980,传其他值抛 ValueError |
min_image_size |
336 |
最小边长,任意维度小于它的图会被放大 |
split_image |
False |
是否把超大图按最接近的 split_resolutions 切成多个 980 方块 crop |
split_resolutions |
见下 | 候选切分分辨率 (高, 宽) 列表,格式为 (h, w) 的 490 倍数网格 |
resample |
BICUBIC |
缩放插值方式 |
image_mean / image_std |
[0.5, 0.5, 0.5] |
归一化均值/方差 |
do_convert_rgb / do_rescale / do_normalize |
True |
流程开关 |
split_resolutions 默认值是在 __init__ 里动态生成的:基础网格为 19 种比例 (1,2),(1,3),…,(2,4),(2,3),(2,2),(2,1),…,(8,1),每个元素乘以 490 得到实际分辨率(如 (1,2) → [490, 980]),见 image_processing_aria.py。
预处理核心流程(image_processing_aria.py)可概括为:
- 若
split_image=True,用get_image_patches选最接近的分辨率,先保持宽高比 resize、再居中 pad 到目标分辨率、最后按max_image_size(980)切块;否则整图作为单个 crop; - 对每个 crop,按
max_image_size等比缩放(短边不足min_image_size时补足),再在右下补零 pad 到max_image_size × max_image_size; - 同步生成
pixel_mask:真实像素区域为True、补零区域为False,这是后续视觉塔构造 patch 注意力掩码、避免把 padding 当真实内容的关键; num_crops记录 batch 内单图最多 crop 数(用于占位 token 计数),所有 crop 堆叠后统一 rescale + normalize,返回BatchFeature(pixel_values, pixel_mask, num_crops)。
配套的 get_number_of_image_patches(height, width, images_kwargs) 则在不真正跑图的情况下预估一张图会被切成几个 patch,供文本侧占位 token 数量计算使用。
AriaProcessor:把「图 + 文」编排成模型输入
AriaProcessor(processing_aria.py)继承 ProcessorMixin,是图像处理器与 tokenizer 的粘合层,核心职责是决定每个图像占位符该重复多少次。
size_conversion:图像边长 → 每 crop 的视觉 token 数,默认{490: 128, 980: 256}(与projector_patch_to_query_dict的 query 数一致);replace_image_token:实际展开占位符——tokens_per_image = size_conversion[pixel_values 边长],num_image_tokens = num_crops × tokens_per_image,返回对应长度的image_token字符串(processing_aria.py);_get_num_multimodal_tokens:按图像实际尺寸调用图像处理器的get_number_of_image_patches,估算每张图的 patch 数与 token 数;- 初始化时若 tokenizer 无
pad_token,自动以unk_token兜底(processing_aria.py)。
它的默认行为在 AriaProcessorKwargs._defaults 中固化:文本侧默认 padding=False、不返回多模态 token type ids;图像侧默认 max_image_size=980, split_image=False;返回张量类型默认 PyTorch(见 processing_aria.py)。同时 unused_input_names = ["num_crops"] 会把计数用的 num_crops 排除在模型前向输入之外。
权重转换与测试佐证
- 权重转换:非 HF 格式的原始 Aria checkpoint 可通过 convert_aria_weights_to_hf.py 转换为 Transformers 可直接加载的格式;
- 测试覆盖:tests/models/aria 目录下包含三套针对性测试:
test_modeling_aria.py验证模型前向/生成与数值一致性、test_image_processing_aria.py验证切分与 mask 逻辑、test_processing_aria.py验证图文输入编排,是复现本文所述行为的现成参考; - 代码组织:以上实现文件均由模块化源文件 modular_aria.py 自动生成,文件头部声明了「直接编辑会被 CI 覆盖」——想改实现应改 modular 源文件。
API 类一览
按模型文档公开的接口,Aria 家族可用的核心类及其行为如下:
| 类 | 角色 | 关键方法 |
|---|---|---|
AriaImageProcessor |
基于 torchvision 后端的图像处理器 | preprocess(产出 pixel_values/pixel_mask/num_crops) |
AriaImageProcessorPil |
基于 Pillow 后端的图像处理器 | preprocess |
AriaProcessor |
图像处理器 + tokenizer 的多模态编排 | __call__(含 apply_chat_template 能力) |
AriaTextConfig / AriaConfig |
文本子配置 / 总配置 | — |
AriaTextModel / AriaTextForCausalLM |
纯文本 MoE 解码器 | 因果语言建模 |
AriaModel / AriaForConditionalGeneration |
多模态组合模型(视觉塔 + 投影器 + MoE 解码器) | forward、get_image_features |
其中 AriaTextForCausalLM 可独立作为纯文本模型使用,而日常图文推理应使用 AriaForConditionalGeneration(通过 AutoModelForCausalLM 自动路由加载)。
小结
Aria 在 Transformers 中的实现呈现出清晰的「复用 + 定制」哲学:视觉编码器复用 Idefics3、文本解码器继承 Llama 并替换为 MoE 前馈、多模态装配沿用 LLaVA 的占位填充范式,真正定制的核心集中在「patch 数 → query 数」的交叉注意力投影器与 grouped-GEMM 专家计算上。配合文档给出的 pipeline、AutoModel 与 torchao int4 三种上手路径,以及 AriaProcessor/AriaImageProcessor 对任意分辨率输入的自适应处理,开发者可以快速在图像问答、多图对比理解等场景中部署这一多模态 MoE 模型,并能从 modeling_aria.py、configuration_aria.py 与 image_processing_aria.py 中随时回溯每个参数与模块的源码级依据。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00