欢迎光临
我们一直在努力

fp32-int4系列数据格式深入理解

晚上在看一篇公众号的文章讲的是不同精度类型的数据,让我顿生了很多疑惑,以前做的大多是传统的深度学习模型,想要进行训练,更新梯度都是需要使用浮点类型才可以的,但是现在是大模型的时代,很多都是int类型的微调,这里主要就是记录学习的内容。

核心困惑:INT4量化不是只能用于推理吗?为什么QLoRA还能用INT4训练?

一句话解答:存储精度 ≠ 计算精度。INT4用于存储权重(省显存),计算时转换为FP16/BF16(保精度)。两者可以同时存在。

文档定位:从零基础完整理解深度学习中各种数值格式的原理、区别和使用场景。


在这里插入图片描述

目录

  • 第一章:从零理解计算机中的数
  • 第二章:浮点数格式家族
  • 第三章:整数格式家族
  • 第四章:所有格式总览对比
  • 第五章:存储精度 vs 计算精度——解开核心困惑
  • 第六章:量化技术详解
  • 第七章:QLoRA的INT4训练原理——彻底搞懂
  • 第八章:实战——如何选择数据格式
  • 第九章:PyTorch中的精度控制
  • 附录:术语表

第一章:从零理解计算机中的数

1.1 计算机只能存储0和1

计算机的所有数据最终都是二进制(0和1)

数字 5 → 二进制 00000101
字母 'A' → 二进制 01000001
小数 3.14 → 二进制 ???

问题来了:小数(浮点数)怎么用0和1表示?

这就是"浮点数格式"要解决的问题。

1.2 定点数 vs 浮点数

定点数(Fixed Point):
小数点位置固定
例:用16位表示小数,前8位整数,后8位小数
00000011.00101100 = 3.171875
问题:范围小,精度固定

浮点数(Floating Point):
小数点位置可以"浮动"
类似科学计数法:3.14 = 3.14 × 10⁰ = 0.314 × 10¹
在计算机中用二进制的科学计数法:
1.575 = 1.575 × 2⁰ = 1.1001 × 2⁰

浮点数 = 符号位 + 指数位 + 尾数位

1.3 IEEE 754标准

IEEE 754是最通用的浮点数标准,定义了多种精度格式

FP32(单精度):1位符号 + 8位指数 + 23位尾数 = 32位
FP64(双精度):1位符号 + 11位指数 + 52位尾数 = 64位

符号位(Sign):0表示正数,1表示负数
指数位(Exponent):表示数量级(类似10^x中的x)
尾数位(Mantissa):表示有效数字(类似3.14中的314)

FP32的位结构:

┌─────────────────────────────────────────────────────────────┐
│ FP32 (32位) 结构 │
│ │
│ 位号: 31 30 29 28 27 26 25 24 23 22 21 … 0 │
│ ┌───┬──────────────────────┬──────────────────────────┐│
│ │ S │ E (指数) │ M (尾数) ││
│ │1位│ 8位 │ 23位 ││
│ └───┴──────────────────────┴──────────────────────────┘│
│ │
│ 值 = (-1)^S × 2^(E-127) × (1.M) │
│ │
│ 示例: 3.14 在FP32中的表示 │
│ S = 0 (正数) │
│ E = 128 (即2^1 = 2) │
│ M = 1.57 的二进制 ≈ 1.10010001111010111000011 │
│ = 0 10000000 10010001111010111000011 │
│ │
│ 范围: ±3.4 × 10^38 │
│ 精度: 约7位有效十进制数字 │
│ 大小: 4 字节 │
└─────────────────────────────────────────────────────────────┘


第二章:浮点数格式家族

2.1 FP32——单精度浮点(传统默认)

┌─────────────────────────────────────────────────────────────┐
│ FP32 格式 │
│ │
│ 位结构: [S 1位][E 8位][M 23位] 共32位 = 4字节 │
│ │
│ ┌───┬────────┬───────────────────────────────────────┐ │
│ │ S │ EEEEEEEE│ MMMMMMMMMMMMMMMMMMMMMMMMM │ │
│ │1位│ 8位 │ 23位 │ │
│ └───┴────────┴───────────────────────────────────────┘ │
│ │
│ 范围: ±3.4 × 10^38 │
│ 精度: 约7位有效十进制数字 │
│ 最小值: ~1.2 × 10^-38 │
│ 每个参数: 4字节 │
│ │
│ 7B模型显存: 7B × 4B = 28GB │
│ │
│ 优点: 精度最高,范围大,不会溢出 │
│ 缺点: 显存占用大,计算速度相对较慢 │
│ 用途: 传统训练默认格式,现在一般被BF16替代 │
└─────────────────────────────────────────────────────────────┘

