想象你在一家开遍全球的连锁餐厅工作。这家餐厅叫 PyTorch,菜单上有几百道招牌菜——加法、矩阵乘法、卷积、注意力机制,每一道都经过严格标准化,无论在 CPU 分店还是 GPU 分店,端上桌的味道必须一模一样。你作为贡献者要做的,不是在家随手炒一道私房菜,而是发明一道全球食客都没见过的新菜(比如“立方根烤肉”),把菜谱写清楚、登记进总部的菜单索引、让每一间分店的厨师都能按同一标准做出来,再经过试吃检测和出餐计时考核。这套流程,就是从“算子开发”到“算子合入 PyTorch”的完整旅程:菜谱是数学定义,灶台是 C++/CUDA 内核,菜单索引是注册机制,后厨记账是反向梯度,试吃是数值测试,秒表是性能基准。

岗位关联

掌握算子开发,意味着你不再只是 PyTorch 的“食客”,而是能亲手扩建深度学习基础设施的“总厨”——这是通往核心维护者、AI 基础设施工程师的钥匙,也是开源社区最稀缺的硬核能力之一。

目录
  1. 可视化演示
  2. 算子基础概念
  3. 注册机制
  4. 前向计算实现
  5. 反向梯度推导
  6. 设备适配规范
  7. 数值正确性测试
  8. 性能基准优化
  9. 生产落地经验
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

动态展示算子注册机制、前向反向实现、Autograd梯度推导与设备适配性能基准。

本节配有交互动效(进入视口后自动加载,约 3KB)。

算子基础概念

连锁餐厅的菜单上,每一道菜都有三样东西:菜名、食材清单、做法说明。PyTorch 里的“算子”就是那道菜——它是张量世界的原子烹饪动作,比如把两个张量相加、对张量做缩放、把整批数字归约成一个平均值。一个张量(Tensor)则像是装食材的标准托盘,上面标着三个关键属性:形状(托盘大小)、数据类型(食材种类,是苹果还是橘子)、所在设备(放在 CPU 冷库还是 GPU 冷库)。

在 PyTorch 的底层世界里,算子分两类:一类是“原始算子”(primitive op),像单味调料,只做一件事;另一类是“复合算子”(composite op),像复合酱料,由多个原始算子拼装而成。开发者贡献新算子时,真正要动手实现的是原始算子,因为它是整条计算链的最小单元,前向决定了“菜怎么做”,反向决定了“模型怎么学”。

理解“算子是菜”之后,下一步自然就是:如何把这道新菜登记到连锁餐厅的官方菜单上?这就引出了注册机制。

output=op(input, weight)\text{output} = \mathrm{op}(\text{input},\, \text{weight})
这个式子就是一个算子的“菜谱”:输入食材和调料,经过一个加工动作,产出成品菜。
  1. 输入 input(食材托盘)\text{input} — 形状为 $(N, D)$ 的张量,像一托盘切好的食材,等待被加工。
  2. 权重 weight(调料)\text{weight} — 算子的可学习参数或系数,决定加工的“口味”。
  3. 算子处理 op(灶台动作)\mathrm{op}(\cdot) — 对输入施加的核心数学运算,是整个算子的灵魂。
  4. 输出 output(成品菜)\text{output} — 加工完的张量,形状和语义由算子定义决定。

注册机制

一家连锁餐厅里,厨师再怎么会做新菜,如果菜不进菜单索引,服务员就没办法下单,后厨也不知道该派哪个灶台做。PyTorch 的注册机制(TORCH_LIBRARY)就是那本“总菜单”——它把每一个算子的名字、输入输出签名、以及对应的实现代码登记在一个全局注册表里。只有在册的算子,PyTorch 的调度系统(dispatcher)才认识它,Python 端才能通过 torch.ops 调用它。

写注册代码时,你会看到类似 TORCH_LIBRARY(my_ops, m) { m.def("cbrt(Tensor x) -> Tensor"); } 的语句:m.def 声明签名,m.impl 把签名和某个后端实现绑定。每个算子还要放在命名空间里(比如官方的 aten:: 或你自建的 custom::),就像不同的餐饮品牌各自维护菜单,互不串味。Schema 声明是这个环节的重中之重——它像合同一样写清楚“要什么食材、出什么菜”,保证调用方和实现方对齐。

