PyPTO-Gym 中的mul_算子实现:无 inplace 语义下的逐元素乘法 kernel 参考骨架
【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym
本指南以 cannbot-skills/ops/pypto-api-explore/examples/mul_.md 为骨架,讲解如何在 PyPTO 编程框架中把 PyTorch 的原地乘法
mul_落地为 NPU 上可执行的 kernel:PyPTO 没有 inplace 语义,所有结果必须显式写回out张量;batch 轴用 loop 切分、last-dim 计算轴整块处理,配合pypto.view、pypto.set_vec_tile_shapes、pypto.mul与pypto.assemble四个核心 API 完成一次完整的逐元素乘法算子开发。读完本文,你将掌握mul_类 inplace 算子向 PyPTO 迁移的映射思路、kernel 骨架的逐行拆解,以及如何在当前仓库中复用这套模式。
1. 背景:为什么mul_需要专门的映射说明
在 PyTorch 中,tensor.mul_(other)是一个原地(inplace)逐元素乘法操作:乘法结果直接写回调用张量自身,不产生新的输出张量,因此具有节省显存、避免额外拷贝的特点,在模型前向/反向中经常出现(例如权重衰减、门控缩放、梯度裁剪前的 inplace 修正等)。
然而在 PyPTO 编程框架中,不存在 inplace 语义。这一点在仓库的算子对标手册 references/torch-pypto-op-mapping.md 中被明确列为「差异映射」条目:
mul_→mul:无 inplace 语义:mul_.md
也就是说,mul_与 PyPTO 的mul是同一计算语义(逐元素相乘)但调用方式不同的关系:不能直接把 PyTorch 的原地写法照搬过来,而必须显式声明一个输出张量out,把乘法结果写回其中。这与 masked_fill_inplace.md 中masked_fill_的处理方式一脉相承——凡是 Torch 侧带_后缀的 inplace 算子,迁移到 PyPTO 时都要走「out 即写回」的模式。
该映射条目同时也说明:mul本身是 PyPTO 内置的原子接口(属于「同名同参」分组中的逐元素双输入算子,见 references/torch-pypto-op-mapping.md 的同名映射表),可以直接以pypto.mul调用,无需组合多个算子。
2.mul_kernel 参考骨架全景
mul_.md 给出的完整骨架如下:
@pypto.frontend.jit(runtime_options={"run_mode": pypto.RunMode.NPU}) def mul__kernel(a: pypto.Tensor(sl, pypto_dtype), b: pypto.Tensor(sl, pypto_dtype), out: pypto.Tensor(sl, pypto_dtype)): for i in pypto.loop(batch, name="batch", unroll_list=[1]): a_s = pypto.view(a, [1] + inner, [i] + [0] * len(inner)) b_s = pypto.view(b, [1] + inner, [i] + [0] * len(inner)) pypto.set_vec_tile_shapes(1, *inner) r = pypto.mul(a_s, b_s) pypto.assemble(r, [i] + [0] * len(inner), out)骨架头部有一句 Note 点明了整个 kernel 的切分策略:
batch 轴 loop 切分;last-dim 计算轴整块;pypto 无 inplace,结果写回 out。
这句 Note 是理解该骨架的钥匙,我们逐一拆解。
2.1 占位符约定(务必先读)
该骨架不是可直接照抄的标准模板,而是一份接口组合与轴切分模式的参考。骨架中出现的sl、pypto_dtype、batch、inner等都是占位符,其含义定义在 examples/README.md:
| 占位符 | 含义 |
|---|---|
sl | 输入 shape 列表,如[B, S, D] |
ol | 输出 shape 列表 |
pypto_dtype | 元素 dtype,如pypto.DT_FP32 |
batch | 被 loop 的外层轴长度(通常sl[0]) |
inner | 单次迭代处理的内层 shape,如sl[1:] |
README 同时强调:loop 轴、unroll_list、tile shape、动态轴处理等需按实际 shape / dtype 与平台约束确定并调优,骨架未逐一经 NPU 编译验证。因此引用本骨架开发正式算子时,还需结合 references/pypto-specific-ops.md 与 references/strategy-comparison.md 做约束核实与策略选型。
对应一份可运行的最小 setup,examples/README.md 给出了示例:
import pypto B, D = 8, 128 sl, ol = [B, D], [B, 1] pypto_dtype = pypto.DT_FP32 batch, inner, inner_out = B, [D], [1]3. 骨架逐行拆解:四个核心 API 的协作
3.1 JIT 装饰器与 NPU 运行模式
@pypto.frontend.jit(runtime_options={"run_mode": pypto.RunMode.NPU})整个 kernel 通过pypto.frontend.jit编译,并通过runtime_options指定run_mode为pypto.RunMode.NPU,明确该算子目标是 NPU 硬件执行路径。这是当前仓库中所有 kernel 参考骨架的统一写法。
3.2 函数签名:三个显式张量
def mul__kernel(a: pypto.Tensor(sl, pypto_dtype), b: pypto.Tensor(sl, pypto_dtype), out: pypto.Tensor(sl, pypto_dtype)):由于无 inplace 语义,输入a、b与输出out三者必须同时显式声明,且 shape 均为sl、dtype 均为pypto_dtype(逐元素乘法的输入输出形状天然一致)。这里的out就扮演了 PyTorchmul_中「被写回的原张量」的角色。
3.3 batch 轴 loop 切分
for i in pypto.loop(batch, name="batch", unroll_list=[1]):batch(通常取sl[0])是外层轴长度,通过pypto.loop切分为多次迭代处理。name="batch"给循环命名,unroll_list=[1]表示对该循环不展开(unroll 因子为 1)。这种「外层轴循环切分、内层整块计算」是 PyPTO Vector 类算子的典型切分模式——用循环换取片上存储空间,避免一次性把整个 batch 载入。
3.4 view:取出单次迭代的切片
a_s = pypto.view(a, [1] + inner, [i] + [0] * len(inner)) b_s = pypto.view(b, [1] + inner, [i] + [0] * len(inner))pypto.view以(shape, offset)的形式取切片:目标 shape 为[1] + inner(首维长度 1,内层保持完整),偏移为[i] + [0] * len(inner)(第 0 维偏移到第i个 batch 切片,其余维度偏移 0)。这样每次迭代拿到的是a、b各自的第i个 batch 切片。
3.5 设置 Vector 算子 Tile Shape
pypto.set_vec_tile_shapes(1, *inner)mul属于逐元素 Vector 类算子,因此在计算前必须调用pypto.set_vec_tile_shapes声明单次计算 tile 的形状:首维为 1,其余维度为inner。这一行是 PyPTO 算子开发中Tiling 配置的关键一步,决定了数据在 Vector 计算单元上的搬运与计算粒度。在 SKILL.md 的算子类型判断流程中,「仅逐元素/归约 → Vector 类型 → set_vec_tile_shapes」正是这条路径。
3.6 核心计算:pypto.mul
r = pypto.mul(a_s, b_s)pypto.mul是 PyPTO 的原子逐元素乘法接口,对a_s与b_s逐元素相乘得到r。这正是 Torchmul_的计算本体,只是结果落在临时张量r上,而不是写回a。
3.7 assemble:显式写回 out
pypto.assemble(r, [i] + [0] * len(inner), out)pypto.assemble把计算结果r按偏移[i] + [0] * len(inner)写回输出张量out。这一步取代了 PyTorch inplace 的「原地写回」语义——out即 inplace 写回的目标。整个循环结束后,out的每个 batch 切片都被逐次填充,等价于完成了out = a * b,也就是a.mul_(b)的迁移结果。
4. 同一模式的横向对照:inplace 算子在 PyPTO 中的统一写法
mul_的「out 写回」模式并非孤例。查看 masked_fill_inplace.md 可以发现masked_fill_的骨架与mul_高度同构:
@pypto.frontend.jit(runtime_options={"run_mode": pypto.RunMode.NPU}) def masked_fill_inplace_kernel(a: pypto.Tensor(sl, pypto_dtype), out: pypto.Tensor(sl, pypto_dtype)): for i in pypto.loop(batch, name="batch", unroll_list=[1]): a_s = pypto.view(a, [1] + inner, [i] + [0] * len(inner)) pypto.set_vec_tile_shapes(1, *inner) zero = pypto.full([1] + inner, 0.0, pypto_dtype) mask = pypto.gt(a_s, zero) fill_val = pypto.full([1] + inner, -1e9, pypto_dtype) r = pypto.where(mask, fill_val, a_s) pypto.assemble(r, [i] + [0] * len(inner), out)可以看到:batch loop 切分、view 取切片、set_vec_tile_shapes 配 tile、assemble 写回 out这一整套流程完全一致,区别仅在于计算主体由pypto.mul换成了pypto.gt+pypto.where的掩码填充组合。这印证了一个可复用的开发结论:在 PyPTO 中迁移任何 Torch inplace 算子,都可以套用「显式 out 参数 + 循环切片 + assemble 写回」的骨架。
5. 仓库源码佐证:pypto.mul 与 set_vec_tile_shapes 的真实生产用法
参考骨架展示的是「接口组合方式」,而当前仓库的生产算子实现中同样大量使用pypto.mul与pypto.set_vec_tile_shapes,可作为真实运行场景的证据。以 src/pypto_gym/ops/pypto_tensor/arctic/sum_lstm.py 为例:
- 逐元素缩放:
y = pypto.mul(x_fp32, x_fp32)、y = pypto.mul(y, 1.0 / x.shape[-1])(L58-L59),以及x_scaled = pypto.mul(x, 1.702)(L86)——可见pypto.mul既支持两个张量相乘,也支持张量与标量相乘; - 门控乘法:
term1 = pypto.mul(prev_cell_tile, f_gate)、term2 = pypto.mul(c_act, i_gate)(L179-L180),这是 LSTM 单元中典型的逐元素门控应用; - Tiling 配置:
pypto.set_vec_tile_shapes(current_tile_bs, tile_config.h_tile)(L129)、pypto.set_vec_tile_shapes(1, hidden_dim_4)(L137)——与参考骨架中set_vec_tile_shapes(1, *inner)的调用形态一致,均以「首维 + 内层 shape」声明 tile。
这些生产代码说明:mul_.md骨架中的 API 组合(view→set_vec_tile_shapes→mul→assemble)正是 PyPTO Vector 算子开发的通用模式,参考骨架的价值在于把模式抽象出来供快速复用。
6. 从骨架到正式算子的落地步骤
结合 SKILL.md 的 API 探索工作流,从这份参考骨架产出正式算子通常经过以下步骤:
- 本地映射优先:查 references/torch-pypto-op-mapping.md 命中
mul_→mul条目,直接取用 mul_.md 骨架; - 约束核实:通过
pypto-docs-search读取pypto-mulAPI 文档与pypto-from_torch入口约束,确认 dtype 支持范围(参考 SKILL.md 硬约束速查表:FP16/BF16/FP32/FP64/INT8/INT16/INT32/INT64 等)、shape 必须非空且 contiguous; - Tiling 与 loop 调优:按实际 shape / dtype 与平台约束确定
batch、inner、unroll_list与set_vec_tile_shapes的具体取值(README 明确要求不能照搬占位符); - 动态轴风险标注:若
mul_涉及动态 shape,需按 SKILL.md 中「动态 shape 兼容性」提示,采用「loop 切 tile」策略并在风险评估中标注; - 生成报告:基于 templates/api_report.md 模板产出 API_REPORT.md,填写 API 映射、约束检查、Tiling 需求、参考实现与风险评估等章节。
7. 总结
mul_(PyTorch 原地逐元素乘法)在 PyPTO 中的迁移要点可以浓缩为三句话:
- 语义映射:
mul_→pypto.mul,但 PyPTO 无 inplace,必须显式声明out并写回; - 切分策略:batch 轴用
pypto.loop切分(unroll_list=[1]),last-dim 计算轴整块,通过pypto.view逐片取出、pypto.set_vec_tile_shapes配置 tile、pypto.assemble写回; - 模式可复用:这一「显式 out + 循环切片 + assemble 写回」的骨架同样适用于
masked_fill_等其他 inplace 算子,并已在仓库生产算子(如 sum_lstm.py)中得到验证。
以 mul_.md 为起点,配合 examples/README.md 的占位符约定与 references/torch-pypto-op-mapping.md 的映射手册,即可快速完成mul_乃至其他 inplace 算子在 NPU 上的 PyPTO 实现。
【免费下载链接】pypto-gymPyPTO-Gym 是基于 PyPTO 编程框架构建的算子与模型样例仓库项目地址: https://gitcode.com/cann/pypto-gym
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考