最近被同一个问题问了好几次:手里已经有一个开源模型,跑FP16总觉得显存吃紧,量化到INT8又怕效果变差,到底该用什么精度?好不容易定下来,转头又纠结硬件怎么配,8G、16G、24G显卡看花了眼,越看越晕。我今天就把这套决策思路完整拆开聊一聊。
先给结论骨架:精度不是越低越好,也不是越高越好,它取决于你的瓶颈是显存放不下、是生成速度慢、还是任务精度敏感;硬件也不是越贵越好,要把显存容量、内存带宽、算力、成本和精度档位放在一起算。这篇文章适合两类人:一是准备把模型部署到服务端做推理的工程师,二是想用自己手头机器跑开源模型的开发者。我会从常见精度格式的原理讲起,按“摸清瓶颈—选精度—配硬件—跑评测”的顺序,给出一套可以直接照做的流程,最后附上我踩过的坑。
1. 先想清楚:你的瓶颈是显存、算力还是带宽
1.1 显存决定模型能不能塞进去
很多人问“精度选什么”的时候,真正的潜台词是“我的显存放不下模型,得想办法瘦身”。所以我习惯先算清楚一个模型在某个精度下到底占多少显存,而不是直接开始量化。
推理阶段最基础的公式是这样的:
权重显存(约)= 模型参数量 × 每参数字节数
FP32是4字节,FP16/BF16是2字节,INT8是1字节,INT4大约是0.5字节。以常见的7B模型为例,光权重部分就是下面这个数量级:
| 精度格式 | 每参数字节 | 7B权重显存 | 13B权重显存 |
|---|---|---|---|
| FP32 | 4 | 约28GB | 约52GB |
| FP16/BF16 | 2 | 约14GB | 约26GB |
| INT8 | 1 | 约7GB | 约13GB |
| INT4 | 0.5 | 约3.5GB | 约6.5GB |
这里只算了权重,实际推理时还要加上激活值、KV Cache和框架自身开销。KV Cache是大头,序列越长占得越多,8GB显卡跑7B模型INT4量化模型能启动,但把上下文拉到4K以上,显存照样可能爆掉。
所以第一步先别问“我要不要用INT8”,而是打开监控工具看一眼真实显存峰值:如果权重加上KV Cache和激活之后还有20%以上余量,可以优先考虑速度更好、精度更高的档位;如果刚好卡在边界,量化到INT8往往是性价比最高的选择;如果INT8都顶不住,那就要么INT4,要么直接换一张显存更大的卡。
1.2 为什么同一模型在不同卡上速度差很多:算力和带宽的博弈
显存决定了“能不能跑”,但“跑得快不快”另一套逻辑。大模型推理通常拆成两个阶段:
- Prefill阶段,也就是处理输入、生成第一个token,这个阶段计算密集,看显卡的算力,单位是TFLOPS。
- Decode阶段,也就是一个token一个token往外蹦,这时主要看你把权重喂给计算单元的速度,也就是显存带宽,单位是GB/s。
我见过不少朋友买了高算力卡,结果发现生成速度没有想象中快,原因就是decode阶段被带宽卡住了。好比仓库(显存)够大,传送带(带宽)太窄,货堆在仓库里运不出来。
几个典型显卡的规格,参数来自公开spec,大致是这样的:
| 典型GPU | 显存 | 显存带宽(典型值) | 适合的角色 |
|---|---|---|---|
| RTX 3090 | 24GB | 约936GB/s | 本地实验、中小模型推理 |
| RTX 4090 | 24GB | 约1TB/s | 消费级顶配、小业务服务 |
| A100 80GB | 80GB | 约2TB/s | 服务端多路并发、训练 |
| H100 80GB | 80GB | 约3.35TB/s | 高负载训练和推理 |
如果你的场景是长文本生成、对话式应用,内存带宽比峰值算力更值得关注。24GB显存的RTX 3090在有些场景下跑解码任务并不输给某些显存更大的专业卡,就是因为带宽差距没那么大。
2. 精度选择不是玄学:先看懂几种常见精度的区别
2.1 浮点数值:FP32、FP16、BF16、TF32是同一把尺子的不同刻度
很多人一听到“FP16精度比FP32低”就默认FP16效果一定差,其实没那么绝对。浮点数本身由符号位、指数位、尾数位组成:指数位决定能表示多大范围,尾数位决定有多精细。
做个不太严谨但很好懂的生活化类比:FP32像一把毫米刻度的尺子,FP16像厘米刻度的尺子。量普通的物件都够用;但如果量特别长的距离,厘米刻度的尺子可能量程不够,这时候就需要BF16——BF16把一半的尾数位让给了指数位,量程和FP32一样大,但刻度更粗。
这就解释了为什么训练大模型时BF16越来越流行:梯度或中间状态如果超出FP16的量程,会出现“inf”和“NaN”;FP16虽然刻度还算细,但量程不够。BF16牺牲了尾数精度,换来了和FP32基本一致的范围,配合混合精度训练反而更稳定。
TF32是另一个容易被忽略的东西,它其实是Ampere架构开始支持的一种内部精度,指数位和FP32一致,尾数位被压缩,常用于框架默认的矩阵加速。很多用户没有显式开启,但代码里已经开始用TF32算,这也是“同一个模型、同一张卡,跑出来结果和文档对不上”的原因之一。
2.2 量化为什么能让模型“瘦身”而不崩
从FP16到INT8,不是简单把每个数掐头去尾,而是做了一次“刻度重绘”。INT8只有256个离散值,拿8bit表示原本FP16那2万多个可能的数值,必须找一个合适的映射关系。
核心流程是:
- 拿一批真实数据跑一遍模型,统计每一层权重或激活的数值分布。
- 根据分布算出缩放系数(scale)和零点(zero point),把连续数值映射到0到255的整数范围。
- 推理时用整数做矩阵运算,最后再反量化回浮点。
这里面有两个关键选择:per-tensor还是per-channel,以及PTQ还是QAT。
per-tensor是整层用一个scale,速度快但碰上分布特别不均匀的层容易掉点;per-channel是每个通道单独算scale,效果通常更好,代价是计算路径更复杂。PTQ不需要训练,拿几百条数据跑一遍校准就能完成,适合快速落地;QAT要把量化误差模拟进训练过程,成本高,但可以挽回大部分掉点。
推理框架里常见的W8A8意思是权重和激活都是INT8,W4A16意思是权重用INT4,激活保持FP16。很多INT4模型其实不是计算全程INT4,而是存储压缩到INT4,算的时候先反量化成FP16,所以省的是显存和带宽,算力未必省。这也是为什么“INT4占得少但速度不一定快”的现象经常出现。
2.3 哪些模型容易在低精度下翻车
根据我实际部署的经验,以下情况掉点风险更高,需要重点盯评测:
- 小模型,尤其是1B以下的模型。参数越少,冗余越少,量化等于把好不容易学出来的表达能力再压一轮,很容易露馅。
- 激活值存在明显长尾分布的模型。个别层会出现很大的离群值,per-tensor量化时这些离群值会抬高整个scale,导致普通数值被压得很粗,表现就是输出开始重复或者胡言乱语。
- 长文本生成场景。KV Cache如果也量化,误差会随着序列长度逐token累积,刚开始几句话还行,越往后越容易跑偏。
- 多个低精度叠加。比如本来是PTQ模型,部署时框架又做了一层动态量化,误差不是相加,而是相乘。
我的判断习惯是:先跑一遍FP16或者BF16,如果任务指标和FP32基本一致,再继续尝试INT8;如果FP16就已经有波动,INT8大概率会掉得更厉害,这时候不如回去用FP32或换硬件,而不是硬量化。
3. 硬件的账本怎么算:GPU、NPU、CPU各有各的脾气
3.1 选GPU:别只看显存,要看“精度和速度一起配”
经常有朋友拿着两万预算问我“买什么卡能跑7B模型”,我的回应是:先回答三个问题——你打算跑服务还是自己玩?同一时刻几个人用?能不能接受INT4的输出质量?
纯本地体验,一块24GB显存的消费级显卡已经能很舒服地跑14B模型BF16档位,7B模型INT8甚至能开很长的上下文。但如果是上线服务的场景,问题就不是“能不能跑一个模型”,而是“在延迟和吞吐约束下能跑几个并发”。这时候多卡方案、NVLink、PCIe带宽都要算进成本里,消费级卡走PCIe多卡互联,通信开销很吃亏。
如果预算有限但手头已经有卡,其实就是反着选精度:8GB显存,7B模型老老实实用INT4,再配合硬件拉升速度;16GB显存,FP16的7B模型和INT8的13B模型都可以考虑;24GB显存,多数中小模型都能覆盖,可以根据速度需求选精度档位。
还有一个小提醒:别只看显卡的FP16算力,要看它在你目标精度下的真实表现。有些显卡BF16标称速度很好看,实际上算子优化没跟上,跑起来还是FP32的速度。选型前上网查一下对应框架的benchmark,比自己猜测靠谱得多。
3.2 不是只有显卡:NPU、CPU也能参与精度博弈
模型部署不只有“买一块NVIDIA显卡”这条路。移动端和嵌入式设备上越来越多NPU,最擅长的恰恰是INT8计算,功耗低、延时可预测。同样的模型,FP16跑不动,INT8加NPU可能跑得比CPU快一个数量级。这时候“选什么精度”的核心逻辑变成:硬件已经定了,精度必须向硬件支持的能力靠拢。
CPU也有一席之地。现代服务器CPU带AMX或者VNNI指令集,对INT8/INT16计算有专门加速路径。用CPU做高并发推理时,INT8的吞吐量提升非常可观,而且不用抢GPU资源。瓶颈通常在内存带宽上,核数再多,数据喂不进去也白搭。
所以硬件选型应该是一条双向路径:从精度需求出发找硬件,或者从现有硬件出发定精度。不要上来就无脑买大显存卡,先量化评估需求,否则很容易花三倍的钱买来根本用不上的特性。
4. 实操流程:从模型文件到稳定可用
4.1 第一步:先把模型和它的算子摸底
不要跳过这一步。我见过太多人把模型下载下来直接开量化,根本不知道里面有哪些算子在目标框架上有没有实现。
具体做三件事:
- 加载原始模型,用脚本统计参数量、层类型、算子类型,找出可能不常见的模块。
- 用一个真实请求跑通原始精度(通常FP32或FP16),记录显存峰值、单次推理耗时、输出结果,作为后续对比的基线。
- 查目标推理框架的算子支持清单,逐个对照模型里的算子,看有没有会回退到低效实现的。
为什么这一步重要?因为如果某个模型里有一个自定义算子不支持INT8 kernel,框架可能直接把这一层回退成FP32计算,结果显存没省多少、速度反而更慢。有一个可用的算子映射表,就能提前判断“这个模型在这套硬/软件栈上该不该量化”。
4.2 第二步:精度对比实验怎么做才可信
很多同学做精度对比只看logits或者loss,这是不够的。业务指标比数值误差重要得多。
我常用的做法是:
- 准备一份固定的评测集,覆盖真实业务分布,包含不同输入长度、不同难度样本。
- 固定随机种子,先跑FP32原始模型,记录结果。
- 依次跑FP16/BF16、INT8、INT4,用同样的输入和参数。
- 对比两类指标:一是任务指标,比如准确率、F1、生成样本的可用率;二是模型输出层的相对误差,比如最大相对误差、平均绝对误差。
按我的经验,如果FP16/BF16相对FP32的最大相对误差能控制在1%以内,任务指标基本不会变化;一旦超过5%,就要回头审视是否某个敏感层被压缩太狠了。这个阈值不是我拍脑袋定的,是我在多个场景下对照出来的经验规律,你可以作为初始参考,再结合你的任务调整。
值得多说一句的是:FP16有时候跑出来比FP32还“好”,这往往是混合精度噪声带来的正则化效应,不代表越低精度越有优势,版权迭代和评估都应以任务指标为准。
4.3 第三步:量化校准与实施
量化不是“把文件名改成int8”就行,PTQ需要一个校准流程。校准集的选择直接决定量化效果。
我踩过一次大坑:随便从网上找了1000条公开数据做校准,离线评测看起来不错,上线后输出突然变差。后来发现是校准集分布和真实业务分布差太远。校准数据应该尽量贴近线上实际输入,比如用户真实问法、不同长度、不同任务边界,数量通常500到2000条就够,不需要特别多。
实施时优先选per-channel量化,不敏感层用per-tensor也可以接受。如果你用工具类框架,比如vLLM的GPTQ/AWQ接入、llama.cpp的量化脚本、ONNX Runtime的量化API,它们的控制参数各不相同,最常见的问题是在某个框架里配置了INT8,但某个模块没有真正走到INT8 kernel,导致“看似量化,实则没有”。
验证办法很简单:开profiler看每一层的实际计算精度,或者用一个小脚本手动构造成INT8算子跑一遍,确认运行路径符合预期。
4.4 第四步:压测看稳定性,别被单条推理骗了
单次推理跑通不等于部署稳定。量化之后最怕的就是“单条完美,并发出问题”。
我建议至少跑这几类压测:
- 并发测试:逐步增加并发数,观察延迟和吞吐曲线,看有没有突然掉底。
- 长序列压力测试:把上下文长度推到配置上限,确认KV Cache增长后显存不会爆炸。
- 长时间稳定性测试:连续跑一段真实流量,监控显存碎片、OOM发生率、温度降频情况。
有一个以前忽略过的细节:显卡温度上来后会自动降频,推理速度会掉20%甚至更多。如果你的部署环境散热不好,再好的精度配置也发挥不出来。量化之后模型小了、功耗低了,很多时候反而比FP16更稳,这也是INT8在服务端的重要优势之一。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
我把平时被问到最多的问题整理成一张表,遇到类似状况可以直接照着排查:
| 现象 | 可能原因 | 解决方向 |
|---|---|---|
| 量化后输出乱码、重复、答非所问 | 校准集偏差大,或敏感层被过度压缩 | 重新用真实数据校准;换per-channel;把敏感层保留为FP16 |
| FP16显存不够,INT8也跑得慢 | 有算子回退到FP32,或INT8 kernel没生效 | 开profiler查算子路径;升级框架版本;换W8A8实现 |
| BF16在显卡上无法启用 | 驱动/框架/架构不支持 | 检查compute capability和框架版本;考虑FP16或INT8 |
| 同一个模型在两个框架里结果差异大 | 算子融合、舍入顺序、内核实现不同 | 统一精度和算子实现版本;以任务指标为准做集成测试 |
| 量化后显存降了,速度反而没涨 | 存储压缩但计算仍走浮点 | 查数据布局和kernel类型;可能需要用低精度计算卡 |
| 多进程加载同一模型导致OOM | 每个进程各自加载权重和CUDA Context | 用共享内存/后端缓存;控制进程数;开启张量并行或分块加载 |
5.2 我踩过的三个细节坑
第一,校准数据千万别用测试集。用测试集校准会让量化误差“作弊”,看起来指标完美,实际上线后真实分布稍有偏移,模型立刻崩。校准集应该来自训练同分布的真实样本,而不是评测样本。
第二,不要迷信“输出层误差很小”。有次我把FP16相对FP32的误差压到很小,以为安全了,结果业务指标还是掉了。后来发现是某一层attention的输出分布悄悄变了,logits整体看着接近,但解码路线已经摇摆。正确做法是单独观察每一层的激活分布变化,不要只看最终层。
第三,别忽略混合精度下的中间值溢出。BF16和FP16虽然范围不同,但中间计算如果出现极大值,依然可能失去数值稳定性。推理时BN层或者归一化层在低精度下尤其敏感,遇到FP16输出全是NaN的情况,先检查归一化层的内部统计量是否在低精度下被截断了。
最后再分享一个小技巧:决定用INT8之前,先跑一遍BF16试试水。如果BF16这关都过不了,INT8大概率只会更糟;反过来,如果BF16表现稳定,往往说明模型本身有冗余空间,INT8就可以放心尝试。这一招能帮你省掉很多来回折腾的时间。
我自己的体会是,精度和硬件永远是一起选的,不是先定模型再随便挑一张卡。先定任务不掉点的精度下限,再拿这个下限去反向筛选硬件,最后留足10%到20%的显存余量,这套流程用到现在几乎没有失手过。希望这篇内容能让你少走点弯路。