[diffusion] perf: improve black-forest-labs/FLUX.2-dev (#14040)

This commit is contained in:
Mick
2025-11-27 14:49:52 +08:00
committed by GitHub
parent 077ca70ee4
commit 6edffc6391
8 changed files with 278 additions and 318 deletions
@@ -1,67 +0,0 @@
# SPDX-License-Identifier: Apache-2.0
from dataclasses import dataclass, field
from sglang.multimodal_gen.configs.models.encoders.base import (
TextEncoderArchConfig,
TextEncoderConfig,
)
def _is_transformer_layer(n: str, m) -> bool:
return "layers" in n and str.isdigit(n.split(".")[-1])
def _is_embeddings(n: str, m) -> bool:
return n.endswith("embed_tokens")
def _is_final_norm(n: str, m) -> bool:
return n.endswith("norm")
@dataclass
class Mistral3ArchConfig(TextEncoderArchConfig):
vocab_size: int = 32000
hidden_size: int = 4096
intermediate_size: int = 11008
num_hidden_layers: int = 32
num_attention_heads: int = 32
num_key_value_heads: int | None = None
hidden_act: str = "silu"
max_position_embeddings: int = 2048
initializer_range: float = 0.02
rms_norm_eps: float = 1e-6
use_cache: bool = True
pad_token_id: int = 0
bos_token_id: int = 1
eos_token_id: int = 2
pretraining_tp: int = 1
tie_word_embeddings: bool = False
rope_theta: float = 10000.0
rope_scaling: float | None = None
attention_bias: bool = False
attention_dropout: float = 0.0
mlp_bias: bool = False
head_dim: int | None = None
hidden_state_skip_layer: int = 2
text_len: int = 256
stacked_params_mapping: list[tuple[str, str, str]] = field(
default_factory=lambda: [
# (param_name, shard_name, shard_id)
(".qkv_proj", ".q_proj", "q"),
(".qkv_proj", ".k_proj", "k"),
(".qkv_proj", ".v_proj", "v"),
(".gate_up_proj", ".gate_proj", 0), # type: ignore
(".gate_up_proj", ".up_proj", 1), # type: ignore
]
)
_fsdp_shard_conditions: list = field(
default_factory=lambda: [_is_transformer_layer, _is_embeddings, _is_final_norm]
)
@dataclass
class Mistral3Config(TextEncoderConfig):
arch_config: TextEncoderArchConfig = field(default_factory=Mistral3ArchConfig)
prefix: str = "mistral3"
@@ -14,9 +14,7 @@ from sglang.multimodal_gen.configs.models.encoders import (
TextEncoderConfig, TextEncoderConfig,
) )
from sglang.multimodal_gen.configs.models.encoders.base import TextEncoderArchConfig from sglang.multimodal_gen.configs.models.encoders.base import TextEncoderArchConfig
from sglang.multimodal_gen.configs.models.encoders.mistral import ( from sglang.multimodal_gen.configs.models.encoders.qwen_image import (
Mistral3Config,
_is_embeddings,
_is_transformer_layer, _is_transformer_layer,
) )
from sglang.multimodal_gen.configs.models.vaes.flux import Flux2VAEConfig, FluxVAEConfig from sglang.multimodal_gen.configs.models.vaes.flux import Flux2VAEConfig, FluxVAEConfig
@@ -346,7 +344,7 @@ class Flux2MistralTextArchConfig(TextEncoderArchConfig):
] ]
) )
_fsdp_shard_conditions: list = field( _fsdp_shard_conditions: list = field(
default_factory=lambda: [_is_transformer_layer, _is_embeddings] default_factory=lambda: [_is_transformer_layer]
) )
def __post_init__(self): def __post_init__(self):
@@ -386,7 +384,6 @@ def format_text_input(prompts: List[str], system_message: str = None):
def flux_2_preprocess_text(prompt: str): def flux_2_preprocess_text(prompt: str):
print(f"{prompt=}")
system_message = "You are an AI that reasons about image descriptions. You give structured responses focusing on object relationships, object attribution and actions without speculation." system_message = "You are an AI that reasons about image descriptions. You give structured responses focusing on object relationships, object attribution and actions without speculation."
return format_text_input([prompt], system_message=system_message) return format_text_input([prompt], system_message=system_message)
@@ -405,9 +402,6 @@ class Flux2PipelineConfig(FluxPipelineConfig):
task_type: ModelTaskType = ModelTaskType.I2I task_type: ModelTaskType = ModelTaskType.I2I
text_encoder_configs: tuple[EncoderConfig, ...] = field(
default_factory=lambda: (Mistral3Config(),)
)
text_encoder_precisions: tuple[str, ...] = field(default_factory=lambda: ("bf16",)) text_encoder_precisions: tuple[str, ...] = field(default_factory=lambda: ("bf16",))
text_encoder_configs: tuple[EncoderConfig, ...] = field( text_encoder_configs: tuple[EncoderConfig, ...] = field(
@@ -70,7 +70,7 @@ class RMSNorm(CustomOp):
residual: Optional[torch.Tensor] = None, residual: Optional[torch.Tensor] = None,
) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
shape = x.shape shape = x.shape
x = x.view(-1, shape[-1]) x = x.reshape(-1, shape[-1])
if residual is not None: if residual is not None:
residual_shape = residual.shape residual_shape = residual.shape
residual = residual.view(-1, shape[-1]) residual = residual.view(-1, shape[-1])
@@ -148,7 +148,6 @@ class FluxAttention(torch.nn.Module, AttentionModuleMixin):
) )
self.to_add_out = ReplicatedLinear(self.inner_dim, query_dim, bias=out_bias) self.to_add_out = ReplicatedLinear(self.inner_dim, query_dim, bias=out_bias)
# Scaled dot product attention
self.attn = USPAttention( self.attn = USPAttention(
num_heads=num_heads, num_heads=num_heads,
head_size=self.head_dim, head_size=self.head_dim,
@@ -12,21 +12,24 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import inspect
from typing import Any, Dict, Optional, Tuple from typing import Any, Dict, Optional, Tuple
import torch import torch
import torch.nn as nn import torch.nn as nn
import torch.nn.functional as F
from diffusers.models.attention import AttentionModuleMixin from diffusers.models.attention import AttentionModuleMixin
from diffusers.models.attention_dispatch import dispatch_attention_fn from diffusers.models.embeddings import (
from diffusers.models.embeddings import TimestepEmbedding, Timesteps TimestepEmbedding,
Timesteps,
get_1d_rotary_pos_embed,
)
from diffusers.models.normalization import AdaLayerNormContinuous from diffusers.models.normalization import AdaLayerNormContinuous
from diffusers.models.transformers.transformer_flux2 import Flux2PosEmbed
from sglang.multimodal_gen.configs.models.dits.flux import FluxConfig from sglang.multimodal_gen.configs.models.dits.flux import FluxConfig
from sglang.multimodal_gen.runtime.layers.attention import USPAttention
from sglang.multimodal_gen.runtime.layers.layernorm import RMSNorm
from sglang.multimodal_gen.runtime.layers.rotary_embedding import _apply_rotary_emb from sglang.multimodal_gen.runtime.layers.rotary_embedding import _apply_rotary_emb
from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT from sglang.multimodal_gen.runtime.models.dits.base import CachableDiT
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__) # pylint: disable=invalid-name logger = init_logger(__name__) # pylint: disable=invalid-name
@@ -110,42 +113,99 @@ class Flux2FeedForward(nn.Module):
return x return x
class Flux2AttnProcessor: class Flux2Attention(torch.nn.Module, AttentionModuleMixin):
_attention_backend = None
_parallel_config = None
def __init__(self): def __init__(
if not hasattr(F, "scaled_dot_product_attention"): self,
raise ImportError( query_dim: int,
f"{self.__class__.__name__} requires PyTorch 2.0. Please upgrade your pytorch version." num_heads: int = 8,
dim_head: int = 64,
dropout: float = 0.0,
bias: bool = False,
added_kv_proj_dim: Optional[int] = None,
added_proj_bias: Optional[bool] = True,
out_bias: bool = True,
eps: float = 1e-5,
out_dim: int = None,
elementwise_affine: bool = True,
):
super().__init__()
self.head_dim = dim_head
self.inner_dim = out_dim if out_dim is not None else dim_head * num_heads
self.query_dim = query_dim
self.out_dim = out_dim if out_dim is not None else query_dim
self.heads = out_dim // dim_head if out_dim is not None else num_heads
self.use_bias = bias
self.dropout = dropout
self.added_kv_proj_dim = added_kv_proj_dim
self.added_proj_bias = added_proj_bias
self.to_q = torch.nn.Linear(query_dim, self.inner_dim, bias=bias)
self.to_k = torch.nn.Linear(query_dim, self.inner_dim, bias=bias)
self.to_v = torch.nn.Linear(query_dim, self.inner_dim, bias=bias)
# QK Norm
self.norm_q = RMSNorm(dim_head, eps=eps)
self.norm_k = RMSNorm(dim_head, eps=eps)
self.to_out = torch.nn.ModuleList([])
self.to_out.append(torch.nn.Linear(self.inner_dim, self.out_dim, bias=out_bias))
self.to_out.append(torch.nn.Dropout(dropout))
if added_kv_proj_dim is not None:
self.norm_added_q = RMSNorm(dim_head, eps=eps)
self.norm_added_k = RMSNorm(dim_head, eps=eps)
self.add_q_proj = torch.nn.Linear(
added_kv_proj_dim, self.inner_dim, bias=added_proj_bias
)
self.add_k_proj = torch.nn.Linear(
added_kv_proj_dim, self.inner_dim, bias=added_proj_bias
)
self.add_v_proj = torch.nn.Linear(
added_kv_proj_dim, self.inner_dim, bias=added_proj_bias
)
self.to_add_out = torch.nn.Linear(self.inner_dim, query_dim, bias=out_bias)
self.attn = USPAttention(
num_heads=num_heads,
head_size=self.head_dim,
dropout_rate=0,
softmax_scale=None,
causal=False,
supported_attention_backends={
AttentionBackendEnum.FA,
AttentionBackendEnum.TORCH_SDPA,
AttentionBackendEnum.SAGE_ATTN,
},
) )
def __call__( def forward(
self, self,
attn: "Flux2Attention",
hidden_states: torch.Tensor, hidden_states: torch.Tensor,
encoder_hidden_states: torch.Tensor = None, encoder_hidden_states: Optional[torch.Tensor] = None,
attention_mask: Optional[torch.Tensor] = None,
freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
) -> torch.Tensor: ) -> torch.Tensor:
query, key, value, encoder_query, encoder_key, encoder_value = ( query, key, value, encoder_query, encoder_key, encoder_value = (
_get_qkv_projections(attn, hidden_states, encoder_hidden_states) _get_qkv_projections(self, hidden_states, encoder_hidden_states)
) )
query = query.unflatten(-1, (attn.heads, -1)) query = query.unflatten(-1, (self.heads, -1))
key = key.unflatten(-1, (attn.heads, -1)) key = key.unflatten(-1, (self.heads, -1))
value = value.unflatten(-1, (attn.heads, -1)) value = value.unflatten(-1, (self.heads, -1))
query = attn.norm_q(query) query = self.norm_q(query)
key = attn.norm_k(key) key = self.norm_k(key)
if attn.added_kv_proj_dim is not None: if self.added_kv_proj_dim is not None:
encoder_query = encoder_query.unflatten(-1, (attn.heads, -1)) encoder_query = encoder_query.unflatten(-1, (self.heads, -1))
encoder_key = encoder_key.unflatten(-1, (attn.heads, -1)) encoder_key = encoder_key.unflatten(-1, (self.heads, -1))
encoder_value = encoder_value.unflatten(-1, (attn.heads, -1)) encoder_value = encoder_value.unflatten(-1, (self.heads, -1))
encoder_query = attn.norm_added_q(encoder_query) encoder_query = self.norm_added_q(encoder_query)
encoder_key = attn.norm_added_k(encoder_key) encoder_key = self.norm_added_k(encoder_key)
query = torch.cat([encoder_query, query], dim=1) query = torch.cat([encoder_query, query], dim=1)
key = torch.cat([encoder_key, key], dim=1) key = torch.cat([encoder_key, key], dim=1)
@@ -160,14 +220,8 @@ class Flux2AttnProcessor:
key, cos, sin, is_neox_style=False, interleaved=True key, cos, sin, is_neox_style=False, interleaved=True
) )
hidden_states = dispatch_attention_fn( hidden_states = self.attn(query, key, value)
query,
key,
value,
attn_mask=attention_mask,
backend=self._attention_backend,
parallel_config=self._parallel_config,
)
hidden_states = hidden_states.flatten(2, 3) hidden_states = hidden_states.flatten(2, 3)
hidden_states = hidden_states.to(query.dtype) hidden_states = hidden_states.to(query.dtype)
@@ -179,10 +233,10 @@ class Flux2AttnProcessor:
], ],
dim=1, dim=1,
) )
encoder_hidden_states = attn.to_add_out(encoder_hidden_states) encoder_hidden_states = self.to_add_out(encoder_hidden_states)
hidden_states = attn.to_out[0](hidden_states) hidden_states = self.to_out[0](hidden_states)
hidden_states = attn.to_out[1](hidden_states) hidden_states = self.to_out[1](hidden_states)
if encoder_hidden_states is not None: if encoder_hidden_states is not None:
return hidden_states, encoder_hidden_states return hidden_states, encoder_hidden_states
@@ -190,165 +244,6 @@ class Flux2AttnProcessor:
return hidden_states return hidden_states
class Flux2Attention(torch.nn.Module, AttentionModuleMixin):
_default_processor_cls = Flux2AttnProcessor
_available_processors = [Flux2AttnProcessor]
def __init__(
self,
query_dim: int,
heads: int = 8,
dim_head: int = 64,
dropout: float = 0.0,
bias: bool = False,
added_kv_proj_dim: Optional[int] = None,
added_proj_bias: Optional[bool] = True,
out_bias: bool = True,
eps: float = 1e-5,
out_dim: int = None,
elementwise_affine: bool = True,
processor=None,
):
super().__init__()
self.head_dim = dim_head
self.inner_dim = out_dim if out_dim is not None else dim_head * heads
self.query_dim = query_dim
self.out_dim = out_dim if out_dim is not None else query_dim
self.heads = out_dim // dim_head if out_dim is not None else heads
self.use_bias = bias
self.dropout = dropout
self.added_kv_proj_dim = added_kv_proj_dim
self.added_proj_bias = added_proj_bias
self.to_q = torch.nn.Linear(query_dim, self.inner_dim, bias=bias)
self.to_k = torch.nn.Linear(query_dim, self.inner_dim, bias=bias)
self.to_v = torch.nn.Linear(query_dim, self.inner_dim, bias=bias)
# QK Norm
self.norm_q = torch.nn.RMSNorm(
dim_head, eps=eps, elementwise_affine=elementwise_affine
)
self.norm_k = torch.nn.RMSNorm(
dim_head, eps=eps, elementwise_affine=elementwise_affine
)
self.to_out = torch.nn.ModuleList([])
self.to_out.append(torch.nn.Linear(self.inner_dim, self.out_dim, bias=out_bias))
self.to_out.append(torch.nn.Dropout(dropout))
if added_kv_proj_dim is not None:
self.norm_added_q = torch.nn.RMSNorm(dim_head, eps=eps)
self.norm_added_k = torch.nn.RMSNorm(dim_head, eps=eps)
self.add_q_proj = torch.nn.Linear(
added_kv_proj_dim, self.inner_dim, bias=added_proj_bias
)
self.add_k_proj = torch.nn.Linear(
added_kv_proj_dim, self.inner_dim, bias=added_proj_bias
)
self.add_v_proj = torch.nn.Linear(
added_kv_proj_dim, self.inner_dim, bias=added_proj_bias
)
self.to_add_out = torch.nn.Linear(self.inner_dim, query_dim, bias=out_bias)
if processor is None:
processor = self._default_processor_cls()
self.set_processor(processor)
def forward(
self,
hidden_states: torch.Tensor,
encoder_hidden_states: Optional[torch.Tensor] = None,
attention_mask: Optional[torch.Tensor] = None,
freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
**kwargs,
) -> torch.Tensor:
attn_parameters = set(
inspect.signature(self.processor.__call__).parameters.keys()
)
unused_kwargs = [k for k, _ in kwargs.items() if k not in attn_parameters]
if len(unused_kwargs) > 0:
logger.warning(
f"joint_attention_kwargs {unused_kwargs} are not expected by {self.processor.__class__.__name__} and will be ignored."
)
kwargs = {k: w for k, w in kwargs.items() if k in attn_parameters}
return self.processor(
self,
hidden_states,
encoder_hidden_states,
attention_mask,
freqs_cis,
**kwargs,
)
class Flux2ParallelSelfAttnProcessor:
_attention_backend = None
_parallel_config = None
def __init__(self):
if not hasattr(F, "scaled_dot_product_attention"):
raise ImportError(
f"{self.__class__.__name__} requires PyTorch 2.0. Please upgrade your pytorch version."
)
def __call__(
self,
attn: "Flux2ParallelSelfAttention",
hidden_states: torch.Tensor,
attention_mask: Optional[torch.Tensor] = None,
freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
) -> torch.Tensor:
# Parallel in (QKV + MLP in) projection
hidden_states = attn.to_qkv_mlp_proj(hidden_states)
qkv, mlp_hidden_states = torch.split(
hidden_states,
[3 * attn.inner_dim, attn.mlp_hidden_dim * attn.mlp_mult_factor],
dim=-1,
)
# Handle the attention logic
query, key, value = qkv.chunk(3, dim=-1)
query = query.unflatten(-1, (attn.heads, -1))
key = key.unflatten(-1, (attn.heads, -1))
value = value.unflatten(-1, (attn.heads, -1))
query = attn.norm_q(query)
key = attn.norm_k(key)
if freqs_cis is not None:
cos, sin = freqs_cis
query = _apply_rotary_emb(
query, cos, sin, is_neox_style=False, interleaved=True
)
key = _apply_rotary_emb(
key, cos, sin, is_neox_style=False, interleaved=True
)
hidden_states = dispatch_attention_fn(
query,
key,
value,
attn_mask=attention_mask,
backend=self._attention_backend,
parallel_config=self._parallel_config,
)
hidden_states = hidden_states.flatten(2, 3)
hidden_states = hidden_states.to(query.dtype)
# Handle the feedforward (FF) logic
mlp_hidden_states = attn.mlp_act_fn(mlp_hidden_states)
# Concatenate and parallel output projection
hidden_states = torch.cat([hidden_states, mlp_hidden_states], dim=-1)
hidden_states = attn.to_out(hidden_states)
return hidden_states
class Flux2ParallelSelfAttention(torch.nn.Module, AttentionModuleMixin): class Flux2ParallelSelfAttention(torch.nn.Module, AttentionModuleMixin):
""" """
Flux 2 parallel self-attention for the Flux 2 single-stream transformer blocks. Flux 2 parallel self-attention for the Flux 2 single-stream transformer blocks.
@@ -358,15 +253,13 @@ class Flux2ParallelSelfAttention(torch.nn.Module, AttentionModuleMixin):
paper](https://arxiv.org/abs/2302.05442) for a visual depiction of this type of transformer block. paper](https://arxiv.org/abs/2302.05442) for a visual depiction of this type of transformer block.
""" """
_default_processor_cls = Flux2ParallelSelfAttnProcessor
_available_processors = [Flux2ParallelSelfAttnProcessor]
# Does not support QKV fusion as the QKV projections are always fused # Does not support QKV fusion as the QKV projections are always fused
_supports_qkv_fusion = False _supports_qkv_fusion = False
def __init__( def __init__(
self, self,
query_dim: int, query_dim: int,
heads: int = 8, num_heads: int = 8,
dim_head: int = 64, dim_head: int = 64,
dropout: float = 0.0, dropout: float = 0.0,
bias: bool = False, bias: bool = False,
@@ -376,15 +269,14 @@ class Flux2ParallelSelfAttention(torch.nn.Module, AttentionModuleMixin):
elementwise_affine: bool = True, elementwise_affine: bool = True,
mlp_ratio: float = 4.0, mlp_ratio: float = 4.0,
mlp_mult_factor: int = 2, mlp_mult_factor: int = 2,
processor=None,
): ):
super().__init__() super().__init__()
self.head_dim = dim_head self.head_dim = dim_head
self.inner_dim = out_dim if out_dim is not None else dim_head * heads self.inner_dim = out_dim if out_dim is not None else dim_head * num_heads
self.query_dim = query_dim self.query_dim = query_dim
self.out_dim = out_dim if out_dim is not None else query_dim self.out_dim = out_dim if out_dim is not None else query_dim
self.heads = out_dim // dim_head if out_dim is not None else heads self.heads = out_dim // dim_head if out_dim is not None else num_heads
self.use_bias = bias self.use_bias = bias
self.dropout = dropout self.dropout = dropout
@@ -402,21 +294,26 @@ class Flux2ParallelSelfAttention(torch.nn.Module, AttentionModuleMixin):
self.mlp_act_fn = Flux2SwiGLU() self.mlp_act_fn = Flux2SwiGLU()
# QK Norm # QK Norm
self.norm_q = torch.nn.RMSNorm( self.norm_q = RMSNorm(dim_head, eps=eps)
dim_head, eps=eps, elementwise_affine=elementwise_affine self.norm_k = RMSNorm(dim_head, eps=eps)
)
self.norm_k = torch.nn.RMSNorm(
dim_head, eps=eps, elementwise_affine=elementwise_affine
)
# Fused attention output projection + MLP output projection # Fused attention output projection + MLP output projection
self.to_out = torch.nn.Linear( self.to_out = torch.nn.Linear(
self.inner_dim + self.mlp_hidden_dim, self.out_dim, bias=out_bias self.inner_dim + self.mlp_hidden_dim, self.out_dim, bias=out_bias
) )
if processor is None: self.attn = USPAttention(
processor = self._default_processor_cls() num_heads=num_heads,
self.set_processor(processor) head_size=self.head_dim,
dropout_rate=0,
softmax_scale=None,
causal=False,
supported_attention_backends={
AttentionBackendEnum.FA,
AttentionBackendEnum.TORCH_SDPA,
AttentionBackendEnum.SAGE_ATTN,
},
)
def forward( def forward(
self, self,
@@ -425,16 +322,44 @@ class Flux2ParallelSelfAttention(torch.nn.Module, AttentionModuleMixin):
freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, freqs_cis: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
**kwargs, **kwargs,
) -> torch.Tensor: ) -> torch.Tensor:
attn_parameters = set( # Parallel in (QKV + MLP in) projection
inspect.signature(self.processor.__call__).parameters.keys() hidden_states = self.to_qkv_mlp_proj(hidden_states)
qkv, mlp_hidden_states = torch.split(
hidden_states,
[3 * self.inner_dim, self.mlp_hidden_dim * self.mlp_mult_factor],
dim=-1,
) )
unused_kwargs = [k for k, _ in kwargs.items() if k not in attn_parameters]
if len(unused_kwargs) > 0: # Handle the attention logic
logger.warning( query, key, value = qkv.chunk(3, dim=-1)
f"joint_attention_kwargs {unused_kwargs} are not expected by {self.processor.__class__.__name__} and will be ignored."
query = query.unflatten(-1, (self.heads, -1))
key = key.unflatten(-1, (self.heads, -1))
value = value.unflatten(-1, (self.heads, -1))
query = self.norm_q(query)
key = self.norm_k(key)
if freqs_cis is not None:
cos, sin = freqs_cis
query = _apply_rotary_emb(
query, cos, sin, is_neox_style=False, interleaved=True
) )
kwargs = {k: w for k, w in kwargs.items() if k in attn_parameters} key = _apply_rotary_emb(
return self.processor(self, hidden_states, attention_mask, freqs_cis, **kwargs) key, cos, sin, is_neox_style=False, interleaved=True
)
hidden_states = self.attn(query, key, value)
hidden_states = hidden_states.flatten(2, 3)
hidden_states = hidden_states.to(query.dtype)
# Handle the feedforward (FF) logic
mlp_hidden_states = self.mlp_act_fn(mlp_hidden_states)
# Concatenate and parallel output projection
hidden_states = torch.cat([hidden_states, mlp_hidden_states], dim=-1)
hidden_states = self.to_out(hidden_states)
return hidden_states
class Flux2SingleTransformerBlock(nn.Module): class Flux2SingleTransformerBlock(nn.Module):
@@ -457,14 +382,13 @@ class Flux2SingleTransformerBlock(nn.Module):
self.attn = Flux2ParallelSelfAttention( self.attn = Flux2ParallelSelfAttention(
query_dim=dim, query_dim=dim,
dim_head=attention_head_dim, dim_head=attention_head_dim,
heads=num_attention_heads, num_heads=num_attention_heads,
out_dim=dim, out_dim=dim,
bias=bias, bias=bias,
out_bias=bias, out_bias=bias,
eps=eps, eps=eps,
mlp_ratio=mlp_ratio, mlp_ratio=mlp_ratio,
mlp_mult_factor=2, mlp_mult_factor=2,
processor=Flux2ParallelSelfAttnProcessor(),
) )
def forward( def forward(
@@ -529,13 +453,12 @@ class Flux2TransformerBlock(nn.Module):
query_dim=dim, query_dim=dim,
added_kv_proj_dim=dim, added_kv_proj_dim=dim,
dim_head=attention_head_dim, dim_head=attention_head_dim,
heads=num_attention_heads, num_heads=num_attention_heads,
out_dim=dim, out_dim=dim,
bias=bias, bias=bias,
added_proj_bias=bias, added_proj_bias=bias,
out_bias=bias, out_bias=bias,
eps=eps, eps=eps,
processor=Flux2AttnProcessor(),
) )
self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps) self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
@@ -674,6 +597,38 @@ class Flux2Modulation(nn.Module):
) )
class Flux2PosEmbed(nn.Module):
# modified from https://github.com/black-forest-labs/flux/blob/c00d7c60b085fce8058b9df845e036090873f2ce/src/flux/modules/layers.py#L11
def __init__(self, theta: int, axes_dim: list[int]):
super().__init__()
self.theta = theta
self.axes_dim = axes_dim
def forward(self, ids: torch.Tensor) -> torch.Tensor:
# Expected ids shape: [S, len(self.axes_dim)]
cos_out = []
sin_out = []
pos = ids.float()
is_mps = ids.device.type == "mps"
is_npu = ids.device.type == "npu"
freqs_dtype = torch.float32 if (is_mps or is_npu) else torch.float64
# Unlike Flux 1, loop over len(self.axes_dim) rather than ids.shape[-1]
for i in range(len(self.axes_dim)):
cos, sin = get_1d_rotary_pos_embed(
self.axes_dim[i],
pos[..., i],
theta=self.theta,
repeat_interleave_real=True,
use_real=True,
freqs_dtype=freqs_dtype,
)
cos_out.append(cos)
sin_out.append(sin)
freqs_cos = torch.cat(cos_out, dim=-1).to(ids.device)
freqs_sin = torch.cat(sin_out, dim=-1).to(ids.device)
return freqs_cos, freqs_sin
class Flux2Transformer2DModel(CachableDiT): class Flux2Transformer2DModel(CachableDiT):
""" """
The Transformer model introduced in Flux 2. The Transformer model introduced in Flux 2.
@@ -701,7 +656,6 @@ class Flux2Transformer2DModel(CachableDiT):
self.inner_dim = num_attention_heads * attention_head_dim self.inner_dim = num_attention_heads * attention_head_dim
# 1. Sinusoidal positional embedding for RoPE on image and text tokens # 1. Sinusoidal positional embedding for RoPE on image and text tokens
# self.rotary_emb = Flux2PosEmbed(theta=rope_theta, axes_dim=axes_dims_rope)
self.rotary_emb = Flux2PosEmbed(theta=rope_theta, axes_dim=axes_dims_rope) self.rotary_emb = Flux2PosEmbed(theta=rope_theta, axes_dim=axes_dims_rope)
# 2. Combined timestep + guidance embedding # 2. Combined timestep + guidance embedding
@@ -18,9 +18,9 @@ from typing import Iterable, Optional, Union
import torch import torch
from torch import nn from torch import nn
from transformers import Cache, DynamicCache, LlavaConfig, Mistral3Config, MistralConfig from transformers import Cache, DynamicCache, LlavaConfig, Mistral3Config, MistralConfig
from transformers.integrations.sdpa_attention import sdpa_attention_forward
from transformers.masking_utils import create_causal_mask from transformers.masking_utils import create_causal_mask
from transformers.modeling_outputs import BaseModelOutputWithPast from transformers.modeling_outputs import BaseModelOutputWithPast
from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS
from transformers.models.mistral3.modeling_mistral3 import ( from transformers.models.mistral3.modeling_mistral3 import (
Mistral3CausalLMOutputWithPast, Mistral3CausalLMOutputWithPast,
Mistral3ModelOutputWithPast, Mistral3ModelOutputWithPast,
@@ -32,7 +32,9 @@ from transformers.models.mistral.modeling_mistral import (
apply_rotary_pos_emb, apply_rotary_pos_emb,
) )
from sglang.multimodal_gen.runtime.layers.attention import USPAttention
from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loader from sglang.multimodal_gen.runtime.loader.weight_utils import default_weight_loader
from sglang.multimodal_gen.runtime.platforms import AttentionBackendEnum
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
logger = init_logger(__name__) logger = init_logger(__name__)
@@ -89,6 +91,17 @@ class MistralAttention(nn.Module):
self.is_causal = True self.is_causal = True
self.num_heads = config.num_attention_heads self.num_heads = config.num_attention_heads
self.num_key_value_heads = config.num_key_value_heads self.num_key_value_heads = config.num_key_value_heads
self.attn = USPAttention(
num_heads=self.num_heads,
head_size=self.head_dim,
dropout_rate=0,
softmax_scale=None,
causal=False,
supported_attention_backends={
AttentionBackendEnum.FA,
AttentionBackendEnum.TORCH_SDPA,
},
)
def forward( def forward(
self, self,
@@ -118,7 +131,7 @@ class MistralAttention(nn.Module):
key_states, value_states, self.layer_idx, cache_kwargs key_states, value_states, self.layer_idx, cache_kwargs
) )
attention_interface = ALL_ATTENTION_FUNCTIONS["sdpa"] attention_interface = sdpa_attention_forward
attn_output, attn_weights = attention_interface( attn_output, attn_weights = attention_interface(
self, self,
query_states, query_states,
@@ -228,6 +228,73 @@
"expected_avg_denoise_ms": 165.83, "expected_avg_denoise_ms": 165.83,
"expected_median_denoise_ms": 169.33 "expected_median_denoise_ms": 169.33
}, },
"flux_2_image_t2i": {
"stages_ms": {
"InputValidationStage": 0.05,
"TextEncodingStage": 530.93,
"ImageVAEEncodingStage": 0.0,
"ConditioningStage": 0.02,
"LatentPreparationStage": 12.71,
"TimestepPreparationStage": 2.91,
"DenoisingStage": 26403.1,
"DecodingStage": 286.85
},
"denoise_step_ms": {
"0": 511.3,
"1": 132.57,
"2": 541.19,
"3": 518.93,
"4": 541.2,
"5": 520.28,
"6": 532.47,
"7": 525.68,
"8": 538.25,
"9": 525.84,
"10": 526.13,
"11": 525.67,
"12": 524.63,
"13": 530.57,
"14": 530.46,
"15": 529.94,
"16": 532.47,
"17": 527.88,
"18": 527.7,
"19": 525.08,
"20": 525.72,
"21": 529.3,
"22": 522.59,
"23": 529.75,
"24": 523.46,
"25": 528.72,
"26": 526.92,
"27": 528.62,
"28": 522.77,
"29": 528.35,
"30": 528.05,
"31": 528.89,
"32": 525.34,
"33": 530.36,
"34": 529.19,
"35": 526.92,
"36": 528.16,
"37": 525.03,
"38": 527.33,
"39": 527.96,
"40": 527.81,
"41": 524.79,
"42": 528.46,
"43": 532.49,
"44": 526.95,
"45": 533.14,
"46": 529.32,
"47": 528.51,
"48": 532.14,
"49": 529.29
},
"expected_e2e_ms": 27648.69,
"expected_avg_denoise_ms": 520.09,
"expected_median_denoise_ms": 528.0
},
"flux_image_t2i_2_gpus": { "flux_image_t2i_2_gpus": {
"stages_ms": { "stages_ms": {
"InputValidationStage": 0.03, "InputValidationStage": 0.03,
@@ -242,19 +242,19 @@ ONE_GPU_CASES_A: list[DiffusionTestCase] = [
output_size="1024x1024", output_size="1024x1024",
), ),
), ),
# DiffusionTestCase( DiffusionTestCase(
# "flux_2_image_t2i", "flux_2_image_t2i",
# DiffusionServerArgs( DiffusionServerArgs(
# model_path="black-forest-labs/FLUX.2-dev", model_path="black-forest-labs/FLUX.2-dev",
# modality="image", modality="image",
# warmup_text=1, warmup_text=1,
# warmup_edit=0, warmup_edit=0,
# ), ),
# DiffusionSamplingParams( DiffusionSamplingParams(
# prompt="A futuristic cityscape at sunset with flying cars", prompt="A futuristic cityscape at sunset with flying cars",
# output_size="1024x1024", output_size="1024x1024",
# ), ),
# ), ),
# === Text and Image to Image (TI2I) === # === Text and Image to Image (TI2I) ===
# TODO: Timeout with Torch2.9. Add back when it can pass CI # TODO: Timeout with Torch2.9. Add back when it can pass CI
# DiffusionTestCase( # DiffusionTestCase(