首页
/ PyTorch 仓库 AI 协作者开发规范全解:从 CLAUDE.md 看构建、测试、Lint、提交与 CUDA 编程约定

PyTorch 仓库 AI 协作者开发规范全解:从 CLAUDE.md 看构建、测试、Lint、提交与 CUDA 编程约定

2026-09-04 21:36:48作者:韦蓉瑛

本文以 PyTorch 仓库根目录的 CLAUDE.md 为主体,系统拆解这份面向 AI 编码智能体(Agent)的强制性协作文档:它规定了 Agent 在 GitHub 上的行为边界(AI 政策)、唯一合法的构建命令、测试框架写法、Lint 与提交信息规范、ghstack 工作流,以及 Dynamo 配置补丁、结构化日志、cuda.bindingscuda::ptx 等一批仓库级编程约定。读完本文,你既能理解 PyTorch 上游贡献流程的工程细节,也能掌握如何约束 AI 工具在大型 C++/Python 混合仓库中安全、合规地协作开发。

一、AI 政策:仓库协作的强制行为边界

CLAUDE.md 开篇即声明"AI Policy — MANDATORY",要求任何与仓库交互的 Agent 必须先阅读 AI_POLICY.md 并遵守其中规则。这份政策在 AI_POLICY.md 中的核心立场是:AI 工具可以被用来辅助准备 issue、PR、评审和评论,但 AI 生成的内容必须明确披露并被限制在代码块或引用块内,且必须伴随人类评论说明其相关性;完全由自主 Agent 生成的贡献不被接受,维护者可能会关闭这类 PR。

CLAUDE.md 将上述政策细化为四条 Agent 必须遵守的硬性规则:

  1. 绝不在 GitHub 上自主行动。除非用户已审阅并明确批准了确切内容,Agent 不得打开、编辑、评论或回复任何 issue/PR。完全由 Agent 生成的贡献是被禁止的,会被直接关闭。
  2. 标记所有 AI 生成内容。凡是进入 issue、PR 或评论的文本,必须包裹在代码块或引用块中,绝不能伪装成人类撰写。
  3. 不得只输出裸的 AI 文本作为回复。任何 AI 内容都必须附带人类评论来解释其相关性。
  4. 不提交用户未读过的代码。变更应保持最小化,去除 AI 痕迹与不必要的复杂度;若 PR 尚未就绪或未经用户审阅,必须以 draft 模式打开。

这条政策的意义在于:PyTorch 是一个由人类维护者承担最终责任的仓库,AI 是"草稿生成器"而非"提交者",所有产出必须经过人类理解与背书。

二、工作环境约定:Scratch 目录、venv 与 PR 评审

CLAUDE.md 规定了三个基础环境约定:

  • Scratch Space:临时脚本、草稿文件和一次性实验一律放在仓库根目录的 agent_space/ 下(该目录被 git 忽略),且不得提交其中任何文件。这避免了临时产物污染版本历史。
  • Environment:当 pippythonspin 等工具缺失时,先检查项目根目录或其父目录是否存在 .venv 目录;找到则激活后重试;找不到就停下来询问用户是否需要环境。明确禁止自行寻找替代方案或擅自安装工具——这一点对避免 Agent 破坏用户环境至关重要。
  • PR Review:当被要求评审 PR 时,必须使用仓库提供的 /pr-review skill,而不是自由发挥评审流程。

此外,文档对 CI Docker 镜像给出了一条反直觉但重要的规则:.ci/docker/ 目录是被**内容哈希(content-hashed)**的,目录内任何文件变化(包括 README)都会改变哈希并触发全量 Docker 镜像重建。因此除非有意重建镜像,否则不要改动该目录;当 Docker 构建因上游原因(如 Ubuntu 故障)损坏时,更不要碰这个目录,以免把重建"钉死"在损坏状态上。从这条规则可以推断,PyTorch 的 CI 体系通过目录哈希实现了镜像缓存的精确失效控制。

三、构建:唯一合法的命令

CLAUDE.md 对构建流程的规定非常强硬:

