上一篇:速通 AI(四):预训练语言模型——BERT 与 GPT — BERT/GPT 原理与 HuggingFace 实战
目标:深入理解 LLaMA 架构——开源大模型的事实标准,掌握 RMSNorm、RoPE、SwiGLU、KV Cache、GQA 等关键改进。
前置要求:上一篇(Transformer 架构、BERT/GPT 原理、HuggingFace 使用)
本阶段知识依赖图
flowchart TD
A["Transformer + BERT/GPT(上一篇)"]
A --> B["LLaMA架构(理解现代大模型)"]
B -->|归一化改进| B1[RMSNorm]
B -->|旋转位置编码| B2[RoPE]
B -->|激活函数改进| B3[SwiGLU]
B -->|推理加速| B4[KV Cache]
B -->|注意力优化| B5[GQA]
B -->|推理策略| B6[采样与解码]
1. LLaMA 架构深入——理解现代大模型
1.1 LLaMA 为什么重要?
类比:如果 Transformer 是"汽车的发明",那 LLaMA 就是"现代汽车的标准设计"。
LLaMA (Large Language Model Meta AI)是 Meta 发布的开源大模型系列。 它是目前大多数开源大模型的基础架构——几乎所有主流开源模型都是基于 LLaMA 微调而来:
flowchart TD
L["LLaMA(原始)"]
L --> Alpaca["Alpaca(斯坦福微调)<br/>用指令数据微调"]
L --> Vicuna["Vicuna(LMSYS微调)<br/>用对话数据微调"]
L --> Chinese["Chinese-LLaMA(中文适配)<br/>加入中文词表"]
L --> Code["CodeLLaMA(代码能力)<br/>用代码数据微调"]
L --> Meta["LLaMA 2/3(Meta官方迭代)<br/>更大、更强"]
理解 LLaMA = 理解当前大模型的核心设计思想。 它在 Transformer 基础上做了 5 个关键改进,每个改进都解决了特定问题。
1.2 RMSNorm——改进的归一化
归一化为什么重要?
类比:考试成绩标准化
假设两个班级的考试:
- A 班:平均分 90 分,最高 95 ,最低 85 (分数集中在 90 附近)
- B 班:平均分 60 分,最高 100 ,最低 20 (分数很分散)
如果直接用原始分数比较两个班的学生,分布差异会干扰判断。 归一化的作用:把两个班的分数都“拉”到同一个范围(比如均值 0 ,标准差 1) → 现在可以公平比较了。
神经网络中也一样:如果每层的输入分布差异很大,网络很难学习。 归一化让每层的输入分布稳定,训练更高效。
LayerNorm vs RMSNorm——详细对比
| LayerNorm (4 步) | RMSNorm (3 步) |
|---|---|
| 1. 计算均值 $\mu = (1/d)\sum x_i$ 2. 计算方差 $\sigma^2 = (1/d)\sum (x_i - \mu)^2$ 3. 归一化 $\hat{x} = (x - \mu) / \sqrt{\sigma^2 + \epsilon}$ 4. 缩放 $y = \gamma \cdot \hat{x} + \beta$ | 1. 计算均方根 $RMS = \sqrt{(1/d)\sum x_i^2}$ 2. 归一化 $\hat{x} = x / RMS$ 3. 缩放 $y = \gamma \cdot \hat{x}$ |
核心区别: RMSNorm 去掉了"减均值"(re-centering)和"偏置 $\beta$"。
动手计算:RMSNorm vs LayerNorm 数值对比
输入:$x = [3, 5, -2, 1]$
LayerNorm(4 步):
- 均值:$\mu = (3+5-2+1)/4 = 1.75$
- 方差:$\sigma^2 = [(3-1.75)^2 + (5-1.75)^2 + (-2-1.75)^2 + (1-1.75)^2]/4 = 5.6875$
- 归一化:$\hat{x} = [(3-1.75), (5-1.75), (-2-1.75), (1-1.75)] / \sqrt{5.6875} = [0.524, 1.362, -1.572, -0.314]$
- 缩放($\gamma=1, \beta=0$):$y = [0.524, 1.362, -1.572, -0.314]$
RMSNorm(3 步):
- 均方根:$RMS = \sqrt{(9+25+4+1)/4} = \sqrt{9.75} = 3.122$
- 归一化:$\hat{x} = [3, 5, -2, 1] / 3.122 = [0.961, 1.602, -0.641, 0.320]$
- 缩放($\gamma=1$):$y = [0.961, 1.602, -0.641, 0.320]$
| 步骤 | LayerNorm | RMSNorm |
|---|---|---|
| 第 1 步 | 计算均值(遍历一次) | 计算均方根(遍历一次) |
| 第 2 步 | 计算方差(再遍历一次) | 归一化(直接除) |
| 第 3 步 | 归一化(减均值+除标准差) | 缩放(乘 $\gamma$) |
| 第 4 步 | 缩放(乘 $\gamma$ + 加 $\beta$) | — |
| 遍历次数 | 2 次 | 1 次 |
RMSNorm 少了一次遍历(不需要算均值和方差),计算量减少约 15%。
为什么去掉"减均值"没问题?
类比:你在调节收音机的音量
- LayerNorm = 先把音量归零(减均值),再调到合适大小(缩放)
- RMSNorm = 直接调到合适大小(只缩放,不归零)
动手计算:RMSNorm vs LayerNorm
输入:$x = [3, 5, -2, 1]$
| 步骤 | LayerNorm | RMSNorm |
|---|---|---|
| 1 | 均值 $\mu=1.75$,方差 $\sigma^2=5.69$ | 均方根 $RMS=\sqrt{9.75}=3.122$ |
| 2 | 归一化 $(x-\mu)/\sigma = [0.52, 1.36, -1.57, -0.31]$ | 归一化 $x/RMS = [0.96, 1.60, -0.64, 0.32]$ |
| 3 | 缩放 $\gamma \cdot \hat{x} + \beta$ | 缩放 $\gamma \cdot \hat{x}$ |
| 遍历次数 | 2 次 | 1 次(少 15% 计算) |
实验发现:先归零再调,和直接调,效果差别很小。 但省去“归零”这一步,计算量减少了约 15% 。 在大模型中(几十亿参数), 15% 的计算节省 = 巨大的成本节约!
哪些模型使用 RMSNorm ?
- LLaMA / LLaMA 2 / LLaMA 3
- Qwen / Qwen2
- Mistral / Mixtral
- Gemma
- DeepSeek
RMSNorm 已经成为现代大模型的标配。
1.3 RoPE——旋转位置编码
为什么需要新的位置编码?
正弦位置编码有三个局限(详见上一篇 §1.3):固定不变、只编码绝对位置、外推能力有限。RoPE 通过旋转矩阵解决了这三个问题。
RoPE 的核心思想——把位置编码变成"旋转"
类比:时钟的指针
想象一个时钟:
- 1 点钟:指针转了 30°
- 2 点钟:指针转了 60°
- 3 点钟:指针转了 90°
每个时刻的位置 = 指针旋转的角度。两个时刻之间的“距离” = 角度之差。
RoPE 做的是同样的事:
- 把词向量的每两个维度看作一个二维平面上的点
- 位置 m 的词向量旋转 $m\times\theta$ 角度
- 每个维度对的旋转速度不同,由频率公式决定:
低维度旋转快($\theta_0 = 1$),高维度旋转慢($\theta_{d/2-1} = 1/10000$)。这让模型同时捕捉近距离和远距离的位置关系。
关键性质:注意力分数只依赖相对位置。$q_m^T \cdot k_n = q^T \cdot R_{(n-m)} \cdot k$,其中 $R_{(n-m)}$ 是旋转 $(n-m) \times \theta$ 的旋转矩阵。这意味着"第 3 个词和第 7 个词的关系"与"第 13 个词和第 17 个词的关系"是相同的——都是相对距离 4。
RoPE 的数学本质:
对于位置 $m$ 的 query 向量 $q$ 和位置 $n$ 的 key 向量 $k$:
应用 RoPE 后的注意力分数:
$$ q_m^T \cdot k_n = (R_m \cdot q)^T \cdot (R_n \cdot k) = q^T \cdot R_{(n-m)} \cdot k $$关键性质:注意力分数只依赖于相对位置 $(n-m)$,而不是绝对位置 $m$ 和 $n$。
RoPE 的几何直觉:想象二维平面上的一个向量 $[x_1, x_2]$,位置 $m$ 的词向量逆时针旋转 $m \times \theta$ 角度。两个位置的词向量之间的"夹角差"就是它们的相对距离。这就像钟表上的两个指针——我们不需要知道现在是几点,只需要看两个指针之间的夹角就知道它们的相对位置。不同维度使用不同的 $\theta$(频率),低维度旋转快(捕获近距离关系),高维度旋转慢(捕获远距离关系),这让模型能同时感知"相邻词"和"远距离词"的关系。
这意味着:
- 模型天然理解“距离”(相隔几个词)
- 不管句子从哪个位置开始,相对关系不变
- 可以外推到更长的序列(因为只依赖相对距离)
RoPE vs 正弦位置编码:
| 特性 | 正弦位置编码 | RoPE |
|---|---|---|
| 编码方式 | 加到输入上 | 乘到 Q/K 上 |
| 位置类型 | 绝对位置 | 相对位置 |
| 是否可训练 | 固定不变 | 可通过缩放因子调整 |
| 外推能力 | 有限 | 较好 |
| 使用模型 | 原始 Transformer | LLaMA 、 Qwen 、 Mistral |
1.4 SwiGLU——改进的激活函数
从 ReLU 到 SwiGLU 的进化
传统 FFN (Transformer 原版):
- $FFN(x) = ReLU(x\cdot W_1 + b_1)\cdot W_2 + b_2$
- 维度变化:$d_{model} \rightarrow 4\times d_{model} \rightarrow d_{model}$
SwiGLU FFN (LLaMA 使用):
- $FFN(x) = (Swish(x\cdot W_1) \odot x\cdot W_3)\cdot W_2$
- 维度变化:$d_{model} \rightarrow (8/3)\times d_{model} \rightarrow d_{model}$
其中:
- $Swish(x) = x \cdot \sigma(x)$($\sigma$ 是 Sigmoid 函数)
- 直觉: Swish 是一个“平滑的 ReLU”——在 $x\lt 0$ 时不完全关闭,而是留一点“缝隙”
- $\odot$ 是逐元素相乘(门控机制)
- 直觉:$W_3$ 产生的值像一个“阀门”,控制 $W_1$ 的信息通过多少
GLU (Gated Linear Unit)的核心思想——门控:
SwiGLU = Swish + GLU (门控线性单元)
门控的意思是:不是简单地“全部通过”或“全部阻断”,而是对信息的每个维度独立地“调节流量”。
类比:
- ReLU = 一个水龙头,要么全开($x>0$),要么全关($x\leq 0$)
- SwiGLU = 一个可调节的阀门,可以控制每个出水孔的流量
效果: SwiGLU 在多个基准测试上优于 ReLU ,训练更稳定。 代价:多了一个权重矩阵 $W_3$,参数量增加约 50% (所以 LLaMA 把隐藏维度从 $4d$ 降到 $8/3d$ 来补偿)。
1.5 KV Cache——推理加速的关键
为什么自回归生成很慢?
类比:翻译一本书
- 翻译第 1 个词时:需要读完整本书(完整前向传播)
- 翻译第 2 个词时:又要读完整本书(但大部分内容和上次一样)
- 翻译第 3 个词时:又要读完整本书
- 翻译第 1000 个词时:还是要读完整本书
问题:每次都重新计算所有位置的 K 和 V ,但之前计算的结果完全可以复用。
KV Cache 的解决方案
KV Cache = “笔记本”:把之前算过的 K 和 V 记下来,下次直接用。
- 生成第 1 个词(Prefill 阶段):
- 计算所有位置的 K 和 V ,全部存入 Cache
- 输出第 1 个词
- 生成第 2 个词(Decode 阶段):
- 只计算新位置的 $K_2, V_2$(1 次计算)
- 从 Cache 读取 $K_1, V_1$(直接读取,不需要计算)
- 拼接后计算注意力
- 输出第 2 个词
- 生成第 3 个词:
- 只计算 $K_3, V_3$
- 从 Cache 读取 $[K_1, K_2], [V_1, V_2]$
- 拼接后计算注意力
- 输出第 3 个词
效果:每个新词只需要 1 次前向传播(而不是 $seq\_len$ 次) → 推理速度提升 $seq\_len$ 倍。
KV Cache 的显存开销:
KV Cache 大小:
$$ 2 \times num\_layers \times num\_heads \times d\_{head} \times seq\_{len} \times batch\_size \times dtype\_size $$示例(LLaMA-7B , float16 ,单条序列):
- $2 \times 32$ 层 $\times 32$ 头 $\times 128$ 维 $\times 2048$ 长度 $\times 2$ bytes $\approx 1GB$
示例(LLaMA-70B , float16 ,单条序列,使用 GQA 8 个 KV 头):
- $2 \times 80$ 层 $\times 8$ KV 头 $\times 128$ 维 $\times 4096$ 长度 $\times 2$ bytes $\approx 1.3GB$
注意:LLaMA-70B 使用 GQA(8 个 KV 头),而非 MHA(64 个 KV 头)。如果用 MHA,KV Cache 会达到约 10GB——这正是 GQA 的价值所在。
结论:
- 大模型的 KV Cache 可以占到模型本身显存的 30%-50%
- 长上下文(100K+token)需要大量显存
- GQA 的出现就是为了减少 KV Cache 的大小
动手计算:有无 KV Cache 的计算量对比
以 LLaMA-7B(32 层,32 头,$d_k=128$)生成一个 100 词的句子为例,对比有无 KV Cache 的计算量差异。
场景设定:Prompt 长度 = 50 tokens,需要生成 50 个新 token(总输出 100 tokens)。
无 KV Cache(每次重新计算全部):
生成第 $i$ 个 token 时,需要对 $i$ 个位置做完整的 Q/K/V 线性变换 + 注意力计算。
| 生成步骤 | 需计算的 K/V 位置数 | Q 投影计算量 | K/V 投影计算量 | 注意力计算量($\approx n \times d$) |
|---|---|---|---|---|
| 第 51 个词 | 51 | $d^2$ | $51 \times 2d^2$ | $51 \times d$ |
| 第 52 个词 | 52 | $d^2$ | $52 \times 2d^2$ | $52 \times d$ |
| … | … | … | … | … |
| 第 100 个词 | 100 | $d^2$ | $100 \times 2d^2$ | $100 \times d$ |
总 K/V 投影次数:$51 + 52 + \cdots + 100 = 3775$ 次(每个 token 每层)。
32 层的总 K/V 投影计算量:$3775 \times 32 \times 2d^2 = 241{,}600 \times d^2$
有 KV Cache(Prefill + Decode):
| 阶段 | 操作 | K/V 投影计算量(单层) |
|---|---|---|
| Prefill(第 1 步) | 计算 50 个 prompt token 的 K/V,全部存入 Cache | $50 \times 2d^2 = 100d^2$ |
| Decode(第 51-100 步) | 每步只计算 1 个新 token 的 K/V | $50 \times 1 \times 2d^2 = 100d^2$ |
单层总 K/V 投影计算量:$100d^2 + 100d^2 = 200d^2$
32 层总计算量:$200 \times 32 = 6{,}400 \times d^2$
对比结果:
$$ \text{加速比} = \frac{241{,}600 \times d^2}{6{,}400 \times d^2} \approx \mathbf{38 倍} $$| 指标 | 无 KV Cache | 有 KV Cache | 节省 |
|---|---|---|---|
| K/V 投影总次数(单层) | 3775 次 | 200 次 | 19 倍 |
| 总 K/V 投影计算量(32 层) | $241{,}600d^2$ | $6{,}400d^2$ | 38 倍 |
| 额外显存 | 0 | $\sim 1$ GB | 换来巨大加速 |
关键洞察:
- KV Cache 用空间换时间:额外占用约 1GB 显存(LLaMA-7B),K/V 投影计算量减少约 38 倍
- 注意力计算量不变:KV Cache 只省掉了重复的 K/V 线性投影,注意力本身的 $Q \cdot K^T$ 计算仍然需要遍历所有缓存位置
- 长序列的挑战:序列越长,KV Cache 占用的显存越大(线性增长),这也是 GQA 和长上下文优化的核心驱动力
1.6 GQA——Grouped Query Attention
为什么要优化注意力的 KV ?
问题: KV Cache 太大了!
标准 Multi-Head Attention (MHA):
- Q : 32 个头,每个头有独立的 $W_Q$
- K : 32 个头,每个头有独立的 $W_K$(32 组 KV)
- V : 32 个头,每个头有独立的 $W_V$
KV Cache 大小 $\propto num\_heads$(头数越多, Cache 越大)。
如何减小 KV Cache ?→ 减少 KV 的“头数”。
三种方案的对比:
| 方案 | Q 头数 / K 头数 / V 头数 | 特点 |
|---|---|---|
| MHA (标准多头注意力) | Q: 32 / K: 32 / V: 32 | 每个 Q 头有自己的 KV ,互不共享;质量最好,但 KV Cache 最大 |
| MQA (多查询注意力) | Q: 32 / K: 1 / V: 1 | 所有 Q 头共享同一个 KV ; KV Cache 最小,但质量下降明显 |
| GQA (分组查询注意力) | Q: 32 / K: 8 / V: 8 | 每 4 个 Q 头共享一组 KV ;质量接近 MHA ,速度接近 MQA |
类比:
- MHA = 每个人都有自己的参考资料(32 份)
- MQA = 所有人共用一份参考资料(1 份)
- GQA = 每 4 人一组,每组一份参考资料(8 份)
GQA 的效果:
- LLaMA 1 :使用 MHA (标准多头注意力)
- LLaMA 2 :使用 GQA (分组查询注意力, 8 个 KV 组)
- LLaMA 3 :使用 GQA (进一步优化)
GQA 让 KV Cache 减小了约 4 倍(LLaMA-2 7B:32 Q 头 / 8 KV 组 = 4x)至 8 倍(LLaMA-2 70B:64 Q 头 / 8 KV 组 = 8x),同时模型质量几乎不变。
动手计算:GQA 的 KV Cache 缩减(以 LLaMA-2 7B 为例)
配置:32 层,$d_k=128$,seq_len=2048,float16(2 bytes)
| 方案 | KV 头数 | 单层 KV Cache | 32 层总 Cache |
|---|---|---|---|
| MHA(32 KV 头) | 32 | $2 \times 32 \times 128 \times 2048 \times 2 = 33.6\text{MB}$ | $1073.7\text{MB} \approx 1\text{GB}$ |
| GQA(8 KV 组) | 8 | $2 \times 8 \times 128 \times 2048 \times 2 = 8.4\text{MB}$ | $268.4\text{MB} \approx 0.26\text{GB}$ |
| MQA(1 KV 头) | 1 | $2 \times 1 \times 128 \times 2048 \times 2 = 1.05\text{MB}$ | $33.6\text{MB}$ |
GQA 的 KV Cache 是 MHA 的 1/4,是 MQA 的 8 倍,但质量接近 MHA——这是最佳平衡点。
- 可以在相同显存下处理更长的序列
- 可以用更大的 batch_size ,提高吞吐量
flowchart TB
IN["输入 Tokens"] --> EMB["Token Embedding"]
EMB --> PE["② RoPE 旋转位置编码<br/>(应用到 Q、K 上)"]
PE --> RMS1["① RMSNorm"]
RMS1 --> SA["Multi-Head Self-Attention"]
SA --> GQA["⑤ GQA 分组查询注意力<br/>(减少 KV 头数)"]
GQA --> ADD1["残差连接"]
ADD1 --> RMS2["① RMSNorm"]
RMS2 --> FFN["③ SwiGLU FFN<br/>(门控激活函数)"]
FFN --> ADD2["残差连接"]
ADD2 --> OUT["输出"]
SA -.->|"推理时缓存"| KVC["④ KV Cache<br/>(加速自回归生成)"]
上图展示了 LLaMA 在 Transformer Block 中的 5 个改进位置:① RMSNorm 替换 LayerNorm(两处);② RoPE 替换正弦位置编码(加在 Q、K 上);③ SwiGLU 替换 ReLU FFN;④ KV Cache 缓存 K、V 加速推理;⑤ GQA 减少 KV 头数节省显存。
LLaMA 五大改进总结
| 改进 | 替代了什么 | 解决的问题 | 效果 |
|---|---|---|---|
| RMSNorm | LayerNorm | 归一化计算量大 | 计算减少 ~15% |
| RoPE | 正弦位置编码 | 绝对位置,外推差 | 支持相对位置,长序列友好 |
| SwiGLU | ReLU FFN | 表达能力有限 | 基准测试提升,训练更稳定 |
| KV Cache | 无缓存 | 重复计算 K/V | 推理速度提升 seq_len 倍 |
| GQA | MHA/MQA | KV Cache 显存大 | 显存减少 ~4 倍,质量不变 |
1.7 LLaMA 推理策略
Temperature——控制输出的"随机性"
类比:选择餐厅
- Temperature = 0 (极度保守):每次都去评分最高的餐厅 → 确定性最高,但可能无聊
- Temperature = 0.7 (平衡):大概率去评分高的,偶尔尝试新餐厅 → 既有质量又有惊喜
- Temperature = 1.0 (随机):随机选一家 → 完全不可预测
Temperature 的数学原理:
- 原始 logits :[2.0, 1.0, 0.1]
- Temperature = logits / T
示例:
- T=0.5 : [4.0, 2.0, 0.2] → softmax 后 [0.86, 0.12, 0.02] → 非常确定
- T=1.0 : [2.0, 1.0, 0.1] → softmax 后 [0.66, 0.24, 0.10] → 原始分布
- T=2.0 : [1.0, 0.5, 0.05] → softmax 后 [0.50, 0.30, 0.20] → 更均匀
结论: T 越小 → 分布越尖锐 → 输出越确定; T 越大 → 分布越平坦 → 输出越随机。
Top-K 和 Top-P 采样
- Top-K 采样:只从概率最高的 K 个词中采样
- K=1 :等价于贪心搜索(永远选概率最高的词)
- K=50 :从 50 个候选词中随机选
- 问题: K 是固定的,简单问题和复杂问题用同一个 K
- Top-P(Nucleus Sampling):动态选择候选集
- 按概率从高到低排序,累加直到概率之和超过 P
- 简单问题:可能只需要前 3 个词就超过 P=0.9 → 候选集小
- 复杂问题:可能需要前 50 个词才超过 P=0.9 → 候选集大
动手计算:Top-K 和 Top-P 数值示例
假设模型输出 6 个词的概率分布:
| 排名 | 词 | 概率 | 累积概率 |
|---|---|---|---|
| 1 | AI | 0.40 | 0.40 |
| 2 | deep | 0.30 | 0.70 |
| 3 | learning | 0.15 | 0.85 |
| 4 | the | 0.08 | 0.93 |
| 5 | a | 0.04 | 0.97 |
| 6 | an | 0.03 | 1.00 |
Top-K=3:只从前 3 个词(AI/deep/learning)中采样,概率重新归一化为 $[0.47, 0.35, 0.18]$。“the/a/an” 完全不可能被选中。
Top-P=0.9:累加概率直到超过 0.9 → 选中前 4 个词(AI/deep/learning/the,累积 0.93)。候选集大小随概率分布自动调整。
实际使用推荐:
- 代码生成: Temperature=0 , Top-P=1.0 (确定性输出)
- 一般对话: Temperature=0.7 , Top-P=0.9 (平衡)
- 创意写作: Temperature=1.0 , Top-P=0.95 (多样输出)
模块小结: LLaMA 架构
| 你学到了什么 | 为什么重要 |
|---|---|
| RMSNorm | 简化归一化,加速训练 |
| RoPE 旋转位置编码 | 相对位置建模,支持长序列 |
| SwiGLU 激活函数 | 门控机制,更强表达能力 |
| KV Cache | 推理加速的关键技术 |
| GQA 分组查询注意力 | 减少 KV Cache 显存开销 |
| Temperature/Top-K/Top-P | 控制生成的随机性和质量 |
下一篇预告:本文理解了 LLaMA 的架构设计。在速通 AI(六):LLM 应用开发中,你将学习如何使用这些模型——提示词工程、LangChain 框架、OpenAI API 调用。