理解大语言模型(LLM)不需要你成为数学家,但需要你建立正确的直觉。本文从最基础的矩阵乘法讲起,一步步搭建起理解 Transformer 的完整框架。
本文结构
| 数学基础 | 核心概念 | Transformer | 全景图 |
|---|---|---|---|
| 矩阵乘法三视角 | Token 嵌入 | QKV、多头注意力 | 完整数据流 |
| 方向与符号约定 | 点积与余弦相似度 | MLP、残差连接 | 速查总结表 |
每个概念只讲直觉,不做证明。目标是看完后,能读懂任何 Transformer 论文的架构图。
一、矩阵乘法的三种视角
设 A 是 m×n 矩阵,B 是 n×p 矩阵,结果 C = AB 是 m×p 矩阵。
同一个乘法,有三种等价的理解方式。
1.1 点积视角(逐元素)
最直观的方式:C 的每个元素,等于 A 的对应行与 B 的对应列做点积。
C[i,j] = A的第i行 · B的第j列 = Σ A[i,k] × B[k,j] (k = 1..n)
这种方式告诉你怎么算,但不利于理解本质。
1.2 列视角(Column View)— 最重要
AB 的每一列,是 A 的各列的线性组合,组合系数来自 B 的对应列。
设 A 的列为 a1, a2, ..., an
设 B 的第 j 列为 [b1j, b2j, ..., bnj]
则 AB 的第 j 列 = b1j·a1 + b2j·a2 + ... + bnj·an
具体例子:
A = [1 2] B = [5 7]
[3 4] [6 8]
AB 的第1列 = 5·[1,3] + 6·[2,4] = [5,15] + [12,24] = [17, 39]
AB 的第2列 = 7·[1,3] + 8·[2,4] = [7,21] + [16,32] = [23, 53]
AB = [17 23]
[39 53]
为什么说"列比行重要"? 因为矩阵的本质是线性变换,矩阵的列就是基向量变换后的去向。当你写 y = Ax 时,x 中的每个分量就是"取多少份对应的列",y 就是 A 的各列按 x 的比例混合的结果。列空间 = 变换的输出空间,这才是矩阵最核心的几何意义。
1.3 行视角(Row View)
AB 的每一行,是 B 的各行的线性组合,组合系数来自 A 的对应行。
AB 的第1行 = 1·[5,7] + 2·[6,8] = [5,7] + [12,16] = [17, 23]
AB 的第2行 = 3·[5,7] + 4·[6,8] = [15,21] + [24,32] = [39, 53]
1.4 三种视角对比
| 视角 | 怎么拆 | 结果怎么组装 |
|---|---|---|
| 点积 | A的行 × B的列 | 逐元素填入 C[i,j] |
| 列视角 | B的每列做系数 | C 的每一列 = A各列的线性组合 |
| 行视角 | A的每行做系数 | C 的每一行 = B各行的线性组合 |
一句话总结: 点积视角告诉你怎么算,列视角告诉你在干什么(线性组合)。理解 LLM 时,始终用列视角思考:“这个矩阵乘法是在把哪些向量按什么比例混合?”
二、矩阵乘法的方向:右到左
矩阵乘法的作用方向是从右到左。
当你写 y = ABx 时:
y = A(Bx) ← B先动手,A后动手
^ 先
^ 后
这就像函数复合:f(g(x)) — g 先执行,f 后执行。写在左边的反而后执行。
数学写法 vs 代码写法
在 LLM 的实现中,惯例是把数据放在行里(每行一个 token),所以代码写成 XW 而不是数学教科书的 Wx。两者是转置关系:
数学写法: q = Wq · x (x是列向量,W在左)
代码写法: Q = X · Wq (X每行是一个token,W在右)
本质相同: (Wx)^T = x^T · W^T
三、统一概念 — 一切都是矩阵
在不同场景下,你会听到"数组"“向量"“矩阵"“张量"等不同叫法。它们本质上是同一类东西,只是维度不同:
| 名称 | 本质 | 形状 | 例子 |
|---|---|---|---|
| 标量 scalar | 一个数 | 1×1 | 温度 = 36.5 |
| 向量 vector | 一行或一列数 | 1×n 或 n×1 | 一个token的嵌入 |
| 矩阵 matrix | 一张二维数表 | m×n | 一句话的所有token堆在一起 |
| 张量 tensor | 多维数表 | 任意维 | 加上batch、head等维度 |
“数组"是程序员的叫法,“向量/矩阵"是数学家的叫法,指的是同一个东西。 在代码中,它们全都是多维数组(ndarray / tensor),只是维度不同。向量是只有一行或一列的矩阵,标量是 1×1 的矩阵。
四、一个 Token 在 LLM 里长什么样
以 GPT-2(d_model = 768)为例:
第 1 步:文本 → Token ID
"猫坐在垫子上" → [猫, 坐, 在, 垫, 子, 上] → [8834, 2351, 553, 9042, 1189, 677]
6个整数
第 2 步:Token ID → 嵌入向量(查表)
Embedding 表: [vocab_size × 768] 的大矩阵,每行是一个词的向量
token "猫" (ID=8834) → 取出第8834行 → [0.02, -0.15, 0.31, ..., 0.07]
|———— 768个浮点数 ————|
这就是"猫"在768维空间中的坐标
第 3 步:一句话 = 多个向量堆起来 = 矩阵
X = [ 猫的768维向量 ] 形状: [6, 768]
[ 坐的768维向量 ] 6行 = 6个token
[ 在的768维向量 ] 768列 = 每个token的维度
[ 垫的768维向量 ]
[ 子的768维向量 ]
[ 上的768维向量 ]
关键认知:一个 token = 768 维空间中的一个点。一句话 = 一组点 = 一个矩阵。
五、点积 vs 余弦相似度
两者都在回答同一个问题:两个向量有多相似?
给定两个向量 a = [1, 2, 3], b = [2, 4, 6]
点积:
a · b = 1×2 + 2×4 + 3×6 = 28
含义: 方向相似度 × 两者的长度
问题: 长向量天然得分高,不公平
余弦相似度:
cos(a,b) = (a · b) / (|a| × |b|) = 28 / (√14 × √56) = 28/28 = 1.0
含义: 纯方向相似度,忽略长度
范围: -1(完全相反)到 +1(完全相同方向)
LLM 中的使用场景
| 场景 | 用什么 | 为什么 |
|---|---|---|
| Attention (QK相似度) | 缩放点积 QKᵀ/√d | Q和K已被训练到长度稳定,除以√d防止数值过大 |
| 检索/RAG (找相似文档) | 余弦相似度 | 不同文档长度不同,需要归一化 |
| 词向量相似度 | 余弦相似度 | 比较语义方向,不关心幅度 |
Attention 用点积而不用余弦,是因为模型训练过程中会自己学会控制向量长度,而且点积计算更快。
六、投影而非截断
一个常见的误解是:多头注意力把 768 维"截断"成 64 维。实际上是线性投影(降维)。
原始 token 向量: 768维
|
| × Wq [768, 64] ← 乘一个"瘦矩阵"
v
每个 head 的 q: 64维
这不是截断(丢掉某些维度),而是:
q = x · Wq
64维中的每一个值 = 768维全部参与计算的加权和
信息被"压缩/重组"到更少的维度里
打个比方:
- 截断 = 一张照片只留左半边,右半边扔了
- 投影 = 一张照片换个角度看,信息被重新组合了
七、QKV 的完整维度图
以 GPT-2 small 为例:
| 参数 | 值 | 含义 |
|---|---|---|
| d_model | 768 | 每个token的向量维度 |
| n_heads | 12 | 注意力头数 |
| d_head | 64 | 每个头的维度 (768 ÷ 12 = 64) |
| T | 序列长度 | 比如这句话有6个token |
投影过程
输入 X: [T, 768] 6个token,每个768维
投影权重(训练学到的参数):
Wq: [768, 768] Q的投影矩阵(所有12个head合在一起)
Wk: [768, 768] K的投影矩阵
Wv: [768, 768] V的投影矩阵
投影结果:
Q = X · Wq → [T, 768] 然后reshape成 [T, 12, 64] → 每个head各拿64维
K = X · Wk → [T, 768] 同上
V = X · Wv → [T, 768] 同上
单个 head 内部(以 head #0 为例)
Q0: [T, 64] 6个token的query,每个64维
K0: [T, 64] 6个token的key,每个64维
V0: [T, 64] 6个token的value,每个64维
注意力分数 = Q0 · K0^T / √64
[T, 64] × [64, T] = [T, T] → 6×6 的相似度矩阵
猫 坐 在 垫 子 上
猫 [ 0.8 0.1 0.05 0.02 0.02 0.01 ] ← 猫关注谁?主要关注自己
坐 [ 0.3 0.5 0.1 0.05 0.03 0.02 ] ← 坐关注谁?猫和自己
在 [ 0.1 0.2 0.4 0.15 0.1 0.05 ]
垫 [ 0.05 0.05 0.1 0.5 0.2 0.1 ]
子 [ 0.02 0.03 0.05 0.4 0.4 0.1 ] ← 子关注谁?垫和自己
上 [ 0.05 0.1 0.1 0.15 0.1 0.5 ]
(经过softmax,每行加起来=1)
输出 = 分数矩阵 × V0
[T, T] × [T, 64] = [T, 64] → 每个token得到新的64维表示
合并所有 head
12个head各输出 [T, 64] → 拼接 → [T, 768] → 再乘 Wo [768, 768] → 最终输出 [T, 768]
八、多头注意力:12 个 head 各看什么
12 组独立参数
每个 head 有自己独立的 Wq、Wk、Wv 参数,是各自训练出来的。实现上用一个大矩阵,逻辑上是 12 组:
实际存储:
Wq: [768, 768] 一个大矩阵
逻辑上等价于:
Wq_0: [768, 64] head 0 的投影
Wq_1: [768, 64] head 1 的投影
...
Wq_11: [768, 64] head 11 的投影
拼在一起 → [768, 12×64] = [768, 768]
不同 head 自发学会的关注模式
训练完成后,研究者观察到不同 head 自发地学会了不同的"关注模式”:
| Head 类型 | 它在做什么 | 例子 |
|---|---|---|
| 位置 head | 关注固定相对位置 | “总是看前一个词” |
| 语法 head | 关注语法依赖关系 | 动词 ↔ 主语 |
| 共指 head | 关注指代关系 | “他” → “小明” |
| 分隔符 head | 关注标点/特殊token | 句号、逗号、[SEP] |
| 稀疏 head | 只强烈关注1-2个token | 类似"精确查找” |
| 均匀 head | 近乎均匀关注所有token | 类似"全局平均” |
没有人告诉模型"你第 3 个 head 负责语法”,这些模式是训练过程中自然涌现的。正因为有多个 head,模型才能同时捕获多种关系。
降维的双重目的
768 维降到每头 64 维,既是为了效率,也是为了维度守恒:
输入: [T, 768]
拆分: 12 个 head × [T, 64]
各自attention后: 12 个 [T, 64]
拼接: [T, 12×64] = [T, 768] ← 回到原始维度
回到原始维度是为了残差连接(下一节讲):output = input + Attention(input),两边必须维度相同才能相加。
九、Transformer 的其他关键机制
9.1 残差连接(Residual Connection)— 最重要
+------------------------+
| |
| Attention(x) |
| | |
x -----+----(+)--+ |
| | |
| x + Attention(x) | ← 不是替换,是叠加!
| | |
| MLP(...) |
| | |
---------(+)--+ |
| |
+------------------------+
每一层的输出 = 输入 + 这一层学到的修正量
为什么重要:
- 没有残差连接,梯度经过 12 层后会接近 0(梯度消失),模型训不动
- 有了它,梯度可以"抄近路"直接传回去,12 层甚至 96 层都能训
- 直觉:每层只需要学"需要改什么”,而不是"从头重建整个表示"
9.2 LayerNorm(层归一化)
每个token的768维向量:减去均值,除以标准差,再缩放
x = [0.2, -3.5, 1.8, ..., 0.7]
| 归一化
x = [0.1, -1.2, 0.6, ..., 0.2] 均值约0,方差约1
作用:防止向量中的数值经过多层后越来越大或越来越小,让训练更稳定。
9.3 因果掩码(Causal Mask)— GPT 的核心约束
在计算注意力分数后,加一个掩码:
猫 坐 在 垫 子 上
猫 [ 0.8 -INF -INF -INF -INF -INF ] 猫只能看到猫
坐 [ 0.3 0.5 -INF -INF -INF -INF ] 坐只能看到猫、坐
在 [ 0.1 0.2 0.4 -INF -INF -INF ] 在只能看到猫、坐、在
垫 [ 0.05 0.05 0.1 0.5 -INF -INF ]
子 [ 0.02 0.03 0.05 0.4 0.4 -INF ]
上 [ 0.05 0.1 0.1 0.15 0.1 0.5 ] 上能看到所有
-INF 经过 softmax 后变成 0,等于完全看不到
这就是"自回归"的含义: 每个 token 只能看到它前面的 token,不能偷看后面的。这样模型才能一个一个往后生成。
9.4 MLP(前馈网络)— “思考"层
Attention 做的事: token之间互相交流("谁和谁有关?")
MLP 做的事: 每个token独立变换("知道了关系后,怎么理解?")
768 → 3072 → 768
| | |
| 先放大4倍 |
| 激活函数 |
| 再压回来 |
一个比喻:
- Attention = 开会讨论,收集信息
- MLP = 会后各自消化理解
9.5 位置编码(Positional Encoding)
问题: Attention本质上是集合操作,不知道词序
"猫吃鱼" 和 "鱼吃猫" 对Attention来说完全一样
解决: 给每个位置加一个位置向量
token向量 = 词义向量 + 位置向量
"猫" 在位置0: [猫的语义768维] + [位置0的编码768维]
"猫" 在位置3: [猫的语义768维] + [位置3的编码768维]
十、一个 Transformer Block 的完整流程
输入 x: [T, 768]
|
v
LayerNorm ← 稳定数值
|
v
Multi-Head Attention ← 12个head各自投影到64维
| QK^T/√64 + 因果掩码 + softmax
| × V → 加权混合
| 拼回768维
|
(+) ← x ← 残差连接:加回原始输入
|
v
LayerNorm ← 再次稳定
|
v
MLP (768→3072→768) ← 独立变换每个token
|
(+) ← 上面的结果 ← 再一次残差连接
|
v
输出: [T, 768] ← 维度和输入完全一样!所以能堆12层
十一、全局框架图
"猫坐在垫子上"
|
v
+-- Tokenizer -----------------------+
| 文本 → [8834, 2351, 553, ...] | 6个整数
+-------------------------------------+
|
v
+-- Embedding ------------------------+
| 查表: 整数 → 768维向量 | [6, 768] 矩阵
| + 位置编码(告诉模型顺序) |
+-------------------------------------+
|
v
+-- Transformer Block ×12 -----------+ (GPT-2有12层,重复12次)
| |
| +-- Multi-Head Attention ---------+ |
| | X → Q,K,V (各768维) | | 投影,不是截断
| | 拆成12个head (各64维) | |
| | 每个head: QK^T/√64 → 分数 | | 点积求相似度
| | 分数 × V → 加权混合 | | 列视角:V各行的线性组合
| | 12个head拼回768维 | |
| +----------------------------------+ |
| | |
| v |
| +-- MLP (前馈网络) ----------------+ |
| | 768 → 3072 → 768 | | 先扩大4倍,再压回来
| | 中间加ReLU/GELU激活 | |
| +----------------------------------+ |
| |
+--------------------------------------+
| (经过12层后)
v
+-- 输出层 ----------------------------+
| [6, 768] × Wout[768, vocab] |
| → [6, vocab_size] | 每个位置对所有词打分
| → softmax → 取最后一个token | "上"之后最可能的下一个词
| → 下一个token |
+--------------------------------------+
十二、速查总结表
| 概念 | 要点 |
|---|---|
| 数组/向量/矩阵/张量 | 同一类东西,只是维度不同,代码里统一叫 tensor |
| 矩阵乘法三视角 | 点积=怎么算,列视角=在干什么(线性组合),行视角=另一种组合 |
| 矩阵乘法方向 | 右到左。y = ABx 中 B 先作用于 x,A 再作用于结果 |
| 一个 token | d_model 维的浮点向量(GPT-2: 768维) |
| 点积 vs 余弦 | 都衡量相似度。Attention 用缩放点积,检索/RAG 用余弦 |
| 投影 vs 截断 | 不是截断!768 维全部参与计算,压缩重组到 64 维 |
| QKV 大小 | 各 [序列长度, d_model],拆成多头后每头 [序列长度, d_head] |
| 多头注意力 | 12 组独立参数,各看不同模式(语法/位置/共指等),自发涌现 |
| 降维 768→64 | 为了拼接后维度守恒 + 残差连接能相加 |
| 残差连接 | output = input + 修正量,解决梯度消失,让深层网络能训练 |
| LayerNorm | 防数值爆炸,稳定训练 |
| 因果掩码 | 禁止偷看未来 token,实现自回归生成 |
| MLP | Attention 负责交流(开会),MLP 负责理解(消化) |
| 位置编码 | 告诉模型 token 的先后顺序 |
系列文章
本文建立了 Transformer 的数学直觉。如果你想看真实训练数据——每一个权重矩阵、每一个中间向量、每一个注意力分数的实际数字——请看:
用 4192 个参数看透 Transformer:QKV、多头注意力、MLP 全拆解
那篇文章用 Karpathy 的 microgpt(200 行纯 Python,零依赖)训练了一个迷你 GPT,然后把模型完全打开——所有数字都来自真实训练,不是示意图。两篇文章配合食用效果最佳。