登记进菜单只是第一步。真正决定这道菜好不好吃的,是灶台上那一铲子——算子的前向计算实现。

op: (Tensor, Tensor)→Tensor\text{op}:\, (\text{Tensor},\, \text{Tensor}) \to \text{Tensor}
这是注册表里最常见的签名格式,等价于菜单上写的“这道菜需要两种食材,端出一道菜”。
  1. 算子名 op(菜名)\text{op} — 算子在注册表里的唯一标识,Python 端靠它下单。
  2. 参数列表(食材清单)(\text{Tensor},\, \text{Tensor}) — 声明算子接收的张量类型和个数,类型不符直接报错。
  3. 返回类型(成品规格)\to \text{Tensor} — 箭头右边表示出锅的成品也是张量。

前向计算实现

菜单登记好了,接下来是动真格:前向计算就是“真的把菜做出来”。在 PyTorch 里,这一步通常用 C++ 编写内核(kernel)函数,而不是 Python——因为 Python 太慢,就像让一位经理亲自颠勺,效率远不如专业厨师。C++ 内核拿到张量的底层数据指针后,直接对内存里的数字做运算:逐元素算子就是一个循环,把输入张量的每个元素取出来、加工、写回输出张量。

厨房效率的关键在于“工作台布局”:如果食材在托盘里排列得整整齐齐(内存连续,contiguous),厨师就能顺着一条线快速取用;如果食材散落在不同仓库(内存不连续),每次取料都要绕路。因此内核第一步常常是保证张量连续。更进一步,现代 CPU 支持一次性同时处理多个数据的向量化指令(SIMD),GPU 则用成千上万个线程各负责一个元素——这就是为什么 CUDA 内核看起来像“一个厨师分身为万人团队,每人炒一粒米”。

菜做好了,客人也吃完了。这时候后厨的财务部门要登场了——他们需要知道:这道菜到底消耗了每样原料多少?这就是反向梯度的职责。

yi=xi×wiy_i = x_i \times w_i
以一个最简单的缩放算子为例:输出第 $i$ 个元素等于输入第 $i$ 个元素乘以权重第 $i$ 个元素。
  1. 读取输入元素 \(x_i\)x_i — 从输入张量中取出第 $i$ 个元素,相当于从托盘拿起第 $i$ 粒食材。
  2. 读取权重 \(w_i\)w_i — 取出第 $i$ 个权重系数,相当于确认这一粒要加多少调料。
  3. 执行乘法运算\times — 核心加工动作,两个数相乘得到结果。
  4. 写入输出元素 \(y_i\)y_i — 把结果写回输出张量的第 $i$ 个位置,完成装盘。

反向梯度推导

连锁餐厅每天打烊后要做“物料回溯”:根据每道菜的销量,倒推每种食材消耗了多少,这样才能精准补货、核算成本。神经网络的训练与之惊人地相似:Loss(总损失)就像当天总营收,梯度(Gradient)就是“每样原料对营收的贡献率”——哪个权重多一克,会让损失变大还是变小?PyTorch 的 Autograd 引擎就是那位精明的财务总管,它顺着计算图反向走一遍,用链式法则把梯度一步步传回每个参数。

问题在于:财务总管不是神算子,它只知道菜单上已有的菜怎么记账。你新发明的菜,必须同时提交一份“记账规则”——这就是算子的反向函数(backward)。在 torch.autograd.Function 里,你写 forward 做菜,写 backward 记账;记账时需要用到前向的哪些中间值?用 ctx.save_for_backward 把它们存进小本本。这两段代码必须数学上严丝合缝,否则梯度算错,模型训练就会像账目错乱一样越亏越多。

记账规则写好了,可拿到不同分店还得能落地——同一道菜,在电磁炉厨房(CPU)和火星灶厨房(CUDA/GPU)上,做法可是不一样的。

