已关闭
[Documentation|文档反馈]: aclnnPowTensorScalar 头文件写 out 须与 self 一致,资料与实现均为「可转换」 #3959
liuhanwen8创建于  10 天前关闭于  6 天前
liuhanwen8
liuhanwen8
10 天前 创建

一、文档链接 (必填)

https://gitcode.com/cann/ops-math/blob/master/math/pow/op_api/aclnn_pow.h

二、问题文档片段 (必填)

math/pow/op_api/aclnn_pow.h:31:@param [in] out … 数据类型支持FLOAT、FLOAT16、…、且数据类型、数据格式和shape与self一致。
math/pow/docs/aclnnPowTensorScalar&aclnnInplacePowTensorScalar.md:127:shape需要与self一致,数据类型需要是self的数据类型与exponent的数据类型推导之后可转换的数据类型。

现象

头文件 :31 要求 out dtype 与 self 一致,与资料 :127 和实现均不符:实测 self=fp16、out=fp32 时两段返回 0/0、out[0]=4.0000 正确;实现只做 OP_CHECK_RESULT_DTYPE_CAST_FAILED(promoteType, outDtype)(math/pow/op_api/aclnn_pow.cpp:188,宏见 common/inc/external/aclnn_kernels/common/op_error_check.h:139),没有「out dtype 必须等于 self」的校验。

输入(self=2, exponent=2,正确结果 4) 两段返回 out[0] 与头文件 :31
对照 self=fp16 out=fp16 0 / 0 4.0000 一致
争议组 self=fp16 out=fp32 0 / 0 4.0000 不一致(头文件称需相同)
争议组 self=fp32 out=fp16 0 / 0 4.0000 不一致
对照 self=complex64 out=fp64 161002 / 161002 - 校验确实生效

期望

以资料 :127 为准:out 只需 shape 与 self 一致、dtype 为推导结果可转换的类型。错的是头文件 :31(写成了严格一致),资料侧无需改动。

环境

  • 硬件:Atlas A2 910B2(8 卡;0 号卡 Health=Alarm,本次使用 1 号卡)
  • CANN:Ascend-cann-910b-ops 9.2.0(V100R001C12SPC001B237),aarch64,g++ 11.4.0
  • 仓库与提交:cann/ops-math 584b2ea6cbaeba684a51a74296d82d4d50ab3b0f(git fetch 后的 origin/master)
  • 复现日期:2026-10-25

复现步骤

  1. 保存下方代码为 repro.cpp
  2. export ASCEND_HOME_PATH=<CANN安装目录>; export ASCEND_OPP_PATH=$ASCEND_HOME_PATH/opp; export ASCEND_RT_VISIBLE_DEVICES=1
  3. g++ -std=c++17 -O2 repro.cpp -o repro -I$ASCEND_HOME_PATH/include -I$ASCEND_HOME_PATH/include/aclnnop -L$ASCEND_HOME_PATH/lib64 -Wl,--allow-shlib-undefined -lascendcl -lopapi -lnnopbase
  4. export LD_LIBRARY_PATH=$ASCEND_HOME_PATH/lib64:/usr/local/Ascend/driver/lib64:$LD_LIBRARY_PATH; ./repro
  5. 看 [disputed] 行:返回 0/0 即证明头文件 :31 的「必须与 self 一致」不存在

复现脚本

// build: g++ -std=c++17 -O2 repro.cpp -o repro -I$ASCEND_HOME_PATH/include -I$ASCEND_HOME_PATH/include/aclnnop \
//        -L$ASCEND_HOME_PATH/lib64 -Wl,--allow-shlib-undefined -lascendcl -lopapi -lnnopbase
#include <cmath>
#include <cstdio>
#include <cstdint>
#include "acl/acl.h"
#include "aclnnop/aclnn_pow.h"

struct Case { const char* name; aclDataType self; aclDataType out; };

static double Read(aclDataType t, const char* p)
{
    if (t == ACL_FLOAT) { return *(const float*)p; }
    if (t == ACL_DOUBLE) { return *(const double*)p; }
    uint16_t h = *(const uint16_t*)p;                       // half
    uint32_t e = (h >> 10) & 0x1f;
    double v = e ? (1.0 + (h & 0x3ff) / 1024.0) * std::pow(2.0, (int)e - 15)
                 : ((h & 0x3ff) / 1024.0) * std::pow(2.0, -14);
    return (h >> 15) ? -v : v;
}

