大模型基础扫盲------从文本到 Token、Embedding、QKV 与 Transformer 大模型处理文本的全流程解析(一)-CSDN博客

大模型基础扫盲------从文本到 Token、Embedding、QKV 与 Transformer(不定期优化修改)大模型处理文本的全流程解析(二)-CSDN博客

大模型基础扫盲------从文本到 Token、Embedding、QKV 与 Transformer(不定期优化修改)大模型处理文本的全流程解析(四)-CSDN博客

11. 多头注意力:从多个角度理解上下文

实际 Transformer 不只有一组 Q、K、V,而是有多组。

这就是多头注意力。

不同的头可能关注不同信息:

Head 1 可能关注语法关系
Head 2 可能关注指代关系
Head 3 可能关注位置关系
Head 4 可能关注语义相似关系

类比:

看一幅画,
有人看构图,
有人看色彩,
有人看笔触,
有人看主题。

每个头就是一个观察角度。


11.1 维度怎么切

多头注意力不是把维度复制很多份,而是把总维度切成多个小维度。

假设:

项目

数值

模型总维度 d_model

4096

头数 h

32

每个头维度 d_head

4096 / 32 = 128

每个头在 128 维的子空间里工作。

总参数量不会因为头数变多而变成 32 倍。


11.2 GQA:现代大模型的注意力优化

标准多头注意力 MHA 中:

Q、K、V 各有 h 个头。

但许多现代大模型使用:

GQA,Grouped Query Attention
MQA,Multi-Query Attention

目的之一是减少 KV Cache。

注意力类型

Q 头数

K/V 头数

特点

MHA,标准

32

32

经典,KV Cache 大

GQA,分组

32

4 或 8

减少 KV Cache

MQA,极端

32

1

KV Cache 最小,精度可能略降

各模型实际配置差异较大。

常见示例:

模型

Q heads

KV heads

注意力类型

LLaMA-7B,原版

32

32

标准 MHA

LLaMA-2-7B

32

32

标准 MHA

LLaMA-3-8B

32

8

GQA

Mistral-7B

32

8

GQA

Qwen2-7B

28

4

GQA

 MLOps 视角:

具体请以 HuggingFace config.json 中字段为准:

num_attention_heads
num_key_value_heads

其中:

num_key_value_heads 直接决定 KV Cache 显存大小。

11.3 多头结果如何合并

每个头得到一个输出向量。

所有头的输出拼接起来,再经过一个线性投影矩阵 W_O

MultiHead(Q, K, V)
= Concat(head_1, ..., head_h) · W_O

其中每个头:

head_i = Attention(Q_i, K_i, V_i)

例如:

32 个头 × 128 维 = 4096 维

经过:

W_O

仍然得到:

4096 维

W_O 的作用是:

把不同子空间的信息融合回统一的主维度空间。

11.4 GQA 为什么能减少 KV Cache

标准 MHA 中:

如果有 32 个 Q 头,
通常也有 32 个 K 头和 32 个 V 头。

也就是:

Q heads = 32
K heads = 32
V heads = 32

每个头都有自己的 K、V。
推理时,每一层都要缓存这些 K、V。
这就是 KV Cache。

现代模型为了降低推理显存,经常使用 GQA。

例如:

Q heads = 32
KV heads = 8

这意味着:

K 和 V 只有 8 组。
32 个 Q 头会分组共享这 8 组 KV。

例如:

Q head 0~3 共享 KV head 0
Q head 4~7 共享 KV head 1
Q head 8~11 共享 KV head 2
...

这样做的直接好处是:

KV Cache 显存大约降低到原来的:

8 / 32 = 1/4

相比标准 MHA,显存压力明显下降。

所以在长上下文、高并发推理场景中,GQA 非常常见。

 MLOps 视角
估算 KV Cache 时,必须使用:

num_key_value_heads

而不是:

num_attention_heads

否则很容易严重高估或低估显存。


12. 残差连接与归一化

Transformer 不是一层就结束。
实际模型通常有几十层。

为了让深层网络能训练,每层通常会使用:

残差连接
归一化

12.1 残差连接,Residual Connection

公式:

X_out = X + F(X)

其中 F 可以是注意力模块,也可以是 FFN。

类比:

你抄写一篇文章,这是原始信息 X。
然后在旁边写批注,这是 F(X)。
最终你手里有原文 + 批注。

即使批注写得不好,原文还在,信息不会丢。