∂L∂xi=wi×∂L∂yi\displaystyle\frac{\partial L}{\partial x_i} = w_i \times \frac{\partial L}{\partial y_i}
链式法则的局部形态:原料 \(x_i\) 对总损失的影响,等于“这一步的消耗系数”乘以“成品对总损失的影响”。
  1. 上游梯度(成品对损失的影响)\displaystyle\frac{\partial L}{\partial y_i} — 财务从下游传来的“这道菜对总损失的贡献”。
  2. 局部系数(消耗系数)w_i — 在本算子内部,原料 \(x_i\) 与成品 \(y_i\) 之间的变化关系。
  3. 链式相乘(逐环回传)\times — 把上游贡献乘上局部系数,得到原料端应承担的梯度。

设备适配规范

菜单上写着“红烧肉”,但北京分店用的是电磁炉,上海分店用的是柴火灶,深圳分店用的是商用燃气灶——火候控制方法完全不同,但端上桌必须味道一致。PyTorch 的设备适配就是这个道理:同一个算子,CPU 上是一个 C++ 循环,CUDA 上是成千上万个 GPU 线程。设备的差异由 Dispatch 机制(分派机制)来消化——它像一个后厨值班长,看看订单来自哪个灶台,就把活派给对应的厨师。

在 PyTorch 源码里,常见做法是用 DispatchStub:先写一个通用的派发桩,再分别注册 CPU 实现和 CUDA 实现。调度器根据张量的 device 字段自动选择正确版本,Python 调用方完全无感。适配时要特别注意两件事:一是数据不能频繁在 CPU 和 GPU 之间搬运,否则就像食材在两地仓库间反复运输,光耗在路上的时间就够再做三道菜了;二是要遵守设备上下文规范(DeviceGuard),确保中途不跑错灶台。优秀的算子实现,必须让“同一个算子”在任何设备上给出数值一致、性能达标的结果。

菜品研发完成、各灶台都能做之后,还不能直接上架——必须经过严格的“试吃与称量”流程,这就是数值正确性测试。

dispatch: (device, inputs)→kernel\mathrm{dispatch}:\, (\mathrm{device},\, \mathrm{inputs}) \to \mathrm{kernel}
Dispatch 可以理解成后厨派单表:输入食材所在的设备类型,决定调用哪一份做菜方案。
  1. 设备 device(灶台类型)\mathrm{device} — 标明张量此刻存放在 CPU 还是 GPU 上。
  2. 输入 inputs(食材)\mathrm{inputs} — 参与运算的张量集合。
  3. 选择内核 kernel(做法方案)\to \mathrm{kernel} — 根据设备从注册表里挑出对应的实现函数。

数值正确性测试

新菜品上架前,品控部要做两件事:让试吃员试吃(确保做出来的东西能吃),再用精密天秤称量每道菜的用料(确保与菜谱完全一致)。算子的数值测试就是这两件事的工程化:先验证功能是否正确(输出数值符合数学定义),再验证梯度是否正确。后者靠的是 torch.autograd.gradcheck——它像一台“营养检测仪”,用数值差分(在输入上戳一个小扰动,观察输出变化)近似算出梯度,再和你的手写解析梯度对比,看两者是否吻合。

为什么需要这台仪器?因为手写 backward 极易出错,一个符号的疏忽就会让梯度“味道不对”。gradcheck 会在多个随机输入点上做中心差分(中央差分比单侧差分更精确),检查最大偏差是否小于阈值。测试还要覆盖“极端食材”:零、负数、超大数、NaN、空张量,以及不同的数据类型(float32/float64)和不同设备。边界处的数值稳定性,恰恰是生产事故的高发区——比如下面这个立方根算子在零点附近的梯度就藏着隐患。

试吃过关后,餐厅还关心一件事:这道菜出餐够快吗?这就进入了性能基准优化的战场。

