探索 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 為本文提供了縮圖。

社群

註冊登入發表評論

© . This site is unofficial and not affiliated with Hugging Face, Inc.