FP16、BF16 是张量里每一个数字的存储格式(数据类型) 。
张量可以理解成一大堆数字组成的矩阵/向量,里面最小单元就是单个数字,FP16/BF16规定:这个数字在内存、权重文件里占多少字节、怎么存小数。
1. 先搞懂基础参照物:FP32(普通32位浮点数)
电脑平时存小数默认用FP32,占4个字节,分三段:
- 正负符号位
- 指数(控制数字能多大/多小)
- 尾数(控制数字精准程度)
大模型原始训练时,所有权重数字本来都是FP32,但4字节一个数,权重文件会巨大,显存扛不住,于是搞出两种压缩到2字节(16位)的方案:FP16、BF16。
两者都只占2字节,区别只是指数和尾数的位数分配不一样。
2. FP16(Float16,标准16位浮点)
分配规则:1位符号 + 5位指数 + 10位尾数
- 优点:尾数10位,精度更高,算乘法、推理速度更快,老显卡加速友好;
- 缺点:只有5位指数,能表示的数字范围很小。
遇到模型里差距很大的权重数值、训练时的梯度,很容易超出范围直接变成0(下溢),训练容易崩。
举个生活化例子:
好比一把尺子,刻度分得特别细(尾数多=精度高),但尺子总长很短(指数少=范围小)。小数能算得很准,但数字稍微一大一小就超出去读不出来。
3. BF16 = BFP16(Brain Float16,谷歌搞出来的)
分配规则:1位符号 + 8位指数 + 7位尾数
- 优点:8位指数,和FP32的指数长度一模一样,能承载的数字范围和完整版FP32完全一致。训练时梯度、权重大小波动再大,也不会溢出失效;
- 缺点:尾数只剩7位,精度比FP16低一点点,微小数值会丢失一点细节,但大模型训练完全感知不到损失。
生活化例子:
尺子总长和完整4字节尺子一样长(指数8位),但刻度稍微粗一点(尾数少),范围足够用,牺牲一点点精细度换稳定。
4. 它们是不是张量的基本单元数据类型?
是的,完全正确。
- 张量 = 一堆数字的集合(权重矩阵、输入向量、中间计算结果全是张量)
- 张量的最小组成单位:单个数值
- FP16/BF16 就是规定「张量里每一个数字用哪种16位格式保存」
举个直观对比:
- 同样100万个权重数字:
FP32:每个4字节,总占用400MB
FP16/BF16:每个2字节,总占用200MB,直接减半显存/硬盘占用
5. 使用场景
- 训练、微调模型(QLoRA/全量微调)一律用 BF16
训练时会产生梯度(极大、极小数),BF16表示数值范围广,不怕数值溢出,训练稳定,现在所有大模型预训练标配。 - 单纯本地推理、显卡老旧(比如老10系NVIDIA卡)优先 FP16
FP16表示的数据范围小,计算速度更快,更省算力。
6、手动转换 两种格式
一、先分清 概念:「训练计算精度」「权重文件存储精度」「推理运行精度」
1. QLoRA/全量微调阶段:为什么强制用BF16做计算?
训练时会产生梯度(极小/极大的数值):
- BF16指数位8位,和FP32范围一模一样,梯度不会溢出、训练稳定;
- FP16只有5位指数,梯度极易变成0/无穷,训练直接崩掉。
这里说的「用BF16」,指整个训练过程的运算统一走BF16浮点计算。
2. 训练完保存的权重文件默认是什么格式?
两种情况:
- 普通LoRA(非QLoRA) :全程基座BF16训练,保存出来的基座权重、LoRA适配器都是 BF16文件;
- QLoRA 4bit训练 :显存里基座是4bit NF4压缩存储,计算时临时反量化BF16;保存只会存两件东西:4bit基座模型 + BF16格式LoRA小权重,不会直接存完整BF16基座。
3. 关键:BF16权重文件 ≠ 推理必须跑BF16
权重文件只是静态存储数字 ,BF16和FP16都是16位浮点数,底层框架(PyTorch/Transformers/vLLM)自带类型强制转换算子,一行代码手动转,不是自动变。
二、两条落地路线:微调完怎么得到FP16推理模型
路线1:LoRA微调(原生BF16权重)转FP16推理
步骤
- 用BF16跑完微调,保存
.safetensors权重(文件里数值是BF16编码); - 推理加载时,手动执行类型转换:
python
# 加载BF16模型
model = AutoModelForCausalLM.from_pretrained("./your_bf16_model")
# 全局强制转FP16,所有权重数值重新编码为FP16格式
model = model.to(torch.float16)
# 此时显存里运行是FP16加速,你也可以重新保存一份FP16权重文件
model.save_pretrained("./fp16_infer_model")
原理大白话
BF16、FP16本质都是记录同一个小数,只是二进制拆分「指数/尾数」规则不同。
框架会把BF16数字先翻译成标准FP32中间值,再重新编码成FP16二进制,存在显存里用于推理;
- 优点:老10系显卡跑FP16算子速度更快;
- 代价:极个别超大/极小权重数值会丢失一点精度(推理无梯度,轻微损失肉眼看不出)。
路线2:QLoRA 4bit微调 → 合并LoRA → 导出FP16完整模型(最常用部署流程)
QLoRA训练完不会有完整BF16基座,必须先合并权重才能导出FP16:
- 加载4bit量化基座 + BF16 LoRA适配器;
- 执行
model.merge_and_unload():框架自动把4bit权重反量化还原成BF16完整基座,再叠加LoRA参数; - 此时内存里是完整BF16大模型,手动转FP16后保存,得到纯FP16权重文件用于部署;
- 以后推理直接加载这份FP16文件,全程FP16运算加速。
三、必须手动操作
- 训练保存的文件固定是BF16/4bit,不会自动变成FP16;
- 「推理优先FP16」不是让原BF16文件自己变形,是两种操作二选一:
- 加载BF16权重,运行时实时转FP16计算(不改动原文件);
- 转换后另存一份独立FP16权重文件,专门给老显卡推理用;
- 反过来也成立:FP16权重推理,加载时转BF16运行,完全可逆。