pip install -e . -v --no-build-isolation
  • 无论是 codegen、C++ 还是 Python 部分,所有构建都只走这一条命令,禁止运行任何其他构建命令(例如直接调用 python setup.py build)。
  • 在跑构建之前,必须先检查本地记忆中是否有构建配置(环境变量、增量构建捷径等),有则应用;没有则询问用户,而不是凭经验猜测。

这条约束的工程背景是:PyTorch 的构建体系极其复杂(codegen、CMake、C++ 扩展、Python 打包层层嵌套),绕过 setup.py 入口的"捷径"往往会导致产物不一致。对应地,仓库根目录存在 setup.pyCMakeLists.txtbuild_variables.bzl 等构建入口文件,pip install -e . 正是统一的驱动入口。

四、测试框架:TestCase、assertEqual 与设备泛型测试

CLAUDE.md 要求所有新测试使用仓库自带测试类与测试运行器:

from torch.testing._internal.common_utils import run_tests, TestCase

class TestFeature(TestCase):
    ...

if __name__ == "__main__":
    run_tests()

并给出三条具体规范:

  • 张量相等性比较用 assertEqual,不要手写逐元素比较;
  • 多输入测试用 @parametrize 装饰器参数化;
  • 任何检查设备上(on-device)实现数值的测试,必须用 instantiate_device_type_tests 写成设备泛型测试,这样同一套测试逻辑可以自动覆盖 CPU、CUDA、XPU 等设备。

这套约束保证了测试代码在 PyTorch 多设备架构下的可移植性——测试只声明"对任意支持设备做数值验证",由 instantiate_device_type_tests 完成设备维度的展开。

五、类型桩(Type Stubs):改 .pyi.in 而不是 .pyi

文档明确指出:许多 .pyi 文件是从对应的 .pyi.in 模板生成的。修改类型定义时永远编辑 .pyi.in 源模板,而不是生成的 .pyi——否则下次重新生成时手改内容会被覆盖丢失。仓库中这类文件成对存在,例如 torch/_C/ 目录下既有多个 .pyi 桩文件也有对应的模板文件,修改前应先确认目标文件是否为生成产物。

六、Lint 规范:spin、S101 与 B950 的精确写法

6.1 只用 spin 命令做 Lint

  • 仅使用 spin 提供的命令做 lint;spin help 列出可用命令;
  • 常规流程:spin lint 运行检查,spin fixlint 应用自动修复;
  • 当用户要求 commit 或 amend 时,先运行 lintrunner -a,修复它报告的所有 lint 错误后再提交。

6.2 绝不使用 noqa 压制 S101

Ruff 的 S101(Use of assert detected)必须通过改写 assert 来修复,绝不能加 # noqa: S101。文档特别强调:lint 工具自己会建议在消息里提 noqa,忽略这个建议。原因是普通的 assert 语句在 python -O(优化模式)下会被剥离,被压制的 assert 相当于一个在优化运行中"静默消失"的检查。文档给出的正反对照示例:

# Bad - silences the rule; the check disappears under `python -O`
assert isinstance(x, Foo)  # noqa: S101

# Good
if not isinstance(x, Foo):
    raise AssertionError(f"expected Foo, got {type(x)}")

改写时还有一套精细规则:

  • 如果原 assert 带消息(assert cond, msg),保留该消息(if not cond: raise AssertionError(msg));没有消息则合成一个简短消息,说明期望值与实际值;
  • 条件能干净取反时直接取反(is not None -> is Nonein -> not in== -> !=),而不是套一层 not (...)
  • 对浮点值不要取反 </>/<=/>=:当值为 NaN 时 not (a < b) 并不等价于 a >= b,所以这类条件保留 if not (a < b) 的写法。

6.3 多行字符串块里的 B950 行长超限

仓库的行长上限是 88 列(pyproject.tomlline-length = 88),且 pyproject.toml 中显式禁用了 E501 而改用 B950 作为行长检查。当 B950 在多行字符串块上触发时:既不能把 # noqa: B950 直接放在超限的那一行(会改变字符串语义),也不能换行拆分字符串(字符串内容必须保持不变)。正确做法是# noqa: B950 放在终止三引号所在的同一行

    self.assertExpectedInline(
        foo(),
        """
this line is too long...
""",  # noqa: B950
    )

