你在文档里看到 [768, 1024],第一反应可能是:768 是行还是列?1024 是 token 数还是特征维?PyTorch 教程写 [batch, seq, hidden],推理引擎源码写 [n_embd, n_tokens]——同一套数据,括号顺序不同,读错维度后面所有公式都会对不上。

这一篇只解决一件事:在本系列采用的约定下,张量形状怎么读、矩阵图怎么画、常见运算后维度怎么变。 搞懂它,后面矩阵乘法、Q/K/V 投影才有共同语言。

这是「大模型数学速成」系列的第 1 篇。建议先读 第 00 篇:读 Transformer 前需要哪些数学?。下一篇我们讲 矩阵乘法——神经网络的「基本变换器」。

一、什么是张量?

在深度学习里,张量(tensor) 就是存放数字的多维数组。维度越高,结构越丰富,但读法有统一套路:

维度 生活类比 大模型中的例子
1D 向量 一行成绩:[语文, 数学, 英语, …] 某一个 token 的 768 维 embedding
2D 矩阵 Excel 表格 [n_embd, n_tokens] 整段序列的表示
3D 张量 一摞 Excel 表 多头注意力里「每个头一张表」

不必被「张量」这个词吓到——你可以暂时把它当成「带形状说明的多维数组」。形状写在中括号里,例如 [768, 1024],两个数字分别描述两个方向上有多少格。

二、本系列的维度写法

文档中频繁出现类似 [n_embd, n_tokens] 的标注:

1
2
3
4
[n_embd, n_tokens]
↑ ↑
│ └── 第 2 维:有多少个 token(列数)
└───────────── 第 1 维:每个 token 用多少维数字描述(特征维 / 嵌入维)

典型 LLM 配置下,$n_{\text{embd}} = 768$、$n_{\text{tokens}} = 1024$ 表示:1024 个 token,每个用 768 维向量表示

行与列对照表

这是读者最容易混淆的地方——不要按「第几个括号」死记,先看清楚本系列约定

第 1 个数 n_embd 第 2 个数 n_tokens
矩阵术语 行数(纵向 ↓) 列数(横向 →)
含义 每个 token 的特征有多少维 序列里有多少个 token
图中方向 每一行是一种特征分量 每一列是一个 token

ASCII 矩阵图

1
2
3
4
5
6
7
8
9
10
11
12
13
                    列 →  (第 2 维 n_tokens,token 个数)
tok_0 tok_1 tok_2 ... tok_N
┌────────┬────────┬────────┬─────┬────────┐
行 ↓ │ 0.12 │ 0.45 │ -0.03 │ ... │ 0.88 │ ← 特征维 0
(第 1 维 ├────────┼────────┼────────┼─────┼────────┤
n_embd, │ -0.34 │ 0.67 │ 0.21 │ ... │ -0.15 │ ← 特征维 1
特征维) ├────────┼────────┼────────┼─────┼────────┤
│ 0.56 │ -0.12 │ 0.78 │ ... │ 0.33 │
│ ... │ ... │ ... │ ... │ ... │
│ 0.09 │ 0.23 │ -0.41 │ ... │ 0.67 │ ← 特征维 767
└────────┴────────┴────────┴─────┴────────┘

一列 = 一个 token 的完整特征向量(768 个数,从上到下读)

关键约定:列 = token。 矩阵的每一列对应一个 token,列里的 $n_{\text{embd}}$ 个数字就是该 token 的特征向量(embedding)

形象类比:参会者档案表

把每个 token 想象成一位参会者,他的「个人档案」有 768 个字段(身高、体重、爱好……)。整张表有 768 行(字段种类)、N 列(参会人数)——形状记作 [768, N]

三、为什么是「行 = 特征维、列 = token」?

这不是随意约定,而是许多 C/C++ 推理引擎(如 ggml 系布局) 的常见存储方式:

  • 形状 [n_embd, n_tokens] 中,第 0 维是嵌入维、第 1 维是序列长度
  • 矩阵乘法 $W \cdot X$ 时,权重 $W$ 的列数必须等于输入 $X$ 的行数(特征维),才能对每个 token 的列向量做变换——第 02 篇会详细讲「握手规则」

读源码或 GGUF 元数据时,看到 ne[0] 通常是特征维,ne[1] 通常是 token 数(具体字段名因框架而异,但逻辑顺序一致)。

四、与 PyTorch 顺序的差异

PyTorch 训练代码里,单个 batch 的张量常写成:

$$\text{shape} = [\text{batch},\ \text{seq_len},\ \text{hidden_dim}]$$

