很多人刚开始学大模型时,最容易陷入一种状态:
Transformer听过Attention听过RMSNorm、RoPE、Tokenizer也都听过
但这些词像散落在桌上的零件,知道名字,却不知道它们为什么会一起工作。
所以这篇文章我不想一上来就推公式,而是先做一张组件地图:
一次大模型训练,到底有哪些核心组件?它们各自解决什么问题?它们之间又是怎么接起来的?
如果只用一句话概括,一个现代大语言模型大概可以看成这条链:
文本 -> Tokenizer -> Embedding -> Positional Information -> Attention/MLP 堆叠 -> lm_head -> Loss -> 反向传播
这条链上的每一个组件,都不是随便放进去的。
一、先有一个全局视角:大模型到底在做什么?
一个大语言模型训练时,本质上是在做一件事:
给定前面的 token,预测下一个 token。
比如:
我 爱 -> 你
今天 天气 -> 不错
The capital of France is -> Paris
所以它不是在“理解世界”之后再生成文字,而是先通过大量训练,学会:
- 哪些 token 经常一起出现
- 哪些结构经常共同出现
- 哪些语义模式在上下文里更合理
而为了完成这件事,它需要下面这些组件。
二、Tokenizer:把语言变成数字
这是第一步,也是整个系统的入口。
计算机并不直接理解:
- 汉字
- 英文单词
- 标点
- 空格
它首先要做的是:
文本 -> token -> token id
比如一句话:
"我爱你"
可能会被编码成:
[314, 928, 1024]
Tokenizer 解决的问题是:
- 如何切分文本
- 如何建立词表
- 每个 token 对应哪个 id
如果你最近在看 tokenizer,可以把它理解成:
大模型输入世界的“编码协议”。
它一般在预训练之前就训练好,后续整个模型训练过程基本固定不动。
三、Embedding:把离散 id 变成连续向量
Tokenizer 只负责编号,但模型不能直接拿整数做复杂计算。
所以第二步要做的是:
token id -> dense vector
假设:
- 词表大小
V = 6400 - 隐藏维度
d = 768
那 embedding 本质上就是一个矩阵:
[ E \in \mathbb{R}^{V \times d} ]
每个 token id 对应矩阵里的一行。
如果当前 token id 是 i,它的向量就是:
[ x = E[i] ]
代码写起来其实非常简单:
import torch
import torch.nn as nn
embed = nn.Embedding(num_embeddings=6400, embedding_dim=768)
input_ids = torch.tensor([[1, 314, 928, 1024]])
hidden_states = embed(input_ids)
print(hidden_states.shape) # [1, 4, 768]
Embedding 解决的问题是:
如何把“离散符号”翻译成模型可以在连续空间里计算的向量表示。
四、位置编码:Transformer 先天不懂顺序,所以必须补位置信息
Attention 的一个好处是并行,坏处是:
它本身不带顺序感。
也就是说,如果你只把 token 向量扔进去,模型天然不知道:
- 谁在前面
- 谁在后面
- 当前 token 距离另一个 token 有多远
所以需要额外注入位置信息。
早期常见的是:
- sinusoidal positional encoding
- learned positional embedding
而现代 LLM 非常常见的是:
- RoPE(Rotary Position Embedding)
RoPE 不是简单把一个位置向量加到输入上,而是:
直接作用在 Attention 的
Q和K上,对它们做带位置的旋转。
这意味着位置信息不是“加在 token 上”,而是“写进匹配关系里”。
这一点非常现代,也非常重要。
五、Attention:模型真正“看上下文”的地方
Attention 是 Transformer 最核心的组件。
它回答的问题是:
当前这个 token,在理解自己时,应该更关注前面哪些 token?
为了做到这一点,每个 token 会先被投影成三种向量:
Q:Query,我想找什么K:Key,我能提供什么V:Value,我真正携带的内容
如果输入 hidden states 形状是:
[ X \in \mathbb{R}^{B \times T \times d} ]
那么通常会先做线性投影:
[ Q = XW_Q,\quad K = XW_K,\quad V = XW_V ]
然后计算 attention score:
[ \text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V ]
这条公式值得你反复看,因为它几乎就是 Transformer 的灵魂。
简单理解:
QK^T算相似度softmax把相似度变成权重- 再用这些权重去加权
V
最后得到一个“结合了上下文”的新表示。
六、Multi-Head Attention:不是只看一遍,而是从多个角度同时看
如果只有一个 attention head,那模型每次只能用一种方式看上下文。
所以 Transformer 通常会把 hidden size 切成多个 head:
- 每个 head 各自做一套 Q/K/V
- 最后再把它们拼起来
这就是 Multi-Head Attention。
你可以把它理解成:
不是一个人盯着全文看,而是很多个小脑袋各自关注不同模式。
有的 head 可能更关心:
- 语法依赖
- 长距离指代
- 标点边界
- 重复模式
- 代码结构
这也是为什么 attention 会这么强。
七、Normalization:从 BatchNorm 到 LayerNorm,再到 RMSNorm
训练深层网络时,一个老问题一直存在:
层数变深之后,数值分布会越来越不稳定,训练容易发散,梯度也容易出问题。
所以神经网络里一直在想办法做“归一化”。
发展路径大致可以粗略理解成:
- BatchNorm
- LayerNorm
- RMSNorm
八、BatchNorm:卷积时代非常成功,但不太适合 LLM
BatchNorm 的核心思想是:
对一个 batch 内的激活做归一化。
公式写成:
[ \hat{x} = \frac{x - \mu_B}{\sqrt{\sigma_B^2 + \epsilon}} ]
再带上可学习参数:
[ y = \gamma \hat{x} + \beta ]
这里:
\mu_B是 batch 均值\sigma_B^2是 batch 方差
它在 CNN 里极其成功,但在 LLM 里问题不少:
- 序列模型里 batch 结构更复杂
- 推理时 batch 行为不如训练稳定
- 长序列场景不够自然
所以 Transformer 路线后来主要用:
- LayerNorm
九、LayerNorm:按 token 自己归一化
LayerNorm 不再依赖整个 batch,而是:
对单个 token 的特征维度做归一化。
公式是:
[ \mu = \frac{1}{d}\sum_{i=1}^{d} x_i ]
[ \sigma^2 = \frac{1}{d}\sum_{i=1}^{d}(x_i - \mu)^2 ]
[ \text{LayerNorm}(x) = \gamma \odot \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}} + \beta ]
它比 BatchNorm 更适合 NLP 和 Transformer,因为:
- 不依赖 batch 统计量
- 每个 token 自己就能完成归一化
- 训练和推理更一致
十、RMSNorm:现代 LLM 很爱用的轻量版本
很多现代大模型,包括 Llama 系列、Qwen 系列的很多实现,都会使用:
- RMSNorm
它相对于 LayerNorm 的变化非常直接:
不再减均值,只看均方根。
公式是:
[ \text{RMS}(x) = \sqrt{\frac{1}{d}\sum_{i=1}^{d} x_i^2 + \epsilon} ]
[ \text{RMSNorm}(x) = \gamma \odot \frac{x}{\text{RMS}(x)} ]
和 LayerNorm 相比,它:
- 更简单
- 计算更轻
- 在大模型里往往足够稳定
这就是它受欢迎的原因。
一个最小实现其实很短:
import torch
import torch.nn as nn
class RMSNorm(nn.Module):
def __init__(self, dim, eps=1e-6):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(dim))
def forward(self, x):
rms = torch.sqrt(torch.mean(x * x, dim=-1, keepdim=True) + self.eps)
return self.weight * (x / rms)
如果你现在刚入门,可以先把它理解成:
RMSNorm 是 LayerNorm 的一个更省事、更轻的现代版本。
十一、Residual Connection:为什么网络可以堆得很深
如果没有残差连接,深层网络非常容易训练困难。
残差连接的思想非常朴素:
[ y = x + F(x) ]
也就是说,当前层不是完全重新生成表示,而是在原表示上做修正。
Transformer block 里通常有两次残差:
- Attention 后一次
- MLP 后一次
它解决的问题是:
- 深层训练困难
- 梯度传不回去
- 表示更新过于激进
所以它其实是现代深网络能“堆起来”的基础设施之一。
十二、MLP / FFN:Attention 负责交流,MLP 负责变换
很多人刚接触 Transformer 时,只盯着 attention,看久了会误以为:
模型的能力几乎都来自 attention。
其实不是。
Attention 更像是在做:
- 信息路由
- 上下文聚合
而 MLP 或 Feed Forward Network 更像是在做:
- 非线性变换
- 特征重组
- 表示扩展与压缩
典型结构大概是:
[ \text{FFN}(x) = W_2 \sigma(W_1 x) ]
在现代 LLM 里还常常会写成门控版本,比如:
- SwiGLU
- GeGLU
这类结构通常比最朴素的两层 MLP 更强。
所以你可以粗略理解:
- Attention 决定“看谁”
- MLP 决定“怎么想”
十三、MoE:不是每次都让所有参数都干活
如果模型继续做大,一个问题会越来越严重:
参数量越来越大,但每个 token 真的需要所有参数都参与计算吗?
于是就出现了:
- MoE(Mixture of Experts)
它的核心思想是:
让不同 token 动态路由到不同专家,而不是每次都激活整块 MLP。
这样做的好处是:
- 总参数可以很大
- 单次激活参数不必同样大
- 计算效率和容量可以更平衡
当然,它也会带来新的问题:
- 路由稳定性
- 负载均衡
- 训练更复杂
所以你会在训练里看到一些:
aux_loss
这常常就是 MoE 路由带来的辅助损失。
十四、lm_head:把 hidden states 投影回词表
前面一堆 Attention、Norm、MLP 堆叠之后,模型最后拿到的是:
- 每个位置的 hidden state
但训练目标是:
预测下一个 token 是词表里的哪一个。
所以最后一步要把 hidden state 映射回词表维度。
如果:
- hidden size 是
d - vocab size 是
V
那么最后一层本质上就是一个线性层:
[ \text{logits} = hW + b ]
其中:
[ W \in \mathbb{R}^{d \times V} ]
输出的 logits 形状就是:
[batch, seq_len, vocab_size]
这一步的作用非常直接:
让模型从“内部语义表示”回到“对整个词表的打分”。
十五、Loss:训练时模型到底在学什么?
一个语言模型训练时,不是只判断“对”或“不对”,而是会给词表里每个 token 一个分数,然后通过 softmax 变成概率分布。
如果真实下一个 token 是 y,而模型预测它的概率是:
[ p(y \mid x) ]
那么单个位置的交叉熵损失就是:
[ \mathcal{L} = -\log p(y \mid x) ]
如果模型给正确 token 很高概率,loss 就小;
如果模型几乎不相信正确 token,loss 就大。
这比简单的 0/1 更有信息量,因为它告诉模型:
- 不是只要知道错了
- 还要知道错得有多离谱
在整段序列上,loss 通常会对所有有效位置求平均。
这就是语言模型训练最核心的目标:
让正确下一个 token 的概率越来越高。
十六、优化器、学习率、反向传播:真正让参数动起来的部分
有了 loss 之后,还不等于训练完成。
接下来发生的事情是:
loss.backward()- 计算所有参数的梯度
- 优化器根据梯度更新参数
最常见的优化器之一就是:
AdamW
它相对朴素 SGD 更适合大模型训练,因为:
- 自适应学习率
- 训练更稳定
- 对不同参数尺度更友好
同时训练里还经常会配:
- warmup
- cosine decay
- gradient clipping
- mixed precision
这些都不是模型结构本身,但它们决定了训练能不能稳定跑起来。
十七、预训练、SFT、DPO、RL:它们不是不同模型,而是不同训练阶段
很多初学者会误以为:
- 预训练模型
- 指令微调模型
- 对齐模型
是三种完全不同的模型。
其实很多时候,它们是:
同一个基座模型,在不同阶段接受了不同训练目标。
可以粗略理解成:
1. 预训练
- 大量文本
- 目标:预测下一个 token
- 学到语言和知识统计规律
2. SFT(监督微调)
- 多轮对话、指令数据
- 目标:学会按助手风格回答
3. DPO / RLHF / RLAIF
- 偏好数据或奖励模型
- 目标:让回答更符合人类偏好
所以“会聊天”并不是预训练天然就有的,而是后续对齐阶段不断塑形的结果。
十八、如果把这些组件串起来,一次训练到底发生了什么?
我们现在可以把全局图补完整一点:
原始文本
-> Tokenizer
-> token ids
-> Embedding
-> 加入位置信息 / RoPE作用于Attention内部
-> 多层 Transformer Block
-> RMSNorm
-> Multi-Head Attention
-> Residual
-> RMSNorm
-> MLP / MoE
-> Residual
-> lm_head
-> logits
-> softmax + cross entropy
-> loss
-> backward
-> optimizer.step()
这就是一条完整的大模型训练链。
如果你能把这条链真正看顺,后面再去读任何一个大模型项目,很多陌生代码都会突然变得好认。
十九、给刚入门时的自己一个建议
如果你现在刚开始学大模型,我觉得最值得优先搞懂的不是“某一个最新 trick”,而是先把下面几件事真正想通:
- Tokenizer 到底在做什么
- Embedding 为什么必要
- Attention 那条公式到底什么意思
- RoPE 到底加在了哪里
- RMSNorm 和 LayerNorm 有什么关系
- lm_head 为什么最后要映射回词表
- cross entropy loss 为什么不是
0/1
因为这些东西一旦通了,后面你再看:
- Llama
- Qwen
- DeepSeek
- MiniMind
会发现它们虽然细节不同,但骨架其实都在这一套里。
二十、阶段性总结
如果一定要把这篇压成一句话,我会这么说:
大模型并不是一个神秘黑盒,它本质上是一套把文本编码成向量、通过注意力和前馈网络不断变换表示、最后再投影回词表并通过损失函数学习参数的系统。
而我们平时听到的那些组件名:
- Tokenizer
- Embedding
- RoPE
- RMSNorm
- Attention
- MLP
- MoE
- lm_head
- Loss
其实都只是这个系统里,各自负责不同环节的零件。
把零件的职责弄清楚之后,大模型这件事就不会再只是一堆名词了。
如果后面我继续写这个系列,我最想往下展开的三篇会是:
Tokenizer:现代 tokenizer 到底怎么设计Attention + RoPE:Transformer 真正的计算核心Loss + 训练阶段:预训练、SFT、DPO、RL 到底有什么本质区别
相关阅读:
交流讨论