Closed
[Bug-Report|缺陷反馈]: aclnnLinalgQr fp32 输入量级 ≤1e-20 时 Q 退化为单位阵、R 退化为 triu(A)(rel_residual=0.68,ret=0) #4329
liuhanwen8Add on  14 days agoClosed at  4 days ago
liuhanwen8
liuhanwen8
14 days ago Create

现象

aclnnLinalgQr 在 fp32 输入量级很小时,返回 0 但结果不满足 A = Q·R:Q 退化为单位阵(前 k 行)、R 等于 A 的上三角部分。

  • scale=1e-18 / 1e-19:相对残差 ||QR-A||/||A|| 为 1.2e-07,正常;
  • scale=1e-20 及以下:Q[0]=1、Q[5]=1(单位阵),残差 6.831e-01;
  • 两段接口均返回 0,无任何日志或告警;
  • scale=1e-20 只是 fp32 正规数区间内的普通量级(fp32 最小正规数约 1.2e-38),不是次正规。

实测输出:

scale=1e-18    ret_gws=0 ret_exec=0 Q[0]=-0.294884 Q[5]=2.89798e-08 rel_residual=1.232e-07
scale=1e-19    ret_gws=0 ret_exec=0 Q[0]=-0.294884 Q[5]=6.12079e-08 rel_residual=1.219e-07
scale=1e-20    ret_gws=0 ret_exec=0 Q[0]=1 Q[5]=1 rel_residual=6.831e-01
scale=1e-30    ret_gws=0 ret_exec=0 Q[0]=1 Q[5]=1 rel_residual=6.831e-01

阈值二分(同一数据集,只改整体量级):|A|max=2.575e-19 时残差 5.6e-08;|A|max=1.624e-19 起残差跳到 4.9e-01。

期望

fp32 正规数区间内,任何非零量级都应满足 A≈QR、Q^H Q≈I、R 为上三角,相对残差应在 1e-6 量级(与 scale=1e-18/1e-19 一致)。若实现确实要按「元素量级过小视为零矩阵」处理,应返回明确的错误码或告警,不能返回 0 且给出不满足 A=QR 的结果。

环境

  • 硬件:Atlas A2 训练服务器(Ascend 910B2)
  • CANN:9.2.0(/usr/local/Ascend/cann-9.2.0),driver 26.1.1
  • 仓与提交:cann/ops-math,1d0e7bab
  • 编译器:g++ 12(aarch64),-std=c++17

复现步骤

  1. 把下方脚本保存为 repro_tiny.cpp。
  2. 编译:g++ -O2 -std=c++17 repro_tiny.cpp -o repro_tiny -I$ASCEND_HOME_PATH/include -L$ASCEND_HOME_PATH/lib64 -lascendcl -lnnopbase -lopapi_math -lopapi。
  3. 运行:./repro_tiny。
  4. 对比 scale=1e-19 与 scale=1e-20 两行:后者 Q[0]=Q[5]=1,残差 0.68。

复现脚本

// aclnnLinalgQr:fp32 输入量级对 A≈QR 的影响(scale 从 1e-18 到 1e-30)
#include <cmath>
#include <cstdio>
#include <vector>
#include "acl/acl.h"
#include "aclnnop/aclnn_linalg_qr.h"

static aclTensor *Mk(std::vector<float> &d, std::vector<int64_t> s, void **p) {
  size_t n = 1;
  for (auto v : s) n *= (size_t)v;
  aclrtMalloc(p, n * sizeof(float), ACL_MEM_MALLOC_HUGE_FIRST);
  aclrtMemcpy(*p, n * sizeof(float), d.data(), n * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE);
  std::vector<int64_t> st(s.size(), 1);
  for (int i = (int)s.size() - 2; i >= 0; --i) st[i] = s[i + 1] * st[i + 1];
  return aclCreateTensor(s.data(), s.size(), ACL_FLOAT, st.data(), 0, ACL_FORMAT_ND,
                         s.data(), s.size(), *p);
}

static void Check(float scale, aclrtStream stream) {
  std::vector<float> hd(16), hz(16, 0.0f);
  for (int i = 0; i < 16; i++) hd[i] = scale * (float)((i * 7 + 3) % 11 - 5);
  std::vector<int64_t> s = {4, 4};
  void *ps = nullptr, *pq = nullptr, *pr = nullptr;
  aclTensor *self = Mk(hd, s, &ps), *Q = Mk(hz, s, &pq), *R = Mk(hz, s, &pr);
  uint64_t ws = 0;
  aclOpExecutor *ex = nullptr;
  auto ret1 = aclnnLinalgQrGetWorkspaceSize(self, 0, Q, R, &ws, &ex);
  int ret2 = ret1 == 0 ? aclnnLinalgQr(nullptr, ws, ex, stream) : -1;
  if (ret2 == 0) aclrtSynchronizeStream(stream);
  std::vector<float> hq(16, 0), hr(16, 0);
  aclrtMemcpy(hq.data(), 64, pq, 64, ACL_MEMCPY_DEVICE_TO_HOST);
  aclrtMemcpy(hr.data(), 64, pr, 64, ACL_MEMCPY_DEVICE_TO_HOST);
  double num = 0, den = 0;
  for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 4; j++) {
      double v = 0;
      for (int t = 0; t < 4; t++) v += (double)hq[i * 4 + t] * hr[t * 4 + j];
      num += (v - (double)hd[i * 4 + j]) * (v - (double)hd[i * 4 + j]);
      den += (double)hd[i * 4 + j] * (double)hd[i * 4 + j];
    }
  }
  printf("scale=%-8.0e ret_gws=%d ret_exec=%d Q[0]=%.6g Q[5]=%.6g rel_residual=%.3e\n",
         scale, ret1, ret2, hq[0], hq[5], std::sqrt(num) / (std::sqrt(den) + 1e-300));
  aclDestroyTensor(self); aclDestroyTensor(Q); aclDestroyTensor(R);
  aclrtFree(ps); aclrtFree(pq); aclrtFree(pr);
}