2.2 TF32——TensorFloat-32(Ampere架构引入)

┌─────────────────────────────────────────────────────────────┐
│ TF32 格式 │
│ │
│ 位结构: [S 1位][E 8位][M 10位] 共19位 │
│ │
│ ┌───┬────────┬──────────────┐ │
│ │ S │ EEEEEEEE│ MMMMMMMMMM │ │
│ │1位│ 8位 │ 10位 │ │
│ └───┴────────┴──────────────┘ │
│ │
│ 范围: 和FP32相同(±3.4 × 10^38) │
│ 精度: 和FP16类似(约3位有效十进制数字) │
│ │
│ 关键特性: │
│ – 只在Tensor Core内部使用 │
│ – 代码层面仍是FP32输入输出 │
│ – 硬件自动将FP32截断为TF32进行矩阵运算 │
│ – 用户无需修改代码 │
│ – Ampere(A100/RTX3090)+架构自动启用 │
│ │
│ 用途: FP32训练的"隐形加速器",无需改代码 │
└─────────────────────────────────────────────────────────────┘

2.3 FP16——半精度浮点

┌─────────────────────────────────────────────────────────────┐
│ FP16 格式 │
│ │
│ 位结构: [S 1位][E 5位][M 10位] 共16位 = 2字节 │
│ │
│ ┌───┬──────┬──────────────┐ │
│ │ S │ EEEEE│ MMMMMMMMMM │ │
│ │1位│ 5位 │ 10位 │ │
│ └───┴──────┴──────────────┘ │
│ │
│ 范围: ±65,504 │
│ 精度: 约3位有效十进制数字 │
│ 每个参数: 2字节 │
│ │
│ 7B模型显存: 7B × 2B = 14GB ← 比FP32省一半! │
│ │
│ 优点: 显存省一半,计算速度快(Tensor Core加速) │
│ 缺点: 范围小(±65,504),容易溢出 │
│ 梯度可能超出范围 → 需要loss scaling │
│ │
│ 用途: 混合精度训练(配合loss scaling使用) │
└─────────────────────────────────────────────────────────────┘

FP16的溢出问题:

FP16的最大值: 65,504

问题场景:
某些中间计算结果可能超过65,504
例:梯度累积过程中,梯度值可能很大
→ 溢出为 inf (无穷大) → NaN → 训练崩溃

解决方案1:Loss Scaling(损失缩放)
将loss乘以一个大数(如1024),让梯度也变大
反向传播后,将梯度除以同样的数恢复
→ 确保小梯度不会因精度不足变成0

解决方案2:使用BF16(范围更大,不会溢出)

2.4 BF16——Brain Float 16(Google Brain提出)

┌─────────────────────────────────────────────────────────────┐
│ BF16 格式 │
│ │
│ 位结构: [S 1位][E 8位][M 7位] 共16位 = 2字节 │
│ │
│ ┌───┬────────┬───────────┐ │
│ │ S │ EEEEEEEE│ MMMMMMM │ │
│ │1位│ 8位 │ 7位 │ │
│ └───┴────────┴───────────┘ │
│ │
│ 范围: 和FP32相同!(±3.4 × 10^38) │
│ 精度: 约2位有效十进制数字(精度较低) │
│ 每个参数: 2字节 │
│ │
│ 7B模型显存: 7B × 2B = 14GB │
│ │
│ 优点: 范围大(不溢出)+ 显存省一半 + 不需要loss scaling │
│ 缺点: 精度比FP16低 │
│ │
│ → AI训练的首选格式!(2024-2026年主流) │
└─────────────────────────────────────────────────────────────┘

BF16 vs FP16 对比图:

┌─────────────────────────────────────────────────────────────┐
│ BF16 vs FP16 核心区别 │
│ │
│ FP16: [S][EEEE E][MMMM MMMMMM] │
│ 指数5位 → 范围小(±65,504)但尾数10位 → 精度高 │
│ │
│ BF16: [S][EEEE EEEE][MMMM MMM] │
│ 指数8位 → 范围大(±3.4e38)但尾数7位 → 精度低 │
│ │
│ 直觉理解: │
│ FP16 像一把短尺子(量程小但刻度细) │
│ BF16 像一把长尺子(量程大但刻度粗) │
│ │
│ 对于AI训练: │
│ 范围比精度更重要 → 选BF16 │
│ 梯度值变化范围很大 → FP16容易溢出 → BF16不会 │
└─────────────────────────────────────────────────────────────┘

