float32转float16对np.isclose的atol影响及模型层输出对比问题
问题描述
我正在对比TensorFlow和自定义轻量库训练的两个机器学习模型的各层输出,二者在单元测试中表现一致,但float32与float16训练模式下的测试差异显著——尽管最终预测性能相近:
- 使用float32权重训练时,用
np.isclose(atol=1e-08)对比层输出,误差极小; - 切换到float16权重训练后,即便将
atol调至1e-04,误差仍大幅增加。
我想确认:仅调大atol是否为正确的解决思路?
复现代码
以Dense层为例对比Keras与自定义实现的输出:float16模式下,即便按位宽比例调整容差,错误率仍达60%以上,而float32时仅约2%。代码如下:
import keras.backend import numpy as np import keras def custom_dense_layer(_input_data, weights, biases) -> np.ndarray: _layer_output = np.matmul(_input_data, weights) + biases # weight matrix = [n1_weights, n2_weights, ..]. Thus computing (X.W + B) instead of (transpose(W).transpose(X) + transpose(B)) return _layer_output if __name__ == '__main__' : bit_precision=16 # used to set the computation and weights / biases dtypes, here the goal is to compare between 16 and 32 keras.backend.set_floatx(f'float{bit_precision}') # set computation dtype weights=np.random.randn(16,16).astype(f'float{bit_precision}') # generate random weights biases=np.random.randn(16).astype(f'float{bit_precision}') # generate random biases input = keras.Input(shape=(16, ),dtype=f'float{bit_precision}') # set input shape input_data = np.random.randn(1000,16).astype(f'float{bit_precision}') # generate random data keras_dense = keras.layers.Dense(units=16) # generate keras dense layer keras_dense(input) keras_dense.set_weights([weights,biases]) # set the weights and biases custom_op = custom_dense_layer(_input_data=input_data, weights=weights, biases=biases) # custom dense layer prediction calculation keras_op = np.array(keras_dense(input_data)) # keras prediction calculation isclose = np.isclose(keras_op, custom_op,atol=10**-(bit_precision/4),rtol=(2**-(bit_precision/2))).all(axis=1) # checking results similarity levels failed_points = np.where(~np.isclose(keras_op, custom_op,atol=10**-(bit_precision/4),rtol=(2**-(bit_precision/2))).all(axis=1))[0]# checking results that are not close enough perc_failed = round(100 * failed_points.size / isclose.size, 3) # "failed" results percentage print(perc_failed)
分析与解决方案
1. 仅调大atol不是最优方案
float16下误差激增的根本原因是计算路径的数值差异,而非单纯的精度不足。Keras/TensorFlow的float16计算包含大量优化逻辑:
- 内部可能启用混合精度(部分计算转float32执行)避免溢出;
- 矩阵乘法、加法依赖硬件加速指令(如CUDA Tensor Core),数值舍入方式和纯Numpy的
np.matmul不同; - 部分操作会做精度补偿(比如bias加法前的类型转换逻辑)。
只调大容差相当于掩盖差异,无法确认自定义实现的数值正确性,后续复杂模型(如带激活、归一化的层)可能出现更严重的偏移。
2. 正确的排查与对齐步骤
- 对齐计算路径的精度逻辑:
检查Keras是否开启混合精度(默认可能自动启用),自定义实现要同步该逻辑——比如矩阵乘法后先转float32加bias再转回float16,或全程保持float16但匹配Keras的舍入模式。 - 排除硬件加速干扰:
若在GPU上运行,可强制切换到CPU计算(tf.config.set_visible_devices([], 'GPU')),对比Numpy和Keras的CPU计算结果,定位是否由硬件指令差异导致。 - 以float32为基准逐步对齐:
先确保float32下两者输出完全一致(atol=1e-8通过),再逐步切换到float16,每一步对比中间结果,定位差异出现的具体环节(是矩阵乘法还是加法?)。 - 基于float16固有精度调整容差:
float16的最小可表示相对误差约为2^-10 ≈ 9.7e-4,合理的rtol应设为1e-3,`atol`设为1e-4(针对绝对值较小的数值),但前提是计算路径已经对齐,否则再大的容差也可能覆盖错误。
3. 单元测试的优化建议
单元测试的核心是验证模型行为一致,而非数值完全相等:
- 对于float16,可采用“相对误差在合理范围内”+“预测结果top-k一致”的组合测试;
- 若自定义库用于部署,可直接对比最终推理的准确率、召回率等业务指标,只要指标一致,层输出的微小差异可接受。
内容的提问来源于stack exchange,提问作者Lucas Barrot
相关产品推荐
相关产品推荐

