LLM模型量化从入门到精通:Shrink, Speed, Repeat

article/2025/7/12 19:22:08

前言

神经网络把它们的知识都存成数字啦,主要是训练时学到的权重,还有运行时在每一层流动的激活值。这些数字必须保持在一个固定的数值格式里,而选的格式就决定了每个参数要占多少内存。要是用默认的32位浮点表示,一个有70亿参数的语言模型可就重达28 GB啦,这比普通笔记本/台式机的GPU能装下的内存多多了。

量化就能解决这个问题啦,它把同样的值重新编码成精度更低的格式:8位整数、4位块浮点数,还能更小呢,能让内存占用缩小4-8倍,还能加速推理,因为小张量在内存里跑得更快。
在这里插入图片描述

1. 模型为啥需要减肥

深度学习网络的体积可是爆炸式增长的,尤其是大型语言模型(LLM):GPT-2(2019年)有15亿参数,GPT-4的公开估计参数数量达到了几百亿,现在好多研究实验室都在训练万亿参数的原型模型了。 [Our World in Data] [Wikipedia]

模型参数数量增长

[**Our World in Data]

然而,消费级GPU的显存从大约8 GB只涨到了24/32 GB,这可和模型的增长速度完全不匹配呀。这就导致很多最先进的模型检查点大到连在单个笔记本或游戏显卡上做推理都装不下。

模型压缩研究就是来填补这个差距的,它能让同样的网络占用更少的字节,运行得还更快。主要有三大类方法:

模型压缩方法

在实际应用中,这些技术常常是组合使用的,比如一个4位量化、稀疏剪枝的学生模型。

不过,量化可是脱颖而出的第一选择呢,因为它能保持原来的架构不变,几乎不需要重新训练,还能立刻节省显存:一个70亿参数的模型从FP32的约28 GB一下子降到现代4位格式的约6 GB,小到能装进中端RTX笔记本GPU里啦。

接下来的指南先从能让模型变小的数值格式讲起,然后一步步从简单的8位线性量化讲到如今领先的4位方法,这些方法你只要几行代码就能试一试哦。

2. 数据类型和大小

不同的数值格式决定了每个权重或激活值要占多少位,也因此决定了模型需要多少内存,以及它的数学运算能跑多快。这一节咱们就来瞅瞅量化中最常遇到的两大类数值格式:整数浮点数格式。

整数格式 [Wikipedia]

整数格式

PyTorch的助手torch.iinfo(dtype)能打印出任何整数类型的最小值/最大值限制。 [PyTorch] 为啥整数很重要:把权重存成8位整数,内存就能减少到FP32的四分之一,还能减轻内存带宽的压力,这可是在推理工作负载里常常是真正的瓶颈呢。

浮点数家族

浮点数编码了三个字段:符号(1位)指数(范围)和小数部分(精度)。把其中任何一个字段缩小,就能减少内存占用,还能解锁更快的GPU张量核心路径。 [Wikipedia]

浮点数格式

PyTorch通过torch.float16torch.bfloat16等来暴露这些格式;用torch.finfo(dtype)就能查询精度和范围。 [PyTorch]

降低精度演示

把一个FP32张量转换成BF16,内存就能减半,不过会出现四舍五入的情况:

# 生成一个FP32类型的随机张量
x_fp32 = torch.rand(1000, dtype=torch.float32)
# 将其转换为BF16类型
x_bf16 = x_fp32.to(torch.bfloat16)# 打印前5个元素,看看转换后的值变粗略了
print(x_fp32[:5])
print(x_bf16[:5]) # fewer mantissa bits → coarse values
# 计算并打印两个张量的点积,看看精度损失
print(torch.dot(x_fp32, x_fp32))
print(torch.dot(x_bf16, x_bf16))

在大多数工作负载里,点积的误差都不到1%,这可就体现了典型的精度/准确率权衡啦。

3. 线性量化

线性量化示意图

当你把一个张量从32位浮点数换成精度更低的类型时,你需要一条规则来告诉每个原始值它应该落在那个短得多的整数刻度的哪个位置。最常见的规则就是线性(均匀)量化啦:把满精度范围[rₘᵢₙ,rₘₐₓ]拉伸平移一下,让它正好能装进整数范围[qₘᵢₙ,qₘₐₓ]里。

