[Feature]: FP8 Quantization Support for DiT (#1034)
Signed-off-by: lishunyang <lishunyang12@163.com> Signed-off-by: SYLAR <125541396+lishunyang12@users.noreply.github.com>
This commit is contained in:
1 parent
f0bca6f66b
commit
830d261f9d
17 files changed
+688
-13
No files matched your search
@@ -4,7 +4,7 @@
|
||||
import enum
|
||||
import os
|
||||
import random
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, Mapping
|
||||
from dataclasses import dataclass, field, fields
|
||||
from typing import Any
|
||||
|
||||
@@ -14,6 +14,10 @@ from typing_extensions import Self
|
||||
from vllm.config.utils import config
|
||||
from vllm.logger import init_logger
|
||||
|
||||
from vllm_omni.diffusion.quantization import (
|
||||
DiffusionQuantizationConfig,
|
||||
get_diffusion_quant_config,
|
||||
)
|
||||
from vllm_omni.diffusion.utils.network_utils import is_port_available
|
||||
|
||||
logger = init_logger(__name__)
|
||||
@@ -369,6 +373,11 @@ class OmniDiffusionConfig:
|
||||
# Omni configuration (injected from stage config)
|
||||
omni_kv_config: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
# Quantization settings
|
||||
# Supported methods: "fp8" (FP8 W8A8 on Ada/Hopper, weight-only on older GPUs)
|
||||
quantization: str | None = None
|
||||
quantization_config: "DiffusionQuantizationConfig | dict[str, Any] | None" = None
|
||||
|
||||
def settle_port(self, port: int, port_inc: int = 42, max_attempts: int = 100) -> int:
|
||||
"""
|
||||
Find an available port with retry logic.
|
||||
@@ -455,6 +464,33 @@ class OmniDiffusionConfig:
|
||||
# If it's neither dict nor DiffusionCacheConfig, convert to empty config
|
||||
self.cache_config = DiffusionCacheConfig()
|
||||
|
||||
# Convert quantization config
|
||||
if self.quantization is not None or self.quantization_config is not None:
|
||||
# Handle dict or DictConfig (from OmegaConf) - use Mapping for broader compatibility
|
||||
if isinstance(self.quantization_config, Mapping):
|
||||
# Convert DictConfig to dict if needed (OmegaConf compatibility)
|
||||
config_dict = dict(self.quantization_config)
|
||||
# Use get() instead of pop() to avoid mutating original dict
|
||||
quant_method = config_dict.get("method", self.quantization)
|
||||
# Filter out "method" key for kwargs
|
||||
quant_kwargs = {k: v for k, v in config_dict.items() if k != "method"}
|
||||
|
||||
# Validate conflicting methods
|
||||
if self.quantization is not None and quant_method is not None and quant_method != self.quantization:
|
||||
logger.warning(
|
||||
f"Conflicting quantization methods: quantization={self.quantization!r}, "
|
||||
f"quantization_config['method']={quant_method!r}. Using quantization_config['method']."
|
||||
)
|
||||
|
||||
self.quantization_config = get_diffusion_quant_config(quant_method, **quant_kwargs)
|
||||
elif self.quantization_config is None and self.quantization is not None:
|
||||
self.quantization_config = get_diffusion_quant_config(self.quantization)
|
||||
elif not isinstance(self.quantization_config, DiffusionQuantizationConfig):
|
||||
raise TypeError(
|
||||
f"quantization_config must be a DiffusionQuantizationConfig, dict, or None, "
|
||||
f"got {type(self.quantization_config)!r}"
|
||||
)
|
||||
|
||||
if self.max_cpu_loras is None:
|
||||
self.max_cpu_loras = 1
|
||||
elif self.max_cpu_loras < 1:
|
||||
|
||||
@@ -13,6 +13,7 @@ from torch import nn
|
||||
from vllm.config import ModelConfig
|
||||
from vllm.config.load import LoadConfig
|
||||
from vllm.logger import init_logger
|
||||
from vllm.model_executor.layers.quantization.base_config import QuantizeMethodBase
|
||||
from vllm.model_executor.model_loader.weight_utils import (
|
||||
download_safetensors_index_file_from_hf,
|
||||
download_weights_from_hf,
|
||||
@@ -217,8 +218,36 @@ class DiffusersPipelineLoader:
|
||||
logger.debug("Loading weights on %s ...", load_device)
|
||||
# Quantization does not happen in `load_weights` but after it
|
||||
self.load_weights(model)
|
||||
|
||||
# Process weights after loading for quantization (e.g., FP8 online quantization)
|
||||
# This is needed for vLLM's quantization methods that need to transform weights
|
||||
self._process_weights_after_loading(model, target_device)
|
||||
|
||||
return model.eval()
|
||||
|
||||
def _process_weights_after_loading(self, model: nn.Module, target_device: torch.device) -> None:
|
||||
"""Process weights after loading for quantization methods.
|
||||
|
||||
This handles vLLM's quantization methods that need to process weights
|
||||
after loading (e.g., FP8 online quantization from BF16/FP16 weights).
|
||||
"""
|
||||
for _, module in model.named_modules():
|
||||
quant_method = getattr(module, "quant_method", None)
|
||||
if isinstance(quant_method, QuantizeMethodBase):
|
||||
# Move module to target device for processing if needed
|
||||
module_device = next(module.parameters(), None)
|
||||
if module_device is not None:
|
||||
module_device = module_device.device
|
||||
needs_device_move = module_device != target_device
|
||||
|
||||
if needs_device_move:
|
||||
module.to(target_device)
|
||||
|
||||
quant_method.process_weights_after_loading(module)
|
||||
|
||||
if needs_device_move:
|
||||
module.to(module_device)
|
||||
|
||||
def load_weights(self, model: nn.Module) -> None:
|
||||
weights_to_load = {name for name, _ in model.named_parameters()}
|
||||
loaded_weights = model.load_weights(self.get_all_weights(model))
|
||||
|
||||
@@ -33,6 +33,7 @@ from vllm_omni.diffusion.models.qwen_image.cfg_parallel import (
|
||||
from vllm_omni.diffusion.models.qwen_image.qwen_image_transformer import (
|
||||
QwenImageTransformer2DModel,
|
||||
)
|
||||
from vllm_omni.diffusion.quantization import get_vllm_quant_config_for_layers
|
||||
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||
from vllm_omni.diffusion.utils.tf_utils import get_transformer_config_kwargs
|
||||
from vllm_omni.model_executor.model_loader.weight_utils import (
|
||||
@@ -272,7 +273,10 @@ class QwenImagePipeline(nn.Module, QwenImageCFGParallelMixin):
|
||||
self.device
|
||||
)
|
||||
transformer_kwargs = get_transformer_config_kwargs(od_config.tf_model_config, QwenImageTransformer2DModel)
|
||||
self.transformer = QwenImageTransformer2DModel(od_config=od_config, **transformer_kwargs)
|
||||
quant_config = get_vllm_quant_config_for_layers(od_config.quantization_config)
|
||||
self.transformer = QwenImageTransformer2DModel(
|
||||
od_config=od_config, quant_config=quant_config, **transformer_kwargs
|
||||
)
|
||||
|
||||
self.tokenizer = Qwen2Tokenizer.from_pretrained(model, subfolder="tokenizer", local_files_only=local_files_only)
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterable
|
||||
from functools import lru_cache
|
||||
from math import prod
|
||||
from typing import Any
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
@@ -23,6 +25,11 @@ from vllm.model_executor.layers.linear import (
|
||||
)
|
||||
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.model_executor.layers.quantization.base_config import (
|
||||
QuantizationConfig,
|
||||
)
|
||||
|
||||
from vllm_omni.diffusion.attention.backends.abstract import (
|
||||
AttentionMetadata,
|
||||
)
|
||||
@@ -390,7 +397,16 @@ class QwenEmbedRope(nn.Module):
|
||||
|
||||
|
||||
class ColumnParallelApproxGELU(nn.Module):
|
||||
def __init__(self, dim_in: int, dim_out: int, *, approximate: str, bias: bool = True):
|
||||
def __init__(
|
||||
self,
|
||||
dim_in: int,
|
||||
dim_out: int,
|
||||
*,
|
||||
approximate: str,
|
||||
bias: bool = True,
|
||||
quant_config: QuantizationConfig | None = None,
|
||||
prefix: str = "",
|
||||
):
|
||||
super().__init__()
|
||||
self.proj = ColumnParallelLinear(
|
||||
dim_in,
|
||||
@@ -398,6 +414,8 @@ class ColumnParallelApproxGELU(nn.Module):
|
||||
bias=bias,
|
||||
gather_output=False,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
prefix=prefix,
|
||||
)
|
||||
self.approximate = approximate
|
||||
|
||||
@@ -415,6 +433,8 @@ class FeedForward(nn.Module):
|
||||
activation_fn: str = "gelu-approximate",
|
||||
inner_dim: int | None = None,
|
||||
bias: bool = True,
|
||||
quant_config: QuantizationConfig | None = None,
|
||||
prefix: str = "",
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
@@ -424,13 +444,17 @@ class FeedForward(nn.Module):
|
||||
dim_out = dim_out or dim
|
||||
|
||||
layers: list[nn.Module] = [
|
||||
ColumnParallelApproxGELU(dim, inner_dim, approximate="tanh", bias=bias),
|
||||
ColumnParallelApproxGELU(
|
||||
dim, inner_dim, approximate="tanh", bias=bias, quant_config=quant_config, prefix=prefix
|
||||
),
|
||||
nn.Identity(), # placeholder for weight loading
|
||||
RowParallelLinear(
|
||||
inner_dim,
|
||||
dim_out,
|
||||
input_is_parallel=True,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
prefix=prefix,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -456,6 +480,7 @@ class QwenImageCrossAttention(nn.Module):
|
||||
pre_only: bool = False,
|
||||
context_pre_only: bool = False,
|
||||
out_dim: int | None = None,
|
||||
quant_config: QuantizationConfig | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
assert dim % num_heads == 0
|
||||
@@ -471,6 +496,8 @@ class QwenImageCrossAttention(nn.Module):
|
||||
hidden_size=dim,
|
||||
head_size=self.head_dim,
|
||||
total_num_heads=num_heads,
|
||||
quant_config=quant_config,
|
||||
prefix="to_qkv",
|
||||
)
|
||||
self.query_num_heads = self.to_qkv.num_heads
|
||||
self.kv_num_heads = self.to_qkv.num_kv_heads
|
||||
@@ -485,6 +512,8 @@ class QwenImageCrossAttention(nn.Module):
|
||||
hidden_size=added_kv_proj_dim,
|
||||
head_size=head_dim,
|
||||
total_num_heads=num_heads,
|
||||
quant_config=quant_config,
|
||||
prefix="add_kv_proj",
|
||||
)
|
||||
self.add_query_num_heads = self.add_kv_proj.num_heads
|
||||
self.add_kv_num_heads = self.add_kv_proj.num_kv_heads
|
||||
@@ -496,6 +525,8 @@ class QwenImageCrossAttention(nn.Module):
|
||||
bias=out_bias,
|
||||
input_is_parallel=True,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
prefix="to_add_out",
|
||||
)
|
||||
|
||||
assert not pre_only
|
||||
@@ -505,6 +536,8 @@ class QwenImageCrossAttention(nn.Module):
|
||||
bias=out_bias,
|
||||
input_is_parallel=True,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
prefix="to_out",
|
||||
)
|
||||
|
||||
self.norm_added_q = RMSNorm(head_dim, eps=eps)
|
||||
@@ -637,6 +670,7 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
qk_norm: str = "rms_norm",
|
||||
eps: float = 1e-6,
|
||||
zero_cond_t: bool = False,
|
||||
quant_config: QuantizationConfig | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
@@ -656,9 +690,10 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
added_kv_proj_dim=dim,
|
||||
context_pre_only=False,
|
||||
head_dim=attention_head_dim,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
self.img_norm2 = AdaLayerNorm(dim, elementwise_affine=False, eps=eps)
|
||||
self.img_mlp = FeedForward(dim=dim, dim_out=dim)
|
||||
self.img_mlp = FeedForward(dim=dim, dim_out=dim, quant_config=quant_config, prefix="img_mlp")
|
||||
|
||||
# Text processing modules
|
||||
self.txt_mod = nn.Sequential(
|
||||
@@ -668,7 +703,7 @@ class QwenImageTransformerBlock(nn.Module):
|
||||
self.txt_norm1 = AdaLayerNorm(dim, elementwise_affine=False, eps=eps)
|
||||
# Text doesn't need separate attention - it's handled by img_attn joint computation
|
||||
self.txt_norm2 = AdaLayerNorm(dim, elementwise_affine=False, eps=eps)
|
||||
self.txt_mlp = FeedForward(dim=dim, dim_out=dim)
|
||||
self.txt_mlp = FeedForward(dim=dim, dim_out=dim, quant_config=quant_config, prefix="txt_mlp")
|
||||
|
||||
self.zero_cond_t = zero_cond_t
|
||||
|
||||
@@ -862,6 +897,7 @@ class QwenImageTransformer2DModel(CachedTransformer):
|
||||
zero_cond_t: bool = False,
|
||||
use_additional_t_cond: bool = False,
|
||||
use_layer3d_rope: bool = False,
|
||||
quant_config: QuantizationConfig | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.parallel_config = od_config.parallel_config
|
||||
@@ -891,6 +927,7 @@ class QwenImageTransformer2DModel(CachedTransformer):
|
||||
num_attention_heads=num_attention_heads,
|
||||
attention_head_dim=attention_head_dim,
|
||||
zero_cond_t=zero_cond_t,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
for _ in range(num_layers)
|
||||
]
|
||||
|
||||
@@ -37,6 +37,7 @@ from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineL
|
||||
from vllm_omni.diffusion.models.z_image.z_image_transformer import (
|
||||
ZImageTransformer2DModel,
|
||||
)
|
||||
from vllm_omni.diffusion.quantization import get_vllm_quant_config_for_layers
|
||||
from vllm_omni.diffusion.request import OmniDiffusionRequest
|
||||
from vllm_omni.model_executor.model_loader.weight_utils import (
|
||||
download_weights_from_hf_specific,
|
||||
@@ -173,7 +174,9 @@ class ZImagePipeline(nn.Module):
|
||||
self.vae = AutoencoderKL.from_pretrained(model, subfolder="vae", local_files_only=local_files_only).to(
|
||||
self._execution_device
|
||||
)
|
||||
self.transformer = ZImageTransformer2DModel()
|
||||
# Get vLLM quantization config for linear layers
|
||||
quant_config = get_vllm_quant_config_for_layers(od_config.quantization_config)
|
||||
self.transformer = ZImageTransformer2DModel(quant_config=quant_config)
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(model, subfolder="tokenizer", local_files_only=local_files_only)
|
||||
|
||||
# Note: Context parallelism is applied centrally in registry.initialize_model()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import math
|
||||
from collections.abc import Iterable
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
@@ -32,6 +33,11 @@ from vllm.model_executor.layers.linear import (
|
||||
)
|
||||
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.model_executor.layers.quantization.base_config import (
|
||||
QuantizationConfig,
|
||||
)
|
||||
|
||||
from vllm_omni.diffusion.attention.layer import Attention
|
||||
from vllm_omni.diffusion.cache.base import CachedTransformer
|
||||
from vllm_omni.diffusion.distributed.sp_plan import (
|
||||
@@ -250,6 +256,7 @@ class ZImageAttention(nn.Module):
|
||||
num_kv_heads: int,
|
||||
qk_norm: bool = True,
|
||||
eps: float = 1e-6,
|
||||
quant_config: "QuantizationConfig | None" = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.dim = dim
|
||||
@@ -264,6 +271,7 @@ class ZImageAttention(nn.Module):
|
||||
total_num_heads=num_heads,
|
||||
total_num_kv_heads=num_kv_heads,
|
||||
bias=False,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
|
||||
assert qk_norm is True
|
||||
@@ -281,6 +289,7 @@ class ZImageAttention(nn.Module):
|
||||
bias=False,
|
||||
input_is_parallel=True,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -343,13 +352,19 @@ class ZImageAttention(nn.Module):
|
||||
|
||||
|
||||
class FeedForward(nn.Module):
|
||||
def __init__(self, dim: int, hidden_dim: int):
|
||||
def __init__(
|
||||
self,
|
||||
dim: int,
|
||||
hidden_dim: int,
|
||||
quant_config: "QuantizationConfig | None" = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.w13 = MergedColumnParallelLinear(
|
||||
dim,
|
||||
[hidden_dim] * 2,
|
||||
bias=False,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
self.act = SiluAndMul()
|
||||
self.w2 = RowParallelLinear(
|
||||
@@ -358,6 +373,7 @@ class FeedForward(nn.Module):
|
||||
bias=False,
|
||||
input_is_parallel=True,
|
||||
return_bias=False,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
@@ -374,6 +390,7 @@ class ZImageTransformerBlock(nn.Module):
|
||||
norm_eps: float,
|
||||
qk_norm: bool,
|
||||
modulation=True,
|
||||
quant_config: "QuantizationConfig | None" = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.dim = dim
|
||||
@@ -384,9 +401,14 @@ class ZImageTransformerBlock(nn.Module):
|
||||
num_kv_heads=n_kv_heads,
|
||||
qk_norm=qk_norm,
|
||||
eps=1e-5,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
|
||||
self.feed_forward = FeedForward(dim=dim, hidden_dim=int(dim / 3 * 8))
|
||||
self.feed_forward = FeedForward(
|
||||
dim=dim,
|
||||
hidden_dim=int(dim / 3 * 8),
|
||||
quant_config=quant_config,
|
||||
)
|
||||
self.layer_id = layer_id
|
||||
|
||||
self.attention_norm1 = RMSNorm(dim, eps=norm_eps)
|
||||
@@ -589,6 +611,7 @@ class ZImageTransformer2DModel(CachedTransformer):
|
||||
t_scale=1000.0,
|
||||
axes_dims=[32, 48, 48],
|
||||
axes_lens=[1024, 512, 512],
|
||||
quant_config: "QuantizationConfig | None" = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.dtype = torch.bfloat16
|
||||
@@ -648,6 +671,7 @@ class ZImageTransformer2DModel(CachedTransformer):
|
||||
norm_eps,
|
||||
qk_norm,
|
||||
modulation=True,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
for layer_id in range(n_refiner_layers)
|
||||
]
|
||||
@@ -662,6 +686,7 @@ class ZImageTransformer2DModel(CachedTransformer):
|
||||
norm_eps,
|
||||
qk_norm,
|
||||
modulation=False,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
for layer_id in range(n_refiner_layers)
|
||||
]
|
||||
@@ -677,7 +702,15 @@ class ZImageTransformer2DModel(CachedTransformer):
|
||||
|
||||
self.layers = nn.ModuleList(
|
||||
[
|
||||
ZImageTransformerBlock(layer_id, dim, n_heads, n_kv_heads, norm_eps, qk_norm)
|
||||
ZImageTransformerBlock(
|
||||
layer_id,
|
||||
dim,
|
||||
n_heads,
|
||||
n_kv_heads,
|
||||
norm_eps,
|
||||
qk_norm,
|
||||
quant_config=quant_config,
|
||||
)
|
||||
for layer_id in range(n_layers)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Quantization support for diffusion models.
|
||||
|
||||
This module provides a unified interface for quantizing diffusion transformers
|
||||
using various methods (FP8, etc.). It wraps vLLM's quantization infrastructure
|
||||
while allowing diffusion-model-specific defaults and optimizations.
|
||||
|
||||
Example usage:
|
||||
from vllm_omni.diffusion.quantization import (
|
||||
get_diffusion_quant_config,
|
||||
get_vllm_quant_config_for_layers,
|
||||
)
|
||||
|
||||
# Create FP8 config for diffusion model
|
||||
diff_config = get_diffusion_quant_config("fp8")
|
||||
|
||||
# Get vLLM config to pass to linear layers
|
||||
vllm_config = get_vllm_quant_config_for_layers(diff_config)
|
||||
|
||||
# Use in model initialization
|
||||
linear_layer = QKVParallelLinear(..., quant_config=vllm_config)
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from vllm.logger import init_logger
|
||||
|
||||
from .base import DiffusionQuantizationConfig
|
||||
from .fp8 import DiffusionFp8Config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.model_executor.layers.quantization.base_config import (
|
||||
QuantizationConfig,
|
||||
)
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
# Registry of supported quantization methods
|
||||
# To add a new method, create a new config class and register it here
|
||||
_QUANT_CONFIG_REGISTRY: dict[str, type[DiffusionQuantizationConfig]] = {
|
||||
"fp8": DiffusionFp8Config,
|
||||
}
|
||||
|
||||
SUPPORTED_QUANTIZATION_METHODS = list(_QUANT_CONFIG_REGISTRY.keys())
|
||||
|
||||
|
||||
def get_diffusion_quant_config(
|
||||
quantization: str | None,
|
||||
**kwargs,
|
||||
) -> DiffusionQuantizationConfig | None:
|
||||
"""Factory function to create quantization config for diffusion models.
|
||||
|
||||
Args:
|
||||
quantization: Quantization method name ("fp8", etc.) or None to disable
|
||||
**kwargs: Method-specific parameters passed to the config constructor
|
||||
|
||||
Returns:
|
||||
DiffusionQuantizationConfig instance or None if quantization is disabled
|
||||
|
||||
Raises:
|
||||
ValueError: If the quantization method is not supported
|
||||
|
||||
Example:
|
||||
# Default FP8 with dynamic activation scaling
|
||||
config = get_diffusion_quant_config("fp8")
|
||||
|
||||
# FP8 with custom parameters
|
||||
config = get_diffusion_quant_config(
|
||||
"fp8",
|
||||
activation_scheme="static",
|
||||
ignored_layers=["proj_out"],
|
||||
)
|
||||
"""
|
||||
if quantization is None or quantization.lower() == "none":
|
||||
return None
|
||||
|
||||
quantization = quantization.lower()
|
||||
if quantization not in _QUANT_CONFIG_REGISTRY:
|
||||
raise ValueError(
|
||||
f"Unknown quantization method: {quantization!r}. Supported methods: {SUPPORTED_QUANTIZATION_METHODS}"
|
||||
)
|
||||
|
||||
config_cls = _QUANT_CONFIG_REGISTRY[quantization]
|
||||
logger.info("Creating diffusion quantization config: %s", quantization)
|
||||
return config_cls(**kwargs)
|
||||
|
||||
|
||||
def get_vllm_quant_config_for_layers(
|
||||
diffusion_quant_config: DiffusionQuantizationConfig | None,
|
||||
) -> "QuantizationConfig | None":
|
||||
"""Get the vLLM QuantizationConfig to pass to linear layers.
|
||||
|
||||
This extracts the underlying vLLM config from a DiffusionQuantizationConfig,
|
||||
which can then be passed to vLLM linear layers (QKVParallelLinear, etc.).
|
||||
|
||||
Args:
|
||||
diffusion_quant_config: The diffusion quantization config, or None
|
||||
|
||||
Returns:
|
||||
vLLM QuantizationConfig instance, or None if input is None
|
||||
"""
|
||||
if diffusion_quant_config is None:
|
||||
return None
|
||||
return diffusion_quant_config.get_vllm_quant_config()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DiffusionQuantizationConfig",
|
||||
"DiffusionFp8Config",
|
||||
"get_diffusion_quant_config",
|
||||
"get_vllm_quant_config_for_layers",
|
||||
"SUPPORTED_QUANTIZATION_METHODS",
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Base class for diffusion model quantization configurations."""
|
||||
|
||||
from abc import ABC
|
||||
from typing import TYPE_CHECKING, ClassVar
|
||||
|
||||
import torch
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from vllm.model_executor.layers.quantization.base_config import (
|
||||
QuantizationConfig,
|
||||
)
|
||||
|
||||
|
||||
class DiffusionQuantizationConfig(ABC):
|
||||
"""Base class for diffusion model quantization configurations.
|
||||
|
||||
This provides a thin wrapper over vLLM's quantization configs,
|
||||
allowing diffusion-model-specific defaults and future extensibility.
|
||||
|
||||
Subclasses should:
|
||||
- Set quant_config_cls to the vLLM QuantizationConfig class
|
||||
- Call super().__init__() after creating self._vllm_config
|
||||
- Optionally override get_name() and get_min_capability() if needed
|
||||
"""
|
||||
|
||||
# Subclasses should set this to the vLLM QuantizationConfig class
|
||||
quant_config_cls: ClassVar[type["QuantizationConfig"] | None] = None
|
||||
|
||||
# The underlying vLLM config instance
|
||||
_vllm_config: "QuantizationConfig | None" = None
|
||||
|
||||
@classmethod
|
||||
def get_name(cls) -> str:
|
||||
"""Return the quantization method name (e.g., 'fp8', 'int8').
|
||||
|
||||
By default, delegates to the underlying vLLM config class.
|
||||
"""
|
||||
if cls.quant_config_cls is not None:
|
||||
return cls.quant_config_cls.get_name()
|
||||
raise NotImplementedError("Subclass must set quant_config_cls or override get_name()")
|
||||
|
||||
def get_vllm_quant_config(self) -> "QuantizationConfig | None":
|
||||
"""Return the underlying vLLM QuantizationConfig for linear layers."""
|
||||
return self._vllm_config
|
||||
|
||||
@classmethod
|
||||
def get_supported_act_dtypes(cls) -> list[torch.dtype]:
|
||||
"""Return supported activation dtypes."""
|
||||
return [torch.bfloat16, torch.float16]
|
||||
|
||||
@classmethod
|
||||
def get_min_capability(cls) -> int:
|
||||
"""Minimum GPU compute capability required.
|
||||
|
||||
By default, delegates to the underlying vLLM config class.
|
||||
"""
|
||||
if cls.quant_config_cls is not None:
|
||||
return cls.quant_config_cls.get_min_capability()
|
||||
return 80 # Ampere default
|
||||
@@ -0,0 +1,50 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""FP8 quantization config for diffusion transformers."""
|
||||
|
||||
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
|
||||
|
||||
from .base import DiffusionQuantizationConfig
|
||||
|
||||
|
||||
class DiffusionFp8Config(DiffusionQuantizationConfig):
|
||||
"""FP8 quantization config optimized for diffusion transformers.
|
||||
|
||||
Uses dynamic activation scaling (no calibration dataset needed) and
|
||||
online weight quantization from BF16/FP16 checkpoints.
|
||||
|
||||
Device Compatibility:
|
||||
- Turing (SM 75+): Weight-only FP8 via Marlin kernel
|
||||
- Ada/Hopper (SM 89+): Full W8A8 FP8 with native hardware support
|
||||
|
||||
The kernel selection is automatic based on GPU capability.
|
||||
|
||||
Args:
|
||||
activation_scheme: Activation quantization scheme.
|
||||
- "dynamic": Per-token dynamic scaling (default, no calibration)
|
||||
- "static": Single per-tensor scale (requires calibration)
|
||||
weight_block_size: Block size for block-wise weight quantization.
|
||||
Format: [block_n, block_k]. If None, uses per-tensor scaling.
|
||||
ignored_layers: List of layer name patterns to skip quantization.
|
||||
"""
|
||||
|
||||
# Tight coupling with vLLM's Fp8Config - delegates get_name() and get_min_capability()
|
||||
quant_config_cls = Fp8Config
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
activation_scheme: str = "dynamic",
|
||||
weight_block_size: list[int] | None = None,
|
||||
ignored_layers: list[str] | None = None,
|
||||
):
|
||||
self.activation_scheme = activation_scheme
|
||||
self.weight_block_size = weight_block_size
|
||||
self.ignored_layers = ignored_layers or []
|
||||
|
||||
# Create underlying vLLM FP8 config
|
||||
self._vllm_config = Fp8Config(
|
||||
is_checkpoint_fp8_serialized=False, # Online quantization from BF16
|
||||
activation_scheme=activation_scheme,
|
||||
weight_block_size=weight_block_size,
|
||||
ignored_layers=ignored_layers,
|
||||
)
|
||||
Reference in new issue
Block a user