PyTorch 的测试体系有一个很容易让新贡献者困惑的特点:很多测试不是静态写在文件里的,而是在导入测试模块时动态生成的。于是你在 CI 日志里看到的失败用例,可能带着设备、dtype 或参数组合后缀,看起来和源码里的模板函数并不完全一致。
这不是 CI 在“乱报错”,而是 PyTorch 为了覆盖多设备、多 dtype、多后端行为而采用的测试生成机制。理解这一点,能显著缩短本地复现和定位失败的时间。
CI 里的名字为什么会变长
在普通 Python 项目里,一个测试函数通常就是一个 pytest node:
def test_add():
assert 1 + 1 == 2
但 PyTorch 这类底层框架需要验证的矩阵要大得多:CPU、CUDA、不同 dtype、不同 layout、不同 device capability,都可能影响行为。为了避免手写大量重复测试,测试通常会以“模板”的形式存在,然后在导入阶段展开成多个具体测试。
可以把它理解成:源码里写的是“测试规则”,CI 跑的是“规则实例”。
例如源码里可能接近这样:
def test_tensor_op(self, device, dtype):
...
而 CI 中看到的失败名称可能类似:
test_tensor_op_cuda_float32
具体命名规则取决于 PyTorch 测试工具链,但核心现象是一样的:失败名包含了生成后的参数信息,而不是只显示模板函数名。
本地复现:先用 pytest -k 缩小范围
排查这类失败时,不要急着全量跑测试。更实用的方式是用 pytest -k 按名称过滤。即使 CI 里的名字是生成后的测试名,-k 仍然很适合做第一轮定位。
可以这样实践,假设失败日志里出现了 test_tensor_op_cuda_float32:
# 进入 PyTorch 源码目录后执行
python -m pytest test/test_torch.py -k "test_tensor_op and cuda and float32" -vv
如果你还不确定具体文件,可以先用 ripgrep 查模板名:
rg "test_tensor_op" test/
找到文件后,再用更窄的命令跑:
python -m pytest test/test_torch.py -k "test_tensor_op" -vv
这里的关键点是:不要要求本地命令里的名字和 CI 完全逐字一致。CI 展示的是展开后的实例名,源码里常常只有模板函数。
test/run_test.py 适合复现 PyTorch 自己的测试语境
PyTorch 还有自己的测试入口 test/run_test.py。当你怀疑失败和 PyTorch 测试基础设施、测试分片、环境变量或测试选择逻辑有关时,用它比直接调用 pytest 更贴近 CI。
可以这样实践:
# 只跑某个测试文件
python test/run_test.py --include test_torch
# 给底层 pytest 传过滤条件,具体参数形式可按本地帮助确认
python test/run_test.py --include test_torch -- -k "test_tensor_op" -vv
如果不确定支持哪些参数,先看帮助:
python test/run_test.py --help
经验上,可以按这个顺序推进:
- 从 CI 日志复制失败测试名里的核心模板名。
- 用
rg在test/下定位源码模板。 - 用
pytest -k快速确认是否能复现。 - 如果直接 pytest 行为和 CI 不一致,再切到
test/run_test.py。
一个最小模型:理解“导入时生成测试”
下面这个小例子不代表 PyTorch 的真实实现,只是帮助理解“源码模板”和“运行时测试名”为什么会不同。你可以复制到任意空目录运行。
创建文件 test_generated.py:
import pytest
DEVICES = ["cpu", "cuda"]
DTYPES = ["float32", "int64"]
def make_test(device, dtype):
def test_case():
if device == "cuda":
pytest.skip("demo machine may not have CUDA")
assert dtype in {"float32", "int64"}
test_case.__name__ = f"test_tensor_op_{device}_{dtype}"
return test_case
for device in DEVICES:
for dtype in DTYPES:
globals()[f"test_tensor_op_{device}_{dtype}"] = make_test(device, dtype)
运行:
python -m pytest test_generated.py -vv
你会看到多个测试实例,而不是一个模板函数。再试试过滤:
python -m pytest test_generated.py -k "tensor_op and float32" -vv
这个模型说明了 PyTorch CI 日志中常见的现象:测试在模块导入时已经被注册成了更具体的名字,本地排查要面向“生成后的实例”和“源码中的模板”两套视角。
排查清单:少跑、精跑、贴近 CI
处理 PyTorch 测试失败时,可以按下面的清单走:
- 看 CI 失败名,拆出模板名、device、dtype 等关键词。
- 用
rg找源码模板,而不是只搜索完整失败名。 - 优先用
python -m pytest ... -k ... -vv做快速复现。 - 当怀疑测试基础设施差异时,改用
python test/run_test.py。 - 注意动态生成测试的边界:源码里的函数名不一定等于最终运行的 pytest node 名。
理解这套机制后,PyTorch 的测试日志会从一长串陌生名字,变成一张可拆解的坐标图:哪个模板、哪个设备、哪个 dtype、哪个参数组合出了问题。定位速度通常就从“全量重跑碰运气”,变成“精准过滤验证假设”。