已关闭
[Bug-Report|缺陷反馈]: ForeachMulScalarList 输入scalar为int64时存在精度问题 #5509
高逸凡创建于  10 天前关闭于  10 天前
高逸凡
10 天前 创建

Thanks for sending an issue! Please fill in the following template to help quickly solve your problem.

Describe the current behavior / 问题描述 (Mandatory / 必填)

ForeachMulScalarList 失败用例与根因分析

  • 验证轮次:NC-100 清洁版(生成器注入性护栏修复后),jp CANN 9.2.0,Ascend950PR
  • 总体结果:98/100 通过,2 例失败(同根因,确认算子缺陷,源码已修复待重编)
  • 裁定标准:binary_equal(整型 bitwise 逐位比对),golden = int64 精确整数运算

一、失败用例

用例 1:ForeachMulScalarList_NCG_0006

字段
source_case_id ForeachMulScalarList_CASE126
布局 nc100:strided_slice:f4:combo:1(注入性 strided 视图:x[0] 末维 stride×4,外层 stride 同步 storage 加宽 4084=1021×4,无重叠 ✓)
x(TensorList 2 成员) shape (64, 1021),dtype int32,全值域随机
scalars dtype int64,全值域随机(-2³¹ ~ 2³¹-1)
输出 bitwise:member0 0.0%(全失配 65344/65344),member1 100%
storage/strides (((64,4084),(64,1021)), …) / (((4084,4),(1021,1)), …)

用例 2:ForeachMulScalarList_NCG_0035

字段
source_case_id ForeachMulScalarList_CASE726
布局 nc100:expand_bcast:f2:combo:11(x[0] stride-0 广播视图,仅输入;输出连续)
x(TensorList 2 成员) shape (8, 64, 127),dtype int32,全值域随机
scalars dtype int64,全值域随机
输出 bitwise:member0 100%,member1 0.0%(全失配 65024/65024)

两例共同特征(失败面精确画像):int32 张量 × int64 全值域标量。清洁轮 26 个 int32 用例中,仅这 2 例标量为全值域随机——其余 24 例标量范围 0/1/16/1000 等(fp32 可精确表示)全部 PASS,即失败与 NC 布局无关、只与标量是否超出 fp32 精确表示域有关。

二、受控实验证据(jp CANN 9.2.0,Ascend950PR device 4)

实验 1:固定标量分离变量(同用例 NCG_0035/0006)

固定标量 s 结果 说明
s=2 / s=3 PASS 小整数,fp32 精确 → 与 NC 布局无关
s=2147483647 PASS fp32 舍入值 2³¹ 饱和回原值,侥幸一致
s=1605116159(非 fp32 可表示,最近 fp32 值 1605116160) FAIL 精度损失暴露

实验 2:数值取证(dump 反解,65344/65344 元素逐位)

x(int32)   正确 wrap(x×1605116159)   缺陷 wrap(x×1605116160)   torch CPU/NPU 基准
-1273774738        -281055086             -1554829824             -281055086 ✓
-2147483648       -2147483648             0                      -2147483648 ✓
2147483647          542367489            -1605116160               542367489 ✓
1                  1605116159             1605116160              1605116159 ✓

NPU 输出与 wrap(x × 1605116160) 逐位一致、与正确结果 wrap(x × 1605116159) 全失配 →
缺陷语义 = y = wrap(x × int32_sat(fp32(s)))乘法本身精确(int32 wrap 整乘),精度损失仅发生在标量的 fp32 舍入一处

实验 3:归因排除链

裁判 结果 排除结论
torch CPU _foreach_mul([x],[s]) / x*s / x*int64_0dim(三形式) wrap(x×1605116159) 精确 golden 语义与 torch 一致
TTK golden(int64 精确整算) 同上 golden 无辜
直连 C++ aclnnForeachMulScalarList(aclScalarList=ACL_INT64,脱离 TTK/golden) wrap(x×1605116160) 舍入,65344/65344 复现 TTK 无辜——缺陷在 op 自身
torch_npu 真机 _foreach_mul 精确 op-plugin 分流保护(见下),非算子正确

三、根因(源码级)

缺陷位置ops/ops-nn/foreach/foreach_mul_scalar_list/op_kernel/arch35/foreach_mul_scalar_list_regbase.h:49(修复前)

// 实例化(arch35/foreach_mul_scalar_list.cpp:41):int 路径为
//   ForeachMulScalarListImpl<int32_t, int64_t>(...)   → T=int32_t, ScalarT=int64_t

using scalarCalcType = typename Conditional<
    AscendC::IsSameType<ScalarT, int32_t>::value,   // ← BUG:判据用【标量 dtype】
    int32_t,                                        //   ScalarT=int64_t(op def 契约)→ 恒为 false
    float>::type;                                   // ← 恒落入 float