这一规则针对的正是 FileCheck/断言黄金字符串这类"内容不可变、行数不可断"的场景。

七、Git 与提交信息规范

7.1 分支策略与 CI 状态拉取

  • 若在默认分支上,遵循"先建分支再提交"的原则;
  • 若 HEAD 处于 detached 状态,这是有意为之(ghstack 工作流所致),不要新建分支,直接提交到当前 detached HEAD 上;
  • 拉取 CI 状态:一个 PR 有数百个 check-run,单次 check-runs?per_page=100 调用会被静默截断,导致"红看成绿"。应使用 gh pr checks <PR> --json name,state,workflow,link,bucket,completedAt(该命令天然只返回 head 状态,无分页问题)。

7.2 Commit message 写作规则

  • 除非用户明确要求,否则不提交
  • 不要写逐条变更的 bullet list:大 PR 应说明评审变更的逻辑顺序,小 PR 干脆省略列表;
  • 提交信息必须清晰、信息充分,并包含 Test Plan 小节描述如何测试该变更;
  • 修复 bug 时,必须说明 bug 的根因和修复如何起作用;
  • 如果存在多种可行技术路径,简要列出并论证所选路径的理由;
  • 测试策略描述中要包含实际运行过的字面命令(放在 Markdown 围栏代码块中);
  • 披露 PR 是在 AI 助手协助下完成的
  • amend 提交时,检查提交信息是否仍准确描述变更;不准确且不是 ghstack 提交时,更新消息。ghstack 提交 amend 消息是 no-op,此时只需提醒用户必要时更新 PR 描述;
  • 若提交信息中包含 ghstack-source-idPull-Request trailer,重写或拆分提交信息时必须保留它们——ghstack 需要时会自动更新 source id。

八、ghstack 工作流细则

ghstack 是 PyTorch 上游大量使用的提交栈工具,其提交遵循与普通 GitHub 分支/PR 完全不同的工作流。CLAUDE.md 给出了一套识别与操作规则。

识别当前是否在 ghstack 提交上

  • HEAD 是 detached commit —— 几乎可以肯定处于 ghstack 流;
  • 提交信息含 ghstack-source-id trailer —— 是既有 ghstack 提交;
  • 提交关联 origin/gh/USERNAME/N 这样的远程分支 —— 大概率是 ghstack 提交(不完美信号:本地 amend 后未 push 会造成失同步)。

操作规则

  • 除非被要求,否则不 amend。用户让 Agent 处理 ghstack 提交时,保持变更未提交,让用户用 git diff 审阅;只有用户明确要求 amend 或直接提交时才 amend。
  • 提交:运行 ghstack。只改单个提交时用 ghstack --no-stack,避免更新整个提交栈、烧掉不必要的 CI;有意更新整栈 CI 时才用完整 ghstack
  • 保留元数据 trailer:编辑提交信息时绝不删除 Pull-Request:ghstack-source-id: trailer。每次 compose amend 都要从 HEAD 重新读取 trailer,绝不复用缓存的旧消息体——因为 ghstack 每次 push 都会重写 ghstack-source-id,过期的 trailer 会覆盖 HEAD 上当前的值。若修改了提交信息,之后运行 ghstack -u 推送更新的 PR 描述。
  • 绝不直接 push:不 git push 到任何分支,也绝不直接修改 gh/USERNAME/N 分支——这些由 ghstack 管理。
  • 找 PR:用户要拉取 ghstack 提交的 CI 结果或代码评审时,从提交信息的 Pull-Request trailer 拿 PR URL,再用 gh CLI 抓取状态/评论。
  • 编辑早期提交/拆分:把它当作普通提交栈处理(用 git rebase 等)。保留元数据 trailer 的提交继续关联原 PR;没有 trailer 的提交在提交时获得新 PR。这类场景通常适合跑一次完整 ghstack

九、编码风格指南