2.5 FP8——8位浮点(Hopper架构引入)

┌─────────────────────────────────────────────────────────────┐
│ FP8 格式 │
│ │
│ FP8有两种变体: │
│ │
│ E4M3(推理/前向传播): │
│ ┌───┬──────┬───────┐ │
│ │ S │ EEEE │ MMM │ │
│ │1位│ 4位 │ 3位 │ 共8位 = 1字节 │
│ └───┴──────┴───────┘ │
│ 范围: ±448 精度: 较高 │
│ │
│ E5M2(训练/梯度): │
│ ┌───┬───────┬──────┐ │
│ │ S │ EEEEE │ MM │ │
│ │1位│ 5位 │ 2位 │ 共8位 = 1字节 │
│ └───┴───────┴──────┘ │
│ 范围: ±57344 精度: 较低 │
│ │
│ 7B模型显存: 7B × 1B = 7GB ← 只需FP32的1/4! │
│ │
│ 用途: H100/B200等Hopper+架构的训练和推理加速 │
│ 需要: Hopper(H100)+ 或 Ada Lovelace(RTX4090)+架构 │
└─────────────────────────────────────────────────────────────┘

为什么FP8有两种变体?

前向传播(E4M3,精度优先):
计算模型输出,需要较高精度
范围较小(±448)但精度较高(3位尾数)

反向传播(E5M2,范围优先):
计算梯度,值可能很大或很小
范围较大(±57344)但精度较低(2位尾数)

→ 和BF16的逻辑一样:梯度需要更大范围

2.6 FP4——4位浮点(Blackwell架构引入)

┌─────────────────────────────────────────────────────────────┐
│ FP4 格式 │
│ │
│ ┌───┬─────┬───┐ │
│ │ S │ EEE │ M │ │
│ │1位│ 3位 │0位│ 共4位 = 0.5字节 │
│ └───┴─────┴───┘ │
│ │
│ 7B模型显存: 7B × 0.5B = 3.5GB ← 只需FP32的1/8! │
│ │
│ 用途: Blackwell(B200)+架构的推理加速 │
│ 精度: 很低,通常只用于推理,不用于训练 │
└─────────────────────────────────────────────────────────────┘


第三章:整数格式家族

3.1 整数格式基础

整数格式没有指数位,只有符号位+数值位

INT32: [S 1位][数值 31位] 范围: ±2,147,483,647
INT16: [S 1位][数值 15位] 范围: ±32,767
INT8: [S 1位][数值 7位] 范围: ±127 (或0-255无符号)
INT4: [S 1位][数值 3位] 范围: ±7 (或0-15无符号)

INT格式的特点:
– 不能表示小数(只有整数)
– 需要额外的"缩放因子"来映射到实际值
– 计算速度快(整数运算比浮点快)
– 显存极省

3.2 INT8——8位整数量化

┌─────────────────────────────────────────────────────────────┐
│ INT8 格式 │
│ │
│ 位结构: [S 1位][数值 7位] 共8位 = 1字节 │
│ 或无符号: [数值 8位] 共8位 = 1字节 │
│ │
│ ┌──┬─────────┐ │
│ │S │ DDDDDDD │ 有符号INT8: -128 到 +127 │
│ │1位│ 7位 │ │
│ └──┴─────────┘ │
│ ┌──────────────┐ │
│ │ DDDDDDDD │ 无符号INT8: 0 到 255 │
│ │ 8位 │ │
│ └──────────────┘ │
│ │
│ 7B模型显存: 7B × 1B = 7GB │
│ │
│ 关键问题:INT8怎么表示小数? │
│ → 量化(Quantization):用缩放因子将浮点数映射到整数 │
│ → 实际值 = INT8值 × 缩放因子(scale) │
│ │
│ 示例: │
│ 原始权重范围: [-1.5, +2.0] │
│ 映射到INT8: [-128, +127] │
│ scale = (2.0 – (-1.5)) / (127 – (-128)) = 0.0137 │
│ 原始值 0.5 → INT8值 = round(0.5 / 0.0137) = 36 │
│ 反量化: 36 × 0.0137 = 0.4932 ≈ 0.5 │
│ │
│ 精度损失: ~0.2%(通常可接受) │
│ │
│ 用途: 模型推理部署(主流方案) │
│ 工具: bitsandbytes, GPTQ, AWQ │
└─────────────────────────────────────────────────────────────┘

3.3 INT4——4位整数量化

