Pull Request已成功合入, 合并人@ascend-robot
(感谢 huyuchao 的贡献)变更摘要
本 PR 为 NPU Inductor 后端新增 enable_permute_gather(opt-in,默认关闭)优化路径,针对 T5 形状的 permute+归约场景(如 out[b,k,i] = sum_j arg1[b,k,i,j] + sum_j arg0[i,j,k]),将归约轴上的非单位 stride tl.load(在 Ascend 上退化为标量 gather)改写为连续 DMA + 寄存器级置换。核心机制是「三态分派」:内部 stride 字节 < 256(H≤63 fp32)走 gather 模式(整行连续 DMA + tl.gather),≥ 256(H≥64 fp32)走 trans 模式(tl.make_block_ptr + tl.trans + K-tile 分块),动态 H 兜底走 trans 分支;gather 受 permute_gather_max_xblock(4096)上限约束,trans 则通过 permute_gather_ktile(64)将 XBLOCK 与 stride_r 解耦,使 H>4096 也能走 trans+K-tile 路径。此外,slot 检查放宽为互异且在 [0, ndim) 范围,消除了对「归约轴在最后」的布局假设。
主要改动
-
config.py新增三态分派函数与 opt-in 门控配置:新增permute_gather_mode(stride_r, elemsize)三态分派函数,按 stride 字节 <256 返回"gather"、≥256 返回"trans"、超permute_gather_max_xblock返回None;同时新增enable_permute_gather、permute_gather_stride_gate_bytes、permute_gather_ktile、permute_gather_dynamic_trans、permute_gather_max_xblock等全部 opt-in 阈值开关。 -
lowering.py中npu_permute新增零拷贝视图路径:当enable_permute_gather开启时,npu_permute直接构造ir.PermuteView零拷贝逻辑视图,绕过 realize 物化;新增_elemsize_of()辅助函数用于门控判断;动态 stride 场景通过permute_gather_dynamic_trans开关兜底。 -
codegen/triton.py中NPUTritonKernel新增 permute-gather 归约 rewrite 全链路:新增_npu_pg_record/_npu_pg_geometry在load时拦截符合条件的 strided 归约 load 并计算几何信息;新增_npu_pg_rewrite_body在codegen_body后验证 tiling 不变式并替换 load 行;新增_npu_pg_emit(gather 模式:平坦 DMA +tl.gather)和_npu_pg_emit_trans(trans 模式:tl.make_block_ptr+tl.trans+boundary_check=[0,1,2]+padding_option="zero")两种发射路径;slot 校验放宽为0<=s<ndim且互异,消除对归约轴末位的布局假设。 -
npu_triton_heuristics.py中reduction()新增 permute-gather 固定配置 pin:当triton_meta中存在npu_permute_gather标记时,固定 XBLOCK 和 R0_BLOCK 为 rewrite 验证通过的配置(real_block_row==1),通过cached_autotune单配置返回,禁止 autotune 扫其他配置。 -
新增
test_permute_gather.py测试文件:包含 9 个用例,覆盖 gather 模式(H=12,32)、trans+K-tile 模式(H=64,72,1024)、动态 H 兜底(H=72,100)、超大 H trans(H=8192)及禁用路径回退,通过内核源码标记(tl.gather/tl.make_block_ptr/boundary_check)验证 rewrite 是否生效,并assert_close(atol=1e-4, rtol=1e-4)校验数值精度。


