Closed
[Bug-Report|缺陷反馈]: aclnnFmodScalar 误用 Tensor-Tensor 推导表,int8 与 uint8 组合被拒(161002),资料 Tensor-Scalar 推导表应得 int8/uint8 #4177
liuhanwen8Add on 13 days agoClosed at 7 days ago
陈思
12 days ago Comments:
12 days ago Comments:
您好,感谢反馈!
问题分析
已核对当前固定提交 8c1f633e3679 的 FmodScalar 实现 与 Tensor-Scalar 推导表。PromoteTypeScalar 在非 RegBase 分支调用 op::PromoteType,CheckPromoteType 又用算子支持列表检查推导结果。INT8 与 UINT8 按这条路径推导到 INT16,而该支持列表不含 INT16,因此会在执行 kernel 前返回参数无效。两种输入各自受支持,并不意味着当前推导后的类型也受支持。
资料表 2 的表头是 Tensor 类型、左列是 Scalar 类型;对应的两种整型组合保留 Tensor 的 INT8/UINT8 类型。这与您报告的 910B 路径存在差异。两个 Scalar 接口共用 GetWorkspaceSizeCommon,因此该选择同时影响原地和非原地入口;本轮确认的是源码机制,未重跑您提供的 64 组真机矩阵。
处理方向
一个可行的方向是在非 RegBase 的 Scalar 路径采用 Tensor-Scalar 推导规则,并让参数预检查与实际 Cast 使用同一推导结果,避免只改检查后仍按旧类型计算。该调整应保留浮点标量、输出转换及现有平台分支的约束,不能通过无条件增加 INT16 支持来替代推导语义核对;真正的 Tensor-Tensor 接口也应继续保留自己的规则。我们会继续确认这条 Scalar 路径及两入口的兼容边界。