如果没有残差连接,深层网络中信息可能:

逐层衰减
逐层爆炸
难以训练

12.2 归一化:LayerNorm 与 RMSNorm

归一化的作用是把向量各维度的数值拉到一个更稳定的范围,缓解层间数值范围变化过大的问题。

类比:

每次考试后把分数标准化。
避免有的科目满分 100,有的满分 1000,导致计算失衡。

标准 LayerNorm

原始 Transformer 常用 LayerNorm。

公式:

LayerNorm(x)
= γ ⊙ (x - μ) / sqrt(σ² + ε) + β

其中:

μ 是均值
σ² 是方差
γ、β 是可学习参数
ε 是防止除零的极小值

RMSNorm

许多现代大模型使用 RMSNorm,例如 LLaMA、Qwen、Mistral 等常见模型。

公式:

RMSNorm(x)
= γ ⊙ x / sqrt((1 / d) · sum_i x_i² + ε)

它去掉了 LayerNorm 中“减去均值”的步骤,只做缩放。

可以理解为:

LayerNorm:中心化 + 缩放
RMSNorm:只做缩放

效果通常接近,但 RMSNorm 计算更简单。


 MLOps 视角:

RMSNorm 省去求均值的计算,在 GPU 上更容易与前面的线性层进行算子融合。

如果你看 vLLM、TensorRT-LLM、SGLang 等推理框架源码,会看到很多定制的:

rms_norm CUDA kernel

这是推理加速的关键一环。


12.3 Pre-LN 与 Post-LN

不同模型归一化的位置不同。

Pre-LN,当前主流

X + Attention(RMSNorm(X))

以及:

X + FFN(RMSNorm(X))

Post-LN,原始 Transformer

LayerNorm(X + Attention(X))

当前主流开源 LLM 多使用:

Pre-LN + RMSNorm

但并非绝对,具体以模型实现为准。

12.4 主流 decoder block 的伪代码表达

以当前常见的 Pre-LN + RMSNorm 结构为例,一个 Transformer block 可以写成:

residual = x
x = rms_norm(x)
x = attention(x)
x = residual + x

residual = x
x = rms_norm(x)
x = ffn(x)
x = residual + x

也可以简写成:

x = x + Attention(RMSNorm(x))
x = x + FFN(RMSNorm(x))

这里的 Attention 内部包括:

Q/K/V 投影
RoPE 或其他位置信息注入
QK^T
缩放
mask
softmax
加权 V
多头合并
W_O 输出投影

FFN 内部包括:

升维
门控或激活函数
降维

 MLOps 视角
RMSNorm 虽然看起来只是一个小操作,但它在每一层都会执行。
所以在高性能推理框架中,经常会把 RMSNorm 和前后算子做融合,减少 kernel launch 和显存读写。

例如:

fused rms norm
fused add rms norm
fused attention output rms norm

这些优化对 Decode 阶段尤其重要,因为 Decode 阶段经常是 memory-bound。


13. 前馈网络 FFN:每个 Token 自己的加工

Transformer 每一层除了注意力,还有一个前馈网络 FFN,也叫 MLP。


13.1 注意力和 FFN 的分工

模块

作用

类比

Attention

让 token 之间互相交流信息

查资料、问别人

FFN

对每个 token 的表示做进一步非线性加工

自己思考、消化、总结

注意力负责上下文中“找信息”。
FFN 负责把找来的信息进一步加工。


13.2 传统 FFN vs 现代 FFN,SwiGLU

传统 FFN

原始 Transformer、BERT 常用:

FFN(x) = W_2 · ReLU(W_1 x + b_1) + b_2

两个大矩阵:

W_1
W_2

结构是:

升维 → 激活 → 降维

例如:

4096 → 16384 → 4096

现代 FFN,SwiGLU

LLaMA、Qwen、Mistral 等模型常见 SwiGLU 风格 FFN。

公式可以写成:

FFN(x)
= (SiLU(x · W_gate) ⊗ (x · W_up)) · W_down

三个大矩阵:

W_gate
W_up
W_down

其中:

⊗ 表示逐元素相乘。

门控机制可以理解为:

网络自己学会哪些信息该通过,哪些信息该抑制。

W_gate 决定开关,W_up 提供内容。
两者逐元素相乘后,再由 W_down 降维。


13.3 为什么 SwiGLU 的隐藏层维度不是 4 倍?

传统 FFN 用 2 个大矩阵。

如果:

d_model = 4096
d_ff = 4 × d_model = 16384

参数量是:

2 × 4096 × 16384

SwiGLU 用 3 个大矩阵。

为了保持总参数量大致相当,隐藏层维度通常缩小为约:

8 / 3 × d_model

例如:

类型

d_model

FFN 隐藏层维度

矩阵数

参数量

传统 FFN,4×

4096

16384

2

2 × 4096 × 16384 ≈ 134M

SwiGLU,约 8/3×

4096

11008

3

3 × 4096 × 11008 ≈ 135M

两者参数量接近:

3 × 4096 × 11008 ≈ 2 × 4096 × 16384

 MLOps 视角:

当你在 HuggingFace 的 config.json 中看到:

"intermediate_size": 11008

不要奇怪为什么不是:

4096 × 4 = 16384

因为这可能使用的是 SwiGLU 风格 FFN,有 3 个矩阵。


13.4 FFN 参数量占比

如果以一个标准 decoder block 为例,并假设:

d_model = 4096
Attention 为标准 MHA,即 KV 头数等于 Q 头数
FFN 为 SwiGLU,intermediate_size = 11008

大致参数如下:

模块

参数量

计算

Attention

≈ 67M

4 × 4096²

FFN,SwiGLU

≈ 135M

3 × 4096 × 11008

合计

≈ 202M

FFN 占比:

135 / 202 ≈ 67%

所以在标准 MHA 的 block 中:

FFN 通常占单个 Transformer block 参数量的约三分之二。

如果使用 GQA:

Attention 参数会更少。
FFN 占比可能更高。

整模型的 FFN 占比还要看:

词表大小
embedding 大小
是否 Weight Tying
是否使用 MoE
是否有额外视觉 / 音频模块

常见范围大致是:

约一半到三分之二,甚至更高。

完整的一层包括:

注意力
FFN
残差连接
归一化

不能把 Transformer 简单理解成“只有注意力”。

13.5 SwiGLU 更清晰的公式表达

现代开源大模型常见 SwiGLU 风格 FFN。

它可以理解成三个矩阵:

W_gate
W_up
W_down

计算过程可以写成:

h_gate = x · W_gate
h_up = x · W_up

h = SiLU(h_gate) ⊗ h_up

output = h · W_down

其中:

SiLU 是激活函数。
⊗ 表示逐元素相乘。
W_gate 控制“哪些信息应该通过”。
W_up 提供“通过的内容”。
W_down 再把高维中间表示映射回模型主维度。

也就是说:

FFN 不只是简单升维降维。
它还通过门控机制让网络学会选择性地保留或抑制信息。

为什么 intermediate_size 经常不是 hidden_size 的 4 倍?

传统 FFN 通常有两个大矩阵:

W_1
W_2

如果:

hidden_size = 4096

FFN 中间维度经常是:

4 × 4096 = 16384

参数量大约是:

2 × 4096 × 16384

SwiGLU 有三个大矩阵:

W_gate
W_up
W_down

为了让总参数量不要增加太多,中间维度通常会缩小。

理论上大约是:

intermediate_size ≈ 8/3 × hidden_size

例如:

hidden_size = 4096

那么:

8/3 × 4096 ≈ 10922.67

但实际工程中,维度通常要对齐,方便 GPU 计算。
所以常见取值可能是:

11008

这就是为什么你在 config.json 里看到:

"hidden_size": 4096,
"intermediate_size": 11008

不要奇怪它不是 16384。
这通常说明模型使用了 SwiGLU 风格 FFN。


14. Transformer Block 与多层堆叠

14.1 一个 Block 的结构

以当前主流的 Pre-LN + RMSNorm 为例:

X_1 = X + Attention(RMSNorm(X))
X_2 = X_1 + FFN(RMSNorm(X_1))

一层可以理解为:

归一化
注意力
残差
归一化
FFN
残差

14.2 多层堆叠

实际大模型会把 Block 堆叠很多层。

模型规模

层数

小型

几层到十几层

中型

几十层

大型

几十层到上百层

常见配置:

24 层
32 层
40 层
80 层
96 层

粗略理解:

层级

可能学到的内容

浅层

局部搭配、词法、简单共现

中层

短语结构、句法关系

深层

抽象语义、指代、推理倾向

注意:这只是经验性理解,并不是模型内部有明确分层规则。


14.3 模型参数量的直观感受

模型

参数量

FP16 显存,仅权重

典型层数

d_model

