示例代码:
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers import BitsAndBytesConfig# 配置 4-bit 加载
bnb_config = BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_compute_dtype="float16", # 用 fp16 来计算bnb_4bit_quant_type="nf4", # 用 NF4 量化算法
)model_name = "unsloth/llama-3.1-8b-unsloth-bnb-4bit"model = AutoModelForCausalLM.from_pretrained(model_name,quantization_config=bnb_config,device_map="auto"
)tokenizer = AutoTokenizer.from_pretrained(model_name)
量化的是「模型参数的存储」,不是模型的「计算过程」;
模型仍然会把这些 4bit 权重在计算时转回更高精度的 float16 或 float32 来乘加运算,否则结果会严重失真!
🧠 分清两个阶段:存储 vs 计算
阶段 | 做了什么事 | 精度 |
---|---|---|
✅ 存储阶段 | 把模型权重变成 4bit 的压缩形式(如 NF4) | 极小(4bit) |
✅ 计算阶段 | 用更高精度的 float16 / float32 做计算 | 高精度(16bit 或 32bit) |
也就是说:
- 存的时候:模型是瘦身版
- 算的时候:仍然需要「扩展」回稍微胖一点的精度来进行乘法、加法等操作
🔍 代码里的这几个参数代表什么?
bnb_config = BitsAndBytesConfig(load_in_4bit=True, # 权重用 4bit 存储bnb_4bit_compute_dtype="float16", # 运算时提升精度到 fp16bnb_4bit_quant_type="nf4", # 用更高质量的 NF4 算法来压缩
)
🔹 load_in_4bit=True
- 指定只在显存或内存中存储 4bit 量化的权重
- 节省 4~8 倍显存(VRAM)
🔹 bnb_4bit_compute_dtype="float16"
-
指定在运算时(如矩阵乘法)把 4bit 权重 解码 成 float16 进行运算
-
比如:
权重(4bit): 编码值 = 12 解码 → float16: 权重 ≈ 0.3125
-
这样可以保留一部分精度,结果不会像用 4bit 直接运算那样严重失真
🚨 如果你选择的是
"int4"
来计算,那模型效果会非常糟糕!完全不可用!
🔹 bnb_4bit_quant_type="nf4"
NF4
是一种 更先进的 4bit 非线性量化算法- 它不像传统线性量化那样平均划分区间,而是对神经网络中实际出现较多的值分配更密集的编码
- 效果更接近未量化模型
✅ 举个具体例子:
假设你有一个权重矩阵(未量化前):
[0.15, -0.33, 0.92, -0.87]
使用 NF4 算法压缩成 4bit 后,存的是:
[9, 3, 14, 2] # 仅 4bit 编码索引,不是 float 值
运算时会发生什么?
步骤1:解码 → float16
[9 → 0.16, 3 → -0.32, 14 → 0.93, 2 → -0.85]步骤2:与输入做 float16 的矩阵乘法
所以虽然你存的是 4bit 的索引值,计算用的是解码后的 float16 值
→ 这就是 "compute_dtype"
的含义
🔥 为什么不是直接 4bit 算呢?
👉 因为:
-
4bit 之间做矩阵乘法(比如权重 × 输入向量)是非常不稳定、误差大的
-
float16 是 GPU 很擅长的精度,速度快又能保持较好的效果
-
所以主流做法是:
存的时候 4bit(为了压缩)
运算的时候 float16(为了准确)
🧠 总结说人话
问题 | 回答 |
---|---|
❓ 4bit 了为什么还用 fp16? | 因为只量化了「存储」,计算时需要解码为更高精度避免精度损失 |
❓ NF4 是什么? | 一种更聪明的 4bit 编码方式,让常见值精度更高 |
❓ bnb_4bit_compute_dtype 是干嘛的? | 决定模型解码后用什么精度来做乘加计算(通常是 float16) |