Relay框架
如上图所示,有两种不同的并行工作正在进行中
关于 Relay Automatic FP16 Downcasting 的讨论很少。还没有任何 RFC。 正在对此进行探索/原型设计,计划提出 RFC。
Relay优化
Relay到硬件
有了优化的Relay图,就需要编写优化的调度。像 FP32 一样,必须只专注于昂贵的算子,如 conv2d、dense 等。有分散的努力,一些在不同后端工作的开发人员(不一定是 Int8),TVM 社区正在努力统一。
在 tvm 中实现了一个量化工作流程,从一些现有的量化框架中,选择采用注释-校准-实现3阶段设计:
在开发过程中,存在一些缺点:
INPUT
/ WEIGHT
/ACTIVATION
种不同的量化战略,这是种特殊算子的,需要不同的组合发生在不同的模式。这个 make annotation 有很多手动规则,变得很难维护。提出了一个新的量化框架,在循环中,引入了硬件和学习方法。已经进行了多项改进以,解决之前的问题:
in_scale
, in_dtype
,
添加out_dtype
到 SimQ 的定义中。在模拟期间,执行比例推理和数据类型分配。在 SimQ 中,模拟溢出错误。Hardware
抽象,描述硬件属性和算子约束。通过这种声明方式,用户只需Hardware
,
为不同的硬件定义不同的对象,无需了解量化逻辑。工作流程
给定目标硬件的模型和描述,系统将生成一组,位的选择空间和Topology
量化的空间。这里的Topology
意思,考虑到硬件和算子约束,哪些节点/边将被量化,这将在后面讨论。
然后搜索循环开始:学习算法将从选择空间中,选择一组参数——每条边上的位数。阈值可以通过从小型校准数据集,收集的统计数据估计。结合拓扑、位和阈值,可以生成模拟图,在校准数据集(大约 128 个样本)上,对其进行评估。输出/精度作为反馈,学习算法可以选择下一组位设置。
最后,通过搜索找到的最佳策略,将模拟模型实现真实的低精度整数模型。
将介绍几种重要性符号:位、阈值、比例。
一般而言,量化的目标,将浮点数(实数值)运行的图,转换为整数(定量值)运行的图,不会牺牲太多精度。给定一个具有真实值的张量,转换后的 quant 值的关系是什么?这是将在当前实现中遵循的规范:
硬件说明
硬件描述,试图为在量化过程中,需要考虑的硬件属性,提供一个中心抽象。通过声明这些属性,可以避免在随后的量化步骤中,处理硬件特定条件。
目前可以指定每个算子的,输入数据类型和输出数据类型。
desc = Hardware()
desc['add'].append(OpDesc(in_dtypes=['int32', 'int32'], out_dtypes=['int32']))
desc['add'].append(OpDesc(in_dtypes=['float32', 'float32'], out_dtypes=['float32']))
desc['nn.conv2d'].append(OpDesc(in_dtypes=['int16', 'int16'], out_dtypes=['int32']))
desc['nn.conv2d'].append(OpDesc(in_dtypes=['int8', 'int8'], out_dtypes=['int32']))
desc['nn.global_avg_pool2d'].append(OpDesc(in_dtypes=['float32', 'float32'], out_dtypes=['float32']))
硬件信息在整个过程中已经使用了多次:
为了估计阈值,在校准数据集上运行模型,收集需要的统计信息。目前将保存中间算子的所有输出。为了从收集的输出中确定阈值,有几种策略:
目前,选择了这种power2_range
方法,可以使用移位来代替乘法,在最终的量化模型中,提供更好的性能。虽然kl_estimate
带来更好的准确度,但相当耗时,目前在搜索中使用不可行。
一个棘手的问题是,对于像加法这样的算子,只能在其算子的标度为 eqaul 时执行。首先统一其算子的规模。为了实现这一点,估计阈值将在模拟之前进行调整。threshold_rectify
引入了一个命名转换和一个特定于算子的属性:
@register_fthreshold_rectify('add')
def threshold_rectify_for_add(in_bits, out_bits, in_tholds, out_tholds):
# choose scale of the one with maximum threshold
idx = np.argmax(in_tholds)
unified_scale = in_tholds[idx] / (2**(in_bits[idx] - sign_bit))
# adjust thresholds according to the unified scale
...
给定比特和阈值,可以尝试生成一个模型,模拟量化带来的误差。经过分析,可以发现误差来自几个方面: 1.舍入误差;2.饱和误差;3.溢出错误。
将simulated_quantize
在每条边上,插入一个算子,试图模拟这些错误。定义如下:
def simulated_quantize(data, in_scale, out_scale, clip_min, clip_max, in_dtype, out_dtype):
if in_dtype == 'float32' and out_dtype == 'float32':
# no need to quantize
return data
# simulated overflow error
data = data / in_scale
data = topi.cast(data, in_dtype)
data = data * in_scale
scaled_data = data / out_scale
# simulate saturated error
clipped_data = topi.clip(scaled_data, clip_min, clip_max)
# simulate round error
rounded_data = topi.cast(topi.round(scaled_data), out_dtype)
out = rounded_data * out_scale
return out
如何通过位和阈值,计算这些参数呢?out_scale、clip_min、clip_max 是非常严格的:
integer_range = 2**(bit - sign_bit)
out_scale = threshold / integer_range
clip_min = - (integer_range - 1)
clip_max = integer_range - 1
对于in_scale、in_dtype、out_dtype,需要做额外推理。
可以在上面的模型中,发现in_scale
,SimQ 的实际上,前一个算子输出的尺度,可以根据算子定义计算。为这样的属性,提供了一个注册函数:
@register_finfer_scale('nn.conv2d'):
def infer_scale_for_conv2d(in_scales):
return in_scales[0] * in_scales[1]
对于数据类型,将遍历算子,从硬件描述中,选择满足输入位和输出位要求的算子规范。
有了上面描述的所有准备工作,量化问题转换为学习问题:希望从选择空间中,找到最佳设置,以实现模拟模型的最佳精度(或其它目标,如性能),可以使用每轮的输出(准确度)作为反馈。
对于这个学习问题,实现了random_search
, simulated_anealing
, 也是一个贪心算法。目前实验表明贪婪搜索是最可行的。
搜索空间很大,搜索过程可能很长,最好有一个正式的日志格式,记录实验细节,实现可重复性和可交换性。选择json格式,详细信息如下:
在得到最佳量化策略后:拓扑、比特、阈值实现模拟图,到低精度量化图,相当直截了当的。只需要用低精度整数运算,替换每条边上的 SimQ 运算。
调试量化模型哪里出了问题,因为通常只知道最终的准确性很差。实现了inspect_graph_statistic
逐层量化前后统计差异的功能,可以快速定位到哪里出错了。开发过程中,证明非常有帮助。
from tvm import hago
# ideally we will have predefined description for x86, arm, gpu and vta
hardware = hago.create_sample_hardware()
strategy, sim_acc = hago.search_quantize_strategy(graph, hardware, dataset)
quantizer = hago.create_quantizer(graph, hardware, strategy)
simulated_graph = quantizer.simulate()
quantized_graph = quantizer.quantize()
在 resnet18_v1 上获得了 68.7% 的初步结果,没有跳过第一个卷积层,只使用 2 的幂范围,不是 kl 距离,应该还有更多的改进空间。
参考链接:
https://discuss.tvm.apache.org/t/rfc-search-based-automated-quantization/5483
https://discuss.tvm.apache.org/t/quantization-story/3920