┌─────────────────────────────────────────────────────────────┐
│ INT4 格式 │
│ │
│ 位结构: [S 1位][数值 3位] 共4位 = 0.5字节 │
│ 或无符号: [数值 4位] 共4位 = 0.5字节 │
│ │
│ 有符号INT4: -8 到 +7 │
│ 无符号INT4: 0 到 15 │
│ │
│ 7B模型显存: 7B × 0.5B = 3.5GB ← 只需FP32的1/8! │
│ │
│ INT4量化方式(NF4为例): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 原始权重分布(通常是正态分布) │ │
│ │ │ │
│ │ ╭────╮ │ │
│ │ ╱ ╲ │ │
│ │ ╱ ╲ │ │
│ │ ╱ ╲ │ │
│ │ ───╱────────────╲─── │ │
│ │ -3 -2 -1 0 1 2 3 │ │
│ │ │ │
│ │ NF4将16个量化级别按正态分布放置 │ │
│ │ 零附近的量化级别更密集(因为大部分权重在0附近) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 用途: QLoRA微调(4-bit量化基座 + LoRA适配器训练) │
│ 工具: bitsandbytes (NF4), GPTQ (INT4), AWQ │
└─────────────────────────────────────────────────────────────┘

3.4 NF4——NormalFloat 4-bit(QLoRA专用)

┌─────────────────────────────────────────────────────────────┐
│ NF4 格式 │
│ │
│ NF4 = NormalFloat 4-bit = 正态分布最优的4-bit量化格式 │
│ │
│ 核心洞察: │
│ 神经网络的权重通常服从正态分布(均值≈0) │
│ → 量化级别应该按照正态分布来放置 │
│ → 零附近更密集,远离零的地方更稀疏 │
│ │
│ 16个NF4量化级别(近似值): │
│ -1.0, -0.69, -0.53, -0.39, -0.29, -0.19, -0.10, 0.0, │
│ 0.08, 0.17, 0.27, 0.37, 0.51, 0.67, 0.89, 1.0 │
│ │
│ 对比均匀INT4: │
│ -7/8, -6/8, -5/8, -4/8, -3/8, -2/8, -1/8, 0, │
│ 1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8 │
│ │
│ NF4的优势: │
│ 对正态分布的数据,信息论最优 │
│ 量化误差比均匀INT4小约30% │
│ → QLoRA选择NF4而非普通INT4 │
└─────────────────────────────────────────────────────────────┘


第四章:所有格式总览对比

4.1 格式对比表

┌────────┬─────┬─────┬──────────┬─────────┬────────┬─────────────┐
│ 格式 │位数 │字节 │ 范围 │ 精度 │7B显存 │ 主要用途 │
├────────┼─────┼─────┼──────────┼─────────┼────────┼─────────────┤
│ FP32 │ 32 │ 4 │ ±3.4e38 │ 7位有效 │ 28GB │ 传统训练 │
│ TF32 │ 19 │ — │ ±3.4e38 │ 3位有效 │ — │ Tensor Core │
│ FP16 │ 16 │ 2 │ ±65,504 │ 3位有效 │ 14GB │ 混合精度训练 │
│ BF16 │ 16 │ 2 │ ±3.4e38 │ 2位有效 │ 14GB │ 训练首选 │
│ FP8 │ 8 │ 1 │ ±448/57K │ 2-3位 │ 7GB │ Hopper+训练 │
│ FP4 │ 4 │ 0.5 │ 很小 │ 很低 │ 3.5GB │ Blackwell推理│
│ INT8 │ 8 │ 1 │ ±127 │ 整数 │ 7GB │ 推理部署 │
│ INT4 │ 4 │ 0.5 │ ±7 │ 整数 │ 3.5GB │ QLoRA/推理 │
│ NF4 │ 4 │ 0.5 │ 自定义 │ 整数 │ 3.5GB │ QLoRA专用 │
└────────┴─────┴─────┴──────────┴─────────┴────────┴─────────────┘

4.2 显存占用对比

7B模型在不同精度下的显存占用:

FP32: ████████████████████████████ 28GB
FP16: ██████████████ 14GB
BF16: ██████████████ 14GB
FP8: ███████ 7GB
INT8: ███████ 7GB
INT4: ████ 3.5GB
NF4: ████ 3.5GB

节省比例(相对于FP32):
FP16/BF16: 省50%
FP8/INT8: 省75%
INT4/NF4: 省87.5%

4.3 精度 vs 范围 二维图

