第四章:Golden Data生成:如何生成标准参考数据?

做算子精度对齐,说白了就是拿你的计算结果跟“标准答案”去比。那这个“标准答案”从哪来?这就是我们今天要聊的——Golden Data生成。

我个人习惯把Golden Data叫做“金标准”。它必须是可信的、可复现的。你想想看,如果参考数据本身就有问题,那后面的精度对齐就是白忙活。我在项目中遇到过好几次,团队花了两周查精度问题,最后发现是Golden Data生成脚本里有个类型转换的bug……嗯,从那以后我定了个规矩:Golden Data的生成代码必须单独review。

4.1 三种主流生成方式

生成Golden Data,常用的路子有三条。我按推荐程度排个序:

生成方式 适用场景 优点 缺点
随机数 单元测试、边界测试 覆盖范围广,能发现意外问题 不可复现(需固定种子)
固定模式 回归测试、性能基线 完全可复现,便于调试 可能遗漏边界情况
真实数据 端到端验证、上线前测试 最贴近实际场景 数据量大,依赖外部输入

别急着选。我建议你根据算子类型和验证阶段来组合使用。比如我自己做卷积算子对齐时,单元测试用随机数,回归测试用固定模式,上线前再用真实数据跑一遍。

4.2 随机数生成:别让“随机”坑了你

随机数看着简单,其实坑不少。我曾经在生成随机数时没固定种子,结果每次跑出来的Golden Data都不一样,精度对齐根本没法做。

重要提醒: 随机数生成必须固定随机种子!否则你的Golden Data就是“薛定谔的猫”——每次打开都不一样。

正确的做法是这样:

import numpy as np

# 固定随机种子,保证可复现
np.random.seed(42)

def generate_random_golden(shape, dtype=np.float32):
    """生成随机Golden Data"""
    # 使用均匀分布,覆盖正负值
    data = np.random.uniform(-1.0, 1.0, shape).astype(dtype)
    return data

# 使用示例
golden_input = generate_random_golden((32, 64, 224, 224))
golden_weight = generate_random_golden((64, 64, 3, 3))

这里有个细节:我习惯用均匀分布而不是正态分布。为什么?因为均匀分布能保证每个数值区间都有样本,更容易暴露数值精度问题。我在做量化算子对齐时,就靠这个发现了几个数值饱和的bug。

4.3 固定模式:调试时的好帮手

固定模式说白了就是“我知道输入是什么,我也知道输出应该是什么”。这在调试时特别有用。

我个人常用的固定模式有几种:

  • 全零/全一矩阵:最简单的基线,用来验证算子是否正常工作
  • 单位矩阵:适合验证矩阵运算类算子
  • 等差数列:比如1,2,3,4,...,方便人工验算中间结果
  • 特殊数值:比如最大值、最小值、NaN、Inf等边界值
def generate_fixed_golden(mode='identity', shape=(4, 4)):
    """生成固定模式的Golden Data"""
    if mode == 'zeros':
        return np.zeros(shape, dtype=np.float32)
    elif mode == 'ones':
        return np.ones(shape, dtype=np.float32)
    elif mode == 'identity':
        return np.eye(shape[0], dtype=np.float32)
    elif mode == 'range':
        return np.arange(np.prod(shape), dtype=np.float32).reshape(shape)
    elif mode == 'extreme':
        data = np.random.randn(*shape).astype(np.float32)
        data[0, 0] = np.finfo(np.float32).max  # 最大值
        data[0, 1] = np.finfo(np.float32).min  # 最小值
        return data
    else:
        raise ValueError(f"Unknown mode: {mode}")
小技巧: 调试时先用全零矩阵跑一遍。如果全零输入下输出不是零,那你的算子大概率有bug。这招帮我省了不少排查时间。

4.4 真实数据:最接近实战的验证

真实数据是检验算子的最终标准。但这里有个矛盾:真实数据往往很大,而且涉及数据版权问题。

我的做法是:

  1. 裁剪数据:从真实数据集中随机抽取小批量,比如100张图片
  2. 预处理对齐:确保预处理流程(归一化、resize等)与训练时完全一致
  3. 保存为numpy格式:避免重复加载原始数据
def generate_real_golden(data_loader, num_samples=100):
    """从真实数据集中生成Golden Data"""
    golden_data = []
    for i, (inputs, _) in enumerate(data_loader):
        if i >= num_samples:
            break
        # 确保数据在CPU上,转为numpy
        golden_data.append(inputs.cpu().numpy())
    
    # 合并并保存
    golden_array = np.concatenate(golden_data, axis=0)
    np.save('golden_real_data.npy', golden_array)
    return golden_array

这里要注意:真实数据的分布可能跟随机数完全不同。我在做图像算子的精度对齐时,发现随机数测试全过,但真实图片一跑就出问题。后来查出来是某些像素值接近0时,浮点精度损失被放大了。

4.5 知识体系总览

下面这张图是我自己总结的Golden Data生成决策流程,你可以参考:

Golden Data生成决策流程 开始生成Golden Data 验证阶段是什么? 单元测试 回归测试 上线前验证 随机数生成 固定种子 均匀分布 固定模式 全零/全一 特殊数值 真实数据 小批量采样 预处理对齐 输出Golden Data

4.6 实战中的避坑指南

最后,分享几个我踩过的坑:

坑1:数据类型不一致

我曾经用float64生成Golden Data,但算子内部用的是float32。结果精度对不上,查了两天才发现是类型不匹配。记住:Golden Data的数据类型必须跟算子输入完全一致。

坑2:随机种子没固定

这个前面提过,但值得再说一遍。不固定种子,你的Golden Data就是一次性的,没法复现问题。

坑3:真实数据预处理不一致

真实数据在训练时可能做了归一化、数据增强等操作。如果你生成Golden Data时没做同样的预处理,那结果肯定对不上。我建议把预处理逻辑封装成一个函数,Golden Data生成和算子调用都用同一个函数。

嗯,Golden Data生成这块就聊到这。记住一个原则:你的Golden Data必须可信、可复现、可追溯。下次做精度对齐时,先花10分钟把Golden Data生成流程理清楚,后面能省你好几天的时间。

想要更多资料,联系可可