2. 数值加速对象与校验边界梳理
在开始任何精度一致性校验之前,必须先回答三个问题:加速的是什么、它吃什么吐什么、哪些环节对精度最敏感。如果跳过这一步直接跑对比测试,往往会得到"整体误差 1e-6"这类看似合格却无法定位问题的结论。本节的任务是把一个待加速的数值计算模块拆解成可校验的边界对象,为后续的误差度量与判定建立基准。
一、识别待加速计算核心
数值加速通常不是对整个程序做替换,而是针对其中的计算热点。识别核心时,建议同时看三个维度:运行时间占比、调用频次、以及是否具备可并行或可降精度的结构。例如一个典型的科学计算流程中,时间可能分布为:稀疏矩阵向量乘 62%、预条件子构造 18%、残差与收敛判断 12%、I/O 与其它 8%。那么加速对象应锁定在 SpMV 及其预条件应用上,而不是把整个求解器当作一个黑盒。
确定核心后,要明确它的算法语义:是确定性求和、迭代逼近,还是带随机采样的估计?语义不同,精度校验的策略完全不同。确定性求和的误差主要来自浮点结合律改变与累加顺序;迭代逼近的误差还叠加了收敛路径差异;随机方法则必须固定随机种子或改用统计意义上的分布对比。
二、输入输出契约的显式化
所谓输入输出契约,是指计算核心对外承诺的数据类型、形状、取值范围、精度要求与副作用。很多精度问题源于契约模糊:调用方以为输入是双精度,实际传入了单精度数组;或者核心内部把索引从 64 位截断为 32 位,在大规模数据下产生静默错误。
建议为每个待加速核心建立一份契约清单,至少覆盖:输入张量的 dtype 与 layout(行主序/列主序)、是否允许原地修改、输出缓冲区是否预分配、异常输入(NaN、Inf、零长度)的约定行为、以及是否存在全局状态依赖(如线程数影响归约顺序)。下面的图示把本节的核心对象、契约接口与精度敏感路径组织在一起,便于对照梳理。
三、精度敏感路径的定位方法
敏感路径不能靠猜,而要靠受控扰动实验。具体做法是:保持输入不变,只改变一个可能影响精度的因素,观察输出误差的变化幅度。例如把并行线程数从 1 调到 8,若误差从 1e-15 跳到 1e-9,说明归约顺序是主要敏感点;若误差几乎不变,则敏感点可能在别处。类似地,可以单独测试"仅把累加器从 fp64 换成 fp32"、"仅改变分块大小"、"仅启用快速数学函数"等变量。
实践中常见的敏感路径有四类。第一类是长归约:当求和项数达到 10⁶ 量级且数值动态范围大时,fp32 累加的误差可能达到 1e-3 量级,必须使用 Kahan 补偿或分块累加。第二类是中间量降精度:输入是 fp64 但中间结果存为 fp32,会引入不可逆的舍入。第三类是索引与边界:32 位索引在 nnz 超过 2³¹ 时溢出,这类错误不表现为小误差而是灾难性错误。第四类是算法近似:用采样或截断替代精确计算,误差性质从舍入误差变为方法误差,需要单独设定阈值。
四、不可接受偏差范围的设定
偏差阈值不能拍脑袋,应结合下游用途反推。如果加速结果仅用于可视化,1e-3 的相对误差可能完全可接受;如果用于迭代求解器的残差计算,误差会被放大并影响收敛判定,阈值可能需要收紧到 1e-8。设定时建议同时给出三个层次的判据:逐元素相对误差、向量范数相对误差、以及结构性判据(如是否出现 NaN/Inf、符号是否翻转、稀疏模式是否改变)。
下面给出一份可直接套用的校验边界记录表,字段按本节梳理的逻辑组织,填入具体核心后即可作为后续测试的基准文档。
| 字段 | 填写内容(示例) | 说明 |
|---|---|---|
| 加速核心 | CSR 格式 SpMV | 明确算法与数据格式 |
| 输入契约 | fp64,行主序,n=10⁵,nnz≈10⁷ | dtype、layout、规模 |
| 输出契约 | fp64 向量,长度 n,新建缓冲区 | dtype、形状、内存归属 |
| 敏感路径 | 并行归约顺序、fp32 累加器 | 按影响排序 |
| 参考基准 | 串行 fp64 实现,固定求和顺序 | 基准必须可复现 |
| 误差阈值 | 相对误差 ≤ 1e-10,无 NaN/Inf | 由下游用途反推 |
| 测试输入集 | 均匀随机、对角占优、病态矩阵各一组 | 覆盖不同数值特性 |
完成上述梳理后,你应当得到一份明确的边界文档:核心是什么、契约有哪些、敏感点在哪里、阈值是多少、基准怎么来。这份文档是后续误差度量、测试用例设计和回归判定的共同依据,也是当加速结果出现异常时快速定位问题的第一手资料。