1.transformers代码
from transformers import AutoModelForCausalLM
path = "./models/Qwen2.5-0.5B-Instruct"
model = AutoModelForCausalLM.from_pretrained(path)
2.transformers/PreTrainedModel 机制
2.1 定位
PreTrainedModel 是”模型骨架的标准化包装盒”,定义了”模型长什么样、怎么存、怎么取”的统一规范,但不包含任何具体网络结构的实现。作为Hugging Face 生态的”模型标准化协议”——它通过 config.json(结构蓝图)+ state_dict(权重数据)两层松耦合设计,实现了”同一份权重,用任何框架、在任何上下文、从任何来源加载”的终极目标。
PreTrainedModel (抽象基类)
│
├── 提供统一的 保存/加载/配置管理 接口
│ ├── .save_pretrained(save_directory)
│ ├── .from_pretrained(model_path)
│ └── .save_to_cache() / .from_cache()
│
├── 提供统一的 配置管理 接口
│ ├── .config → PretrainedConfig 实例
│ └── .save_config(config, save_directory)
│
├── 提供统一的 设备/精度管理 接口
│ ├── .to(device) / .cuda() / .cpu() / .half() / .bfloat16()
│ └── .float() / .double()
│
├── 提供统一的 梯度检查点 / 编译 支持
│ ├── .gradient_checkpointing_enable()
│ └── .compile()
│
└── 派生为各种具体模型类
├── PreTrainedModel → Qwen2Model (基座模型,无 LM Head)
│ └── Qwen2ForCausalLM (加了 LM Head)
│ └── Qwen2ForSequenceClassification (加分类头)
├── PreTrainedModel → BertModel (基座)
│ └── BertForSequenceClassification
│ └── BertForQuestionAnswering
└── PreTrainedModel → LlamaForCausalLM
└── MistralForCausalLM
└── Qwen2ForCausalLM
2.2 模型目录结构
典型目录结构
(AI) peile.duan@U-7K6V5HCV-0153 AI % ls models/Qwen3.8-27B-4bit/
README.md model-00001-of-00003.safetensors processor_config.json
chat_template.jinja model-00002-of-00003.safetensors tokenizer.json
config.json model-00003-of-00003.safetensors tokenizer_config.json
configuration.json model.safetensors.index.json video_preprocessor_config.json
generation_config.json preprocessor_config.json vocab.json
from_pretrained 能精准还原一个模型,依赖两大文件的协同存储。PreTrainedModel 的职责就是:读取 config.json 用代码把骨架搭起来,再把 model.safetensors 里的肉填进去。
2.3 结构描述文件 config.json
(AI) peile.duan@U-7K6V5HCV-0153 AI % cat models/Qwen3.8-27B-4bit/config.json
{
"architectures": [
"Qwen3_5ForConditionalGeneration"
],
"do_sample": true,
"eos_token_id": [
248046,
248044
],
"generation_config": {
"bos_token_id": 248044,
"do_sample": true,
"eos_token_id": [
248046,
248044
],
"pad_token_id": 248044,
"temperature": 1.0,
"top_k": 20,
"top_p": 0.95
},
"image_token_id": 248056,
"language_model_only": false,
"model_type": "qwen3_5",
"quantization": {
"group_size": 64,
"bits": 4,
"mode": "affine"
},
"quantization_config": {
"group_size": 64,
"bits": 4,
"mode": "affine"
},
"temperature": 1.0,
"text_config": {
"attention_bias": false,
"attention_dropout": 0.0,
"attn_output_gate": true,
"bos_token_id": 248044,
"dtype": "bfloat16",
"eos_token_id": 248044,
"full_attention_interval": 4,
"head_dim": 256,
"hidden_act": "silu",
"hidden_size": 5120,
"initializer_range": 0.02,
"intermediate_size": 17408,
"layer_types": [
"linear_attention",
"linear_attention",
"linear_attention",
"linear_attention",
......
"full_attention"
],
"linear_conv_kernel_dim": 4,
"linear_key_head_dim": 128,
"linear_num_key_heads": 16,
"linear_num_value_heads": 48,
"linear_value_head_dim": 128,
"mamba_ssm_dtype": "float32",
"max_position_embeddings": 262144,
"model_type": "qwen3_5_text",
"mtp_num_hidden_layers": 1,
"mtp_use_dedicated_embeddings": false,
"num_attention_heads": 24,
"num_hidden_layers": 64,
"num_key_value_heads": 4,
"output_gate_type": "swish",
"pad_token_id": null,
"partial_rotary_factor": 0.25,
"rms_norm_eps": 1e-06,
"rope_parameters": {
"mrope_interleaved": true,
"mrope_section": [
11,
11,
10
],
"partial_rotary_factor": 0.25,
"rope_theta": 10000000,
"rope_type": "default"
},
"tie_word_embeddings": false,
"use_cache": true,
"vocab_size": 248320
},
"tie_word_embeddings": false,
"top_k": 20,
"top_p": 0.95,
"transformers_version": "5.8.0.dev0",
"video_token_id": 248057,
"vision_config": {
"deepstack_visual_indexes": [],
"depth": 27,
"hidden_act": "gelu_pytorch_tanh",
"hidden_size": 1152,
"in_channels": 3,
"initializer_range": 0.02,
"intermediate_size": 4304,
"model_type": "qwen3_5",
"num_heads": 16,
"num_position_embeddings": 2304,
"out_hidden_size": 5120,
"patch_size": 16,
"spatial_merge_size": 2,
"temporal_patch_size": 2
},
"vision_end_token_id": 248054,
"vision_start_token_id": 248053
}%
2.3.1 顶级架构声明
|
字段 |
值 |
含义 |
|
|
|
顶层模型类名。 |
|
|
|
HuggingFace 路由机制的匹配键。告诉 去哪个映射表找对应的 Python 类 |
|
|
|
极新。目前主流 transformers 是 4.x,5.8.0.dev0 意味着需要未来版本/自编译版本才能原生支持 |
|
|
|
声明此模型不是纯语言模型,必须加载视觉分支 |
|
/ / / |
见 generation_config |
顶层的冗余采样参数,供旧版库读取 |
2.3.2 text_config:语言模型核心架构
这是整个配置中最关键、信息量最大的部分。它揭示了一个非常前卫的混合注意力架构。
1.基础维度
|
字段 |
值 |
含义 |
|
|
|
词表大小 ≈ 248K。超大词表(LLaMA-3 是 128K),支持多语言高压缩率 |
|
|
|
隐藏层维度 = 5120(约 5B 参数级别的标准宽度,对应 DeepSeek-V2 级别) |
|
|
|
FFN 中间维度(SwiGLU: |
|
|
|
总层数 64 层。比 Qwen2.5-72B 的 80 层少,但比 27B 级别(如 Gemma-2-27B 的 46 层)深得多 |
|
|
|
注意力头维度 256。异常大(传统 128),配合 |
2.GQA(分组查询注意力)配置
|
字段 |
值 |
含义 |
|
|
|
Query 头数量 = 24 |
|
|
|
KV 头数量 = 4 |
|
GQA 比率 |
6:1 |
每 6 个 Query 头共享 1 组 KV。这是 DeepSeek-V3(8:1)和 LLaMA-3-70B(8:1)级别的激进压缩 |
3.layer_types:混合注意力层(最核心创新)
"layer_types": [
"linear_attention", ×N 层
...
"full_attention" 若干层
]
|
Layer 类型 |
功能 |
特点 |
|
|
通过线性注意力(类似Mamba/Gated Linear Attention)处理长序列 |
时间复杂度 O(L),显存不随序列长度增长 |
|
|
标准 Softmax 自注意力 |
时间/显存 O(L²),但表达能力更强 |
full_attention_interval: 4 的含义:
layer_types 的实际排布:
第 0~2 层:linear_attention
第 3 层: full_attention ← 每 4 层插入 1 个全注意力
第 4~6 层:linear_attention
第 7 层: full_attention
...
- 大部分层用线性注意力处理 256K 长上下文,计算量可控
- 每 4 层插入一个全注意力层,让模型定期”全局回顾”——这是 Qwen3.5 架构的首创策略
4.线性注意力/SSM 细节
|
字段 |
值 |
含义 |
|
|
|
线性注意力中的卷积核维度(类似 Mamba 的卷积 short-conv) |
|
|
|
线性注意力 K 的维度 |
|
|
|
线性注意力的 K 头数量 |
|
|
|
线性注意力 V 的维度 |
|
|
|
线性注意力的 V 头数量 |
|
|
|
SSM 内部计算用 FP32 保持稳定(外部 BF16) |
解读:KV 头数不对称(16K vs 48V),说明这是 GLA(Gated Linear Attention) 或自研变体,而非原始 Mamba。linear_conv_kernel_dim=4 说明引入了局部卷积增强位置感知。
5.RoPE 位置编码
|
字段 |
值 |
含义 |
|
|
|
RoPE 基频 = 10M(远超 LLaMA-3 的 500K),支持更长上下文 |
|
|
|
部分旋转:只旋转 25% 的 head_dim,其余 75% 不旋转。这是 DeepSeek-V3 的优化,保留绝对位置信息 |
|
|
|
标准 RoPE(无 NTK/YaRN 变体) |
|
|
|
多模态 RoPE(MRoPE)交织模式,视觉 token 使用 3D 空间坐标旋转 |
|
|
|
MRoPE 将 head_dim=256 切分为三段: ,对应时间/高度/宽度三个维度的位置编码 |
🧠 架构提示:mrope_interleaved + partial_rotary_factor + rope_theta=1e+7 这套组合拳,是典型的 LongRoPE / NTK-aware RoPE 的现代实现。它使得模型既能处理 256K 的超长上下文,又避免了长文本中的位置编码混乱(token 间的关系衰减过慢)。
6.其他关键机制
|
字段 |
值 |
含义 |
|
|
|
在注意力输出后增加一个门控机制(类似 DeepSeek-V3 的 head gate),用 Swish 函数调节信息流 |
|
|
|
上述门控的激活函数 = Swish(SiLU) |
|
|
|
MTP(Multi-Token Prediction):额外的 1 层预测头,用于同时预测下一个和再下一个 token(DeepSeek-V3 的训练技巧) |
|
|
|
MTP 不额外学习独立的 embedding,复用主 embedding |
|
|
|
不绑定权重:输入 embedding 和输出 LM Head 独立 |
|
|
|
最大上下文长度 = 256K tokens |
|
|
|
RMSNorm 的数值稳定常数 |
|
|
|
推理时使用 KV Cache |
2.3.3 vision_config:视觉编码器
|
字段 |
值 |
含义 |
|
|
|
视觉编码器也标记为 qwen3_5 |
|
|
|
图像 patch 大小 = 16×16 像素 |
|
|
|
RGB 三通道 |
|
|
|
ViT 内部隐藏维度 |
|
|
|
ViT FFN 中间维度 |
|
|
|
ViT 共 27 层 |
|
|
|
ViT 注意力头数 |
|
|
|
ViT 最大 patch 位置数(对应 2304 个 patch = 约 768×768 分辨率) |
|
|
|
投影输出维度 = 语言模型的 hidden_size。通过 MLP Projector 将 1152 映射到 5120 |
|
|
|
空间合并:2×2 patch 合并为 1 个 token(降低视觉 token 数量) |
|
|
|
视频处理:时间维度每 2 帧取 1 帧 |
|
|
|
空数组:不使用 DeepStack 视觉融合(Qwen2-VL 的机制,将视觉特征注入不同 LLM 层) |
2.3.4 量化配置(4-bit)
"quantization_config": {
"group_size": 64,
"bits": 4,
"mode": "affine"
}
含义:明示该模型不是 FP16 还是 FP32,而是经过 4-bit 量化的。
|
字段 |
值 |
含义 |
|
|
|
权重用 INT4 存储 |
|
|
|
每 64 个权重共享一个 scale/zero-point |
|
|
|
仿射量化: |
两个重复字段 quantization 和 quantization_config:
quantization:顶层冗余声明,供 MLX/llama.cpp 等非 HF 引擎读取quantization_config:HuggingFace bitsandbytes/AutoGPTQ 的标准格式
2.3.5 generation_config:推理采样参数
这是生成器默认值的清单。当你只写 model.generate(**inputs) 而不加任何额外参数时,它会拿这里的值当作 baseline。
|
字段 |
值 |
含义 |
|
/ / |
|
复用同一个 token 作为 BOS/EOS/PAD(ChatML 语系常见做法) |
|
|
|
两个 EOS: |
|
|
|
温度 1.0,不做温度缩放 |
|
|
|
只保留概率最高的 20 个 token 采样 |
|
|
|
累积概率 95% 截断 |
|
|
|
随机采样(关闭则贪心) |
注意:temperature=1.0 + top_k=20 + top_p=0.95 组合意味着采样偏保守(top_k 限制了候选池)。
2.3.6 多模态 token 映射(桥梁配置
|
字段 |
数值 |
含义 |
|
|
248056 |
告诉 tokenizer:看到 ID 248056 时,这是一个图像 patch 占位符。 |
|
|
248057 |
视频占位符。 |
|
|
248053 |
视觉序列开始标记(在 text 中插入 |
|
|
248054 |
视觉序列结束标记。 |
2.3.7 多模态工作流程
- Tokenizer 渲染对话:
<|im_start|>user <tokens_vision_start> <|image_pad|> ... 描述这张图<|im_end|> - Tokenizer 识别到
image_token_id(248056)。 - LLM 在生成序列后,前向传播。Vision Encoder (
vision_config) 计算出 27 层的图像特征,形状是(batch, num_patches, 1152)。 - Embedding 替换: LLM 装配器(通常是 Processor 的一部分,而非
PreTrainedModel本身)在运行前知道:“喂,这个词表 ID 248056 的位置,应该用 Vision Encoder 输出的 1152 维向量去替换掉文本的 Embedding。” - 输入 LLM (
text_config)。文本 Embedding 和 Visual Embedding 组装成一个长序列,包含所有文本 token + 图像 token + 文档。 - Attention。所有这些 token 在
text_config的linear_attention或full_attention机制中被融合在一起。
2.3.8 架构总结
Qwen3.8-27B-4bit 架构总览
│
├── 输入层
│ ├── Tokenizer (248K 词表, ChatML 格式, 多模态占位)
│ └── Embedding (5120 维)
│
├── 视觉分支
│ ├── ViT Encoder (27层, 1152维, patch=16)
│ ├── Spatial Merge (2×2)
│ └── MLP Projector (1152 → 5120)
│
├── 语言模型主体 (64层混合注意力)
│ ├── Layer 0,1,2: Linear Attention (Mamba-like, GLA)
│ ├── Layer 3: Full Attention (GQA 24/4, head_dim=256)
│ ├── Layer 4,5,6: Linear Attention
│ ├── Layer 7: Full Attention
│ └── ... (每4层1个全注意力)
│
├── 位置编码
│ ├── RoPE θ=10M, partial_rotary=0.25
│ ├── Full Attention 层用标准 RoPE
│ └── 视觉 token 用 MRoPE (3D 交织)
│
├── 输出层
│ ├── LM Head (不绑定权重)
│ └── MTP 额外预测头 (1层)
│
└── 量化
└── INT4, group_size=64, 仿射量化
核心定位:这是一个面向 256K 长上下文多模态场景的 27B 模型。通过混合注意力(线性+全注意力)平衡长序列效率与表达能力,通过 MRoPE 处理视觉-文本位置对齐,通过激进 GQA (6:1) 和 4-bit 量化控制部署成本。
2.4 权重文件(.safetensors 或 .bin)
作用:存储所有可学习参数的实际数值。
两种格式对比:
|
维度 |
(PyTorch Pickle) |
(Safetensors) |
|
安全性 |
⚠️ 任意代码执行风险 |
✅ 纯数据,无代码执行 |
|
加载速度 |
慢(反序列化无索引) |
快3-5倍(mmap + zero-copy) |
|
分片支持 |
手动 |
自动 |
|
跨框架 |
仅 PyTorch |
PyTorch / TF / JAX / MLX 通用 |
2.4.1 SafeTensors格式介绍
https://github.com/safetensors/safetensors
SafeTensors 是 HuggingFace 在 2022 年提出的一种张量序列化标准格式。它不是新的神经网络架构,而是一种安全、高效、零拷贝的模型权重存储协议,现已成为 LLM 时代的通用标准。
SafeTensors 当前已成为AI 基础设施领域的”HTTP 协议”——简单、安全、通用、高性能。它把 PyTorch 繁琐复杂的模型加载过程,降维成 3 步:读 8 字节头 → 解析 JSON → mmap 数据。配合 config.json 中的 Python 代码骨架,构成了当前 LLM 生态的存储与传输标准。
从 OpenAI 的 GLM 到 DeepSeek 的 FP8 训练,从 3090 上的 7B 量化版到超大规模集群的 405B MoE,这个不起眼的 JSON 头格式正在背后支撑着整个大模型时代的 reproducibility。
2.4.2 设计目标
|
目标 |
实现手段 |
|
安全性 |
完全移除 Python 对象机制,只存纯数值 |
|
零拷贝 |
直接内存映射(mmap)加载张量,不建 Python 对象 |
|
跨框架 |
与语言无关的二进制规范,Rust/C++/Go 均可读写 |
|
高性能 |
加载速度比 PyTorch bin 快 10-20 倍 |
|
精度保持 |
支持任意 dtype(BF16/FP16/INT4),无损保存 |
2.4.3 格式规范:文件内部长什么样?
SafeTensors 是一个极简的二进制容器,与通常的认知不同——它不包含任何模型架构信息,只保存张量数据和元数据。模型存在于两个平行世界:
世界一:模型 "骨架"(Python 代码 + JSON 配置)
└── config.json: {"model_type":"qwen2", "hidden_size":896, ...}
└── qwen2.py: class Qwen2ForCausalLM(nn.Module) { ... }
└── 这是有逻辑、可执行的代码
世界二:模型"肌肉"(纯数值矩阵)
└── model.safetensors: {"model.embed_tokens.weight": [151936×896], ...}
└── 这是一堆没有灵魂的浮点数
具体文件布局如下:
SafeTensors 文件布局:
┌──────────────────────────────────────────┐
│ Header 长度 (8字节, 大端序 uint64) │ ← 告诉你后面有多少字节是 JSON Header
├──────────────────────────────────────────┤
│ JSON Header (UTF-8, 变长) │ ← 所有张量的名称、形状、dtype、偏移
├──────────────────────────────────────────┤
│ 对齐的空余空间 (可选) │ ← 将后续数据对齐到内存页边界
├──────────────────────────────────────────┤
│ 张量数值数据块 (Raw binary) │ ← 按 header 声明的偏移顺序存储
├── ... 更多 tensor ... │
└──────────────────────────────────────────┘
2.4.4 JSON Header 详解
{
"metadata": { "__version__": "4" },
"model.embed_tokens.weight": {
"dtype": "BF16",
"shape": [151936, 896],
"data_offsets": [0, 272322528]
},
"model.layers.0.self_attn.q_proj.weight": {
"dtype": "BF16",
"shape": [896, 896],
"data_offsets": [272322528, 273374976]
},
...
}
关键字段含义:
|
字段 |
类型 |
含义 |
|
|
字符串(如 |
数据类型,HF 扩展的 NumPy 类型 |
|
|
|
张量各维大小 |
|
|
|
该张量在整个文件后面的绝对字节偏移区间 |
核心设计:
- 字典键:在 safetensors 的 header 里,张量名本身就是 JSON 对象的 key,而不是作为 value 里的某个字段保存。
- 地址与数据解耦:元数据只记录偏移量,实际数据在文件后半段连续存放。
- dtype 字符串规范:SafeTensors 定义了严格的对照表(BF16 ↔
bfloat16,I64 ↔ torch.int64 等),避免 Python-ism。
2.4.5 大文件分片
大型模型的分片方式(例如 Qwen2.5-72B):
├── model-00001-of-00009.safetensors (第 1-7 层)
├── model-00002-of-00009.safetensors (第 8-14 层)
├── model-00003-of-00009.safetensors
├── ...
└── model.safetensors.index.json ← 全局分片索引
Index 文件结构:
{
"metadata": {"total_size": 144897451008},
"weight_map": {
"model.embed_tokens.weight": "model-00001-of-00009.safetensors",
"model.layers.0.self_attn.q_proj.weight": "model-00001-of-00009.safetensors",
"model.layers.24.self_attn.v_proj.weight": "model-00002-of-00009.safetensors",
...
}
}
加载逻辑:HuggingFace 加载器读 index.json,找到每个 tensor 在哪个分片文件中,然后按需 mmap 对应文件。
3.PreTrainedModel初始化
PreTrainedModel 是 Hugging Face transformers 库的核心基类,所有模型类(如 Qwen2ForCausalLM、BertModel、LlamaForCausalLM)都继承自它。它是整个生态”一次保存、随处加载“能力的支柱。
model = AutoModelForCausalLM.from_pretrained(path)
该函数对应的完整调用链如下所示:
AutoModelForCausalLM.from_pretrained("/path/to/qwen2")
│
│ ┌──── 第一层: 工厂分发 ──────────────────────────────────┐
│ │ [modeling_auto.py:2187] AutoModelForCausalLM │
│ │ → super().from_pretrained() │
│ │ │
│ │ [auto_factory.py:261] _BaseAutoModelClass.from_pretrained│
│ │ ├─ A. 提取参数 (config, trust_remote_code, hub_kwargs) │
│ │ ├─ B. cached_file(path, "config.json") → commit_hash │
│ │ ├─ C. PEFT 适配器处理 │
│ │ ├─ D. AutoConfig.from_pretrained(path) │
│ │ │ → 读 config.json → model_type="qwen2" │
│ │ │ → Qwen2Config 实例 │
│ │ ├─ E. 判断: has_local_code=True (在 MODEL_MAPPING 中) │
│ │ └─ F. _get_model_class(config, MODEL_FOR_CAUSAL_LM_MAPPING)│
│ │ │ [auto_factory.py:178] │
│ │ ├─ model_mapping[Qwen2Config] │
│ │ │ → _LazyAutoMapping.__getitem__ │
│ │ │ ├─ _reverse_config_mapping["Qwen2Config"] → "qwen2"│
│ │ │ ├─ _model_mapping["qwen2"] → "Qwen2ForCausalLM"│
│ │ │ └─ _load_attr_from_module("qwen2", "Qwen2ForCausalLM")│
│ │ │ ├─ model_type_to_module_name → "qwen2" │
│ │ │ ├─ importlib.import_module(".qwen2", "transformers.models")│
│ │ │ └─ getattr → Qwen2ForCausalLM 类 │
│ │ │ │
│ │ └─ model_class.from_pretrained(path, config=config)│
│ └──────────────────────────────────────────────────────────┘
│
│ ┌──── 第二层: PreTrainedModel.from_pretrained ────────────┐
│ │ [modeling_utils.py:3859] │
│ │ │
│ │ 1. 参数提取: dtype, device_map, quantization_config, ...│
│ │ torch_dtype → dtype; dtype=None → "auto" │
│ │ │
│ │ 2. check_and_set_device_map(device_map) │
│ │ │
│ │ 3. (如未传入) config_class.from_pretrained() │
│ │ │
│ │ 4. get_hf_quantizer(config, quantization_config, ...) │
│ │ → hf_quantizer (或 None) │
│ │ → validate_environment() + update_device_map() │
│ │ │
│ │ 5. _get_resolved_checkpoint_files(path, ...) │
│ │ → checkpoint_files = ["model.safetensors"] (可能多个) │
│ │ → sharded_metadata (分片索引) │
│ │ │
│ │ 6. _get_dtype("auto", checkpoint_files, config, ...) │
│ │ → config.dtype / sharded_metadata / state_dict 推断 │
│ │ │
│ │ 7. ★ 模型实例化 (meta device) ★ │
│ │ get_init_context(dtype, ...) → 上下文管理器列表: │
│ │ ├─ local_torch_dtype(dtype) │
│ │ ├─ init.no_tie_weights() │
│ │ ├─ torch.device("meta") ← 零内存初始化 │
│ │ └─ init.meta_device_safe_creation_ops() │
│ │ │
│ │ with ContextManagers(init_context): │
│ │ model = cls(config) ← Qwen2ForCausalLM(config) │
│ │ if hf_quantizer: │
│ │ hf_quantizer.preprocess_model(model, ...) │
│ │ (替换 nn.Linear → Linear4bit 等) │
│ │ │
│ │ 8. get_model_conversion_mapping(model, key_mapping, ...)│
│ │ → WeightRenaming / WeightConverter 列表 │
│ │ │
│ │ 9. ★ 权重加载 ★ │
│ │ _load_pretrained_model(model, state_dict, │
│ │ checkpoint_files, load_config) │
│ │ [modeling_utils.py:4391] │
│ │ ├─ expected_keys = model.state_dict().keys() │
│ │ ├─ caching_allocator_warmup() ← GPU 内存预热 │
│ │ ├─ 加载 state_dict: │
│ │ │ safe_open(file) → get_slice(k) ← 惰性切片 │
│ │ └─ convert_and_load_state_dict_in_model() │
│ │ [core_model_loading.py:1465] │
│ │ │ │
│ │ ├─ 阶段1: 收集 (遍历 state_dict) │
│ │ │ ├─ rename_source_key() → checkpoint key 映射 │
│ │ │ ├─ 判断量化需求 │
│ │ │ ├─ 确定 dtype │
│ │ │ ├─ TP/DTensor 分片 │
│ │ │ └─ spawn_materialize() → Future/Callable │
│ │ │ (异步线程池物化 tensor) │
│ │ │ │
│ │ └─ 阶段2: 转换和写入 │
│ │ ├─ mapping.convert() → 执行 WeightTransform │
│ │ │ (Chunk/Concatenate/量化反序列化...) │
│ │ └─ set_param_for_module() │
│ │ ├─ param._is_hf_initialized = True │
│ │ └─ setattr(module, param_name, param_value)│
│ │ │
│ │ 10. 后处理: _finalize_model_loading() │
│ │ ├─ mark_tied_weights_as_initialized() │
│ │ ├─ _move_missing_keys_from_meta_to_device() │
│ │ ├─ _initialize_missing_keys() │
│ │ ├─ tie_weights() │
│ │ └─ _adjust_missing_and_unexpected_keys() │
│ │ │
│ │ 11. model.eval() │
│ │ 12. set_use_kernels() (内核替换) │
│ │ 13. adjust_generation_fn() (如果 can_generate) │
│ │ 14. accelerate_dispatch() (多设备 hook, 如果需要) │
│ │ 15. hf_quantizer.postprocess_model() (量化后处理) │
│ └─────────────────────────────────────────────────────────┘
│
= 返回 model (Qwen2ForCausalLM 实例,权重已加载)
model.device → 实际设备
model.config → Qwen2Config
model.can_generate() → True
3.1 AutoModelForCausalLM类定义
类定义位于 modeling_auto.py 第 2187-2199 行。AutoModelForCausalLM 继承自 _BaseAutoModelClass,本身不包含任何加载逻辑:
class AutoModelForCausalLM(_BaseAutoModelClass):
_model_mapping = MODEL_FOR_CAUSAL_LM_MAPPING
@classmethod
def from_pretrained(cls, pretrained_model_name_or_path, *model_args, **kwargs):
return super().from_pretrained(pretrained_model_name_or_path, *model_args, **kwargs)
3.1.1 _BaseAutoModelClass.from_pretrained
位于 auto_factory.py 第 261-408 行。这是工厂模式的核心,核心职责是类型推断——它不做任何权重加载,只负责”根据 config 找到正确的模型类,然后把控制权交给该类的 from_pretrained“。真正的模型实例化和权重加载发生在下一层 PreTrainedModel.from_pretrained 中。
from_pretrained(path, **kwargs)
│
├─ A. 参数提取 → config, trust_remote_code, hub_kwargs, commit_hash
├─ B. 获取 commit_hash → cached_file("config.json") → extract_commit_hash
├─ C. PEFT 适配器 → find_adapter_config_file → base_model_name_or_path
├─ D. 加载配置 → AutoConfig.from_pretrained → config.json → model_type → Config 子类
├─ E. 判断代码来源
│ ├─ has_remote_code? (config.auto_map 中有 cls.__name__)
│ ├─ has_local_code? (type(config) in _model_mapping)
│ └─ resolve_trust_remote_code()
│
└─ F. 委托
├─ 远程代码 → get_class_from_dynamic_module → register → model_class.from_pretrained
└─ 本地代码 → _get_model_class(config, mapping)
│
├─ mapping[type(config)]
│ → _LazyAutoMapping.__getitem__
│ ├─ reverse_config_mapping → model_type
│ ├─ model_mapping → model_name
│ └─ _load_attr_from_module → importlib + getattr
│
├─ tuple/list? → config.architectures 精确匹配
└─ model_class.from_pretrained(path, config=config)
3.1.2 _get_model_class 函数
位于 auto_factory.py 第 178-191 行:
def _get_model_class(config, model_mapping):
supported_models = model_mapping[type(config)] # 触发 _LazyAutoMapping.__getitem__
if not isinstance(supported_models, (list, tuple)):
return supported_models
name_to_model = {model.__name__: model for model in supported_models}
architectures = getattr(config, "architectures", [])
for arch in architectures:
if arch in name_to_model:
return name_to_model[arch]
return supported_models[0]
与 AutoTokenizer 的关键区别:模型映射的值可以是 tuple/list(一个 Config 类对应多个模型类,如 Qwen2Config 可能对应 Qwen2ForCausalLM 和 Qwen2ForSequenceClassification)。此时用 config.architectures 精确匹配,无匹配时取第一个(默认)。
3.1.3 映射表与懒加载
MODEL_FOR_CAUSAL_LM_MAPPING_NAMES(modeling_auto.py 第 678 行起)是 OrderedDict,典型条目:
("qwen2", "Qwen2ForCausalLM"),
("qwen3", "Qwen3ForCausalLM"),
("llama", "LlamaForCausalLM"),
("gemma", "GemmaForCausalLM"),
MODEL_FOR_CAUSAL_LM_MAPPING = _LazyAutoMapping(CONFIG_MAPPING_NAMES, MODEL_FOR_CAUSAL_LM_MAPPING_NAMES)(第 2032 行),使用与 AutoTokenizer 完全相同的 _LazyAutoMapping 机制:
Qwen2Config → _reverse_config_mapping → "qwen2"
→ _model_mapping → "Qwen2ForCausalLM"
→ model_type_to_module_name("qwen2") → "qwen2"
→ importlib.import_module(".qwen2", "transformers.models")
→ getattr(module, "Qwen2ForCausalLM") → 具体模型类
3.2 权重加载层
选定具体模型类后(如 Qwen2ForCausalLM),调用其继承的 PreTrainedModel.from_pretrained,位于 modeling_utils.py 第 3859-4388 行。这是最复杂的部分,具体调用链如下:
PreTrainedModel.from_pretrained(path, *model_args, config=config, **kwargs)
│ [modeling_utils.py:3859]
│
├─ 1. 参数提取 [:4101-4131]
│ pop: dtype, torch_dtype(→dtype), device_map, quantization_config,
│ offload_folder, gguf_file, key_mapping, use_kernels, ...
│ 移除废弃参数: low_cpu_mem_usage, _fast_init, from_tf, from_flax
│ dtype 为 None → 设为 "auto"
│
├─ 2. 适配器加载 + device_map 规范化 [:4176-4181]
│ maybe_load_adapters()
│ check_and_set_device_map(device_map) # → {"": device} 或保留 "auto"
│
├─ 3. 加载配置 (如果上层未传入) [:4188-4210]
│ config_class.from_pretrained(path, return_unused_kwargs=True)
│
├─ 4. 量化器初始化 [:4225-4227]
│ get_hf_quantizer(config, quantization_config, device_map, ...)
│ ├─ 读 config.quantization_config (预量化) 或用户传入 (即时量化)
│ ├─ AutoHfQuantizer.from_config() → hf_quantizer
│ ├─ validate_environment() + update_device_map() + update_tp_plan()
│ └─ 返回 hf_quantizer (或 None)
│
├─ 5. 解析权重文件 [:4248-4258]
│ _get_resolved_checkpoint_files(path, variant, ...)
│ 优先级: safetensors > .bin > .gguf, 支持分片索引
│ → checkpoint_files (文件路径列表), sharded_metadata
│
├─ 6. 确定 dtype [:4263-4265]
│ _get_dtype(dtype, checkpoint_files, config, ...)
│ "auto" → config.dtype / sharded_metadata / state_dict 推断
│ 量化器: hf_quantizer.update_dtype(dtype)
│
├─ 7. ★ 模型实例化 (meta device, 零内存) ★ [:4301-4315]
│ init_context = cls.get_init_context(dtype, is_quantized, ...)
│ │ [modeling_utils.py:3769]
│ │ ├─ local_torch_dtype(dtype) # 临时设 torch 默认 dtype
│ │ ├─ init.no_tie_weights() # 禁止 __init__ 时 tie weights
│ │ ├─ torch.device("meta") # ← meta 设备, 不分配内存
│ │ └─ init.meta_device_safe_creation_ops()
│ │
│ with ContextManagers(init_context):
│ model = cls(config, *model_args, **model_kwargs)
│ │ → 如 Qwen2ForCausalLM(config) 在 meta 上构建网络结构
│ │
│ if hf_quantizer is not None:
│ hf_quantizer.preprocess_model(model, ...)
│ # 替换 nn.Linear → Linear4bit 等 (不碰权重值)
│
├─ 8. 获取权重转换映射 [:4326-4330]
│ weight_conversions = get_model_conversion_mapping(
│ model, key_mapping, hf_quantizer
│ )
│ → WeightRenaming (键名重命名) / WeightConverter (tensor 变换)
│
├─ 9. ★ 权重加载 ★ [:4331-4348]
│ load_config = LoadStateDictConfig(
│ device_map=device_map, dtype=dtype, dtype_plan=...,
│ hf_quantizer=hf_quantizer, weight_mapping=weight_conversions, ...
│ )
│ loading_info, disk_offload_index = cls._load_pretrained_model(
│ model, state_dict, checkpoint_files, load_config
│ )
│ │ [modeling_utils.py:4391-4494]
│ │
│ ├─ expected_keys = model.state_dict().keys() # meta 模型参数名
│ ├─ 磁盘 offload 准备 (如果 device_map 含 "disk")
│ ├─ caching_allocator_warmup() # GPU 一次性 cudaMalloc 预热
│ │
│ ├─ 加载 state_dict:
│ │ ├─ safetensors: safe_open() + get_slice(k) ← 惰性切片
│ │ ├─ .bin: torch.load() 全量加载
│ │ └─ 分片: 逐文件 safe_open() 合并
│ │
│ └─ convert_and_load_state_dict_in_model(model, state_dict, load_config, ...)
│ [core_model_loading.py:1465-1756]
│ │
│ ├─ 阶段 1: 收集 (遍历 state_dict)
│ │ ├─ rename_source_key() → checkpoint key → 模型 key
│ │ ├─ 判断量化需求: hf_quantizer.param_needs_quantization()
│ │ ├─ 确定 dtype: dtype_plan / empty_param.dtype
│ │ ├─ TP/DTensor 分片处理
│ │ └─ spawn_materialize() → 异步线程池物化 tensor (Future)
│ │
│ └─ 阶段 2: 转换和写入
│ ├─ mapping.convert() → 执行 WeightTransform
│ │ (Chunk / Concatenate / 量化反序列化 ...)
│ └─ set_param_for_module()
│ ├─ param._is_hf_initialized = True # 阻止重新初始化
│ └─ setattr(module, param_name, param_value) → 写入模型
│
├─ 10. 后处理: _finalize_model_loading() [:4349]
│ [modeling_utils.py:4497-4533]
│ ├─ mark_tied_weights_as_initialized()
│ ├─ _move_missing_keys_from_meta_to_device()
│ ├─ _initialize_missing_keys() # 用正确分布初始化缺失权重
│ ├─ tie_weights() # 绑定共享权重 (如 embedding ↔ lm_head)
│ └─ _adjust_missing_and_unexpected_keys()
│
├─ 11. model.eval() # 切换评估模式 [:4358]
├─ 12. set_use_kernels(use_kernels, kernel_config) [:4362]
├─ 13. adjust_generation_fn() (如果 can_generate) [:4366]
├─ 14. accelerate_dispatch() (多设备时安装 forward hooks)[:4370]
├─ 15. hf_quantizer.postprocess_model() (量化后处理) [:4372]
│
└─ 返回 model
核心要点:模型先在 meta device 零内存初始化(第 7 步),然后通过惰性 safetensors 切片 + 异步线程池逐参数加载真实权重(第 9 步),最后通过 tie_weights 和 missing key 初始化完成收尾(第 10 步)。整个过程的关键设计是“先建结构(meta),再填数据(权重加载),最后修补(finalize)”的三阶段模式。
3.3 关键设计决策
- 懒加载映射:
_LazyAutoMapping通过importlib.import_module按需导入,避免 200+ 模型模块拖慢import transformers。模块缓存避免重复导入。与 AutoTokenizer 使用完全相同的机制。 - Meta Device 初始化 + 分离式权重加载:V5 核心架构变化。模型先在
torch.device("meta")上零内存初始化,然后通过convert_and_load_state_dict_in_model逐参数加载真实权重。替代了 V4 的low_cpu_mem_usage参数(已废弃)。 - WeightTransform 声明式权重转换:V5 引入
WeightRenaming和WeightConverter,将权重转换从硬编码的if-else变为声明式模式匹配系统。支持 Chunk/Concatenate/MergeModulelist/TorchaoDeserialize 等操作,使不同 checkpoint 格式可统一加载。 - 惰性 safetensors 切片 + 异步加载:使用
get_slice(k)而非get_tensor(k),仅在spawn_materialize阶段才读取 tensor 数据。结合ThreadPoolExecutor异步加载,极大降低峰值内存和加载时间。 - GPU 缓存分配器预热:加载前根据参数总量一次性
cudaMalloc,避免逐参数分配导致的内存碎片。可将 70B 模型加载从数分钟降至约 13 秒。 - 统一量化集成:V5 废弃
load_in_4bit/load_in_8bit/bnb_config,统一通过quantization_config传入。三步集成:preprocess_model(替换模块)→ 即时量化(权重加载时)→postprocess_model(清理)。量化器还能调整 device_map 和 TP plan。 _is_hf_initialized标记:加载的参数设置此标记,阻止模型__init__中的_init_weights重新初始化已加载的权重——避免覆盖正确值。auto_class_update装饰器:用copy_func复制方法,为每个 Auto 类生成独立的文档字符串(包含支持的模型列表),但共享同一份代码逻辑。
4.模型特化实现
模型特化实现保存在models目录下,以qwen3_5为例,下面包含如下文件
(AI) peile.duan@U-7K6V5HCV-0153 transformers % ls models/qwen3_5
__init__.py configuration_qwen3_5.py modular_qwen3_5.py
__pycache__ modeling_qwen3_5.py
qwen3_5/ 目录共 5 个文件,3086 行代码:
|
文件 |
行数 |
职责 |
|
__init__.py |
28 |
模块入口,惰性加载导出 |
|
configuration_qwen3_5.py |
200 |
配置类(自动生成) |
|
modeling_qwen3_5.py |
2026 |
完整模型实现(自动生成) |
|
modular_qwen3_5.py |
738 |
模块化源文件(唯一手工维护) |
|
tokenization_qwen3_5.py |
94 |
BPE 分词器 |
注意:没有 image_processing_*.py 或 processing_*.py——Qwen3.5 复用了 Qwen3VL 的处理器,在 auto/processing_auto.py 中注册为 ("qwen3_5", "Qwen3VLProcessor")。
4.1 configuration_qwen3_5.py
定义”模型长什么样”,包含三个配置类,全部继承 PreTrainedConfig:
|
类 |
model_type |
职责 |
|
|
|
文本模型参数(vocab_size、hidden_size、layer_types 等) |
|
|
|
视觉编码器参数 |
|
|
|
多模态顶层配置,包含 text_config + vision_config 子配置 |
配置类解决的核心问题:
- 声明模型所有超参数及其默认值
- 设置
model_type标识符,供AutoConfig自动映射 - 配置张量并行计划(
base_model_tp_plan:每个线性层指定colwise/rowwise切分策略) - 多模态配置的父子关系(
sub_configs+base_config_key) __post_init__中的自动生成逻辑——Qwen3.5 的layer_types为 None 时自动生成交替层类型(默认每 4 层一个 full_attention)
4.2 modeling_qwen3_5.py
Qwen3.5 的架构创新在于混合注意力——大部分层(默认 3/4)使用 Qwen3_5GatedDeltaNet(线性注意力,O(L) 复杂度,通过门控 Delta 规则维护递归状态),少量层(默认 1/4)使用 Qwen3_5Attention(标准 softmax 注意力,O(L²) 复杂度,门控查询)。这种设计在保持长序列建模能力的同时大幅降低了计算开销。
它是模型的”建筑图纸”:它定义了完整的神经网络计算图,网络由哪些层组成、层与层怎么连接、前向传播怎么计算。但它不包含任何具体的参数数值——参数数值在 model.safetensors 里。
Qwen3.5 的核心设计思想:
1. "大部分层用线性注意力处理长序列,每隔 N 层插入全注意力做全局回顾"
→ 对应 layer_types: ["linear_attention", ..., "full_attention"]
2. "视觉和文本共享同一套 Transformer 主干,但视觉 token 用 3D MRoPE"
→ 对应 mrope_section: [11, 11, 10](时间/高度/宽度)
3. "门控无处不在"
→ RMSNormGated, attn_output_gate, SwiGLU, output_gate_type="swish"
4. "梯度检查点是标配,不是可选"
→ 所有 DecoderLayer 继承 GradientCheckpointingLayer
4.2.1 类说明
nn.Module
├── Qwen3_5VisionRotaryEmbedding ← 视觉 1D RoPE
├── Qwen3_5TextRotaryEmbedding ← 文本 3D MRoPE (交错)
├── Qwen3_5RMSNormGated ← 带门控 RMSNorm (线性注意力用)
├── Qwen3_5RMSNorm ← 标准 RMSNorm (1+weight)
├── Qwen3_5GatedDeltaNet ← 线性注意力层 (O(L))
├── Qwen3_5Attention ← 全注意力层 (O(L²), 门控查询)
├── Qwen3_5MLP ← SwiGLU FFN
├── Qwen3_5VisionMLP ← 视觉 FFN
├── Qwen3_5VisionPatchEmbed ← 3D 卷积 patch embedding
├── Qwen3_5VisionPatchMerger ← 2×2 空间合并
└── Qwen3_5VisionAttention ← 视觉注意力 (变长序列)
GradientCheckpointingLayer
├── Qwen3_5DecoderLayer ← 混合架构路由层
└── Qwen3_5VisionBlock ← 视觉 Transformer 块
PreTrainedModel
└── Qwen3_5PreTrainedModel ← 基类 (权重初始化 + 能力声明)
├── Qwen3_5VisionModel ← 完整视觉编码器
├── Qwen3_5TextModel ← 纯文本主干
├── Qwen3_5Model ← 多模态主干 (视觉+文本)
├── Qwen3_5ForCausalLM ← 纯文本因果LM
│ └── + GenerationMixin
└── Qwen3_5ForConditionalGeneration ← 多模态条件生成
└── + GenerationMixin
GenericForTokenClassification + Qwen3_5PreTrainedModel
└── Qwen3_5ForTokenClassification
GenericForSequenceClassification + Qwen3_5PreTrainedModel
├── Qwen3_5TextForSequenceClassification ← 纯文本序列分类
└── Qwen3_5ForSequenceClassification ← 多模态序列分类
BaseModelOutputWithPast
└── Qwen3_5ModelOutputWithPast ← 输出容器 (+rope_deltas)
1.基础组件
定义模型会反复用到的底层模块:
- RMSNorm / Gated RMSNorm
- RoPE / MRoPE 位置编码
- MLP / VisionMLP
- 一些辅助的门控、卷积、状态更新单元
这些类解决的是“一个小积木怎么计算”的问题。
2.核心注意力模块
Qwen3.5 不是纯 Transformer,而是线性注意力 + 全注意力混合架构。
Qwen3_5GatedDeltaNet
-
- 线性注意力/状态空间风格模块
- 复杂度近似 O(L)
- 负责长上下文高效建模
Qwen3_5Attention
-
- 标准全注意力
- 复杂度 O(L²)
- 负责精确的全局信息交互
3.视觉编码器相关模块
为多模态能力服务:
Qwen3_5VisionPatchEmbed:把图像/视频切成 patch 并映射成向量Qwen3_5VisionAttention:视觉分支内部的注意力Qwen3_5VisionMLPQwen3_5VisionPatchMerger:把 patch 特征合并压缩Qwen3_5VisionBlock:视觉 Transformer block
这部分负责把图片/视频先编码成可供语言模型消费的视觉 token 表示。
4.ecoder 层 / Block 级组装
Qwen3_5DecoderLayer
-
- 是文本主干的单层 block
- 内部会根据配置路由到:
-
-
- 线性注意力层,或
- 全注意力层
-
-
- 再接 MLP 和残差、Norm
这一层体现了 Qwen3.5 的核心设计:不同层不是同构的,而是混合排布的。
5.完整模型主干
在更高一层把所有 block 拼成完整网络,这部分解决的是“整台模型机器怎么装起来”。
Qwen3_5VisionModel:完整视觉编码器Qwen3_5TextModel:纯文本主干Qwen3_5Model:多模态主干(视觉 + 文本融合)
6.任务头(Head)
在主干上再加不同任务输出层,同一个底座可以支持不同任务。
Qwen3_5ForCausalLM
-
- 纯文本生成模型
- 用于自回归 next-token prediction
Qwen3_5ForConditionalGeneration
-
- 多模态条件生成模型
- 输入图像/视频/文本,输出文本
Qwen3_5ForTokenClassification
-
- token 级分类任务
7.Transformers 集成基类
它是连接 config.json、权重文件和模型代码的桥梁。
Qwen3_5PreTrainedModel
-
- 提供和 Transformers 框架对接所需的统一能力
- 比如:
-
-
- 权重初始化
from_pretrainedsave_pretrained- gradient checkpointing 支持
- generation 支持声明
-
8.跨模块调用关系
用户输入: "描述这张图" + [图片]
│
▼
┌─ Qwen3_5VisionPatchEmbed ─────────────────────┐
│ Conv3d(3→1152, kernel=(2,16,16)) │
│ 图片 → [B, num_patches, 1152] │
└─────────────────────┬──────────────────────────┘
▼
┌─ Qwen3_5VisionBlock × 27 ─────────────────────┐
│ RMSNorm → VisionAttention → RMSNorm → VisionMLP│
│ 2D RoPE 位置编码 │
└─────────────────────┬──────────────────────────┘
▼
┌─ Qwen3_5VisionPatchMerger ─────────────────────┐
│ 2×2 空间合并 → 投影到 5120 维 │
│ [B, num_patches, 1152] → [B, num_merged, 5120]│
└─────────────────────┬──────────────────────────┘
│ vision_features
▼
┌─ Token 替换 ───────────────────────────────────┐
│ input_ids 中的 <|image_pad|> 位置 │
│ 替换为 vision_features │
│ 构造 3D MRoPE position_ids: │
│ 文本 token → (t, 0, 0) │
│ 视觉 token → (t, h, w) │
└─────────────────────┬──────────────────────────┘
▼
┌─ Qwen3_5DecoderLayer × 64 ─────────────────────┐
│ │
│ Layer 0-2: GatedDeltaNet (线性注意力) │
│ Conv1d(4) → QKV → Delta Rule → GatedNorm │
│ O(L) 复杂度,处理长视觉序列高效 │
│ │
│ Layer 3: Full Attention (全注意力) │
│ GQA(24:4) → MRoPE → Swish Gate → O(L²) │
│ 全局回顾,建立跨模态关联 │
│ │
│ Layer 4-6: GatedDeltaNet │
│ Layer 7: Full Attention │
│ ... │
│ Layer 63: Full Attention (最后一层总是全注意力) │
│ │
│ 每层后接 SwiGLU MLP │
└─────────────────────┬──────────────────────────┘
▼
┌─ Qwen3_5RMSNorm → lm_head ─────────────────────┐
│ [B, L, 5120] → [B, L, 248320] │
│ → softmax → next token prediction │
└──────────────────────────────────────────────────┘
4.2.2 forward实现
Qwen3_5ForConditionalGeneration.forward()
│
├─ self.model (Qwen3_5Model).forward()
│ │
│ ├─ self.visual (Qwen3_5VisionModel).forward()
│ │ ├─ Qwen3_5VisionPatchEmbed — patch 切分
│ │ ├─ Qwen3_5VisionRotaryEmbedding — 视觉 RoPE
│ │ ├─ [Qwen3_5VisionBlock] × N
│ │ │ ├─ Qwen3_5VisionAttention — 视觉自注意力
│ │ │ └─ Qwen3_5VisionMLP — 视觉 FFN
│ │ └─ Qwen3_5VisionPatchMerger — 空间合并
│ │
│ ├─ 替换占位 token 的 embedding → 视觉特征注入
│ ├─ compute_3d_position_ids → 4D position_ids
│ │
│ └─ self.language_model (Qwen3_5TextModel).forward()
│ ├─ embed_tokens → input embeddings
│ ├─ Qwen3_5TextRotaryEmbedding — 交错 MRoPE
│ │
│ └─ [Qwen3_5DecoderLayer] × N
│ ├─ if layer_types[i] == "linear_attention":
│ │ └─ Qwen3_5GatedDeltaNet — 线性注意力 (O(L))
│ │ ├─ torch_chunk/recursive_gated_delta_rule
│ │ ├─ causal_conv1d_fn/update
│ │ └─ Qwen3_5RMSNormGated
│ │
│ ├─ if layer_types[i] == "full_attention":
│ │ └─ Qwen3_5Attention — 全注意力 (O(L²))
│ │ └─ apply_rotary_pos_emb + eager/SDPA/Flash
│ │
│ ├─ Qwen3_5RMSNorm (input_layernorm)
│ └─ Qwen3_5MLP + Qwen3_5RMSNorm (post_attention)
│
└─ self.lm_head → logits
4.2.3 与config.json的关系
config.json(施工参数) 模型 py(施工图纸)
───────────────────── ────────────────────
hidden_size: 5120 → nn.Linear(5120, ...)
num_hidden_layers: 64 → for i in range(64): DecoderLayer()
num_attention_heads: 24 → q_proj = Linear(5120, 24*head_dim)
num_key_value_heads: 4 → k_proj = Linear(5120, 4*head_dim)
intermediate_size: 17408 → gate_proj = Linear(5120, 17408)
vocab_size: 248320 → Embedding(248320, 5120)
hidden_act: "silu" → act_fn = SiLU()
rms_norm_eps: 1e-6 → RMSNorm(eps=1e-6)
rope_theta: 10000000 → RotaryEmbedding(theta=10000000)
4.2.4 config.json vs modeling_*.py vs model.safetensors
┌─────────────────────────────────────────────────────────────────┐
│ │
│ config.json modeling_*.py model.safetensors │
│ ─────────── ───────────── ───────────────── │
│ "造多大" "怎么造" "填什么值" │
│ │
│ hidden_size=5120 nn.Linear(5120,...) weight=[5120×...] │
│ num_layers=64 for i in range(64) layers.0.q_proj │
│ vocab_size=248320 Embedding(248320,5120) embed.weight │
│ │
│ 三者缺一不可: │
│ • 没有 config → 不知道建多大的结构 │
│ • 没有模型代码 → 不知道怎么连接 │
│ • 没有权重 → 结构是空壳,输出是随机数 │
│ │
│ 调用入口:AutoModelForCausalLM.from_pretrained(path) │
│ 路由桥梁:config.json 中的 model_type 字段 │
│ │
└─────────────────────────────────────────────────────────────────┘
4.3 modular_qwen3_5.py
Hugging Face Transformers 库在 v4.39+ 版本引入的一项重大工程重构:Modular Transformers(模块化架构)。简单来说,modeling_qwen3_5.py 不再是人类手写和维护的源码,而是由构建工具根据 modular_qwen3_5.py 自动编译/生成的产物。
4.3.1 核心痛点
在 Modular 机制出现之前,Transformers 库里的每个模型都是完全独立的“孤岛”。这导致了严重的代码重复与维护灾难:
- 复制粘贴泛滥:Qwen2 是从 Llama 复制来的,Qwen2.5 又是从 Qwen2 复制来的。RMSNorm、RoPE、SwiGLU MLP 等基础组件在几百个模型文件中被重复定义了上万次。
- 牵一发而动全身:如果修复了 RoPE 的一个 bug,或者为 FlashAttention-2 增加了一种新优化,开发者需要手动去修改几百个
modeling_*.py文件,极易遗漏。 - Diff 不可读:PR 里充斥着大量无意义的重复代码变更,Code Review 极其痛苦。
4.3.2 Modular 机制
- 声明依赖与继承:在
modular_qwen3_5.py中,开发者不再重写所有类,而是通过import引入基础模型(如 Qwen2 或 Llama),然后只写差异部分。python
# modular_qwen3_5.py (人类编写的源码)
from ..qwen2.modeling_qwen2 import Qwen2ForCausalLM, Qwen2Model
class Qwen3_5Model(Qwen2Model):
# 只重写有变化的方法或属性
def __init__(self, config):
super().__init__(config)
# 添加 Qwen3.5 特有的 MTP Head
if hasattr(config, "mtp_num_hidden_layers"):
self.mtp_head = MTPHead(config)
# 没有重写的类(如 RMSNorm, MLP)会自动从父类继承
- AST 解析与代码展开:当你运行
make style或 CI 流水线时,Transformers 内部的脚本(utils/modular_model_converter.py)会使用 Python AST(抽象语法树)解析这个文件。 - 生成扁平化代码:脚本将所有的继承关系、import 引用进行静态展开(Flattening),把零散的增量代码合并成一个完整的、自包含的、没有任何外部模型依赖的传统
modeling_qwen3_5.py文件。 - 写入文件并校验:生成的代码会覆盖目标文件,并通过 Ruff/Black 进行格式化。最终提交到 Git 仓库中的依然是完整的
modeling_*.py。
4.3.3 为什么还要保留生成的 modeling_qwen3_5.py?
既然有了模块化的源码,为什么不直接在运行时动态组装,而是要生成一个物理文件并提交到仓库?
- 向后兼容:数以万计的第三方推理引擎(vLLM、SGLang、TGI)和用户脚本都硬编码了
from transformers.models.qwen3_5.modeling_qwen3_5 import ...。保留生成的完整文件,保证了整个生态无需任何修改即可无缝工作。 - 可读性与调试:生成的文件是标准的 Python 代码。当用户在本地 debug 断点时,看到的是完整的逻辑,而不是复杂的动态 mixin 或元类魔法。
- 零运行时开销:代码展开发生在开发/构建阶段,运行时没有任何额外的反射或动态加载成本。
4.3.4 对开发者和用户的影响
|
角色 |
影响与注意事项 |
|
模型贡献者 |
禁止直接修改 |
|
框架维护者 |
修复底层 Bug(如 Attention Mask 逻辑)时,只需修改基础模型(如 Llama/Qwen2),所有继承它的衍生模型会在下次构建时自动获得修复。 |
|
普通用户 |
使用体验完全不变。 |
|
下游引擎开发者 |
如果你通过正则表达式或 AST 解析 |