max⁡i∣ gia−gin ∣<ε\displaystyle\max_i \left|\, g_i^{\mathrm{a}} - g_i^{\mathrm{n}} \,\right| < \varepsilon
数值校验的核心判据:所有采样点上,解析梯度与数值梯度的最大偏差必须小于容忍误差 \(\varepsilon\)。
  1. 解析梯度 \(g^a\)(理论用料表)g_i^{\mathrm{a}} — 你手写的 backward 公式算出的梯度。
  2. 数值梯度 \(g^n\)(实测称量)g_i^{\mathrm{n}} — 检测仪用数值差分逼近出的梯度。
  3. 最大偏差(最离谱的一处)\max_i \left| \cdot \right| — 所有采样点中误差最大的那一个,它决定了质量下限。
  4. 误差阈值 ε(允许偏差)\varepsilon — 品控容忍度,通常由 atol/rtol 参数设定。

性能基准优化

米其林餐厅也要讲翻台率。一道菜再好吃,如果一份要做 40 分钟,顾客就会流失。算子的性能基准(Benchmark)就是那道“出餐秒表”——测量算子处理一定量数据需要多少毫秒,或者每秒能处理多少元素。PyTorch 提供了 torch.utils.benchmark 以及 C++ 层的 benchmark 框架,规范的流程是:先热身(让缓存和调度器进入稳定状态),再重复测量多次取平均值,避免偶然抖动欺骗数据。

性能优化的第一课是认清瓶颈。大多数逐元素算子其实不是“算得慢”,而是“搬得慢”——数据从内存搬到寄存器的时间远超计算本身,这叫内存带宽瓶颈。就像后厨做凉拌菜,切菜只要 3 秒,但去仓库取菜要 20 秒。因此优化思路往往不是改进计算,而是减少搬运:把多个连续算子“融合”成一个(kernel fusion),比如把“缩放 → 加偏置 → 激活函数”三段工序合并成一道菜,数据和中间结果都不用落盘,直接一条流水线做完,出餐速度立竿见影。

测试与优化全部完成,最后一道关卡是“全国上架”——把菜从中央厨房试制推向所有分店,这就是生产落地。

Throughput=Ntˉ\displaystyle\text{Throughput} = \frac{N}{\bar{t}}
吞吐量等于一锅食材总粒数除以平均耗时,是衡量算子性能最直观的指标。
  1. 元素总量 \(N\)(食材粒数)N — 一次运算处理的张量元素总个数。
  2. 平均耗时 \(\bar{t}\)(平均出餐时间)\bar{t} — 多次测量取平均后的单次耗时。
  3. 吞吐量(单位时间出品量)\frac{N}{\bar{t}} — 比值越大,说明每秒能处理的元素越多,算子越快。

生产落地经验

一道新菜从研发到全国门店上架,要经过食品安全审查(代码审查)、冷链物流(持续集成)、溯源登记(版本日志)三道关卡。PyTorch 的 PR 合入流程与此一一对应:你的代码要符合官方风格规范(clang-format 格式化、命名规范),要通过 CI 在多种配置下的全部测试,还要附上 benchmark 数据证明性能不倒退,并更新文档与变更记录。

版本兼容是这里最容易踩的坑:PyTorch 的算子接口一旦发布,就不能随便改签名,否则下游所有用户的代码会瞬间“食物中毒”。所以贡献者必须遵守向后兼容(backward compatibility)原则——升级像换包装,但菜的味道和配料表不能变。实战建议是:先从一个小而清晰的算子入手,开 GitHub issue 与维护者对齐设计,再提交逐步拆分的 PR;每一条评论都认真回应,CI 红灯时先查是不是自己的改动引入的回归。开源社区看重的不仅是“菜好吃”,更是“这位厨师靠不靠谱”。覆盖率指标可以帮你量化测试质量:

走完这八步,一道新菜正式挂在连锁餐厅的菜单上,全球数以万计的 AI 工程师都能点单。这就是算子开发从技术原型到大规模生产的完整闭环。