scalarCalcType scaleVal = scalarCalcType(inScalarGM_.GetValue(tensorIndex));
//                              ↑ int64 标量 → float 转换点:1605116159 → 1605116160(fp32 舍入)

根因链

  1. op def 契约scalars 支持 INT64(aclnnForeachMulScalarList.md),int32 x 的合法组合为 x(DT_INT32) × scalars(DT_INT64);公式 y_i = x_i * scalars_i 为整数乘法。
  2. arch35 regbase 的标量承载选型写反了判据Conditional 按标量 dtype(ScalarT)选型——但整型数据路径的标量按契约必为 int64_t,条件恒 false,标量恒以 float 承载。
  3. fp32 舍入损失:fp32 尾数 24 位,|s|>2²⁴ 的 int64 标量不可精确表示(1605116159 → 1605116160)。标量被舍入后转 int32 参与 int 整乘(乘法本身精确 wrap),因此错误完全等价于"乘错了标量"。
  4. 标准 kernel 路径无此 bug:非 arch35 的 foreach_one_scalar_list_binary.hT(GetValue) 按张量 dtype 直接整型截断,精确——缺陷仅在 arch35 regbase(Ascend950 实际执行路径)
  5. 潜伏同款foreach_add_scalar_list_regbase.h:49 同一行写法(int32×INT64 组合 Adds 路径),清洁轮 26 个 int32 用例标量恰在 fp32 精确域未触发——已一并修复。

历史注记:20260825 验证报告曾把同类现象判为纯 golden bug(golden 用 float64 计算 >2⁵³ 丢精度——确为真 bug 已修)并回滚了当时的 kernel 修改;本轮受控实验将两者分离实锤:golden float64 是一回事,kernel 标量 fp32 舍入是另一回事,均真实存在。

四、为什么 torch_npu 真机没问题(影响面界定)

torch_npu op-plugin(ForeachMulKernelNpuOpApi.cpp)对 _foreach_mul(TensorList, ScalarList) 的分流:

if (!at::native::can_use_fast_route(tensors, scalars,
                                    /*does_op_promote_integer_inputs_to_float=*/true)) {
    return at::native::foreach_tensor_mul_scalarlist_kernel_slow(tensors, scalars);  // ← int32×int 走这里
}
EXEC_NPU_CMD(aclnnForeachMulScalarList, ...)  // fast path

ATen 规则:整型张量 + 整型标量(promote=true)→ fast path 被禁 → slow path 逐张量 at::mul(int32, int64_scalar)(整型精确)。真机行为矩阵全部吻合:int32×INT→slow 精确;int32×FLOAT→先 torch promote 输出 fp32;fp32×INT→fast path 尝试被 op def 矩阵拒(161002,反向证明 INT 标量对浮点张量确实进 fast path)。

结论:torch_npu 的"正确"来自 op-plugin fallback 的巧合性屏蔽,非算子无缺陷。影响面 = 直调 aclnnForeachMulScalarList 且标量为 INT64 的调用方(TTK、自研引擎);PTA 生产栈不受影响。

五、修复

// 修复后(mul + add 两文件同款):判据改为【张量 dtype T】
using scalarCalcType = typename Conditional<
    AscendC::IsSameType<T, int32_t>::value,   // int 路径 T=int32_t → true
    int32_t,                                   // int64→int32 直接截断
    float>::type;

正确性:int32 wrap 乘法结果只依赖标量 mod 2³²,int64→int32 截断保持低 32 位不变 → 与 int64 精确乘再 wrap 逐位等价(torch 基准验证)。编译先例:foreach_addc_list_regbase.h ComputeIntPath 已用 int32_t 标量调 Muls(RegTensor<int32_t>,…,int32_t,maskReg)。附:div_scalar_list 无此问题(其 def 契约 x/scalars 全浮点,float 承载即语义本身)。

修复文件:

  • ops/ops-nn/foreach/foreach_mul_scalar_list/op_kernel/arch35/foreach_mul_scalar_list_regbase.h:49
  • ops/ops-nn/foreach/foreach_add_scalar_list/op_kernel/arch35/foreach_add_scalar_list_regbase.h:49(潜伏同步修)

待算子重建后复跑 python3 rerun_nc100_failed_cann92.py ForeachMulScalarList(RERUN_CANN=完整 9.2.0 包)验证。

附:取证数据索引

  • 清洁轮用例/结果:quality-report/nc_et_v6/ForeachMulScalarList/ForeachMulScalarList_nc100{,_result}.csv
  • 受控探针(固定标量/语义矩阵):/tmp/opencode/mulsll_probe*/tmp/opencode/mulsll_sem*
  • 直连 C++ 程序:/tmp/opencode/nc_test/test_foreach_mulsll_direct.cpp
  • torch_npu 分流取证:op-plugin 源码 ForeachMulKernelNpuOpApi.cpp(gitee 镜像)+ 真机行为矩阵
  • 主报告:quality-report/NC100_ACLNN_VALIDATION_FINAL_20260901.mdDEFECT_FIX_REPORT_NC100_20260831.md

