您好,感谢反馈!您提供的 x/1 用例反映了整数业务对精度的要求。这里需要分别看输入/输出类型支持、除法计算类型和舍入语义。
问题分析
本次核对的是 cann/ops-math 的 b19eaee4f58e5e1e3cb3b2ecbfed2d02c11c820a,不是报告中的 efff3523,以下为这一提交的代码结论,尚不能替代对 CANN 9.2.0 / 910B2 实测结果的确认。
在该提交中,Div 的非混合类型路径对 A2 的两路 INT64 输入仍推导为 FLOAT,随后先 Cast 两个输入、调用 RealDiv,再将结果 Cast 到指定的 out 类型。输入在转 FLOAT 时丢失的信息,最后转回 INT64 无法恢复,因此能够解释您列出的 4294967297/1 这类现象。A2 的两路 INT32 输入已有保留 INT32 的分支,不能再概括为所有整数输入都先转 FLOAT;但 RealDiv 的 mode=0 路径仍分配 FLOAT 结果,再转回 INT32,同样不能据此保证大整数商精确。
FP32 可精确表示所有绝对值不超过 2^24 的整数;超过后只可表示其中一部分整数,并非每个超过该值的输入都会出错,也不能由此给出所有除法结果都精确的保证。aclnnInplaceDiv 对这类整数输入复用 Div 路径,结果写回 selfRef;它还要求广播后的 shape 与 selfRef 一致,并满足输出类型可转换条件,原地计算本身不会增加精度。
Div 文档列出 INT32/INT64,并要求满足类型推导及输出转换规则,但没有对 A2 说明上述整数精度边界。文档中“整数推导为 FLOAT、禁止整数输出”的说明限定于 Ascend 950,不能套用于 A2。类型表表示接口接受这些类型,单凭类型表和除法公式还不能认定接口承诺 C/C++ 的精确截断整除;不过,A2 的计算精度和结果转换行为确实需要更清楚的说明。
处理方向
您期望的 C/C++ 截断语义,更对应 aclnnDivMod/aclnnInplaceDivMod 的 mode=1:mode=0 不执行舍入,mode=1 向零舍入,mode=2 向下舍入。这里仍需区分“舍入方式”与“整型全范围精度”:当前 A2 的 INT64 trunc 路径也会先转 FLOAT,因此不能把切换 mode=1 当作您这些 INT64 大数用例的已验证解决方案。
一个可行的优化方向是在明确承诺精确截断整除的接口/模式中保留整数计算路径,避免中间经过 FP32,并保留混合类型推导、广播及原地写回约束,同时明确除零与最小负整数除以 -1 的边界。仅将 FP32 改为 FP64 也不能覆盖 INT64 全范围;FP64 只能保证不超过 2^53 的整数均可精确表示。若调整现有无 mode 的 Div,还需评估浮点商语义及既有调用的兼容影响,不能只按输出是整数就直接改变整个接口的计算语义。
另一项可行方向是在 A2 文档中明确实际计算类型、结果转换及大整数精度限制,并加入 x/1 的示例;文档澄清有助于选择接口,但不等于已经满足业务的精确整除需求。我们会继续确认该精度诉求与接口契约的关系,评估整数路径和文档说明的适用范围。


暂时先补充资料,写明精度边界(|值| > 2^24 结果不可预期),并明确 x/1 也可能不精确


