您好,感谢提供稳定的误差特征和 FLOAT64 对照!
问题分析
核对 cann/ops-math master b08e885e7fac370203a51904fd0680523b9a9205,Log2 接口通过共用的 Log 算子传入 base=2、scale=1、shift=0。COMPLEX128 保持复数双精度类型,并进入 AICPU 的 LogCompute<std::complex<double>, std::complex<double>>,没有在这一入口统一转换为 FP32。接口调用、类型与后端
可定位的精度损失在复数 Log2 的归一化常量:kLog2Base 是 float,T(std::log(kLog2Base)) 会先用单精度重载求 ln(2),随后才扩展为 complex<double>;分子的 std::log(input[index]) 仍接收 complex<double>。已经舍入的常量在扩展类型后无法恢复精度,因此这条路径会引入近似固定比例的系统偏差。复数 Log2 计算
host 算术模型中,单精度 ln(2) 为 0.69314718246459961,由此得到比例 ln(2)_double / ln(2)_float = 0.99999999725216471,偏差约 -2.7478353e-9;该模型得到的 2、10、1e8 三个结果均与您报告的数值吻合。FLOAT64 则走直接调用 std::log2(double) 的分支,这也解释了它与复数对照的差异。实型与复数分支
以上核查包括固定源码和 host 算术模型;您在 A2 / CANN 9.2.0 上的设备结果沿用报告记录,安装库与所查源码的构建对应关系尚未核实。因此,目前可以将问题具体定位到复数分支的低精度归一化常量,不宜概括为整个复数对数都在 FP32 上计算。
处理方向
一个可行方向是在求 ln(2) 之前,按复数分量的类型选择常量及计算精度,再生成归一化因子;仅在求对数之后转成 complex<double> 无法消除这项误差。调整还需核对 COMPLEX64、实型分支以及复数特殊值和分支语义。原地与非原地接口共用该计算路径,也适合一并核对。
我们会继续确认 AICPU 复数分支的精度处理及适用边界。


现象
aclnnInplaceLog2的COMPLEX128路径只在 float32 级精度上计算:实测相对误差恒为 ~2.75e-9(≈28 bit 有效位),而 fp64 目标应达 ~1e-16。100% 复现。同机、同调用方式的对照:本接口
FLOAT64逐位相等(max abs_err = 0);aclnnSqrt+COMPLEX128与 double 参考逐位一致;CPUtorch.log2(complex128) 逐位一致;本接口COMPLEX64相对误差 ~1e-7(符合 fp32,正常)。只有COMPLEX128掉到 28 bit。批量验证:237 条
COMPLEX128用例里 231 条可给出相对误差,231/231 都等于 2.7478e-9——算法定标问题,非个别值抖动。期望
资料参数表把
COMPLEX128列为selfRef支持类型;它即 64 位浮点复数,log2应以 double 精度计算 —— 同接口FLOAT64与同机aclnnSqrt都做到了逐位一致。环境
cann/ops-mathmaster21848df4(本机工作副本5eaca2328,math/log)aclnnInplaceLog2GetWorkspaceSize+aclnnInplaceLog2),不经 torchg++ -std=c++17 -O2 repro.cpp -I$ASCEND_HOME/include -I$ASCEND_HOME/include/aclnnop -L$ASCEND_HOME/lib64 -L$ASCEND_HOME/runtime/lib64 -lascendcl -lnnopbase -lopapi -lopapi_math复现步骤
repro.cpp,用上面那条g++命令编译($ASCEND_HOME指向本机 CANN 安装目录)。./a.out。[缺陷组 COMPLEX128] max|abs_err|:应为 0,实测 7.30e-08。[对照组 FLOAT64] max|abs_err|:实测 0,证明同接口 fp64 路径无精度损失。复现脚本
#include <acl/acl.h> #include "aclnnop/aclnn_log2.h" #include <cmath> #include <complex> #include <cstdio> #include <vector> static double run(const std::vector<double>& host, aclDataType dt, bool complex_, aclrtStream stream, int* ret1, int* ret2) { size_t elems = complex_ ? host.size() / 2 : host.size(); std::vector<int64_t> shape{(int64_t)elems}; void* dev = nullptr; aclrtMalloc(&dev, host.size() * sizeof(double), ACL_MEM_MALLOC_HUGE_FIRST); aclrtMemcpy(dev, host.size() * sizeof(double), host.data(), host.size() * sizeof(double), ACL_MEMCPY_HOST_TO_DEVICE); aclTensor* t = aclCreateTensor(shape.data(), 1, dt, nullptr, 0, ACL_FORMAT_ND, shape.data(), 1, dev); uint64_t ws = 0; aclOpExecutor* ex = nullptr; *ret1 = (int)aclnnInplaceLog2GetWorkspaceSize(t, &ws, &ex); void* wsp = nullptr; if (*ret1 == 0) { if (ws > 0) aclrtMalloc(&wsp, ws, ACL_MEM_MALLOC_HUGE_FIRST); *ret2 = (int)aclnnInplaceLog2(wsp, ws, ex, stream); if (*ret2 == 0) aclrtSynchronizeStream(stream); } std::vector<double> back(host.size()); aclrtMemcpy(back.data(), host.size() * sizeof(double), dev, host.size() * sizeof(double), ACL_MEMCPY_DEVICE_TO_HOST); double maxerr = 0.0; for (size_t i = 0; i < elems; ++i) { double got = back[complex_ ? 2 * i : i]; double exp; if (complex_) { std::complex<double> z(host[2 * i], host[2 * i + 1]); exp = (std::log(z) / std::log(2.0)).real(); } else { exp = std::log2(host[i]); } double e = std::fabs(got - exp); printf(" in=%-12.6g expect=%.17g actual=%.17g abs_err=%.3g\n", host[complex_ ? 2 * i : i], exp, got, e); if (e > maxerr) maxerr = e; } aclDestroyTensor(t); aclrtFree(dev); if (wsp) aclrtFree(wsp); return maxerr; } int main() { if (aclInit(nullptr) != ACL_SUCCESS) return 2; if (aclrtSetDevice(0) != ACL_SUCCESS) return 2; aclrtStream stream = nullptr; if (aclrtCreateStream(&stream) != ACL_SUCCESS) return 2; int r1 = 0, r2 = 0; printf("[缺陷组 COMPLEX128 log2]\n"); double bad = run({2.0, 0.0, 10.0, 0.0, 1e8, 0.0}, ACL_COMPLEX128, true, stream, &r1, &r2); printf("[缺陷组 COMPLEX128] max|abs_err| = %.6e (ret=%d/%d)\n", bad, r1, r2); printf("[对照组 FLOAT64 log2(2^k)]\n"); double ctl = run({2.0, 4.0, 8.0, 1024.0}, ACL_DOUBLE, false, stream, &r1, &r2); printf("[对照组 FLOAT64] max|abs_err| = %.6e (ret=%d/%d)\n", ctl, r1, r2); aclrtDestroyStream(stream); aclrtResetDevice(0); aclFinalize(); return bad > 0 ? 1 : 0; }实测输出(本机实跑):
影响
COMPLEX128做log2的链路(复对数、频谱/相位分析、复域归一化)带入 1e-9 级误差,比应有的 1e-16 差 7 个数量级,再进迭代/求导会放大成可见偏差。COMPLEX128正是要高精度,实际被静默降精度。建议
COMPLEX128分支:从特征看更像在 float32 上求log/log2再升到 double;若 A2 上确为 fp32 实现,请改为 double 路径。COMPLEX128的实际精度等级;并补 UT/ST:log2(2+0i) == 1、log2(10+0i)与 fp64 参考在 1e-14 内。