Closed
[Bug-Report|缺陷反馈]: aclnnPowScalarTensor/aclnnSubs 的 out 非白名单 dtype 返回 561000/561103 而非 161002 #3964
liuhanwen8Add on  13 days agoClosed at  7 days ago
liuhanwen8
liuhanwen8
13 days ago Create

aclnnPowScalarTensor / aclnnSubs / aclnnInplaceSubs 在 out 的 dtype 传资料输出支持列表之外的值(如 int4)时,第一段接口返回内部错误码,而资料错误码表要求 161002(ACLNN_ERR_PARAM_INVALID)。

  • aclnnPowScalarTensor:out=DT_INT4 → 561000(ACLNN_ERR_INNER)
  • aclnnSubs / aclnnInplaceSubs:out=DT_INT4 → 561103(ACLNN_ERR_INNER_NULLPTR)

非法入参确实被拒了,但错误码降级为「内部错误 / 内部空指针」,调用方无法按资料区分「参数非法」与「算子内部失败」。

与既有 issue 的关系(不重复提交):#3937(aclnnPowTensorScalar out dtype=int4 → 561103)、#3926(aclnnAddN 出参 dtype 非法 → 561103)、#3927(aclnnSum 出参 dtype 与输入不一致 → 561103)记录的是同一根因(CheckDtypeValid / CheckParams 只对输入侧做白名单,out 仅过 CanCast)。本条补的是另外两个接口入口(PowScalarTensor 与 Subs 族),并且给出同仓 aclnnAll / aclnnEye 的正面实现作为修复参照,便于一次性收敛同类问题。

二、环境信息 (必填)

  • 昇腾硬件型号:Atlas A2 训练系列产品(910B2,8 die)
  • CANN 版本:9.2.0(/usr/local/Ascend/ascend-toolkit/latest)
  • 驱动 / 固件:26.1.1 / 7.8.0.6.201
  • 操作系统:openEuler(aarch64),g++ 11.4.0
  • 仓库与提交:cann/ops-math 584b2ea6cbaeba684a51a74296d82d4d50ab3b0f(origin/master,2026-10-24)
  • 复现日期:2026-10-25

三、复现步骤 (必填)

  1. 保存下方 repro.cpp
  2. export ASCEND_HOME_PATH=/usr/local/Ascend/ascend-toolkit/latest
  3. g++ -std=c++17 -O2 repro.cpp -o repro -I$ASCEND_HOME_PATH/include -L$ASCEND_HOME_PATH/lib64 -lnnopbase -lascendcl -lopapi -lpthread
  4. ./repro
#include <cstdio>
#include <cstdint>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_pow.h"
#include "aclnnop/aclnn_sub.h"

static aclTensor* mk(const std::vector<int64_t>& sh, aclDataType dt, void* dev)
{
    std::vector<int64_t> st(sh.size(), 1);
    for (int64_t i = (int64_t)sh.size() - 2; i >= 0; --i) { st[i] = sh[i + 1] * st[i + 1]; }
    return aclCreateTensor(sh.data(), sh.size(), dt, st.data(), 0, ACL_FORMAT_ND,
                           sh.data(), sh.size(), dev);
}