两个小小的参数就能搞定:

线性量化公式

  • 比例尺s决定了一个整数步有多宽
    在这里插入图片描述

  • 零点z告诉哪个整数应该代表现实世界里的零
    在这里插入图片描述

用这种技术把32位浮点数换成8位整数,我们通常能在语言模型基准测试里把准确率损失控制在不到一个百分点以内哦。 [NVIDIA Developer] 虽然这技术看起来挺基础的,但它其实超有效,还是2025年目前大多数最先进的量化技术(比如SmoothQuant、GPTQ、QLoRA的NF4块、AWQ的激活感知变体)的基础呢。

4. 动手实践小实验

在咱们聊现代的INT8和4位方案到底是咋回事之前,先让咱们用自己的GPU看看量化到底有多厉害吧。下面这个短短的脚本会用三种不同的方式加载同一个TinyLlama-1.1 B检查点:全精度FP16、8位INT8和4位NF4,然后:

  • 记录每种版本占用了多少显存
  • 对短文本生成操作计时几次
  • 打印出一行总结,让你能直观比较速度和内存的差别。

导入模块和选择模型

# 导入必要的模块
from transformers import (AutoModelForCausalLM, AutoTokenizer,BitsAndBytesConfig)
import torch, time, GPUtil, gc
  • transformers模块给我们提供了模型加载器和BitsAndBytes包装器。
  • GPUtil能读取实时的GPU内存。
  • ID设置为TinyLlama/TinyLlama-1.1B-Chat-v1.0(在FP16下约2GB)。如果你的显存有12GB或更多,可以取消注释Mistral那行哦。
# ID = 'mistralai/Mistral-7B-Instruct-v0.3'ID = 'TinyLlama/TinyLlama-1.1B-Chat-v1.0'
tok = AutoTokenizer.from_pretrained(ID, use_fast=True)

辅助函数:加载并基准测试

# 定义一个函数来加载模型并测试显存占用
def load_and_benchmark(label, kwargs):torch.cuda.empty_cache(); gc.collect()start_vram = GPUtil.getGPUs()[0].memoryUsedmodel = AutoModelForCausalLM.from_pretrained(ID, **kwargs)used = GPUtil.getGPUs()[0].memoryUsed - start_vramreturn model, used
  • 清空缓存,让每次加载都从同一个基准线开始。
  • 返回模型对象以及它给显存增加了多少GB。

辅助函数:运行几个提示词

# 定义一个函数来运行几次推理并记录时间
def run_inference(model, model_mem, tag, prompt="Tell me one fact about volcanoes.", n=3):times = []for _ in range(n):t0 = time.time()_ = model.generate(**tok(prompt, return_tensors="pt").to(model.device),max_new_tokens=100, do_sample=False,pad_token_id=tok.eos_token_id)times.append(time.time() - t0)avg = sum(times) / ntotal_vram = GPUtil.getGPUs()[0].memoryUsedprint(f"{tag:>6}: {avg:>5.2f}s avg | {model_mem:>4.1f} GB model | {total_vram:>4.1f} GB total")return avg, model_mem
  • 确定地生成文本(do_sample=False),这样时间才好比较。
  • 打印三个数字:平均延迟、模型的增量大小以及显存占用。

定义三种加载配置

configs = [("FP16", {"torch_dtype": torch.float16, "device_map": "auto"}),("INT8", {"quantization_config":BitsAndBytesConfig(load_in_8bit=True),"device_map": "auto"}),("NF4",  {"quantization_config":BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_quant_type="nf4",bnb_4bit_compute_dtype=torch.float16,bnb_4bit_use_double_quant=True),"device_map": "auto"})
]

运行实验

# 打印实验的标题和分隔线
print("Quantization Effectiveness Comparison")
print("="*50)
print(f"{'Type':>6}: {'Time':>8} | {'Model':>11} | {'Total':>11}")
print("-"*50)results = []
for name, kwargs in configs:model, mem = load_and_benchmark(name, kwargs)avg, _     = run_inference(model, mem, name)results.append((name, avg, mem))del model; torch.cuda.empty_cache(); gc.collect()

打印一行总结