12 days ago Assign chen_0715 as responsible person
12 days ago Added label: pending-confirmation
12 days ago Assign m0_55003149 as responsible person
12 days ago Removed responsible person chen_0715
8 days ago Associated pull request
7 days ago Close issue
7 days ago Added label: resolved
一、问题描述 (必填)
aclnnFmodScalar/aclnnInplaceFmodScalar的other形参类型是const aclScalar*(Tensor-Scalar 接口),但其数据类型推导调用的是op::PromoteType(Tensor-Tensor 推导表),而不是资料规定的 Tensor-Scalar 推导表。后果:
self=int8且other=uint8(以及反向self=uint8+other=int8)这 2 种资料允许的合法组合被拒绝,返回 161002。资料依据(
docs/zh/context/deduction_relationship.md)aclnnFmodTensor):s8 × u8 → s16aclnnAdds、aclnnMuls、本接口):s8 × u8 → s8,u8 × s8 → u8推导表说明原文:
本接口
other是aclScalar,属于表2 适用场景。实测结果(8×8 全部 64 组 dtype 组合,逐组直调)
统计:64 组中与表1 不一致 0 组、与表2 不一致 2 组。即实现完全按表1 推导。
支持列表(源码
aclnn_fmod_scalar.cpp:36-38,910B)为{FLOAT, INT32, INT8, FLOAT16, UINT8, DOUBLE, INT64, BF16}—— 不含 INT16。表1 推出的
s16不在列表内,于是被CheckPromoteType拒绝为 161002;表2 推出的s8/u8在列表内,本应正常计算。代码定位
math/mod/op_api/aclnn_fmod_scalar.cpp:130-139static DataType PromoteTypeScalar(const op::DataType selfDtype, const op::DataType otherDtype) { if (IsRegBase()) { auto otherDefaultDtype = GetScalarDefaultDtype(otherDtype); auto promoteType = CombineCategoriesWithComplex(selfDtype, otherDefaultDtype); return promoteType; } return op::PromoteType(selfDtype, otherDtype); // ← 非 RegBase 分支直接套用 Tensor-Tensor 推导 }函数名叫
PromoteTypeScalar,但非 RegBase 分支的实现是op::PromoteType(表1语义),名实不符。IsRegBase()仅在DAV_3510 / DAV_9201 / DAV_9202为真(common/inc/op_api/aclnn_check.h:196-207),Ascend 910B 走else分支,因此必现。同仓同类实现对照(同类接口的正确写法)
math/floor_mod/op_api/aclnn_remainder.cpp:133-148—— 同为 Tensor-Scalar 接口,其PromoteTypeScalar明确区分了 scalar 语义:// tensor + scalar混合场景下,推导出应该cast的dtype (并不是promoteType) static inline DataType PromoteTypeScalar(const op::DataType selfDtype, const op::DataType otherDtype) { auto npuArch = op::GetCurrentPlatformInfo().GetCurNpuArch(); if (IsRegBase(npuArch)) { return PromoteTypeScalarV35(selfDtype, otherDtype); } if (IsFloatingType(selfDtype)) { return selfDtype; } else { if (IsFloatingType(otherDtype) || selfDtype == op::DataType::DT_BOOL) { return op::PromoteType(selfDtype, otherDtype); } return selfDtype; // ← 整型 tensor + 整型 scalar → 保持 tensor dtype(即表2 语义) } }同文件还配套了独立的
CheckPromoteTypeTensorScalar(:204-230),注释写明是「tensor + scalar 混合场景」。而
aclnn_fmod_scalar.cpp只有一个CheckPromoteType,Tensor-Tensor 与 Tensor-Scalar 未做区分。影响
int8/uint8是量化场景的常用 dtype,aclnnFmodScalar(标量取模,如量化后做范围折叠)在int8张量配uint8标量(或反向)时直接报 161002,而这是资料允许的组合;aclnnInplaceFmodScalar共用同一GetWorkspaceSizeCommon,同样受影响。二、环境信息 (可选)
npu-smi26.1.1 显示910B3,8 die)/usr/local/Ascend/cann-9.2.0)libopapi_math.so提供aclnnFmodScalar符号;IsRegBase()对 910B 为 false,必现;该 SoC 的 Mod kernel 走 CANN 内置ops_legacy实现,但本问题发生在 aclnn 接口层(host 侧)的参数校验阶段,在到达 kernel 之前就被拒绝,与 kernel 版本无关cann/ops-mathmasterd8fc7a6三、重现步骤 (可选)
自包含 C++ 复现(8×8 全组合扫描,一次跑完 64 组):
// build: // g++ -O2 -std=c++17 repro.cpp -o fmod_promote \ // -I/usr/local/Ascend/cann-9.2.0/include \ // -L/usr/local/Ascend/cann-9.2.0/aarch64-linux/lib64 \ // -lascendcl -lnnopbase -lopapi_math -lopapi // run: // source /usr/local/Ascend/ascend-toolkit/set_env.sh // export LD_LIBRARY_PATH=/usr/local/Ascend/cann-9.2.0/aarch64-linux/lib64:$LD_LIBRARY_PATH // ./fmod_promote #include <cstdio> #include <cstring> #include <cstdint> #include <vector> #include <unistd.h> #include "acl/acl.h" #include "aclnnop/aclnn_fmod_scalar.h" struct D { const char* n; aclDataType a; int sz; }; static D kD[] = { {"f32", ACL_FLOAT, 4}, {"f16", ACL_FLOAT16, 2}, {"f64", ACL_DOUBLE, 8}, {"bf16", ACL_BF16, 2}, {"s8", ACL_INT8, 1}, {"u8", ACL_UINT8, 1}, {"s32", ACL_INT32, 4}, {"s64", ACL_INT64, 8}, }; int main() { setvbuf(stdout, nullptr, _IONBF, 0); int32_t dev = 0; const char* e = getenv("FMOD_DEV"); if (e != nullptr) dev = atoi(e); aclInit(nullptr); aclrtSetDevice(dev); for (int i = 0; i < 8; i++) { // self = kD[i] for (int j = 0; j < 8; j++) { // other = kD[j](aclScalar) int64_t n = 8; std::vector<int64_t> sh = {4, 2}; std::vector<int64_t> sd = {2, 1}; void* dS = nullptr; void* dO = nullptr; aclrtMalloc(&dS, (size_t)(n * kD[i].sz), ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc(&dO, (size_t)(n * kD[i].sz), ACL_MEM_MALLOC_HUGE_FIRST); std::vector<uint8_t> hb((size_t)(n * kD[i].sz), 0); for (int64_t q = 0; q < n; q++) { double v = (double)(q * 5 + 2); uint8_t* p = hb.data() + q * kD[i].sz; if (strcmp(kD[i].n, "f32") == 0) { float f = (float)v; memcpy(p, &f, 4); } else if (strcmp(kD[i].n, "f16") == 0) { uint16_t h = aclFloatToFloat16((float)v); memcpy(p, &h, 2); } else if (strcmp(kD[i].n, "f64") == 0) { memcpy(p, &v, 8); } else if (strcmp(kD[i].n, "bf16") == 0) { uint32_t u; float f = (float)v; memcpy(&u, &f, 4); uint16_t b = (uint16_t)(u >> 16); memcpy(p, &b, 2); } else if (strcmp(kD[i].n, "s8") == 0) { int8_t x = (int8_t)(v - 30); memcpy(p, &x, 1); } else if (strcmp(kD[i].n, "u8") == 0) { uint8_t x = (uint8_t)(v + 40); memcpy(p, &x, 1); } else if (strcmp(kD[i].n, "s32") == 0) { int32_t x = (int32_t)v; memcpy(p, &x, 4); } else if (strcmp(kD[i].n, "s64") == 0) { int64_t x = (int64_t)v; memcpy(p, &x, 8); } } aclrtMemcpy(dS, (size_t)(n * kD[i].sz), hb.data(), (size_t)(n * kD[i].sz), ACL_MEMCPY_HOST_TO_DEVICE); uint8_t sb[8] = {0}; double ov = 3.0; if (strcmp(kD[j].n, "f32") == 0) { float f = (float)ov; memcpy(sb, &f, 4); } else if (strcmp(kD[j].n, "f16") == 0) { uint16_t h = aclFloatToFloat16((float)ov); memcpy(sb, &h, 2); } else if (strcmp(kD[j].n, "f64") == 0) { memcpy(sb, &ov, 8); } else if (strcmp(kD[j].n, "bf16") == 0) { uint32_t u; float f = (float)ov; memcpy(&u, &f, 4); uint16_t b = (uint16_t)(u >> 16); memcpy(sb, &b, 2); } else if (strcmp(kD[j].n, "s8") == 0) { int8_t x = 3; memcpy(sb, &x, 1); } else if (strcmp(kD[j].n, "u8") == 0) { uint8_t x = 3; memcpy(sb, &x, 1); } else if (strcmp(kD[j].n, "s32") == 0) { int32_t x = 3; memcpy(sb, &x, 4); } else if (strcmp(kD[j].n, "s64") == 0) { int64_t x = 3; memcpy(sb, &x, 8); } aclTensor* self = aclCreateTensor(sh.data(), 2, kD[i].a, sd.data(), 0, ACL_FORMAT_ND, sh.data(), 2, dS); aclTensor* out = aclCreateTensor(sh.data(), 2, kD[i].a, sd.data(), 0, ACL_FORMAT_ND, sh.data(), 2, dO); aclScalar* other = aclCreateScalar(sb, kD[j].a); uint64_t ws = 0; aclOpExecutor* ex = nullptr; aclnnStatus r1 = aclnnFmodScalarGetWorkspaceSize(self, other, out, &ws, &ex); printf("self=%-4s other=%-4s r1=%d\n", kD[i].n, kD[j].n, (int)r1); aclDestroyTensor(self); aclDestroyTensor(out); aclDestroyScalar(other); aclrtFree(dS); aclrtFree(dO); } } fflush(stdout); _exit(0); }关键两行输出(Ascend 910B3 / CANN 9.2.0):
完整 64 组与两表的比对统计:
四、预期结果 (可选)
self=int8, other(aclScalar)=uint8应按表2 推导为int8并正常执行(返回 0);self=uint8, other=int8应推导为uint8并返回 0。建议修复方向(与同仓
aclnn_remainder.cpp对齐,math/mod/op_api/aclnn_fmod_scalar.cpp:130-139):static DataType PromoteTypeScalar(const op::DataType selfDtype, const op::DataType otherDtype) { if (IsRegBase()) { auto otherDefaultDtype = GetScalarDefaultDtype(otherDtype); return CombineCategoriesWithComplex(selfDtype, otherDefaultDtype); } // Tensor-Scalar 场景:照 TensorScalar 推导表(表2)语义 if (IsFloatingType(selfDtype)) { return selfDtype; } if (IsFloatingType(otherDtype) || selfDtype == op::DataType::DT_BOOL) { return op::PromoteType(selfDtype, otherDtype); } return selfDtype; }同时建议把
CheckPromoteType拆成CheckPromoteTypeTensorScalar/ 复用现有实现,避免 Tensor-Tensor 与 Tensor-Scalar 两条语义混用。💡 备注(选填)
为什么这不是「能力不支持」而是「选错推导表」
int8与uint8各自都在 支持列表内(aclnn_fmod_scalar.cpp:36-38);被拒的不是输入 dtype,而是推导出的 INT16。aclnnRemainder(aclnn_remainder.cpp:133-148)对同样输入返回selfDtype,即s8/u8,与表2 一致 —— 说明本仓已有正确写法可参照。aclnnFmodTensor(真正的 Tensor-Tensor 接口,aclnn_fmod_tensor.cpp:77,144)用op::PromoteType是正确的;本单指出的是FmodScalar照抄了 Tensor 版推导。与已有 issue 的关系(查重说明)
已翻查
cann/ops-math全部 1200 条 issue:aclnnFmodTensor/aclnnFmodScalar头文件公式用 floor 与资料 trunc 不符)—— 那条是头文件公式口径,本单是 dtype 推导表选择;aclnnInplaceRemainderTensorScalar大数值域取模塌成 0)—— 那条是数值精度,本单是入参校验阶段被拒。本次挖掘已核对一致的项(同批排查,供参考排除范围):8 种 dtype 单 dtype 路径全部可用且数值正确;rank 0-8 边界正确;
outshape 不一致正确返回 161002;self不在支持面(int16/bool/complex64)正确返回 161002;空 tensor 返回 0;NaN/±inf/除零特殊值符合 IEEE-754;精度(336 组)+ 正常场景 fuzz(1000 组)+ 多线程并发(4 线程 × 100 次)全部 0 失败。