QAT 查表算子调优 02 | 查表算子精度调优

一、前言
在上一篇帖子【https://xie.infoq.cn/article/8de1223b32629b83bc672a737】当中,我们针对定位引起误差的查表算子展开了细致且全面的讨论,详细地阐述了其中所涉及的思路以及具体的方法。在实际的工作场景里,准确且高效地定位到引发误差的查表算子,能够为后续的调优工作奠定坚实的基础。当我们通过特定的流程和手段,成功地锁定了具体的查表算子之后,接下来需要面对的关键问题便是:应该怎样对这些查表算子进行调优呢?
这篇文章将选取一些在实际应用中较为常见的查表算子 softmax、cos/sin 作为具体的案例,逐步剖析并说明针对这些算子进行优化的具体思路以及切实可行的方法。我们会结合实际的操作流程与技巧,为大家说明一套系统且具有可操作性的调优方案。
二、查表算子调优
目前,征程 6 工具链已经针对定点查表算子的精度问题进行了专门的优化。不过,在实际的使用场景中,尽管已经做了优化,但仍不能完全排除存在一些特殊情况(即 corner case),在这些特殊情况下定点查表算子可能依然无法达到理想的精度表现。
鉴于此,为了让用户能够获得更好的使用体验以及更精准的计算结果,我们给出以下建议:
首先,强烈建议用户对 horizon_plugin_pytorch 进行升级操作。在最新版本的 horizon_plugin_pytorch 中,集成了最新的优化策略,这些策略经过了精心的设计和测试,能够进一步提升定点查表算子的精度,帮助用户在使用过程中减少可能出现的误差。
其次,在完成升级之后,如果依然存在比较严重的定点查表误差问题,那么用户需要针对可能出现的特殊情况进行细致的调优。
接下来,笔者将围绕那些可能会出现误差的查表算子展开详细说明。这些算子主要包括 softmax、cos/sin。在后续内容中,会着重介绍针对这些算子常见的调优手段,帮助用户更好地应对可能出现的精度问题。
2.1 Softmax
Softmax 是深度学习中常用的激活函数,通常用于分类任务的输出层。其作用是将一个实数向量转换为一个概率分布,使得:
每个输出值在 (0, 1) 之间;
所有输出值的和为 1。
公式如下:

softmax 常见查表方式为:
预先量化输入范围:将输入 减去最大值(提升数值稳定性)后,限定在某个固定范围内;
构建 exp 函数查找表:把这个范围离散成 N 个点,计算这些点的 e^x 值,保存在查找表中。
通过查表 + 插值估算:推理时将输入值映射到最近的查表索引并查找。
查表带来误差的主要来源包括:查表精度不足(量化误差)、数值归一化误差、浮点精度 / 定点表示。
其中查表精度不足一般是因为:
表的点数越少,间隔越大,近似误差越大;
即使插值(如线性插值)也不能完美还原原函数。
了解了查表误差等大概原因后,那么我们就提供具体的调优手段。
2.1.1 配置 auto_divide_strategy="mix"(首推)
新版本horizon_plugin_pytorch
目前已经推出了优化后的查表范围划分策略 auto_divide_strategy="mix", 这种方式不需要重新做 calibration/qat,且配置方式比较简单,所以笔者首推这种方式。
单算子配置方式:
prepare 之前,对 softmax 算子配置 reciprocal_kwargs,如下所示:
全局配置方式:
也可以尝试将模型中所有查表算子的 auto_divide_strategy 配置为 mix,配置方法为进入horizon_plugin_pytorch
源码路径进行修改。
docker 中进入/usr/local/lib/python3.10/dist-packages/horizon_plugin_pytorch/nn/qat/reciprocal.py
路径下,修改class Reciprocal
中的 default_lut_kwargs,如下所示:
2.1.2 修改 softmax 的 dividing_points
在上面我们讲到了查表范围的划分对误差影响比较大,这里提供了手动对 softmax 算子配置 dividing_points 的方法。
prepare 之前,配置 softmax 的 dividing_points,如下所示:
修改完成后可以在 prepare 以后打印确定是否已经修改成功,如下所示:
那么,dividing_points 如何确定呢?dividing_points 的确定严格依赖于实验结果,这里讲述一下具体的过程:
构建 reciprocal 单算子测试样例,其输入为 精度 debug 工具保存下来的 op_info;
绘制在修改 dividing_points 前后,查表转定点后输出的误差分布图,可以包括绝对误差、相对误差;
根据误差在每个输入范围的情况来划分 dividing_points,比如[-50,50]范围内的绝对误差比较大,那么就可以进一步精细划分这部分。
2.1.3 修改 input_range
修改 input_rang 的场景包括工具统计的不准确或者是存在 badcase,这里先提供修改的方式,然后再讲一下如何确定 input_range。
prepare 之前,配置 softmax 的 input_range,如下所示:
那么。重点是我们如何确定 input_range 呢?这里依赖于精度 debug 工具产物baseline_model/statistic.txt
中 min/max 值。
debug 工具如何运行可以参考上一篇帖子。
2.2 cos/sin
cos/sin 算子与 softmax 算子有所不同,它是具有周期性和对称性的,所以如果没有正确处理,可能会引起较大的误差。
若未处理角度周期性,如输入角度 > 2*pi,需要进行模运算,若实现不当,模运算精度误差也可能导致查错位置。
有些实现只查 [0, pi/2]并利用对称性计算其他象限,如果符号处理或镜像不准确,也会引入误差。
针对这种情况,工具链支持对 cos/sin 配置 single_period=True,不过,这里有两点要特别注意,也可以说这种配置方式的不足:
需要将模型中的 cos/sin 替换为
horizon_plugin_pytorch
的实现,示例代码如下所示:
模型需要重新做 calib/qat,时间和经济代价会比较高;
评论