@PyTorch:让树外加速器与快速迭代的生态系统保持同步并非易事。在一篇新的社区博客中,贡献者们分享了……
摘要
来自 IBM 和 Red Hat 的贡献者联合发表了一篇 PyTorch 社区博客,介绍了他们如何基于 PyTorch 的跨仓库 CI 中继(CRCR)构建 Torch Spyre,通过智能体测试选择流水线和声明式 YAML 测试复用框架,实现树外加速器后端的 L2 集成。
查看缓存全文
缓存时间: 2026/09/30 18:09
从上游变更到下游信心:Torch Spyre 与 PyTorch CRCR 的集成深度解析
原文来源: https://pytorch.org/blog/from-upstream-changes-to-downstream-confidence-inside-torch-spyres-integration-with-pytorch-crcr/
特色项目
- PyTorch logo (https://pytorch.org/projects/pytorch/)
摘要(TL;DR)
PyTorch 的跨仓库 CI 中继(Cross-Repository CI Relay,CRCR)为树外(out-of-tree)加速器提供了一条清晰、可扩展的接入上游 CI 的路径,同时让每个后端自主决定 PyTorch 数以万计的测试中哪些真正对自己的硬件有意义,以及当 PyTorch、后端和测试套件共同演进时,如何保持这一判断的时效性。本文介绍了 Torch Spyre 如何在这一基础之上构建并达成 CRCR 的 L2 集成,其中包括:一条 Agentic 流水线,依据代码与执行证据选择并重新分桶测试;一套声明式 YAML 框架,无需打补丁即可适配上游测试;以及一系列保障大规模可靠性与可复现性的工作流模式。其中大部分内容适用于任何通用的 privateuse1 设备,我们期待与 PyTorch 社区合作,使其可复用,并在合适之处推入上游。
引言
PyTorch 的跨仓库 CI 中继(CRCR)弥补了 PyTorch 与树外(OOT)加速器仓库之间的协作缺口。它提供了一套标准化方式:在上游变更时触发下游 CI、将结果直接呈现给 PyTorch 的评审者,并通过 PyTorch CI CRCR HUD 将回归以统一视图呈现。具体做法是:允许 OOT 加速器仓库接收来自 pytorch/pytorch 的 repository_dispatch,并将结果回报至 HUD,集成负担极小——只需一个 allowlist 条目、一个监听 repository_dispatch 的 workflow,以及一个执行组合式回调 action。此外,CRCR 提供了四个集成层级,使下游项目可以渐进式地完成接入:从接收上游派发通知,到通过 HUD 回报结果,最终参与非阻塞或阻塞式上游 PR 验证。
真正有意思的工程工作出现在稍后的环节——即中继留给各个后端自己决策的部分:哪些是测试候选?哪些派发值得触发构建?PyTorch 数以万计的测试中,哪些在你的硬件上有意义?如何在不分叉的前提下适配为 CUDA 编写的测试?以及“绿灯“可以被允许代表什么含义?这些决策理应由后端来掌控,因为只有后端才能做出正确判断。本文描述了我们为此构建的机制,包括一条 Agentic 测试选择流水线、一套声明式测试复用框架,以及可靠的工作流。这些共同将 Torch Spyre 推进了 L2 集成。按设计,其中没有任何部分是 Spyre 专属的——加速器位于配置文件之后,而非 CI 逻辑之中。因此,与其把这些作为模式留给每个后端自行重新实现,我们计划与 PyTorch 团队合作,将可复用的部分推入上游,以 PyTorch OpenReg 作为天然的参考点。
三个不断演进的测试对象:OOT 加速器 PyTorch 后端、PyTorch 核心、测试套件
**
图 1:PyTorch 与 Torch Spyre 中的测试面
挑战一:决定要测什么:三个持续变化的对象,以及选择何种组合来运行
在收到 PyTorch CRCR 的派发之后,OOT 加速器通常要面对三个不断演进的测试对象。第一,是 OOT 加速器的 PyTorch 后端代码库,例如 Torch Spyre。作为 IBM Spyre 加速器的 PyTorch 后端,Torch Spyre 通过 PyTorch 的 OOT 扩展接口实现深度集成,这暴露了一个庞大的测试面,涵盖了对 PyTorch 核心的各类钩子,包括算子与运行时行为。第二是 PyTorch 核心本身——运行时组件或其他核心功能的变更,都可能在 OOT 后端中引入回归。第三是持续演进的测试套件,测试可能被修改,从而暴露出回归。以上任意一个对象都可能引入回归。用 PyTorch 测试套件来测试 OOT 加速器后端,有助于同时在这三者之间暴露出回归问题。
明确了这三个对象之后,紧接着的决策是:实际运行它们的哪一种组合?当一次派发到达时,OOT 加速器后端可能自上次以来已有进展,PyTorch 核心有了新提交,测试套件本身也可能发生了变化。最主要的组合是三者的最新状态——最新的后端代码、最新的 PyTorch 核心,以及派发发生时所对应的测试套件。这正是让回归具备可归因性的原因:当自上次绿色运行以来唯一发生变化的是上游时,新的失败自然指向了上游。
除此之外,也可以扩展纳入其他有用的组合,例如将一系列 OOT 加速器后端版本与持续演进的 PyTorch 核心和测试套件进行测试。这有助于发现每个 OOT 加速器后端版本的前向兼容与后向兼容问题。进一步地,每种组合都可以作为独立 job 报告到 HUD,使与每种组合相关的结果和回归更易追踪。
驯服 PyTorch 测试套件
挑战二:为你的 OOT 加速器后端识别候选测试
**
图 2:用于测试选择的 Agentic 工作流
PyTorch 拥有数以万计的测试。人工找出其中与你的后端相关的测试并不能规模化的扩展,而在 PyTorch 与你的后端都不断演进的同时保持这一筛选的时效性则更加困难。因此,我们构建了一条 Agentic 流水线来完成这项工作。
该流水线分为四个阶段(见图 2)。前两个阶段构建上下文;后两个阶段做出决策:先依据代码,再依据执行。
- 高层选择。 第一个 Agent 利用两项输入,将搜索范围从整个测试树缩小到值得分析的文件夹和顶层文件:一是后端实际钩入了哪些 PyTorch 扩展点,二是一些用自然语言描述的范围偏好。不钩入
torch.dynamo、或尚未准备好接管 autograd 的后端,可以直接在这里说明,并完全跳过相应测试树。 - 仓库记忆。 仓库记忆生成器(Repository Memory Generator)将这份精简清单转化为一个可查询的索引,其中包含符号、文件、由 LLM 撰写的摘要以及逐个测试的 embedding。这让下一阶段的选择 Agent 能够一次性对整个候选集合进行推理,而不必受单个上下文窗口容量的限制。
- 底层选择。 随后,第二个 Agent 阅读后端自身的代码库、文档和元数据(例如支持的算子),再查询仓库,确定要运行的确切测试,并将它们划分为
mandatory_success或skip。每项决策都会连同其背后的推理一并记录。 - 基于真实执行的修正。 选中的测试在真实硬件上运行。Agent 会利用执行日志进行第二遍处理,这有助于捕获静态分析所遗漏的运行时失败与数值差异。最终输出是一个经过修正、重新分桶的测试集合,并附带更新后的推理。
该流水线目前会为运行的每个上游测试文件生成一份逐文件配置,覆盖数以千计的具名测试用例,并通过一次合并运行在真实硬件上对它们加以验证。覆盖率会自动增长:当后端新增支持一个算子时,所有等待它的测试都会自动被纳入,无需任何人工维护列表。
可审计性至关重要:每一次分桶决策都会以注释形式写回配置文件,说明原因。当有人问“为什么这个测试被跳过了?“,答案就在文件里,而不是在某个模型的记忆中。Agent 提出建议;配置文件才是可评审的正式产物。
此外,这条 Agentic 流水线还可以针对特定用例进行定制。下文将讨论其中两个用例。
用例 A:启用一个新的算子
当后端启用了一个新算子时,我应该打开哪些测试?
这涉及准备一份“测试—算子“映射:为每个测试记录其所使用的算子,并构建一个反向索引,将每个算子映射到使用它的完整测试列表。仓库记忆生成器会在一次性的执行过程中捕获这些元数据:
- Eager 路径:通过
TorchDispatchMode捕获 - 编译路径:通过
TORCH_LOGS捕获
然而,通过这种方式捕获的测试并不能直接加入测试套件。一个测试虽然可能使用了某个期望的算子,但也可能同时用到其他与 OOT 加速器无关、或目前尚不支持的 PyTorch 特性。因此,算子信息会连同已识别出的测试文件一并传递给底层选择 Agent。该 Agent 会利用这些信息进一步筛选候选,生成最终可以加入测试套件的测试与文件集合。
结果: 启用一个算子即可浮现出所有相关测试,无需任何人工筛选。
用例 B:升级 PyTorch 版本
在升级到新的 PyTorch 版本时,如何在不重新处理全部内容的前提下更新测试选择?
在不同 PyTorch 版本之间,测试套件会随着新增测试、修改测试和删除测试而演进。这些变更可以被纳入仓库记忆,以反映更新后的测试套件。随后,底层选择 Agent 只需在增量(即新增和修改的测试)上运行,识别出与 OOT 加速器相关的变更,并生成修订后的测试套件。这样既避免了重新处理整个测试套件,又能让选中的测试与更新后的 PyTorch 版本保持一致。
结果: 版本升级变成了配置文件的 diff,而非全量重跑。例如,从 PyTorch 2.13 升级到 2.14,意味着只需评估约 4000 个变更测试,而不是数以万计。
挑战三:适配候选测试
PyTorch 的测试往往在数据类型、张量形状和算子维度上做了参数化。仅仅因为某个 dtype 不受支持就跳过整个测试,粒度过于粗糙;在这种情况下,只排除该 dtype,其余部分照常运行即可。更深层的适配(例如移除 CUDA 特有的硬编码)传统上需要对上游打补丁。为避免这种情况,Torch Spyre 维护了一套声明式测试复用框架,通过 YAML 配置来表达所有适配内容。
该框架提供三类控制:
- 参数级旋钮: 对 dtype 做子集选择、排除特定形状、为关注的 dtype 增加覆盖。
- 结果分桶: 将每个测试指定为
mandatory_success(必须通过)、xfail(预期失败)、xfail_strict(预期失败,且意外通过将被视作失败)或skip。 - 能力驱动的纳入: 全局声明支持的算子和 dtype;启用一个新算子即可自动纳入所有等待它的测试。
具体而言,声明式接口的实际形态如下。文件条目先为未显式列出的内容设定默认策略,再按分桶列出测试,并通过一个 edits: 块适配个别测试用例,而完全不触碰上游源码:
test_suite_config:
labels: [trunk]
files:
- path: ${TORCH_ROOT}/test/test_view_ops.py
unlisted_test_mode: skip # 未列出测试的默认策略
tests:
# 基本的张量元数据操作,在 Spyre 上完全受支持。
- names:
- TestOldViewOps::test_broadcast_tensors
- TestViewOps::test_contiguous_self
mode: mandatory_success
# bool/int64 之所以失败,仅因测试 setup 使用了 aten::random_.from。
- names:
- TestOldViewOps::test_broadcast_to
mode: mandatory_success
edits:
dtypes:
exclude:
- name: bool
- name: int64
global:
supported_dtypes: [{name: bfloat16}, {name: float16}, {name: float32}]
supported_ops:
- name: _scaled_mm
dtypes: [{name: bfloat16}]
有了这些额外的面向用户的控制手段,Agentic 工作流可以进一步扩展,为 OOT 测试复用框架生成相应的配置文件。底层 Agent 与测试仓库记忆随后不仅可以捕获测试结果(例如 mandatory_success),还能识别受支持的 dtype 和其他测试参数,从而让测试能够在单个测试用例级别上进行分桶。
该 schema 中有四项特性发挥了大部分作用:
- 声明式默认值。
unlisted_test_mode为所有未列出的内容设定结果,因此明天在上游新增的测试在落地当天即有明确的结果,而不是立刻打破 CI。 - 四种结果分桶。
mandatory_success、xfail、xfail_strict和skip。严格模式有其存在的必要:一次意外通过会被呈现为提升该测试优先级的信号,而非被静音。 - 逐用例适配。
edits:作用于单个 dtype、算子和模块,因此一个不被支持的 dtype 只影响它本身,而不会波及整个测试。 - 能力一次性声明。
global.supported_ops与supported_dtypes按文件声明,因此在后端启用一个算子只需一行改动,即可纳入所有等待它的测试。
工作原理: 该框架在收集(collection)阶段打上游 @ops、@modules 和 @dtypes 装饰器的“补丁“,并发出 pytest marks(op__、dtype__)。这意味着同一份配置既驱动适配,也驱动选择:-m op__add 恰好会运行使用该算子的所有测试。
关键保证: 上游测试树始终保持原貌。不对 PyTorch 的测试做任何补丁。版本升级变成配置文件的 diff,而不是合并冲突。该框架面向通用的 privateuse1 设备,因此不依赖任何特定硬件。
CRCR 集成到现有工作流中
**
图 3:Torch Spyre 中集成 CRCR 的工作流
挑战四:消费派发:基于载荷进行门控,自行解析 SHA
| 派发字段 | 用途 |
|---|---|
| SHA | 标识需要验证的确切上游 commit。 |
| PR Number | 将下游测试结果与特定上游 PR 关联起来。 |
| Action | 指明 PR 是被打开、被更新(synchronize)还是被关闭/合并。 |
Base Branch(pull_request.base.ref) | 标识目标分支,并支持对 main 以及 release/{version} 等发布分支进行过滤。 |
PR Label(pull_request.labels) | 与该 PR 关联的标签。可用于通过标签 “Merged” 识别已合并的 PR。 |
表 1:关键派发字段
集成 CRCR 的第一步是消费来自 PyTorch 的派发。派发载荷中包含如表 1 所示的关键信息。通过消费这些信息,可以针对不同用例对派发进行过滤。若要过滤出已合并到 main 的 pull request,则要求 action 为 closed 并带有合并信号,且 base branch 为 main。这个合并信号是间接的。PyTorch 并不使用 GitHub 的合并按钮,也就是说 PR 先被关闭,然后由 PyTorchBot 执行 squash-merge 并应用
相似文章
@PyTorch:PyTorch 加速器集成工作组正在建立跨平台的标准化硬件启用机制……
PyTorch 加速器集成工作组发布了 2026 年上半年的技术回顾,内容涵盖跨仓库 CI 中继(CRCR)、重构后的设备无关测试套件、OpenReg 参考后端的成熟、分布式与 torch.compile 支持,以及新的附加计算平台门户,旨在实现厂商中立的标准化硬件启用。
@PyTorch:一个 PyTorch 工作负载究竟如何变成 AI 加速器能够实际执行的东西?在我们的最新博客文章中,@IBM Spyre……
PyTorch 的博客详细介绍了其基于 MLIR 的开源编译器后端(KTIR → DataflowIR → SpyreCode),该后端可将 PyTorch Inductor 的编译决策转换为可在 IBM Spyre AI 加速器上执行的数据流管线。
@PyTorch:如何在异构加速器之间实现完整的 API 兼容性,且零额外开销?在 PyTorch Conferen…
PyTorch Conference North America 2026 将带来 Huawei 的 Zesheng Zong 的演讲,主题是如何在异构加速器之间实现完整的 API 兼容性,重点介绍 600,000+ 上游测试用例的适配工作,以及用于实时双向测试的全新 Cross-Repository CI Relay。
@PyTorch:单一框架如何支持日益多样化的 AI 硬件?在一篇新的 PyTorch Foundation 博客中,IBM 的贡献者分享了如何利用 PyTorch 现有的设备、流、内存管理和编译抽象,将 Spyre 数据流 AI 加速器集成为原生 PyTorch 设备,从而在无需为每种硬件单独开发框架的情况下支持更广泛的 AI 硬件。
PyTorch Foundation 博客介绍了 IBM 如何利用 PyTorch 现有的设备、流、内存管理和编译抽象,将 Spyre 数据流 AI 加速器集成成为原生 PyTorch 设备,从而在无需为每种硬件单独开发框架的情况下支持更广泛的 AI 硬件。
@PyTorch:硬件供应商和开发者,共同构建为 PyTorch 的开放、高效的异构计算生态系统。在…
在 PyTorchCon China 上,PyTorch TAC 加速器集成工作组分享了其成就和未来路线图,包括改进的设备无关 API 和扩展的多后端测试矩阵,以应对 AI 计算硬件异构性。