代码审查
现在我已审查完所有 5 个文件。以下是最终总结。
审查总结
发现的问题
| 优先级 | 数量 |
|---|---|
| P0 | 0 |
| P1 | 1 |
| P2 | 0 |
| P3 | 1 |
各文件审查结果
- test/_inductor/test_permute_gather.py:无问题。测试结构正确(setUp/tearDown 隔离、compile_threads=1 避免 fork 问题)、模式标记合理、数值容差适当。
- torch_npu/_inductor/triton_experimental/codegen/triton.py:发现 2 个问题(P1 + P3),详见上述报告。核心逻辑(geometry 匹配、UB 预算计算、tiling 不变量验证、body 重写、trans 模式 perm 推导)经审查均正确。
- torch_npu/_inductor/triton_experimental/config.py:无问题。
permute_gather_mode的三态分派逻辑正确,配置项均有合理默认值,阈值注释与实测数据一致。 - torch_npu/_inductor/triton_experimental/lowering.py:无问题。
npu_permute中enable_permute_gather与realize_permute_gather的控制流正确,benefit gate 逻辑经审查与permute_gather_mode一致。 - torch_npu/_inductor/triton_experimental/npu_triton_heuristics.py:无问题。
npu_permute_gatherconfig pin 正确地从triton_meta读取并传递给cached_autotune。
整体风险评估
中等风险。P1 问题(raise AssertionError 在生成的 Triton kernel 代码中)在编译期守卫失效时会导致 kernel 编译硬失败,但由于 _npu_pg_rewrite_body 中的编译期检查充分,触发概率较低。P3 问题(正则解析 other 值的脆弱性)在当前 T5 sum 模式下不会触发,但若未来扩展到 max/min 归约则需修复。由于 enable_permute_gather 默认为 False(opt-in),现有用户不受影响。建议在合入前修复 P1 问题(移除无效的运行时断言行)。
| 类型 | 数量 |
|---|---|
| 🔴 阻塞 | 1 |
| 🟡 建议 | 0 |
⛔ 需要修改


Pull Request 已合并或已关闭。
If you want to solve this problem, you can click here to do it in the FAQs.




