06. 怎样选择与验证精度
同一个模型有 BF16、FP8、INT4 几个版本,应该下载哪一个?训练时,FP16 和 BF16 又该怎样选?前面已经拆开了它们的位与刻度,这一篇把这些差别放回实际任务,比较空间、数值误差、计算速度与使用成本,再用一个显存预算案例走完选择过程。
减少位数,换来了什么
首先节省的是保存和搬运数字的空间。从 16 位缩到 8 位,同样多的数只需一半载荷;压到 4 位,载荷又减半。实际模型还要保存 scale 等附加信息,计算总占用时需要一起计入。内存带宽指单位时间能搬运多少字节;总字节数减少,就有机会缓解存储与数据搬运压力。
付出的第一笔代价是可用刻度变少。原来的两个数可能挤到同一个刻度,大值也可能超出范围。浮点数需要分配指数和小数位,量化方案需要安排 scale、分组和裁切。哪种分配合适,要看模型中的数实际落在哪里。
第二笔代价落在执行与维护上:设备要支持对应的计算,程序要有匹配的实现,需要校准的方案还要准备覆盖实际输入的数据,升级模型后重新检查质量。低位格式常能带来收益,也把原来一个保存格式开关变成了需要共同配合的一套方案。
因此可以先按“收益、代价、用途”比较,再进入具体模型验证:
| 方案 | 主要收益 | 主要代价 | 常见用途 |
|---|---|---|---|
| FP32 | 范围与局部细节较充足,便于数值排查 | 每值 4 字节;占用和搬运较多 | 对照配置、敏感计算、累加及部分训练状态 |
| FP16 | 每值 2 字节;在 1 附近比 BF16 刻度细 | 范围较窄,训练时需留意梯度下溢与溢出 | 支持 FP16 的推理,以及配合损失缩放的训练 |
| BF16 | 每值 2 字节,保留宽指数范围 | 在 1 附近的小数刻度比 FP16 疏 | 支持 BF16 的模型训练与推理 |
| FP8 | 每值 1 字节;匹配硬件上可降低存储与计算开销 | 依赖缩放、算子支持和合适配方;变体要对齐 | 有对应实现的低精度训练、推理或 KV 缓存 |
| INT8 | 每值 1 字节;有成熟整数计算或反量化路径时可降低开销 | scale、零点与校准会影响误差;极端值可能拉大刻度 | 权重量化,或同时量化权重与激活的推理 |
| INT4、NF4、GGUF 四位块 | 权重载荷较 16 位缩到约四分之一 | 刻度更少;附加信息、分组和反量化实现都需考虑 | 内存紧张时的权重压缩;NF4 也常用于训练少量新增参数 |
| MXFP4、NVFP4 | 四位浮点配细分组缩放,适应不同组的数值范围 | 共享参数有开销;需要匹配的硬件、模型和执行方案 | 已支持对应块格式的低位训练或推理 |
表中的“每值”只计算数值载荷。INT4、NF4、GGUF 四位块使用各自的编码和缩放规则,实际支持情况要按完整方案检查。比如 NF4 常用于压缩已有模型的基座权重,保持这些权重固定,只训练另外添加的一小部分参数;被压缩的权重与新增参数可以采用不同精度。
FP64 更适合数值误差要求严格的科学计算和验证,每值占 8 字节;深度学习是否需要它,应由具体算法的数值要求决定。FP6 位于四位与八位之间,当前可用路径更依赖具体实现,可以随支持它的 MX 等方案比较。TF32 则是 FP32 张量上的矩阵计算模式,主要取舍在乘法精度与速度,权重仍按 FP32 保存时,文件与张量的存储空间保持 4 字节/值。
FP16 与 BF16:同样空间,优先保哪一边
两者都占 2 字节,选择主要围绕范围、局部细节和硬件支持展开。训练中的中间值和梯度可能跨越很大数量级,在支持良好的设备上,BF16 常是方便的起点:宽指数范围减轻了范围方面的压力,常见训练也无需梯度缩放。
当两者都处于规格化范围时,FP16 能保留更细的局部刻度。前文的 1.0351 保存为 FP16 后是 1.03515625,BF16 则是 1.03125;换成 70000,FP16 又会超出有限范围。极小数还要检查次正规区域,例如 10^-7 附近,BF16 的刻度反而更细。模型最终哪种效果更好,需要把范围与局部间距放进完整任务观察。
如果目标设备的 BF16 路径缺失或较慢,FP16 仍是合理候选。训练时配合损失缩放,推理时检查极端输入;敏感计算可以局部保留 FP32。
FP8 与 INT8:同样八位,刻度怎样安排
整数配固定 scale 时,一组里的刻度通常等间距;FP8 在组级缩放之外,还有数值自身的指数,刻度会随数量级变化。FP8 的 E4M3FN 多保留一位小数,E5M2 则保留更大的范围,选择要结合张量分布和训练、推理配方。
已有高效 INT8 后端和代表性校准数据,可以先比较 INT8;设备与模型已有经过验证的 FP8 执行路径,可以先比较 FP8。遇到大幅波动的激活,既要检查格式范围,也要检查 scale 的更新方式。名称中的“八位”只给出了预算,完整方案决定怎样花掉它。
四位:先看省下的空间是否用得上
INT4、NF4 与 FP4 的刻度很少,分组缩放会帮助它们适应每一组数据。对权重做四位压缩时,激活、累加和缓存仍可分别采用较高精度。把量化码在计算时恢复成 FP16/BF16,也是常见执行方式。
如果八位已经满足显存、质量和速度目标,保留八位可以少承担一些压缩与验证成本。四位更值得比较的情形是:需要把原本装不下的模型放入设备,给更长对话或更多请求让出空间,或者目标硬件已经有高效的四位计算方案。选 INT4、NF4 还是 NVFP4,要一起看模型版本、误差表现和后端支持。
先算需要省哪部分空间
假设模型有 70 亿个参数,原始权重的粗略预算如下。只计算参数值,按十亿字节为 1 GB:
| 保存方案 | 每个参数 | 原始权重 |
|---|---|---|
| FP32 | 4 字节 | 28 GB |
| FP16 / BF16 | 2 字节 | 14 GB |
| FP8 / INT8 | 1 字节 | 7 GB |
| FP4 / INT4 | 0.5 字节 | 3.5 GB |
实际运行还要放 scale、未量化层、输入与中间结果、计算临时空间。文本生成还会缓存已处理内容的状态;对话越长、人数越多,这部分可能越大。训练还会保存梯度,以及更新算法为后续调整保留的历史记录,后者属于优化器状态。因此,估算完权重以后,要测一次完整运行的峰值占用。
权重本身装不下,可以优先比较权重量化;权重能放下、长对话后才超限,就该检查缓存和并发。空间来自哪里,优化对象就跟着落到哪里。
同一个 70 亿参数模型,放进 16 GB 预算
假设可用显存预算为 16 GB,一次目标工作负载需要 4 GB 缓存,另留 2 GB 给激活与临时空间。这是方便计算的预算案例,所有 GB 都按十亿字节计算。先用同一份缓存与临时空间,单独比较权重:
- 16 位权重:
14 + 4 + 2 = 20 GB,已经超出预算。 - 8 位权重:
7 + 4 + 2 = 13 GB,剩下 3 GB 用于 scale、部分层保留高精度产生的额外空间与运行余量。 - 4 位权重:
3.5 + 4 + 2 = 9.5 GB,余量更多,还需计入同类附加开销。
这里暂按所有参数都缩成相同位数计算。若有 1 亿参数仍保留 16 位,八位权重应改为 69 亿 × 1 字节 + 1 亿 × 2 字节 = 7.1 GB,再加 scale 等信息。这 1 亿参数从原来的一字节改为两字节,额外增加 0.1 GB;四位方案也按同样方式重新计算。
如果八位候选加上实际附加开销后仍能稳定运行,任务质量和速度也达标,就有理由停在八位。若还想增加对话长度或并发,四位权重省出的空间才会进一步转化为容量收益。
现在把缓存需求改成 12 GB,即使四位权重也已有 3.5 + 12 + 2 = 17.5 GB。这时继续只压权重收效有限,需要检查 KV 缓存精度、输入长度、并发或缓存调度。缓存改为低精度后,仍要验证长上下文检索和任务结果。每项预算最终都要用目标负载下的峰值占用回读。
格式能读取,还要能高效计算
一台设备能读取 FP16 文件,可能随后转换成 FP32 执行;另一台设备有专门的低精度矩阵单元,可以直接在匹配的路径上完成大量计算。后一种情况下,低精度才更有机会同时减少空间和计算时间。
设备能力和计算程序共同决定实际路径。量化后如果多了一道格式转换,或者小模型本来就主要在等数据,文件减半也可能只带来很小的速度变化。实际测量才能知道时间花在了哪里。
对于小批量、逐步生成文本的请求,每一步可能花大量时间从显存读取权重。能直接读取压缩权重、并把反量化融合进计算的实现,有机会减少这部分等待。若程序先把整个模型恢复成 16 位,权重文件虽小,运行时占用和搬运行为又会变化。
长输入的批量处理通常包含较大的矩阵运算,更需要看目标设备的矩阵计算能力与算子效率。对话很长、并发很多时,读取 KV 缓存也可能成为主要开销。因此,同一个低精度版本在不同输入长度和并发下,可以呈现不同的速度收益。
部署扩展:计算后端与 TF32
框架会选择完成计算的底层程序,称为后端。设备的矩阵计算能力、框架版本、模型层形状和量化实现共同决定这条路径。加载日志、算子执行记录和性能分析可以帮助确认。
TF32 是特定硬件上的矩阵计算模式:它可读取 FP32 输入并采用较短的乘法精度。保存格式与计算模式需要分别记录。使用 vLLM、SGLang、PyTorch 等工具时,对照安装版本的支持矩阵,确认设备架构、模型和量化方案的组合是否受支持。
训练、推理与缓存,分别怎样开始
训练或继续调整模型,可以先采用框架与设备已支持的混合精度配方:常见起点是 BF16,敏感计算与累加按算子规则使用更高精度;采用 FP16 时,检查损失缩放。训练还要保存梯度、优化器状态等内容,权重空间减半后,总训练显存的变化要重新计算。使用 FP8/FP4 训练,需要对应模型、硬件和训练配方共同支持。
部署推理服务,先保留一个能正确运行的 FP16/BF16 或 FP32 配置作对照。需要压权重时,比较受支持的八位候选;容量仍不足,或四位路径更适合当前硬件与目标,再比较四位候选。实际选择可以沿用模型已经验证过的发布方案,减少自行转换引入的变量。
长对话与多人并发,单独估算 KV 缓存。它保存的是随请求增长的状态,权重量化后这部分仍会继续增长。支持 FP8 等缓存路径时,可以保持权重方案不变,只改变缓存精度,重点观察长输入、信息回查与持续生成质量。
语音识别、向量检索或分类阈值附近出现变化,先检查输出头、相似度计算、归一化和后处理。保留主干的低精度收益,逐处恢复 FP32,有时能用较小开销改善敏感结果。具体保留位置由对照实验确定。
用同一批输入,只改变一个因素
先选一个已经正确运行的配置作为基线,例如设备支持的 FP32 或 BF16。保存模型版本、运行参数、固定输入和输出,并记录累加格式、TF32 等计算开关。再选择一个候选配置:只改权重、只改激活,或者只改缓存。
质量检查要贴近任务。分类模型比较正确率和阈值附近样本;语音识别比较同一批录音的漏字、替换和重复;文字生成固定采样条件,检查回答正确性与结构化格式。第三篇的舍入实验已经说明:分数偏差很小,也可能在决策边界产生变化。
性能则分开记录峰值内存、加载时间、首个结果等待时间、持续生成速度和总吞吐。同一输入、多次运行,先预热再统计;把输入长度、并发和输出上限保持一致。只测文件大小,很难解释一次真实请求为何变快或变慢。
预热就是先运行几次,让加载、编译和初始化完成,再记录后续运行耗时;总吞吐表示一定时间内处理了多少工作。第一次启动的等待仍要单独记录,读者真实使用时也会遇到它。
比较前先写下可接受的质量变化、峰值占用和响应要求。平均分之外,保留短输入、长输入、极端值和阈值附近样本的分组结果。某种精度只有在这些约束内带来需要的收益,才值得采用;若只是文件变小、运行成本和任务结果没有改善,可以保留较简单的配置。
从能核对的数字开始
还没有设备与模型时,可以先用前面的算例检查自己是否理解。下面这张表只记录数值计算,不涉及模型质量或速度测量:
| 输入与操作 | 观察结果 | 对应原因 |
|---|---|---|
1.0351 存为 FP16 | 1.03515625 | 舍入到 FP16 刻度 |
1.0351 存为 BF16,再转 FP32 | 1.03125 | 更宽的容器接收的是已经舍入的值 |
| 从 1024 开始,八次加 0.25,每步存 FP16 | 1024 | 每次增量都被舍回去 |
| 同样八次加法,用 FP32 累加,最后存 FP16 | 1026 | 累加期间保住小增量 |
在自己的电脑上复现这些结果
需要 Python 3.10 或更新版本,普通电脑即可,无需 GPU 或第三方库。下载 precision_numbers.py,在文件所在目录执行:
bash
python3 precision_numbers.py输出依次包含 FP32、FP16、BF16、转回 FP32 以及两种累加结果:
text
FP32: 1.035099983215332
FP16: 1.03515625
BF16: 1.03125
BF16 -> FP32: 1.03125
FP16 each step: 1024.0
FP32 sum -> FP16: 1026.0脚本用 Python 标准库的二进制格式转换核对 FP32/FP16;BF16 在 FP32 编码上按取偶规则舍入。它处理这里选定的有限数,完整模型仍应由目标框架和设备执行。提示找不到文件时,检查终端当前目录;提示无法识别半精度编码时,检查 Python 版本。
出现退化时,把问题缩小到一处
如果某批样本在候选配置中失败,先恢复基线,确认问题跟随精度变化。再逐步恢复敏感层、累加或后处理精度,观察哪一步使结果恢复。出现 Inf/NaN,寻找第一次生成异常值的位置;结果有限但质量变差,则检查舍入、scale、校准分布和决策阈值。
训练中 FP16 小梯度消失,可以检查损失缩放;范围溢出需要检查前向数值和格式范围;量化后个别输入明显退化,可以回看分组和极端值。这些处理各自对应前文的一种具体机制。
选择最终可以落在一套具体配置上:权重采用什么编码,激活与缓存用什么格式,累加和敏感位置保留什么精度,以及它在哪个设备、模型和负载下通过了质量与性能检查。满足实际目标时,就可以停下来;节省出来的空间和时间应当能对应到更低成本、更长输入或更多请求。
