上一篇速通 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 步)

  1. 均值:$\mu = (3+5-2+1)/4 = 1.75$
  2. 方差:$\sigma^2 = [(3-1.75)^2 + (5-1.75)^2 + (-2-1.75)^2 + (1-1.75)^2]/4 = 5.6875$
  3. 归一化:$\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]$
  4. 缩放($\gamma=1, \beta=0$):$y = [0.524, 1.362, -1.572, -0.314]$

RMSNorm(3 步)

  1. 均方根:$RMS = \sqrt{(9+25+4+1)/4} = \sqrt{9.75} = 3.122$
  2. 归一化:$\hat{x} = [3, 5, -2, 1] / 3.122 = [0.961, 1.602, -0.641, 0.320]$
  3. 缩放($\gamma=1$):$y = [0.961, 1.602, -0.641, 0.320]$
步骤LayerNormRMSNorm
第 1 步计算均值(遍历一次)计算均方根(遍历一次)
第 2 步计算方差(再遍历一次)归一化(直接除)
第 3 步归一化(减均值+除标准差)缩放(乘 $\gamma$)
第 4 步缩放(乘 $\gamma$ + 加 $\beta$)
遍历次数2 次1 次

RMSNorm 少了一次遍历(不需要算均值和方差),计算量减少约 15%。

为什么去掉"减均值"没问题?

类比:你在调节收音机的音量

  • LayerNorm = 先把音量归零(减均值),再调到合适大小(缩放)
  • RMSNorm = 直接调到合适大小(只缩放,不归零)

动手计算:RMSNorm vs LayerNorm

输入:$x = [3, 5, -2, 1]$

步骤LayerNormRMSNorm
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_i = 10000^{-2i/d}, \quad i = 0, 1, \ldots, d/2-1 $$

低维度旋转快($\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 上
位置类型绝对位置相对位置
是否可训练固定不变可通过缩放因子调整
外推能力有限较好
使用模型原始 TransformerLLaMA 、 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. 生成第 1 个词(Prefill 阶段):
    • 计算所有位置的 K 和 V ,全部存入 Cache
    • 输出第 1 个词
  2. 生成第 2 个词(Decode 阶段):
    • 只计算新位置的 $K_2, V_2$(1 次计算)
    • 从 Cache 读取 $K_1, V_1$(直接读取,不需要计算)
    • 拼接后计算注意力
    • 输出第 2 个词
  3. 生成第 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换来巨大加速

关键洞察

  1. KV Cache 用空间换时间:额外占用约 1GB 显存(LLaMA-7B),K/V 投影计算量减少约 38 倍
  2. 注意力计算量不变:KV Cache 只省掉了重复的 K/V 线性投影,注意力本身的 $Q \cdot K^T$ 计算仍然需要遍历所有缓存位置
  3. 长序列的挑战:序列越长,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 Cache32 层总 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 五大改进总结

改进替代了什么解决的问题效果
RMSNormLayerNorm归一化计算量大计算减少 ~15%
RoPE正弦位置编码绝对位置,外推差支持相对位置,长序列友好
SwiGLUReLU FFN表达能力有限基准测试提升,训练更稳定
KV Cache无缓存重复计算 K/V推理速度提升 seq_len 倍
GQAMHA/MQAKV 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 个词的概率分布:

排名概率累积概率
1AI0.400.40
2deep0.300.70
3learning0.150.85
4the0.080.93
5a0.040.97
6an0.031.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 调用。