训练后量化(PTQ)包含基本原则,例如使用有代表性的数据进行校准,以确定合适的缩放因子和零点。这些原则主要应用于大型语言模型架构中占据主导地位的层类型。在典型的基于Transformer的大型语言模型中,计算量最大和内存占用最多的层是线性层(或全连接层)和嵌入层。将PTQ应用于线性层线性层执行矩阵乘法,构成Transformer前馈网络(FFN)和注意力机制中计算的主干。线性层计算 $Y = XW^T + b$,其中 $X$ 是输入激活张量,$W$ 是权重矩阵,$b$ 是可选的偏置向量。权重量化: 权重矩阵 $W$ 由于其大小,通常是线性层中量化的主要目标。使用校准数据(或有时仅使用权重本身),我们确定 $W$ 中值的范围。根据此范围以及选择的量化方案(对称或非对称)和目标比特深度(例如,INT8、INT4),我们计算将FP32权重映射到低精度整数格式所需的缩放因子 ($s_W$) 和零点 ($z_W$)。$$ W_{量化} = \text{四舍五入}(W / s_W) + z_W $$这里一个重要的考量是粒度:逐张量: 对整个权重矩阵使用单个 $s_W$ 和 $z_W$。这是最简单的方法,但如果矩阵的行或列之间的值范围差异很大,则可能表现不佳。逐通道(或逐行/逐列): 为每个输出通道($W$ 的行)或有时输入通道($W$ 的列)计算单独的 $s_W$ 和 $z_W$ 值。权重的逐通道量化通常能提供比逐张量量化更好的精度,特别是在INT4等较低比特深度下,因为它更能适应不同神经元之间变化的权重分布。与矩阵乘法本身相比,它增加的开销最小。偏置项 ($b$) 通常保持为FP32,或使用从权重和激活中得出的缩放因子,量化为更高精度的整数格式(如INT32),因为它们占总参数的比例小得多。激活量化(针对静态PTQ): 在静态PTQ中,输入激活 $X$ 也被量化。这需要在校准阶段观察激活值的范围。我们将校准数据集通过模型,并记录每个线性层的输入张量 $X$ 观察到的最小值和最大值。根据这些观察到的范围,我们计算激活缩放因子 ($s_X$) 和零点 ($z_X$)。$$ X_{量化} = \text{四舍五入}(X / s_X) + z_X $$激活通常逐张量量化,主要是出于性能原因,因为在运行时计算逐通道的缩放因子(或存储逐通道的统计数据)会增加复杂性。然而,如前所述,激活异常值会带来重大挑战,如果处理不当(例如,使用裁剪或第三章中介绍的更高级的PTQ技术),可能导致大的量化误差。量化后的计算随后使用整数运算近似原始矩阵乘法,通常将结果累积为更高精度的整数格式(例如INT32),然后反量化回FP32(或中间格式):$$ Y \approx (X_{量化} - z_X) s_X \cdot ((W_{量化} - z_W) s_W)^T + b $$(注意:具体的实现细节取决于所使用的硬件和库,侧重于高效的整数矩阵乘法。)digraph G { rankdir=LR; node [shape=box, style=filled, color="#ced4da", fontname="helvetica"]; edge [color="#495057", fontname="helvetica"]; X [label="输入激活\n(FP32)", fillcolor="#a5d8ff"]; W [label="权重矩阵\n(FP32)", fillcolor="#ffec99"]; B [label="偏置\n(FP32)", fillcolor="#b2f2bb"]; Q_X [label="量化激活\n(缩放因子 sX, 零点 zX)", shape=ellipse, fillcolor="#74c0fc"]; Q_W [label="量化权重\n(缩放因子 sW, 零点 zW)", shape=ellipse, fillcolor="#ffe066"]; X_q [label="X_量化\n(INT8)", fillcolor="#a5d8ff"]; W_q [label="W_量化\n(INT8)", fillcolor="#ffec99"]; MatMul [label="整数矩阵乘法\n(INT8 x INT8 -> INT32)", shape=invhouse, fillcolor="#ffc078"]; AddBias [label="添加偏置", shape=ellipse, fillcolor="#8ce99a"]; Dequant [label="反量化\n(缩放因子 sX * sW)", shape=ellipse, fillcolor="#ffa8a8"]; Y [label="输出激活\n(FP32)", fillcolor="#ffc9c9"]; X -> Q_X; W -> Q_W; Q_X -> X_q; Q_W -> W_q; X_q -> MatMul; W_q -> MatMul [label=" 转置"]; MatMul -> AddBias [label=" 结果 (INT32)"]; B -> AddBias; AddBias -> Dequant; Dequant -> Y; }线性层 ($Y = XW^T + b$) 的静态量化流程。激活 ($X$) 和权重 ($W$) 使用校准期间获得的缩放因子和零点进行量化。计算使用整数运算进行,随后进行反量化。将PTQ应用于嵌入层嵌入层将离散输入令牌(由整数ID表示)映射到密集的浮点向量。它们本质上是查找表,其中“权重”构成嵌入矩阵。对于输入令牌ID序列,该层检索相应的向量。权重量化: 嵌入矩阵本身可能很大,特别是对于具有大词汇量(例如,50,000个或更多令牌)和高嵌入维度(例如,4096)的模型。量化此矩阵会大大减少模型的内存占用。与线性层类似,我们将量化应用于嵌入表权重。此过程包括确定所有嵌入向量的值范围,并计算缩放因子和零点。逐张量: 对整个嵌入表使用单个缩放因子和零点。如果向量范数和值分布在令牌之间相对均匀,这种方法简单有效。逐通道(逐向量): 每个嵌入向量(表中的行)都有自己的缩放因子和零点。如果某些令牌的嵌入值比其他令牌大得多或小得多,这种方法可以更好地捕获变化,可能保留更多细节,但代价是元数据存储量略有增加。选择取决于压缩、精度和实现复杂性之间的期望权衡。为了节省内存,即使是将嵌入逐张量量化为INT8,也能比FP32提供4倍的缩减。激活量化: 嵌入层的直接输入是整数令牌ID序列。这些ID通常不以与连续激活值相同的意义进行“量化”。它们作为查找操作的索引。量化影响与嵌入查找的输出有关。在静态PTQ场景中,如果后续层期望量化输入,那么获取的嵌入向量(如果只量化权重,则原始为FP32,或者从INT8/INT4反量化而来)需要在馈入下一层(例如,第一个注意力层)之前进行量化。此量化步骤将使用根据校准期间观察到的输出嵌入向量范围获得的缩放因子和零点。然而,通常将PTQ主要集中在嵌入权重上以减少内存。后续层随后根据需要处理其输入(查找后的嵌入)的量化。层特有考量应用PTQ并非总是在所有层上都是统一的。敏感度: 有些层可能比其他层对精度降低更敏感。例如,网络早期涉及的层或特定组件,例如注意力值投影,如果激进量化,可能会出现更大的精度下降。虽然基本PTQ通常采用统一策略,但高级方法(第三章)可能会对不同层使用不同的比特分配或技术。相互关联: 请记住,一个量化层的输出分布成为下一个层的输入分布。这种链式效应凸显了使用有代表性的校准数据的重要性,该数据能够捕获激活通过整个模型的典型流动。不佳的校准可能导致层层累积误差。工具抽象: 幸运的是,Hugging Face Optimum等库或特定框架工具(第五章讨论)通常抽象掉许多这些逐层细节,允许您将PTQ配置应用于整个模型或特定模块类型。然而,理解线性层和嵌入层内部发生的情况非常重要,对于故障排除和在标准PTQ无法满足精度要求时做出明智决策而言。通过将PTQ应用于线性层和嵌入层的权重矩阵,我们实现了模型大小的大幅缩减。当使用静态PTQ时,量化激活进一步支持基于整数的计算,可能加速推理,尽管需要仔细校准来管理精度影响。