Environment / 环境信息 (Mandatory / 必填)

CANN 9.2.0,Ascend950PR

Steps to reproduce the issue / 重现步骤 (Mandatory / 必填)

构造int64 scalar输入用例,值固定为1605116159:
case_id source_case_id testcase_name network_name api_name tensor_view_shapes tensor_dtypes tensor_formats tensor_storage_shapes tensor_view_offsets tensor_view_strides output_tensor_indexes output_inplace_indexes attributes scalar_dtypes scalar_data_ranges input_data_ranges precision_tolerances absolute_precision is_enabled remark soc_series priority dump_file_prefix manual_tensor_binaries manual_golden_binaries coverage_tags
ForeachMulScalarList_NCG_0006 ForeachMulScalarList_CASE126 ForeachMulScalarList_NCG_0006 op_quality_review aclnnForeachMulScalarList (((64, 1021), (64, 1021)), ((64, 1021), (64, 1021))) (('int32', 'int32'), ('int32', 'int32')) (('ND', 'ND'), ('ND', 'ND')) (((64, 4084), (64, 1021)), ((64, 1021), (64, 1021))) ((0, 0), (0, 0)) (((4084, 4), (1021, 1)), ((1021, 1), (1021, 1))) (1,) () {} (('int64', 'int64'),) (((1605116159, 1605116159), (1605116159, 1605116159)),) (((-2147483648, 2147483647), (-2147483648, 2147483647)), ((None, None), (None, None))) 0 TRUE nc100:strided_slice:f4:combo:1 0 () () prototype;prototype_dtype_format_attr;dtype;format;shape;optional_input;optional_input:NOT_APPLICABLE;dynamic_shape;shape_bucket:medium;value_profile:mixed_magnitude;value_profile;shape_structure;thin_dimension;pta_aclnn;geir;dtype_relation:impl_inferred;attr:NOT_APPLICABLE;attr_boundary:NOT_APPLICABLE;attr_pairwise_combo:NOT_APPLICABLE;input_correlation;empty_tensor:NOT_APPLICABLE;format_variant:NOT_APPLICABLE;magnitude_profile:mixed_magnitude;magnitude_profile;alignment;broadcast;core_count;host_tiling;kernel_branch;kernel_entry;tail;tilingKey;tiling_data;workspace;non_contiguous:strided_slice:f4:1
ForeachMulScalarList_NCG_0035 ForeachMulScalarList_CASE726 ForeachMulScalarList_NCG_0035 op_quality_review aclnnForeachMulScalarList (((8, 64, 127), (8, 64, 127)), ((8, 64, 127), (8, 64, 127))) (('int32', 'int32'), ('int32', 'int32')) (('ND', 'ND'), ('ND', 'ND')) (((1, 64, 127), (1, 64, 127)), ((8, 64, 127), (8, 64, 127))) ((0, 0), (0, 0)) (((0, 127, 1), (0, 127, 1)), ((8128, 127, 1), (8128, 127, 1))) (1,) () {} (('int64', 'int64'),) (((1605116159, 1605116159), (1605116159, 1605116159)),) (((-2147483648, 2147483647), (-2147483648, 2147483647)), ((None, None), (None, None))) 0 TRUE nc100:expand_bcast:f2:combo:11 0 () () prototype;prototype_dtype_format_attr;dtype;format;shape;optional_input;optional_input:NOT_APPLICABLE;dynamic_shape;shape_bucket:medium;value_profile:mixed_magnitude;value_profile;shape_structure;thin_dimension;pta_aclnn;geir;dtype_relation:impl_inferred;attr:NOT_APPLICABLE;attr_boundary:NOT_APPLICABLE;attr_pairwise_combo:NOT_APPLICABLE;input_correlation;empty_tensor:NOT_APPLICABLE;format_variant:NOT_APPLICABLE;magnitude_profile:mixed_magnitude;magnitude_profile;alignment;broadcast;core_count;host_tiling;kernel_branch;kernel_entry;tail;tilingKey;tiling_data;workspace;non_contiguous:expand_bcast:f2:11

Describe the expected behavior / 预期结果 (Mandatory / 必填)

预期精度通过

精度失败

Special notes for this issue/备注 (Optional / 选填)

likedislike
高逸凡
10 天前 修改了issue 的描述
CANN-robotCANN-robot成员
10 天前 关闭了 issue
CANN-robotCANN-robot成员
10 天前 添加了label:resolved