已开启
fix(npu-inductor): int32 index overflow — promote only overflow addends (variant C) #43736
fix(npu-inductor): int32 index overflow — promote only overflow addends (variant C) #43736
已开启
huyuchao创建于 8月4日
huyuchao成员
8月4日

fix(npu-inductor): int32 index overflow — 构造性正确升宽(int64 地址 + int32 tile)

【合入来源】

关联 issue:https://gitcode.com/Ascend/pytorch/issues/4373

【问题背景】

总元素数超过 2^31 的 kernel,其线性地址 = Σ(stride × 轴索引) 会越过 int32 表示范围,形成两条死路:

路线 失败模式
全部按 int32 算 268435456*x1(x1≥8)回卷成负数 → ptr + 负偏移507035(MTE 非法访存,vector core exception)
全部升成 int64 tile tile 占用 = lane×8B,UB(片上暂存)翻倍 → 507034(vector core 挂死 / 编译期 Ub overflow)

硬件事实:AIV 的 UB 预算有限;int64 向量 ALU 为仿真(compute-bound 实测 3.5×)。二者把「全 int32」与「全 int64」同时判死,正确解必须让 i64 精确出现在最小必要位置集合上。

【方案设计】

核心思想:构造性正确(Constructive Correctity)

lane tile 永远 int32;i64 只出现在两处——地址表达式使用点的轴因子(NpuWiden),以及 i64 运行时形参(size_dtype=index_dtype)。

正确性由 triton 类型提升规则构造性保证:每个地址算术至少含一个 i64 操作数 ⇒ 任意形状、任意项形态、任意求值顺序下不可能回卷。不依赖任何求界分析、形状快照或运行时 guard

实拍生成形态(8388609×256 sum,恰 2^31+256 元素):

tmp0 = tl.load(in_ptr0 + (256*x0.to(tl.int64) + r0_1.to(tl.int64)), r0_mask & x0mask, ...)
tl.store(out_ptr0 + (x0.to(tl.int64)), tmp2, x0mask)
# tl.arange / tl.full 全零 int64 —— tile 保持 int32(逐行断言钉死)

机制分层

机制 要点
检测 select_index_dtype()(上游继承)+ _npu_should_widen_address int64 模式必武装;int32 模式按 #186057 类防御武装(字面量 ≥2^30 / 含 ModularIndexing / 轴→len−1 代换后整表达式 bound 超 int32);所有平局向 fail-safe 破,换形状经普通 guard 重编译重判
信号 dtype_to_str 解除 int64 降级 防全局降级映射吃掉检测信号(否则回到 507035);作用域经全调用面审计并注释在案
结构 force_linearize 溢出 kernel 强制线性化(防 pid.to(i64)*XBLOCK 混合广播升宽全部 header tile);检测异常时 warn + 安全侧 True
使用点 NpuWiden + printer hook sympy 标记节点渲染 .to(tl.int64)(ToFloat 同款惯用法);节点级 subs、零文本匹配——负系数项、复合 stride、FloorDiv/Mod 项全部正确(正则方案对负项渲染形态 (-c)*x 从不命中)
块派发 超大块数派发链修复(评审补充项) 静态 numel ≥2^31 绝不落 triton 字面量([2^31,2^32) 定型 uint32,三条毒径:stomp 符号性冲突 / constexpr 轴同毒 / equal_to 特化 uint32→i64 vcast 被 BiShengIR 拒绝);一律别名 i64 形参,整条 odometer/rsplit 标量链经提升规则自然 i64;动态兄弟多轴角落 fail-loud
类型路由 npu_triton_compute_type / npu_triton_store_type / value_expr 补丁 int64 在计算/存储/值转换路由全开放;映射 int64 条目仅为 default 后端混合进程共存保留(归属文档化)
mask _mask_cmp_lhs 收窄 + 终端性防护 int64 index 且静态 numel<2^31 时,mask LHS 收窄为 int32 比较并提前返回。该提前返回是终端性设计:可选优化开关 mask_cmp_fp32(默认关)开启时的 fp32 比较路径在 triton-ascend 存在已知 lowering 缺陷(507034 挂死),终端性保证收窄覆盖的 kernel 无论开关状态都结构上不可达该路径。行为由纯文本断言钉死:默认关闭时四种形态布局逐字节不变;开启时 int64-narrow 形态保持 int32 比较
调度 exact-grid 非 linearize kernel 精确 ceil(xnumel/XBLOCK),防过读 507035 / 静默丢 tile
runtime 缓存记账键修复 NPU _load_cached_autotuning 漏 pop 上游恒写的 found_by_coordesc/triton_cache_hash → 任何后端热缓存重跑全 config 失败;补 pop + TE 边界 constexpr 过滤

mask 比较的防护机理:mask 不参与地址算术,走独立的比较路径。_mask_cmp_lhs 对 int64 索引 kernel(静态轴长 <2^31)生成 (index).to(tl.int32) < numel——int32 比较落在 AIV 的确定性路径上;该分支 return 早于 mask_cmp_fp32 的 fp32 cast 判断,因此开关开启时控制流也止步于 int32 比较,不会生成 to(tl.float32) 形态。测试以两态断言钉死:默认关闭时布局逐字节不变(零行为差异);开启时 int64-narrow 形态必须保持 int32 比较。

