想象一座管理严格的美食城。城里的中央厨房有一本固定的“标准食材库”:番茄、牛肉、面粉……每个档口的厨师都只能从库里选料,按规定流程出餐。这套体系高效、稳定,但有一个尴尬时刻——当你想做一道“分子料理”,食材库里根本没有这种组合,你只能望锅兴叹。 终于有一天,美食城宣布开放“原创菜品入驻通道”。只要你按流程申请摊位号、登记菜谱规格、通过虚拟食材预审,就能在菜单里永久增加一道专属于你的菜。更棒的是,你的菜还能接入美食城的“积分返现”和“中央调度”体系,与其他经典菜一起被高效处理。 这正是 PyTorch 自定义算子开发的缩影。本文带你以“创意菜厨师”的身份,走完这道菜的全部流程:申请登记(torch.library 注册)、虚拟预审(FakeTensor 形状推导)、积分绑定(AutogradFunction 自动微分)、配合中央调度(TorchCompile 编译兼容),最后通过试菜(调试测试)走向社区(开源贡献)。
掌握自定义算子开发,意味着你不再被框架内置操作“困在盒子里”。你能为论文里的新公式、硬件上的新加速器编写自己的算子,这也是从“调包侠”走向“造轮者”的必修课。
目录
可视化演示
步进式展示PyTorch自定义算子开发全流程:torch.library注册算子定义、FakeTensor抽象与Shape推断、AutogradFunction封装前向反向传播、TorchCompile兼容性保障,点击步骤切换查看各阶段算子图与数据流。
本节配有交互动效(进入视口后自动加载,约 3KB)。
torch.library算子注册 使用torch.library.define注册算子签名,指定输入输出类型 通过impl函数注册CPU/CUDA后端实现,分离定义与实现 define(schema) → impl(backend) 算子定义与后端实现分离注册
一、认知定位:为什么要开发自定义算子
美食城最开始的规则是:你只能把现有食材组合起来做菜。番茄炒鸡蛋、牛肉炖土豆,这些是“组合式创作”。可当你想做一道“低温慢煮芒果鱼子酱”时,你会发现——无论怎么组合现有食材,都做不出那种口感。要么风味差一截,要么为凑合而浪费太多时间。
在 PyTorch 里,“现有食材”就是内置算子:卷积、矩阵乘法、ReLU……大多数模型可以靠组合它们完成。但研究者经常需要全新的计算逻辑,比如一篇新论文提出的激活函数、一种新的注意力变体、一个特殊的数据变换。这些逻辑如果硬拆成几十个内置算子的组合,不仅代码丑陋,而且运行慢、显存占用高。
自定义算子就是“新食材”的入场券。它允许你在不修改 PyTorch 源码的情况下,向框架注册一段全新的计算逻辑,让模型能直接调用。开发流程通常分为四步:先用 torch.library 注册算子身份,再为它提供“虚拟预审”用的 Meta Kernel,接着用 AutogradFunction 绑定梯度规则,最后确保它能融入 torch.compile 的优化流水线。
所以,自定义算子不是一个“炫技工具”,而是一条理性的技术路线:当组合成本超过自主研发成本时,直接开一个“新摊位”。
- 输入张量$x$ 是算子的输入,可以是特征图、权重或任意张量数据。
- 算子参数$\theta$ 代表算子携带的额外参数,比如缩放系数、平滑因子,有时也可以为空。
- 自定义计算$f_{\text{op}}$ 是你要实现的计算核心,它可能是数学论文里的一个新函数。
- 输出结果$y$ 是算子执行后产生的输出张量,后续层会继续消费它。
二、注册机制:给算子办一张“摊位许可证”
你想在美食城卖创意菜,第一件事不是炒菜,而是去管委会窗口办证。工作人员会问你三件事:招牌叫什么(算子在哪个命名空间)、菜品的规格是什么(输入输出长什么样)、后厨在哪儿做(哪个后端执行)。这三件事全部登记完,你的菜才可能出现在菜单上。
PyTorch 对应的“管委会窗口”是 torch.library。第一步用 Library("my_ops", "DEF") 创建一个命名空间——相当于领一个摊位号;第二步用 lib.define("smooth_relu(Tensor x, float alpha) -> Tensor") 声明算子的 schema——相当于登记菜谱规格;第三步用 @impl(lib, "smooth_relu", "CPU") 注册具体执行逻辑——相当于告诉管委会“这道菜在哪个灶台做”。
注册完成后,PyTorch 内部的分发器(dispatcher)就会认识这个算子。当模型代码调用 torch.ops.my_ops.smooth_relu 时,dispatcher 会根据输入张量的 device 类型(CPU、CUDA 或 Meta),把调用转发到对应的 kernel 实现。这个过程就是你点菜时,前台把订单分派给相应后厨。
一张小小的“摊位许可证”,换来的是整个美食城体系对你新菜的认可:自动微分会找上你,编译优化会看见你,其他开发者也能放心地调用你。
- 接口声明Schema 用字符串描述算子名、参数类型和返回类型,是算子最正式的“身份证”。
- 建立入口define 之后,算子成为一个可通过
torch.ops访问的入口对象。 - 后端路由dispatch 环节根据输入张量的设备选择对应 Kernel,CPU 走 CPU 实现,Meta 走 Meta 实现。
三、虚拟张量预审:让系统先“空跑”一遍
美食城有一个特别的岗位叫“虚拟食材审核员”。他在新菜正式开卖前,不看真实食材,只看包装上的标签——尺寸、类型、产地,然后就能在纸上推导出“这道菜出锅后应该装多大的盘子”。他不碰菜刀,不点火,却让整个后厨调度提前有了依据。
PyTorch 中的这个“审核员”就是 FakeTensor。它是一类特殊的张量:拥有真实的 shape、dtype、stride 和 device,但不保存具体数值。当你的自定义算子遇到 FakeTensor 时,PyTorch 会调用它的 Meta Kernel——一种“只看标签推导结果”的简化实现,从而在不做真实计算的情况下,获得输出的形状信息。
为什么需要这一套“空跑”?因为自动微分建图和 TorchCompile 图优化都必须提前知道张量形状。如果每个算子都要真实算一遍才能知道输出尺寸,那编译优化就无从谈起。Meta Kernel 通常极其简单,例如逐点操作只需返回一个 torch.empty_like(x),就代表“输出形状和输入一样”。
没有 Meta Kernel 的算子,就像一个不让审核员看标签的摊位——美食城不敢把你的菜纳入任何自动化计划,只能每次特殊处理。
- 输入标签$\text{Shape}_{\text{in}}$ 和 $\text{dtype}_{\text{in}}$ 是输入 FakeTensor 携带的形状与类型信息。
- 预审公式$\text{MetaKernel}$ 是你为算子编写的“形状推导规则”,告诉框架输出张量长什么样。
- 输出标签$\text{Shape}_{\text{out}}$ 是推导出的输出元数据,供后续编译与自动微分使用。
四、自动微分封装:把“积分返现”规则写明白
美食城有自己的会员积分体系:顾客每点一道菜,系统自动计算积分并返还到账户。作为新摊位老板,你必须告诉积分系统两个规则:顾客点菜时怎么计算这笔交易的积分(前向),顾客取消订单时要怎么扣回积分(反向)。这两条规则写清楚了,你的菜才能进入会员体系。
PyTorch 的 torch.autograd.Function 就是你的“积分规则书”。你需要继承它,重写 forward 和 backward 两个静态方法。forward 接收输入张量,返回输出张量;backward 接收关于输出的梯度,返回关于输入的梯度。中间需要用 ctx.save_for_backward 把前向过程中的必要张量“留样”,供反向阶段使用。
梯度规则为什么必须手动写?因为 PyTorch 可以自动求导已知算子组合,但你的算子是一个“全新物种”,框架不知道它的导数长什么样。AutogradFunction 的意义在于:把自定义算子的导数以代码形式显式告知框架,让它能顺利参与端到端的反向传播。
就像积分规则写得越细致,会员系统越能放心——Autograd 封装越完整,PyTorch 就能越流畅地把你的算子编织进整个计算图。
- 上游梯度$\frac{\partial L}{\partial y}$ 是损失 $L$ 对算子输出 $y$ 的梯度,从网络更深的层反向传回。
- 前向留样$v_{\text{saved}}$ 是通过
ctx.save_for_backward保存的张量,如前向输入,供反向计算使用。 - 反向规则$g(\cdot)$ 是自定义的反向函数,把上游梯度和留样张量组合成输入梯度。
五、编译兼容:让新算子上得了“中央调度流水线”
美食城后来引入了中央厨房调度系统。它会观察整条出餐流:哪个灶台先开火,哪个环节能合并,哪些等待可以消除。这个系统非常强势——它希望所有摊位都按标准节奏配合。如果你的菜在某个环节“卡壳”了,整个调度线就会在这里断一截,其他摊位的优化也会被打折扣。
这个“中央调度系统”就是 TorchCompile。torch.compile 会把整个模型的计算过程捕获成一张图,然后用 FakeTensor 把图“空跑”一遍,摸清每层张量的形状,再执行算子融合、内存规划等优化。你的自定义算子如果没注册 Meta Kernel,图流传到它这里就会“断流”——编译无法确定输出的形状,只能退回 Eager 模式逐个算子执行,也就是所谓的 graph break。
graph break 听起来只是“退回原速”,真实代价却很大:编译优化往往是整体性的,一个断点会让前后范围的融合都失效。更糟糕的是,在正式训练中,这个算子所在区域可能反复触发 recompile 检查,带来额外开销。所以,想让自定义算子真正“上得了流水线”,Meta Kernel 不是可选项,而是必选项。
如果说注册是“办证”,那编译兼容就是“通过消防验收”。只有验收合格,你的摊位才能待在美食城的自动化流水线上,而不是每次都被单独拎出来手动处理。
- 原始计算图$G_{\text{raw}}$ 是模型被捕获后的未优化图,其中包含你的自定义算子节点。
- 元内核集合$\mathcal{K}_{\text{meta}}$ 是所有可用的 Meta Kernel,编译期用它们推断图中每个张量的形状。
- 编译优化器$\text{Compile}$ 把原始图与元内核结合,应用融合、消除、调度等优化,产出最终图。
六、调试测试:开业前的“试菜环节”
新摊位正式营业前,聪明的主厨都会做两件事:请资深食客连续试菜,确认味道稳定;在高峰时段试跑一次,观察后厨会不会手忙脚乱。如果味道时咸时淡,或者高峰期出不了菜,这道菜就不该上菜单。
PyTorch 里最经典的“试味工具”是 torch.autograd.gradcheck。它的原理非常朴素:用有限差分法近似算出数值梯度,再与你 backward 中实现的解析梯度对比。如果两者在每个位置都小于预设误差,就证明你的反向传播逻辑正确。这正是“资深食客”在反复确认:你宣称的“积分扣回规则”和实际味道变化是否吻合。
性能层面的“高峰期试跑”则由 torch.profiler 负责。它能统计你的算子执行耗时、显存占用、调用次数,帮你一眼看出新算子是“快如闪电”还是“慢如蜗牛”。此外,边界测试同样重要:空张量、零维张量、非连续内存布局,这些“刁钻食客”往往会暴露实现中的隐藏缺陷。
只有“味道稳定、高峰不慌”的算子,才有资格正式写入模型训练脚本。试菜环节省掉的每一分钟,都会在未来的深夜 debug 中加倍还回来。
- 数值梯度$J_{\text{num}}$ 通过对输入施加微小扰动、观察输出变化来逼近真实梯度,不依赖你的 backward 实现。
- 解析梯度$J_{\text{analytical}}$ 是你通过 AutogradFunction 的 backward 显式计算出的梯度矩阵。
- 误差容限$\varepsilon$ 是允许的最大偏差,通常取 $10^{-5}$ 量级;超过它说明 backward 写错了。
七、实践与社区:从个人脚本到开源共建
美食城里最受欢迎的摊位,往往有几个共同点:招牌名字清爽不撞车、菜单版本稳定不变来变去、服务流程写成了清晰的小册子。这样的摊位不仅食客爱来,其他摊主也愿意互相合作。反之,那些招牌乱起、菜单朝令夕改的摊位,很快就会被大家列入“黑名单”。
自定义算子的社区实践遵循同样的逻辑。命名上,开发者应使用自己的命名空间前缀(如 my_ops::),避免使用 torch:: 或 aten:: 等 PyTorch 保留前缀,防止全局调度表冲突。版本上,Schema 变更应尽量向后兼容:新增参数要给默认值,删改参数要谨慎评估影响面。贡献上游时,一份合格的 PR 应当包含完整的测试用例、Meta Kernel 注册和用户文档。
PyTorch 的算子注册机制之所以强大,正因为它把“扩展”设计成了一等公民。你可以先在本地脚本里验证想法,再提炼为可复用算子,最后通过社区评审合入框架。这条路径让许多研究者从“框架用户”成长为“框架贡献者”。而这一切的起点,不过是给算子上好三张牌:清晰的命名空间、完整的注册信息、扎实的测试保障。
从自己脚本里的一个 Function 类,到 PyTorch 官方文档里的一页 API,自定义算子开发的终点不是“能用”,而是“让别人也能放心地用”。
- 稳定接口$\text{StableSchema}$ 表示算子的 schema 定义稳定,不会在小版本中随意变更。
- 元内核$\text{MetaKernel}$ 是编译兼容的基石,让 FakeTensor 能预推形状。
- 测试文档$\text{TestSuite}$ 和 $\text{Doc}$ 分别保证正确性与可维护性,是社区评审最关注的维度。
AI 应用场景
场景一:论文新激活函数的快速验证
阶段:模型研究与算法设计
解决的问题:研究者在论文中提出一种新激活函数,如果硬用 x.masked_fill、x*alpha 等组合拼凑,代码可读性差、数值不稳定,且无法方便地对比梯度正确性。用自定义算子封装后,模型代码只需一行 torch.ops.my_ops.smooth_relu(x)。
具体案例:某研究团队设计了一个“平滑 ReLU”用于增强梯度流动。他们用 torch.library 注册算子,用 AutogradFunction 定义前向与反向,再用 gradcheck 验证正确性,三天内就把该激活函数植入了 ResNet 实验,而不用等 C++ 扩展编译完成。
场景二:推理引擎的多后端算子适配
阶段:模型部署与推理优化
解决的问题:同一个新算子要在 CPU、CUDA 甚至 NPU 上运行,传统做法是为每个硬件写一套独立的调用代码。借助 dispatcher 机制,可以为同一个算子注册多份 kernel,运行时按设备自动选择。
具体案例:某推理引擎团队为自研的量化算子注册了 CPU 和 CUDA 两套实现,并为它们补充了 Meta Kernel。结果同一个量化模型在 GPU 服务器和边缘 CPU 设备上都能无缝跑起来,torch.compile 的图优化也没有产生 graph break。
场景三:大模型训练中的算子融合优化
阶段:训练系统优化
解决的问题:大模型训练时频繁的内存读写是主要瓶颈。将多个相邻算子融合成一个自定义算子,可以在一次 kernel launch 中完成原本需要多次显存往返的计算。
具体案例:某性能优化工程师把“线性变换 + GELU 激活 + Dropout”融合为一个自定义算子,并在算子内部用 TORCH_CUDA_KERNEL_LAUNCH 实现高效并行。经过 torch.profiler 验证,该模块端到端训练吞吐提升了约 15%。
Python 演示
import torchfrom torch.library import Library, implfrom torch.autograd import Function class SmoothReLU(Function): @staticmethod def forward(ctx, x: torch.Tensor, alpha: float) -> torch.Tensor: ctx.save_for_backward(x) ctx.alpha = alpha return torch.where(x > 0, x, alpha * x) @staticmethod def backward(ctx, grad_output: torch.Tensor) -> tuple: x, = ctx.saved_tensors alpha = ctx.alpha grad_input = grad_output * torch.where(x > 0, 1.0, alpha) return grad_input, None lib = Library("my_ops", "DEF")lib.define("smooth_relu(Tensor x, float alpha) -> Tensor") @impl(lib, "smooth_relu", "CPU")def smooth_relu_cpu(x: torch.Tensor, alpha: float) -> torch.Tensor: return SmoothReLU.apply(x, alpha) @impl(lib, "smooth_relu", "Meta")def smooth_relu_meta(x: torch.Tensor, alpha: float) -> torch.Tensor: return torch.empty_like(x) def main(): x = torch.randn(4, 4, requires_grad=True) y = torch.ops.my_ops.smooth_relu(x, 0.2) loss = y.sum() loss.backward() print("output:", y) print("grad:", x.grad) if __name__ == "__main__": main()
Library 命名空间注册 smooth_relu,用 AutogradFunction 提供前向与反向,用 Meta Kernel 支持 FakeTensor 预推,最后调用它完成一次带反向传播的计算。class SmoothReLU(Function):定义一个自定义自动微分节点,相当于在积分系统里登记“返现规则”。ctx.save_for_backward(x)把前向输入张量“留样”,供反向梯度计算使用。torch.where(x > 0, x, alpha * x)实现平滑 ReLU 的主逻辑:正数原样输出,负数按 alpha 缩放。lib = Library("my_ops", "DEF")创建一个名为my_ops的算子命名空间,相当于申请摊位号。lib.define("smooth_relu(Tensor x, float alpha) -> Tensor")声明算子的 schema,告诉框架这个新算子的输入输出签名。@impl(lib, "smooth_relu", "Meta")注册 Meta Kernel,让 FakeTensor 在不做真实计算时也能推导输出形状。torch.ops.my_ops.smooth_relu(x, 0.2)像调用内置算子一样调用自定义算子,dispatcher 自动路由到对应 kernel。
常见误区
误区“自定义算子不是要写 C++ 扩展吗?我 Python 都没学明白,根本没资格碰这个。”
正解torch.library 的注册机制完全支持纯 Python 实现。你可以先用 Python 写算子原型、注册 schema 和 Meta Kernel,验证正确性后再用 C++/CUDA 重写性能关键路径。写 C++ 扩展只是“性能优化手段”,不是“自定义算子的前置条件”。
为何常见:很多教程从 C++ 扩展(pybind11)讲起,给人造成“自定义算子=C++”的错觉,忽略了 torch.library 本身是 Python 友好的。
误区“我用 `lib.define` 注册了算子,然后直接在模型里调用,可是 `backward()` 报错说没有梯度实现。”
正解注册算子的 schema 和 kernel 只解决了“前向调用”问题,并不代表自动微分认识它。PyTorch 内置算子自带梯度规则,但自定义算子的梯度规则必须通过 AutogradFunction 或 register_autograd 单独提供。就像办好了营业执照,但积分系统规则还要单独签协议。
为何常见:框架内置算子的“注册+梯度”是打包好的,用户容易把这种体验误当成自定义算子也自带。
误区“AutogradFunction 的 forward 我写好了,里面能算输出形状,为什么 `torch.compile` 还是在我这断了?”
正解forward 是给真实张量算数值的,而 FakeTensor 需要的是另一条独立路径——Meta Kernel。Meta Kernel 通过 @impl(lib, "op", "Meta") 或 register_fake 注册,专门用于“只推断形状、不算数值”。两者是并行的两套实现,缺一不可。
为何常见:从用户视角看,forward 确实接收了 FakeTensor 也能跑,但它内部可能执行了真实运算,违背了 FakeTensor“零计算”的假设,导致编译行为不可预测。
误区“我写了个 `my_op`,没加命名空间,本地跑得好好的,反正只要不跟官方算子重名就行。”
正解PyTorch 的算子注册表是全局的,裸名字很容易与第三方库冲突,更可能撞上框架内部保留字。社区规范要求使用带命名空间的名字,如 my_ops::smooth_relu。这就像美食城不允许两家摊位都挂“招牌菜”三个字而不写前缀。
为何常见:本地小脚本中冲突概率低,开发者容易低估全局调度表的冲突风险,直到某天两个库都定义了同名算子才追悔莫及。
误区“`torch.compile` 遇到我的自定义算子就 fallback 了,但模型反正能训练,性能差点无所谓。”
正解fallback 意味着 graph break,编译器无法在该算子前后做融合优化,而且多次编译尝试可能带来额外开销。更关键的是,一旦整个模型只有一处 graph break,所有跨算子的优化都会失效。这不是“差点”,而是“白编译”。补齐 Meta Kernel 通常只要几行代码,性价比极高。
为何常见:Eager 模式本来就是 PyTorch 的默认运行方式,用户容易觉得“能运行=能接受”,忽略了编译优化是整体性的。
面试题
Q1. 自定义算子开发最核心的价值是什么?
- A.替换 PyTorch 所有内置算子
- B.不动框架源码前提下扩展新计算← 正确答案
- C.绕过 Python 解释器提升所有代码速度
- D.让模型一定能在 GPU 上运行
答案:B。自定义算子解决的是“PyTorch 内置算子表达不了新计算”的问题,它是在框架不动源码的情况下扩展能力。A 过于极端,C 混淆了解释器与算子概念,D 与主题无关。就像创意菜入驻不是取代整个美食城,而是新增一个摊位。
Q2. torch.library 中 schema 声明的作用是什么?
- A.规定算子的输入输出类型签名← 正确答案
- B.设置 Python 解释器版本号
- C.决定损失函数的表达形式
- D.自动生成训练用的数据集
答案:A。schema 是算子的“身份证”,用字符串形式描述算子名、参数类型和返回类型。它既不影响 Python 版本,也不碰损失函数和数据生成。就像菜品规格登记表只写“用什么食材、出什么菜”,不规定灶台品牌。
Q3. 下列关于 FakeTensor 的说法,正确的是:
- A.保存真实数据但无法被观测
- B.只保存元数据不保存真实数据← 正确答案
- C.专门用于数据增强与增广
- D.大幅加快真实计算速度
答案:B。FakeTensor 携带 shape、dtype、device 等元数据,但没有实际数值内容。它的作用是让编译系统在“不真实计算”的情况下预推形状。它不是数据增强工具,也不会加速真实计算——它本身就是“虚假”的。
Q4. 自定义算子想被 torch.compile 平滑兼容,最关键的是:
- A.注册 meta kernel 提供形状推断← 正确答案
- B.把所有参数全部变成字符串
- C.关闭 CUDA 强制 CPU 执行
- D.使用绝对路径导入模块
答案:A。TorchCompile 在捕获图和优化时需要预知每个算子的输出形状,meta kernel 正好提供这一能力。没有它,编译会在算子处 graph break。字符串参数、关 CUDA、绝对路径都和形状推断没有关系。
Q5. AutogradFunction 中 ctx.save_for_backward 的主要作用是:
- A.保存前向中反向需要的张量← 正确答案
- B.将每步梯度持久化到磁盘
- C.动态更新模型的全部参数
- D.计算并返回最终的损失值
答案:A。反向传播时,backward 经常需要前向阶段的输入或中间结果,save_for_backward 就是把这些张量“留样”保存。它不是存储梯度,也不是更新参数或算 loss,而是为反向梯度公式提供必要素材。
Q6. 以下哪些是使用 torch.library 注册自定义算子的必要步骤?
- A.创建 Library 命名空间实例← 正确答案
- B.声明算子的 schema 类型签名← 正确答案
- C.为各后端注册 kernel 实现← 正确答案
- D.重新编译整个 PyTorch 框架
答案:ABC。一个完整注册要三步:创建 Library 获取命名空间、define 声明 schema、impl 注册具体 kernel。自定义算子的意义恰恰是不需要重新编译框架,D 是错误选项。
Q7. 为 smooth_relu 编写 Meta Kernel 时,合理的实现是:
- A.直接返回与输入同形的空张量← 正确答案
- B.对输入执行逐元素乘二加一
- C.用随机分布生成输出张量
- D.对输入求 sigmoid 作为输出
答案:A。Meta Kernel 只负责推导输出元数据,不执行真实数值计算。smooth_relu 是逐点算子,输出形状与输入一致,用 torch.empty_like(x) 即可。B、C、D 都做了真实数值运算,违背了 FakeTensor 的零计算原则。
Q8. 关于 dispatcher 分发机制,正确的描述是:
- A.dispatcher 根据设备选择合适 kernel← 正确答案
- B.同一算子可为多个后端注册多份实现← 正确答案
- C.dispatcher 是一个 Python 解释器
- D.Meta 也会作为分发键参与选择← 正确答案
答案:ABD。dispatcher 是 PyTorch 内部的高效调度器,不是 Python 解释器。它根据 device、layout 等分发键把算子调用路由到对应 kernel。Meta 是一个特殊分发键,负责把 FakeTensor 路由到 Meta Kernel。
Q9. torch.autograd.gradcheck 的核心验证原理是:
- A.有限差分近似梯度并与反向梯度对比← 正确答案
- B.反复多次跑前向并取平均值比较
- C.比较 CPU 与 GPU 执行速度差异
- D.检查算子命名是否符合社区规范
答案:A。gradcheck 通过对输入施加微小扰动,用有限差分近似数值梯度,再与 backward 计算出的解析梯度对比,误差须小于容差。它不比较运行速度,也不检查命名,而是严格验证梯度数学上的正确性。
Q10. 为 PyTorch 上游贡献自定义算子时,推荐的做法包括:
- A.使用带命名空间的算子名字← 正确答案
- B.PR 中补充完善的测试用例← 正确答案
- C.schema 变更尽量做到向后兼容← 正确答案
- D.私下给全部维护者发送邮件
答案:ABC。社区贡献讲究规范与透明:清晰命名空间避免冲突,测试用例保证正确性,向后兼容的 schema 保护既有用户。给维护者发邮件不是提 PR 的流程,真正的沟通发生在 GitHub 评审区。