常见数值格式存储图解
FP16 的 16 位分给了谁?INT4 为什么能表示负数?下载的“四位模型”为何还需要额外空间?这篇把模型训练、推理和量化工具中常见的格式逐个拆开,跟着实际编码把数读回来。第一次接触二进制,可以先读数字怎样存进计算机,再回来按格式查阅。
每张图上方的字段条按位数比例绘制,下面标出字段名称、位编号、示例编码和读数过程。位编号从最右侧的 0 开始;一位里面只保存 0 或 1,字段规定这些位的用途。浮点图中的粉色是符号,绿色是指数,蓝色是小数部分。
FP32、FP16、BF16 与 FP64
这四种格式都把普通的规格化数拆成三段:符号决定正负,指数决定数量级,小数部分决定当前数量级内的细节。读取时先把指数编码减去偏移量,再计算:
正负号 × (1 + 小数部分) × 2^实际指数
小数部分从左到右的位权依次是 1/2、1/4、1/8……。下面都保存 1.25,所以小数部分从 01 开始,表示 1/4;前面的 1 由规格化规则补回。指数全零等特殊编码另有读取规则,在范围与刻度中继续展开。
FP32
1 位符号、8 位指数、23 位小数部分,共 32 位、4 字节。指数偏移量为 127。训练中的累加、优化器状态以及部分敏感计算经常会用到它。
FP16
1 位符号、5 位指数、10 位小数部分,共 16 位、2 字节。指数偏移量为 15,最大有限值为 65504。在 1 附近,它比 BF16 保留更多小数细节。
BF16
1 位符号、8 位指数、7 位小数部分,共 16 位、2 字节。指数偏移量与 FP32 相同,都是 127。它保留宽数量级范围,把小数部分压缩得更短。常见拼写是 BF16,来自 bfloat16。
FP64:科学计算中的双精度
FP64 有 1 位符号、11 位指数、52 位小数部分,共 8 字节,偏移量为 1023。规格化数算上隐含的开头 1,共有 53 位二进制有效信息。科学计算和高精度数值验证中常见,主流大模型权重通常采用更短的格式。
FP8:同样八位,还有不同约定
FP8 每个数只占 1 字节。E4M3 表示四位指数、三位小数,E5M2 表示五位指数、两位小数;两者都还留了一位符号。实际使用时,需要连同 FN、FNUZ 等后缀一起识别。
E4M3FN
偏移量为 7,最大有限值 448。指数全 1 的部分编码继续表示有限数,只在特定位置保留 NaN(无效数),没有无穷大编码。保存 1.25 时,八位写作 0 | 0111 | 010。
最大正数的编码是 0 | 1111 | 110:指数 15−7 = 8,小数部分 0.110₂ = 3/4,读回 (1 + 3/4) × 2^8 = 448。继续把小数部分写成 111,就到了 NaN 的保留位置。
E5M2
偏移量为 15,最大有限值 57344,支持无穷大与 NaN。与 E4M3FN 相比,多一位指数扩大了范围,少一位小数使局部刻度变疏。它们在同一个 FP8 工作流中也可能用于不同组数据。
E4M3FNUZ 与 E5M2FNUZ:偏移量也变了
FNUZ 变体采用有限值、NaN 与单一零的约定:00000000 是零,原本可用于负零的 10000000 改为 NaN。两种格式都没有无穷大编码。
E4M3FNUZ 的偏移量为 8,最大有限值 240。相同的 1.25,其指数编码变成 1000。因此,判断 FP8 的数值还要知道具体变体。
E5M2FNUZ 的偏移量为 16,最大有限值 57344。保存 1.25 时,指数编码为 10000。带 FNUZ 的 dtype 在部分 AMD/ROCm 等软件路径中会遇到,交换模型时需要对齐完整类型名称。
FP4 与 FP6:进一步缩短浮点编码
FP4 E2M1
1 位符号、2 位指数、1 位小数,偏移量为 1。正幅值只有 0、0.5、1、1.5、2、3、4、6,再用符号位表示负值,包含正零与负零。所有编码都用于有限值。下面保存 1.5,它恰好能用唯一的小数位表示。
1.25 位于 1 和 1.5 之间,通常的就近、平局取偶舍入会选到 1。四位刻度很少,所以 FP4 常与组级 scale 一起工作,让每组数落在合适的范围。
FP6 E2M3 与 E3M2:六位的两种分配
OCP MX 体系还定义了六位浮点。它们保留一位符号,其余五位分配给指数和小数部分,所有编码都表示有限值,支持零和次正规数。
E2M3:2 位指数、3 位小数,偏移量为 1,最大有限值 7.5。与 E2M1 相比,数量级相近,局部刻度更细。
E3M2:3 位指数、2 位小数,偏移量为 3,最大有限值 28。它把一位小数预算移给了指数,换取更大的范围。
六位是数值编码的长度。具体硬件和文件怎样打包、对齐,还要查看其存储约定。
INT 与 UINT:每一位参与整数求和
浮点数把位分成三种职责;整数按每一位的位权相加。常见有符号整数使用补码:最高位的位权为负,其余位为正。最高位为 1 时,要把那一项负值加入求和。
INT4
四个位权从左到右是 −8、4、2、1。所以 1011 读作 −8 + 2 + 1 = −5,范围是 −8 到 7。模型量化时还会配 scale,把整数刻度映射回小数。
INT8
八个位权为 −128、64、32、16、8、4、2、1。图中的低七位之和为 123,加上最高位的 −128 得到 −5,完整范围为 −128 到 127。
UINT4 与 UINT8:最高位也取正位权
U 表示 Unsigned,即无符号。所有位都取正位权,因此 UINT4 范围为 0 到 15,UINT8 范围为 0 到 255。相同的 1011 在 UINT4 中读作 11;相同的 11111011 在 UINT8 中读作 251。
图像像素、无符号量化码里经常能见到 UINT8。量化方案可以另存零点,把某个无符号编码指定为原始数值的零。
INT16、INT32 与 INT64:整数长度继续增加
补码读取规则沿用下来,只改变位数。n 位有符号整数的范围是 −2^(n−1) 到 2^(n−1)−1。下面三个例子都保存 −5,最高位的负位权与其余位的正位权相抵后得到它。
INT16 占 2 字节,范围 −32768 到 32767。整数音频 PCM 等数据中常见。
INT32 占 4 字节,范围 −2147483648 到 2147483647。整数量化矩阵乘法常用 INT32 累加中间结果。
INT64 占 8 字节,范围 −9223372036854775808 到 9223372036854775807。模型程序中的索引、计数等也可能采用它。
NF4:四位保存的是查表编号
NF4 来自 NormalFloat 4。它安排了一个固定的 16 项非均匀码本,用来贴近归一化后呈正态分布的权重。四位保存 0 到 15 的索引,读取时先查码本,再乘这组数共用的倍率。这个倍率叫缩放因子(scale),负责把归一化后的刻度恢复到原来一组数的大小。
图中采用 bitsandbytes 的 NF4 编码:索引 0 对应 −1,7 对应 0,15 对应 1。例如 1111 是索引 15,查出 1,再乘约为 0.3 的共享 scale,恢复为约 0.3。码本是固定规则,scale 则随数据块保存。以 64 值分组、FP32 保存 scale 为例,每组有 64 × 4 + 32 = 288 位,平均 4.5 位/值。
NF4 的完整 16 项码本
这里列出约数。例如 0010 对应索引 2,先查到约 −0.525073,再乘这组的 scale。
| 四位编码 | 索引 | 码本值(约) |
|---|---|---|
0000 | 0 | −1 |
0001 | 1 | −0.696193 |
0010 | 2 | −0.525073 |
0011 | 3 | −0.394917 |
0100 | 4 | −0.284441 |
0101 | 5 | −0.184773 |
0110 | 6 | −0.091050 |
0111 | 7 | 0 |
1000 | 8 | 0.079580 |
1001 | 9 | 0.160930 |
1010 | 10 | 0.246112 |
1011 | 11 | 0.337915 |
1100 | 12 | 0.440710 |
1101 | 13 | 0.562617 |
1110 | 14 | 0.722957 |
1111 | 15 | 1 |
因此 NF4 的四位内部没有独立的符号、指数和小数区域。双重量化等方案还会压缩 scale,自身的附加信息也需要计入文件大小。
一组数怎样共用 scale
假设一组编码能读出 1.5,共享 scale 为 2,最终恢复值就是 1.5 × 2 = 3。把这一组都放大或缩小,可以让有限的低位刻度适应这组数据的范围。代价是需要保存 scale,所以“四位”载荷算上元数据后,平均占用通常会高于四位。
E8M0:专门保存 2 的幂
MX 常用 E8M0 保存 scale,八位全部用于指数,没有符号和小数区域。有限编码从 0 到 254,读为 2^(编码−127);255 用作 NaN。编码 128 就是 2^1 = 2。它只保存正的 2 的幂,没有零编码。
MXFP4
32 个 E2M1 四位值共用一个八位 E8M0 scale。载荷占 32 × 4 = 128 位,scale 占 8 位,共 136 位、17 字节,平均 4.25 位/值。
MXFP8 与 MXFP6:相同的 32 值分组
MXFP8 每 32 个八位值配一个 E8M0 scale,共 33 字节,平均 8.25 位/值。图中数值采用 E4M3;MX 规范也涵盖 E5M2。
MXFP6 每 32 个六位值配一个 E8M0 scale,共 25 字节,平均 6.25 位/值,数值可以采用 E2M3 或 E3M2。
NVFP4
张量是按一定形状排列的一组数,例如一行权重或一个矩阵。NVIDIA Transformer Engine 中常见的一维 NVFP4 分组,每 16 个 E2M1 值配一个 FP8 E4M3 块级 scale,还使用一个整个张量共享的 FP32 scale。恢复时把数值编码、块级 scale 和张量级 scale 相乘。FP8 scale 可以保存比 2 的整数次幂更细的比例。
图中一组占 16 × 4 + 8 = 72 位,平均 4.5 位/值,再加张量级 scale 的摊销。训练实现还会对权重采用二维分组。具体文件格式会安排自己的字段,例如 GGML 的 NVFP4 块结构就有自己的打包方式;读取模型时要对齐对应实现。
GGUF 模型中的量化块
GGUF 容器内的张量可以采用多种块格式。这里拆开 GGML 常见的 Q4_0 和 Q4_K:它们都保存四位量化码,但共享参数的组织方式不同。
Q4_0:32 个数共用一个 FP16 缩放因子
一块保存 32 个值,包含一个 FP16 缩放因子 d(2 字节),以及 16 字节四位量化码。每个码 q 取 0 到 15,恢复时计算 d × (q−8)。例如 d 为 0.25、q 为 9,得到 0.25。
一块 18 字节,平均 4.5 位/值。图中的比例条按字段总位数绘制;具体字节中的半字节排列由 GGML 解码实现规定。
Q4_K:256 个数里再分八个子组
一个 256 值超块里有八个 32 值子组。块头保存 FP16 的总 scale d 和总 min dmin;每个子组再保存六位 scale 和六位 min,八组共打包为 12 字节;最后是 128 字节四位量化码。
组 scale 和组 min 都是 0 到 63 的六位无符号码。组 scale 乘 d,得到这一组的刻度间距;组 min 乘 dmin,得到这一组要减去的偏移。读取时按 d × 组 scale × q − dmin × 组 min 恢复。
例如 d 为 0.25、组 scale 为 2、量化码 q 为 3,正向刻度得到 0.25 × 2 × 3 = 1.5;再取 dmin 为 0.5、组 min 为 1,减去 0.5 × 1,恢复值就是 1。组 min 保存的是参与计算偏移的无符号码。
合计 144 字节、平均 4.5 位/值。模型名中的 Q4_K_M 还包含按不同张量混用量化类型的预设,整个文件的平均位数要继续计入其他张量与元数据。
TF32 与实际字节排列
TF32:乘法时使用哪些位
TF32 是 NVIDIA Tensor Core 的矩阵计算模式。输入可以继续用 FP32 保存,在进入相应乘法路径时舍入到 8 位指数、10 位小数部分,再通常用 FP32 累加。配置里应分别看张量 dtype 与 TF32 开关。
图中的最后 13 位是 FP32 原有的小数低位,它们在舍入时影响保留下来的位。若模型仍按 FP32 存文件,每个数仍占 4 字节。
两个 INT4 拼进一个字节
每个四位编码只占半字节。以 ONNX 的紧凑打包顺序为例,第一个数放低四位,第二个数放高四位。−5 的编码是 1011,3 是 0011,拼成的字节就是 00111011。
其他库可以采用不同的半字节顺序,因此读取文件需要遵循文件自身的约定。奇数个四位值打包时,最后一个字节还会留下半字节用于填充。
位字段与大小端怎样对应
上面的位图统一把数学上的高位画在左边。为了缩短长编码,程序员常把每四位二进制换成一个十六进制数字:0 到 9 沿用数字,10 到 15 用 A 到 F。例如 FP16 的 1.25 是 0011 1101 0000 0000,四组分别读作 3、13、0、0,简写成 0x3D00,其中 0x 表示十六进制。
多字节数放进内存时,还要决定字节先后次序,这就是大小端。这个 FP16 数的小端字节顺序是 00 3D,大端是 3D 00。每个字节仍是八位,FP16 内部的三段分工保持相同。
看到一个精度名称,现在可以沿着图读出三个层次:单个数的位怎样分工,一组数共享哪些附加信息,计算程序怎样还原并使用它。模型名里经常还会出现 AWQ、GPTQ 和 W4A16:前两者是选择量化参数的方法,W4A16 描述权重与激活各用多少位,GGUF 则是装载张量和元数据的文件容器。读到这些名字时,可以继续追问它实际装了哪一种数。
实际使用时怎样取舍
FP16 与 BF16 花的是同样的两字节预算:在两者都处于规格化范围时,FP16 在同一数量级内分得更细;BF16 留出了更宽的指数范围,在极小数附近也可能保留更细的刻度。训练和推理先看模型的数值范围、局部误差与设备支持,再决定哪一种更合适。
降到八位或四位,主要换来更少的存储与搬运;可用刻度减少后,需要缩放、分组和质量验证配合。权重采用四位时,激活、累加和 KV 缓存仍可各选自己的精度。若长对话主要占在缓存上,就把优化重点放到缓存与请求规模。
速度还取决于执行程序能否高效使用这些编码。已有高效八位方案并且质量、内存都达标,可以保留八位;四位省出的空间有实际用途,或设备已有合适的四位路径,再比较四位。FP32 则可以留给敏感计算和对照,帮助找到误差从哪里进入结果。
完整的精度取舍与使用场景分析按方案比较收益与代价,再用 70 亿参数、16 GB 预算的案例推演选择过程。继续理解误差,可以回到舍入、溢出与计算误差;低位刻度的计算过程见FP8、FP4 与整数量化。