coverage=passedtotal\displaystyle\text{coverage} = \frac{\text{passed}}{\text{total}}
覆盖率 = 通过的测试用例数 ÷ 总用例数,衡量你的测试矩阵对算子的保护伞有多大。
  1. 通过用例 passed(过关菜品)\text{passed} — 在全部测试配置下通过检查的用例数量。
  2. 总用例 total(全部检查项)\text{total} — 涵盖多设备、多 dtype、边界输入的完整测试矩阵。
  3. 覆盖率(保护强度)\frac{\text{passed}}{\text{total}} — 趋近 1 说明你的算子经受住了全方位考验。

AI 应用场景

大模型推理的注意力融合算子

阶段:模型部署与推理优化

解决的问题:传统多头注意力要把 Q、K、V 投影、缩放、Softmax、加权求和拆成十多个独立算子串行执行,每个算子都要读写一遍显存,就像每道工序都要把食材端回仓库再取出来。AI 工程师开发一个“融合注意力”算子,把整段注意力的数学逻辑合并进一个 CUDA kernel,用共享内存缓存中间结果,显著降低显存带宽压力。

具体案例:某团队为 70B 大模型的 KV-Cache 解码场景开发了融合注意力算子,在 A100 上把 prefill 阶段延迟降低 40%,端到端吞吐提升 2 倍以上。

自研网络层的数值稳定算子

阶段:模型训练

解决的问题:论文提出了一种新型归一化层,直接在 Python 里用 torch 拼装时,梯度在边界处剧烈震荡甚至发散。用 torch.autograd.Function 实现自定义算子并配合 gradcheck 校验梯度,从根源上保证数值稳定。

具体案例:训练 Transformer 时,对 LayerNorm 的变体开发了分段式 CUDA 内核,把数值误差从 \(10^{-3}\) 量级压到 \(10^{-6}\) 量级,训练收敛更平稳。

算子库共建与开源贡献

阶段:平台工程与开源生态建设

解决的问题:公司内部推理引擎缺少某个稀疏算子的 CPU 实现,每个业务团队都自己写一份,既重复又容易出错。把实现贡献回 PyTorch 上游,一次开发、全局受益。

具体案例:工程师用 C++ 实现稀疏卷积算子,完成注册、数值测试与性能基准后提交 PR,合入后 CI 自动在 20+ 硬件配置上验证,成为团队的长期基础设施资产。

Python 演示

python38 行
import torch  class CubeRootFunction(torch.autograd.Function):    @staticmethod    def forward(ctx, x):        ctx.save_for_backward(x)        return torch.sign(x) * torch.abs(x) ** (1.0 / 3.0)     @staticmethod    def backward(ctx, grad_output):        (x,) = ctx.saved_tensors        safe_x = torch.abs(x) + 1e-8        grad_input = grad_output / (3.0 * safe_x ** (2.0 / 3.0))        return grad_input  def cube_root(x):    return CubeRootFunction.apply(x)  def main():    x = torch.tensor([-27.0, -8.0, -1.0, 1.0, 8.0, 27.0], requires_grad=True)    y = cube_root(x)    y.sum().backward()    print("x   :", x.tolist())    print("y   :", y.tolist())    print("dx  :", x.grad.tolist())     sample = torch.linspace(-2.0, 2.0, 20)    ok = torch.autograd.gradcheck(        CubeRootFunction.apply, (sample,), eps=1e-6, atol=1e-5    )    print("gradcheck 通过:", ok)  if __name__ == "__main__":    main()
目的:演示用 torch.autograd.Function 实现一个完整自定义算子——立方根的前向计算与手写反向梯度,并通过 gradcheck 做数值校验。这是所有算子开发(无论是纯 Python 原型还是 C++/CUDA 内核)的通用测试范式。
  1. class CubeRootFunction(torch.autograd.Function):自定义算子的“标准容器”,让 Autograd 引擎认识你的新菜。
  2. ctx.save_for_backward(x)把前向用的食材 \(x\) 存在小本本上,反向记账时才能计算消耗系数。
  3. torch.sign(x) * torch.abs(x) ** (1.0 / 3.0)立方根的数学实现,用 sign 和 abs 组合保证负数输入也能得负数结果。
  4. grad_input = grad_output / (3.0 * safe_x ** (2.0 / 3.0))手写链式法则梯度,safe_x 加极小值防止除零。
  5. return CubeRootFunction.apply(x)必须用 .apply 调用,直接调用 forward 会绕过 Autograd 记录。
  6. torch.autograd.gradcheck(...)用数值差分校验手写梯度,相当于品控部的营养检测仪。