┌─────────────────────────────────────────────────────────────┐
│ 精度 vs 范围 二维空间 │
│ │
│ 范围(大) │
│ ↑ │
│ │ FP32 │
│ │ • │
│ │ BF16 • │
│ │ • │
│ │ TF32 • │
│ │ • │
│ │ │
│ │ │
│ │ FP8(E5M2) │
│ │ • │
│ │ │
│ │ FP8(E4M3) │
│ │ • FP16 │
│ │ • │
│ │ │
│ │ │
│ │ │
│ │ INT4/NF4 │
│ │ • INT8 │
│ │ • │
│ └─────────────────────────────────────────────→ 精度(高) │
│ │
│ 理想位置:右上角(范围大+精度高)→ 但位数有限,必须权衡 │
│ FP32最平衡但最费显存 │
│ BF16用范围换精度 → AI训练最佳权衡 │
└─────────────────────────────────────────────────────────────┘


第五章:存储精度 vs 计算精度——解开核心困惑

5.1 你的困惑

"INT4量化不是只能用于推理吗?为什么QLoRA还能用INT4训练?"

这个问题的答案在于区分两个概念:
1. 存储精度(Storage Precision):权重用什么格式存在显存中
2. 计算精度(Compute Precision):实际计算时用什么格式

关键结论:
存储精度 ≠ 计算精度
可以用INT4存储权重,但在计算时转换为FP16/BF16

5.2 存储精度 vs 计算精度

┌─────────────────────────────────────────────────────────────┐
│ 存储精度 vs 计算精度 分离架构 │
│ │
│ ════════ 标准训练(FP32)════════ │
│ │
│ 权重存储: FP32 (4字节/参数) │
│ 计算精度: FP32 │
│ 梯度存储: FP32 │
│ 优化器: FP32 │
│ 显存: 7B × 4B × 4 ≈ 112GB │
│ │
│ ════════ 混合精度训练(AMP)════════ │
│ │
│ 权重存储: FP32 (主副本) + FP16/BF16 (计算副本) │
│ 计算精度: FP16/BF16 │
│ 梯度存储: FP16/BF16 │
│ 优化器: FP32(维护主副本) │
│ 显存: 约70GB (省约35%) │
│ │
│ ════════ QLoRA训练(INT4存储 + BF16计算)════════ │
│ │
│ 权重存储: NF4/INT4 (0.5字节/参数) ← 只用于存储! │
│ 计算精度: BF16 (2字节/参数) ← 计算时转换为BF16! │
│ LoRA适配器: BF16 (2字节/参数) ← 可训练参数用BF16 │
│ 梯度: BF16(只对LoRA参数计算梯度) │
│ 优化器: BF16/FP32(只对LoRA参数) │
│ 显存: 约10GB (省约90%) │
│ │
│ 关键理解: │
│ INT4权重 → 反量化为BF16 → 做矩阵乘法(BF16) → 结果(BF16) │
│ 梯度只通过LoRA参数反向传播,不更新INT4权重 │
└─────────────────────────────────────────────────────────────┘

5.3 不同方法的存储/计算组合

┌────────────────┬──────────────┬──────────────┬───────────────┐
│ 方法 │ 权重存储精度 │ 计算精度 │ 可训练参数精度 │
├────────────────┼──────────────┼──────────────┼───────────────┤
│ 标准FP32训练 │ FP32 │ FP32 │ FP32 │
│ 混合精度(AMP) │ FP32+FP16 │ FP16/BF16 │ FP32(主副本) │
│ 纯BF16训练 │ BF16 │ BF16 │ BF16 │
│ QLoRA │ NF4/INT4 │ BF16 │ BF16(LoRA) │
│ INT8推理 │ INT8 │ INT8 │ 无(只推理) │
│ INT4推理 │ INT4/NF4 │ INT4/FP16 │ 无(只推理) │
│ FP8训练 │ FP8 │ FP8 │ FP32(主副本) │
└────────────────┴──────────────┴──────────────┴───────────────┘

5.4 类比理解

存储精度 vs 计算精度的类比——图书馆管理系统:

INT4存储 = 把书压缩打包放在仓库里(省空间)
BF16计算 = 读者借阅时解压打开阅读(需要完整内容)

仓库里(INT4):书被压缩到最小,省存储空间
阅读时(BF16):解压还原为完整版本,才能正常阅读

类比到QLoRA:
显存中存储的基座模型权重 = INT4压缩的书(省显存)
GPU计算时读取的权重 = BF16解压后的书(保精度)
LoRA适配器 = 新写的批注(用正常字迹BF16书写,可以修改)

你不能修改压缩的书(INT4基座权重冻结)
但你可以在书上添加批注(LoRA适配器可训练)
阅读时,书和批注一起看(INT4反量化为BF16 + LoRA BF16 → 合并输出)


第六章:量化技术详解

6.1 什么是量化?

量化(Quantization) = 将高精度数值映射为低精度数值