int main()
{
    aclInit(nullptr);
    aclrtSetDevice(0);
    const int64_t N = 32;
    for (auto pr : {std::make_pair(ACL_FLOAT, "FLOAT(ctrl)"),
                    std::make_pair(ACL_BOOL,  "BOOL(ctrl)"),
                    std::make_pair(ACL_INT4,  "INT4(defect)")}) {
        void* ed = nullptr; aclrtMalloc(&ed, N * 4, ACL_MEM_MALLOC_HUGE_FIRST);
        aclTensor* e = mk({4, 8}, ACL_FLOAT, ed);
        void* od = nullptr; aclrtMalloc(&od, N + 128, ACL_MEM_MALLOC_HUGE_FIRST);
        aclTensor* o = mk({4, 8}, pr.first, od);
        float sv = 2.0f;
        aclScalar* s = aclCreateScalar(&sv, ACL_FLOAT);
        uint64_t ws = 0; aclOpExecutor* ex = nullptr;
        int ret = aclnnPowScalarTensorGetWorkspaceSize(s, e, o, &ws, &ex);
        printf("PowScalarTensor out=%-14s ret=%d
", pr.second, ret);
        aclDestroyScalar(s); aclDestroyTensor(e); aclDestroyTensor(o);
        aclrtFree(ed); aclrtFree(od);
    }
    for (auto pr : {std::make_pair(ACL_FLOAT, "FLOAT(ctrl)"),
                    std::make_pair(ACL_BOOL,  "BOOL(ctrl)"),
                    std::make_pair(ACL_INT4,  "INT4(defect)")}) {
        void* sd = nullptr; aclrtMalloc(&sd, N * 4, ACL_MEM_MALLOC_HUGE_FIRST);
        aclTensor* s = mk({4, 8}, ACL_FLOAT, sd);
        void* od = nullptr; aclrtMalloc(&od, N + 128, ACL_MEM_MALLOC_HUGE_FIRST);
        aclTensor* o = mk({4, 8}, pr.first, od);
        float ov = 2.0f, av = 1.2f;
        aclScalar* other = aclCreateScalar(&ov, ACL_FLOAT);
        aclScalar* alpha = aclCreateScalar(&av, ACL_FLOAT);
        uint64_t ws = 0; aclOpExecutor* ex = nullptr;
        int ret = aclnnSubsGetWorkspaceSize(s, other, alpha, o, &ws, &ex);
        printf("Subs            out=%-14s ret=%d
", pr.second, ret);
        aclDestroyScalar(other); aclDestroyScalar(alpha);
        aclDestroyTensor(s); aclDestroyTensor(o);
        aclrtFree(sd); aclrtFree(od);
    }
    aclrtResetDevice(0); aclFinalize();
    return 0;
}

四、实际输出

PowScalarTensor out=FLOAT(ctrl)    ret=0
PowScalarTensor out=BOOL(ctrl)     ret=161002
PowScalarTensor out=INT4(defect)   ret=561000      ← 应为 161002
Subs            out=FLOAT(ctrl)    ret=0
Subs            out=BOOL(ctrl)     ret=161002
Subs            out=INT4(defect)   ret=561103      ← 应为 161002

补充扫描(同一脚本,out 全 dtype):

out dtype 在资料 out 列表 PowScalarTensor Subs
FLOAT / FLOAT16 / DOUBLE / BFLOAT16 / COMPLEX64 / COMPLEX128 是 0 0
INT8/INT16/INT32/INT64/UINT8/UINT16/UINT32/UINT64 是(与 fp32 推导不兼容) 161002 ✔ 161002 ✔
BOOL 否 161002 ✔ 161002 ✔
INT4 否 561000 ✘ 561103 ✘
UINT1 否 561000 ✘ 561103 ✘
HIFLOAT8 否 561000 ✘ 561103 ✘
FLOAT8_E5M2 否 561000 ✘ 561103 ✘
COMPLEX32 否 561000 ✘ 561103 ✘

规律:「在列表内但不兼容」与「CanCast 能识别的不兼容」都能正确返回 161002;只有「完全不在列表且 CanCast 放行」的 dtype 才漏到内部错误码。

五、首条 plog(定位漏在哪一层)

ASCEND_GLOBAL_LOG_LEVEL=1 后 aclnnPowScalarTensor out=INT4:

[cast.cpp:390][NNOP][Cast] errno[561000] OpName:[aclnnPowScalarTensor_1]
    Check IsAiCpuSupport(self, dstDtype) failed.
[aclnn_pow.cpp:569][NNOP][BuildPowScalarTensorCompute] errno[561000]
    Check castOut != nullptr failed.
[aclnn_pow.cpp:610][NNOP][aclnnPowScalarTensorGetWorkspaceSize] errno[561000]
    Check BuildPowScalarTensorCompute(...) == ACLNN_SUCCESS failed.

首条落在 Cast,不在 CheckParams —— 直接指认「参数校验阶段没拦住」。

六、根因(源码锚点)

math/pow/op_api/aclnn_pow.cpp:164 CheckDtypeValid:

if (!CheckType(selfDtype, DTYPE_SUPPORT_LIST)) { ...; return false; }   // L170
if (!CheckType(expDtype, DTYPE_SUPPORT_LIST))  { ...; return false; }   // L175
op::DataType promoteType = op::PromoteType(selfDtype, expDtype);
if (promoteType == DataType::DT_UNDEFINED) { ...; return false; }
OP_CHECK_RESULT_DTYPE_CAST_FAILED(promoteType, outDtype, return false);  // L187 只有 CanCast

CanCast(fp32, DT_INT4) 返回 true → 放行 → 进入 BuildPowScalarTensorCompute → L485
l0op::Cast(powOut, out->GetDataType(), ...) 返回空 → L486 CHECK_RET(..., ACLNN_ERR_INNER_NULLPTR)
→ 561000。

math/sub/op_api/aclnn_sub.cpp:240 CheckParams:

OP_CHECK_DTYPE_NOT_SUPPORT(self,  dtypeSupportList, return ACLNN_ERR_PARAM_INVALID);  // L248
OP_CHECK_DTYPE_NOT_SUPPORT(other, dtypeSupportList, return ACLNN_ERR_PARAM_INVALID);  // L249
// ← 缺 out

aclnnInplaceSubs 与 aclnnSubs 共用同一 CheckParams,因此同源。

七、同仓正面实现(修复参照)

grep -rn 'OP_CHECK_DTYPE_NOT_SUPPORT' \
    math/eye/op_api/aclnn_eye.cpp math/reduce_all/op_api/aclnn_all.cpp \
    math/sub/op_api/aclnn_sub.cpp math/pow/op_api/aclnn_pow.cpp
文件:行 对 out 做白名单
math/eye/op_api/aclnn_eye.cpp:60 ✅ OP_CHECK_DTYPE_NOT_SUPPORT(out, dtypeSupportList, return false)
math/reduce_all/op_api/aclnn_all.cpp:157 ✅ OP_CHECK_DTYPE_NOT_SUPPORT(out, out_dtype_support_list, return false)
math/sub/op_api/aclnn_sub.cpp:248-249 ❌ 只有 self / other
math/pow/op_api/aclnn_pow.cpp ❌ 无该宏

全仓扫描(op_api/aclnn_*.cpp,546 个接口):有 out 白名单 230 个、缺 316 个。
建议在公共宏层(op_error_check.h)加一条 OP_CHECK_OUT_DTYPE_NOT_SUPPORT,
把「是否给 out 建白名单」变成显式选择,而不是默认漏掉。

八、期望行为

补上与 aclnnEye / aclnnAll 一致的 out 白名单检查,使上述场景返回 161002:

// pow:OUT_DTYPE_SUPPORT_LIST 取资料 out 列表(比 self/exponent 多 UINT16/UINT32/UINT64)
if (!CheckType(outDtype, OUT_DTYPE_SUPPORT_LIST)) {
    OP_LOGE(ACLNN_ERR_PARAM_INVALID,
            "out dtype %s should be in dtype support list %s.",
            op::ToString(outDtype).GetString(),
            op::ToString(OUT_DTYPE_SUPPORT_LIST).GetString());
    return false;
}
// sub:
OP_CHECK_DTYPE_NOT_SUPPORT(out, dtypeSupportList, return ACLNN_ERR_PARAM_INVALID);

九、影响面

  • 不是静默错值:非法 dtype 没有被写出脏数据。
  • 是错误码语义漂移:561000 = 内部错误、561103 = 内部空指针。上层框架按 161002
    分支做参数纠错提示,收到 56xxxx 会走「算子内部异常」路径 → 误判为算子 bug,
    掩盖真实原因(调用方传错 out dtype),并拉长定位链路。
  • 可触发 dtype:INT4 / UINT1 / HIFLOAT8 / FLOAT8_E5M2 / FLOAT8_E4M3FN / COMPLEX32(6 种)。

十、附:同批确认「不是缺陷」的相邻项(供维护者区分)

场景 实测 判定
out=UINT16/UINT32/UINT64(在列表但 fp32 推不进去) 161002 符合资料
self=fp32 + exponent=int32 + out=int32 161002 符合资料(推导 fp32,转不回 int32)
self=2(int) + exponent=int32 + out=int32(同型) 0 正确
subs: self=int32 + alpha=fp32(1.2) 161002 符合资料(fp32 alpha 让推导变 fp32)
subs: self=int32 + other=int32 + alpha=int32(同型) 0 正确
exponent rank=9 / shape 不一致 161002 符合资料
pow: self=int64 + exponent=bool + out=bool 161002 CanCast(int64,bool)=false,符合资料
pow: self=bool + exponent=bool 161002 源码明确拒绝,符合资料
likedislike
陈思
陈思Collaborators
12 days ago Comments:

您好,感谢反馈!按提交 584b2ea6 核对,PowScalarTensor 与非原地 Subs 都没有独立检查 out 是否位于各自的公开类型列表内,仅靠转换关系检查,确有让不支持的输出类型进入后续构图的缺口。报告的错误码差异可由两条不同的返回链解释。

源码核对

  • PowScalarTensor:共享类型检查只检查两个输入的类型列表;计算分支中末尾 Cast返回空指针时,辅助函数先返回 561103,而入口将辅助函数失败统一映射为 561000。这里应定位到 ScalarTensor 的辅助函数,报告引用的 485 行属于另一个 TensorScalar 入口。
  • Subs:实际调用的是 CheckParamsScalar,其中只对白名单检查 self,并未独立检查 out;输出 Cast 失败则直接映射为 561103。报告引用的 240 行是 Tensor 版本的检查函数。
  • InplaceSubs:转发时 selfRef 同时作为输入和输出。因此,selfRef=INT4 会先被输入白名单检查拒绝为 161002;现有复现脚本也没有调用该原地入口,尚不能将相同的“独立 out 非法”路径和 561103 结论直接推广到它。

处理方向

建议在两个独立输出入口的参数阶段,按产品与具体接口补齐输出类型范围,再保留已有推导、alpha 和转换关系校验,使不支持的类型尽早返回 161002。内部计算对象失败仍应保留内部错误码。

输出列表须按入口分别核对:PowScalarTensor输出比输入多 UINT16/UINT32/UINT64,且与 TensorScalar 的 BOOL 描述不同,不能把同一个输入列表直接复用到共享检查;Subs 的输出列表实际包含 BOOL,所以该类型的转换失败不能解释为“BOOL 不在输出列表”。已有通用类型检查宏可以承载输出检查,新宏属于可选的代码组织方式;也不能仅凭全仓是否出现某个宏就判定其他接口都存在同样缺陷。

本次未新增硬件复测,外部 CanCast 对全部扫描类型的具体判定也尚未独立验证。后续将继续按入口核对输出类型、返回码与原地别名边界,跟踪合法类型及转换规则的回归确认。

likedislike
陈思陈思Collaborators
12 days ago Assign chen_0715 as responsible person
陈思陈思Collaborators
12 days ago Added label: pending-confirmation
陈思陈思Collaborators
12 days ago Removed label: pending-confirmation
庄昭雄庄昭雄
9 days ago Associated pull request
庄昭雄
庄昭雄
9 days ago Comments:

Subs 部分已提交修复:!6792

  • CheckPromoteType 增加 out 白名单(含平台相关的 BF16 处理),越界 dtype 第一段返回 161002;
  • aclnnInplaceSubs 经 selfRef 复用同一路径。

真机(910B / CANN 8.5.1):修复前 out=UINT16 返回 0 且执行成功,修复后返回 161002;out=FLOAT 仍为 0。

PowScalarTensor 未包含在本 MR 中(8.5.1 无该接口头文件,未做真机验证)。

likedislike
庄昭雄
庄昭雄
9 days ago Comments:

更正:Subs 修复的 MR 号是 !6793(上一条中 "!6792" 为笔误)。

likedislike
庄昭雄庄昭雄
9 days ago Associated pull request
庄昭雄
庄昭雄
9 days ago Comments:

补充:PowScalarTensor 也已被 !6796 覆盖(与 PowTensorScalar 共用 CheckDtypeValid 的 out 白名单;真机以 PowTensorScalar 的 INT4 → 161002 验证)。Subs 部分见 !6793。

likedislike
Bbaijinxiang_hwCollaborators
9 days ago Associated pull request
baijinxiang_hwCollaborators
9 days ago Comments:

/assign @doufloat

likedislike
CANN-robotCANN-robotCollaborators
9 days ago Assign doufloat as responsible person
CchenyifengCollaborators
9 days ago Removed responsible person chen_0715
baijinxiang_hwCollaborators
8 days ago Comments:

已按兼容性要求将修正范围收敛到 A2/A3:

  • A2/A3 均走 DAV_2201 路径。新增 out dtype 白名单仅在 DAV_2201 启用,非法 out 在 ACLNN 第一段参数校验中返回 ACLNN_ERR_PARAM_INVALID(161002)。
  • A5 走 DAV_3510 路径,不启用新增白名单,保持原有 CanCast 及后续 Build 行为不变。
  • A2/A3 合法 out dtype 路径不变;本次只修正非法 dtype 的错误码语义。
  • A5 真机实测保持基线行为:PowScalarTensor 的 out=INT4/UINT1/COMPLEX32 返回 561000,PowTensorScalar/PowTensorTensor/Subs/Sub 的对应场景返回 561103;HIFLOAT8/FLOAT8_E5M2 仍返回 0。
  • A2/A5 均使用当前补丁运行 math_op_api_ut,结果均为 158/158 PASS。A2 真机实测非法 out 返回 161002,合法 INT64 -> UINT16/UINT32/UINT64 仍返回 0。
  • A3 与 A2 使用相同的 DAV_2201 实现路径;本轮 A2 已完成真机验证,A3 未单独上板复测。

兼容性影响:本次只改变 A2/A3 非法 out dtype 的报错语义,不改变合法 dtype 行为;A5 暂不修正,保持现有行为。

关联 PR:https://gitcode.com/cann/ops-math/pull/6800
PR head:d803c07af069cf7444e95d5a1ea535a8d1246dfe

likedislike
Bbaijinxiang_hwCollaborators
7 days ago changed custom state from 进行中 to 已解决
Bbaijinxiang_hwCollaborators
7 days ago Close issue