CLAUDE.md 对仓库内所有代码变更规定了一组风格准则:

  • 最小化注释,代码应自解释;注释用于提供无法从本地推断的全局背景;
  • 不为只用一次的 1-2 行逻辑建平凡辅助函数(除非显著可读性收益);
  • 偏好清晰的抽象、显式的状态管理。例如 Python 类应显式声明全部成员,而不是运行时 setattr 一个字段、之后再动态 getattr
  • 匹配现有代码风格与架构模式;
  • 假设读者熟悉 PyTorch:读者未必是所读代码的专家,但该领域有基础经验;
  • 对抗 ruff 列宽限制:代码被 linter 折成多行通常比单行更差读。当 linter 折行时,应优先通过改变变量名或引入局部辅助变量把它还原为单行;对断言黄金字符串的测试,只保留黄金字符串本身在单行上,用 noqa: B950 豁免列宽规则;
  • 新增注释只用 ASCII:不引入 Unicode 字符(智能引号、em dash、箭头、非 ASCII 字母等)。已存在的 Unicode 注释保持原样,该规则只约束新增或重写的注释。

收尾原则一句话:"拿不准时,选更简单、更简洁的实现。"

十、cuda.bindings 的两大约定

10.1 错误检查统一走 _check_cuda_bindings

文档要求:对 cuda.bindings 的 runtime 调用做错误检查时,必须使用 torch.cuda._utils._check_cuda_bindings,不要自己写内联的错误检查辅助函数。该函数确实定义在 torch/cuda/_utils.py 中(同文件还另有 _check_cuda_bindings_driver 用于 driver API 返回值),统一入口保证了错误转换逻辑(把 CUDA 错误码翻译为 Python 异常)在仓库内一致。

10.2 原始句柄(int)直接传入

cuda.bindings 的 runtime 函数接受以 Python int 直接传入的原始句柄。当你手上已经有一个 int 句柄——无论它来自 CUDAGraph.raw_cuda_graph() / raw_cuda_graph_exec()(见 torch/cuda/graphs.py)、流的 .cuda_streamint(node) 还是其他来源——直接传入即可,不要为了把已有的 int 交给 bindings 调用而去构造类型化包装对象(cudaGraph_t(init_value=...)cudaGraphExec_t(init_value=...)cudaStream_t(init_value=...) 等)。

文档给出的正确/错误对照:

# Good
_cuda_runtime.cudaGraphGetId(g.raw_cuda_graph())

# Bad - 仅为传递一个 int 而构造 typed wrapper
cudaGraphGetId(cudaGraph_t(init_value=g.raw_cuda_graph()))

只有当一个类型化对象本身确实需要作为独立值使用时,才构造它。

十一、Dynamo 配置:永远用 torch._dynamo.config.patch

文档规定:临时修改 Dynamo 配置时必须使用 torch._dynamo.config.patch,它既可以作为测试方法的装饰器,也可以作为上下文管理器:

# Good - use patch as decorator on test method
@torch._dynamo.config.patch(force_compile_during_fx_trace=True)
def test_my_feature(self):
    # test code here
    pass

# Good - use patch as context manager
with torch._dynamo.config.patch(force_compile_during_fx_trace=True):
    # test code here
    pass

# Bad - manual save/restore
orig = torch._dynamo.config.force_compile_during_fx_trace
try:
    torch._dynamo.config.force_compile_during_fx_trace = True
    # test code here
finally:
    torch._dynamo.config.force_compile_during_fx_trace = orig

从源码结构看,这一约定有坚实的实现基础:PyTorch 的配置模块统一由 torch/utils/_config_module.py 中的 ConfigModule 机制驱动,其中内置了 ConfigPatch 上下文装饰器——它自动完成"保存旧值、应用新值、退出时恢复"的事务,杜绝了手动 save/restore 在异常路径下漏恢复、污染全局配置状态的典型 bug。文档示例中的 force_compile_during_fx_trace 也确实存在于 torch/_dynamo/config.py(默认值为 False)。

十二、日志与结构化追踪:面向两类用户人群写诊断

文档要求添加调试日志时考虑两类用户场景:

  1. 本地开发:用户本地运行,可以访问磁盘文件;
  2. 生产作业:用户只能通过 tlparse 从结构化 trace 中提取日志。

针对生产调试,使用 trace_structured 记录产物(artifact):

from torch._logging import trace_structured

# Log an artifact (graph, edge list, etc.)
trace_structured(
    "artifact",
    metadata_fn=lambda: {
        "name": "my_debug_artifact",
        "encoding": "string",
    },
    payload_fn=lambda: my_content_string,
)