原始(FP32):0.123456789, -0.987654321, 1.234567890
量化后(INT8):36, -127, 127

需要存储:
– 量化后的整数值
– 缩放因子(scale):用于反量化
– 零点偏移(zero_point):某些量化方法需要

反量化:原始值 ≈ 量化值 × scale + zero_point

6.2 量化方法分类

┌─────────────────────────────────────────────────────────────┐
│ 量化方法分类 │
│ │
│ 按时间点分类: │
│ ├── 训练后量化(PTQ, Post-Training Quantization) │
│ │ 训练完成后直接量化,不需要重新训练 │
│ │ 代表:GPTQ, AWQ, bitsandbytes │
│ │ │
│ └── 量化感知训练(QAT, Quantization-Aware Training) │
│ 训练过程中模拟量化效果 │
│ 代表:QLoRA的NF4量化训练 │
│ │
│ 按粒度分类: │
│ ├── 逐层量化:每层一个scale │
│ ├── 逐通道量化:每个输出通道一个scale(更精确) │
│ └── 分组量化:每g个元素一个scale(QLoRA使用,g=64) │
│ │
│ 按对称性分类: │
│ ├── 对称量化:zero_point = 0 │
│ └── 非对称量化:zero_point ≠ 0(更灵活) │
└─────────────────────────────────────────────────────────────┘

6.3 GPTQ——训练后INT4量化

GPTQ是一种高效的训练后量化方法

工作原理:
1. 逐层量化权重矩阵
2. 用Hessian矩阵信息最小化量化误差
3. 每量化一个权重,调整后续权重来补偿误差

特点:
– 只需一次前向传播(用少量校准数据)
– 量化速度快(几分钟完成7B模型)
– 精度损失小
– 推理时直接使用INT4计算

示例:
原始FP16模型(14GB)→ GPTQ INT4量化 → 量化模型(3.5GB)
推理精度损失:通常<1%

6.4 AWQ——激活感知量化

AWQ = Activation-aware Weight Quantization

核心思想:
不是所有权重都同等重要
对激活值大的通道对应的权重要保护(少量化或高精度)

工作原理:
1. 分析激活值的分布
2. 找到"显著"通道(激活值大的)
3. 对显著通道用更高精度量化
4. 整体精度损失更小

特点:
– 精度通常优于GPTQ
– 推理速度也很快
– 社区广泛使用

6.5 bitsandbytes——QLoRA的量化后端

bitsandbytes是HuggingFace生态中最常用的量化库

支持的量化方式:
– 8-bit量化:用于推理
– 4-bit NF4量化:用于QLoRA训练
– 4-bit FP4量化:另一种4-bit格式
– 双重量化(Double Quantization):对量化参数也量化

QLoRA的量化过程:
1. 加载FP16/BF16模型
2. 对每一层的权重矩阵做NF4量化
3. 量化时使用分组量化(每64个元素一个scale)
4. scale也进一步量化为FP32(双重量化)
5. 存储:NF4权重 + FP32 scale


第七章:QLoRA的INT4训练原理——彻底搞懂

7.1 QLoRA的完整数据流

┌─────────────────────────────────────────────────────────────┐
│ QLoRA 完整数据流 │
│ │
│ ═══ 存储状态(显存中)═══ │
│ │
│ 基座模型权重: NF4格式(3.5GB for 7B) │
│ ┌──────────────────────────────────────┐ │
│ │ NF4压缩权重 + FP32缩放因子 │ │
│ │ 冻结,不可训练 │ │
│ └──────────────────────────────────────┘ │
│ │
│ LoRA适配器: BF16格式(~50MB for 7B) │
│ ┌──────────────────────────────────────┐ │
│ │ A矩阵: [r × hidden_dim] BF16 │ │
│ │ B矩阵: [hidden_dim × r] BF16 │ │
│ │ 可训练! │ │
│ └──────────────────────────────────────┘ │
│ │
│ 优化器状态: BF16/FP32(只存LoRA参数的) │
│ ┌──────────────────────────────────────┐ │
│ │ Adam的m和v: BF16 │ │
│ │ 只有LoRA参数的优化器状态 │ │
│ └──────────────────────────────────────┘ │
│ │
│ ═══ 前向传播(计算时)═══ │
│ │
│ 输入 x (BF16) │
│ │ │
│ ├──→ NF4权重 → 反量化 → BF16权重 → 矩阵乘法 → Wx (BF16) │
│ │ │
│ ├──→ LoRA: x → A(x) → B(A(x)) → BAx (BF16) │
│ │ │
│ └──→ 输出 = Wx + BAx (BF16) │
│ │
│ ═══ 反向传播(计算梯度)═══ │
│ │
│ 损失 L (BF16) │
│ │ │
│ ├──→ ∂L/∂A = ∂L/∂output × ∂output/∂A (计算LoRA A的梯度)│
│ ├──→ ∂L/∂B = ∂L/∂output × ∂output/∂B (计算LoRA B的梯度)│
│ │ │
│ └──→ 不计算∂L/∂W(基座权重冻结,不更新) │
│ │
│ ═══ 参数更新 ═══ │
│ │
│ A ← A – lr × ∂L/∂A (只更新LoRA参数) │
│ B ← B – lr × ∂L/∂B (只更新LoRA参数) │
│ W 不更新(INT4权重保持不变) │
└─────────────────────────────────────────────────────────────┘

