Lecture 2 (一)
几个表格,快速回顾知识
| 缩写 | 全称 | 中文含义 | 常见解释 |
|---|---|---|---|
| B | Batch size | 批大小 | 一次并行送进模型的样本数 |
| H | Heads | 注意力头数 | Multi-Head Attention 里的头数 |
| T | Time steps / Sequence length | 时间步 / 序列长度 | 序列中 token 的数量 |
| D | Dimension / Hidden size | 特征维度 / 隐藏维度 | 向量长度、embedding 维度 |
| V | Vocabulary size | 词表大小 | 不同 token 的总数 |
| d | Head dim | 每个 head 的维度 | D / H |
tensor shape:
| 名称 | Shape | 解释 |
|---|---|---|
| tokens | (B, T) |
原始输入的 Token ID 序列(整数)。 |
| embeddings | (B, T, D) |
嵌入后的向量,包含语义及位置信息。 |
| Q / K / V | (B, H, T, d) |
线性变换后的 Query, Key, Value;其中 $D = H \times d$ |
| attn score | (B, H, T, T) |
注意力权重矩阵 |
| logits | (B, T, V) |
未经归一化的预测概率,对应词表(Vocabulary)的大小。 |
reshape / transpose:
| 操作 | 做什么 | 理解 |
|---|---|---|
view / reshape |
改 shape | 不动数据 |
transpose(i, j) |
换轴 | 第 i、j 维互换位置 |
| 典型用法 | (B,T,D) → (B,H,T,d) |
拆多头 |
broadcasting:
| 规则 | 说明 |
|---|---|
| 从右往左对齐 | 维度对齐规则 |
| 相同或为 1 | 可以广播 |
| 常见例子 | (B,T,D) + (T,D) |
课程笔记
1. tensor memory
数据类型
fp32

fp16

bfloat16

fp8

表格整理
| 数据类型 | 别名 | 占用字节 (Byte) | 动态范围 | 精度 (分辨率) | 评价 |
|---|---|---|---|---|---|
| float32 | fp32 / 单精度 | 4 | 极大 | 极高 | 默认格式,最稳,但最占空间 |
| float16 | fp16 / 半精度 | 2 | 较小 | 中 | 省空间,但容易产生“下溢” (Underflow) |
| bfloat16 | bf16 / Brain | 2 | 等同fp32 | 较低 | 省空间且不容易崩溃 |
| fp8 | 8位浮点数 | 1 | 极小 | 极低 | 极致压缩,仅限 H100 等新型显卡 |
例:GPT-3 中的一个前馈层矩阵,大小为 $(12288 \times 4) \times 12288$,而 $内存 = 元素个数 \times 字节数$。
| 使用类型 | 每个元素大小 | 总内存占用 | 显存压力 |
|---|---|---|---|
| float32 | 4 字节 | 约 2.3 GB | 大 |
| float16 / bf16 | 2 字节 | 约 1.15 GB | 减半 |
| fp8 | 1 字节 | 约 0.57 GB | 极小 |
用 1e-8 测试了不同格式的极限。
| 数据类型 | 输入数值 | 计算机存储的结果 | 结论 |
|---|---|---|---|
| float32 | 1e-8 | 1e-8 | 正常 |
| float16 | 1e-8 | 0 | 下溢 (Underflow):数值太小,被强行抹除,会导致训练中断 |
| bfloat16 | 1e-8 | 1e-8 (或近似) | 正常:虽然它精度低,但它能“容纳”这么小的数 |
方案:
| 方案 | 优点 | 缺点 | 建议 |
|---|---|---|---|
| 纯float32 | 最稳定,不会出错 | 耗显存,速度最慢 | 仅在模型极小或显存极多时使用 |
| 纯低精度 (fp16/fp8) | 速度极快,省空间 | 危险 | 容易训练失败 |
解决方案:混合精度 (mixed precision training),兼顾速度与稳定。
2. 计算与硬件
tensor on GPU
要把 tensor 从 CPU 移到 GPU 上。

1 | import torch |
操作
存储:

lecture_02.py 讲义中的重要笔记:
1 | x = torch.tensor([[1., 2, 3], [4, 5, 6]]) # @inspect x |
| 特性 | view | reshape |
|---|---|---|
| 内存要求 | 必须是连续的 (Contiguous) | 没有要求,连续不连续都能用 |
| 底层逻辑 | 永远不复制数据,只改变读取方式 | 如果内存连续就用 view;如果不连续,复制一份数据使其连续 |
| 安全性 | 容易报错(如果不连续的话) | 更安全,基本不会报错 |
示例:
1 | x = torch.randn(4, 4) # 16 个元素 |
讲义中的其他重要知识点:einops
einops 非常高效。
einsum:1
2
3
4
5
6
7
8
9
10from einops import rearrange, einsum, reduce
x: Float[torch.Tensor, "batch seq1 hidden"] = torch.ones(2, 3, 4)
y: Float[torch.Tensor, "batch seq2 hidden"] = torch.ones(2, 3, 4)
z = x @ y.transpose(-2, -1) # batch, sequence, sequence
# 等价于
z = einsum(x, y, "batch seq1 hidden, batch seq2 hidden -> batch seq1 seq2")
# 等价于
z = einsum(x, y, "... seq1 hidden, ... seq2 hidden -> ... seq1 seq2")
reduce:1
2
3
4
5
6# You can reduce a single tensor via some operation (e.g., sum, mean, max, min).
x: Float[torch.Tensor, "batch seq hidden"] = torch.ones(2, 3, 4)
# Old way:
y = x.mean(dim=-1)
# New (einops) way:
y = reduce(x, "... hidden -> ...", "sum")
rearrange:1
2
3
4
5
6
7
8
9
10
11# Sometimes, a dimension represents two dimensions
# ...and you want to operate on one of them.
x: Float[torch.Tensor, "batch seq total_hidden"] = torch.ones(2, 3, 8)
# ...where total_hidden is a flattened representation of heads * hidden1
w: Float[torch.Tensor, "hidden1 hidden2"] = torch.ones(4, 4)
# Break up total_hidden into two dimensions (heads and hidden1):
x = rearrange(x, "... (heads hidden1) -> ... heads hidden1", heads=2)
# Perform the transformation by w:
x = einsum(x, w, "... hidden1, hidden1 hidden2 -> ... hidden2")
# Combine heads and hidden2 back together:
x = rearrange(x, "... heads hidden2 -> ... (heads hidden2)")
其他知识点:tensor_operations_flops, gradients_basics, gradients_flops,
Parameter Initialization
标准正态分布随机初始化参数,可能梯度爆炸:
1 | x = nn.Parameter(torch.randn(input_dim)) |
Large values can cause gradients to blow up,即梯度爆炸。初始值太大,经过激活函数(比如 Sigmoid 或 Tanh)时,会落入饱和区;在反向传播时,梯度逐层累积可能变得无穷大,Loss 变成 NaN。
Xavier 初始化,即 Glorot 初始化
希望无论输入维度多大,输出值的方差保持一致。
由数学可知,标准差变大了 $\sqrt{\text{input_dim}}$ 倍。
所以初始化权重时,预先除以 $\sqrt{\text{input_dim}}$。
1 | w = nn.Parameter(torch.randn(input_dim, output_dim) / np.sqrt(input_dim)) |
为了更安全,把正态分布两端罕见值切掉。例:[-3, 3]。
1 | w = nn.Parameter(nn.init.trunc_normal_(torch.empty(input_dim, output_dim), std=1 / np.sqrt(input_dim), a=-3, b=3)) |