大型语言模型 (LLM) 具有强大的能力,但也因其庞大的内存需求而臭名昭著。无论是在云端部署还是在边缘设备上进行优化,效率都成为了至关重要的问题。一种常见的经验法则是:模型大小 = 参数数量 × 每个参数的精度。为了解决这个问题,量化提供了一个引人注目的解决方案:将权重从 32 位浮点数压缩为更小的格式,例如 INT8。本文将深入探讨大模型量化技术,从简单的Absmax 量化和ZeroPoint 量化入手,分析其失效的原因,并详细介绍 LLM.int8() 如何通过一种巧妙的混合精度方法解决问题,最后通过实际的内存占用和困惑度数据,展示 LLM.int8() 作为一种实用的解决方案的优势。
量化:压缩大模型的关键
量化本质上是将高精度数字(如 FP32 或 FP16)映射到低精度格式(如 INT8)的过程。这种映射带来了显著的好处:
- 减少内存占用: 这是最直接的好处。例如,将 FP32 转换为 INT8,理论上可以将模型大小减少到原来的 1/4。
- 加速推理: 尤其是在 CPU 和专用加速器上,低精度计算可以显著提高计算速度,缩短推理时间。
- 降低带宽和缓存压力: 较小的模型尺寸意味着更少的内存带宽需求和更有效的缓存利用率。
然而,简单地将所有权重都转换为 INT8 可能会损害模型性能,尤其是在 Transformer 层中,因为这些层对数值精度要求很高。为了避免这种性能下降,需要更精细的量化策略。
朴素量化方法:Absmax 和 ZeroPoint
在探索更高级的量化技术之前,先了解两种常见的朴素量化方法,它们分别是 Absmax 量化和 ZeroPoint 量化。以下将介绍如何在 PyTorch 中从头开始实现它们,并分析其局限性。
-
Absmax 量化 (对称量化)
Absmax 量化通过其最大绝对值缩放每个权重张量,将范围映射到 INT8 范围 [-127, 127]。它简单且计算高效,使其成为快速量化实验的常见选择。
- 核心思想: 找到张量中绝对值最大的元素,将其映射到 INT8 的最大值 (127),其余元素按比例缩放。
- 优点: 实现简单,计算速度快。
- 缺点: 对异常值非常敏感。如果张量中存在极大的正值或负值,整个缩放范围会被拉伸,导致大部分权重的信息损失。
- 示例: 假设一个权重张量是
[-1.0, 0.5, 0.8, -20.3]
。Absmax
量化会找到最大绝对值 20.3,并以此来缩放所有值。这意味着 -20.3 被映射到 -127,其他值都会被压缩到一个很小的范围内,例如 -1.0 会被映射到 -6.26。
-
ZeroPoint 量化 (非对称量化)
ZeroPoint 量化通过引入缩放因子和零点偏移来改进对称方法。这使得量化范围能够更好地匹配具有非零中心分布的张量,这些张量在激活和一些权重层中很常见。
- 核心思想: 不仅进行缩放,还引入一个偏移量,使得量化后的零点对应于原始数据分布的某个特定值,通常是均值或中位数。
- 优点: 可以处理非对称分布的数据,在某些情况下可以提高精度。
- 缺点: 仍然容易受到异常值的影响,并且需要额外的计算来确定零点。
- 示例: 考虑一个权重张量
[0.1, 0.2, 0.3, 10.0]
。ZeroPoint
量化会计算该张量的最小值和最大值,然后将整个范围映射到 INT8 范围。如果最小值是 0.1,最大值是 10.0,那么零点就会被设置为 0.1。但是,由于存在 10.0 这个异常值,量化后的精度仍然会受到影响。
尽管这两种方法都能显著减少模型内存,但它们也带来了一定的代价:模型性能的下降,通常体现在困惑度的增加上。
异常值问题:朴素量化的阿喀琉斯之踵
异常值——极正或极负的权重值——在基于 Transformer 的模型中很常见。这些异常值在对称和非对称量化中都主导了缩放。即使在一个主要为 [-1, 1] 的张量中出现像 -20.3 这样的单个值,也会迫使整个缩放范围被拉伸,从而损失其余部分的分辨率。
这就留下了一个两难的境地:
- 保留异常值: 会破坏量化范围,导致大部分权重的精度降低。
- 移除异常值: 会损害模型语义,因为这些异常值可能包含了重要的信息。
例如,在自然语言处理任务中,某些罕见的词汇或组合可能对应于非常大的权重值,这些权重对于模型理解和生成文本至关重要。如果直接移除或过度压缩这些异常值,模型的性能将会受到严重影响。
LLM.int8():混合精度量化的巧妙解决方案
由 Tim Dettmers 及其团队开发的 LLM.int8() 提供了一种巧妙的折衷方案:
- 将 99.9% 的权重量化为 INT8。 这保证了显著的内存压缩。
- 将约 0.1% 的异常值保留在 FP16 中。 这保留了模型的关键信息,避免了性能下降。
- 使用向量化量化而不是逐张量量化 (更好的粒度)。
这意味着:
- 仍然可以获得 2.5 倍–3 倍的内存节省。
- 准确性几乎保持不变。
这种混合精度策略在大幅降低内存的同时,保持了模型的准确性。LLM.int8() 的核心在于它能够识别并处理 Transformer 模型中的异常值,并通过混合精度量化的方式,在性能和精度之间取得平衡。
具体实现细节:
LLM.int8() 的具体实现细节涉及到对矩阵乘法的拆解和重组。它将矩阵乘法分解为两个步骤:
-
异常值处理: 首先,检测并提取输入矩阵中的异常值(大于某个阈值的值)。这些异常值会单独进行处理,并使用 FP16 精度进行计算。
-
INT8 量化: 对于剩余的非异常值部分,使用 INT8 精度进行量化和计算。
最后,将两部分的结果合并,得到最终的输出。通过这种方式,LLM.int8() 能够有效地利用 INT8 带来的内存节省和加速优势,同时避免了异常值带来的精度损失。
与其他量化方法的对比:
与 Absmax 量化和 ZeroPoint 量化 相比,LLM.int8() 的优势在于:
- 更强的鲁棒性: LLM.int8() 能够有效地处理异常值,避免了朴素量化方法中常见的精度下降问题。
- 更高的精度: 通过混合精度策略,LLM.int8() 能够在保持较高精度的同时,实现显著的内存压缩。
- 更广泛的适用性: LLM.int8() 可以应用于各种 Transformer 模型,而不需要对模型结构进行修改。
内存占用比较
假设我们正在运行一个 Transformer 模型层。下表是 FP32 和 INT8 精度下的内存比较:
| 量化方法 | 内存占用 |
| ———– | —— |
| FP16 (基线) | 510 MB |
| INT8 (朴素) | 176 MB |
困惑度比较
为了量化不同量化策略对模型准确性的影响,测量了困惑度——一种用于评估语言模型的标准指标。较低的困惑度意味着模型在其预测中更有信心(并且通常更准确)。
下表是四种变体的比较图表:
| 量化方法 | 困惑度 |
| ————— | —- |
| FP16 (基线) | 15.1 |
| Absmax 量化 | 28.0 |
| ZeroPoint 量化 | 27.5 |
| LLM.int8() | 18.0 |
关键要点:
- LLM.int8() 仅引入了约 3 点的困惑度增加,使性能接近原始基线。
- 朴素量化技术(Absmax 量化和 ZeroPoint 量化)显示出显著的性能下降,在某些情况下困惑度几乎翻倍。
- 该差距说明了异常值感知的混合精度量化(如 LLM.int8() 中使用的)比朴素方法更好地保持了模型质量。
这使其成为生产级推理的理想选择,尤其是在内存和延迟都很重要的环境中。
实际案例
-
在边缘设备上部署 LLM: 由于内存限制,在边缘设备上部署大型语言模型一直是一个挑战。LLM.int8() 可以显著减少模型的大小,使其能够在资源受限的设备上运行。例如,可以将一个原本需要 4GB 内存的模型压缩到 1.3GB 左右,从而可以在手机或嵌入式设备上进行推理。
-
降低云端部署成本: 在云端部署 LLM 需要大量的计算资源和内存。通过使用 LLM.int8() 减少内存占用,可以降低云服务器的成本。例如,可以减少服务器的内存需求,或者在同一台服务器上部署更多的模型实例,从而提高资源利用率。
-
加速推理服务: LLM.int8() 不仅可以减少内存占用,还可以提高推理速度。这对于需要实时响应的在线服务至关重要。例如,可以将一个模型的推理速度提高 2 倍以上,从而可以更快地处理用户请求。
结论
大模型量化是优化 LLM 以适应各种部署场景的关键技术。 虽然朴素的 Absmax 量化和 ZeroPoint 量化 方法可以减少内存占用,但它们往往会导致精度下降。 LLM.int8() 通过其独特的混合精度方法克服了这些限制,在内存效率和模型性能之间实现了理想的平衡。 凭借其对异常值的巧妙处理和向量化量化,LLM.int8() 已成为在资源受限的环境中部署高性能 LLM 的强大工具,并有可能进一步推动人工智能技术在各个领域的应用。 在未来,我们可以期待更多针对不同模型架构和应用场景的量化技术涌现,从而进一步释放大模型的潜力。