7.2 反量化过程详解

NF4权重(存储)→ BF16权重(计算)的过程:

步骤1:读取NF4值(4-bit整数,0-15)
例:NF4值 = 7

步骤2:查NF4量化级别表
第7个级别 = 0.0(NF4设计中,中间值对应0)

步骤3:乘以分组缩放因子
BF16值 = 0.0 × scale_group = 0.0

实际代码(bitsandbytes内部):
def dequantize_nf4(weight_nf4, scales):
# 1. NF4 → FP16/BF16
weight_fp16 = nf4_to_fp16(weight_nf4) # 查表
# 2. 乘以缩放因子
weight_fp16 = weight_fp16 * scales
return weight_fp16

这个反量化操作在每次前向传播时都会执行
计算完成后,BF16结果被使用,NF4原始值保留在显存中

7.3 为什么INT4权重不会被梯度更新?

核心原因:计算图中,INT4权重是"常量",不是"变量"

PyTorch的autograd机制:

可训练参数:requires_grad=True → 反向传播时计算梯度
冻结参数: requires_grad=False → 不计算梯度,不更新

QLoRA中:
基座模型权重:requires_grad=False(INT4格式,冻结)
LoRA A矩阵: requires_grad=True(BF16格式,可训练)
LoRA B矩阵: requires_grad=True(BF16格式,可训练)

所以:
– 梯度只流过LoRA参数
– INT4权重不参与梯度计算
– INT4权重只在前向传播时被读取和反量化

7.4 QLoRA的显存分析

7B模型在QLoRA训练时的显存分解:

基座模型权重(NF4): 7B × 0.5B = 3.5 GB
缩放因子(FP32): ~0.2 GB
反量化临时BF16权重: ~14 GB(前向传播时临时创建,可复用)
LoRA适配器(BF16): ~50 MB
LoRA梯度(BF16): ~50 MB
优化器状态(BF16): ~100 MB
激活值(BF16): ~5-10 GB(取决于batch_size和序列长度)
─────────────────────
总计: 约 10-20 GB ← 单张RTX 3090/4090就能跑!

对比:
标准BF16训练: 7B × 2B(参数) + 2B(梯度) + 8B(优化器) + 激活 ≈ 70+ GB
QLoRA训练: 3.5B(NF4) + 0.1B(LoRA) + 0.15B(优化器) + 激活 ≈ 15 GB
节省约80%!


第八章:实战——如何选择数据格式

8.1 决策树

你的情境是什么?

训练模型?
├── 全量微调
│ ├── 显存充足 → BF16(首选)
│ └── 显存紧张 → DeepSpeed ZeRO + BF16

├── LoRA微调
│ ├── 显存充足(>24GB per 7B) → BF16基座 + BF16 LoRA
│ └── 显存紧张(<24GB per 7B) → QLoRA (NF4基座 + BF16 LoRA)

└── 推理/部署?
├── 追求精度 → BF16
├── 平衡精度和速度 → INT8
├── 极致压缩 → INT4 (GPTQ/AWQ)
└── H100+硬件 → FP8

8.2 不同场景的推荐格式

┌──────────────────┬──────────────┬─────────────────────────────┐
│ 场景 │ 推荐格式 │ 原因 │
├──────────────────┼──────────────┼─────────────────────────────┤
│ 全量微调(7B) │ BF16 │ 范围大不溢出,省显存 │
│ LoRA微调(7B) │ BF16基座 │ 24GB显存够用,精度最高 │
│ QLoRA微调(7B) │ NF4基座 │ 12GB显存即可,性价比最高 │
│ 推理部署(高性能) │ BF16/FP16 │ 精度损失极小 │
│ 推理部署(低成本) │ INT8 │ 显存省75%,精度损失<1% │
│ 推理部署(极致) │ INT4(GPTQ) │ 显存省87.5%,精度损失~2% │
│ H100训练 │ FP8 │ 速度提升2x,显存省50% │
│ 边缘设备推理 │ INT4(INT4) │ 显存最小,适合手机/IoT │
└──────────────────┴──────────────┴─────────────────────────────┘