常见误区

误区PyTorch 不是有自动求导吗?那我只要实现 forward 就行,backward 它自己会算出来吧?

正解自动求导只对“框架已知的算子”有效。你发明的全新算子,框架根本没有对应的记账规则,必须手写 backward 并通过 gradcheck 验证。就像总财务官再厉害,也猜不出新菜的神秘酱料配方——配方卡必须由发明者提交。

为何常见:日常用 PyTorch 时,torch.relu、torch.matmul 都是官方实现好的,自动求导开箱即用,让人误以为一切导数都自动存在。

误区我在 Python 里用几个 torch 操作把功能拼出来,跑通测试了,算子开发不就大功告成了?

正解Python 拼装只是“私房菜试做”,距离连锁餐厅标准菜谱还差得远。生产级算子需要 C++/CUDA 内核、注册表登记、多设备适配、数值测试和性能基准。Python 版本的性能通常差几十倍,也无法被 C++ 调用链调度。

为何常见:原型验证阶段用 Python 很顺手,容易把“验证可行”误当成“工程完成”。

误区拿 \(x=[1,2,3]\) 算一遍,输出和手算一致,测试就通过了吧?

正解算子测试必须覆盖极端输入:零、负数、超大数、NaN、空张量、不同形状与 dtype。很多 bug 只在边界处暴露,比如立方根算子在零点附近梯度趋近无穷,正常样本完全测不出来。gradcheck 也要在多个随机点上运行。

为何常见:手算验证时人只会挑好算的点,形成“正常即正确”的思维盲区。

误区GPU 不是算力更强吗?那算子的 CUDA 版本一定全面碾压 CPU 版本。

正解对小张量,GPU 内核的启动开销(几十微秒级)可能超过实际计算时间,CPU 反而更快;对带宽密集的逐元素算子,两者都受内存带宽限制。性能要以 benchmark 数据为准,而不是拍脑袋。

为何常见:营销宣传里 GPU “算力强”深入人心,但忽略了数据搬运与启动开销这些工程细节。

误区我的算子数学上完全正确,测试也过了,提交 PR 应该很快就合入了吧?

正解PyTorch 合入标准包含代码风格、格式检查、跨设备跨 dtype 的完整测试、基准性能数据、文档与变更记录、向后兼容性评估。维护者会逐行 review,CI 要全绿,性能回归会被门禁拦截。任何一环缺失,PR 都可能长期滞留。

为何常见:开源新手只关注“能用”,不理解大型项目对可维护性与稳定性的苛刻要求。

面试题

Q1. 在 PyTorch 自定义算子开发中,反向(梯度)计算通常由谁提供?

单选题 · easy

  • A.前向计算自动推导
  • B.开发者手动实现← 正确答案
  • C.编译器自动生成
  • D.数据加载器自动生成

答案:B。自定义算子是框架“没见过的新菜”,Autograd 不知道怎么记账,必须由开发者手写 backward。选项 A(自动推导)只对内置算子成立;C 和 D 分别是编译期与数据侧的概念,与梯度无关。

Q2. 在 torch.autograd.Function 中保存前向中间变量应调用哪种方法?

单选题 · mid

  • A.ctx.save_for_backward← 正确答案
  • B.self.saved_tensors
  • C.ctx.return_graph()
  • D.torch.store_tensor()

答案:A。save_for_backward 是官方推荐的在上下文对象上保存张量的 API,反向时可用 ctx.saved_tensors 取回。B 是读取 API 而非保存 API;C 是控制计算图的参数;D 是虚构 API。

Q3. TORCH_LIBRARY 注册新算子最主要的作用是什么?

