[Quantization] feat: add FP8 for Omnigen2 (#2441)

Signed-off-by: Zhang <jianmusings@gmail.com>
This commit is contained in:
Zhang Jian authored and GitHub committed 2026-04-16 23:33:40 +08:00
1 parent c3ca5daafb
commit 817e32d548
2 files changed
+174 -10

No files matched your search

@@ -5,6 +5,8 @@ from types import SimpleNamespace
import torch
import torch.nn as nn
import torch.nn.functional as F
import vllm._custom_ops as ops
from diffusers.models.activations import get_activation
from diffusers.models.embeddings import Timesteps, get_1d_rotary_pos_embed
from diffusers.models.modeling_outputs import Transformer2DModelOutput
@@ -16,6 +18,7 @@ from vllm.model_executor.layers.linear import (
QKVParallelLinear,
RowParallelLinear,
)
from vllm.model_executor.layers.quantization import QuantizationConfig
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm_omni.diffusion.attention.layer import Attention
@@ -24,6 +27,105 @@ from vllm_omni.platforms import current_omni_platform
logger = logging.getLogger(__name__)
def _patch_cutlass_padded_fp8():
"""Monkey-patch vllm._custom_ops.cutlass_scaled_mm to pad tensors whose
dimensions are not multiples of 16, so the CUTLASS FP8 kernel is used.
OmniGen2 has hidden_size=2520 (2520 % 16 == 8). Without this patch,
vLLM's cutlass_scaled_mm falls back to a Triton scaled_mm kernel for
every FP8 linear layer (QKV, attn output, gate_up_proj, down_proj),
which is dramatically slower than the native CUTLASS FP8 tensor-core
path on H100/H200 GPUs.
Weight tensors (b) are constant across forward passes, so padded
versions are computed once and cached by data_ptr to avoid repeated
allocation and column-major conversion overhead.
"""
_orig_cutlass_scaled_mm = ops.cutlass_scaled_mm
# Cache: data_ptr → (padded_b, padded_bias, padded_scale_b, pad_k, pad_n, orig_n)
_weight_cache: dict[int, tuple] = {}
def _padded_cutlass_scaled_mm(
a: torch.Tensor,
b: torch.Tensor,
scale_a: torch.Tensor,
scale_b: torch.Tensor,
out_dtype: torch.dtype,
bias: torch.Tensor | None = None,
) -> torch.Tensor:
if b.shape[0] % 16 == 0 and b.shape[1] % 16 == 0:
return _orig_cutlass_scaled_mm(a, b, scale_a, scale_b, out_dtype, bias)
# Reshape to 2D (mirrors the original function)
target_shape = (*a.shape[:-1], b.shape[1])
a = a.view(-1, a.shape[-1])
orig_n = b.shape[1]
# Cache the padded weight — it's a model parameter that never changes.
key = b.data_ptr()
if key not in _weight_cache:
pad_k = (16 - b.shape[0] % 16) % 16
pad_n = (16 - orig_n % 16) % 16
b_pad = b
if pad_k > 0:
b_pad = F.pad(b_pad, (0, 0, 0, pad_k))
if pad_n > 0:
b_pad = F.pad(b_pad, (0, pad_n))
# CUTLASS requires b column-major (stride(0)==1).
b_pad = b_pad.t().contiguous().t()
bias_pad = None
if bias is not None and pad_n > 0:
bias_pad = F.pad(bias, (0, pad_n))
scale_b_pad = scale_b
if scale_b.numel() > 1 and pad_n > 0:
scale_b_pad = F.pad(
scale_b.view(-1, scale_b.shape[-1]),
(0, pad_n),
value=1.0,
)
_weight_cache[key] = (
b_pad,
bias_pad,
scale_b_pad,
pad_k,
pad_n,
orig_n,
)
b_pad, bias_pad, scale_b_pad, pad_k, pad_n, orig_n = _weight_cache[key]
# Pad activations on K dimension (cheap — activations are small).
if pad_k > 0:
a = F.pad(a, (0, pad_k)).contiguous()
out = torch.empty((a.shape[0], b_pad.shape[1]), dtype=out_dtype, device=a.device)
torch.ops._C.cutlass_scaled_mm(
out,
a,
b_pad,
scale_a,
scale_b_pad,
bias_pad if bias is not None else None,
)
if pad_n > 0:
out = out[:, :orig_n]
return out.view(*target_shape)
ops.cutlass_scaled_mm = _padded_cutlass_scaled_mm
logger.info(
"Patched vllm._custom_ops.cutlass_scaled_mm with CUTLASS-padded FP8 "
"variant (avoids slow Triton fallback for non-%%16 dimensions)"
)
_patch_cutlass_padded_fp8()
class OmniGen2Attention(nn.Module):
def __init__(
self,
@@ -31,6 +133,8 @@ class OmniGen2Attention(nn.Module):
num_heads: int,
num_kv_heads: int,
eps: float = 1e-5,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
):
super().__init__()
self.dim = dim
@@ -46,12 +150,26 @@ class OmniGen2Attention(nn.Module):
total_num_kv_heads=num_kv_heads,
disable_tp=True,
bias=False,
quant_config=quant_config,
prefix=f"{prefix}.to_qkv",
)
self.norm_q = RMSNorm(self.head_dim, eps=eps)
self.norm_k = RMSNorm(self.head_dim, eps=eps)
self.to_out = nn.ModuleList([nn.Linear(dim, dim, bias=False)])
self.to_out = nn.ModuleList(
[
RowParallelLinear(
dim,
dim,
bias=False,
input_is_parallel=False,
quant_config=quant_config,
return_bias=False,
prefix=f"{prefix}.to_out.0",
)
]
)
self.attn = Attention(
num_heads=num_heads,
head_size=self.head_dim,
@@ -78,6 +196,9 @@ class OmniGen2Attention(nn.Module):
"""
batch_size = hidden_states.shape[0]
# Contiguous layout for FP8 quantized linear GEMMs (matches FLUX DiT).
hidden_states = hidden_states.contiguous()
# Get Query-Key-Value Pair
qkv, _ = self.to_qkv(hidden_states)
@@ -121,7 +242,7 @@ class OmniGen2Attention(nn.Module):
hidden_states = hidden_states.reshape(batch_size, -1, self.num_heads * self.head_dim)
hidden_states = hidden_states.to(dtype)
hidden_states = self.to_out[0](hidden_states)
hidden_states = self.to_out[0](hidden_states.contiguous())
return hidden_states
@@ -233,6 +354,7 @@ class LuminaRMSNormZero(nn.Module):
embedding_dim: int,
norm_eps: float,
norm_elementwise_affine: bool,
**kwargs,
):
super().__init__()
self.silu = nn.SiLU()
@@ -325,6 +447,8 @@ class LuminaFeedForward(nn.Module):
inner_dim: int,
multiple_of: int | None = 256,
ffn_dim_multiplier: float | None = None,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
):
super().__init__()
@@ -338,6 +462,8 @@ class LuminaFeedForward(nn.Module):
[inner_dim, inner_dim],
bias=False,
return_bias=False,
quant_config=quant_config,
prefix=f"{prefix}.gate_up_proj",
)
self.act_fn = get_act_and_mul_fn("silu")
self.down_proj = RowParallelLinear(
@@ -346,6 +472,8 @@ class LuminaFeedForward(nn.Module):
bias=False,
input_is_parallel=True,
return_bias=False,
quant_config=quant_config,
prefix=f"{prefix}.down_proj",
)
def forward(self, x):
@@ -591,6 +719,8 @@ class OmniGen2TransformerBlock(nn.Module):
ffn_dim_multiplier: float,
norm_eps: float,
modulation: bool = True,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
) -> None:
"""Initialize the transformer block."""
super().__init__()
@@ -602,6 +732,8 @@ class OmniGen2TransformerBlock(nn.Module):
num_heads=num_attention_heads,
num_kv_heads=num_kv_heads,
eps=1e-5,
quant_config=quant_config,
prefix=f"{prefix}.attn",
)
# Initialize feed-forward network
@@ -610,11 +742,19 @@ class OmniGen2TransformerBlock(nn.Module):
inner_dim=4 * dim,
multiple_of=multiple_of,
ffn_dim_multiplier=ffn_dim_multiplier,
quant_config=quant_config,
prefix=f"{prefix}.feed_forward",
)
# Initialize normalization layers
if modulation:
self.norm1 = LuminaRMSNormZero(embedding_dim=dim, norm_eps=norm_eps, norm_elementwise_affine=True)
self.norm1 = LuminaRMSNormZero(
embedding_dim=dim,
norm_eps=norm_eps,
norm_elementwise_affine=True,
quant_config=quant_config,
prefix=f"{prefix}.norm1",
)
else:
self.norm1 = RMSNorm(dim, eps=norm_eps)
@@ -713,6 +853,7 @@ class OmniGen2Transformer2DModel(nn.Module):
axes_lens: tuple[int, int, int] = (1024, 1664, 1664),
text_feat_dim: int = 2048,
timestep_scale: float = 1000.0,
quant_config: QuantizationConfig | None = None,
) -> None:
"""Initialize the OmniGen2 transformer model."""
super().__init__()
@@ -770,8 +911,10 @@ class OmniGen2Transformer2DModel(nn.Module):
ffn_dim_multiplier,
norm_eps,
modulation=True,
quant_config=quant_config,
prefix=f"noise_refiner.{i}",
)
for _ in range(num_refiner_layers)
for i in range(num_refiner_layers)
]
)
@@ -785,8 +928,10 @@ class OmniGen2Transformer2DModel(nn.Module):
ffn_dim_multiplier,
norm_eps,
modulation=True,
quant_config=quant_config,
prefix=f"ref_image_refiner.{i}",
)
for _ in range(num_refiner_layers)
for i in range(num_refiner_layers)
]
)
@@ -800,8 +945,10 @@ class OmniGen2Transformer2DModel(nn.Module):
ffn_dim_multiplier,
norm_eps,
modulation=False,
quant_config=quant_config,
prefix=f"context_refiner.{i}",
)
for _ in range(num_refiner_layers)
for i in range(num_refiner_layers)
]
)
@@ -816,8 +963,10 @@ class OmniGen2Transformer2DModel(nn.Module):
ffn_dim_multiplier,
norm_eps,
modulation=True,
quant_config=quant_config,
prefix=f"layers.{i}",
)
for _ in range(num_layers)
for i in range(num_layers)
]
)
@@ -847,11 +996,25 @@ class OmniGen2Transformer2DModel(nn.Module):
temb,
):
batch_size = len(hidden_states)
has_ref_tokens = any(ref_img_len > 0 for ref_lens in l_effective_ref_img_len for ref_img_len in ref_lens)
max_combined_img_len = max(
[img_len + sum(ref_img_len) for img_len, ref_img_len in zip(l_effective_img_len, l_effective_ref_img_len)]
)
hidden_states = self.x_embedder(hidden_states)
if not has_ref_tokens:
# FP8 kernels do not support zero-token GEMM on ref_image_patch_embedder; skip that path only.
# Still run noise_refiner and return the same combined layout as the no-ref case below
# (batch, max_combined_img_len, hidden) — not raw noise tokens alone.
for layer in self.noise_refiner:
hidden_states = layer(hidden_states, padded_img_mask, noise_rotary_emb, temb)
combined_img_hidden_states = hidden_states.new_zeros(
batch_size, max_combined_img_len, self.config.hidden_size
)
for i, img_len in enumerate(l_effective_img_len):
combined_img_hidden_states[i, :img_len] = hidden_states[i, :img_len]
return combined_img_hidden_states
ref_image_hidden_states = self.ref_image_patch_embedder(ref_image_hidden_states)
for i in range(batch_size):
@@ -676,7 +676,10 @@ class OmniGen2Pipeline(CFGParallelMixin, nn.Module):
)
transformer_kwargs = get_transformer_config_kwargs(od_config.tf_model_config, OmniGen2Transformer2DModel)
self.transformer = OmniGen2Transformer2DModel(**transformer_kwargs)
self.transformer = OmniGen2Transformer2DModel(
**transformer_kwargs,
quant_config=od_config.quantization_config,
)
self.mllm = Qwen2_5_VLForConditionalGeneration.from_pretrained(
model, subfolder="mllm", local_files_only=local_files_only
).to(self.device)
@@ -1253,8 +1256,6 @@ class OmniGen2Pipeline(CFGParallelMixin, nn.Module):
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
timestep = t.expand(latents.shape[0]).to(latents.dtype)
batch_size, num_channels_latents, height, width = latents.shape
optional_kwargs = {}
if "ref_image_hidden_states" in set(inspect.signature(self.transformer.forward).parameters.keys()):
optional_kwargs["ref_image_hidden_states"] = ref_image_hidden_states