【合入来源】
【修改方案】
T5 形状的 permute+归约(
out[b,k,i] = sum_j arg1[b,k,i,j] + sum_j arg0[i,j,k])在默认路径下 permute 物化为独立 HBM 全量转置 + 归约分 kernel,且归约轴非单位 stride 的 stridedtl.load在 Ascend 落标量 gather,DMA 效率低。本 PR 新增enable_permute_gather(opt-in,默认关)将归约轴 strided load 改写为连续 DMA + 寄存器级置换。组件交互:config.py:
permute_gather_mode(stride_r, elemsize)三态分派——stride 字节 <256 →"gather"(H≤63 fp32);≥256 →"trans"(H≥64);gather 分支受permute_gather_max_xblock(4096) 上限约束(gather pin XBLOCK=stride_r,受 TRITON max_block 限制),超限返回 None 回退 realize。阈值 flags 全部 opt-in(permute_gather_stride_gate_bytes/permute_gather_ktile/permute_gather_dynamic_trans/permute_gather_max_xblock)lowering.py:
enable_permute_gather下npu_permute直接构造ir.PermuteView零拷贝逻辑 view(绕开 realize 物化);关闭时走既有 realize 逻辑(基线行为)codegen/triton.py:
NPUTritonKernel.load拦截 eligible 归约 load:tl.gather寄存器置换tl.make_block_ptr+tl.trans+boundary_check=[0,1,2]+padding_option="zero";静态 trans pin XBLOCK=min(stride_r, ktile=64),int 轴跨程序 chunk(x1_blocks=ceil(H/64));trans 分支不受 max_xblock 上限约束(XBLOCK 与 stride_r 解耦),H>4096 同样走 trans+K-tile(H=8192 → 128 个满 chunk,整除无 tail 浪费),realize 仅保留给 gather 超限场景;R0 公式 eff=min(stride_r, ktile) 解耦 R0/H0<=s<ndim(ndim=max(slots)+1),消除「归约轴在最后」的布局假设(DEFAULT scheduler patch 对大 H kernel 产生 R 树 slot 0 布局时不再误拒)npu_triton_heuristics.py:rewrite 下固定 XBLOCK/R0_BLOCK(real_block_row==1 才合法),autotune 不扫其它配置
评审修复(d308b142b5 / bc0df673bb / 562d97e39f,响应 @rmch 与 robot 评审;其中 562d97e39f 的 autotune winner 缓存键修复后移至独立 PR):删 kernel 内非法
raise AssertionError(P1,Python raise 非 Triton 合法语句);slot 预检先类型检查再max(slot 缺失正常回退 strided,不再 TypeError);gatherpidx/reshape 与 slot 顺序解耦(R-first 布局不再静默重排);enable_permute_gather与pin_xr同开时 PG marker 优先(避免 body 已 rewrite 而 config 被 pin_xr 决定);加载缓存 autotune winner 时剥离非 kwarg 键(issue 4367);新增 4 个回归用例(UT 9→13)字符串手术 AST 化(925b5372e1,评审遗留整改):codegen 内三处对生成代码的正则/eval 改写全部替换为结构化 AST 操作(复用本文件既有
_npu_parse_tl_load_assignment/_npu_assignment_parts基建,与_maybe_rewrite_select_lane_load同款模式),删除 PR 自身引入的import re:_npu_pg_rewrite_body:load 行定位由行文本正则改为按赋值目标名匹配——可穿透.to(tl.float32)提升链(贪婪(.*)\)无法定界,fp16/bf16 候选原实现会静默错配);非 str 行(DeferredLine)透传;emit 拒绝时保留原行回退(原实现返回空列表会丢行且仍下发 config pin)_npu_pg_emit:ptr/other/eviction_policy由 argtext 正则(P3:other=([^)]*)遇float('-inf')等嵌套括号即截断)改为loadAST 节点 args[0]/keywords 结构化读取;末行经节点拼接把tl.where(...)换回原value_ast再 unparse,保留.to()dtype 提升_npu_pg_eval_real_blocks:正则+eval改为 AST 解析(name : tl.constexpr = ...AnnAssign 归一化)+ 新增_npu_pg_eval_expr小型求值器(int 算术/比较/IfExp tile 三元式/min-max;不支持的节点照旧跳过,保持「对发射文本求值、免疫排序逻辑漂移」性质)评审二轮(ff49a90cde / 73f2144299 / 0c785d501a):为满足 PR 总行数 ≤1000(现 967),autotune 缓存键修复移至独立 PR(本 PR revert);
_npu_pg_eval_expr求值器表驱动化、三处重复注释去重、测试 prose 压缩(13 用例与断言不动,232→197 行)——13 个 PG kernel 生成源码 sha256 与整改前逐字节一致;config.py 阈值分节 banner + 三个阈值取值依据导语(256=实测 gather/trans 交叉点+UB 预算而非 DMA 对齐边界、64=256B DMA 效率单元、4096=TRITON max_block)。评审二轮补齐(89ea3c481c):全部
_npu_pg_*实现提取到独立模块codegen/permute_gather_rewrite.py(section:eligibility/geometry/validation + gather/trans 发射分区;NPUTritonKernel留薄委托,调用点与测试 monkeypatch 不变);统一回退 debug 日志(模块 logger:geometry/mode 门、slot 缺失/非互异、tiling 校验失败、load 定位失败、rewrite 成功,均带原因与变量名);no_raise断言并入 gather/trans 用例族;PR 总量 996 行。验证:12/12 + 13/13(clean);11 个唯一 PG kernel 源码 sha256 与迁移前逐字节一致。【资料变更】
不涉及
【接口变更】
不涉及(
enable_permute_gather为 Inductor 后端内部配置项;对外 API 不变,生成 kernel 文本属内部产物)【功能验证】
UT(新增
pytorch/test/_inductor/test_permute_gather.py,12 用例整批通过;no_raise 断言已并入 gather/trans 用例族):数值精度:
assert_close(atol=1e-4, rtol=1e-4)全过(H=1024 max_err 3.05e-05、H=8192 max_err 1.53e-05、J=96 bit-exact)。AST 整改回归(925b5372e1):
test_permute_gather.py13/13 +test_triton_experimental_enable.py13/13(source env.sh clean冷缓存);13 个 PG kernel 生成源码与整改前 sha256 逐字节一致(纯重构、零行为变化,上表性能数据不受影响);fp16 端到端补充验证——生成行tmp1 = tl.where(r0_mask, tmp1_pg_g, 0.0).to(tl.float32)保留 dtype 提升,compiled 与 fp32 金标 max|diff|=0.0000(eager fp16 自身 3.12e-02,即保 cast 后走 fp32 累加、比 eager 更准;旧正则实现在该场景会丢 cast)。性能(CANN profiler 设备时长 ground truth,per-launch 各 kernel 均值求和;对比 eager / 基线
enable_permute_gather=False):已知倒退(如实):H=72 trans+K-tile 2.0× 慢于基线(ceil(72/64)=2 程序拆分,tail chunk 仅 8 行按 64 行 tile 读取读放大);非 32B 对齐 H(65/71/73/129)系统性 1.7–2.0× 慢(block_ptr 非对齐行宽读放大,后续优化方向 H%8≠0 gate 回退);H=32 gather 14% 慢;墙钟口径下短 kernel 场景(H≤128)被 ~130us/launch host 开销吞掉收益(设备时长收益 ≠ 端到端收益)。
CI:未跑仓 CI 流水线(本环境仅本地 NPU 单机验证)。
【CheckList】