# 提取FP16的基准时间和内存占用
fp_time, fp_mem = results[0][1], results[0][2]
print("\nSummary")
print("-"*30)
for name, t, m in results:print(f"{name}: {fp_time/t:>4.2f}× speed | {fp_mem/m:>4.2f}× memory")

我得到的结果是:

Quantization Effectiveness Comparison:
==================================================Type:       Time |           Model |       Total
--------------------------------------------------FP16:  0.16s avg | 2346.0 GB model | 3321.0 GB totalINT8:  0.07s avg | 1288.0 GB model | 2387.0 GB totalNF4:  0.07s avg | 1072.0 GB model | 2171.0 GB totalSummary:
------------------------------
FP16: 1.00x speed, 1.00x memory efficiency
INT8: 2.24x speed, 1.82x memory efficiency
NF4: 2.34x speed, 2.19x memory efficiency

5. 大型语言模型的量化

在刚才的小实验里,咱们看到8位和4位格式能把TinyLlama的内存占用减少2-4倍呢。要是想在大型语言模型(LLM)上玩同样的把戏,可就难多了,因为Transformer架构有些古怪的地方,会把简单的线性量化给搞砸。

大型语言模型量化面临的挑战

和计算机视觉模型里表现良好的激活值不同,LLM有好些特性让简单的量化变得困难:

  • 异常激活值:有些通道的激活值能比其他通道高出100倍。要是你硬要把所有值都塞进一个整数网格里,大部分编码就浪费在空白区域啦 [arXiv]
  • 每个通道都不一样:注意力头和MLP行都有自己的尺度,要是“一刀切”地用同一个尺度,准确率可就遭殃了 [OpenReview]
  • 小错误会滚雪球:在自回归模型里,一个错的标记会改变后面所有的标记,所以即使是小的权重误差也很重要。

为了解决这些挑战,出现了三种流行的后训练量化工具包:

  • GPTQ(Frantar 22)
  • AWQ(Lin 23)
  • SmoothQuant(Xiao 22)

这三种方法都是在训练之后运行的,最多只需要几百个样本提示词,而且压根不碰梯度。

GPTQ:生成式预训练Transformer的量化

GPTQ示意图

GPTQ [Frantar et al. 2023] 是把经典的最优大脑量化算法改编给Transformer架构用的。关键的洞见是逐层量化权重,同时通过调整剩下的未量化权重来补偿量化误差。

算法如下:

  1. 校准:把一个小数据集(通常是128个样本)通过模型,收集激活值的统计信息。
  2. 逐层量化:对于每个Transformer层:
    • 量化一组权重
    • 测量量化误差
    • 更新剩下的权重,以最小化误差对层输出的影响。
  3. 4位效率:把多个4位权重打包到一个内存字里,方便GPU快速访问。

优点:准确率保留得非常好(通常小于1%的退化),能和任何4位格式搭配,不需要重新训练。

AWQ:激活感知权重量化
在这里插入图片描述

AWQ [Lin et al. 2023] 采用了一种不同的方法:与其事后修正量化误差,不如一开始就防止它们出现。这个方法会识别出“关键”权重(那些对重要激活值贡献最大的权重),然后给它们应用通道级的缩放,减少它们的量化误差。不太重要的权重就可以更激进地量化啦。

关键创新:AWQ发现大约1%的权重就能撑起模型的大部分能力。通过用更高精度(或者更好的缩放)保留这些权重,整个模型就能保持性能啦。

SmoothQuant:激活值和权重量化

SmoothQuant示意图

标准的方法是把权重量化到INT8,但激活值还保留在FP16,这就造成了一个混合精度的瓶颈。SmoothQuant [Xiao et al. 2023] 通过用不同的方式处理异常值,实现了全INT8推理。

它不是保留异常值,而是通过把难度从激活值转移到权重上来“平滑”异常值:

  • 给激活值应用通道级的缩放(减少异常值)
  • 给权重应用反向缩放(权重更能扛得住这个调整)
  • 这样一来,激活值和权重都能量化到INT8,而且不会损失准确率

这种方法特别适合部署场景,因为全INT8推理能带来显著的速度提升。

结语