设计权衡(为什么不是其它方案)

  • 不整体继承上游 uniform int64 发射arange/full/pid.to(i64)):UB 翻倍是功能故障(507034)非性能问题;
  • 不用选择性分析承担正确性:逐项求界 + 快照 + guard 的方案,分析漏判即静默回卷(±1 轴项、Mod 非单调点代换低估、hint 越界 compile-on 均为实例);
  • 上游 #91028 无法反向补位:其接在 fx to_dtype 节点,地址算术是 sympy 渲染,层位不可见;
  • 性能豁免(显式决策):地址算术 i64 税被接受——语料级 ≈+3%,compute-bound 上界 3.5×(memory-bound 被 HBM 掩盖为 1.0×),模型级武装率 0(无回归)。

【改动文件】

文件 内容
codegen/triton.py NpuWiden + printer、无条件升宽、R3 门控、超大块数派发链修复(static numels 覆写 / rsplit 模板 / 常量特化守卫)、类型路由补丁、force_linearize 安全侧
codegen/npu_header.py _npu_emit_axis_numel(≥2^31 别名 i64 形参)、blocks 守卫与说明
npu_triton_heuristics.py TE 边界 constexpr 过滤、ValueError 按目标错误收窄(串并行一致)
runtime/autotune_cache.py 记账键 pop(上游对齐,两后端受益)
test/_inductor/test_triton_experimental_int32_overflow.py 9 用例 + 五类风险钉子

【自测信息】

结果
overflow 套件(冷缓存,含 8.6GB 真实跨界 kernel ×3、动态 8.6GB、expand odometer 2.2e9) 9/9
enable 套件(三入口 + default/experimental 混跑隔离,冷/热) 13/13
in-range kernel 生成代码零 tl.int64(逐行断言)
模型级 A/B(resnet18,基线 vs 本方案) 生成代码相同(武装率 0),时延差在运行噪声内
warm-cache 重跑 修复后 9/9 可重跑

防回归钉子五类:with_index 哨兵/类型配对、in-range 零 int64、类型路由策略、上游类型助手调用面 28 位点快照(增改必红,torch 升级触发重审计)、≥2^31 裸字面量 NotRegex。大张量用例均带 @skipIfInsufficientHBM 保护。

likedislike
合并受阻
Hhuyuchao成员
8月4日 创建了 pull request,commit 05fba31b
atomgit-bot
atomgit-bot
8月4日 评论:

变更摘要

此 PR 修复了 NPU inductor 在元素总数超过 2^31 时因 int32 索引溢出导致的问题。先前将所有 arange 切片统一提升为 int64 会导致 UB 翻倍并引发挂起(bug 507034);若全部保持 int32 则线性地址会回绕造成错误(bug 507035)。该变更采用"变体 C"方案:保持大规模向量切片为 int32,仅在指针运算的线性地址合成处,将那些绝对值 × (轴长度-1) 超过 2^31 的溢出加数项(如 268435456*x1)提升为 int64,同时将掩码比较的 LHS 在静态可判定安全时回退为 int32 以保持在向量单元上执行。

主要改动

  • 恢复 dtype_to_str 中的 int64 索引类型:在 NPUTritonKernel.dtype_to_str 中新增对 torch.int64 的处理,返回 "tl.int64",覆盖上游补丁将 int64 退化为 int32 的行为,确保超过 2^31 元素的内核正确触发 int64 索引路径。

  • 新增 _npu_promoted_overflow_terms 方法:从 sympy 表达式中识别哪些乘加项(c * axis)在 int32 下会溢出,贪心地按溢出量降序选出需要提升的项,确保剩余未提升的部分一定在 int32 范围内。

  • index_to_str 中实现按使用点提升:当 index_dtype"tl.int64" 时,对表达式文本中的溢出加数项追加 .to(tl.int64) 转换(如 268435456*x1268435456*x1.to(tl.int64)),使提升仅作用于小规模/标量潜伏轴切片,而非大规模 arange 切片。

  • arange 切片保持 int32:移除 codegen_range_tree_indexing_range_codeiteration_ranges_scalar_code 中对 arange 切片和标量偏移的 int64 上溯转换,统一硬编码为 tl.int32,由使用点的选择性提升覆盖溢出风险。

  • _mask_cmp_lhs 增强 int32 回退逻辑:新增 numelindex_dtype 参数,当索引类型为 int64 但轴长度静态已知且小于 2^31 时,将比较左操作数显式转换回 tl.int32,使比较保持在向量单元执行,避免因缺少原生 int64 向量比较而降级为标量循环。

likedislike
Hhuyuchao成员
8月4日 关联了看板:FrameworkPTAdapter 版本issue看板
atomgit-bot
atomgit-bot
8月4日 评论:

代码审查

✅ 未发现问题

likedislike
此处折叠了199条消息 查看更多
ascend-robotascend-robot成员
8 天前 删除了label:docs-ci-pipeline-success
ascend-robotascend-robot成员
8 天前 添加了label:docs-ci-pipeline-running
ascend-robot
ascend-robot成员
8 天前 评论:

✅ 跳过 docs ci 检查,没有需要检查的文档文件

likedislike
ascend-robotascend-robot成员
8 天前 删除了label:docs-ci-pipeline-running
ascend-robotascend-robot成员
8 天前 添加了label:docs-ci-pipeline-success