前言

在上一篇文章中,我们讲了 Tokenizer、BPE 和 SentencePiece。

若未看过,可顺道移步上篇:大模型不是直接读文字的:一文讲透 Tokenizer、BPE 和 SentencePiece

也就是说,我们已经知道了:

原始文本
↓
Tokenizer
↓
Token IDs
↓
Embedding
↓
向量表示

但是问题来了:

文本变成向量之后,LLaMA 到底是怎么处理这些向量的?

这篇文章就专门解决这个问题。

LLaMA 本质上是一个 Decoder-only Transformer。如果我们把它拆开看,会发现它不是一个神秘黑箱,而是由一层一层的 Transformer Block 堆起来的。

比如:

LLaMA-7B:32 层 Transformer Block
LLaMA-13B:40 层 Transformer Block
LLaMA-33B:60 层 Transformer Block
LLaMA-65B:80 层 Transformer Block

每一层 Transformer Block 主要做两件事:

第一步:Attention
让不同 token 之间交流信息。

第二步:FFN / MLP
让每个 token 对自己的特征进行加工。

如果用一句话概括:

Attention 负责“互相交流”,FFN 负责“独立思考”。

这一篇我们把每个模块拆开讲清楚。本文会重点讲:

  1. LLaMA 为什么是 Decoder-only Transformer?
  2. 一个 Transformer Block 的整体结构是什么?
  3. RMSNorm 是什么?为什么 Pre-Norm 更稳定?
  4. Q、K、V 是什么?Attention 到底怎么算?
  5. Causal Attention 为什么不能看未来?
  6. RoPE 为什么能表示相对位置?
  7. FFN / MLP 是什么?为什么要升维再降维?
  8. SwiGLU 门控机制是怎么工作的?
  9. 一个完整 LLaMA Block 的公式是什么?
  10. 这些结构和后续 VLM 有什么关系?

一、先看整体:一层 LLaMA Block 长什么样?

先不要急着看公式,我们先看一层 LLaMA Transformer Block 的整体流程。

输入 x
 │
 │
 ▼
RMSNorm
 │
 ▼
Causal Multi-Head Self-Attention
 │
 ▼
Residual Add
 │
得到 h
 │
 │
 ▼
RMSNorm
 │
 ▼
SwiGLU FFN / MLP
 │
 ▼
Residual Add
 │
得到 y

如果写成更直观的结构:

x
├─────────────── 残差连接 ───────────────┐
│                                       │
RMSNorm                                 │
│                                       │
Causal Multi-Head Attention             │
│                                       │
└────────────── Add ◄───────────────────┘
│
h
├─────────────── 残差连接 ───────────────┐
│                                       │
RMSNorm                                 │
│                                       │
SwiGLU FFN                              │
│                                       │
└────────────── Add ◄───────────────────┘
│
y

这一层做完之后,输出 (y) 会进入下一层 Transformer Block。

所以 LLaMA 的完整模型可以理解成:

Token Embedding
↓
Transformer Block 1
↓
Transformer Block 2
↓
Transformer Block 3
↓
...
↓
Transformer Block N
↓
输出层
↓
预测下一个 token

二、LLaMA 是 Decoder-only Transformer

先解释这个名字。

2.1 Transformer 是什么?

Transformer 是一种基于 Attention 机制的神经网络结构。

它最核心的特点是:

不像 RNN 那样一个词一个词顺序处理,而是通过 Attention 直接建模 token 之间的关系。

比如一句话:

The cat sat on the mat.

Transformer 可以让 cat 直接和 satmatThe 等 token 建立联系。

对于该部分不熟悉的看客,可移步此篇博文:
结合 I love you 彻底读懂 Transformer:从输入表示到概率输出


2.2 Decoder-only 是什么?

Transformer 原始结构有两部分:

Encoder
Decoder

BERT 用的是 Encoder-only。
GPT、LLaMA 用的是 Decoder-only。

Decoder-only 的意思是:

模型只使用 Transformer Decoder 堆叠,通过自回归方式预测下一个 token。

这里的 自回归 Autoregressive,意思是:

预测当前位置时,只能依赖前面的 token,不能偷看后面的 token。

比如输入:

I love machine

模型要预测下一个 token:

learning

它只能看:

I love machine

不能提前看到真实答案 learning


2.3 LLaMA 的训练目标是什么?

LLaMA 的训练目标是 next-token prediction,也就是预测下一个 token。
语言模型训练 loss 通常写成:
L L M = − ∑ t = 1 T log ⁡ p θ ( x t ∣ x < t ) \mathcal{L}_{LM} = -\sum_{t=1}^{T}\log p_\theta(x_t \mid x_{<t}) LLM=t=1Tlogpθ(xtx<t)
这个公式必须慢慢拆开。

其中:

  • ( L L M \mathcal{L}_{LM} LLM):语言模型的损失函数,loss 越小越好;
  • (T):当前训练序列的长度;
  • (t):序列中的第 (t) 个位置;
  • ( x t x_t xt):第 (t) 个真实 token;
  • ( x < t x_{<t} x<t):第 (t) 个 token 前面的所有 token;
  • ( p θ ( x t ∣ x < t ) p_\theta(x_t \mid x_{<t}) pθ(xtx<t)):模型在看到前文 (x_{<t}) 后,预测真实 token (x_t) 的概率;
  • ( θ \theta θ):模型参数;
  • ( log ⁡ \log log):对概率取对数;
  • 负号 (-):因为我们希望真实 token 的概率越大越好,而训练优化器通常是最小化 loss。

如果模型把真实 token 的概率预测得很高,那么:

p 越大
log p 越接近 0
-loss 越小

如果模型把真实 token 的概率预测得很低,那么:

p 越小
log p 是很大的负数
-loss 就很大

所以这个公式的直观含义是:

模型要尽可能提高真实下一个 token 的概率。


三、输入 x 是什么?

在进入 Transformer Block 之前,文本已经经过了 tokenizer 和 embedding。

假设输入句子是:

I love machine learning.