量化已经成为在消费级硬件上运行大型语言模型最实用的解决方案啦,它能立刻把内存减少4-8倍,同时还能保持模型的质量。从咱们动手做的实验里,咱们也看到了INT8和4位技术能把需要昂贵服务器GPU的模型变成能在笔记本上舒舒服服运行的模型。

从基础的线性量化发展到像GPTQ、AWQ和SmoothQuant这样复杂的方法,反映了这个领域的成熟。这些可不是学术上的奇思妙想,而是能直接用在生产环境里的工具,它们能在大大减少计算需求的同时保留模型的智能。最吸引人的是它们的易用性,现代的量化方法只要几行代码就能作为即插即用的替代品。

模型越来越大,但多亏了量化,它们也越来越容易接触到了。这种组合有望加速整个领域的创新呢。


http://www.hkcw.cn/article/WDVRlkWnfv.shtml

相关文章

PHP舆情监控分析系统(9个平台)

PHP舆情监控分析系统(9个平台) 项目简介 基于多平台热点API接口的PHP实时舆情监控分析系统,无需数据库,直接调用API实时获取各大平台热点新闻,支持数据采集、搜索和可视化展示。 功能特性 🔄 实时监控 …

贪心算法应用:多重背包启发式问题详解

贪心算法应用:多重背包启发式问题详解 多重背包问题是经典的组合优化问题,也是贪心算法的重要应用场景。本文将全面深入地探讨Java中如何利用贪心算法解决多重背包问题。 多重背包问题定义 **多重背包问题(Multiple Knapsack Problem)**是背包问题的变…

AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年6月2日第96弹

从今天开始,咱们还是暂时基于旧的模型进行预测,好了,废话不多说,按照老办法,重点8-9码定位,配合三胆下1或下2,杀1-2个和尾,再杀4-5个和值,可以做到100-300注左右。 (1)定…

布隆过滤器

文章目录 布隆过滤器(Bloom Filter)详解:原理、实现与应用场景一、引言二、布隆过滤器的基本原理1. 数据结构2. 插入操作3. 查询操作4. 误判率 三、布隆过滤器的实现四、布隆过滤器的应用场景1. 网络爬虫2. 缓存穿透防护3. 垃圾邮件过滤4. 分…

给stm32cubeide编译出来的bin文件追加crc32

在工程目录下创建ci目录&#xff0c;将AddCrc32.exe丢进去&#xff0c;在stm32cubeide的properties----C/C Build----Settings----Build Steps----Post-build steps Command:添加AddCrc32.exe的路径: source code如下&#xff1a; #include <stdio.h> #include <stdl…

算法-集合的使用

1、set常用操作 set<int> q; //以int型为例 默认按键值升序 set<int,greater<int>> p; //降序排列 int x; q.insert(x); //将x插入q中 q.erase(x); //删除q中的x元素,返回0或1,0表示set中不存在x q.clear(); //清空q q.empty(); //判断q是否为空&a…

网络地址转换

