Closed
[Bug-Report|缺陷反馈]: aclnnLinalgQr fp32 输入量级 ≤1e-20 时 Q 退化为单位阵、R 退化为 triu(A)(rel_residual=0.68,ret=0) #4329
liuhanwen8Add on 15 days agoClosed at 4 days ago
15 days ago Added label: bug-report
陈思
14 days ago Comments:
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 的数值计算路径确认退化原因,评估缩放或稳定范数方案。


14 days ago Assign chen_0715 as responsible person
14 days ago Added label: pending-confirmation
12 days ago Removed label: pending-confirmation
12 days ago Assign Ding_Jing as responsible person
12 days ago Removed responsible person Ding_Jing
12 days ago Assign Ding_Jing as responsible person
12 days ago Removed responsible person chen_0715
4 days ago Close issue
4 days ago changed custom state from 进行中 to 已完成
4 days ago Added label: resolved
现象
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;scale=1e-20只是 fp32 正规数区间内的普通量级(fp32 最小正规数约1.2e-38),不是次正规。实测输出:
阈值二分(同一数据集,只改整体量级):
|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的结果。环境
/usr/local/Ascend/cann-9.2.0),driver 26.1.1cann/ops-math,1d0e7bab-std=c++17复现步骤
repro_tiny.cpp。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。./repro_tiny。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 求逆等逻辑全部失真,且不会报错;1e-150~1e-200)残差为 0,未受影响,说明失效点在 fp32 计算路径的判零阈值。建议
Qrkernel 里改用相对判零(以本次输入的最大元或列范数为基准),或先把矩阵按最大元归一到 O(1) 再分解、最后按同一比例回写R;self = A * 1e-20(4×4,固定整数图案),断言||QR - A||/||A|| < 1e-5。