Tokenizer 得到 token IDs:

[306, 5360, 4933, 6509, 29889]

Embedding 层把每个 token ID 变成向量。

Embedding 公式是:

x i = E [ t i ] x_i = E[t_i] xi=E[ti]

其中:

  • ( t i t_i ti):第 (i) 个 token 的 ID;
  • ( E E E):embedding 矩阵,是模型的可学习参数;
  • ( E [ t i ] E[t_i] E[ti]):从 embedding 矩阵中取出第 ( t i t_i ti) 行;
  • ( x i x_i xi):第 (i) 个 token 对应的向量表示。

如果 hidden size 是 4096,那么每个 token 会变成一个 4096 维向量。

假设序列长度是 5,那么输入矩阵形状是:

[5, 4096]

一般写成:

X ∈ R^{seq_len × d_model}

其中:

  • (X):整个序列的 hidden states;
  • (seq_len):序列长度;
  • ( d _ m o d e l d\_{model} d_model):模型 hidden dimension,比如 LLaMA-7B 是 4096;
  • ( R { s e q _ l e n × d _ m o d e l } R^{}\{seq\_len \times d\_model\} R{seq_len×d_model}):表示这是一个形状为 ( s e q _ l e n × d _ m o d e l seq\_len \times d\_{model} seq_len×d_model) 的实数矩阵。

四、RMSNorm:进入子层之前先稳定一下

在 LLaMA 中,每个子模块前都会先做 RMSNorm。

也就是说,LLaMA 用的是 Pre-Norm 结构。


4.1 Norm 是什么?

Norm 是 normalization,中文叫 归一化

它的作用是:

控制向量的数值尺度,让模型训练更稳定。

大模型有很多层。如果每一层输出的数值忽大忽小,后面的层会很难训练。

归一化就像是在提醒模型:

别让激活值太大,也别让它太小。

4.2 Pre-Norm 和 Post-Norm 的区别

原始 Transformer 更接近 Post-Norm

x → Sublayer → Add → Norm

公式是:

y = Norm ( x + F ( x ) ) y = \text{Norm}(x + F(x)) y=Norm(x+F(x))

其中:

  • (x):当前层输入;
  • (F(x)):子层计算结果,可以是 Attention,也可以是 FFN;
  • (x + F(x)):残差连接;
  • ( Norm ( ⋅ ) \text{Norm}(\cdot) Norm()):归一化;
  • (y):输出。

LLaMA 使用 Pre-Norm

x → Norm → Sublayer → Add

公式是:

y = x + F ( Norm ( x ) ) y = x + F(\text{Norm}(x)) y=x+F(Norm(x))

其中:

  • (x):当前层输入;
  • ( Norm ( x ) \text{Norm}(x) Norm(x)):先对输入做归一化;
  • ( F ( Norm ( x ) ) F(\text{Norm}(x)) F(Norm(x))):把归一化后的结果送入子层;
  • ( x + F ( Norm ( x ) ) x + F(\text{Norm}(x)) x+F(Norm(x))):残差连接;
  • (y):输出。

4.3 为什么 Pre-Norm 更稳定?

你可能会问:

Post-Norm 的输出不也是下一层的输入吗?为什么 Pre-Norm 更稳?

关键点不是“下一层有没有归一化”,而是:

反向传播时,梯度能不能沿着残差连接稳定传回去。

对于 Pre-Norm:

y = x + F ( Norm ( x ) ) y = x + F(\text{Norm}(x)) y=x+F(Norm(x))

这里有一条很直接的路径:

y → x

也就是说,哪怕 ( F ( Norm ( x ) ) F(\text{Norm}(x)) F(Norm(x))) 这个子层暂时学得不好,(x) 仍然可以通过残差连接直接传递下去。

这条路径很像高速公路。

而 Post-Norm:

y = Norm ( x + F ( x ) ) y = \text{Norm}(x + F(x)) y=Norm(x+F(x))

残差相加后还要经过 Norm。梯度回传时,这条残差路径被 Norm 包住了,不如 Pre-Norm 那么直接。

所以可以这样理解:

Post-Norm:残差路径上有一个 Norm 检查站。
Pre-Norm:残差路径更像一条直通高速路。

大模型层数很深,几十层甚至上百层,训练稳定性非常重要。 所以 LLaMA、GPT 类模型普遍更倾向 Pre-Norm。


4.4 RMSNorm 公式

LLaMA 使用的是 RMSNorm

RMSNorm 全称是 Root Mean Square Layer Normalization,中文可以叫 均方根归一化

公式是:

RMSNorm ( x i ) = γ i x i 1 d ∑ j = 1 d x j 2 + ϵ \text{RMSNorm}(x_i) = \gamma_i \frac{x_i}{\sqrt{\frac{1}{d}\sum_{j=1}^{d}x_j^2+\epsilon}} RMSNorm(xi)=γid1j=1dxj2+ϵ xi

逐个解释:

  • ( x = [ x 1 , x 2 , . . . , x d ] x = [x_1, x_2, ..., x_d] x=[x1,x2,...,xd]):一个 token 的 hidden vector;
  • ( x i x_i xi):这个向量的第 (i) 个维度;
  • ( d d d):hidden dimension,也就是向量维度;
  • ( ∑ j = 1 d x j 2 \sum_{j=1}^{d}x_j^2 j=1dxj2):把所有维度的平方加起来;
  • ( 1 d ∑ j = 1 d x j 2 \frac{1}{d}\sum_{j=1}^{d}x_j^2 d1j=1dxj2):所有维度平方的平均值;
  • ( 1 d ∑ j = 1 d x j 2 + ϵ \sqrt{\frac{1}{d}\sum_{j=1}^{d}x_j^2+\epsilon} d1j=1dxj2+ϵ ):均方根 RMS,( ϵ \epsilon ϵ) 是防止除零的小常数;
  • ( γ i \gamma_i γi):第 (i) 个维度的可学习缩放参数;
  • ( RMSNorm ( x i ) \text{RMSNorm}(x_i) RMSNorm(xi)):归一化后的第 (i) 个维度。

