探索 Diffusers 中的量化后端

发布于 2025 年 5 月 21 日
在 GitHub 上更新

像 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")

注意:当将 PipelineQuantizationConfigbitsandbytes 结合使用时,您需要分别从 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.ModuleListtorch.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() 结合使用以加快推理速度。
  • 优先考虑推理速度: torchaoGGUFbitsandbytes 都可以与 torch.compile() 结合使用,以潜在地提高推理速度。
  • 对于硬件灵活性(CPU/MPS)、FP8 精度: Quanto 是一个不错的选择。
  • 简洁性(Hopper/Ada):探索 FP8 分层转换 (enable_layerwise_casting)。
  • 对于使用现有 GGUF 模型:使用 GGUF 加载 (from_single_file)。
  • 对量化训练感兴趣?请关注后续关于该主题的博客文章!更新(2025 年 6 月 19 日):文章在此

量化显著降低了使用大型扩散模型的门槛。尝试这些后端,为您的需求找到内存、速度和质量的最佳平衡点。

致谢:感谢 Chunte 为本文提供了缩略图。

社区

注册登录发表评论