int main() {
  aclInit(nullptr);
  aclrtSetDevice(0);
  aclrtStream stream;
  aclrtCreateStream(&stream);
  for (float sc : {1e-18f, 1e-19f, 1e-20f, 1e-30f}) Check(sc, stream);
  aclrtDestroyStream(stream);
  aclrtResetDevice(0);
  aclFinalize();
  return 0;
}

影响

  • 训练中梯度/激活被归一化到 1e-20 量级(如深层残差、极小 learning rate 累积)时,linalg_qr 会静默返回「Q=I、R=triu(A)」的错误结果,下游正交化、正交正则、QR 求逆等逻辑全部失真,且不会报错;
  • 该错误仅由输入量级触发,与 shape、mode(0/1/2 均可复现)无关,排查时容易误判成「模型问题」;
  • fp64 通路在同量级(1e-150~1e-200)残差为 0,未受影响,说明失效点在 fp32 计算路径的判零阈值。

建议

  1. 在 AICPU Qr kernel 里改用相对判零(以本次输入的最大元或列范数为基准),或先把矩阵按最大元归一到 O(1) 再分解、最后按同一比例回写 R;
  2. 若保持绝对阈值,请在资料「约束说明」里写明「元素量级低于 X 时结果不保证」,并在低于该量级时返回非 0 错误码,不要返回 0;
  3. 建议补一条 UT:self = A * 1e-20(4×4,固定整数图案),断言 ||QR - A||/||A|| < 1e-5。
likedislike
liuhanwen8liuhanwen8
14 days ago Added label: bug-report
陈思
陈思Collaborators
14 days ago Comments:

您好,感谢反馈!按您提供的结果,非零矩阵得到 Q=I、R=triu(A) 且相对残差约为 0.68,已经偏离接口约定的 A≈QR;当前文档也没有将该输入量级列为限制。

问题分析

我们核对了 cann/ops-math 提交 212ce9d5460b1be26cfbee481aff1acbf5cc5c62,其相关接口实现与您报告的 1d0e7bab 一致。输入处理会保留 FP32 类型,经连续化后调用 AICPU Qr,接口层没有按输入幅度缩放,也没有计算完成后的残差检查。

1e-20 本身属于 FP32 正规数,但其平方约为 1e-40,已进入次正规数范围。因此需要继续检查底层范数归约、判零和反射向量构造,区分平方中间量下溢、次正规数处理与绝对阈值的影响。当前仓库的 Qr 目录仅包含接口,尚不能从这些代码确认具体判零条件。

处理方向

一个可行的方向是对底层范数及反射计算采用稳定缩放,避免直接对极小数据平方;也可以评估先按每个矩阵的非零有限幅度 s 分解 A/s,再用 s 恢复 R。这类调整需保留零矩阵、秩亏输入、各 mode 输出形状及复数共轭关系,不能只降低一个阈值或统一拒绝小输入。

您提供的固定图案和跨量级对比适合作为回归用例,验证时应同时检查稳定计算的相对重构残差与正交误差。复核还需按返回的 workspace 大小分配并传入有效地址、检查同步返回码,排除脚本中固定传入 nullptr 对执行的影响。我们会继续沿 AICPU 的数值计算路径确认退化原因,评估缩放或稳定范数方案。

likedislike
陈思陈思Collaborators
14 days ago Assign chen_0715 as responsible person
陈思陈思Collaborators
14 days ago Added label: pending-confirmation
陈思陈思Collaborators
12 days ago Removed label: pending-confirmation
Ding_JingDing_JingCollaborators
12 days ago Assign Ding_Jing as responsible person
Ding_JingDing_JingCollaborators
12 days ago Removed responsible person Ding_Jing
Ding_JingDing_JingCollaborators
12 days ago Assign Ding_Jing as responsible person
CchenyifengCollaborators
12 days ago Removed responsible person chen_0715
CANN-robotCANN-robotCollaborators
4 days ago Close issue
CANN-robotCANN-robotCollaborators
4 days ago changed custom state from 进行中 to 已完成
CANN-robotCANN-robotCollaborators
4 days ago Added label: resolved