1B

10 亿

~2 GB

~24 层

2048

7B

70 亿

~14 GB

32 层

4096

13B

130 亿

~26 GB

40 层

5120

70B

700 亿

~140 GB

80 层

8192

快速估算:

参数量 × 2 字节,FP16/BF16 ≈ 权重显存

但推理时还要加上:

KV Cache
激活值
临时缓冲

14.4 config.json:MLOps 必看的模型配置字段

 部署任何模型前,第一件事就是看 config.json

字段

含义

MLOps 关注点

hidden_size

模型主维度 d_model

决定向量宽度

num_hidden_layers

Transformer 层数

影响深度和显存

num_attention_heads

Q 头数

注意力并行切分

num_key_value_heads

KV 头数

直接决定 KV Cache 大小

intermediate_size

FFN 中间维度

判断 FFN 参数量

vocab_size

词表大小

embedding / LM Head 显存

tie_word_embeddings

是否绑定输入输出 embedding

影响显存

max_position_embeddings

最大位置长度

长上下文参考

rope_theta / rope_scaling

RoPE 相关参数

长文本外推

torch_dtype

权重精度

FP16/BF16/FP32 显存估算

hidden_act

激活函数类型

辅助判断 FFN / 激活风格

14.5 一个 Block 的完整信息流

以一个 token 序列为例:

输入:
X: [batch_size, seq_len, hidden_size]

第一步:归一化

输入:
X: [batch_size, seq_len, hidden_size]

第二步:注意力

attn_output = Attention(normed_x)

Attention 内部会完成:

Q = normed_x · W_Q
K = normed_x · W_K
V = normed_x · W_V

如果是 RoPE:

对 Q、K 施加旋转位置编码。

然后:

scores = Q K^T / sqrt(d_k)
scores = mask(scores)
weights = softmax(scores)
attn_output = weights · V

多头结果拼接后:

attn_output = attn_output · W_O

第三步:第一次残差连接

X = X + attn_output

第四步:第二次归一化

normed_x = RMSNorm(X)

第五步:FFN

ffn_output = FFN(normed_x)

第六步:第二次残差连接

X = X + ffn_output

最终:

X: [batch_size, seq_len, hidden_size]

这个输出会传给下一层 Transformer Block。

注意:

每一层之后,token 的向量都会被更新。
同一个 token 在第 1 层、第 10 层、第 30 层的表示可能完全不同。

这也是为什么:

初始 embedding 只是起点。
深层 hidden state 才是模型真正用于预测和理解的语义表示。

15. 训练目标:预测下一个 Token

15.1 训练数据有多大

现代大模型通常用几万亿 token 的文本训练。

直观感受:

LLaMA-2 用了约 2 万亿 token。
一些更大的模型使用 10 万亿+ token。

“大模型”的“大”,既指:

参数量大

也指:

训练数据大

15.2 训练任务

给模型一句话:

我 吃 鱼

训练时会同时学习多个位置的预测。

输入上下文

目标下一个 token

我 吃

我 吃 鱼

[EOS] 或结束符号

模型输出词表上的概率分布。

如果真实下一个 token 是“鱼”,模型就应该给“鱼”更高概率。


15.3 交叉熵损失

单个位置的 loss 可以理解为:

loss = -log p(y)

其中:

p(y) 是模型给正确 token 的概率。

示例:

模型给正确 token 的概率

损失

0.9

较低

0.5

中等

0.01

很高

训练就是不断调整参数,让 loss 越来越小。


15.4 哪些参数会被更新

全参数训练时,通常会更新:

embedding table
可学习位置编码,如果有
W_Q
W_K
W_V
W_O
FFN 参数,例如 W_gate、W_up、W_down
归一化参数,例如 RMSNorm 的 γ
LM Head 参数

注意:

RoPE 的基础旋转参数通常不是逐位置学习的。
但有些模型会有 rope_scaling 或长上下文微调配置。

15.5 训练时所有位置可以并行

虽然生成文本时,模型是一个 token 一个 token 往后生成的:

我 → 今天 → 很 → 开心

但在训练时,并不需要一个 token 一个 token 串行跑。

由于因果掩码的存在,一次前向传播可以同时计算所有位置的预测。

例如句子:

我 吃 鱼

训练时可以在一次前向传播中同时学习:

输入位置

可见上下文

目标下一个 token

第 1 个位置

第 2 个位置

我 吃

第 3 个位置

我 吃 鱼