static void Run(aclrtStream st, const Case& c)
{
    const size_t bytes = 64;
    int64_t dim[1] = {8}, str[1] = {1};
    char sh[64] = {0}, oh[64] = {0};
    for (int i = 0; i < 8; ++i) {
        if (c.self == ACL_FLOAT16) { ((uint16_t*)sh)[i] = 0x4000; }        // 2.0
        if (c.self == ACL_FLOAT) { ((float*)sh)[i] = 2.0f; }
    }
    void *sd = nullptr, *od = nullptr, *wd = nullptr;
    aclrtMalloc(&sd, bytes, ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMemcpy(sd, bytes, sh, bytes, ACL_MEMCPY_HOST_TO_DEVICE);
    aclrtMalloc(&od, bytes, ACL_MEM_MALLOC_HUGE_FIRST);
    aclrtMemcpy(od, bytes, oh, bytes, ACL_MEMCPY_HOST_TO_DEVICE);
    int64_t e = 2;
    aclScalar* exp = aclCreateScalar(&e, ACL_INT64);
    aclTensor* self = aclCreateTensor(dim, 1, c.self, str, 0, ACL_FORMAT_ND, dim, 1, sd);
    aclTensor* out = aclCreateTensor(dim, 1, c.out, str, 0, ACL_FORMAT_ND, dim, 1, od);
    uint64_t ws = 0;
    aclOpExecutor* ex = nullptr;
    aclnnStatus r1 = aclnnPowTensorScalarGetWorkspaceSize(self, exp, out, &ws, &ex);
    aclnnStatus r2 = r1;
    if (r1 == ACL_SUCCESS) {
        if (ws > 0) { aclrtMalloc(&wd, ws, ACL_MEM_MALLOC_HUGE_FIRST); }
        r2 = aclnnPowTensorScalar(wd, ws, ex, st);
        aclrtSynchronizeStream(st);
        aclrtMemcpy(oh, bytes, od, bytes, ACL_MEMCPY_DEVICE_TO_HOST);
    }
    printf("%-44s ret_first=%6d ret_second=%6d out[0]=%.4f\n", c.name, (int)r1, (int)r2, Read(c.out, oh));
    aclDestroyScalar(exp); aclDestroyTensor(self); aclDestroyTensor(out);
    aclrtFree(sd); aclrtFree(od); if (wd != nullptr) { aclrtFree(wd); }
}

int main()
{
    // 对照组:合法输入;缺陷组:头文件声明非法、实测返回 0 的组合
    const Case cases[] = {
        {"[control]  self=fp16 exp=int64 out=fp16", ACL_FLOAT16, ACL_FLOAT16},
        {"[disputed] self=fp16 exp=int64 out=fp32", ACL_FLOAT16, ACL_FLOAT},
        {"[narrow]   self=fp32 exp=int64 out=fp16", ACL_FLOAT, ACL_FLOAT16},
        {"[reject]   self=complex64 exp=int64 out=fp64", ACL_COMPLEX64, ACL_DOUBLE},
    };
    aclInit(nullptr); aclrtSetDevice(0);
    aclrtStream st = nullptr; aclrtCreateStream(&st);
    printf("ACL_SUCCESS=0, ACLNN_ERR_PARAM_INVALID=161002; self=2 exp=2, 正确结果 out[0]=4\n");
    for (const auto& c : cases) { Run(st, c); }
    aclrtDestroyStream(st); aclrtResetDevice(0); aclFinalize();
    return 0;
}

实测输出(ASCEND_OPP_PATH 显式指向本机 CANN 的 opp,1 号卡):

[control]  self=fp16 exp=int64 out=fp16      ret_first=     0 ret_second=     0 out[0]=4.0000
[narrow]   self=fp32 exp=int64 out=fp16      ret_first=     0 ret_second=     0 out[0]=4.0000
[disputed] self=fp16 exp=int64 out=fp32      ret_first=     0 ret_second=     0 out[0]=4.0000
[reject]   self=complex64 exp=int64 out=fp64 ret_first=161002 ret_second=161002 out[0]=0.0000

影响

按头文件 :31 实现时,fp16 self 的调用只能拿到 fp16 out;需要 fp32 输出(与框架侧 out dtype 对齐)时得自己再插一次 cast,多一次搬运与显存。两份文档口径相反,接入方无法判定以哪份为准。

建议

改 math/pow/op_api/aclnn_pow.h:31 为「shape需与self一致,数据类型需为self与exponent推导后可转换的数据类型」,与资料 :127、实现 :188 对齐(aclnnInplacePowTensorScalar 的 out 即 self,不受影响)。查重:同接口 #3937 是另一问题(out dtype=int4 报 561103),同类 #3950 是另一接口且方向相反(头文件写「可转换」、资料/实现要求严格一致)。

likedislike
liuhanwen8liuhanwen8
10 天前 添加了label:documentation
陈思
陈思成员
9 天前 评论:

您好,感谢反馈!在 cann/ops-math@584b2ea6 中,头文件的“out 数据类型与 self 一致”确实比资料和实现更严格。资料要求的是 shape 与 self 一致,dtype 为 self 与 exponent 推导后可转换的类型;实现也没有要求 out dtype 等于 self。头文件说明、资料说明。

具体而言,入口先检查类型推导及转换关系,再按 TensorScalar 的推导结果检查输出可转换性,并核对 shape;宏使用的是 CanCast。计算结束后还有显式转换到 out 类型并写回的步骤,因此 dtype 不同本身不是拒绝条件。参数检查、转换宏、输出转换。

可行的修正方向是将该头注释改为“shape 需与 self 一致,数据类型需为 self 与 exponent 推导后可转换的类型”,同时保留格式、产品和其他参数约束。原地接口的输出仍使用 self 本身,不应因此改变原地别名关系。

“可转换”也不代表无精度损失,或指定 FP32 输出就强制内部使用 FP32 计算。例如所述 A2、FP16 self 与 INT64 exponent 的组合,其 TensorScalar 推导仍保留 FP16 计算类型,再转成目标输出类型;结果恰好为 4 的案例不能代表所有数值都具有相同精度。类型推导。本条可按头文件类型关系说明不一致处理。

likedislike
陈思陈思成员
9 天前 将 chen_0715 设为负责人
陈思陈思成员
9 天前 添加了label:pending-confirmation
陈思陈思成员
9 天前 删除了label:pending-confirmation
Nice tryNice try成员
8 天前 将 Nice_try 设为负责人
Nice tryNice try成员
7 天前 关联了pull request:[WIP]fix some docs
CANN-robotCANN-robot成员
6 天前 关闭了 issue
CANN-robotCANN-robot成员
6 天前 添加了label:resolved