问题描述
aclnnDiv(以及共用同一实现路径的aclnnInplaceDiv)在 Atlas A2(910B2,CANN 9.2.0) 上,self/other/out都是 INT32 或 INT64 时,结果按 FP32 计算后再截断,只要输入超过 2^24 就会错——连x / 1都返回错值。(实测值就是 FP32 舍入后的结果,例如
4294967297在 FP32 里就是4294967296。)定位(master
efff3523)math/div/op_api/aclnn_div.cpp:op::DataType PromoteIntegerInputsToFloat(const op::DataType input) { if (IsIntegralType(input)) { return op::DataType::DT_FLOAT; // ← int32/int64 一律提升到 FP32 } return input; }FP32 只有 24 位有效位,所以任何
|值| > 2^24的整型输入都会丢位;INT64 场景误差可达 1e8 量级。影响
math/div/docs/aclnnDiv&aclnnInplaceDiv.md)给出的公式是out_i = input_i / other_i,参数表self/other/out都列了 INT32/INT64,没有任何整型精度损失的说明(对比aclnnAdds文档会明确写「int32/int64 的 other 大于 2^24 时可能存在精度损失」)。x / 1都不等于x这一点最容易用来判定。环境信息
重现步骤
repro_div_int.cpp;source ${ASCEND_HOME_PATH}/set_env.sh;g++ -O2 repro_div_int.cpp -o repro_div_int \ -I${ASCEND_HOME_PATH}/include -L${ASCEND_HOME_PATH}/lib64 \ -lascendcl -lnnopbase -lopapi ./repro_div_int # aclnnDiv ./repro_div_int inplace # aclnnInplaceDiv(同样错)预期结果
INT32/INT64的除法结果等于 C/C++ 的截断除法(a / b):1073741825 / 1 == 1073741825、4294967297 / 1 == 4294967297;2147483647 / 2 == 1073741823、2000000001 / 3 == 666666667;1000000000000000001 / 3 == 333333333333333333。建议二选一:
|值| > 2^24结果不可预期),并明确x/1也可能不精确——但这与现有参数表把 INT64 列为支持相矛盾。复现脚本 repro_div_int.cpp(仅依赖 CANN)
/** * aclnnDiv / aclnnInplaceDiv 整型除法精度复现(A2 910B2 / CANN 9.2.0) * 编译: g++ -O2 repro_div_int.cpp -o repro_div_int \ * -I${ASCEND_HOME_PATH}/include -L${ASCEND_HOME_PATH}/lib64 -lascendcl -lnnopbase -lopapi * 运行: ./repro_div_int [inplace] */ #include <cstdio> #include <cstring> #include <cstdint> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_div.h" #define CK(x) do { auto r_ = (x); if (r_ != 0) { printf("[FAIL] %s rc=%d\n", #x, (int)r_); return 1; } } while (0) static aclTensor* mk(const std::vector<int64_t>& shape, void* dev, aclDataType dt) { std::vector<int64_t> st(shape.size(), 1); for (int64_t i = (int64_t)shape.size() - 2; i >= 0; --i) st[i] = shape[i + 1] * st[i + 1]; return aclCreateTensor(shape.data(), shape.size(), dt, st.data(), 0, ACL_FORMAT_ND, shape.data(), shape.size(), dev); } static aclrtStream g_stream; static bool run_div(aclDataType dt, void* d_s, void* d_o, void* d_out, int64_t n, void* got, bool inplace) { std::vector<int64_t> shape = {n}; aclTensor* ts = mk(shape, d_s, dt); aclTensor* to = mk(shape, d_o, dt); aclTensor* tout = mk(shape, d_out, dt); uint64_t ws = 0; aclOpExecutor* ex = nullptr; aclnnStatus st = inplace ? aclnnInplaceDivGetWorkspaceSize(ts, to, &ws, &ex) : aclnnDivGetWorkspaceSize(ts, to, tout, &ws, &ex); if (st != 0) { printf(" 第一段 rc=%d\n", (int)st); aclDestroyTensor(ts); aclDestroyTensor(to); aclDestroyTensor(tout); return false; } void* wsp = nullptr; if (ws > 0) CK(aclrtMalloc(&wsp, ws, ACL_MEM_MALLOC_HUGE_FIRST)); st = inplace ? aclnnInplaceDiv(wsp, ws, ex, g_stream) : aclnnDiv(wsp, ws, ex, g_stream); aclrtSynchronizeStream(g_stream); aclrtMemcpy(got, n * (dt == ACL_INT64 ? 8 : 4), inplace ? d_s : d_out, n * (dt == ACL_INT64 ? 8 : 4), ACL_MEMCPY_DEVICE_TO_HOST); if (wsp) aclrtFree(wsp); aclDestroyTensor(ts); aclDestroyTensor(to); aclDestroyTensor(tout); return st == 0; } int main(int argc, char** argv) { bool inplace = argc > 1 && strcmp(argv[1], "inplace") == 0; CK(aclInit(nullptr)); CK(aclrtSetDevice(0)); CK(aclrtCreateStream(&g_stream)); const int64_t N = 8; int32_t s32[N] = {2147483647, 2147483647, 1073741825, 2000000001, -2147483647, 123456789, 1000000007, 16777217}; int32_t o32[N] = {1, 2, 1, 3, 2, 7, 1000000007, 2}; int64_t s64[N] = {4294967297LL, 1152921504606846977LL, 1000000000000000001LL, 1234567890123456789LL, 9007199254740993LL, 1000000000000001LL, 17179869184LL, -4611686018427387904LL}; int64_t o64[N] = {1, 1, 3, 7, 1, 3, 1000000000LL, 2}; void *ds = nullptr, *do_ = nullptr, *dout = nullptr; const size_t b32 = N * 4, b64 = N * 8; CK(aclrtMalloc(&ds, b64, ACL_MEM_MALLOC_HUGE_FIRST)); CK(aclrtMalloc(&do_, b64, ACL_MEM_MALLOC_HUGE_FIRST)); CK(aclrtMalloc(&dout, b64, ACL_MEM_MALLOC_HUGE_FIRST)); printf("==== %s ====\n", inplace ? "aclnnInplaceDiv" : "aclnnDiv"); int32_t got32[N] = {0}; CK(aclrtMemcpy(ds, b32, s32, b32, ACL_MEMCPY_HOST_TO_DEVICE)); CK(aclrtMemcpy(do_, b32, o32, b32, ACL_MEMCPY_HOST_TO_DEVICE)); if (run_div(ACL_INT32, ds, do_, dout, N, got32, inplace)) { printf("INT32:\n"); for (int i = 0; i < N; ++i) { int64_t exp = (int64_t)s32[i] / (int64_t)o32[i]; printf(" %12d / %-12d 实测=%-12d 期望=%-12lld %s\n", s32[i], o32[i], got32[i], (long long)exp, (int64_t)got32[i] == exp ? "OK" : "*** 错"); } } int64_t got64[N] = {0}; CK(aclrtMemcpy(ds, b64, s64, b64, ACL_MEMCPY_HOST_TO_DEVICE)); CK(aclrtMemcpy(do_, b64, o64, b64, ACL_MEMCPY_HOST_TO_DEVICE)); if (run_div(ACL_INT64, ds, do_, dout, N, got64, inplace)) { printf("INT64:\n"); for (int i = 0; i < N; ++i) { int64_t exp = s64[i] / o64[i]; printf(" %21lld / %-10lld 实测=%-21lld 期望=%-21lld %s\n", (long long)s64[i], (long long)o64[i], (long long)got64[i], (long long)exp, got64[i] == exp ? "OK" : "*** 错"); } } aclrtFree(ds); aclrtFree(do_); aclrtFree(dout); aclrtDestroyStream(g_stream); aclrtResetDevice(0); aclFinalize(); return 0; }