探索 Diffusers 中的量化后端
像 Flux(一种基于流的文本到图像生成模型)这样的大型扩散模型可以创建令人惊叹的图像,但它们的尺寸可能是一个障碍,需要大量的内存和计算资源。量化提供了一个强大的解决方案,可以在不大幅降低性能的情况下缩小这些模型,使其更易于访问。但最大的问题始终是:您真的能看出最终图像中的差异吗?
在我们深入了解 Hugging Face Diffusers 中各种量化后端的技术细节之前,为什么不先测试一下您自己的感知能力呢?
找出量化模型
我们设置了一个环境,您可以提供一个提示,然后我们使用原始高精度模型(例如 BF16 中的 Flux-dev)和几个量化版本(BnB 4 位、BnB 8 位)生成结果。然后将生成的图像呈现给您,您的挑战是识别哪些图像来自量化模型。
在此或下方尝试!
通常,特别是对于 8 位量化,差异是微妙的,不仔细检查可能不会注意到。更激进的量化,如 4 位或更低,可能更明显,但结果仍然很好,特别是考虑到巨大的内存节省。不过,NF4 通常能提供最佳的权衡。
现在,让我们深入探讨。
Diffusers 中的量化后端
在我们的上一篇文章“使用 Quanto 和 Diffusers 实现内存高效的扩散变换器”的基础上,本文将探讨直接集成到 Hugging Face Diffusers 中的各种量化后端。我们将研究 bitsandbytes、GGUF、torchao 和原生 FP8 支持如何使大型和强大的模型更易于访问,并通过 Flux 演示它们的用法。
在深入研究量化后端之前,让我们先介绍 FluxPipeline(使用 black-forest-labs/FLUX.1-dev 检查点)及其组件,我们将对其进行量化。以 BF16 精度加载完整的 FLUX.1-dev
模型大约需要 31.447 GB 内存。主要组件是
- 文本编码器 (CLIP 和 T5)
- 功能:处理输入文本提示。FLUX-dev 使用 CLIP 进行初步理解,使用更大的 T5 进行细致理解和更好的文本渲染。
- 内存:T5 - 9.52 GB;CLIP - 246 MB(BF16 格式)
- Transformer(主模型 - MMDiT)
- 功能:核心生成部分(多模态扩散 Transformer)。从文本嵌入生成潜在空间中的图像。
- 内存:23.8 GB(BF16 格式)
- 变分自编码器 (VAE)
- 功能:在像素空间和潜在空间之间转换图像。将生成的潜在表示解码为基于像素的图像。
- 内存:168 MB(BF16 格式)
- 量化焦点:示例将主要集中在 `transformer` 和 `text_encoder_2` (T5) 上,以实现最显著的内存节省。
prompts = [
"Baroque style, a lavish palace interior with ornate gilded ceilings, intricate tapestries, and dramatic lighting over a grand staircase.",
"Futurist style, a dynamic spaceport with sleek silver starships docked at angular platforms, surrounded by distant planets and glowing energy lines.",
"Noir style, a shadowy alleyway with flickering street lamps and a solitary trench-coated figure, framed by rain-soaked cobblestones and darkened storefronts.",
]
bitsandbytes (BnB)
bitsandbytes
是一个流行且用户友好的 8 位和 4 位量化库,广泛用于 LLM 和 QLoRA 微调。我们也可以将其用于基于 Transformer 的扩散和流模型。
BF16 |
BnB 4 位 |
BnB 8 位 |
使用 BF16(左)、BnB 4 位(中)和 BnB 8 位(右)量化对 Flux-dev 模型输出进行视觉比较。(点击图片放大) |
精度 | 加载后内存 | 峰值内存 | 推理时间 |
---|---|---|---|
BF16 | ~31.447 GB | 36.166 GB | 12 秒 |
4位 | 12.584 GB | 17.281 GB | 12 秒 |
8位 | 19.273 GB | 24.432 GB | 27 秒 |
所有基准测试均在 1 块 NVIDIA H100 80GB GPU 上执行
示例(Flux-dev 与 BnB 4 位)
import torch
from diffusers import FluxPipeline
from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig
from diffusers.quantizers import PipelineQuantizationConfig
from transformers import BitsAndBytesConfig as TransformersBitsAndBytesConfig
model_id = "black-forest-labs/FLUX.1-dev"
pipeline_quant_config = PipelineQuantizationConfig(
quant_mapping={
"transformer": DiffusersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
"text_encoder_2": TransformersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
}
)
pipe = FluxPipeline.from_pretrained(
model_id,
quantization_config=pipeline_quant_config,
torch_dtype=torch.bfloat16
)
pipe.to("cuda")
prompt = "Baroque style, a lavish palace interior with ornate gilded ceilings, intricate tapestries, and dramatic lighting over a grand staircase."
pipe_kwargs = {
"prompt": prompt,
"height": 1024,
"width": 1024,
"guidance_scale": 3.5,
"num_inference_steps": 50,
"max_sequence_length": 512,
}
print(f"Pipeline memory usage: {torch.cuda.max_memory_reserved() / 1024**3:.3f} GB")
image = pipe(
**pipe_kwargs, generator=torch.manual_seed(0),
).images[0]
print(f"Pipeline memory usage: {torch.cuda.max_memory_reserved() / 1024**3:.3f} GB")
image.save("flux-dev_bnb_4bit.png")
注意:当将
PipelineQuantizationConfig
与bitsandbytes
结合使用时,您需要分别从diffusers
导入DiffusersBitsAndBytesConfig
并从transformers
导入TransformersBitsAndBytesConfig
。这是因为这些组件来源于不同的库。如果您更喜欢一个更简单的设置,而无需管理这些不同的导入,您可以采用另一种方法进行管道级量化,此方法的示例可在 Diffusers 文档的管道级量化部分中找到。
有关更多信息,请查看 bitsandbytes 文档。
torchao
torchao
是一个 PyTorch 原生库,用于架构优化,提供量化、稀疏性和自定义数据类型,旨在与 torch.compile
和 FSDP 兼容。Diffusers 支持各种 torchao
的特殊数据类型,可实现对模型优化的精细控制。
int4_weight_only |
int8_weight_only |
float8_weight_only |
使用 torchao int4_weight_only(左)、int8_weight_only(中)和 float8_weight_only(右)量化对 Flux-dev 模型输出进行视觉比较。(点击图片放大) |
torchao 精度 | 加载后内存 | 峰值内存 | 推理时间 |
---|---|---|---|
int4_weight_only | 10.635 GB | 14.654 GB | 109 秒 |
int8_weight_only | 17.020 GB | 21.482 GB | 15 秒 |
float8_weight_only | 17.016 GB | 21.488 GB | 15 秒 |
示例(Flux-dev 与 torchao INT8 仅权重)
@@
- from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig
+ from diffusers import TorchAoConfig as DiffusersTorchAoConfig
- from transformers import BitsAndBytesConfig as TransformersBitsAndBytesConfig
+ from transformers import TorchAoConfig as TransformersTorchAoConfig
@@
pipeline_quant_config = PipelineQuantizationConfig(
quant_mapping={
- "transformer": DiffusersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
- "text_encoder_2": TransformersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
+ "transformer": DiffusersTorchAoConfig("int8_weight_only"),
+ "text_encoder_2": TransformersTorchAoConfig("int8_weight_only"),
}
)
示例(Flux-dev 与 torchao INT4 仅权重)
@@
- from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig
+ from diffusers import TorchAoConfig as DiffusersTorchAoConfig
- from transformers import BitsAndBytesConfig as TransformersBitsAndBytesConfig
+ from transformers import TorchAoConfig as TransformersTorchAoConfig
@@
pipeline_quant_config = PipelineQuantizationConfig(
quant_mapping={
- "transformer": DiffusersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
- "text_encoder_2": TransformersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
+ "transformer": DiffusersTorchAoConfig("int4_weight_only"),
+ "text_encoder_2": TransformersTorchAoConfig("int4_weight_only"),
}
)
pipe = FluxPipeline.from_pretrained(
model_id,
quantization_config=pipeline_quant_config,
torch_dtype=torch.bfloat16,
+ device_map="balanced"
)
- pipe.to("cuda")
欲了解更多信息,请查看 torchao 文档。
Quanto
Quanto 是一个通过 optimum
库与 Hugging Face 生态系统集成的量化库。
INT4 |
INT8 |
FP8 |
Flux-dev 模型输出的视觉比较,使用 Quanto INT4(左)、INT8(中)和 FP8(右)量化。(点击图片放大) |
Quanto 精度 | 加载后内存 | 峰值内存 | 推理时间 |
---|---|---|---|
INT4 | 12.254 GB | 16.139 GB | 109 秒 |
INT8 | 17.330 GB | 21.814 GB | 15 秒 |
FP8 | 16.395 GB | 20.898 GB | 16 秒 |
示例(Flux-dev 与 quanto INT8 仅权重)
@@
- from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig
+ from diffusers import QuantoConfig as DiffusersQuantoConfig
- from transformers import BitsAndBytesConfig as TransformersBitsAndBytesConfig
+ from transformers import QuantoConfig as TransformersQuantoConfig
@@
pipeline_quant_config = PipelineQuantizationConfig(
quant_mapping={
- "transformer": DiffusersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
- "text_encoder_2": TransformersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
+ "transformer": DiffusersQuantoConfig(weights_dtype="int8"),
+ "text_encoder_2": TransformersQuantoConfig(weights_dtype="int8"),
}
)
注意:在撰写本文时,对于 Quanto 的 float8 支持,您需要 `optimum-quanto<0.2.5` 并直接使用 quanto。我们正在努力解决此问题。
示例(Flux-dev 与 quanto FP8 仅权重)
import torch
from diffusers import AutoModel, FluxPipeline
from transformers import T5EncoderModel
from optimum.quanto import freeze, qfloat8, quantize
model_id = "black-forest-labs/FLUX.1-dev"
text_encoder_2 = T5EncoderModel.from_pretrained(
model_id,
subfolder="text_encoder_2",
torch_dtype=torch.bfloat16,
)
quantize(text_encoder_2, weights=qfloat8)
freeze(text_encoder_2)
transformer = AutoModel.from_pretrained(
model_id,
subfolder="transformer",
torch_dtype=torch.bfloat16,
)
quantize(transformer, weights=qfloat8)
freeze(transformer)
pipe = FluxPipeline.from_pretrained(
model_id,
transformer=transformer,
text_encoder_2=text_encoder_2,
torch_dtype=torch.bfloat16
).to("cuda")
有关更多信息,请查看 Quanto 文档。
GGUF
GGUF 是 llama.cpp 社区中流行的文件格式,用于存储量化模型。
Q2_k |
Q4_1 |
Q8_0 |
使用 GGUF Q2_k(左)、Q4_1(中)和 Q8_0(右)量化对 Flux-dev 模型输出进行视觉比较。(点击图片放大) |
GGUF 精度 | 加载后内存 | 峰值内存 | 推理时间 |
---|---|---|---|
Q2_k | 13.264 GB | 17.752 GB | 26 秒 |
Q4_1 | 16.838 GB | 21.326 GB | 23 秒 |
Q8_0 | 21.502 GB | 25.973 GB | 15 秒 |
示例(Flux-dev 与 GGUF Q4_1)
import torch
from diffusers import FluxPipeline, FluxTransformer2DModel, GGUFQuantizationConfig
model_id = "black-forest-labs/FLUX.1-dev"
# Path to a pre-quantized GGUF file
ckpt_path = "https://huggingface.co/city96/FLUX.1-dev-gguf/resolve/main/flux1-dev-Q4_1.gguf"
transformer = FluxTransformer2DModel.from_single_file(
ckpt_path,
quantization_config=GGUFQuantizationConfig(compute_dtype=torch.bfloat16),
torch_dtype=torch.bfloat16,
)
pipe = FluxPipeline.from_pretrained(
model_id,
transformer=transformer,
torch_dtype=torch.bfloat16,
)
pipe.to("cuda")
有关更多信息,请查看 GGUF 文档。
FP8 分层转换 (enable_layerwise_casting
)
FP8 分层转换是一种内存优化技术。它通过将模型权重以紧凑的 FP8(8 位浮点)格式存储来实现,该格式的内存使用量大约是标准 FP16 或 BF16 精度的一半。在层执行计算之前,其权重会被动态转换为更高的计算精度(如 FP16/BF16)。紧接着,权重会被转换回 FP8 以进行高效存储。这种方法之所以有效,是因为核心计算保持了高精度,并且对量化特别敏感的层(如归一化)通常会被跳过。该技术还可以与组卸载结合使用,以进一步节省内存。
FP8 (e4m3) |
使用 FP8 分层转换 (e4m3) 量化后的 Flux-dev 模型视觉输出。 |
精度 | 加载后内存 | 峰值内存 | 推理时间 |
---|---|---|---|
FP8 (e4m3) | 23.682 GB | 28.451 GB | 13 秒 |
import torch
from diffusers import AutoModel, FluxPipeline
model_id = "black-forest-labs/FLUX.1-dev"
transformer = AutoModel.from_pretrained(
model_id,
subfolder="transformer",
torch_dtype=torch.bfloat16
)
transformer.enable_layerwise_casting(storage_dtype=torch.float8_e4m3fn, compute_dtype=torch.bfloat16)
pipe = FluxPipeline.from_pretrained(model_id, transformer=transformer, torch_dtype=torch.bfloat16)
pipe.to("cuda")
欲了解更多信息,请查看分层转换文档。
结合更多内存优化和 torch.compile
大多数这些量化后端都可以与 Diffusers 中提供的内存优化技术结合使用。让我们探讨一下 CPU 卸载、组卸载和 torch.compile
。您可以在 Diffusers 文档中了解更多这些技术。
注意:在撰写本文时,如果 bnb 从源代码安装并使用 PyTorch nightly 或 `fullgraph=False`,bnb + `torch.compile` 也能正常工作。
示例(Flux-dev 与 BnB 4 位 + enable_model_cpu_offload)
import torch
from diffusers import FluxPipeline
from diffusers import BitsAndBytesConfig as DiffusersBitsAndBytesConfig
from diffusers.quantizers import PipelineQuantizationConfig
from transformers import BitsAndBytesConfig as TransformersBitsAndBytesConfig
model_id = "black-forest-labs/FLUX.1-dev"
pipeline_quant_config = PipelineQuantizationConfig(
quant_mapping={
"transformer": DiffusersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
"text_encoder_2": TransformersBitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16),
}
)
pipe = FluxPipeline.from_pretrained(
model_id,
quantization_config=pipeline_quant_config,
torch_dtype=torch.bfloat16
)
- pipe.to("cuda")
+ pipe.enable_model_cpu_offload()
模型 CPU 卸载 (enable_model_cpu_offload
):此方法在推理管道期间在 CPU 和 GPU 之间移动整个模型组件(如 UNet、文本编码器或 VAE)。它提供了大量的 VRAM 节省,并且通常比更精细的卸载更快,因为它涉及更少、更大的数据传输。
bnb + enable_model_cpu_offload
:
精度 | 加载后内存 | 峰值内存 | 推理时间 |
---|---|---|---|
4位 | 12.383 GB | 12.383 GB | 17 秒 |
8位 | 19.182 GB | 23.428 GB | 27 秒 |
示例(Flux-dev 与 fp8 分层转换 + 组卸载)
import torch
from diffusers import FluxPipeline, AutoModel
model_id = "black-forest-labs/FLUX.1-dev"
transformer = AutoModel.from_pretrained(
model_id,
subfolder="transformer",
torch_dtype=torch.bfloat16,
# device_map="cuda"
)
transformer.enable_layerwise_casting(storage_dtype=torch.float8_e4m3fn, compute_dtype=torch.bfloat16)
+ transformer.enable_group_offload(onload_device=torch.device("cuda"), offload_device=torch.device("cpu"), offload_type="leaf_level", use_stream=True)
pipe = FluxPipeline.from_pretrained(model_id, transformer=transformer, torch_dtype=torch.bfloat16)
- pipe.to("cuda")
组卸载 (enable_group_offload
用于 diffusers
组件或 apply_group_offloading
用于通用 torch.nn.Module
):它将内部模型层组(如 torch.nn.ModuleList
或 torch.nn.Sequential
实例)移动到 CPU。这种方法通常比完全模型卸载更节省内存,并且比顺序卸载更快。
FP8 分层转换 + 组卸载:
精度 | 加载后内存 | 峰值内存 | 推理时间 |
---|---|---|---|
FP8 (e4m3) | 9.264 GB | 14.232 GB | 58 秒 |
示例(Flux-dev 与 torchao 4 位 + torch.compile)
import torch
from diffusers import FluxPipeline
from diffusers import TorchAoConfig as DiffusersTorchAoConfig
from diffusers.quantizers import PipelineQuantizationConfig
from transformers import TorchAoConfig as TransformersTorchAoConfig
from torchao.quantization import Float8WeightOnlyConfig
model_id = "black-forest-labs/FLUX.1-dev"
dtype = torch.bfloat16
pipeline_quant_config = PipelineQuantizationConfig(
quant_mapping={
"transformer":DiffusersTorchAoConfig("int4_weight_only"),
"text_encoder_2": TransformersTorchAoConfig("int4_weight_only"),
}
)
pipe = FluxPipeline.from_pretrained(
model_id,
quantization_config=pipeline_quant_config,
torch_dtype=torch.bfloat16,
device_map="balanced"
)
+ pipe.transformer = torch.compile(pipe.transformer, mode="max-autotune", fullgraph=True)
注意:
torch.compile
可能会引入细微的数值差异,导致图像输出发生变化。
torch.compile: 另一种补充方法是使用 PyTorch 2.x 的 torch.compile() 功能来加速模型的执行。编译模型不会直接降低内存使用,但可以显著加快推理速度。PyTorch 2.0 的 compile (Torch Dynamo) 通过提前跟踪和优化模型图来工作。
torchao + torch.compile
:
torchao 精度 | 加载后内存 | 峰值内存 | 推理时间 | 编译时间 |
---|---|---|---|---|
int4_weight_only | 10.635 GB | 15.238 GB | 6 秒 | ~285 秒 |
int8_weight_only | 17.020 GB | 22.473 GB | 8 秒 | ~851 秒 |
float8_weight_only | 17.016 GB | 22.115 GB | 8 秒 | ~545 秒 |
在此处探索一些基准测试结果
即用型量化检查点
您可以在我们的 Hugging Face 收藏中找到此博客文章中介绍的 `bitsandbytes` 和 `torchao` 量化模型:收藏链接。
结论
以下是选择量化后端的快速指南
- 最简单的内存节省 (NVIDIA):从
bitsandbytes
4/8 位开始。这也可以与torch.compile()
结合使用以加快推理速度。 - 优先考虑推理速度:
torchao
、GGUF
和bitsandbytes
都可以与torch.compile()
结合使用,以潜在地提高推理速度。 - 对于硬件灵活性(CPU/MPS)、FP8 精度:
Quanto
是一个不错的选择。 - 简洁性(Hopper/Ada):探索 FP8 分层转换 (
enable_layerwise_casting
)。 - 对于使用现有 GGUF 模型:使用 GGUF 加载 (
from_single_file
)。 - 对量化训练感兴趣?请关注后续关于该主题的博客文章!更新(2025 年 6 月 19 日):文章在此!
量化显著降低了使用大型扩散模型的门槛。尝试这些后端,为您的需求找到内存、速度和质量的最佳平衡点。
致谢:感谢 Chunte 为本文提供了缩略图。