数据截至 (上游 commit b6c0bfe04c82)
Transformers — 架构与原理
30 秒导读: Hugging Face Transformers 是 transformer 模型时代的「标准图书馆 + 运行时」:507 个架构目录各装一份能单独读懂的参考实现(
models/<name>/modeling_<name>.py),上面架着共享的三根梁——PreTrainedModel.from_pretrained负责把 Hub 上的权重正确装进模型、generate()负责自回归解码、Trainer负责训练——再用AutoModel一行代码按 config.json 里的model_type把 507 选 1 的路由做掉。整个开源 LLM 生态的 checkpoint 格式(config.json + safetensors)就是从这个库长出来的事实标准。本文基于 v5.16.0.dev0(v5 大版本,含不兼容重构)。
1. 这是什么(零基础也能懂)
一句话定义
Transformers 是一个 PyTorch 模型库 + 配套运行时:它把论文里的每个 transformer 架构用统一风格的 Python 类实现出来,并给所有模型同一套「加载预训练权重 → 前向/生成 → 微调 → 保存回 Hub」的工作流。
它要解决谁的什么问题
设想你拿到一篇新论文(比如一个新 MoE 架构),或者拿到 Hub 上别人训好的 70B 权重:
- 你想直接用:得有正确实现 + 正确的权重加载,权重键名、dtype、分片、量化一个都不能错。
- 你想改着用:得读得懂这份实现,最好能拷出来改,不牵一发动全身。
- 你想训着用:得有训练回路,处理分布式、混精度、梯度累积、checkpoint 恢复。
这三件事的难度都在「每换一个架构就要重来一遍」。Transformers 把重复的部分抽成基类,把不同的部分留在每个模型自己的文件里。
它能做什么
| 能力 | 具体形态 |
|---|---|
| 架构参考实现 | src/transformers/models/ 下 507 个目录,每目录一个(基本)自包含的 modeling_*.py |
| 权重加载/保存 | from_pretrained / save_pretrained,safetensors 分片、量化、device_map、张量并行 |
| 文本生成 | generate():贪心、采样、beam search、投机解码,带 KV cache |
| 训练 | Trainer + TrainingArguments,底层委托 accelerate,支持 FSDP/DeepSpeed |
| 任务封装 | pipeline("text-generation", model=...) 三行跑通一个任务 |
| 生态契约 | config.json / generation_config.json / safetensors 分片格式,被 vLLM、PEFT 等全线读取 |
用起来什么样
# 生态里最经典的三行(示意,非源码)
from transformers import AutoModelForCausalLM, AutoTokenizer
tok = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B", device_map="auto")
out = model.generate(**tok("你好", return_tensors="pt"), max_new_tokens=64)
AutoTokenizer 与 AutoModelForCausalLM 都不知道 Llama 是什么——它们读 config.json 里的 model_type: "llama",查表路由到 LlamaForCausalLM(见第 6 章)。训练侧对应的最小形态是:包一个 Trainer(model, args, train_dataset=...) 然后 trainer.train()(见第 5 章)。
一句话直觉
把它想成「硬件行业的 ATX 标准 + 主板说明书库」。 config.json/safetensors 是接口标准(谁都能读写),每个 modeling_*.py 是一块主板的说明书(单独可读),PreTrainedModel/generate/Trainer 是所有主板共享的电源、总线和 BIOS。
2. 顶层全景(它大 概怎么转)
2.1 分层结构
库内部可以看成五层,上层只依赖下层的公共接口:
┌─────────────────────────────────────────────────────────────┐
│ 任务层 pipeline("text-generation", ...) │
│ pipelines/base.py:753 (Pipeline) │
├─────────────────────────────────────────────────────────────┤
│ 路由层 AutoModelForCausalLM / AutoConfig / AutoTokenizer │
│ models/auto/ (按 model_type 查表,懒加载) │
├──────────────────┬───────────────────────┬──────────────────┤
│ 推理引擎 │ 权重装载 │ 训练引擎 │
│ GenerationMixin │ PreTrainedModel │ Trainer │
│ generation/utils │ modeling_utils.py │ trainer.py │
│ .generate() │ .from_pretrained() │ .train() │
├──────────────────┴───────────────────────┴──────────────────┤
│ 模型层 507 个 models/<arch>/modeling_<arch>.py │
│ 每个文件自带 Attention/MLP/DecoderLayer/Model 全套 │
├─────────────────────────────────────────────────────────────┤
│ 基设层 KV cache 抽象(cache_utils.py) · 注意力后端注册表 │
│ (modeling_utils.py ALL_ATTENTION_FUNCTIONS) · 掩码 │
│ 工具(masking_utils.py) · 配置基类(configuration_utils)│
└─────────────────────────────────────────────────────────────┘
怎么读这张图: 垂直方向是依赖方向(下不依赖上);中间三个引擎是并列的,都坐在同一批模型类上。Trainer 与 generate 不互相调用——训练走 forward,生成走 GenerationMixin。
2.2 部件职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
Pipeline | 任务级封装:preprocess → forward → postprocess | pipelines/base.py:753(run_single 在 :1297) |
AutoModelForCausalLM 等 | 按 config 的 model_type 选模型类,再调它的 from_pretrained | models/auto/auto_factory.py:194、:261 |
AutoConfig | 读 config.json → 实例化对应 PreTrainedConfig 子类 | models/auto/configuration_auto.py:303 |
PreTrainedModel | 所有模型的基类:加载、保存、权重绑定、device_map、量化钩子 | modeling_utils.py:1180 |
GenerationMixin | 混入模型类的生成能力:generate() 与各种解码循环 | generation/utils.py:359 |
Cache / DynamicCache | KV cache 容器:一个模型一个 Cache,每层一个 Layer | cache_utils.py:1269、:1737 |
ALL_ATTENTION_FUNCTIONS | 注意力后端注册表:eager/sdpa/flash/flex 按名取函数 | modeling_utils.py:5185 |
Trainer | 训练编排:数据加载、梯度累积、checkpoint、评测、回调 | trainer.py:258 |
TrainingArguments | 训练的全部超参(dataclass,几百个字段) | training_args.py:180 |
PreTrainedConfig | 所有配置的基类,序列化为 config.json | configuration_utils.py:148 |
modular_*.py | 「继承式建模」源文件:用 Python 继承从别的架构 diff 出新架构 | 如 models/qwen3/modular_qwen3.py(287 个) |
2.3 主线走一遍(推理)
一次 model.generate(**inputs, max_new_tokens=64) 经过的部件,高层不进代码:
① 路由 AutoModelForCausalLM.from_pretrained 已把模型类选好、权重装好(第 2、6 章)
│
② 定模式 GenerationConfig.get_generation_mode():看 num_beams/do_sample
决定走采样、beam 还是投机解码(generation/configuration_utils.py:534)
│
③ 组装处理器 _get_logits_processor():temperature/top_p/重复惩罚…
每个参数变成一个 LogitsProcessor(generation/utils.py:1123)
│
④ prefill _prefill():整段 prompt 一次 forward,KV 全部进 cache
│
⑤ 逐 token 解码循环 _sample():
forward 1 个 token → 取最后一位 logits → processor 加工
→ softmax/argmax 或 multinomial 选 token → 拼到 input_ids 尾部
→ stopping_criteria 判断每条序列是否该停(EOS/长度/时间)
│
⑥ 收尾 按 return_dict_in_generate 决定返回裸 token 还是结构化输出
第 ③~⑤ 步的全部细节见第 3 章,cache 在循环里怎么滚动见第 4 章。
2.4 主线走一遍(训练)
trainer.train() 的高层轮廓(_inner_training_loop,trainer.py:1465):
初始化 set_initial_training_values → _init_training_state(可恢复)
→ _prepare_for_training(建 optimizer/scheduler,accelerator.prepare 包模型)
│
epoch 循环 for epoch in range(epochs_trained, num_train_epochs) (trainer.py:1538)
│
step 循环 get_batch_samples(数出 num_items_in_batch)
→ training_step():compute_loss → accelerator.backward(loss)
→ 攒够梯度累积步数 → optimizer.step + scheduler.step
→ CallbackHandler 一路发 on_step_end/on_log/on_save 事件
→ control.should_training_stop? 退出
│
收尾 _finalize_training:评测、存最终 checkpoint
关键认知:Trainer 自己不做分布式、不做混精度——这些都委托给它内部持有的 accelerate.Accelerator(trainer.py:829 创建)。Trainer 只做「训练流程编排 + 回调事件」。细节见第 5 章。
3. 阅读地图(建议顺序)
六章由浅入深。只想抓要害:读 01 → 03 → 06 就能明白「为什么这个库长这样」。
| 顺序 | 章节 | 讲什么 | 适合谁 |
|---|---|---|---|
| 1 | 01-model-file-anatomy.md | 一个 modeling_*.py 的内部结构、modular 继承式生成、注意力注册表 | 想读懂/新增一个架构的人,必读 |
| 2 | 02-model-loading.md | from_pretrained 全流程、v5 权重转换系统、tie_weights | 被 missing/unexpected keys 警告折磨过的人 |
| 3 | 03-generation.md | generate() 总控与三种解码循环、logits processor 链 | 要改采样行为、加约束解码的人 |
| 4 | 04-kv-cache.md | Cache/Layer 二级抽象与各 variant、offloading | 关心推理显存与吞吐的人 |
| 5 | 05-trainer.md | Trainer 的编排哲学、训练循环、compute_loss 细节 | 微调模型的、要自定义 loss 的人 |
| 6 | 06-auto-config.md | Auto 路由、懒加载映射、trust_remote_code、config.json 契约 | 想理解生态互操作格式的人 |
4. 巧妙之处(可借鉴的技术)
挑四条最有迁移价值的;各章末尾还有更多。
-
「单文件自读」是政策,不是巧合。 卡片里写得很白:模型文件间的刻意重复是设计决策——每个
modeling_*.py要能单独读懂、单独拷走。为了既保可读又消重复,v5 用 modular 继承系统:作者在modular_qwen3.py里写class Qwen3RMSNorm(Qwen2RMSNorm): pass这样的 Python 继承 diff,CI 再把它展开生成自包含的modeling_qwen3.py(文件 头第 2 行明示 "automatically generated from ... modular_qwen3.py")。源是 diff,产物是全量——兼得两者。见第 1 章。 -
权重加载在 v5 被建成一套「转换算子代数」。 旧 checkpoint 键名不同?QKV 三块要合并?不再是模型里一坨 if,而是声明式的
WeightTransform算子(Chunk/Concatenate/Transpose/PermuteForRope…,core_model_loading.py:81起),加载时对 checkpoint 键做模式匹配后统一执行。生态十年积累的「权重格式漂移」被收敛成一个可扩展的算子库。见第 2 章。 -
注意力实现是「名字 → 函数」的全局注册表。 模型代码里只有一句
ALL_ATTENTION_FUNCTIONS.get_interface(self.config._attn_implementation, eager_attention_forward)(models/llama/modeling_llama.py:264-266);eager/sdpa/flash/flex 的切换发生在查表,不在模型代码里。新架构默认获得全部后端,新后端默认覆盖全部架构。 见第 1 章。 -
解码行为全部由「组装处理器链」决定,而不是 if-else。
generate()把 temperature、top_p、重复惩罚等参数各自变成一个LogitsProcessor,把 EOS/最大长度变成StoppingCriteria,主循环_sample只剩三步:forward → 加工 logits → 选 token(generation/utils.py:2916-2922)。用户想加约束,往链上挂一个处理器即可。见第 3 章。
5. 边界与局限
诚实清单,按「会咬人的程度」排序:
- 单文件自读 = 海量刻意重复。 507 个目录意味着一个横跨所有模型的 bug 要修几百个文件(或用 modular 重新生成)。读一个文件很舒服,改全库很痛苦。这是明知的取舍。
- 不是推理引擎。
generate()追求通用与可读,吞吐被 vLLM/SGLang 这类带连续批处理、paged attention 的服务化引擎碾压(库里有generation/continuous_batching/,但主线仍以单请求 decode 为主)。要上线服务请出门左转 vllm。 trust_remote_code是真执行远端代码。 Auto 体系允许 Hub 仓库自带 Python 建模代码并在你机器上执行(models/auto/configuration_auto.py:389-408的分支)。方便,但本质是eval别人的仓库,只对信得过的源开启。- 版本移动极快。 本 commit 是 v5.16.0.dev0,根目录就有
MIGRATION_GUIDE_V5.md;引用行为务必对 commit,跨版本行号必漂。 - Trainer 是「单机为主、分布式靠 accelerate」。 超大模型的 3D 并行(TP/PP 手工切)不是 Trainer 的主场;它接 DeepSpeed/FSDP 是通过配置透传,控制力不如专用框架。
6. 横向对比
同书架兄弟库与 transformers 的关系,多数是「读它的格式」或「替它干活」:
| 库 | 关系 | 不同取舍 |
|---|---|---|
| vllm | 读 HF checkpoint/config 格式,替 generate 干活 | 为吞吐牺牲通用性:连续批处理、paged KV、CUDA graph;transformers 为 「任何架构都能跑」牺牲速度 |
| accelerate | 被 Trainer 持有,替它干分布式的活 | accelerate 只做分布式原语,不含训练逻辑;Trainer 只做编排,不碰 NCCL |
| peft | 寄生在 PreTrainedModel 上改权重结构 | LoRA 等只加小参数,冻结基座;from_pretrained 还原生支持 adapter 路径探测(models/auto/auto_factory.py:298-318) |
| trl | 拿 Trainer 当底座写 RLHF/DPO | TRL 的 SFTTrainer/DPOTrainer 都是 Trainer 子类,重写 compute_loss 等钩子 |
| tokenizers | 分词的 Rust 引擎,被 PreTrainedTokenizerFast 包装 | tokenizers 只管文本↔id;transformers 的 tokenizer 层管特殊 token、chat 模板、与模型配置的耦合 |
一句话:transformers 是这个生态的「格式与基类提供者」——别家或消费它的格式,或继承它的基类,或补上它刻意不做的性能/分布式纵深。
7. 代码地图(入口级)
每章末尾有细粒度地图。这里只列从零开始读源码的六个入口:
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 一个架构的完整形态(教科书) | src/transformers/models/llama/modeling_llama.py | LlamaAttention、LlamaDecoderLayer、LlamaModel、LlamaForCausalLM |
| 模型基类与权 重加载 | src/transformers/modeling_utils.py | PreTrainedModel、from_pretrained、tie_weights |
| v5 权重转换系统 | src/transformers/core_model_loading.py | WeightTransform、WeightConverter、convert_and_load_state_dict_in_model |
| 生成总控与解码循环 | src/transformers/generation/utils.py | GenerationMixin.generate、_sample、_beam_search |
| KV cache | src/transformers/cache_utils.py | Cache、DynamicCache、DynamicLayer、StaticLayer |
| 训练回路 | src/transformers/trainer.py | Trainer.train、_inner_training_loop、training_step、compute_loss |
| Auto 路由 | src/transformers/models/auto/auto_factory.py、configuration_auto.py | _BaseAutoModelClass.from_pretrained、AutoConfig.from_pretrained |