[EOS] 或下一个 token

所以:

训练并行,生成串行。

这是 Transformer 训练效率高的关键原因之一。

15.6 Teacher Forcing:训练时为什么可以并行

训练大模型时,通常使用 Teacher Forcing。

意思是:

训练时,模型不需要真的自己生成下一个 token。
训练数据已经提供了正确的上下文。

例如完整句子是:

今天天气很好

训练时会构造这样的学习任务:

输入:今天
目标:天气

输入:今天天气
目标:很

输入:今天天气很
目标:好

输入:今天天气很好
目标:EOS 或下一个 token

因为有因果掩码,模型在一次前向传播中,可以同时计算所有位置的预测。

这就是训练高效的重要原因。

对比推理:

推理时没有标准答案。
模型必须先生成第 1 个 token,
才能把它作为上下文生成第 2 个 token。

所以:

训练可以高度并行。
生成必须逐步串行。

 MLOps 视角
训练瓶颈通常是:

算力
通信
优化器显存
激活值

推理瓶颈通常是:

KV Cache
显存带宽
batch 调度
延迟指标

16. 生成下一个 Token

16.1 输出层,LM Head

最后一层输出的向量是:

h_last

维度通常是:

d_model

通过 LM Head 映射到词表大小:

logits = h_last · W_LM_Head

再经过 softmax:

probs = softmax(logits)

得到词表上的概率分布。


16.2 权重绑定,Weight Tying

部分模型中,输入 Embedding 表和输出 LM Head 共享同一套权重。

这叫做:

Weight Tying

输入时:

token id 查表得到向量

输出时:

最后一层向量直接和这张表做点积,算出词表概率

是否绑定取决于具体模型。

查看:

"tie_word_embeddings": true

或:

"tie_word_embeddings": false

常见情况:

GPT-2 系列通常绑定。
LLaMA 系列很多实现不绑定。
不同开源模型需要具体看 config。

 MLOps 视角:

Weight Tying 直接影响显存估算。

假设:

vocab_size = 128K
hidden_size = 4096
精度 = BF16/FP16

一张表的参数量:

128000 × 4096 ≈ 524M

显存约:

524M × 2 bytes ≈ 1.05GB

如果不绑定:

Embedding + LM Head ≈ 2.1GB

如果绑定:

约 1.05GB

大约省下 1GB 显存。


16.3 生成过程

自回归生成过程如下:

输入:今天天气
模型预测:很

输入变成:今天天气很
模型预测:好

最终生成:今天天气很好

每一步只生成一个 token。


16.4 常见生成策略

策略

说明

greedy

每次选概率最高的 token

top-k

从前 k 个候选中采样

top-p,nucleus

从累积概率达到 p 的候选中采样

temperature

调整概率分布的平滑程度


temperature 的具体数字感受

假设原始 logits 是:

[5.0, 2.0, 1.0]

temperature

实际计算

softmax 后概率

效果

0.1

softmax([50, 20, 10])

[≈1.00, ≈0.00, ≈0.00]

几乎 greedy

1.0

softmax([5, 2, 1])

[0.936, 0.047, 0.017]

原始分布

2.0

softmax([2.5, 1.0, 0.5])

[0.736, 0.164, 0.100]

更平滑

3.0

softmax([1.67, 0.67, 0.33])

[0.612, 0.225, 0.162]

更随机

规律:

temperature 越低,越确定。
temperature 越高,越随机。

16.5 停止条件和特殊 token

模型生成文本时,不是一直无限生成下去。
通常需要在满足某个条件时停止。

常见停止条件包括:

生成了 EOS token。
达到了 max_new_tokens。
出现了用户指定的 stop strings。
生成了特定 chat 模板中的结束标记。

 MLOps 视角
部署 chat 模型时,必须清楚模型使用哪些特殊 token。

例如:

BOS
EOS
PAD
system token
user token
assistant token
tool token
function call token

不同模型差异很大。

例如有些模型使用:

<|im_start|>
<|im_end|>

有些模型使用:

<|system|>
<|user|>
<|assistant|>

有些模型使用:

[INST]
[/INST]

如果 chat template 用错,可能导致:

模型不遵循角色设定
回复格式混乱
无法正常停止
重复生成
工具调用失败

所以在部署前,一定要查看 tokenizer_config.json 里的:

chat_template

并且优先使用:

tokenizer.apply_chat_template()

不要凭感觉手拼 prompt。

更多推荐