已关闭
[Documentation|文档反馈]: aclnnPowTensorScalar 头文件写 out 须与 self 一致,资料与实现均为「可转换」 #3959
liuhanwen8创建于 10 天前关闭于 6 天前
10 天前 添加了label:documentation
陈思
9 天前 评论:
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 的案例不能代表所有数值都具有相同精度。类型推导。本条可按头文件类型关系说明不一致处理。


9 天前 将 chen_0715 设为负责人
9 天前 添加了label:pending-confirmation
9 天前 删除了label:pending-confirmation
8 天前 将 Nice_try 设为负责人
7 天前 关联了pull request:[WIP]fix some docs
6 天前 关闭了 issue
6 天前 添加了label:resolved
一、文档链接 (必填)
https://gitcode.com/cann/ops-math/blob/master/math/pow/op_api/aclnn_pow.h
二、问题文档片段 (必填)
现象
头文件 :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」的校验。期望
以资料 :127 为准:out 只需 shape 与 self 一致、dtype 为推导结果可转换的类型。错的是头文件 :31(写成了严格一致),资料侧无需改动。
环境
584b2ea6cbaeba684a51a74296d82d4d50ab3b0f(git fetch 后的 origin/master)复现步骤
repro.cppexport ASCEND_HOME_PATH=<CANN安装目录>; export ASCEND_OPP_PATH=$ASCEND_HOME_PATH/opp; export ASCEND_RT_VISIBLE_DEVICES=1g++ -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 -lnnopbaseexport LD_LIBRARY_PATH=$ASCEND_HOME_PATH/lib64:/usr/local/Ascend/driver/lib64:$LD_LIBRARY_PATH; ./repro[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 号卡):影响
按头文件 :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 是另一接口且方向相反(头文件写「可转换」、资料/实现要求严格一致)。