@PyTorch:让树外加速器与快速迭代的生态系统保持同步并非易事。在一篇新的社区博客中,贡献者们分享了……

X AI KOLs Timeline 新闻

摘要

来自 IBM 和 Red Hat 的贡献者联合发表了一篇 PyTorch 社区博客,介绍了他们如何基于 PyTorch 的跨仓库 CI 中继(CRCR)构建 Torch Spyre,通过智能体测试选择流水线和声明式 YAML 测试复用框架,实现树外加速器后端的 L2 集成。

让树外加速器(out-of-tree accelerator)与快速迭代的生态系统保持同步,并非易事。 在一篇新的社区博客中,来自 @IBM 和 @Red Hat 的贡献者分享了他们如何基于 PyTorch 的跨仓库 CI 中继(Cross-Repository CI Relay,简称 CRCR)构建 Torch Spyre,以应对大规模的兼容性测试挑战。博客中还分享了一些测试和工作流模式,这些模式可能让整个生态系统中的加速器开发者受益。 阅读完整的技术深度解析:https://bit.ly/46XkjGi @mehant1
查看原文
查看缓存全文

缓存时间: 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)。前两个阶段构建上下文;后两个阶段做出决策:先依据代码,再依据执行。

  1. 高层选择。 第一个 Agent 利用两项输入,将搜索范围从整个测试树缩小到值得分析的文件夹和顶层文件:一是后端实际钩入了哪些 PyTorch 扩展点,二是一些用自然语言描述的范围偏好。不钩入 torch.dynamo、或尚未准备好接管 autograd 的后端,可以直接在这里说明,并完全跳过相应测试树。
  2. 仓库记忆。 仓库记忆生成器(Repository Memory Generator)将这份精简清单转化为一个可查询的索引,其中包含符号、文件、由 LLM 撰写的摘要以及逐个测试的 embedding。这让下一阶段的选择 Agent 能够一次性对整个候选集合进行推理,而不必受单个上下文窗口容量的限制。
  3. 底层选择。 随后,第二个 Agent 阅读后端自身的代码库、文档和元数据(例如支持的算子),再查询仓库,确定要运行的确切测试,并将它们划分为 mandatory_success 或 skip。每项决策都会连同其背后的推理一并记录。
  4. 基于真实执行的修正。 选中的测试在真实硬件上运行。Agent 会利用执行日志进行第二遍处理,这有助于捕获静态分析所遗漏的运行时失败与数值差异。最终输出是一个经过修正、重新分桶的测试集合,并附带更新后的推理。

该流水线目前会为运行的每个上游测试文件生成一份逐文件配置,覆盖数以千计的具名测试用例,并通过一次合并运行在真实硬件上对它们加以验证。覆盖率会自动增长:当后端新增支持一个算子时,所有等待它的测试都会自动被纳入,无需任何人工维护列表。

可审计性至关重要:每一次分桶决策都会以注释形式写回配置文件,说明原因。当有人问“为什么这个测试被跳过了?“,答案就在文件里,而不是在某个模型的记忆中。Agent 提出建议;配置文件才是可评审的正式产物。

此外,这条 Agentic 流水线还可以针对特定用例进行定制。下文将讨论其中两个用例。

用例 A:启用一个新的算子

当后端启用了一个新算子时,我应该打开哪些测试?

这涉及准备一份“测试—算子“映射:为每个测试记录其所使用的算子,并构建一个反向索引,将每个算子映射到使用它的完整测试列表。仓库记忆生成器会在一次性的执行过程中捕获这些元数据:

  1. Eager 路径:通过 TorchDispatchMode 捕获
  2. 编译路径:通过 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 配置来表达所有适配内容。

该框架提供三类控制:

  1. 参数级旋钮: 对 dtype 做子集选择、排除特定形状、为关注的 dtype 增加覆盖。
  2. 结果分桶: 将每个测试指定为 mandatory_success(必须通过)、xfail(预期失败)、xfail_strict(预期失败,且意外通过将被视作失败)或 skip。
  3. 能力驱动的纳入: 全局声明支持的算子和 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 中有四项特性发挥了大部分作用:

  1. 声明式默认值。 unlisted_test_mode 为所有未列出的内容设定结果,因此明天在上游新增的测试在落地当天即有明确的结果,而不是立刻打破 CI。
  2. 四种结果分桶。 mandatory_success、xfail、xfail_strict 和 skip。严格模式有其存在的必要:一次意外通过会被呈现为提升该测试优先级的信号,而非被静音。
  3. 逐用例适配。 edits: 作用于单个 dtype、算子和模块,因此一个不被支持的 dtype 只影响它本身,而不会波及整个测试。
  4. 能力一次性声明。 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:单一框架如何支持日益多样化的 AI 硬件?在一篇新的 PyTorch Foundation 博客中,IBM 的贡献者分享了如何利用 PyTorch 现有的设备、流、内存管理和编译抽象,将 Spyre 数据流 AI 加速器集成为原生 PyTorch 设备,从而在无需为每种硬件单独开发框架的情况下支持更广泛的 AI 硬件。

X AI KOLs Timeline

PyTorch Foundation 博客介绍了 IBM 如何利用 PyTorch 现有的设备、流、内存管理和编译抽象,将 Spyre 数据流 AI 加速器集成成为原生 PyTorch 设备,从而在无需为每种硬件单独开发框架的情况下支持更广泛的 AI 硬件。