1. 精度与硬件的匹配逻辑:为什么同一个模型换个精度就像换了台机器
同一个模型,权重文件一模一样,推理框架也没动,只是把精度从 FP16 换成 BF16,或者从 INT8 换成 FP8,显存占用、吞吐量、甚至输出质量都会出现肉眼可见的差异。这件事我在过去两年里反复验证过很多次,每次换硬件平台或者换推理后端,精度选型都是第一个要拍板的事。它不像调 batch size 那样可以随手改,精度一旦定下来,后面所有的性能基线、显存预算、甚至模型微调策略都要跟着走。
先把最基础的概念理清楚。精度在这里指的是数值的存储格式和计算格式,它决定了每个参数占多少比特、能表示多大的动态范围、保留多少有效数字。常见的几种格式可以分成两大阵营:浮点型和整型。浮点型里有 FP32、TF32、FP16、BF16、FP8(又分 E4M3 和 E5M2 两种变体),整型里有 INT8、INT4。最近一两年 NVFP4 这种 4 比特浮点格式也开始在特定硬件上落地。每一种格式背后都是一组取舍:比特数越少,显存和带宽压力越小,但数值表示的精度和动态范围也越窄。
为什么同一个模型换精度会像换了台机器?核心原因在于显存带宽和计算吞吐是两个独立的瓶颈。大语言模型推理在 decode 阶段是典型的 memory-bound,每生成一个 token 都要把整个模型的权重从显存里读一遍。这时候权重占多少比特,直接决定了每秒能读多少遍。FP16 的 7B 模型权重约 14GB,换成 INT8 就降到 7GB,理论上 decode 吞吐能翻倍。但在 prefill 阶段又是 compute-bound,矩阵乘的算力利用率取决于硬件的 Tensor Core 支持哪些精度。所以精度选型本质上是在带宽、算力、数值稳定性这三个维度上找平衡点。
我见过太多人踩的坑是:看到某个格式理论吞吐高就直接上,结果模型输出开始胡言乱语,或者 loss 曲线直接飞掉。这不是框架的锅,是精度和硬件、和模型本身的数值特性不匹配。下面我会把每种精度的适用场景、硬件门槛、以及实际部署时的参数选择逻辑拆开讲。
1.1 浮点家族:FP32、TF32、FP16、BF16 的动态范围差异
FP32 是基准,1 位符号、8 位指数、23 位尾数,动态范围大约 10 的 ±38 次方,有效精度约 7 位十进制。它的问题是太占地方,7B 模型光权重就 28GB,消费级卡根本放不下。所以实际部署里 FP32 基本只用于训练或者对数值极其敏感的少量算子。
TF32 是 NVIDIA 在 Ampere 架构引入的,本质上是把 FP32 的尾数截断到 10 位,指数保持 8 位。它只在 Tensor Core 内部使用,显存里存的还是 FP32。好处是矩阵乘速度接近 FP16,但动态范围和 FP32 一致,不容易溢出。缺点是精度只有约 3 位十进制,对某些敏感任务不够用。
FP16 是 1 位符号、5 位指数、10 位尾数。它的动态范围只有 10 的 ±5 次方左右,尾数精度约 3 位十进制。问题在于指数位太少,遇到极大或极小的激活值容易溢出成 inf 或者下溢成 0。训练时通常要配 loss scaling 来缓解,推理时如果模型里有异常大的激活值也会出问题。
BF16 是 1 位符号、8 位指数、7 位尾数。它牺牲了尾数精度换来了和 FP32 一样的动态范围。这意味着它几乎不会溢出,训练稳定性极好,这也是为什么现在主流大模型训练默认用 BF16。代价是有效精度只有约 2 位十进制,对精度敏感的任务可能不够。但在推理场景下,BF16 和 FP16 的吞吐通常是一样的,因为硬件 Tensor Core 对两者的支持速率相同,所以选哪个更多看数值稳定性需求。
这里有个容易被忽略的点:BF16 和 FP16 的显存占用完全相同,都是 2 字节每参数。所以如果你的卡支持 BF16,又没有特殊的精度需求,直接选 BF16 通常比 FP16 更省心,因为不用折腾 loss scaling 和溢出处理。
1.2 整型与低比特浮点:INT8、FP8、NVFP4 的硬件门槛
INT8 是 1 字节整型,表示范围 -128 到 127。它的优势是显存直接砍半,而且整数运算在多数硬件上能跑满算力。但整型没有指数位,动态范围极窄,必须配合量化校准才能用。校准的本质是统计激活值的分布,找出一个缩放因子,把浮点值线性映射到整数区间。这个过程会引入量化误差,误差大小取决于校准数据的代表性和量化算法的选择。
FP8 有两个主流变体:E4M3 和 E5M2。E4M3 是 4 位指数、3 位尾数,动态范围约 10 的 ±2 次方,精度约 1 位十进制;E5M2 是 5 位指数、2 位尾数,动态范围更大但精度更低。FP8 的好处是比 INT8 更容易做量化,因为它保留了指数位,对异常值的容忍度更高。NVIDIA 的 H100 及之后的卡对 FP8 有原生 Tensor Core 支持,吞吐是 FP16 的两倍。
NVFP4 是更激进的 4 比特浮点格式,1 位符号、2 位指数、1 位尾数。它的动态范围极小,必须配合分块缩放(block scaling)使用,也就是每 16 或 32 个元素共享一个缩放因子。这种格式目前只在 Blackwell 架构上有原生支持,而且对模型权重的分布有较强假设。我实测下来,NVFP4 在部分模型上能保持可用质量,但在另一些模型上会出现明显的质量下降,选型时必须做充分的评测。
硬件门槛这块要特别说清楚:不是所有卡都支持所有精度。比如 INT8 在多数现代 GPU 上都有支持,但 FP8 需要 Hopper 及以上,NVFP4 需要 Blackwell。消费级卡上,RTX 40 系支持 FP8,但 NVFP4 支持有限。如果你用的是国产加速卡或者端侧 NPU,支持的精度集合又完全不同。选精度之前,先查清楚你的硬件到底原生支持哪些格式,不支持的格式要么走软件模拟(慢),要么根本跑不起来。
2. 硬件平台与精度的对应关系:从数据中心到端侧
精度选型从来不是孤立的,它和硬件平台是绑定的。同一套模型,放在 A100 集群上和放在 Jetson Orin 上,能用的精度集合、最优的精度选择都不一样。这一章我按硬件层级来拆,从数据中心卡到消费级卡再到端侧设备,把每个层级的精度能力和选型建议讲透。
2.1 数据中心 GPU:H100、A100、B200 的精度能力矩阵
数据中心卡是精度支持最全的。A100 支持 FP32、TF32、FP16、BF16、INT8,不支持 FP8。H100 在 A100 基础上增加了 FP8 的 E4M3 和 E5M2 原生支持,Tensor Core 吞吐翻倍。B200 进一步增加了 NVFP4 和 FP6 支持。这个演进路线很清晰:每代新卡都在往更低比特走,因为低比特意味着更高的有效算力。
实际部署时,H100 上跑 7B 模型,BF16 的 decode 吞吐大约在每秒 1500 到 2000 token 这个量级(取决于 batch size 和序列长度),换成 FP8 能提升到 2500 到 3500。但 FP8 需要模型做量化校准,而且不是所有层都适合 FP8。我的经验是:注意力层的 QKV 投影和 FFN 层适合 FP8,但 LayerNorm 和 softmax 这类对数值敏感的算子要保留 BF16 或 FP32。混合精度部署是常态,纯 FP8 反而少见。
A100 上就没法用 FP8,只能在 BF16 和 INT8 之间选。INT8 需要校准,而且对 batch size 敏感,小 batch 下收益不明显。所以 A100 上我通常直接上 BF16,省事且稳定。
B200 的 NVFP4 目前生态还在完善中,推理框架的支持参差不齐。我试过用 TensorRT-LLM 在 B200 上跑 NVFP4 量化的模型,吞吐确实惊人,但量化过程需要仔细调参,而且不同模型的适配难度差异很大。如果你的团队没有专门的量化工程师,NVFP4 暂时不建议作为首选。
2.2 消费级与工作站卡:RTX 4090、5090 的精度取舍
消费级卡的情况复杂得多。RTX 4090 基于 Ada 架构,支持 FP32、TF32、FP16、BF16、INT8、FP8。但注意,4090 的 FP8 Tensor Core 吞吐虽然比 FP16 高,但显存带宽是瓶颈,所以在 memory-bound 的 decode 阶段,FP8 带来的实际吞吐提升远不如数据中心卡明显。我实测 4090 上跑 7B 模型,BF16 和 FP8 的 decode 吞吐差距只有 15% 到 20%,但 FP8 带来的量化误差风险却是实打实的。
RTX 5090 基于 Blackwell 架构,增加了 NVFP4 支持。但消费级卡的 NVFP4 支持是否完整、驱动和框架是否跟上,还需要时间验证。我目前拿到的测试数据看,5090 上 NVFP4 的吞吐提升在部分模型上能达到 2 倍以上,但质量损失需要逐模型评估。
消费级卡上我的建议很直接:优先 BF16,显存不够再考虑 INT8 或 FP8。因为消费级卡的显存通常是最紧的约束,7B 模型 BF16 要 14GB,4090 的 24GB 能放下但留给 KV Cache 的空间就不多了。这时候 INT8 把权重压到 7GB,能腾出更多空间给 KV Cache,支持更长的上下文。这个收益往往比吞吐提升更实际。
2.3 端侧与嵌入式:NPU、Jetson、手机 SoC 的精度约束
端侧是完全不同的游戏规则。Jetson Orin 支持 FP16、INT8、INT4,不支持 BF16 和 FP8。手机 SoC 上的 NPU 通常只支持 INT8 和 INT4,部分支持 FP16。这意味着你在数据中心调好的 BF16 模型,到端侧必须重新量化。
端侧量化的难点在于校准数据的获取。数据中心的模型可以用大量真实数据做校准,但端侧设备往往拿不到有代表性的校准集。我的做法是用数据中心的一小部分真实数据做校准,然后在端侧做充分的回归测试。如果回归测试发现某些场景质量下降明显,就对这些场景的算子单独保留 FP16。
另一个坑是端侧 NPU 对算子支持不全。比如某些 NPU 不支持动态 shape 的 INT8 卷积,或者不支持 FP16 的 LayerNorm。这时候要么改模型结构,要么把这些算子回退到 CPU 跑 FP32。回退到 CPU 会严重拖慢速度,所以模型结构设计阶段就要考虑目标硬件的算子支持列表。
3. 精度选型的实操决策流程:从需求到落地
前面讲了原理和硬件,这一章讲怎么落地。我把它整理成一个可执行的决策流程,你拿到一个新模型和新硬件时,按这个流程走一遍,基本能定出合理的精度方案。
3.1 第一步:明确瓶颈是显存、带宽还是算力
选精度之前先搞清楚你的瓶颈在哪。用 nvidia-smi 或者框架自带的 profiler 看几个关键指标:显存占用、SM 利用率、显存带宽利用率。如果显存带宽利用率接近 100% 而 SM 利用率很低,说明是 memory-bound,这时候降低精度(减少每参数比特数)能直接提升吞吐。如果 SM 利用率高而带宽利用率低,说明是 compute-bound,这时候要看硬件对目标精度的 Tensor Core 支持速率。
我常用的一个快速判断方法:跑一个 batch size 为 1 的 decode 测试,看每秒 token 数。如果这个数字远低于显存带宽除以模型大小的理论值,说明有优化空间。比如 7B 模型 BF16 占 14GB,4090 带宽约 1TB/s,理论 decode 上限约 70 token/s。如果实测只有 30 token/s,说明框架或算子实现有优化空间,换精度可能帮助不大。
3.2 第二步:查硬件原生支持的精度集合
这一步不能想当然。我见过有人在 A100 上试图跑 FP8,折腾半天才发现硬件根本不支持。查硬件支持列表最可靠的方式是看官方文档的 Tensor Core 支持矩阵,或者直接跑一个最小测试用例。下面这张表是我整理的常见硬件精度支持情况,供参考。
| 硬件平台 | FP32 | TF32 | FP16 | BF16 | FP8 | INT8 | NVFP4 |
|---|---|---|---|---|---|---|---|
| A100 | 是 | 是 | 是 | 是 | 否 | 是 | 否 |
| H100 | 是 | 是 | 是 | 是 | 是 | 是 | 否 |
| B200 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
| RTX 4090 | 是 | 是 | 是 | 是 | 是 | 是 | 否 |
| RTX 5090 | 是 | 是 | 是 | 是 | 是 | 是 | 部分 |
| Jetson Orin | 是 | 否 | 是 | 否 | 否 | 是 | 否 |
| 典型手机 NPU | 否 | 否 | 部分 | 否 | 否 | 是 | 否 |
这张表是粗略参考,具体型号可能有差异,以官方文档为准。
3.3 第三步:评估模型对精度的敏感度
不同模型对精度的敏感度差异很大。一般来说,参数量越大的模型对低精度越鲁棒,因为冗余参数多,量化误差被平均掉了。小模型反而更敏感。另外,训练时用了哪些技巧也影响敏感度,比如用了 QK Norm 的模型对低精度更稳定。
评估敏感度的方法很简单:用目标精度跑一遍评测集,和 BF16 基线对比。评测集要覆盖你的实际使用场景,不能只看 perplexity。我通常会看三类指标:通用能力(如 MMLU 这类知识评测)、任务特定指标(如代码生成通过率、摘要 ROUGE)、以及生成质量的人工抽检。如果低精度下通用能力下降超过 2%,或者任务特定指标下降超过 5%,就要谨慎。
3.4 第四步:确定混合精度策略
纯低精度部署很少见,混合精度才是常态。我的默认策略是:权重用低精度存储,计算时按算子敏感度选择精度。具体来说,矩阵乘这类计算密集且对精度相对宽容的算子用低精度,LayerNorm、softmax、残差连接这类对数值敏感的算子用高精度。
以 FP8 为例,一个典型的混合精度配置是:QKV 投影和 FFN 的线性层用 FP8,注意力分数计算和 softmax 用 BF16,LayerNorm 用 FP32。这个配置在多数模型上能保持质量,同时拿到大部分 FP8 的吞吐收益。具体到框架配置,TensorRT-LLM 和 vLLM 都支持这种细粒度控制,但配置方式不同,需要查对应文档。
4. 常见问题与排查技巧实录
这一章是我踩过的坑和解决方法的汇总,都是实际部署中会遇到的问题,不是理论推演。
4.1 精度切换后输出质量下降怎么排查
质量下降是最常见也最头疼的问题。排查思路是从粗到细:先确认是不是所有输入都变差,还是只有特定类型的输入变差。如果只有特定输入变差,大概率是那些输入触发了数值溢出或下溢。这时候可以打开框架的数值调试开关,看哪些层的激活值出现了 inf 或 nan。
如果所有输入都变差,先检查量化校准是否充分。校准集太小或者分布和实际输入差异大,都会导致量化误差偏大。我的经验是校准集至少要有几百条样本,而且要覆盖实际场景的主要输入类型。另外,校准算法也有讲究,min-max 校准对异常值敏感,移动平均校准更稳但可能欠拟合。可以两种都试,看哪个效果好。
还有一个容易被忽略的点:KV Cache 的精度。很多人只关注权重精度,忽略了 KV Cache 也可以量化。KV Cache 量化能显著降低显存占用,但对长上下文的质量影响较大。如果发现长上下文场景质量下降明显,先检查 KV Cache 是不是被量化了。
4.2 显存够但吞吐上不去的原因分析
显存够但吞吐低,通常是这几个原因:一是 batch size 太小,GPU 利用率不足。decode 阶段 batch size 为 1 时,GPU 大部分算力是闲置的。可以尝试增大 batch size 或者用 continuous batching。二是算子实现没有针对目标精度优化。比如某些框架的 INT8 算子还是用 FP16 模拟的,那自然没有加速效果。三是显存带宽被其他进程占用,或者 PCIe 传输成了瓶颈。
排查方法是用 profiler 看时间花在哪里。如果大部分时间花在 kernel launch 或者内存拷贝上,说明是框架开销问题,不是精度问题。如果时间花在矩阵乘 kernel 上但利用率低,说明是 kernel 实现问题,可能需要换框架或者等框架更新。
4.3 低精度训练与推理的精度不一致问题
训练用 BF16,推理用 INT8,结果发现推理质量比训练时验证集差很多。这个问题的根源是训练和推理的数值行为不一致。训练时用的是 BF16 的权重和激活,推理时权重被量化成 INT8,激活也被量化,误差叠加。缓解方法是在训练时引入量化感知训练(QAT),让模型在训练阶段就适应量化误差。如果不想重训,至少要做充分的校准和评测,确认质量损失在可接受范围内。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 输出乱码或重复 | 激活值溢出 | 检查各层激活值范围 | 敏感层回退高精度 |
| 吞吐低于预期 | batch size 太小 | 看 SM 利用率 | 增大 batch 或用 continuous batching |
| 长上下文质量下降 | KV Cache 量化 | 对比 KV Cache 精度 | KV Cache 保留 FP16 |
| 显存溢出 | 权重精度过高 | 看显存占用分布 | 权重降精度或分片 |
| 量化后质量骤降 | 校准不充分 | 检查校准集分布 | 扩充校准集或换算法 |
| 端侧速度慢 | 算子回退 CPU | 看算子执行设备 | 改模型结构适配 NPU |
5. 几个实际部署案例的精度选择复盘
理论讲完了,看几个我实际做过的案例,把前面的决策流程走一遍。
5.1 案例一:7B 模型在单张 4090 上的部署
需求是在一张 4090 上部署 7B 模型,支持 8K 上下文,吞吐要求不高但要稳定。显存是主要约束:BF16 权重 14GB,8K 上下文的 KV Cache 大约 2GB,加上框架开销,24GB 显存刚好够但很紧。如果并发请求多,KV Cache 会涨,容易 OOM。
我的选择是权重用 INT8,KV Cache 保留 FP16。INT8 权重占 7GB,腾出 7GB 给 KV Cache,能支持更多并发。吞吐方面,INT8 在 4090 上的 decode 吞吐比 BF16 高约 30%,因为权重读取量减半。质量方面,用 500 条校准样本做校准,评测集上通用能力下降约 1%,任务指标下降约 2%,在可接受范围内。
这里的关键决策是 KV Cache 不量化。我试过 KV Cache 也量化成 INT8,显存能再省 1GB,但长上下文质量下降明显,8K 上下文下摘要任务的 ROUGE 掉了 8 个点,得不偿失。
5.2 案例二:H100 集群上的 FP8 推理服务
这个案例是给一个中等规模模型做 FP8 推理,目标是最大化吞吐。H100 原生支持 FP8,Tensor Core 吞吐是 BF16 的两倍。但纯 FP8 质量不稳定,所以用了混合精度。
具体配置是:QKV 投影和 FFN 线性层用 FP8 E4M3,注意力分数和 softmax 用 BF16,LayerNorm 用 FP32。这个配置下,吞吐比纯 BF16 提升了约 60%,质量下降在 1% 以内。校准用了 2000 条真实请求样本,覆盖了主要业务场景。
踩过的坑是 FP8 的缩放因子需要动态更新。静态缩放因子在输入分布变化时会失效,导致溢出。后来改成了动态缩放,每若干步重新统计激活值范围,问题解决。这个动态缩放的频率需要调,太频繁有开销,太稀疏跟不上分布变化。
5.3 案例三:端侧设备上的 INT4 量化
端侧设备显存极小,必须用 INT4。INT4 的量化难度比 INT8 大得多,因为表示范围只有 16 个值。我的做法是分组量化,每 128 个权重共享一个缩放因子,这样能更好地适应权重分布。另外,对敏感层保留 INT8,比如第一层和最后一层,以及注意力输出投影。
端侧量化的另一个坑是算子支持。目标 NPU 不支持动态 shape 的 INT4 卷积,所以模型输入必须固定 shape。这限制了应用场景,但端侧设备通常场景固定,可以接受。如果场景需要动态 shape,就要考虑换硬件或者用 INT8。
6. 精度选型的经验法则与后续演进
做了这么多部署,我总结出几条经验法则,不一定普适,但能帮你快速缩小选择范围。
第一条:显存是硬约束,先满足显存再谈吞吐。如果模型放不下,什么精度都白搭。所以选精度的第一步是算显存账,权重加 KV Cache 加框架开销,留 20% 余量。
第二条:BF16 是默认选项,除非有明确理由换别的。BF16 在支持它的硬件上稳定、省心、质量好。只有在显存不够或者吞吐要求极高时,才考虑 INT8 或 FP8。
第三条:低精度部署必须做质量评测,不能只看吞吐。我见过太多为了吞吐牺牲质量最后被用户投诉的案例。评测集要覆盖实际场景,指标要全面。
第四条:混合精度是常态,纯低精度是例外。不要追求全模型统一精度,按算子敏感度分别处理,收益更大风险更小。
关于后续演进,低比特是明确的方向。NVFP4 和更激进的格式会逐渐成熟,硬件支持会越来越广。但低比特带来的量化复杂度也在上升,对工程能力的要求更高。我的建议是保持关注,但不要盲目追新,等生态成熟、有充分的工具链支持再上。
最后分享一个我常用的调试技巧:当你拿不准某个精度是否可用时,先在一个小规模评测集上跑对比,重点看最差的那几条样本,而不是平均值。平均值会掩盖问题,最差样本才能暴露数值稳定性的边界。如果最差样本的质量下降在可接受范围内,再扩大评测规模。这个技巧帮我省了很多时间,也避免了几次线上事故。