PyTorch 源码漫游
从一行 loss.backward() 出发,看清这台深度学习引擎底层到底怎么转。
序 · loss.backward() 之后,到底发生了什么
写 nanoGPT 时,你敲下一行 loss.backward(),梯度就神奇地出现在每个参数的 .grad 里。你写 a + b,它在 CPU 上、GPU 上、要不要求导,全都「自动」处理妥当。你从没关心过这些——因为 PyTorch 把底下那台庞大的引擎,藏在了一层极薄的 Python 外衣底下。
这个专栏想掀开这层外衣,顺着源码走一遍,看清引擎到底怎么转:
PyTorch 是一台用 C++ 写的庞大引擎,披着一件轻薄的 Python 外套。 你平时碰的是外套;这趟漫游,我们往下走,一层层看到引擎的齿轮。
为什么值得往下看
平时把 PyTorch 当黑箱用,完全没问题。但总有些时刻,你会希望自己看得更深一点:
loss.backward()报了个看不懂的 autograd 错误,栈里全是AccumulateGrad、grad_fn——它们到底是什么?- 想写个自定义算子或自定义反向,却不知道该从哪一层介入;
torch.compile有时飞快、有时「graph break」失效,想知道它到底在编译什么;- 显存莫名其妙就满了,想搞懂 PyTorch 到底怎么管 GPU 内存。
这些问题的答案,都不在 API 文档里,而在引擎的构造里。看懂它,你调试、扩展、优化 PyTorch 时,就从「碰运气」变成「知道自己在动哪根齿轮」。
方法:一次调用,从 Python 走到金属
先说清楚这趟漫游的性质。 PyTorch 源码有上百万行 C++/Python,且版本间变动频繁——不可能像 nanoGPT 那样逐行读完。所以本专栏是一次导览(漫游),不是某个 commit 的逐行注释:我们锁定引擎的几个核心子系统,讲清它们的构造与原理,给出真实的源码位置(文件 / 类 / 函数)供你按图索骥,并处处标注权威出处(Edward Yang 的 PyTorch internals 系列、官方文档、PyTorch 的论文)。具体某个文件路径、某个 dispatch key 的名字会随版本漂移,以你本地 import torch; torch.__version__ 那一版为准——但底层的设计思想,多年来相当稳定。
漫游的主线,是跟着一次调用往下走:一句 a + b(或 loss.backward())从 Python 出发,一层层穿过 PyTorch 的身体,直到落在 CPU/GPU 的金属上。走完这一趟,你就看清了整台引擎的骨架。
路线图
第一站 · 数据结构 —— 一切的地基:Tensor 到底是什么,它和底层 Storage 是什么关系,view、stride、contiguous 这些词背后是同一个内存怎么被「不同地看」。
第二站 · 一次调用的旅程 —— a + b 从 Python 怎么落到具体的 CPU/CUDA 内核。核心是 PyTorch 的中枢神经:dispatcher(分发器),以及它下面的 ATen 算子库和 native_functions.yaml。
第三站 · autograd 引擎 —— 最神奇的部分。前向时它怎么悄悄记账、把每步运算连成一张计算图;loss.backward() 又怎么逆着回放这张图、用链式法则把梯度算出来、堆进 .grad。
第四站 · 上层与底层 —— 向上看 nn.Module 那层薄薄的糖(参数、buffer、hook 怎么组织);向下看张量在 GPU 上怎么活(CUDA 流、异步、以及那个「一上来就吃光显存」的缓存分配器)。
第五站 · 编译提速 —— torch.compile 怎么给一个「动态到骨子里」的框架做图编译:TorchDynamo 抓 Python 字节码、TorchInductor 生成飞快的内核。
收尾 —— 把整个技术栈串成一张图,回望这趟从 Python 到金属的旅程。
读之前,你最好有
- 用过 PyTorch(读过 nanoGPT 就够)——你其实已经用过本专栏要拆的每一个东西(
Tensor、backward、nn.Module、.to('cuda')、torch.compile),只是没往下看过。 - 会读 Python;C++ 能看个大概更好,但看不懂 C++ 也没关系——涉及底层时我们讲思想、给位置,不要求你读 C++。
- 一颗「想知道齿轮怎么咬合」的好奇心。
准备好了,我们从最底下的地基开始:你天天用的 Tensor,其实只是一层「怎么看内存」的说明书。👉 Tensor 与 Storage:同一块内存的不同看法