8.3 精度损失对比

不同量化方案的精度损失(以7B模型为例):

BF16(基准): 0% 精度损失
INT8量化: ~0.5-1% 精度损失(通常可忽略)
INT4(GPTQ): ~1-3% 精度损失(可接受)
INT4(AWQ): ~1-2% 精度损失(通常优于GPTQ)
INT4(NF4 QLoRA): ~2-5% 精度损失(但可以微调恢复)
FP4: ~3-5% 精度损失

关键点:
INT8量化后的模型,精度损失通常可以忽略
INT4量化后精度损失明显,但配合LoRA微调可以恢复
→ 这就是QLoRA的核心价值


第九章:PyTorch中的精度控制

9.1 混合精度训练(AMP)

import torch
from torch.cuda.amp import autocast, GradScaler

# 创建模型(FP32)
model = MyModel().cuda()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
scaler = GradScaler() # 用于FP16的loss scaling

for data, target in dataloader:
optimizer.zero_grad()

# 自动混合精度:前向传播用FP16/BF16
with autocast(dtype=torch.bfloat16):
output = model(data)
loss = criterion(output, target)

# 反向传播(BF16不需要GradScaler,FP16需要)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()

# 注:BF16不需要GradScaler(范围大,不溢出)
# 只有FP16才需要GradScaler

9.2 模型加载时指定精度

import torch
from transformers import AutoModelForCausalLM

# FP32加载(默认)
model = AutoModelForCausalLM.from_pretrained("model_path")

# BF16加载(推荐)
model = AutoModelForCausalLM.from_pretrained(
"model_path",
torch_dtype=torch.bfloat16
)

# FP16加载
model = AutoModelForCausalLM.from_pretrained(
"model_path",
torch_dtype=torch.float16
)

# INT8量化加载(推理)
model = AutoModelForCausalLM.from_pretrained(
"model_path",
load_in_8bit=True
)

# INT4/NF4量化加载(QLoRA训练)
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16, # 计算时用BF16
bnb_4bit_use_double_quant=True
)
model = AutoModelForCausalLM.from_pretrained(
"model_path",
quantization_config=bnb_config
)

9.3 手动控制精度

import torch

# 创建BF16张量
x_bf16 = torch.tensor([1.5, 2.7, 3.14], dtype=torch.bfloat16)

# 创建FP16张量
x_fp16 = torch.tensor([1.5, 2.7, 3.14], dtype=torch.float16)

# 精度转换
x_fp32 = x_bf16.float() # BF16 → FP32
x_bf16 = x_fp32.bfloat16() # FP32 → BF16
x_fp16 = x_fp32.half() # FP32 → FP16

# 查看张量精度
print(x_bf16.dtype) # torch.bfloat16

# 模型整体转换精度
model_bf16 = model.bfloat16() # 整个模型转为BF16
model_fp16 = model.half() # 整个模型转为FP16


附录:术语表

FP32: 32位单精度浮点 (1+8+23)
FP16: 16位半精度浮点 (1+5+10)
BF16: 16位Brain浮点 (1+8+7),AI训练首选
TF32: 19位Tensor浮点 (1+8+10),Tensor Core内部使用
FP8: 8位浮点,E4M3(推理)和E5M2(训练)两种变体
FP4: 4位浮点,Blackwell架构引入
INT32: 32位整数
INT8: 8位整数,推理部署主流格式
INT4: 4位整数,极致压缩
NF4: NormalFloat 4-bit,QLoRA专用的最优4-bit格式

量化(Quantization): 将高精度数值映射为低精度数值
反量化(Dequantization): 将低精度数值还原为高精度
缩放因子(Scale): 量化时的缩放系数
零点(Zero Point): 非对称量化的偏移量
分组量化(Group Quantization): 每g个元素共享一个scale

GPTQ: 训练后INT4量化方法
AWQ: 激活感知的INT4量化方法
QLoRA: 4-bit量化基座 + LoRA微调
AMP: Automatic Mixed Precision,自动混合精度
Loss Scaling: FP16训练时防止梯度下溢的技术

存储精度: 权重在显存中的存储格式
计算精度: GPU实际计算时使用的格式
反量化开销: INT4→BF16转换的额外计算成本

赞(0)
未经允许不得转载:171主机测评 » fp32-int4系列数据格式深入理解
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址