我写这套模型量化教程,不是要把你按在公式堆里。它更像一张地图:哪里是 FP32,哪里是 INT8、INT4,哪些收益真实,哪些坑要提前躲。我自己从实验脚本走到生产部署,踩过校准集选错、算子不支持、精度掉点的麻烦。这篇文章用第一人称把基础篇拆开,给后续 PyTorch、LLM、部署章节铺路。
1.1 什么是模型量化:从FP32到INT8、INT4的低比特表示
我最早理解量化,是从“把小数换成整数”开始的。神经网络里权重和激活值通常用 FP32 保存,每个数占 32 位。量化做的是低比特表示,把连续的浮点范围映射到 INT8 的 256 个格子,或者 INT4 的 16 个格子。映射需要 scale 和 zero point,公式大概是 q = round(x / scale) + zero_point。反量化时再乘回去、减掉零点。这里会丢信息,像把一张高清图压成小图标,轮廓还在,细节会变。
从学习者的角度看,FP32 到 INT8 是入门,INT8 到 INT4 是进阶。INT8 常被当作“安全区”,很多硬件都有整数指令。INT4 更激进,显存省得多,精度风险也大。对称量化和非对称量化会影响零点是否为零,逐张量量化和逐通道量化会影响粒度。我在做 CNN 时偏爱逐通道权重量化,Transformer 的激活值有时要更细看。
部署工程师会关心一件事:低比特表示不只是存储格式,它改变了计算方式。FP32 乘法换成 INT8 乘加,硬件能并行更多。INT4 还会牵扯打包、解包和混合精度。我的建议是先把 FP32 到 INT8 的映射搞明白,再碰 INT4。别急着追新格式,先把 scale、zero point、舍入误差这三样握在手里。
1.2 模型量化的核心收益:推理加速、显存压缩与边缘部署
我做过一个图像分类服务,FP32 模型在 GPU 上跑得动,换成 INT8 后延迟降了一截,吞吐量也上来了。推理加速来自整数运算和更少的内存带宽。显存压缩更直观:FP32 变 INT8,理论上是 4 倍压缩;变 INT4,理论上是 8 倍。对 LLM 来说,权重占大头,KV Cache 也吃显存,低比特压缩能让你在单卡上多塞几层、多跑几个并发。
边缘部署是另一个角度。手机、摄像头、MCU、NPU 这些设备,算力和内存都紧。一个 FP32 的 BERT 可能放不进端侧,INT8 后就有机会。我见过同事把模型量化后塞进安卓 NNAPI,也见过 TFLite 在树莓派上跑。收益真实,但别把它当免费午餐。量化误差会让某些样本掉点,异常激活值会放大误差,算子不支持会让你回退到 FP32。
从产品视角看,收益要换算成钱和体验。显存省下来,云成本降;延迟降下来,用户等待少;边缘能部署,隐私和离线可用性变好。我的习惯是同时记录准确率、延迟、吞吐量和内存峰值。只看一个指标,容易把优化做成数字游戏。
1.3 量化方法分类:动态量化、静态量化、训练后量化PTQ与量化感知训练QAT
我常用动态量化做快速验证。它在推理时动态计算激活值的 scale,权重可以提前量化。LSTM、线性层、部分 BERT 结构用动态量化很方便,代码改动少。代价是运行时多了一点计算开销。静态量化更常见:用校准集喂给模型,收集激活值分布,提前定好 scale 和 zero point。推理时不再动态算,速度更稳。校准集选得好不好,直接决定精度。
PTQ 是训练后量化,模型训练完再压。QAT 是量化感知训练,训练时插入伪量化节点,让模型学会适应低比特。PTQ 快,适合快速上线和实验;QAT 慢,适合精度要求高的场景。我在项目里会先跑 PTQ,看掉点多少,再决定要不要 QAT 微调。QAT 不是必须,但它能把一些掉点的模型拉回来。
多角度描述同一件事:研究者看 QAT 的梯度传播和伪量化建模;工程师看 PTQ 的校准成本;产品经理看上线时间。我的经验是,PTQ 和 QAT 不是对立关系,它们是一条流水线上的两道工序。动态量化、静态量化也会混用,比如权重静态、激活动态,或者部分层保持 FP32。关键是把每层的敏感度摸清,别一刀切。
1.4 模型量化教程的学习路径:数学基础、框架API与部署工具链
如果你问我模型量化教程怎么学,我会给一条不那么线性的路。数学基础要有:线性代数、概率统计、数值分析里的舍入误差。量化特有的数学包括 scale/zero point、仿射映射、KL 散度校准、MSE 最小化、直方图截断。别被公式吓到,先理解“用有限格子表示无限范围”这件事。我当年把 INT8 的 256 个格子画在纸上,很快就通了。
框架 API 这一块。PyTorch 有 Eager Mode、FX Graph Mode、TorchScript 量化;TensorFlow 有 TFLite 量化;ONNX 有量化工具。我的建议是选一个主框架深挖,PyTorch 生态现在很全,从 PTQ 到 QAT 都有例子。边学边跑小模型,别一上来就压 BERT 或 LLM。MNIST、CIFAR、小 CNN 能帮你快速看到精度和延迟变化。
部署工具链这块。ONNX Runtime、TensorRT、OpenVINO、TFLite、llama.cpp 各有脾气。你得知道量化格式怎么导出、算子怎么映射、硬件后端支持哪些位宽。我的路线是:先把 PyTorch 量化跑通,再导出 ONNX,放到 ONNX Runtime 或 TensorRT 跑。端侧就看 TFLite 或 NNAPI。大模型绕不开 GGUF、GPTQ、AWQ。学习路径不必严格按顺序,缺哪块补哪块。
1.5 量化精度评估指标:准确率、困惑度、延迟与吞吐量
评估量化效果,我从不只看准确率。分类任务看 Top-1、Top-5;检测看 mAP;分割看 mIoU;生成任务看 BLEU、ROUGE、人工评价。语言模型常用困惑度 PPL,PPL 涨一点可能还能用,涨很多就要查。我的做法是准备一个固定验证集,量化前后跑同一批样本,记录每个指标的变化。掉点 0.5% 和掉点 5% 是两回事。
延迟和吞吐量要一起看。延迟是单次推理耗时,吞吐量是单位时间处理多少请求。批处理能提高吞吐量,也会拉高延迟。显存峰值、CPU 占用、功耗也要记。我在服务化场景里会画延迟-吞吐量曲线,找业务能接受的拐点。边缘设备还要看内存占用和发热。指标之间会打架,调优就是找平衡。
多角度描述:算法同学盯精度,工程同学盯延迟,运维同学盯显存和稳定性。我的习惯是建一个表格,量化配置、指标、硬件、批次大小都写清楚。这样复现和对比都方便。基础篇到这里,后面我会进入 PyTorch 实战,把 PTQ 和 QAT 一步步跑通。模型量化教程的价值不在背概念,而在你能用这些概念做选择。 import torch import torch.nn as nn from torch.quantization import quantize_dynamic
class TinyLSTM(nn.Module):
def __init__(self, vocab=5000, emb=128, hid=256):
super().__init__()
self.embed = nn.Embedding(vocab, emb)
self.lstm = nn.LSTM(emb, hid, batch_first=True, bidirectional=True)
self.fc = nn.Linear(hid * 2, 2)
def forward(self, x):
x = self.embed(x)
out, _ = self.lstm(x)
return self.fc(out[:, -1, :])
model = TinyLSTM().eval() q_model = quantize_dynamic(
model,
{nn.LSTM, nn.Linear},
dtype=torch.qint8
) torch.save(q_model.state_dict(), "tiny_lstm_int8.pth")
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig import torch
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-chat-hf",
quantization_config=bnb_config,
device_map="auto",
) tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-chat-hf")
inputs = tokenizer("量化是什么?", return_tensors="pt").to("cuda") out = model.generate(**inputs, max_new_tokens=128) print(tokenizer.decode(out[0], skip_special_tokens=True))
上一章我把大模型量化的几条主流路线跑了一遍,从 GPTQ、AWQ 到 vLLM 和 llama.cpp,基本能覆盖单机和大规模服务的场景。接下来这一步才是真正决定能不能落地的环节——你把量化模型训好了,评测分数也好看,结果换成目标硬件跑不起来,或者延迟比 FP32 还高,这种翻车我见过太多次。这一章我按工程视角重新捋一遍,从推理引擎选型、硬件后端适配、性能调优,一直讲到上线后的监控和检查清单。
4.1 量化格式与推理引擎:ONNX Runtime、TensorRT、OpenVINO与TFLite
模型量化完之后,你手里其实不是一份“模型”,而是一份“模型加量化格式”的组合。PyTorch 训练出来的 QAT 模型带伪量化节点,导出成 ONNX 之后量化信息就藏在 QDQ 节点里,ONNX Runtime 能直接吃。ONNX 的好处是跨平台,Windows、Linux、ARM 都能跑,做工业软件和边缘盒子的时候我经常选它。量化的粒度有 per-tensor、per-channel,ORT 对 per-channel 支持很好,精度损耗比 per-tensor 小一截。
TensorRT 是 NVIDIA 平台的性能天花板。INT8 量化走的是校准加显式量化两步,权重用 PTQ,激活用校准集统计 KL 散度或最小化 MSE 来定 scale。它对算子融合的力度最大,Conv+BN+ReLU 能合成一个 kernel,量化之后这些融合算子直接跑 INT8 Tensor Core。代价是编译慢,模型结构一变就要重新 build engine,ONNX 转换环节还经常报不支持某些算子,得自己写 plugin。
OpenVINO 是我在 Intel 平台上最常用的工具。它对 INT8 的支持相当成熟,NNCF 做 QAT 或 PTQ 之后直接喂给 OpenVINO runtime,CPU 上能拿到三到四倍的加速比。它支持从 ONNX、PaddlePaddle、TensorFlow 多种格式导入,中间做图优化和量化层融合。工业视觉和安防场景里,很多客户是 Xeon 加核显的配置,OpenVINO 几乎是默认选项。
TFLite 走的是移动端和嵌入式的路。它量化之后模型文件能压到原模型的四分之一,Android、iOS、Raspberry Pi 都能加载。TFLite 的量化分 full integer 和 dynamic range 两种,前者连激活都用 INT8,需要代表性数据集做校准。手机端跑图像分类和轻量检测,TFLite INT8 在骁龙芯片上延迟能压到十几毫秒。用之前得看一眼目标设备的算子支持表,不是所有 op 都能在 INT8 下跑。
4.2 硬件后端适配:CPU、GPU、NPU与移动端量化算子支持
同一份量化模型丢到不同硬件上,跑出来的效果天差地别。CPU 这块,x86 的 AVX-512 VNNI 指令集能把 INT8 矩阵乘的吞吐拉到很高,ARM 那边是 dotprod 和 i8mm 扩展。没有这些指令集的老 CPU,量化之后可能比 FP32 还慢,因为反量化开销吃掉了收益。我之前在一台没有 VNNI 的服务器上跑 ONNX Runtime INT8,推理速度和 FP32 几乎持平,后来换到支持 VNNI 的机器才看到明显提升。
GPU 侧主要看是否支持 INT8 Tensor Core。从 Turing 架构开始,Tensor Core 才原生支持 INT8,Volta 之前的卡只能靠 DP4A 指令,性能差一大截。大模型量化里的 W4A16 在 GPU 上走的是反量化加 FP16 GEMM 的路子,本质没用到 INT8 算力,加速主要来自显存带宽节省。TensorRT 的 INT8 才是真用上了 Tensor Core,但只适用于 CNN 类和对量化友好的 Transformer 小模型。
NPU 是最难搞的一环。华为昇腾、高通 Hexagon、寒武纪、地平线,每家对量化算子支持的列表都不一样。有些 NPU 只支持 per-tensor 对称量化,不支持 per-channel,你用 PyTorch QAT 训出来的 per-channel 模型转过去可能直接失败或者精度崩掉。我一般会在量化之前先看目标 NPU 的算子支持文档,把不支持的算子替换或拆开,然后再走量化流程。这个工作量大但省不了。
移动端又是另一套逻辑。手机芯片里 CPU、GPU、DSP、NPU 混在一起,一个模型可能被拆到不同硬件上跑。Android NNAPI 和 Apple Core ML 都在做这个调度层,但它们对量化算子的支持参差不齐。高通平台的 QNN SDK、联发科的 NeuroPilot 都有自己的量化工具链,走官方路径最省心。我踩过一个坑,用 PyTorch 直接量化的模型在骁龙 888 上跑,某个反量化算子在 NNAPI 里没有实现,退回到 CPU 执行,整个 pipeline 延迟翻倍。
4.3 性能调优:算子融合、内存布局、量化粒度与线程配置
量化完之后做不做调优,性能差距能有几倍。算子融合是第一刀。Conv 加 BN 加 ReLU 三个算子合一个,kernel 启动次数少了,中间张量也不用落显存。量化之后融合还能减少反量化次数——不融合的话每个算子都要 dequant 输入再 quant 输出,来回折腾。TensorRT 和 OpenVINO 的图优化器会自动做这些,ONNX Runtime 需要在 session options 里开 graph_optimization_level。
内存布局这块容易被忽略。NHWC 和 NCHW 的选择会直接影响量化算子的性能,很多移动端芯片的卷积加速器只吃 NHWC。你从 PyTorch 导出模型默认是 NCHW,转 TFLite 或者某些 NPU 的时候需要显式转换。转换本身不改数值,但布局变了之后算子调度路径就变了,性能差别能到百分之二三十。另外要注意的是内存对齐,INT8 张量按 16 或 32 字节对齐能让 SIMD 加载少几条指令。
量化粒度是精度和性能的平衡点。per-tensor 量化最省事,硬件支持最广,但激活分布不均匀的时候精度掉得厉害。per-channel 量化精度好,x86 和大多数 GPU 也支持,但某些 NPU 不认。per-group 量化在大模型里常见,权重按 128 或 64 个一组共享 scale,精度接近 per-channel 但存储开销小。选粒度的时候不能只看精度,还得看目标硬件的实际支持情况,文档里写支持不代表性能好。
线程配置是收尾环节,也是很多人调优到最后才想起来的地方。ONNX Runtime 的 intra_op_num_threads 控制算子内部并行度,inter_op_num_threads 控制算子之间并行度。CPU 上跑的时候这两个参数直接影响性能。我之前在一个 16 核的机器上跑 INT8 模型,默认线程配置下延迟 20ms,把 intra 设成 8、inter 设成 2 之后降到 9ms。核数不是越多越好,线程抢锁和缓存失效会抵消并行收益。
4.4 生产环境部署监控:精度回退、版本管理与A/B测试
模型上线只是开始。量化模型比其他模型更容易出问题,因为精度回退可能只在特定输入分布下才暴露。上线第一天跑得好好的,第五天某个批次的输入触发了量化误差累积,输出开始偏。监控要盯两面:一是业务指标,比如推荐点击率、搜索相关性、对话满意度;二是模型指标,包括输出分布偏移、置信度分布、异常输出比例。
精度回退的检测得有基线。上线前跑一版 FP32 或者未量化的模型,把它的输出作为参考。线上流量采样一小部分同时过两个模型,算输出差异。差异超过阈值就告警。这种影子模式对延迟有要求,一般只在低峰期开,或者按 1% 到 5% 的采样率跑。我做过一个搜索排序的量化模型,线上跑了一周没发现问题,采样比对之后才发现长尾 query 的排序结果有 8% 的位置偏移。
版本管理在量化场景下比普通模型更复杂。同一个模型可能有 FP32、FP16、INT8、INT4 多个版本,还有不同量化配置和校准集训出来的变体。命名规范和存储隔离要提前定好,我一般按 模型_版本_精度_量化方法_日期 的格式存,同时记录校准集 hash 和量化脚本的 commit id。这样出问题能追溯到具体哪次改动。
A/B 测试是量化上线的最后一道关。把线上流量按比例分给量化模型和原始模型,跑一到两周,用统计显著性判断是否切换。要看的指标包括业务核心指标、延迟 P50/P99、错误率、资源占用。量化模型在 P99 延迟上通常会好一些,但如果 P99 反而变差,可能是某些请求触发了慢路径,得单独查。A/B 期间保持回滚能力,一旦指标异常立刻切回原模型。
4.5 模型量化教程最佳实践:从实验到上线的检查清单
我把这些年踩坑总结出来的检查清单放出来,每次上线之前过一遍能省不少返工。
第一步是明确目标。你量化是为了省显存、降延迟还是上特定硬件?三个目标对应的量化方法和部署路径完全不同。省显存优先权重量化,INT4 就够;降延迟要看硬件支不支持 INT8 算力;上特定硬件必须先确认算子支持列表,别训完才发现跑不起来。
第二步是量化前的基线建立。跑一遍 FP32 模型的精度、延迟、显存占用,记下来。所有后续对比都以这个为准。精度评估要覆盖真实业务场景的样例,光看公开数据集不够。延迟测试要在目标硬件上做,开发机和部署机不是一个东西。
第三步是量化配置选型。方法上 PTQ 优先,效果不够再上 QAT。粒度上能 per-channel 就 per-channel,不能就 per-group,再不行才 per-tensor。校准集要覆盖目标输入分布,数量几百到几千条就够,质量比数量重要。量化完立刻跑精度评测,涨了 0.3 以内可以接受,超过 0.5 就得排查。
第四步是部署引擎适配。选引擎的时候先看目标硬件官方支持什么,TensorRT 配 NVIDIA、OpenVINO 配 Intel、TFLite 配移动端。引擎版本和驱动版本要锁定,升级引擎可能改变量化算子实现,性能会变。导出模型的时候检查量化节点是否完整保留,有些转换环节会悄悄丢掉量化信息退成 FP32。
第五步是性能调优和压力测试。算子融合、内存布局、线程配置逐项调,每一步记录前后延迟变化。压测要覆盖单请求延迟、批量吞吐、并发场景、长尾输入。特别关注 P99 延迟和超时率,量化模型的性能分布可能和 FP32 不一样。
第六步是上线监控和回滚预案。灰度发布,先切 1% 流量观察一天,没有问题再逐步放大。监控指标包括业务核心指标、模型输出分布、延迟分位数、错误率。回滚路径要提前准备好,模型文件、配置、流量切换脚本都要就位。量化模型出问题的时候往往是渐进的,不会一下子崩,盯紧趋势比盯阈值更重要。
这一章我讲的都是部署侧的硬骨头,代码量不大但坑多。你要是刚开始做量化部署,我建议先在一个小模型上把整条链路走通,从 PyTorch 量化到 ONNX 导出再到 ONNX Runtime 加载,跑通之后再换大模型和复杂引擎。下一章我会把量化工具链横向对比一遍,再把几个完整的项目实战从头到尾做一遍,包括端侧部署和大模型 API 服务搭建。 from llmcompressor.transformers import oneshot from llmcompressor.modifiers.quantization import GPTQModifier
recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"]) oneshot(model=model, dataset=calib_data, recipe=recipe, max_seq_length=2048)