先把话说在前头这篇文章不是为了给你背一张“FP32为什么比FP16精度高”的教科书表格而是为了让真正跑模型、部署模型的人在看到int8、fp16、fp8、int4这些参数时不再发怵。量化quantization这几年几乎成了模型发布帖的标配词从蒸馏压缩到本地部署谁都要提一嘴“量化后显存减半”。但数字变小了背后到底发生了什么变化很多人其实是懵的明明模型原本跑得好好的凭什么int4就能塞进小显卡为什么有时候量化完速度反而不升反降这些问题如果你也有那这篇文章应该能给你捋清楚。这篇内容适合两类人一类是刚接触模型部署打算把开源模型放在自己电脑或服务器上跑的小白另一类是已经在用FP16/INT8但遇到精度下降、算子不兼容、速度不达标等问题想系统搞明白原因的朋友。我会先把五种格式的原理讲透再带你把ONNX、GPTQ、AWQ这些实操路线走一遍最后把我在实际部署中踩过的坑和排查思路全部摊开讲。1. 为什么模型要量化量化的本质是什么1.1 先搞清树根FP32在底层长什么样子FP32就是32位浮点数计算机里用一个符号位、8个指数位、23个尾数位来表示一个数。它能表示的范围大约是1e-38到3e38精度大概是小数点后7位有效数字。深度学习模型为什么默认用它一个很现实的原因是训练时梯度变化幅度极大有时候会非常小有时候又会突然涨好几个数量级FP32的动态范围大能兜住这种剧烈变化。可以简单理解为FP32是一把能伸缩的尺子既能量亿分之一的小数也能量万亿级的大数。但这种灵活是有代价的一个权重就要占4字节。以现在的主流大语言模型为例7B模型光权重就有70亿个参数FP32格式下需要28GB存储空间一块24GB的消费级显卡直接放不下。所以实际训练中通常用混合精度推理时则要想办法把体积进一步压下来。这也是量化首先解决的第一件事让模型塞得进显存。1.2 量化的数学原理把连续的尺子换成固定刻度量化的核心逻辑并不复杂就是把连续浮点数映射到一组离散的整数或低精度浮点数上。拿最常见的INT8举个例子INT8只有256个整数等级范围是[-128, 127]。如果一组FP32权重分布在[-1.0, 1.0]我需要找一个scale值把浮点数值缩放成整数scale 1.0 / 127 ≈ 0.00787量化后的整数 q round(r / scale)反量化 r_hat q * scale比如原始数值0.5除以scale得到63.5四舍五入成64再乘scale得到约0.5039和原始值差了一点点。这个差值就是量化误差。所有量化算法本质上都是在做同一件事想办法让这个误差尽量小小到不影响最终输出。神经网络对这类误差的容忍度比想象中高很多。因为网络不是精确计算器更像一个鲁棒的近似器权重和激活里混入一点噪声时通常只改变输出的置信度而不改变最终的分类标签或生成的语义。这也解释了为什么我们可以放心把FP32模型压到INT8甚至INT4。1.3 为什么量化能带来实打实的性能收益量化带来的收益主要有两个来源。第一是存储和带宽降低。模型推理时需要从内存或显存里反复读取权重权重从FP32变成INT8后体积缩到四分之一读取时间也会大幅缩短。尤其大语言模型是典型的“带宽饥饿型”应用生成每个token时都要把全部权重扫一遍权重缩小四份之一生成速度很可能直接翻倍。第二是硬件算力提升。现在主流的CPU、GPU、NPU都对低精度计算有专门优化比如GPU上的Tensor Core、CPU上的AMX指令INT8的峰值算力通常能达到FP32的数倍。两个因素叠加量化后的收益自然非常明显。当然量化不是免费午餐。精度下降、算子不兼容、额外反量化开销都是代价。接下来把五种格式一个一个拆开看你就能理解每个数字背后到底该怎么选。2. INT4、INT8、FP8、FP16、FP32五种格式逐一拆解2.1 FP32与FP16/BF16训练与推理的分岔路口FP32虽然精度最高但在实际推理部署中的性价比已经越来越低。FP16半精度浮点数用16位存储包含1个符号位、5个指数位、10个尾数位。它的动态范围比FP32小很多大概在6e-5到65504之间训练时容易出现梯度下溢的问题。所以行业内才发明了“损失缩放loss scaling”技巧在反向传播时把梯度放大更新参数时再缩小回来。推理阶段没有这个问题因为前向传播的数值范围通常比较稳定FP16表现很好显存占用直接减半。BF16是另一种16位格式用1个符号位、8个指数位、7个尾数位指数位数量和FP32一样所以它能覆盖的动态范围跟FP32几乎一致但精度低不少。BF16主要用于训练避免梯度下溢推理场景用得不多。另一个值得注意的点是现代GPU对FP16/BF16有Tensor Core加速计算吞吐通常是FP32的两倍以上。所以如果你的显卡支持且模型不太大FP16往往是推理部署的最低成本起点。2.2 INT8目前最普及的推理量化格式INT8这些年一直是推理部署的绝对主力。一个很重要的原因是硬件支持最广从NVIDIA GPU到Intel CPU再到各种NPU芯片几乎都内置了针对INT8计算的高效指令或硬件单元。软件生态也最成熟ONNX Runtime、TensorRT、OpenVINO、llama.cpp等主流框架都有完善支持。INT8量化分成动态和静态两种。动态量化会提前把权重离线量化为INT8但激活值比如矩阵乘法中的输入张量在推理时实时计算scale再量化因此不需要校准数据实现简单适合以权重开销为主的线性层、矩阵乘操作。静态量化则把权重和激活都提前量化在推理前通过一组校准数据确定激活的量化参数推理时不额外计算scale性能更好但需要额外准备校准集。实际使用时小模型或CV模型一般优先考虑静态量化大模型的很多场景则选择动态量化或带校准数据的weight-only方案。校准集的选择直接决定量化效果。常见方法有minmax、百分位、KL散度等。TensorRT的校准器就使用KL散度来选择阈值尽可能保留原始分布的相对熵。经验上校准集不需要太大几百到几千条有代表性的数据就足够但覆盖面一定要广如果只有少数几种固定模板量化后的模型在处理真实输入时就会现出原形。2.3 FP8新一代硬件上的甜点FP8是这两年随着Ada、Hopper、Blackwell等新架构显卡出现的新格式。它并不是一个单精度格式而是包含两种变体E4M34位指数、3位尾数和E5M25位指数、2位尾数。简单说E4M3精度更高适合前向计算和推理E5M2动态范围更大适合反向传播时保存梯度。FP8相比INT8有一个天然优势动态范围大不少不太容易出现某个极端权重直接被clip掉的情况。所以在很多模型上FP8量化后的效果比INT8更稳尤其在权重分布存在明显长尾的模型中。硬件上RTX 40系列、RTX 50系列以及专业卡都提供了FP8 Tensor Core加速实测中部分模型的FP8运行速度甚至能接近INT8同时精度损失更小。如果你的显卡较新且框架支持FP8是下一个值得优先尝试的量化格式。2.4 INT4把权重压到极限的做法INT4听起来很诱人4bit存储意味着比FP16小四倍比INT8小两倍。但直接做naive四舍五入量化精度会崩得没法看。所以实际部署中使用的INT4量化几乎都不会是简单的round操作而是经过调优的算法常见的有GPTQ、AWQ以及GGUF中定义的Q4_K_M这类组合方案。GPTQ的核心思路是利用二阶信息Hessian矩阵逐层调整量化权重尽量补偿由于rounding带来的误差。AWQ的思路则不同它发现并不是所有权重通道都同样重要会根据激活值的大小保留某些更重要的通道的精度通过per-channel缩放来保护这些通道不被过度量化。这些算法听上去很高深但使用体验已经非常平民化HuggingFace上有很多量化好的模型可以直接下载transformers加载时也几乎无缝。需要纠正一个常见误区INT4推理时权重虽然按4bit存储但计算时通常会反量化回FP16/BF16再做矩阵乘法。这意味着INT4主要省的是显存和带宽并不一定会直接提升纯算力。对于大语言模型这种带宽瓶颈型应用省带宽就是省时间收益依然明显。格式bit数7B权重存储典型用途备注FP323228GB训练基线、调试精度最高但体积大FP161614GB训练/推理默认普遍通用BF161614GB训练首选之一动态范围大、精度略低FP887GB新硬件训练/推理E4M3和E5M2分场景使用INT887GB推理部署主力软硬件支持最广INT443.5GBLLM极致压缩需要GPTQ/AWQ等算法支撑3. 实操从模型文件到量化部署3.1 用ONNX Runtime把模型变成INT8/FP16ONNX Runtime是目前最通用的推理引擎之一量化操作也相当简单。先把PyTorch模型导出成ONNX然后调用量化接口。动态量化的代码通常长这样from onnxruntime.quantization import quantize_dynamic, QuantType quantize_dynamic( model_inputmodel_fp32.onnx, model_outputmodel_int8.onnx, weight_typeQuantType.QInt8, )这个函数会遍历模型里的算子把能量化的算子权重转成INT8推理时再动态反量化。优点是省事但性能上限不高。如果你希望获得更好的性能可以试试静态量化先准备一个校准数据读取器from onnxruntime.quantization import quantize_static, CalibrationDataReader, QuantType class DataReader(CalibrationDataReader): def __init__(self, dataloader): self.iter iter(dataloader) def get_next(self): try: return next(self.iter) except StopIteration: return None quantize_static( model_inputmodel_fp32.onnx, model_outputmodel_int8_static.onnx, calibration_data_readerDataReader(calib_dataloader), quant_formatQuantFormat.QDQ, per_channelTrue, weight_typeQuantType.QInt8, activation_typeQuantType.QInt8, )校准数据要构造到和模型输入完全一致的格式。比如图像模型要处理好归一化文本模型要使用相同的tokenizer。静态量化的好处是激活值也离线量化推理时不需要频繁计算scale速度更快。如果只想做FP16转换可以用onnxconverter_commonfrom onnxconverter_common import float16 model_fp16 float16.convert_float_to_float16(model_fp32)这里有个常见的坑某些算子对FP16特别敏感比如LayerNorm之类的归一化操作在half精度下可能数值不稳定。通常需要用block_list参数把这类算子排除在转换范围之外。3.2 大语言模型的量化路线GPTQ、AWQ与GGUF大语言模型的量化工具链和传统ONNX略有不同但现在也已经很成熟。想用GPTQ量化模型可以借助AutoGPTQ和transformers来加载from transformers import AutoModelForCausalLM, AutoTokenizer from auto_gptq import AutoGPTQForCausalLM tokenizer AutoTokenizer.from_pretrained(your_model_path) model AutoGPTQForCausalLM.from_quantized( your_model_path, use_tritonFalse, device_mapauto, )AWQ方案类似用AutoAWQ即可。这两类量化好的模型在HuggingFace上非常多下载时注意看模型的量化配置其中group_size通常为128或32desc_act决定是否按激活列排序。group_size越小量化粒度越细效果越好但内存和速度开销也会增加。如果目标平台是CPU或者想跨平台跑GGUF格式是更稳妥的选择。llama.cpp生态里的Q4_K_M、Q5_K_M、Q8_0等量化等级各有侧重点简单选择原则是内存有限就选Q4_K_M效果优先就选Q8_0Q5_K_M则介于二者之间。这套方案的好处是运行环境非常轻量单文件就能跑不需要装一堆Python依赖。3.3 量化后的效果评估不要只盯着PPL很多人量化完模型拿perplexityPPL测一下看到数值只涨了一点点就觉得没问题。但PPL只能反映整体分布层面的差异并不能完全代表真实业务质量。尤其对生成式模型PPL接近不代表具体输出不会出错代码模型可能会少写一个分号数学模型可能推导步骤直接乱套。我的习惯是准备一组固定prompt分别用原始精度模型和量化后模型跑一遍进行人工对比。比如让模型解同一个数学题、写一段带条件的代码、做一段摘要。如果有评测集跑一套完整benchmark自然更好。这一步虽然不能量化到每个指标但至少能帮你拦住中位数的退化。还有一个容易被忽略的点量化后的模型如果继续做有监督微调有时能把精度损失补回来一部分。虽然这在训练成本上不划算但如果你已经有一个量化好的模型且业务指标差一截可以适度尝试。3.4 一个量化模型的实际性能记录模板做量化部署时最忌讳“凭感觉”判断速度快不快。建议按下面的表格记录几项核心指标配置权重格式显存占用首Token延迟生成速度PPL或任务得分基线FP1614GB55ms30 tok/s12.3方案AINT87.2GB42ms38 tok/s12.5方案BINT43.8GB48ms36 tok/s13.1表格里每一行都要固定测试条件输入长度、输出长度、batch大小、是否使用流式输出、测试硬件。只有控制变量你才能判断当前瓶颈究竟在显存、内存带宽还是算子实现。4. 不同量化格式的硬件适配与性能收益4.1 CPU、GPU、NPU分别该用什么精度CPU的场景里INT8几乎是标准答案。Intel从Ice Lake开始加入VNNI指令到Sapphire Rapids加入AMX指令对INT8矩阵计算加速明显。用ONNX Runtime OpenVINO跑INT8模型吞吐通常比FP32能提升好几倍。INT4在CPU上则要看框架是否支持目前llama.cpp对CPU做了很多优化GGUF的INT4量化在CPU上也有不错表现但其他框架支持度不一。GPU上要分新旧卡。老一点的Turing、Ampere架构对INT8支持很好Tensor Core吞吐大概是FP16的两倍而Ada、Hopper、Blackwell这些新架构的FP8表现更亮眼。RTX 50系列在FP8上的速度优势尤其明显。要注意的是FP16在新旧GPU上都有比较均衡的兼容性所以如果没有特殊原因GPU推理的最低起点建议放到FP16而不是FP32。NPU和各类AI加速卡的偏好也基本以INT8为主部分新型号提供FP16支持。如果你做边缘部署INT8永远是兼容性最好的选择。4.2 量化后显存、延迟、吞吐的收益幅度量化最直观的收益是显存。以7B模型为例FP16权重约14GBINT8约7GBINT4约3.5GB。但这是纯权重部分实际部署还要算上激活值、KV cache对大语言模型而言和运行时开销所以总显存下降幅度会略小于这个比例。如果你的batch较大KV cache开销会占很大比例权重量化的总收益会被稀释一点但仍然可观。延迟和吞吐受多个因素影响推理引擎、batch大小、算子实现、硬件指令。模型单次推理时首Token延迟通常对带宽不敏感量化带来的提升有限但长序列生成时权重读取量巨大量化收益就会非常明显。我的实测经验是在7B级别的LLM上从FP16切到INT8后生成速度普遍能提升20%到40%显存占用下降约一半切到INT4后生成速度提升幅度反而可能变小因为反量化开销开始成为新的瓶颈但显存优势仍然突出适合塞进较小显存的设备。4.3 如何快速记录和分析性能分析性能时我一般分三步。第一步监控硬件状态用nvidia-smi看显存、功耗和利用率nvidia-smi --query-gpumemory.used,utilization.gpu,power.draw --formatcsv -l 1第二步用推理引擎自带或自编脚本测延迟和吞吐。比如vLLM有benchmark脚本llama.cpp有perplexity和token速度测试ONNX Runtime可以用Python脚本连续跑几十遍求均值。第三步把不同精度的结果放到同一张表格里横向对比重点看“变化趋势”而不是单个数字。这里有一个经验测延迟时一定要丢弃第一次调用因为很多框架有warming up逻辑第一次往往包含了初始化开销。测吞吐时batch至少要尝试4、8、16等多个档位有些模型在小batch下INT8优势不明显但batch提升后优势会变大。5. 常见问题与排坑指南5.1 量化后模型输出变差怎么办如果你的模型量化后明显变傻、输出乱序、回答质量下降常见原因有三个校准集偏差、量化粒度过粗、模型本身对噪声过于敏感。校准集过于单一会导致激活量化参数不符合真实分布解决办法是换用覆盖面更广的校准数据量化粒度是指per-tensor还是per-channel一般来说per-channel效果远好于per-tensor如果框架支持优先打开per-channel如果这些调整后仍然不够考虑换用FP8或者升级到GPTQ/AWQ这类更复杂算法。还有一种情况是模型极小比如参数量只有几千万到一两亿这种模型对量化误差非常敏感压缩到INT4可能直接崩掉。这时候可以先把精度底线放在INT8不要盲目追求极致压缩。5.2 量化后速度不升反降是怎么回事这种情况我踩过不少次。首先要判断是不是量化没有生效。有些框架在不支持INT8的算子上会自动回退到FP32而你又没看日志结果模型说得头头是道跑起来却没任何优化。解决办法检查推理日志或profile输出看算子的执行精度。其次模型太小或batch太小时量化带来的计算收益不足以抵消反量化等额外开销反而会变慢。还有一个容易忽略的点某些框架在没有VNNI/AMX指令的CPU上跑INT8会比FP32还要慢。实践中建议优先确认模型算子是否真正跑在目标精度上再尝试增大batch。如果batch1和batch16的收益差异很大说明量化确实在计算环节生效只是batch太小时边际收益不明显。5.3 工具链报错与算子不支持怎么办常见的报错是“Quantization not implemented for op”或“Unsupported operator”原因是当前版本的推理框架还没有覆盖你这个模型里的某个算子。最直接的三个处理方向升级推理引擎版本把模型导出的opset版本调高比如导出ONNX时设置opset17把不支持的算子或者整段子图排到CPU执行。如果是在TensorRT上遇到算子不支持可以先尝试用onnx2trt转换时开启更高精度模式。这些报错看上去很吓人但大多数情况都只是版本匹配问题。如果你是新手我建议直接使用社区里已经验证过的模型文件而不是自己从零导出能省很多时间。5.4 什么情况下需要做量化感知训练QAT训练后量化PTQ处理了95%以上的场景但也有例外。如果目标模型用于语音识别、目标检测这类任务且精度要求极高或者模型本身很小、对数值扰动极度敏感PTQ可能怎么调都达不到标准。这时候就该考虑QAT量化感知训练了。QAT的原理是在训练过程中模拟量化噪声让模型参数主动适应低精度的数值范围。PyTorch里可以用torch.ao.quantization的fake_quantize模块来实现流程上先插入伪量化节点再正常训练和导出。QAT成本不低因为需要重新训练并且还要维护一套量化训练流程。所以我的建议永远是先PTQ把校准、粒度、精度格式这些都尝试一遍实在不行再考虑QAT。还有一个小技巧如果模型有微调计划可以先做个针对性的小模型蒸馏或任务微调再实施PTQ效果往往比直接QAT省时间。量化这件事说到底是一个精度、速度、显存三方权衡的问题。我自己的习惯是任何新模型上线前先拿FP16跑一遍业务冒烟测试记录baseline再试INT8如果显存吃紧或模型比较大再考虑INT4或FP8。每次只改一个变量所有结果写进表格。你最后选什么格式不是最重要的最重要的是你能清楚地知道每一个选择换来了什么、牺牲了什么。这种可控感才是量化部署最值钱的东西。