📑 本页目录(点开跳转)
06 · 读源码路线图
⏱ 66 分钟 | ⭐ 从一条 Python 报错走到 C++ 实现,全程不需要下载源码,更不需要能编译 PyTorch
🎯 一句话
读 PyTorch 源码不是「把仓库 clone 下来从头看」,而是【从一条报错反查】:报错给你算子名 → 算子名给你 schema → dispatcher 给你注册位置 → native_functions.yaml 给你真正算数的那个函数名。
这条链上的前三步,用你机器上已经装好的 torch 就能查完;只有最后一步要去 GitHub 上看一眼文本。
🧩 一、⭐ 起点还是那条报错
本章 Python 侧的输出全是实跑的,环境 torch 2.13.0+cpu + CPython 3.13,CUDA 不可用。
沿用 05 章第一节那条:
# c06_start.py
import torch
x = torch.ones(2, 2).to_sparse()
torch.nn.functional.gelu(x)
NotImplementedError: Could not run 'aten::gelu' with arguments from the 'SparseCPU' backend.
⭐ 05 章到「格子空着」就停了。本章往下走一步:aten::gelu 那个非空的格子里,代码到底在哪个文件里。
⭐ 先把整条路线摆出来,后面每一节就是其中一格:
| 步 | 手里有什么 | 用什么工具 | 得到什么 | 在哪一节 |
|---|---|---|---|---|
| ① | 一条报错 | 读 | 算子名 aten::gelu |
本节 |
| ② | 算子名 | torch.ops.aten.gelu.default._schema |
完整签名 | 第五节 |
| ③ | 算子名 | torch._C._dispatch_dump |
「注册在哪个文件第几行」 | 05 章第四节 |
| ④ | ⚠️ 那个文件名 | —— | ⚠️ 它在 GitHub 上根本不存在 | 第三节 |
| ⑤ | 算子名 | 去仓库搜 native_functions.yaml |
⭐ 真正算数的那个函数名 | 第三、六节 |
| ⑥ | 函数名 | 在 aten/src/ATen/native/ 里搜它 |
实现 | 第六节 |
⚠️ 第 ④ 步那个转弯是本章的关键,绝大多数人卡在这里:dump 明明告诉你 RegisterCPU_3.cpp:4482,你去仓库里搜,一个结果都没有。原因在第三节。
🧩 二、三个目录名,一句话一个
翻源码时最先撞上的是三个陌生目录。先记住它们各自的边界,比记住里面有什么重要得多。
| 目录 | 全称 / 由来 | 一句话 | 里面典型有什么 |
|---|---|---|---|
c10/ |
Caffe2 + ATen 合并时的公共底座(读作 "see-ten") | ⭐ 最底层的公共设施,不认识任何具体算子 | DispatchKey、TensorImpl、Device、Scalar、intrusive_ptr |
aten/ |
A Tensor library | ⭐ 算子的家 —— 张量库本体 | native/ 下的手写 kernel、native_functions.yaml |
torch/csrc/ |
PyTorch 自己的 C++ 源 | ⭐ 认识 Python 的那一层,以及框架级功能 | pybind 绑定、autograd/、jit/、distributed/ |
⭐ 判断一个东西该在哪个目录,只问两个问题:
| 它认识 Python 吗 | 它认识具体算子吗 | 那它在 |
|---|---|---|
| ❌ | ❌ | c10/ |
| ❌ | ✅ | aten/ |
| ✅ | —— | torch/csrc/ |
⚠️ c10 不认识算子这条是硬的:DispatchKey 这个枚举定义在 c10 里,但「aten::gelu 在 CPU 这一格填了什么」不在。表的骨架归 c10,表的内容归 aten。 这也解释了 05 章那个现象 —— 表是运行时一批批填起来的,因为定义骨架的那层压根不知道有哪些行。
⭐ torch/csrc 是唯一会 #include <Python.h> 的一层。 01 章讲的那条边界,物理上就落在这个目录里。反过来说:你在 aten/ 和 c10/ 里看到 Python 相关的东西,基本可以确定自己看错文件了。
🧩 三、⭐ native_functions.yaml:算子声明的总账本
这是全 PyTorch 最该知道的一个文件名。路径是 aten/src/ATen/native/native_functions.yaml,一个几万行的 YAML,站在整条生成链的最上游。
⚠️⚠️ 这个文件不在 pip 装的 wheel 里 —— 它是构建期的输入,编译完就没它什么事了。 所以下面不贴仓库原文(贴了你也没法在本机核对,本教程不写无法验证的东西), 只讲它的形状,然后用你本机就能跑出来的东西去印证。
一条 entry 由三段构成:
| 段 | 是什么 | 对应到 05 章那张表 |
|---|---|---|
func: |
⭐ schema 本尊 | 新开一行(等价于 C++ 侧的 m.def) |
dispatch: |
分派键 → C++ 函数名的映射 | ⭐ 一次填好几个格子(等价于一串 m.impl) |
structured_delegate: |
「我不自己写,转给 xxx.out」 |
让代码生成器替你填 CPU / CUDA / Meta 那几格 |
🚦 ⭐ 用本机能跑的两条命令,把这张账本反推出来
⭐ func: 那一段不用去仓库找,它就在你装好的包里:
import torch
print(torch.ops.aten.gelu.default._schema)
# aten::gelu(Tensor self, *, str approximate="none") -> Tensor
⭐ dispatch: 那一段的结果,也能直接 dump 出来(本机 torch 2.13.0+cpu 实测):
import torch
print(torch._C._dispatch_dump("aten::gelu"))
真实输出里出现的分派键有:
MkldnnCPU · Tracer · FuncTorchBatched · CPU · Meta · QuantizedCPU ·
NestedTensorCPU · NestedTensorHPU · Autograd[alias] · CompositeExplicitAutograd…
⚠️ 注意这是「填完之后的表」,不是账本原文 —— 你看不到每一格背后的 C++ 函数叫什么名字, 那个只有仓库里的 yaml 才有。但要判断「这个后端到底有没有实现」,这份 dump 就够了, 而这恰恰是你 90% 的时候真正想知道的事。
⭐ 这份 dump 里的行,来路不止一种。 像 MkldnnCPU / QuantizedCPU / NestedTensorCPU 这类
后端专属实现,是账本里一行行手写登记的;而 CPU / Meta 这几个基础后端,很多算子
(gelu 就是其一)根本没有手写注册,是 structured_delegate: 让代码生成器按 schema 替你生成的。
⚠️ 所以「dump 里有这一行」不等于「仓库里有人为它写过一个函数」—— 你去 grep 函数名却什么也搜不到时,八成就是撞上了这种生成出来的实现。这是读源码最容易卡住的一处。
⚠️ 这正好解释了第一节第 ④ 步那个转弯:
⭐ dump 里带
build\的路径全是【生成物】。RegisterCPU_3.cpp、VariableType_1.cpp、RegisterSchema.cpp—— 这些文件在 PyTorch 仓库里根本不存在,是构建时按native_functions.yaml吐出来的,构建完才躺在build/目录里。 所以拿这个文件名去 GitHub 搜,永远是零结果。
⭐ 正确走法:不要搜文件名,搜算子名。 在 native_functions.yaml 里找到 - func: gelu(,读它的 dispatch: 段拿到函数名,再拿函数名去 aten/src/ATen/native/ 里搜 —— 那才是人手写的、真正算数的代码。
⭐ 顺带解释了另外三件一直没头绪的事:
| 现象 | 原因 |
|---|---|
那些 Register*.cpp 动辄几千行、名字还带 _0 _1 _3 编号 |
⭐ 生成物,编号是为了切开编译单元,否则一个文件编译不动 |
| 改一个算子签名在 PyTorch 里是件大事 | ⭐ C++ 签名 / torch.ops.* / .pyi 存根 / ATen/ops/*.h 全从这一份 yaml 生成,动一处牵一大片 |
有些算子你在 native/ 里怎么也搜不到实现 |
它是 CompositeImplicitAutograd 的组合实现,或者被 structured_delegate 转走了 |
🛑 读到这里可以停 —— 前半章讲完了(约 19 分钟)。 后半章还有:你机器上已经有半个源码树 · ⭐ 不下载源码、只用 Python 就能查的三件事 · ⭐ 一次完整的定位演练 · ⚠️ 诚实收尾:你不需要能编译 PyTorch 才能读它的源码 回来的时候不用重读,直接从下一节接着看就行。
🧩 四、你机器上已经有半个源码树
⭐ 很多人不知道:pip 装的 torch 里就带着一大堆 C++ 头文件。 现场看一眼:
# c06_layout.py
import os
import torch
root = os.path.dirname(torch.__file__) # ⭐ 现场取,别写死绝对路径
for p in ["include/ATen", "include/c10", "include/torch/csrc", "lib"]:
ok = os.path.exists(os.path.join(root, *p.split("/")))
print(("✅ 有" if ok else "❌ 无"), p)
# ⚠️ _C 的文件名带 Python 版本和平台,别写死
print([f for f in os.listdir(root) if f.startswith("_C.") and f.endswith((".pyd", ".so"))])
实跑输出:
要点
✅ 有 include/ATen
✅ 有 include/c10
✅ 有 include/torch/csrc
✅ 有 lib
['_C.cp313-win_amd64.pyd']
⚠️ _C.cp313-win_amd64.pyd 这个名字里的 cp313 和 win_amd64 就是 04 章那三种报错的来源 —— Python 版本和平台焊在文件名里,换一个都不认。⭐ 所以你的机器上一定不叫这个名字,上面那段代码才要动态列出来。
三样东西各是什么:
| 东西 | 是什么 | ⭐ 你能从里面读到 | ⚠️ 读不到 |
|---|---|---|---|
include/ |
头文件(.h),第二节那三个目录都在 |
类型定义、函数签名、宏(05 章那段 TORCH_LIBRARY 展开就是从 include/torch/library.h 抄的)、大量注释 |
⚠️ kernel 怎么算的 —— .cpp 不在 wheel 里 |
lib/ |
动态库(.dll / .so),编译好的二进制 |
导出符号 | 源码(除非你想读汇编) |
_C.*.pyd |
⭐ Python 扩展模块本体,import torch 时被加载的就是它 |
—— | —— |
⭐ 这三样正好是「编译一个 C++ 扩展」需要的全部输入:编译时找 include/,链接时找 lib/,运行时和 _C 待在同一个进程里。04 章讲的 ABI 匹配,匹的就是这三样。
🚦 ⭐ 头文件不是废料,它是最好读的那部分
「只有声明没有实现」听起来像残缺品,实际上恰恰是最适合读的形态:
c10/core/DispatchKey.h—— 分派键的完整枚举加大段注释,05 章那张表的列有哪些,这里一次看全torch/library.h——TORCH_LIBRARY系列宏的定义和官方注释(05 章第五节那段真实源码就在本机第 975 行起)ATen/ops/*.h—— ⭐ 每个算子一个头文件,从 yaml 生成的,签名和 schema 一一对应
⚠️ 但别指望在头文件里找到 kernel:gelu 到底怎么算的那几十行在 aten/src/ATen/native/ 的 .cpp 里,wheel 里没有,必须去 GitHub。
🧩 五、⭐ 不下载源码、只用 Python 就能查的三件事
这一节是全章最实用的部分。一个 REPL 就够,不 clone、不编译。
# c06_probe.py
import torch
# ① 这个算子的签名到底长什么样
print(torch.ops.aten.add.Tensor._schema)
# ② 这个 tensor 会走哪一列
print(torch._C._dispatch_key_set(torch.ones(2)))
# ③ 全站算子表有多大
print(len(torch._C._jit_get_all_schemas()), "条 schema")
实跑输出:
关键信息
三件事分别回答什么问题:
| 工具 | 回答的问题 | ⭐ 什么时候用 |
|---|---|---|
torch.ops.aten.<op>.<overload>._schema |
参数顺序、默认值、哪个参数会被就地写 | 写 C++ kernel 前对签名;看不懂某个算子的 out= 变体 |
torch._C._dispatch_key_set(t) |
这个具体的 tensor 属于哪一列 | 遇到 Could not run ... backend 时,⭐ 第一件该做的事 |
torch._C._jit_get_all_schemas() |
全部 4362 条 schema,可以拿来搜 | 找「有没有这么个算子」「它有几个重载」 |
⚠️ 4362 随版本变(torch 2.13.0+cpu 上是这个数,你的一定不同),要看的是量级:四千多条。⭐ 这个数字本身就是一条信息 —— 「把 PyTorch 源码通读一遍」在数量级上就是不成立的目标,你只能反查。
第三件的真正用法是当离线索引来 grep:
# c06_grep.py
import torch
kw = "gelu" # ⭐ 换成你要找的算子
for s in torch._C._jit_get_all_schemas():
if kw in str(s):
print(s)
🗓️ 这一段的输出没在本章实跑核对(上面 c06_probe.py 那三行是实跑的),⚠️ 而且命中数一定随版本变 —— 自己跑一眼就知道了。
⭐ 加上 05 章第四节那个 torch._C._dispatch_dump("aten::gelu"),四件工具凑齐:签名、列、全表、以及每一格注册在哪个文件第几行。整条路线的前三步到此全部完成,一行源码都没下载。
🧩 六、⭐ 一次完整的定位演练
把前面五节串起来,走一遍 aten::gelu:
| 步 | 动作 | 结果 |
|---|---|---|
| ① | 读报错 | 拿到 aten::gelu 和 SparseCPU |
| ② | torch._C._dispatch_key_set(x) |
确认这个 tensor 的第一个键确实是 SparseCPU |
| ③ | torch.ops.aten.gelu.default._schema |
aten::gelu(Tensor self, *, str approximate="none") -> Tensor |
| ④ | torch._C._dispatch_dump("aten::gelu") |
⭐ 没有 SparseCPU 那一行 —— 结论到手:格子空着 |
| ⑤ | ⚠️ 拿 RegisterCPU_3.cpp 去 GitHub 搜 |
❌ 零结果,它是生成物(第三节) |
| ⑥ | ⭐ 改搜 native_functions.yaml 里的 - func: gelu( |
拿到 dispatch: 段和 structured_delegate: |
| ⑦ | 拿 dispatch: 段里的函数名去 aten/src/ATen/native/ 搜 |
✅ 人手写的实现 |
⭐ 第 ⑤ 步失败不是意外,是这条路线的固定环节。 知道它必然失败,就不会在那里耗半小时。
🚦 ⚠️ 三个必踩的坑
① dump 里的路径是官方 CI 机器的。 C:\actions-runner\_work\pytorch\... 这种前缀,你本地绝对没有 —— 它是构建那个 wheel 的机器上的路径,⭐ 但文件名和行号是真的,对着同一个版本的 tag 看仍然有效。
② 一定要按 tag 看,不要看默认分支。 你本机是 torch.__version__ 那个版本,主分支已经往前走了几千个 commit,行号对不上是常态。⭐ 先 print(torch.__version__),再去仓库切到对应的 tag。
③ 搜到的可能是「另一个同名东西」。 算子名在 native/ 里经常有 CPU、CUDA、量化、嵌套张量好几份同名实现。⭐ 判据是 dispatch: 段里写的那个函数名,不是算子名 —— dispatch: 段告诉你 QuantizedCPU 对应的是 gelu_quantized_cpu 这种带后缀的名字,拿它去搜就不会串。
⚠️ 这三条全是「认词不认结构」的老毛病:拿算子名当函数名搜、拿生成物文件名当源文件搜、拿主分支当自己那个版本 —— 每一条都能让你在错的地方读半天正确的代码。
🧩 七、⚠️ 诚实收尾:你不需要能编译 PyTorch 才能读它的源码
这是本板块最后要说的一件事,也是最容易劝退人的一个误解。
🗓️ 未实跑 —— 本机无编译环境(05 章第五节已说明:MinGW g++ 配 MSVC 编译的 CPython + CPU 版 torch,编不出能导入的扩展)。以下关于「从源码编译 PyTorch」的描述作者没有在本机做过,只说它的性质,不给具体数字:
⚠️ 从源码全量编译 PyTorch 要装齐匹配的编译器工具链、CMake、(若要 GPU)CUDA toolkit,占用大量磁盘和内存,耗时以小时计,且第一次基本不会一次成功。
⭐ 但是 —— 上面六节里,需要编译的步骤是零。
三档阅读深度,按需选:
| 档 | 你能做什么 | 代价 | 什么时候够用 |
|---|---|---|---|
| ① 只用本机 Python | 第五节那四件工具:schema、keyset、全表、dump | ⭐ 零 | ⭐ 绝大多数「这个算子为什么报错 / 走了哪条路」的问题,到这里就答完了 |
| ② 加上 GitHub 按 tag 看文本 | 第六节整条路线走完,读到 kernel 源码 | 一个浏览器 | 想搞懂「它到底怎么算的」「为什么这么设计」 |
| ③ 真编译 | 改 PyTorch 本身并验证 | 小时级 + 反复踩工具链 | ⭐ 只有一种情况:你要给 PyTorch 提 PR |
⭐ 想验证自己写的算子机制对不对,也不用编译整个 PyTorch —— 05 章第七节给的那两条路(load_inline 运行时编译、或者干脆用 torch.library 的 Python 接口填格子)都比全量编译便宜好几个数量级。
⭐ 「读源码」和「改源码」是两件事,成本差三个数量级。 把它们混为一谈,是很多人一辈子没打开过 PyTorch 源码的唯一原因。
⚠️ 最后一句提醒:读源码是手段不是目的。01 章第三节那张反面表和 AI 基础设施 07的三个条件依然管用 —— 先确认你真的需要下到这一层,再动手。
🔗 这一章连到哪里
| 相关的地方 | 为什么 |
|---|---|
| 05 章 | 本章第一、三、六节全建在它的 _dispatch_dump 输出上;⭐ 它讲格子怎么填,本章讲填格子的代码在哪个文件 |
| 04 章 | 第四节 _C.cp313-win_amd64.pyd 名字里的 cp313 / win_amd64,⭐ 就是那三种报错的直接来源;include/ + lib/ + _C 三件套正是 ABI 要匹的东西 |
| 01 章 | 第二节说 torch/csrc 是唯一认识 Python 的那层 —— 那条边界物理上就落在这个目录里 |
| 02 章 | 想读 c10/core/TensorImpl.h 的话,intrusive_ptr 的引用计数语义在那一章讲过 |
| 00 章 | ⭐ 本板块到这里读完了,回去看一眼「读完之后往哪走」 |
| 《PyTorch 这个框架本身》09 | ⭐ 想动手而不是只读的话,torch.library 的 Python 接口在那边 —— 第七节说的「不用编译也能验证机制」就是指它 |
| AI 基础设施 07 | 第七节最后一句:下到这一层之前先对那三个条件 |
✅ 检查点
- 从一条
Could not run 'aten::gelu'报错走到 C++ 实现,本章给的路线有哪几步?其中哪一步注定失败,为什么? c10/aten/torch/csrc各是什么?判断一个东西该放在哪个目录,本章给的两个问题是什么?- 为什么
DispatchKey的定义在c10里,而「aten::gelu的CPU格填了什么」不在?这和 05 章哪个现象对得上? native_functions.yaml在哪个路径?它的func:/dispatch:/structured_delegate:三段分别对应 05 章那张表上的什么动作?- 05 章 dump 里的
CPU和Meta两行并不在dispatch:段里,为什么它们照样出现了? RegisterCPU_3.cpp为什么在 GitHub 上搜不到?正确的搜法是什么?这条也顺带解释了「为什么这些文件动辄几千行还带编号」——为什么?- pip 装的 torch 里有
include/lib/_C.*.pyd,各能读到什么、读不到什么?_C的文件名里那两段信息为什么不能写死? - 不下载源码、只用 Python 能查的三件(加 05 章那件共四件)工具分别是什么、各回答什么问题?本机实跑的 schema 总数是多少,这个数字本身说明了什么?
- 去 GitHub 对源码时,本章点名的三个坑是什么?
- 读 PyTorch 源码的三档深度分别要付出什么代价?什么情况下才真的需要从源码编译 PyTorch?
👀 答案
- 六步:报错 → 算子名 → keyset 确认列 → schema 拿签名 →
_dispatch_dump拿「注册在哪个文件第几行」→ 去native_functions.yaml找dispatch:段 → 拿函数名去aten/src/ATen/native/搜实现。⚠️ 注定失败的是「拿 dump 给的文件名去 GitHub 搜」:RegisterCPU_3.cpp这类带build\路径的文件是构建时生成的,仓库里根本不存在。知道它必然失败,就不会在那里耗时间。 c10= 最底层公共设施(DispatchKey、TensorImpl、Device、intrusive_ptr),不认识任何具体算子;aten= A Tensor library,算子的家(native/下的 kernel 和native_functions.yaml);torch/csrc= 认识 Python 的那一层外加autograd/jit/distributed/。两个问题:「它认识 Python 吗」(认识 →torch/csrc)、「它认识具体算子吗」(不认识 →c10,认识 →aten)。- 因为 表的骨架归 c10,表的内容归 aten —— 定义骨架的那一层压根不知道有哪些行。这正好对上 05 章那个现象:这张表不是编译期写死的常量表,是运行时一批批被加载进来的(本机
CUDA那列全空,就是因为 CPU 版轮子里没有那批库)。 aten/src/ATen/native/native_functions.yaml。func:= schema 本尊 = 新开一行(等价于 C++ 的m.def);dispatch:= 键→函数名映射 = 一次填好几个格子(等价于一串m.impl);structured_delegate:= 「转给gelu.out」,让代码生成器替你填CPU/CUDA/Meta那几格。- 因为它们不是手写注册的,是生成的 ——
structured_delegate: gelu.out让代码生成器按 schema 吐出了这几格。反过来,dump 里的MkldnnCPU/QuantizedCPU/NestedTensorCPU/NestedTensorHPU正是dispatch:段里手写列出的那几个,两边严丝合缝对得上。 - 因为它是生成物,按
native_functions.yaml在构建时吐出来、构建完躺在build/目录里,仓库里没有。正确搜法是 不搜文件名、搜算子名:在native_functions.yaml里找- func: gelu(,读dispatch:段拿到函数名,再拿函数名去aten/src/ATen/native/搜。几千行加_0_1_3编号也是同一个原因 —— 生成出来的,编号是为了切开编译单元,否则一个文件编译不动。 include/= 头文件,能读到类型定义、函数签名、宏(05 章那段TORCH_LIBRARY展开就抄自本机include/torch/library.h第 975 行起)和大量注释,⚠️ 读不到 kernel 怎么算的(.cpp不在 wheel 里);lib/= 编译好的动态库,只有导出符号没有源码;_C.*.pyd= Python 扩展模块本体,import torch加载的就是它。文件名里的cp313(Python 版本)和win_amd64(平台)焊死在名字里,⚠️ 换一个都不认 —— 那正是 04 章三种报错的来源,所以代码里要os.listdir动态列,不能写死。- ①
torch.ops.aten.<op>.<overload>._schema→ 参数顺序、默认值、哪个参数会被就地写;②torch._C._dispatch_key_set(t)→ 这个 tensor 属于哪一列(遇到Could not run ... backend第一件该做的事);③torch._C._jit_get_all_schemas()→ 全表,可以拿来 grep;④(05 章)torch._C._dispatch_dump(op)→ 每一格注册在哪个文件第几行。实跑 4362 条(⚠️ 随版本变)。这个量级本身说明:「把 PyTorch 源码通读一遍」在数量级上就不成立,只能反查。 - ① dump 里的
C:\actions-runner\...是官方 CI 机器的路径,本地绝对没有(但文件名行号是真的);② 必须按 tag 看不能看默认分支 —— 先print(torch.__version__)再切 tag,否则行号对不上是常态;③ 同名实现有好几份(CPU / CUDA / 量化 / 嵌套张量),判据是dispatch:段里那个函数名(如gelu_quantized_cpu)而不是算子名。⚠️ 三条都是「认词不认结构」的老毛病。 - ① 只用本机 Python:零代价,绝大多数「为什么报错 / 走了哪条路」的问题到这就答完了;② 加 GitHub 按 tag 读文本:一个浏览器,能读到 kernel 源码;③ 真编译:要装齐匹配工具链 + CMake +(GPU 的话)CUDA toolkit,耗时以小时计且第一次基本不会成功(🗓️ 本章未实跑)。只有一种情况真需要它:你要给 PyTorch 提 PR。 想验证自己写的算子机制,用
load_inline或torch.library的 Python 接口就够,便宜好几个数量级。
🛑 可以停在这里
⚡ 走神救援
🗺️ 读 PyTorch 源码不是「clone 下来从头看」,是【从一条报错反查】。 实测总 schema 有四千多条——⭐ 这个量级本身就判了「通读」死刑。
整条路线是六步:报错 → 算子名 → 确认分派键 → 拿 schema → 拿「注册在哪个文件第几行」→ 去
native_functions.yaml拿真正的函数名 → 找实现。⚠️⚠️ 其中有一步注定失败:dump 里写的那个文件名去仓库里搜是零结果——带
build\的全是构建时生成物。⭐ 知道它必然失败,就不会在那儿耗半小时。📁 三个目录,两个问题就能分清:「它认识 Python 吗」——认识的是
torch/csrc(⭐ 唯一会 includePython.h的一层,那条跨语言边界物理上就在这里);「它认识具体算子吗」——不认识的是c10,认识的是aten。⭐ 一句话记法:表的骨架归 c10,表的内容归 aten——这正好解释了「表不是编译期常量」,因为定义骨架的那层压根不知道有哪些行。📒
native_functions.yaml是整条生成链的上游:func:等于新开一行,dispatch:等于一次填好几个格子,而structured_delegate:让代码生成器替你填。⭐ 拿 dump 一对就明白:有些键在dispatch:段里手写着,有些不在段里却照样出现——后者就是生成的。⭐ 正确搜法:不搜文件名,搜算子名。 ⚠️ 另外那个动态库的文件名把 Python 版本和平台焊死在名字上,换一个都不认——所以路径要动态取,绝不写死。
下一节 👉 附录A · 报错反查与速查