网络地址转换 网络地址转换(Network Address Translation&#xff0c;NAT)的功能是将企业内部自行定义的私有IP地址转换为Internet上可识别的合法IP地址。由于现行IP地址标准--IPv4的限制&#xff0c;Internet面临着IP地址空间短缺的问题&#xff0c;因此从ISP申请并给企业的每…

4.大语言模型预备数学知识

大语言模型预备数学知识 复习一下在大语言模型中用到的矩阵和向量的运算&#xff0c;及概率统计和神经网络中常用概念。 矩阵的运算 矩阵 矩阵加减法 条件&#xff1a;行数列数相同的矩阵才能做矩阵加减法 数值与矩阵的乘除法 矩阵乘法 条件&#xff1a;矩阵A的列数 矩阵…

leetcode hot100刷题日记——35.子集

解答&#xff1a; 方法一&#xff1a;选or不选的dfs&#xff08;输入视角&#xff09; 思路&#xff1a;[1,2,3]的全部子集可以看成是对数组的每一位数字做选择。 eg.空集就是一个数字都不选&#xff0c;[1,2]就是1&#xff0c;2选&#xff0c;3不选。 class Solution { pub…

【数据库】关系数据库标准语言-SQL(金仓)下

4、数据查询 语法&#xff1a; SELECT [ALL | DISTINCT] <目标列表达式> [,<目标列表达式>] … FROM <表名或视图名>[, <表名或视图名> ] … [ WHERE <条件表达式> ] [ GROUP BY <列名1> [ HAVING <条件表达式> ] ] [ ORDER BY <…

好用的C/C++/嵌入式 IDE: CLion的下载安装教程(保姆级教程)

CLion简介 CLion是由著名的JetBrains公司开发出的一个C/C的IDE。它原是付费软件&#xff0c;但在最近(指2025年5月)开放了非商业用途免费&#xff0c;就像WebStorm、Rider、RustRover等。 除了这些&#xff0c;JetBrains的IntelliJ IDEA(社区版)和PyCharm(社区版)也是免费的。…

SpringBoot统一功能处理

1.拦截器 拦截器是Spring框架提供的核心功能之一,主要是用来拦截用户的请求,在指定方法前后,根据业务需要执行预先设定的…

prometheus v3.4.1正式发布!解析全新特性与安装指南,打造高效云原生监控体系

一、引言 随着云原生时代的快速发展&#xff0c;监控系统成为保障业务平稳运行的核心利器。作为CNCF&#xff08;Cloud Native Computing Foundation&#xff09;旗下的开源监控项目&#xff0c;Prometheus凭借其卓越的多维数据模型、灵活强大的查询语言及自主运行的架构设计&a…

PCA(K-L变换)人脸识别(python实现)

数据集分析 ORL数据集&#xff0c; 总共40个人&#xff0c;每个人拍摄10张人脸照片 照片格式为灰度图像&#xff0c;尺寸112 * 92 特点&#xff1a; 图像质量高&#xff0c;无需灰度运算、去噪等预处理 人脸已经位于图像正中央&#xff0c;但部分图像角度倾斜&#xff08;可…

资源预加载+懒加载组合拳:从I/O拖慢到首帧渲染的全面优化方案

简介 在移动应用开发领域,首帧渲染性能已成为用户体验的关键指标之一。根据2025年最新行业数据,首屏加载时间每延迟1秒,用户跳出率可能增加32%,直接影响应用评分和留存率。当应用启动时,布局解析、图片解码等I/O操作往往成为首帧渲染的主要瓶颈,导致用户看到白屏或黑屏时…

【Doris基础】Apache Doris中的Coordinator节点作用详解

目录 1 Doris架构概述 2 Coordinator节点的核心作用 2.1 查询协调与调度 2.2 执行计划生成与优化 2.3 资源管理与负载均衡 2.4 容错与故障恢复 3 Coordinator节点的关键实现机制 3.1 两阶段执行模型 3.2 流水线执行引擎 3.3 分布式事务管理 4 Coordinator节点的高可…

【基于阿里云搭建数据仓库(离线)】IDEA导出Jar包(包括第三方依赖)

1.双击"package”即可进行打包呈jar 2.双击后就会自动打包生成jar了&#xff0c; 生成的jar在这个目录下 3.右击&#xff0c;点击“复制路径/引用”&#xff0c;即可获得“绝对路径”、“根路径”等相关信息

id()函数:窥探Python变量内存地址的奥秘

在Python程序设计中&#xff0c;变量、对象和内存是紧密相连的核心概念。理解变量的内存地址&#xff0c;是理解Python变量本质、内存管理与性能优化的关键。Python内置函数id()&#xff0c;作为变量与对象身份&#xff08;identity&#xff09;的“指纹识别器”&#xff0c;为…

MySQL中的事务

事物特性 原子性:事物时最小的执行单位&#xff0c;不允许分割。事物的原子性确保动作要么全部完成&#xff0c;要么完全不起作用&#xff0c;如果在执行过程中发生错误&#xff0c;会被回滚到事物开始前的状态&#xff0c;就像这个事务从来没有执行过一样。一致性&#xff1a…

像素转换案例实战

本案例介绍像素单位的基本知识与像素单位转换API的使用。通过像素转换案例&#xff0c;向开发者讲解了如何使用像素单位设置组件的尺寸、字体的大小以及不同像素单位之间的转换方法。主要功能包括&#xff1a; 展示了不同像素单位的使用。展示了像素单位转换相关API的使用。 …