一句话理解:

RMSNorm 不减均值,只按向量整体大小做缩放。

它和 LayerNorm 的区别是:

LayerNorm:减均值 + 除标准差
RMSNorm:不减均值,只除 RMS

为什么可以不减均值?

因为在很多大模型实践中,真正关键的是控制向量尺度。
只要每层输入的整体大小稳定,训练就已经稳定很多。


五、Self-Attention:token 之间如何交流?

接下来进入最核心的模块:Self-Attention


5.1 Self-Attention 是什么?

Attention,中文叫 注意力机制

它解决的问题是:

当前 token 应该关注序列中的哪些 token?

比如句子:

The animal didn't cross the street because it was too tired.

这里的 it 指的是谁?

很可能是:

animal

模型需要让 it 去关注前面的 animal,而不是只看自己。

Attention 就是做这件事的。


5.2 Q、K、V 是什么?

Self-Attention 里,每个 token 会生成三个向量:

Q:Query
K:Key
V:Value

先解释名字:

Query,查询向量:我想找什么信息?
Key,键向量:我有什么特征可以被别人匹配?
Value,值向量:如果别人关注我,我提供什么信息?

它们由输入 (X) 乘以三个可学习矩阵得到:

Q = X W Q Q = XW_Q Q=XWQ

K = X W K K = XW_K K=XWK

V = X W V V = XW_V V=XWV

逐个解释:

  • (X):当前输入序列的 hidden states,形状通常是 ( [ s e q _ l e n , d _ m o d e l ] [seq\_len, d\_{model}] [seq_len,d_model]);
  • ( W Q W_Q WQ):Query 投影矩阵,是可学习参数;
  • ( W K W_K WK):Key 投影矩阵,是可学习参数;
  • ( W V W_V WV):Value 投影矩阵,是可学习参数;
  • (Q):所有 token 的 Query 向量;
  • (K):所有 token 的 Key 向量;
  • (V):所有 token 的 Value 向量。

注意这里有一个很容易混的点:

W Q 、 W K 、 W V W_Q、W_K、W_V WQWKWV 是训练出来的参数。
Q、K、V 是根据当前输入动态算出来的结果。

推理时,参数矩阵固定。
但是不同输入会产生不同的 Q、K、V。


5.3 Attention 分数怎么算?

Attention 的核心是计算 Query 和 Key 的相似度。

公式是:

Score = Q K T \text{Score} = QK^T Score=QKT

解释:

  • (Q):Query 矩阵;
  • (K):Key 矩阵;
  • ( K T K^T KT):Key 的转置;
  • ( Q K T QK^T QKT):每个 query 和每个 key 的点积相似度;
  • ( Score \text{Score} Score):注意力分数矩阵。

如果序列长度是 4,那么 (QK^T) 会得到一个 (4 \times 4) 的矩阵:

        key1  key2  key3  key4
query1   .     .     .     .
query2   .     .     .     .
query3   .     .     .     .
query4   .     .     .     .

每个位置表示:

当前 token 对另一个 token 的关注程度

5.4 为什么要除以 ( d k \sqrt{d_k} dk )?

Attention 公式里不是直接用 ( Q K T QK^T QKT),而是:

Q K T d k \frac{QK^T}{\sqrt{d_k}} dk QKT

其中:

  • ( d k d_k dk):Key/Query 向量的维度;
  • ( d k \sqrt{d_k} dk ):对 ( d k d_k dk) 开平方;
  • 除以 ( d k \sqrt{d_k} dk ):为了防止点积值太大。

为什么点积值会太大?

如果 Q 和 K 的维度很高,点积累加很多项,数值可能变大。
如果分数太大,softmax 会变得非常尖锐,导致梯度不稳定。

所以除以 ( d k \sqrt{d_k} dk ) 是一种缩放。

这就是 Scaled Dot-Product Attention,缩放点积注意力。


5.5 Softmax 是什么?

Attention 分数还需要经过 softmax。

Softmax 的作用是:

把一组分数变成概率分布。

比如某个 token 对其他 token 的原始分数是:

[2.0, 1.0, 0.1]

softmax 后可能变成:

[0.66, 0.24, 0.10]

这些值加起来等于 1,表示关注比例。

Softmax 公式是:

softmax ( z i ) = e z i ∑ j = 1 n e z j \text{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{n}e^{z_j}} softmax(zi)=j=1nezjezi
逐个解释:

  • ( z i z_i zi):第 (i) 个原始分数;
  • ( e z i e^{z_i} ezi):对第 (i) 个分数取指数;
  • ( n n n):这一组分数的数量;
  • ( ∑ j = 1 n e z j \sum_{j=1}^{n}e^{z_j} j=1nezj):所有分数指数值的总和;
  • ( softmax ( z i ) \text{softmax}(z_i) softmax(zi)):第 (i) 个分数对应的归一化概率。

5.6 完整 Attention 公式

完整的 Attention 公式是:

A t t e n t i o n = softmax ( Q K T d k ) V Attention = \text{softmax} \left( \frac{QK^T}{\sqrt{d_k}} \right) V Attention=softmax(dk QKT)V

逐个解释:

  • (Q):Query 矩阵,表示每个 token 想查询什么;
  • (K):Key 矩阵,表示每个 token 有什么可被匹配的特征;
  • (V):Value 矩阵,表示每个 token 真正提供的信息;
  • ( K T K^T KT):Key 矩阵的转置;
  • ( Q K T QK^T QKT):Query 和 Key 的相似度矩阵;
  • ( d k d_k dk):Query/Key 的维度;
  • ( d k \sqrt{d_k} dk ):缩放因子,防止分数过大;
  • ( softmax ( ⋅ ) \text{softmax}(\cdot) softmax()):把相似度分数转成注意力权重;
  • 最后乘以 (V):根据注意力权重,对 Value 做加权求和。

一句话理解:

Attention 先算“我应该关注谁”,再根据关注权重把别人的信息汇总过来。