框架 / 场景 典型形状 token 在哪一维 特征维在哪一维
本系列(2D,单 batch) [768, 1024] 第 2 维(列) 第 1 维(行)
PyTorch(3D) [1, 1024, 768] 中间 seq_len 最后 hidden_dim
NumPy 手算(行=样本) [1024, 768] 第 1 维(行) 第 2 维(列)

转换关系(单 batch、无转置)

  • PyTorch [1, S, H] 去掉 batch 维 → [S, H](每一个 token)
  • ggml 系 [H, S](每一个 token)
  • 二者互为转置:$\text{ggml} = \text{PyTorch}^\top$(在去掉 batch 后)

读公式前先确认约定。 论文、框架文档、推理引擎源码可能用不同顺序。本系列一律采用 [特征维, token 数],与 第 00 篇 的符号表一致。

五、维度在运算中如何变化?

记住一条主线:大多数逐 token 运算是「按列处理」——改变每个 token 有多少维,但不改变有多少个 token(列数通常不变)。

运算类型 输入形状 输出形状 列数(token 数)
LayerNorm / RMS Norm [768, 1024] [768, 1024] 不变
残差加法 $X + Y$ [768, 1024] + 同形 [768, 1024] 不变
方阵线性层 $W \cdot X$ [768,768]·[768,1024] [768, 1024] 不变
FFN 升维 [3072,768]·[768,1024] [3072, 1024] 不变
FFN 降维 [768,3072]·[3072,1024] [768, 1024] 不变
注意力 $QK^\top$ 见第 05 篇 中间 [S, S] token 间两两打分

用一句话概括:

矩阵乘法改变的是「每个 token 有多少维」(行数),一般不改变「有多少个 token」(列数)。

LayerNorm、残差、激活函数等同形运算,则是每个位置单独处理,输入输出形状完全一致

六、3D 张量:多头在哪里?

多头注意力会把特征维拆成多个「头」,此时引入第 3 维:

$$[\underbrace{d_{\text{head}}}{\text{每头维度}},\ \underbrace{n{\text{head}}}{\text{头数}},\ \underbrace{n{\text{tokens}}}_{\text{token 数}}]$$

例如 $d_{\text{head}}=64$、$n_{\text{head}}=12$、$n_{\text{tokens}}=1024$:

  • $64 \times 12 = 768$,与单头时的 $n_{\text{embd}}$ 一致
  • 列 = token 的约定仍然成立——只是每个头各有一张 [64, 1024] 的小表

第 09 篇会专门讲多头如何拆分与合并;现在只需知道:维度可以叠高,但「列 = token」不会变。

七、小结表格

问题 本系列答案
[768, 1024] 怎么读? 768 维特征 × 1024 个 token
哪一维是 token? 第 2 维(列)
哪一维是特征? 第 1 维(行)
与 PyTorch [B,S,H] 关系? 去掉 batch 后互为转置
线性层后 token 数变吗? 通常不变
线性层后特征维变吗? 可能变(由权重矩阵行数决定)

八、与训练 / 推理的关系

  • 训练:PyTorch 用 [B, S, H] 方便 batch 并行;反向传播时形状规则相同,只是多了 batch 维。
  • 推理 Prefill:一次送入 prompt 的 $S$ 个 token,激活张量本质是 [H, S](或 3D 多头形式)。
  • 推理 Decode:每步只来 1 个新 token,$S$ 每次 +1;列数在增长,特征维 $H$ 不变——第 11 篇 KV Cache 会用到这一点。

九、动手练习(可选)

用 NumPy 建立直觉(NumPy 默认行 = 样本/token,与本系列列 = token 相反,正好练转置):

1
2
3
4
5
6
7
8
9
10
11
12
13
import numpy as np

H, S = 4, 3 # 4 维特征,3 个 token(缩小版方便手算)

# ggml 风格:[特征, token]
X_ggml = np.random.randn(H, S)

# PyTorch 风格(单 batch):[1, token, 特征]
X_torch = X_ggml.T[np.newaxis, :, :] # shape [1, 3, 4]

print("ggml:", X_ggml.shape) # (4, 3)
print("torch:", X_torch.shape) # (1, 3, 4)
print("转置关系:", np.allclose(X_ggml, X_torch[0].T))

观察:X_ggml[:, j] 是第 $j$ 个 token 的特征向量;X_torch[0, j, :] 是同一个向量。

大模型数学速成系列第 1 篇完。下一篇我们讲 矩阵乘法——对每个 token 的特征向量做线性变换,以及 $W$ 与 $X$ 的「握手规则」。

系列导航

篇号 标题 状态
00 读 Transformer 前需要哪些数学?
01 张量、维度与「列 = token」(本篇)
02 矩阵乘法:神经网络的基本变换 下一篇

完整大纲见工作区 docs/MATH_SERIES_OUTLINE.md