尽管训练后量化 (PTQ) 旨在将浮点值范围映射到低精度整数范围,但由于异常值的存在,面临着一个重要的挑战。异常值是指权重或激活张量中远超典型值分布的值。即使一个极端值也可能不成比例地影响量化过程,从而可能导致精度大幅下降。异常值带来的问题大多数基本的 PTQ 算法,如 MinMax 量化,是根据在张量中(或校准期间的子集中)观察到的绝对最小值和最大值来确定缩放因子的。设想一个张量,其中大多数值介于 -1.0 和 1.0 之间,但存在一个值为 100.0 的异常值。如果我们使用 MinMax 缩放,则整个范围从 -1.0 到 100.0 必须映射到可用的整数范围(例如,对于 $INT8$ 为 -128 到 127)。$$ \text{缩放因子} = \frac{\text{最大浮点值} - \text{最小浮点值}}{\text{最大整数值} - \text{最小整数值}} = \frac{100.0 - (-1.0)}{127 - (-128)} = \frac{101.0}{255} \approx 0.396 $$如此大的缩放因子意味着大多数原始值(介于 -1.0 和 1.0 之间)将被压缩到整数范围的一个非常小的部分:原始值 1.0 变为 $round(1.0 / 0.396) \approx round(2.52) = 3$。原始值 -1.0 变为 $round(-1.0 / 0.396) \approx round(-2.52) = -3$。之前在 -1.0 和 1.0 之间表示的所有详细信息现在被挤压到大约 6 个整数值(从 -3 到 3)。整数范围的其余部分(从 -128 到 -4 以及 4 到 127)主要用于映射单个异常值 100.0(该值映射到 $round(100.0 / 0.396) \approx 127$,可能在调整零点后)。这种大部分值的解析度损失常常导致模型性能显著下降。{"layout": {"title": "异常值对值分布的影响", "xaxis": {"title": "值", "range": [-10, 110]}, "yaxis": {"title": "频率", "showticklabels": false}, "bargap": 0.1}, "data": [{"type": "bar", "x": ["正常值<br>(-1 到 1)", "异常值<br>(~100)"], "y": [100, 1], "marker": {"color": ["#339af0", "#fa5252"]}, "name": "值分布"}]}异常值的存在会大幅拓宽 MinMax 量化所需的范围,从而降低了大部分值的精度。管理异常值的策略有几种技术可以减轻 PTQ 期间异常值的负面影响:裁剪 (饱和): 我们可以不使用校准数据中的绝对最小值和最大值,而是选择阈值(裁剪值),并将超出这些阈值的任何值强制限定到边界值。这个过程也称为饱和。例如,我们可能决定根据校准期间观察到的值的第 1 和第 99 百分位数来裁剪范围。确定裁剪范围 $[c_{min}, c_{max}]$。这通常基于校准期间观察到的绝对值的百分位数(例如,99.9% 或 99.99%)。对于张量中的每个值 $x$:$x_{裁剪后} = \max(c_{min}, \min(x, c_{max}))$。使用裁剪后的最小值 ($c_{min}$) 和最大值 ($c_{max}$) 计算量化参数(缩放因子和零点)。digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="sans-serif"]; subgraph cluster_0 { label = "原始分布"; style=dashed; color="#adb5bd"; orig_dist [label="主要值", shape=ellipse, style=filled, color="#a5d8ff"]; outlier_neg [label="负异常值", shape=ellipse, style=filled, color="#ffc9c9"]; outlier_pos [label="正异常值", shape=ellipse, style=filled, color="#ffc9c9"]; } subgraph cluster_1 { label = "已应用裁剪"; style=dashed; color="#adb5bd"; clipped_dist [label="主要值", shape=ellipse, style=filled, color="#a5d8ff"]; clip_min [label="裁剪最小值\n(例如, p1)", shape=point, color="#f03e3e"]; clip_max [label="裁剪最大值\n(例如, p99)", shape=point, color="#f03e3e"]; } subgraph cluster_2 { label = "结果量化范围"; style=dashed; color="#adb5bd"; quant_range [label="有效范围\n(更紧凑)", shape=box, style=filled, color="#96f2d7"]; }orig_dist -> clipped_dist [style=invis]; outlier_neg -> clip_min [label="裁剪到", style=dashed, color="#f03e3e"]; outlier_pos -> clip_max [label="裁剪到", style=dashed, color="#f03e3e"]; clipped_dist -> quant_range [style=invis]; clip_min -> quant_range [label="定义范围最小值", style=dashed, color="#1c7ed6"]; clip_max -> quant_range [label="定义范围最大值", style=dashed, color="#1c7ed6"]; {rank=same; orig_dist; outlier_neg; outlier_pos;} {rank=same; clipped_dist; clip_min; clip_max;} {rank=same; quant_range;}} ```> 裁剪通过将异常值饱和到预定义的最小和最大阈值来限制量化范围。 裁剪阈值的选择涉及权衡:裁剪过于激进会丢弃来自潜在重要的较大值的信息,而裁剪过少则会保留常见值分辨率差的问题。选择合适的阈值通常需要使用代表性验证数据集来评估对模型精度的影响。2. 替代校准方法: 除了简单的 MinMax 之外,其他算法,例如基于最小化 Kullback-Leibler (KL) 散度(熵)或使用均方误差 (MSE) 的算法,本身能更优雅地处理异常值。这些方法试图找到最佳保留整体分布形状或最小化大多数值误差的量化参数,而不是完全由极端值决定。使用基于百分位数的校准可以直接根据百分位数设置范围,从而有效地裁剪异常值。更细致的量化粒度: 如第一章所述,量化可以按张量、按通道或按组应用。异常值通常不会均匀分布在大型权重张量中。例如,它们可能集中在线性层的特定输出通道中。按通道量化: 为每个通道(例如,权重矩阵的每一行或每一列)计算单独的缩放因子和零点值。一个通道中的异常值将只影响该特定通道的量化范围,而其他通道则不受影响。按组量化: 为张量内的小块权重计算参数。这在按张量和按通道之间提供了一种折衷,将异常值的影响隔离到更小的组中。使用更细致的粒度会增加元数据开销(需要存储更多的缩放因子/零点值),并且可能需要特定的硬件支持才能获得最佳性能,但它显著提高了对局部异常值的鲁棒性。高级技术(展望): 像 SmoothQuant(将在第三章讨论)这样的方法明确解决了激活异常值的问题,这些异常值在大型语言模型 (LLM) 中通常比权重异常值更成问题。SmoothQuant 的工作原理是通过数学方式将量化难度从激活(具有难以量化的动态范围)迁移到权重(静态且更易于量化),而不改变层的数学函数。这通常允许即使激活中包含显著异常值也能进行有效的 INT8 量化。选择合适的异常值处理策略通常涉及实验。从代表性数据集上的 MinMax 校准开始,评估精度,然后如果精度显著下降,则尝试裁剪或更细致的粒度,这是一种常见的工作流程。监测权重和激活的分布也可以提供关于异常值是否是量化错误的可能原因的见解。