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