Closed
[Bug-Report|缺陷反馈]: aclnnInplaceLog2 COMPLEX128 输入精度仅 ~28 bit(log2(2+0i) 实测 0.999999997,相对误差恒 2.75e-9) #4120
liuhanwen8Add on  14 days agoClosed at  7 days ago
liuhanwen8
liuhanwen8
14 days ago Create

现象

aclnnInplaceLog2 的 COMPLEX128 路径只在 float32 级精度上计算:实测相对误差恒为 ~2.75e-9(≈28 bit 有效位),而 fp64 目标应达 ~1e-16。100% 复现。

输入 期望(fp64 精确) 实测 绝对误差 折合 fp64 ulp
2+0i 1 0.99999999725216471 2.75e-09 12,375,150
10+0i 3.3219280948873626 3.3219280857592515 9.13e-09 20,554,679
1e8+0i 26.575424759098901 26.575424686074012 7.30e-08 20,554,679

同机、同调用方式的对照:本接口 FLOAT64 逐位相等(max abs_err = 0);aclnnSqrt+COMPLEX128 与 double 参考逐位一致;CPU torch.log2(complex128) 逐位一致;本接口 COMPLEX64 相对误差 ~1e-7(符合 fp32,正常)。只有 COMPLEX128 掉到 28 bit。

批量验证:237 条 COMPLEX128 用例里 231 条可给出相对误差,231/231 都等于 2.7478e-9——算法定标问题,非个别值抖动。

期望

资料参数表把 COMPLEX128 列为 selfRef 支持类型;它即 64 位浮点复数,log2 应以 double 精度计算 —— 同接口 FLOAT64 与同机 aclnnSqrt 都做到了逐位一致。

环境

  • 硬件:Atlas A2 训练系列(Ascend 910B2);软件:CANN 9.2.0;代码:cann/ops-math master 21848df4(本机工作副本 5eaca2328,math/log)
  • 调用方式:aclnn 两段式 C 直调(aclnnInplaceLog2GetWorkspaceSize + aclnnInplaceLog2),不经 torch
  • 编译:g++ -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

复现步骤

  1. 把下面代码块存成 repro.cpp,用上面那条 g++ 命令编译($ASCEND_HOME 指向本机 CANN 安装目录)。
  2. ./a.out。
  3. 看 [缺陷组 COMPLEX128] max|abs_err|:应为 0,实测 7.30e-08。
  4. 看 [对照组 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]
  in=2     expect=1 actual=0.99999999725216471 abs_err=2.75e-09
  in=1e+08 expect=26.575424759098901 actual=26.575424686074012 abs_err=7.3e-08
[缺陷组 COMPLEX128] max|abs_err| = 7.302489e-08  (ret=0/0)
[对照组 FLOAT64] max|abs_err| = 0.000000e+00  (ret=0/0)

影响

  1. COMPLEX128 做 log2 的链路(复对数、频谱/相位分析、复域归一化)带入 1e-9 级误差,比应有的 1e-16 差 7 个数量级,再进迭代/求导会放大成可见偏差。
  2. 误差是系统性常数偏差,多次平均消不掉;用户按资料选 COMPLEX128 正是要高精度,实际被静默降精度。

建议

  1. 核查 COMPLEX128 分支:从特征看更像在 float32 上求 log/log2 再升到 double;若 A2 上确为 fp32 实现,请改为 double 路径。
  2. 若短期不能修,请在资料「约束说明」写明 COMPLEX128 的实际精度等级;并补 UT/ST:log2(2+0i) == 1、log2(10+0i) 与 fp64 参考在 1e-14 内。
likedislike
liuhanwen8liuhanwen8
14 days ago Added label: bug-report
xujiachen8Collaborators
14 days ago Comments:

您好,感谢提供稳定的误差特征和 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 复数分支的精度处理及适用边界。

likedislike
Xxujiachen8Collaborators
14 days ago Assign liu-wei as responsible person
Xxujiachen8Collaborators
14 days ago Added label: pending-confirmation
陈思陈思Collaborators
13 days ago Removed label: pending-confirmation
陈思陈思Collaborators
12 days ago Assign chensi79 as responsible person
陈思陈思Collaborators
12 days ago Removed responsible person liu-wei
陈思陈思Collaborators
12 days ago Removed responsible person chensi79
陈思陈思Collaborators
12 days ago Assign chen_0715 as responsible person
陈思陈思Collaborators
12 days ago Added label: pending-confirmation
CchenyifengCollaborators
12 days ago Assign gcw_SUaZx3UQ as responsible person
CchenyifengCollaborators
12 days ago Removed responsible person chen_0715
Zzhangqijia1Collaborators
8 days ago Associated pull request
CANN-robotCANN-robotCollaborators
7 days ago Close issue
CANN-robotCANN-robotCollaborators
7 days ago Added label: resolved