地基与加料 — PyTorch 底座、训练三件套、LoRA
这一章收三个附录: 附录 A 把前面九章一直在用却没细讲的 PyTorch 底座摊开; 附录 D 给第 06 章那个「最小可用」训练循环补上真实训练的三件稳定器; 附录 E 讲 LoRA——一种只动 2% 参数就能微调大模型的省法。 位置在全书的角色:它们是「回到地基」,读正文时随时可以翻到这一章。
1. 这一章讲什么
前九章我们一直在用 PyTorch,却没回答过「它到底替我们做了什么」; 第 06 章的训练循环能跑,但真实训练里还要再加三样东西才稳; 第 08、09 章的微调动了 1%~100% 的参数,而工业界今天最常用的微调其实只动 2%。 三个附录正好各补一个洞。
2. 顶层全景
附录 A 的三块(第 02~09 章每一行代码都站在上面):
张量库 —— 多维数组 + GPU 加速
自动微分引擎 —— 你写前向,它替你算所有梯度(反向传播的工程实现)
网络积木 —— nn.Module / Linear / Dataset / DataLoader / 优化器
附录 D 的三件稳定器(加在第 06 章的循环上):
学习率先爬坡(预热)→ 再沿余弦曲线降下来(余弦衰减)→ 梯度太长就截短(梯度裁剪)
附录 E 的省法(对比第 08 章的全量微调):
不学完整 ΔW,学两个小矩阵 A、B 让 AB≈ΔW;原权重冻结不动
1.24 亿参数冻结 → 只训 267 万个,准确率不降
图说:A 管「底下怎么跑」,D 管「怎么训得稳」,E 管「怎么微调得省」。
3. 核心原理
3.1 附录 A:PyTorch 就三样东西
第一样:张量库。 张量就是多维数组:标量(单个的数)是 0 维、向量是 1 维、矩阵是 2 维,再往上统称几维张量。 PyTorch 的张量和 NumPy 数组界面几乎一样,但多两个本事:能搬上 GPU 算、能自动记梯度1。 默认数据类型是 32 位浮点——精度与效率的折中,GPU 电路就是为 32 位优化的2。
第二样:自动微分引擎(autograd)。 这是全书最该讲透的地基。 第 06 章说「反向传播一次算清全部参数的调整方向」,这里看它是怎么做到的。
先立一个概念:计算图——把一串运算画成有向图,每个节点是一次运算,边是数据的流向。 PyTorch 在你写前向代码时,在后台悄悄把这张图建了起来3。 书里用一行 logistic 回归走完整个机制(本章主走查之一,全是真实数字)4:
输入 x1=1.1,权重 w1=2.2,偏置 b=0.0,真标签 y=1.0
z = x1*w1 + b = 2.42 (净输入)
a = sigmoid(z) ≈ 0.918 (压到 0~1 之间,当「是类别 1」的置信度)
loss = 二元交叉熵(a, y) (与真标签比)
grad(loss, w1) = -0.0898 ← autograd 沿图倒着走算出来的
grad(loss, b) = -0.0817
负号的读法:损失对 w1 的梯度是负的,意味着「把 w1 往大里调,损失会变小」。
反向传播就是微积分的链式法则在这张图上从右往左推——每个节点只需知道局部的求导规则,
乘起来就得到损失对任何一个参数的影响5。而你要做的全部工作,是调用一次 .backward();
算完梯度存在每个参数的 .grad 属性里6。
书里把读者该带走的压缩成一句:微积分由 PyTorch 替你做,你不需要手算任何导数7。
补充(不在书里,依据我们的 frontier 书架):autograd 引擎内部怎么挂反向图、怎么调度, 我们拆过 PyTorch 源码。依据: shelf=ai-frontier-reference/pytorch#03-autograd-engine.md 事实=前向时每个输出挂上 grad_fn 节点,backward() 时 C++ 引擎按依赖计数拓扑调度整张图, 边算边把梯度累加进叶子参数的 .grad。
第三样:网络积木。 三条使用纪律,前九章其实都默默在用,这里说破:
- 写模型 = 继承
nn.Module,__init__里定义层,forward里写数据怎么流8; - 权重要用小的随机数初始化,为的是打破对称——如果所有节点初始相同, 它们在训练里做同样的运算、收同样的更新,永远学不出分工9;
- 推理时包一层
torch.no_grad()——不训练就不必建图,省内存省算力10。
数据侧的纪律:自定义 Dataset 只需写 __init__、__getitem__、__len__ 三个方法,
洗牌、拼批、多进程加载都交给 DataLoader11。num_workers=0 时数据在主进程加载,
GPU 会空等;作者的经验值是 4,但小数据集上开多进程反而更慢12。
3.2 附录 A 的后半:GPU 与多卡
GPU 使用只有一条铁律:参与同一个运算的所有张量必须在同一块设备上,
否则 PyTorch 当场报 RuntimeError13。小模型上 GPU 可能反而更慢(数据搬运有成本),
LLM 这种规模才真正见得着加速14。
多卡训练书里讲了 PyTorch 的 DDP(DistributedDataParallel,分布式数据并行):
每张卡跑一个独立进程、各持一份完整模型副本;数据被 DistributedSampler 切成互不重叠的份;
每轮各卡独立算梯度,然后把所有卡的梯度取平均、同步回去,保证各副本不跑偏。
理想情况下两卡快一倍,八卡快八倍15。
两个实战注脚:DDP 在 Jupyter 笔记本里跑不起来(要起多进程,得用脚本);
CUDA_VISIBLE_DEVICES=0,2 可以在不改代码的情况下挑卡16。