检查结构化追踪是否启用(用于条件化提示信息):

from torch._logging._internal import trace_log

if trace_log.handlers:
    # Structured tracing is enabled, suggest tlparse in error messages
    msg += "[Use tlparse to extract debug artifacts]"

错误诊断最佳实践

  • 生产环境永远记到 trace_structured(禁用时零运行时开销——metadata_fn/payload_fn 是惰性求值的 lambda,未启用时根本不会被调用,这一点可从 torch/_logging/_internal.pytrace_structured 的函数签名得到印证:它接受的是 metadata_fn: Callablepayload_fn: Callable 而非裸值);
  • 遇到真正的内部编译器异常时,可以考虑同时写本地文件,方便本地调试;
  • 错误消息中向用户同时说明两种途径:本地文件(如 FX graph dump: min_cut_failed_graph.txt)与生产途径("Use tlparse to extract artifacts",仅在追踪启用时提示);
  • 使用 _get_unique_path() 模式避免覆盖已有的调试文件。

十三、cuda::ptx 类型化包装器的五条实现细节

当使用 <cuda/ptx> 类型化包装器编写 PTX 指令时,文档总结了一组踩过坑的实现细节:

  • 命名空间解析:在 namespace at::native 内部,非限定名 cuda::ptx 会解析到相邻的 at::cuda 命名空间。必须写 ::cuda::ptx,或者加别名:namespace ptx = ::cuda::ptx;
  • 头文件冲突:单体头 <cuda/ptx> 与重量级 PyTorch 头(如 Loops.cuh)一起包含时可能编译失败,原因是传递头(如 cp_async_bulk_tensor.h)中的 CCCL 缺陷。规避方式:把使用 <cuda/ptx> 的 kernel 放进一个只包含最小头文件的独立 .cu 文件
  • mbarrier_try_wait_parity 是非阻塞的ptx::mbarrier_try_wait_parity() 返回 bool(只尝试一次),必须自己包一层自旋循环:while (!ptx::mbarrier_try_wait_parity(mbar, parity)) {}
  • Half/BFloat16 类型cuda::ptx 的重载使用 CUDA 原生类型(__half__nv_bfloat16),不是 PyTorch 包装类型(c10::Halfc10::BFloat16)。在调用点用 reinterpret_cast 转换。
  • cp_async_bulk_wait_group:通过 ptx::n32_t<N>{} 接受编译期常量,而不是运行时整数。
  • Mbarrier 的共享内存:mbarrier 内存绝不允许与 TMA 操作目标的数据产生别名(alias)。把 mbarrier 放在与数据缓冲区分离的独立 smem 区域。

十四、小结:一份可检索的仓库级"操作手册"

CLAUDE.md 实质上是一份把 PyTorch 上游贡献流程中"隐性知识"显性化的操作手册,其各章节与仓库设施一一对应:

主题 关键约定 对应仓库设施
AI 政策 不自主行动、AI 内容必须包裹并披露 AI_POLICY.md
构建 唯一命令 pip install -e . -v --no-build-isolation setup.py
测试 TestCase + assertEqual + parametrize + 设备泛型测试 torch/testing/_internal/common_utils.py
类型桩 只改 .pyi.in torch/_C/
Lint spin lint / spin fixlint / lintrunner -a;S101 与 B950 精确修法 pyproject.toml
提交 Test Plan、根因说明、AI 披露、保留 ghstack trailer ghstack 工作流
Dynamo 配置 torch._dynamo.config.patch torch/_dynamo/config.py
结构化日志 trace_structured 惰性记录 artifact torch/_logging/_internal.py
CUDA 绑定 _check_cuda_bindings 统一检查、int 句柄直传 torch/cuda/_utils.py
PTX 命名空间、头文件隔离、mbarrier 自旋、类型转换 <cuda/ptx> 相关 kernel

对贡献者而言,这份文档最大的价值是把"为什么"(为什么 S101 不能 noqa、为什么 B950 要放在终止引号行、为什么 CI 检查要用 gh pr checks)和"怎么做"(字面命令与代码示例)成对给出,使人与 AI 协作者都能在同一套可验证的规则下工作。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384