让我们将浮点数的高精度范围映射到整数的紧凑范围。正如我们所讨论的,量化是将浮点数值($r$,代表“实数”)转换为低精度整数值($q$,代表“量化值”)。这不仅仅是简单的类型转换;它需要一个明确的映射过程。这种映射的核心涉及两个参数:缩放因子 ($s$): 一个正浮点数,它决定了相邻量化整数值之间的步长。它关联着实数的大小与整数范围。零点 ($z$): 一个对应于实数0.0的整数值。这确保了浮点零在量化域中得到正确表示。连接实数值 $r$、量化值 $q$、缩放因子 $s$ 和零点 $z$ 的基本关系是:$$r \approx s \cdot (q - z)$$这是反量化公式,描述了如何从量化整数近似得到原始实数值。量化过程,即从 $r$ 到 $q$ 的转换,涉及除以缩放因子、加上零点、四舍五入到最近的整数,并将结果箝制到允许的整数范围(例如,INT8 为 [-128, 127]):$$q = \text{clip}(\text{round}(r / s) + z, q_{min}, q_{max})$$我们如何确定缩放因子($s$)和零点($z$)界定了量化方案。两种主要方案是对称量化和非对称量化。对称量化对称量化,顾名思义,将浮点值对称地映射在零点周围。它假定您的张量(权重或激活值)中重要值的范围接近零点居中。映射方式: 它将浮点范围 $[-R, R]$ 映射到有符号整数范围 $[q_{min}, q_{max}]$,通常如 INT8 的 $[-127, 127]$ 或 $[-128, 127]$。其特点是浮点值 0.0 直接映射到整数值 0。缩放因子和零点:零点 ($z$) 固定为 0。缩放因子 ($s$) 由您要表示的范围中的最大绝对值 ($R$) 确定。具体而言,$R = \max(|\text{min}(r)|, |\text{max}(r)|)$ 或简言之针对整个张量/通道/组的 $R = \max(|r|)$。然后计算缩放因子,将此最大绝对值 $R$ 映射到整数范围的边界。对于映射到 $[-127, 127]$ 的有符号 INT8,缩放因子将是 $s = R / 127$。公式:量化:$q = \text{clip}(\text{round}(r / s), q_{min}, q_{max})$ (零点为 0)反量化:$r \approx s \cdot q$例子: 假设将范围 $[-3.8, 3.2]$ 中的值量化为有符号 INT8(使用 $[-127, 127]$ 范围进行对称处理)。确定范围限值 $R$:$R = \max(|-3.8|, |3.2|) = 3.8$。计算缩放因子 $s$:$s = 3.8 / 127 \approx 0.0299$。设定零点 $z$:$z = 0$。量化一个值,例如 $r = 1.5$:$q = \text{round}(1.5 / 0.0299) = \text{round}(50.167) = 50$。量化一个值,例如 $r = -0.8$:$q = \text{round}(-0.8 / 0.0299) = \text{round}(-26.75) = -27$。反量化 $q=50$:$r \approx 0.0299 \cdot 50 = 1.495$。反量化 $q=-27$:$r \approx 0.0299 \cdot (-27) = -0.8073$。<!-- end list -->digraph G { rankdir=LR; node [shape=plaintext, fontsize=10]; subgraph cluster_float { label = "浮点范围(对称量化示例)"; style=dashed; color="#adb5bd"; float_axis [label = < <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"> <TR><TD>-R (-3.8)</TD><TD ALIGN="CENTER">0.0</TD><TD ALIGN="RIGHT">+R (3.8)</TD></TR> <TR><TD COLSPAN="3" BGCOLOR="#4c6ef5" HEIGHT="5"></TD></TR> </TABLE> >]; } subgraph cluster_int { label = "有符号 INT8 范围 [-127, 127]"; style=dashed; color="#adb5bd"; int_axis [label = < <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"> <TR><TD>-127</TD><TD ALIGN="CENTER">z=0</TD><TD ALIGN="RIGHT">127</TD></TR> <TR><TD COLSPAN="3" BGCOLOR="#12b886" HEIGHT="5"></TD></TR> </TABLE> >]; } edge [style=invis]; float_axis -> int_axis; subgraph cluster_map { node [shape=none, margin=0]; edge [arrowhead=none, style=dashed, color="#868e96"]; f_min [label="-3.8"]; f_zero [label="0.0"]; f_max [label="3.8"]; i_min [label="-127"]; i_zero [label="0"]; i_max [label="127"]; f_min:s -> i_min:n; f_zero:s -> i_zero:n; f_max:s -> i_max:n; } {rank=same; float_axis; f_min; f_zero; f_max;} {rank=same; int_axis; i_min; i_zero; i_max;} }对称量化映射。浮点范围围绕 0.0 居中,它直接映射到整数零点(z=0)。缩放因子(s)确保最大绝对值映射到整数范围的边界。优点:简洁性: 零点固定为 0,简化了计算。效率: 当零点为 0 时,某些硬件加速器可以更高效地执行整数计算。缺点:潜在的低效: 如果原始浮点分佈偏斜(未围绕零点居中),则整数范围的一侧可能未充分利用或未使用,这相较于使用相同位数的非对称量化,会有效降低可用精度。非对称量化非对称量化将张量中观测到的精确浮点最小值和最大值映射到整数范围的最小值和最大值。当数据分佈未围绕零点居中时,这尤其有用。映射方式: 它将浮点范围 $[\min(r), \max(r)]$ 映射到完整的整数范围 $[q_{min}, q_{max}]$,例如无符号 INT8 (UINT8) 的 $[0, 255]$ 或有符号 INT8 的 $[-128, 127]$。缩放因子和零点:缩放因子 ($s$) 基于完整浮点范围计算:$s = (\max(r) - \min(r)) / (q_{max} - q_{min})$。零点 ($z$) 的计算旨在确保浮点值 0.0 正确映射到其对应的量化值。它通常被计算为一个整数偏移量:$z = \text{round}(q_{min} - \min(r) / s)$。请注意,$z$ 可能不为 0。它表示对应于实数 0.0 的整数值。公式:量化:$q = \text{clip}(\text{round}(r / s) + z, q_{min}, q_{max})$反量化:$r \approx s \cdot (q - z)$例子: 假设将已知在范围 $[0.5, 6.2]$ 中的激活值量化为无符号 INT8 (UINT8, 范围 [0, 255])。确定范围:$\min(r) = 0.5$,$\max(r) = 6.2$。计算缩放因子 $s$:$s = (6.2 - 0.5) / (255 - 0) = 5.7 / 255 \approx 0.02235$。计算零点 $z$:公式为 $z = \text{round}(q_{min} - \min(r) / s)$。 $z = \text{round}(0 - 0.5 / 0.02235) = \text{round}(-22.37) = -22$。 注:在实际框架中,零点通常会箝制到整数范围 $[q_{min}, q_{max}]$(例如,UINT8 的 [0, 255])。如果需要箝制,缩放因子可能会略微调整。为了在此处简化,我们使用计算出的值。量化一个值,例如 $r = 1.5$: $q = \text{clip}(\text{round}(1.5 / 0.02235) + (-22), 0, 255) = \text{clip}(\text{round}(67.11) - 22, 0, 255) = \text{clip}(67 - 22, 0, 255) = 45$。量化一个值,例如 $r = 5.0$: $q = \text{clip}(\text{round}(5.0 / 0.02235) - 22, 0, 255) = \text{clip}(\text{round}(223.7) - 22, 0, 255) = \text{clip}(224 - 22, 0, 255) = 202$。反量化 $q=45$:$r \approx 0.02235 \cdot (45 - (-22)) = 0.02235 \cdot 67 = 1.497$。反量化 $q=202$:$r \approx 0.02235 \cdot (202 - (-22)) = 0.02235 \cdot 224 = 4.99$。<!-- end list -->digraph G { rankdir=LR; node [shape=plaintext, fontsize=10]; subgraph cluster_float { label = "浮点范围(非对称量化示例)"; style=dashed; color="#adb5bd"; float_axis [label = < <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"> <TR><TD>min(r) (0.5)</TD><TD ALIGN="RIGHT">max(r) (6.2)</TD></TR> <TR><TD COLSPAN="2" BGCOLOR="#fd7e14" HEIGHT="5"></TD></TR> </TABLE> >]; float_zero [label="0.0", pos="0,0!", style=invis]; // Helper for positioning } subgraph cluster_int { label = "无符号 INT8 范围 [0, 255]"; style=dashed; color="#adb5bd"; int_axis [label = < <TABLE BORDER="0" CELLBORDER="0" CELLSPACING="0"> <TR><TD>0</TD><TD ALIGN="CENTER">整数零点 (z ≈ -22)</TD><TD ALIGN="RIGHT">255</TD></TR> <TR><TD COLSPAN="3" BGCOLOR="#7048e8" HEIGHT="5"></TD></TR> </TABLE> >]; } edge [style=invis]; float_axis -> int_axis; subgraph cluster_map { node [shape=none, margin=0]; edge [arrowhead=none, style=dashed, color="#868e96"]; f_min [label="0.5"]; f_max [label="6.2"]; i_min [label="0"]; i_max [label="255"]; f_min:s -> i_min:n; f_max:s -> i_max:n; } {rank=same; float_axis; f_min; f_max;} {rank=same; int_axis; i_min; i_max;} }非对称量化映射。精确的浮点最小值和最大值映射到整数范围的最小值和最大值。缩放因子(s)和零点(z)适应这种映射,即使浮点范围不包含 0.0。优点:灵活性: 能够精确表示偏斜或未围绕零点居中的数据分佈(例如,ReLU 激活函数的输出始终为非负)。精度: 最大化利用可用整数值,相较于使用相同位数的对称量化,可能为非对称分佈带来更低的量化误差。缺点:复杂性: 反量化和后续计算需要计算、存储和使用缩放因子和零点。这相较于 $z=0$ 的对称情况,会引入少量计算开销。选择适宜方案对称量化和非对称量化之间的选择取决于几个因素:特性对称量化非对称量化浮点范围映射 $[-R, R]$映射 $[\min(r), \max(r)]$零点映射$0.0 \rightarrow 0$$0.0 \rightarrow z$ (整数零点)零点$z = 0$ (通常)$z$ 需计算,可非零参数缩放因子 ($s$)缩放因子 ($s$) 和零点 ($z$)适用场景围绕零点居中的分布 (例如,权重)偏斜分佈 (例如,ReLU 后的激活值)计算可能更简洁 (如果 $z=0$)需要处理非零的 $z$权重: 在许多神经网络中,权重分佈倾向于大致对称围绕零点。因此,对称量化通常是权重的一个良好预设选择,它提供了简洁性和潜在的硬件优势。激活值: 激活值,尤其是在诸如 ReLU ($f(x) = \max(0, x)$) 等函数之后,通常具有高度非对称的分布(例如,始终非负)。非对称量化通常更受青睐用于激活值,因为它可以更好地利用整数范围来表示这些偏斜值,从而减少精度损失。然而,这些是一般指导原则。最佳选择可能因具体层类型、模型架构、目标硬件性能(某些硬件可能针对其中一种方案进行最佳化)以及精度/性能权衡的经验评估而异。现代量化库和框架通常允许您独立配置这些方案用于权重和激活值。了解这些方案非常重要,因为选择直接影响低精度整数表示原始浮点值的精确程度,进而影响量化大型语言模型的整体性能和精度。接下来,我们将审视量化的另一个方面:粒度,它定义了这些缩放因子和零点参数的应用范围。