理解大语言模型(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ᵀ/√dQ和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_model768每个token的向量维度
n_heads12注意力头数
d_head64每个头的维度 (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 再作用于结果
一个 tokend_model 维的浮点向量(GPT-2: 768维)
点积 vs 余弦都衡量相似度。Attention 用缩放点积,检索/RAG 用余弦
投影 vs 截断不是截断!768 维全部参与计算,压缩重组到 64 维
QKV 大小各 [序列长度, d_model],拆成多头后每头 [序列长度, d_head]
多头注意力12 组独立参数,各看不同模式(语法/位置/共指等),自发涌现
降维 768→64为了拼接后维度守恒 + 残差连接能相加
残差连接output = input + 修正量,解决梯度消失,让深层网络能训练
LayerNorm防数值爆炸,稳定训练
因果掩码禁止偷看未来 token,实现自回归生成
MLPAttention 负责交流(开会),MLP 负责理解(消化)
位置编码告诉模型 token 的先后顺序

系列文章

本文建立了 Transformer 的数学直觉。如果你想看真实训练数据——每一个权重矩阵、每一个中间向量、每一个注意力分数的实际数字——请看:

用 4192 个参数看透 Transformer:QKV、多头注意力、MLP 全拆解

那篇文章用 Karpathy 的 microgpt(200 行纯 Python,零依赖)训练了一个迷你 GPT,然后把模型完全打开——所有数字都来自真实训练,不是示意图。两篇文章配合食用效果最佳。