六、Causal Attention:为什么不能看未来?

LLaMA 是 Decoder-only 自回归模型,所以 Attention 必须是 causal 的。

Causal,中文叫 因果的

意思是:

当前 token 只能看自己和前面的 token,不能看后面的 token。


6.1 为什么不能看未来?

因为训练任务是预测下一个 token。

假设训练序列是:

I love machine learning

模型在预测 machine 时,只能看:

I love

不能提前看到:

machine learning

如果它能看到未来,就相当于考试时偷看答案。

所以必须加 causal mask。


6.2 Causal Mask 长什么样?

假设序列有 4 个 token:

t1 t2 t3 t4

普通 self-attention 理论上每个 token 都能看所有 token:

      t1  t2  t3  t4
t1    ✓   ✓   ✓   ✓
t2    ✓   ✓   ✓   ✓
t3    ✓   ✓   ✓   ✓
t4    ✓   ✓   ✓   ✓

但 causal attention 是:

      t1  t2  t3  t4
t1    ✓   ✗   ✗   ✗
t2    ✓   ✓   ✗   ✗
t3    ✓   ✓   ✓   ✗
t4    ✓   ✓   ✓   ✓

只能看左边和自己,不能看右边。

这就是下三角矩阵。


6.3 Mask 在公式里怎么体现?

Attention 原始分数是:

S = Q K T d k S = \frac{QK^T}{\sqrt{d_k}} S=dk QKT

其中:

  • (S):attention score matrix;
  • ( Q K T QK^T QKT):Q 和 K 的点积相似度;
  • ( d k d_k dk):Q/K 的维度。

加 causal mask 后:

S ′ = S + M S' = S + M S=S+M

其中:

  • (S’):加 mask 后的分数;
  • (S):原始 attention 分数;
  • (M):mask 矩阵。

对于允许看的位置,(M) 取 0。
对于不允许看的未来位置,(M) 取一个非常大的负数,比如 ( − ∞ -\infty )。

也就是:

M i j = { 0 if  j ≤ i − ∞ if  j > i M_{ij} = \begin{cases} 0 & \text{if } j \leq i \\ -\infty & \text{if } j > i\end{cases} Mij={0if jiif j>i

逐个解释:

  • (i):当前 query 的位置;
  • (j):被看的 key 的位置;
  • ( j ≤ i j \leq i ji):说明被看的 token 在当前位置之前或就是自己,可以看;
  • (j > i):说明被看的 token 在未来,不能看;
  • ( M i j M_{ij} Mij):mask 矩阵第 (i,j) 个位置的值。

为什么用 ( − ∞ -\infty )?

因为 softmax 之后:

e^{-∞} ≈ 0

也就是说,未来 token 的注意力权重会变成 0。


七、Multi-Head Attention:为什么要多头?

LLaMA 用的是 Multi-Head Attention,多头注意力。

先解释:

Head,可以理解成一组独立的注意力视角。

一个 head 可能关注语法关系。
一个 head 可能关注实体关系。
一个 head 可能关注长距离依赖。
一个 head 可能关注标点和格式。

当然,这只是帮助理解,不是每个 head 都能被严格解释成一个固定功能。


7.1 多头注意力公式

单个 head 的计算是:

head i = Attention ( Q i , K i , V i ) \text{head}_i = \text{Attention}(Q_i,K_i,V_i) headi=Attention(Qi,Ki,Vi)
其中:

  • ( head i \text{head}_i headi):第 (i) 个注意力头的输出;
  • ( Q i Q_i Qi):第 (i) 个头对应的 Query;
  • ( K i K_i Ki):第 (i) 个头对应的 Key;
  • ( V i V_i Vi):第 (i) 个头对应的 Value;
  • ( Attention ( ⋅ ) \text{Attention}(\cdot) Attention()):缩放点积注意力计算。

多个 head 拼接起来:
MultiHead ( Q , K , V ) = Concat ( head 1 , head 2 , . . . , head h ) W O \text{MultiHead}(Q,K,V) = \text{Concat}(\text{head}_1,\text{head}_2,...,\text{head}_h)W_O MultiHead(Q,K,V)=Concat(head1,head2,...,headh)WO
逐个解释:

  • (h):head 的数量;
  • ( head 1 , head 2 , . . . , head h \text{head}_1,\text{head}_2,...,\text{head}_h head1,head2,...,headh):不同注意力头的输出;
  • ( Concat ( ⋅ ) \text{Concat}(\cdot) Concat()):把多个 head 的输出在维度上拼接起来;
  • ( W O W_O WO):输出投影矩阵,是可学习参数;
  • ( MultiHead ( Q , K , V ) \text{MultiHead}(Q,K,V) MultiHead(Q,K,V)):多头注意力最终输出。

为什么最后要乘 ( W O W_O WO)?

因为多个 head 拼接后,需要再通过一个线性层融合不同 head 的信息,并投影回模型的 hidden dimension。


八、RoPE:旋转位置编码

Attention 本身有一个问题:

它不知道 token 的顺序。

如果不加位置信息,Transformer 很难区分:

我 爱 机器 学习

和:

学习 机器 爱 我

所以必须加入位置编码。

LLaMA 使用的是 RoPE


8.1 RoPE 是什么?

RoPE 全称是 Rotary Position Embedding,中文叫 旋转位置编码

它的核心思想是:

不直接把位置向量加到 token embedding 上,而是在计算 Attention 时,对 Q 和 K 做旋转,让 Q 和 K 的点积自然包含相对位置信息。

注意:

RoPE 作用在 Q 和 K 上
一般不作用在 V 上

8.2 二维旋转公式

先看最基础的二维旋转。

给定二维向量:

[ x 1   x 2 ] \begin{bmatrix} x_1 \ x_2 \end{bmatrix} [x1 x2]

旋转角度 (\theta) 后:

x 1 ′ , x 2 ′ = [ cos ⁡ θ − sin ⁡ θ sin ⁡ θ cos ⁡ θ ] [ x 1   x 2 ] x_1', x_2' = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} \begin{bmatrix} x_1\ x_2 \end{bmatrix} x1,x2=[cosθsinθsinθcosθ][x1 x2]
逐个解释:

  • ( x 1 , x 2 x_1, x_2 x1,x2):旋转前二维向量的两个坐标;
  • ( x 1 ′ , x 2 ′ x_1', x_2' x1,x2):旋转后的两个坐标;
  • ( θ \theta θ):旋转角度;
  • ( cos ⁡ θ \cos\theta cosθ)、( sin ⁡ θ \sin\theta sinθ):三角函数;
  • 中间的 ( 2 × 2 2 \times 2 2×2) 矩阵:旋转矩阵。

这个公式表达的意思是:

向量长度不变,但方向发生旋转。


8.3 RoPE 如何作用在高维向量上?

真实模型里的 Q、K 不是二维,而是高维向量。

假设一个 head 的维度是 8:

[x1, x2, x3, x4, x5, x6, x7, x8]

RoPE 会把它两两分组:

(x1, x2)
(x3, x4)
(x5, x6)
(x7, x8)

每一组都当成一个二维向量进行旋转。

但是不同组使用不同频率。

位置为 (m) 的 token,第 (i) 组使用的旋转角度大致是:

m θ i m\theta_i mθi

其中:

  • (m):token 的位置;
  • (i):第 (i) 个二维维度组;
  • ( θ i \theta_i θi):第 (i) 组对应的旋转频率。

通常:

θ i = 10000 − 2 i / d \theta_i = 10000^{-2i/d} θi=100002i/d

逐个解释:

  • ( θ i \theta_i θi):第 (i) 个维度组的基础频率;
  • (i):维度组编号;
  • (d):当前 attention head 的维度;
  • (10000):一个常用的频率基数;
  • ( − 2 i / d -2i/d 2i/d):让不同维度组拥有不同频率。

简单理解:

有些维度转得快,用来感知短距离。
有些维度转得慢,用来感知长距离。

就像钟表:

秒针转得快
分针转得慢
时针转得更慢

多个频率组合起来,就能表达不同尺度的位置关系。


8.4 RoPE 为什么能表示相对位置?

Attention 分数本来是:

q m T k n q_m^T k_n qmTkn

其中:

  • ( q m q_m qm):位置 (m) 的 Query;
  • ( k n k_n kn):位置 (n) 的 Key;
  • ( q m T k n q_m^T k_n qmTkn):二者的点积相似度。

RoPE 会先对它们按位置旋转:

q m ′ = R m q m q_m' = R_m q_m qm=Rmqm

k n ′ = R n k n k_n' = R_n k_n kn=Rnkn

其中:

  • ( R m R_m Rm):位置 (m) 对应的旋转矩阵;
  • ( R n R_n Rn):位置 (n) 对应的旋转矩阵;
  • ( q m ′ q_m' qm):旋转后的 Query;
  • ( k n ′ k_n' kn):旋转后的 Key。

然后计算:

( R m q m ) T ( R n k n ) (R_m q_m)^T(R_n k_n) (Rmqm)T(Rnkn)
旋转矩阵有一个重要性质:

( R m q m ) T ( R n k n ) = q m T R n − m k n (R_m q_m)^T(R_n k_n) =q_m^T R_{n-m} k_n (Rmqm)T(Rnkn)=qmTRnmkn
这个公式很关键。

逐个解释:

  • 左边 ( ( R m q m ) T ( R n k n ) (R_m q_m)^T(R_n k_n) (Rmqm)T(Rnkn)):位置 (m) 的 Query 和位置 (n) 的 Key 分别旋转后再点积;
  • 右边 ( q m T R n − m k n q_m^T R_{n-m} k_n qmTRnmkn):最终效果和 (n-m) 有关;
  • (n-m):两个 token 的相对距离。

所以 RoPE 的核心优势是:

表面上给 Q、K 注入的是绝对位置,点积之后体现出来的是相对位置差。

这就是为什么 RoPE 很适合自回归语言模型。


8.5 旋转一圈会不会重合?

这个问题非常关键。

答案是:

单个频率确实可能重合,但 RoPE 使用多组不同频率,整体不容易在正常上下文长度内完全重合。

二维旋转有周期性:

cos ⁡ ( θ ) = cos ⁡ ( θ + 2 π ) \cos(\theta)=\cos(\theta+2\pi) cos(θ)=cos(θ+2π)

sin ⁡ ( θ ) = sin ⁡ ( θ + 2 π ) \sin(\theta)=\sin(\theta+2\pi) sin(θ)=sin(θ+2π)

这说明如果只看一个二维平面,旋转一圈确实会回到原方向。

但是 RoPE 不是只有一个二维平面。它是很多二维组,每组频率不同。

想让整个高维向量完全重合,需要所有频率同时回到相同状态。
这就像多个不同速度的指针同时回到同一个位置,周期会非常长。

所以可以这样记:

单个频率:会周期重复。
多个频率组合:整体重复周期大大拉长。
RoPE 主要服务于相对位置建模,而不是给每个位置发唯一身份证。

不过 RoPE 也不是完美的。
当序列长度远远超过训练长度时,RoPE 可能出现长上下文外推问题。后来的很多长上下文模型会使用 RoPE scaling、NTK-aware scaling、YaRN 等方法改进它。

这部分可以以后在长上下文模型里专门讲。


九、Residual Connection:残差连接

Attention 算完后,LLaMA 不会直接把 Attention 输出作为最终结果,而是会和原输入相加。

公式是:

h = x + Attention ( RMSNorm ( x ) ) h = x + \text{Attention}(\text{RMSNorm}(x)) h=x+Attention(RMSNorm(x))

逐个解释:

  • (x):当前 Transformer Block 的输入;
  • ( RMSNorm ( x ) \text{RMSNorm}(x) RMSNorm(x)):先对输入做 RMSNorm;
  • ( Attention ( RMSNorm ( x ) ) \text{Attention}(\text{RMSNorm}(x)) Attention(RMSNorm(x))):Attention 子层的输出;
  • ( x + Attention ( RMSNorm ( x ) ) x + \text{Attention}(\text{RMSNorm}(x)) x+Attention(RMSNorm(x))):残差连接;
  • (h):Attention 子层之后的输出。

残差连接的作用是:

保留原始信息
稳定梯度传播
让深层网络更容易训练

可以简单理解为:

子层学到的是对原输入的“补充修改”,而不是完全重写原输入。


十、FFN / MLP:每个 token 自己的“思考”

Attention 之后,token 之间已经交流过信息。

接下来进入 FFN / MLP。


10.1 FFN 是什么?

FFN 全称是 Feed-Forward Network,中文叫 前馈神经网络

在 Transformer 里,它通常指:

对每个 token 的 hidden vector 独立进行非线性加工的模块。

它不是单纯的激活层,而是一个完整的小网络。

普通 FFN 公式是:

FFN ( x ) = W 2 σ ( W 1 x ) \text{FFN}(x)=W_2\sigma(W_1x) FFN(x)=W2σ(W1x)

逐个解释:

  • (x):某个 token 的 hidden vector;
  • ( W 1 W_1 W1):第一层线性变换矩阵,通常负责升维;
  • ( σ \sigma σ):激活函数,比如 ReLU、GELU、SiLU;
  • ( W 2 W_2 W2):第二层线性变换矩阵,通常负责降维;
  • ( FFN ( x ) \text{FFN}(x) FFN(x)):FFN 的输出。

流程是:

x
↓
Linear 升维
↓
Activation 非线性激活
↓
Linear 降维
↓
输出

10.2 MLP 是什么?

MLP 全称是 Multi-Layer Perceptron,中文叫 多层感知机

它是最基础的神经网络结构:

Linear
↓
Activation
↓
Linear

在 Transformer 语境里,很多论文会把 FFN 和 MLP 混着叫。

更准确地说:

MLP:通用神经网络名字。
FFN:Transformer 里那个 position-wise 的 MLP 子层。

所以:

Transformer 里的 FFN 本质上就是一个对每个 token 独立应用的 MLP。


10.3 position-wise 是什么意思?

position-wise,可以翻译成 按位置独立处理

这里的 position 指的是 token 在序列中的位置。

假设序列有 4 个 token:

t1, t2, t3, t4

它们的 hidden states 是:

x1, x2, x3, x4

FFN 做的是:

FFN(x1)
FFN(x2)
FFN(x3)
FFN(x4)

它不会在 FFN 里直接让 (x1) 和 (x3) 交流信息。

token 之间的交流主要发生在 Attention 里。
FFN 是对每个 token 自己的表示做加工。

但是注意:

每个位置使用的是同一套 FFN 参数。

不是:

x1 用 FFN1
x2 用 FFN2
x3 用 FFN3

而是:

x1、x2、x3、x4 都用同一个 FFN

所以 position-wise 的完整意思是:

每个 token 独立通过 FFN,但所有 token 共享同一套 FFN 参数。


10.4 为什么 FFN 要升维?

普通 Transformer FFN 会先升维,再降维。

比如:

4096 → 11008 → 4096

为什么要这样做?

因为如果只是:

4096 → 4096

表达能力有限。

升维可以理解为:

临时把 token 表示投影到更大的特征空间,让模型有更多维度去展开、组合、筛选信息。

类比一下:

原来只有 4096 个抽屉放信息。
升维后临时打开 11008 个抽屉。
模型可以把信息拆得更细、更丰富。
最后再压回 4096 维,继续进入下一层。

十一、SwiGLU:LLaMA 的门控 FFN

LLaMA 没有使用最普通的 ReLU FFN,而是使用了 SwiGLU


11.1 Gate 是什么?

Gate,中文叫 门控机制

它的核心思想是:

不是什么信息都无条件通过,而是让模型学习一个“门”,决定哪些特征通过,哪些特征被抑制。

举个简单例子:

候选信息 u = [10, 5, -3, 8]
门控值 g = [0.9, 0.1, 0.0, 0.7]

逐元素相乘:

u ⊙ g = [9, 0.5, 0, 5.6]

这表示:

第 1 个特征:强烈保留
第 2 个特征:大幅削弱
第 3 个特征:关掉
第 4 个特征:部分保留

11.2 GLU 是什么?

GLU 全称是 Gated Linear Unit,中文叫 门控线性单元

普通 FFN 是一条路:

x → Linear → Activation → Linear

GLU 变成两条路:

一路生成候选信息
一路生成门控值
然后相乘

GLU 的经典公式是:
GLU ( x ) = ( x W a ) ⊙ σ ( x W b ) \text{GLU}(x) = (xW_a)\odot\sigma(xW_b) GLU(x)=(xWa)σ(xWb)
逐个解释:

  • (x):输入向量;
  • ( W a W_a Wa):候选信息投影矩阵,是可学习参数;
  • ( x W a xW_a xWa):候选信息;
  • ( W b W_b Wb):门控投影矩阵,是可学习参数;
  • ( x W b xW_b xWb):门控分支的线性输出;
  • ( σ ( ⋅ ) \sigma(\cdot) σ()):sigmoid 函数,把值压到 0 到 1;
  • ( ⊙ \odot ):逐元素相乘;
  • ( GLU ( x ) \text{GLU}(x) GLU(x)):门控后的输出。

这里的关键是:

W_a 和 W_b 是参数。
xW_a 和 σ(xW_b) 是根据当前输入算出来的动态结果。

11.3 SiLU / Swish 是什么?

SwiGLU 里的 “Swi” 来自 Swish,也常叫 SiLU

SiLU 公式是:

SiLU ( z ) = z ⋅ σ ( z ) \text{SiLU}(z)=z\cdot\sigma(z) SiLU(z)=zσ(z)

逐个解释:

  • (z):输入值;
  • ( σ ( z ) \sigma(z) σ(z)):sigmoid 函数;
  • ( z ⋅ σ ( z ) z\cdot\sigma(z) zσ(z)):输入 (z) 乘以一个 0 到 1 之间的门控值;
  • ( SiLU ( z ) \text{SiLU}(z) SiLU(z)):SiLU 激活后的结果。

Sigmoid 公式是:

σ ( z ) = 1 1 + e − z \sigma(z)=\frac{1}{1+e^{-z}} σ(z)=1+ez1

逐个解释:

  • (z):输入值;
  • ( e − z e^{-z} ez):自然指数;
  • ( 1 + e − z 1+e^{-z} 1+ez):分母;
  • ( σ ( z ) \sigma(z) σ(z)):输出范围在 0 到 1 之间。

SiLU 比 ReLU 更平滑。

ReLU 是:

ReLU ( z ) = max ⁡ ( 0 , z ) \text{ReLU}(z)=\max(0,z) ReLU(z)=max(0,z)

其中:

  • 如果 (z > 0),输出 (z);
  • 如果 ( z ≤ 0 z \leq 0 z0),输出 0。

ReLU 很简单粗暴。
SiLU 则是平滑地控制信息通过。


11.4 SwiGLU 公式

LLaMA 中的 SwiGLU 可以写成:

SwiGLU ( x ) = W down ( SiLU ( W gate x ) ⊙ W up x ) \text{SwiGLU}(x)= W_{\text{down}} \left( \text{SiLU}(W_{\text{gate}}x) \odot W_{\text{up}}x \right) SwiGLU(x)=Wdown(SiLU(Wgatex)Wupx)

逐个解释:

  • (x):输入 token 的 hidden vector;
  • ( W gate W_{\text{gate}} Wgate):门控矩阵,是可学习参数;
  • ( W gate x W_{\text{gate}}x Wgatex):门控分支的线性输出;
  • ( SiLU ( W gate x ) \text{SiLU}(W_{\text{gate}}x) SiLU(Wgatex)):经过 SiLU 激活后的门控值;
  • ( W up W_{\text{up}} Wup):升维矩阵,是可学习参数;
  • ( W up x W_{\text{up}}x Wupx):候选特征;
  • ( ⊙ \odot ):逐元素相乘;
  • ( W down W_{\text{down}} Wdown):降维矩阵,是可学习参数;
  • ( W down ( ⋅ ) W_{\text{down}}(\cdot) Wdown()):把中间高维特征投影回 hidden size;
  • ( SwiGLU ( x ) \text{SwiGLU}(x) SwiGLU(x)):SwiGLU FFN 的输出。

用流程图表示:

输入 x
 │
 ├── W_gate x → SiLU → 门控值 g
 │
 └── W_up x          → 候选特征 u
          │
          ▼
       g ⊙ u
          │
          ▼
       W_down
          │
          ▼
        输出

11.5 门控矩阵是固定的吗?

训练时不是固定的,训练完后固定。

具体来说:

训练阶段:
W_gate、W_up、W_down 都是可学习参数,会被 optimizer 更新。

推理阶段:
W_gate、W_up、W_down 固定不变。

但是注意:

参数固定,不代表门控值固定。

门控值是:

g = SiLU ( W gate x ) g = \text{SiLU}(W_{\text{gate}}x) g=SiLU(Wgatex)

其中:

  • ( W gate W_{\text{gate}} Wgate):训练好的固定参数;
  • (x):当前输入 token 的 hidden vector;
  • (g):根据当前输入动态算出来的门控值。

不同输入 (x) 不同,得到的 (g) 也不同。

所以:

W_gate 是固定参数。
gate value 是动态结果。

这点非常关键。


11.6 参数和语义是什么关系?

不能简单理解为:

某一个参数 = 某一个语义

比如不能说:

W_gate 里的某个数字就是“金融语义”
另一个数字就是“河岸语义”

更准确的理解是:

语义是分布在大量参数和特征维度组合中的。

这叫 Distributed Representation,分布式表示

比如单词 bank 有两个意思:

银行
河岸

模型看到:

The bank approved the loan.

Attention 会让 bank 关注 loanapproved 等词。
这时 bank 的 hidden state 会更偏向金融语义。

模型看到:

The bank near the river collapsed.

Attention 会让 bank 关注 rivercollapsed 等词。
这时 bank 的 hidden state 会更偏向河岸语义。

然后 FFN/SwiGLU 根据这个上下文表示 (x),动态计算门控值,决定哪些内部特征要保留,哪些要抑制。

所以可以这样理解:

Attention 先根据上下文调整 token 表示。
SwiGLU 再根据这个表示筛选内部特征。

十二、FFN 子层的残差连接

FFN 输出后,也会做残差连接。

公式是:

y = h + FFN ( RMSNorm ( h ) ) y = h + \text{FFN}(\text{RMSNorm}(h)) y=h+FFN(RMSNorm(h))

逐个解释:

  • (h):Attention 子层之后的 hidden state;
  • ( RMSNorm ( h ) \text{RMSNorm}(h) RMSNorm(h)):进入 FFN 前先做 RMSNorm;
  • ( FFN ( RMSNorm ( h ) ) \text{FFN}(\text{RMSNorm}(h)) FFN(RMSNorm(h))):FFN/SwiGLU 子层输出;
  • ( h + FFN ( RMSNorm ( h ) ) h + \text{FFN}(\text{RMSNorm}(h)) h+FFN(RMSNorm(h))):残差连接;
  • (y):当前 Transformer Block 的最终输出。

这一步和 Attention 子层类似:

FFN 学到的是对 (h) 的补充加工,而不是完全替换 (h)。


十三、一个完整 LLaMA Block 的公式

现在我们把一层 LLaMA Block 串起来。

第一步,Attention 子层:

h = x + Attention ( RMSNorm ( x ) ) h=x+\text{Attention}(\text{RMSNorm}(x)) h=x+Attention(RMSNorm(x))

逐个解释:

  • (x):当前 block 的输入;
  • ( RMSNorm ( x ) \text{RMSNorm}(x) RMSNorm(x)):对子层输入做归一化;
  • ( Attention ( RMSNorm ( x ) ) \text{Attention}(\text{RMSNorm}(x)) Attention(RMSNorm(x))):因果多头自注意力输出;
  • ( x + Attention ( RMSNorm ( x ) ) x + \text{Attention}(\text{RMSNorm}(x)) x+Attention(RMSNorm(x))):残差连接;
  • (h):Attention 子层后的中间结果。

第二步,FFN 子层:

y = h + FFN ( RMSNorm ( h ) ) y= h + \text{FFN}(\text{RMSNorm}(h)) y=h+FFN(RMSNorm(h))

逐个解释:

  • (h):Attention 子层后的中间结果;
  • ( RMSNorm ( h ) \text{RMSNorm}(h) RMSNorm(h)):进入 FFN 前先归一化;
  • ( FFN ( RMSNorm ( h ) ) \text{FFN}(\text{RMSNorm}(h)) FFN(RMSNorm(h))):SwiGLU FFN 输出;
  • ( h + FFN ( RMSNorm ( h ) ) h + \text{FFN}(\text{RMSNorm}(h)) h+FFN(RMSNorm(h))):残差连接;
  • (y):当前 block 的最终输出。

所以完整流程就是:

输入 x
↓
RMSNorm(x)
↓
Causal Multi-Head Attention
↓
和 x 残差相加,得到 h
↓
RMSNorm(h)
↓
SwiGLU FFN
↓
和 h 残差相加,得到 y

一句话总结:

LLaMA Block = Pre-Norm Attention + Residual + Pre-Norm SwiGLU FFN + Residual。


十四、用代码伪代码理解一层 LLaMA

下面写一个简化伪代码,帮助理解。

def llama_block(x):
    # 1. Attention 子层
    norm_x = RMSNorm(x)
    attn_out = CausalMultiHeadAttention(norm_x)
    h = x + attn_out

    # 2. FFN 子层
    norm_h = RMSNorm(h)
    ffn_out = SwiGLU_FFN(norm_h)
    y = h + ffn_out

    return y

如果把 Attention 展开一点:

def attention(x):
    Q = x @ W_q
    K = x @ W_k
    V = x @ W_v

    Q = RoPE(Q)
    K = RoPE(K)

    scores = Q @ K.T / sqrt(d_k)
    scores = scores + causal_mask

    weights = softmax(scores)
    out = weights @ V

    return out @ W_o

如果把 SwiGLU 展开一点:

def swiglu_ffn(x):
    gate = SiLU(x @ W_gate)
    up = x @ W_up
    hidden = gate * up
    out = hidden @ W_down
    return out

注意这里的 @ 表示矩阵乘法,* 表示逐元素相乘。


十五、这些模块各自负责什么?

我们最后把所有模块放到一张表里。

模块 中文名 作用
Tokenizer 分词器 把文本变成 token ID
Embedding 嵌入层 把 token ID 变成向量
RMSNorm 均方根归一化 控制向量尺度,稳定训练
Pre-Norm 前归一化 让残差路径更稳定
Q/K/V 查询/键/值 Attention 中的信息匹配与汇总
Causal Attention 因果注意力 当前 token 只能看过去,不能看未来
Multi-Head Attention 多头注意力 从多个子空间建模 token 关系
RoPE 旋转位置编码 让 Attention 感知相对位置
Residual 残差连接 保留原信息,稳定梯度
FFN / MLP 前馈网络/多层感知机 对每个 token 的特征进行非线性加工
SwiGLU 门控 FFN 用门控机制筛选特征

十六、新手常见误区

误区一:FFN 就是激活层

不对。

FFN 不是单纯激活层,而是:

Linear 升维 + 激活/门控 + Linear 降维

激活函数只是 FFN 里面的一步。


误区二:position-wise 表示每个位置有独立参数

不对。

position-wise 的意思是:

每个 token 独立处理
但所有 token 共享同一套 FFN 参数

误区三:门控矩阵推理时还会变

不对。

推理时门控矩阵参数固定。
但门控值会根据输入动态变化。

也就是:

W_gate 固定
SiLU(W_gate x) 动态变化

误区四:RoPE 是给每个位置一个绝对编号

不完全对。

RoPE 表面上根据绝对位置旋转 Q/K,
但在 Q/K 点积中体现为相对位置差。

它更重要的作用是:

让 attention score 感知 token 之间的距离

十七、总结

这一篇我们从一层 LLaMA Transformer Block 出发,拆解了它的核心组件。

LLaMA 的一层可以写成两个公式:

h = x + Attention ( RMSNorm ( x ) ) h= x + \text{Attention}(\text{RMSNorm}(x)) h=x+Attention(RMSNorm(x))

y = h + FFN ( RMSNorm ( h ) ) y= h + \text{FFN}(\text{RMSNorm}(h)) y=h+FFN(RMSNorm(h))

其中:

  • (x):当前 block 输入;
  • (h):Attention 后的中间结果;
  • (y):当前 block 输出;
  • ( RMSNorm \text{RMSNorm} RMSNorm):控制输入尺度;
  • ( Attention \text{Attention} Attention):让 token 之间交流;
  • ( FFN \text{FFN} FFN):对每个 token 的特征进行加工;
  • 残差连接:保留原始信息,稳定训练。

更具体地说:

RMSNorm:稳定训练
Attention:token 间交流
Causal Mask:防止偷看未来
RoPE:注入相对位置信息
Residual:保留原信息
FFN/MLP:单 token 特征加工
SwiGLU:门控筛选特征

一句话总结:

LLaMA 的 Transformer Block 并不神秘,它就是让 token 先通过 Attention 和上下文交流,再通过 SwiGLU FFN 对自身表示进行加工,最后通过残差连接稳定地传递到下一层。

到这里,我们已经理解了 LLaMA 的结构基础。

下一篇我们继续讲训练部分:

LLaMA 为什么小而强?从 Chinchilla Scaling Law 到 AdamW,一文讲清大模型训练配方。

更多推荐