单选题 · mid

  • A.让框架识别并调度该算子← 正确答案
  • B.为算子自动生成测试数据
  • C.替换所有已有内置算子
  • D.大幅提升 Python 执行效率

答案:A。注册机制把算子名字、Schema 签名与实现写进全局注册表,dispatcher(调度器)才能识别并派单。B 是 gradcheck 的职责;C 冲突且危险;注册本身不直接影响 Python 执行效率,D 混淆了概念。

Q4. 关于 torch.autograd.gradcheck 校验,下列说法正确的有?

多选题 · mid

  • A.用数值差分近似验证解析梯度← 正确答案
  • B.仅需一个样本输入即可完成
  • C.对比解析梯度和数值梯度误差← 正确答案
  • D.能自动修复错误的梯度公式

答案:AC。gradcheck 使用数值差分(通常是中心差分)逼近梯度,再与解析梯度对比最大误差。它只能“发现”错误,不能“修复”错误,因此 D 错;为了保证可靠,需要在多个随机输入点上校验,单一输入远远不够,所以 B 错。

Q5. 反向传播中梯度沿计算图流动遵循的核心法则是?

单选题 · easy

  • A.链式法则原则← 正确答案
  • B.贝叶斯定理原则
  • C.大数定律原则
  • D.中心极限定理原则

答案:A。链式法则把复合函数的导数分解为各步局部导数的乘积,是反向传播的数学根基。贝叶斯定理处理条件概率,大数定律与中心极限定理属于统计学,都与梯度回传无关。

Q6. 影响 CUDA 算子在 GPU 上性能的关键因素包括?

多选题 · hard

  • A.线程块与线程配置← 正确答案
  • B.全局内存访问模式← 正确答案
  • C.算子注册表大小
  • D.内核启动开销频率← 正确答案

答案:ABD。GPU 性能由线程组织方式(A)、访存是否合并连续(B)、以及内核启动次数(D)共同决定。注册表大小(C)只影响调度查找,与该算子实际执行速度无关。

Q7. PyTorch 中设备 Dispatch(分派)机制的含义是?

单选题 · mid

  • A.根据设备类型选择内核实现← 正确答案
  • B.将算子部署到云服务器
  • C.自动生成设备测试用例
  • D.在多个 GPU 间复制模型

答案:A。Dispatch 是 PyTorch 调度器的核心能力:根据张量所在设备(CPU/CUDA 等)从注册表中挑选对应实现。B 是部署概念,C 是测试概念,D 是并行策略,都与 Dispatch 无关。

Q8. 立方根算子 \(y=x^{1/3}\) 在 \(x=0\) 处的梯度会出现什么情况?

单选题 · hard

  • A.趋于零很好训练
  • B.趋于无穷大不稳定← 正确答案
  • C.恒等于一不受影响
  • D.变成复数无定义

答案:B。立方根的导数是 \(\frac{1}{3}x^{-2/3}\),当 \(x\to 0\) 时该项趋近无穷大。这意味着训练中若输入接近零,梯度会爆炸,必须做数值防护(加 epsilon)。因此 A、C、D 均不符合数学事实。

Q9. 新算子合并进 PyTorch 主仓库前必须完成?

单选题 · easy

  • A.数值校验与性能基准← 正确答案
  • B.更换全新网络结构
  • C.删除所有旧版本接口
  • D.仅编写单元测试即可

答案:A。合入门禁要求数值正确性(gradcheck 等)与性能数据(benchmark)双达标,同时还需文档和兼容性评估。B 无关,C 破坏兼容性,D 只是 A 的一部分而非全部。

Q10. 生产级算子实现需要遵守的工程规范包括?

多选题 · hard

  • A.代码风格与格式检查← 正确答案
  • B.多设备多精度验证← 正确答案
  • C.符号反向梯度推导← 正确答案
  • D.变更记录与兼容性← 正确答案

答案:ABCD。合并主仓库是系统工程:风格检查保证可读性(A),多设备多 dtype 验证保证鲁棒性(B),手写 backward 必须按链式法则严格推导(C),变更日志与向后兼容保护下游用户(D)。四项缺一不可。