[Model] Avoid repeated attention and MLP work in Klein TP (#8553)

Signed-off-by: QianCyrus <101633534+QianCyrus@users.noreply.github.com>
Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
This commit is contained in:
QianCyrusandHongsheng Liu authored and GitHub committed 2026-10-07 21:21:05 +08:00
1 parent b21df3bcb6
commit 8ef77be66e
4 files changed
+415 -14

No files matched your search

@@ -0,0 +1,331 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""CPU regressions for Klein's packed TP layout and local-head execution."""
from contextlib import contextmanager
import pytest
import torch
import torch.nn.functional as F
from vllm.model_executor import parameter
from vllm.model_executor.layers import linear
from vllm.model_executor.layers.utils import default_unquantized_gemm
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm_omni.diffusion.data import DiffusionParallelConfig, OmniDiffusionConfig
from vllm_omni.diffusion.forward_context import ForwardContext
from vllm_omni.diffusion.models.flux2_klein import flux2_klein_transformer as klein
pytestmark = [pytest.mark.core_model, pytest.mark.cpu, pytest.mark.diffusion]
@contextmanager
def _default_dtype(dtype):
previous = torch.get_default_dtype()
torch.set_default_dtype(dtype)
try:
yield
finally:
torch.set_default_dtype(previous)
class _CPUAttention(torch.nn.Module):
"""Use SDPA while retaining the model's actual Q/K norm and RoPE."""
def __init__(self, num_heads, head_size, softmax_scale, causal=False, **kwargs):
super().__init__()
self.num_heads = num_heads
self.scale = softmax_scale
def forward(self, query, key, value, metadata=None):
assert query.shape[2] == self.num_heads
return F.scaled_dot_product_attention(
query.transpose(1, 2),
key.transpose(1, 2),
value.transpose(1, 2),
attn_mask=None if metadata is None else metadata.attn_mask,
scale=self.scale,
).transpose(1, 2)
@pytest.fixture(autouse=True)
def _cpu_dispatch(monkeypatch):
monkeypatch.setattr(linear, "dispatch_unquantized_gemm", lambda *a, **kw: default_unquantized_gemm)
monkeypatch.setattr(klein, "Attention", _CPUAttention)
monkeypatch.setattr(klein, "get_forward_context", ForwardContext)
def _set_tp(monkeypatch, tp, rank):
# Keep real vLLM parameters, constructors and checkpoint loaders. Only the
# distributed process metadata is substituted for this CPU unit test.
for module in (linear, parameter):
monkeypatch.setattr(module, "get_tensor_model_parallel_world_size", lambda: tp)
monkeypatch.setattr(module, "get_tensor_model_parallel_rank", lambda: rank)
monkeypatch.setattr(klein, "get_tensor_model_parallel_world_size", lambda: tp)
def _attention(monkeypatch, tp, rank, *, local_tp=True, heads=8, dim_head=8, mlp_ratio=3, sp=1):
_set_tp(monkeypatch, tp, rank)
with _default_dtype(torch.bfloat16):
attention = klein.Flux2ParallelSelfAttention(
parallel_config=DiffusionParallelConfig(tensor_parallel_size=tp, ulysses_degree=sp),
query_dim=heads * dim_head,
heads=heads,
dim_head=dim_head,
mlp_ratio=mlp_ratio,
bias=True,
local_tp=local_tp,
)
attention.norm_q.forward = attention.norm_q.forward_native
attention.norm_k.forward = attention.norm_k.forward_native
attention.rope.forward = attention.rope.forward_native
return attention
def _rank_rows(tensor, sizes, tp, rank):
return torch.cat([part.chunk(tp, dim=0)[rank] for part in tensor.split(sizes, dim=0)])
@pytest.mark.parametrize("tp", [1, 2, 4, 8])
@pytest.mark.parametrize("name", ["weight", "bias"])
def test_packed_checkpoint_reconstruction_and_reload(monkeypatch, tp, name):
sizes = [64, 64, 64, 192, 192]
shape = (sum(sizes), 64) if name == "weight" else (sum(sizes),)
canonical = torch.arange(torch.tensor(shape).prod().item(), dtype=torch.float32).reshape(shape)
shards = []
for rank in range(tp):
attention = _attention(monkeypatch, tp, rank)
assert attention.local_tp == (tp > 1)
assert attention.query_num_heads == 8 // tp
projection = attention.to_qkv_mlp_proj
param = getattr(projection, name)
# Exact row markers verify the installed loader, without BF16 rounding
# hiding a channel-order error. The constructor still uses BF16.
param.data = param.data.float()
for weights in (canonical + 1, canonical):
param.weight_loader(param, weights)
torch.testing.assert_close(param, _rank_rows(weights, sizes, tp, rank), rtol=0, atol=0)
shards.append(param.detach().clone())
local_sizes = [size // tp for size in sizes]
reconstructed = torch.cat(
[torch.cat([shard.split(local_sizes, dim=0)[i] for shard in shards]) for i in range(len(sizes))]
)
torch.testing.assert_close(reconstructed, canonical, rtol=0, atol=0)
@pytest.mark.parametrize("tp,heads", [(2, 8), (4, 24), (8, 24)])
@pytest.mark.parametrize("mask_kind", ["none", "3d", "broadcast", "per_head"])
@torch.inference_mode()
def test_local_heads_and_gather_order_match_dense_attention(monkeypatch, tp, heads, mask_kind):
torch.manual_seed(17)
dim = heads * 8
baseline = _attention(monkeypatch, 1, 0, local_tp=False, heads=heads)
for name, param in baseline.named_parameters():
if name.startswith("norm_"):
param.fill_(1)
else:
param.normal_(std=0.03)
# A non-contiguous input and batch > 1 exercise the reshape boundaries.
hidden = torch.randn(2, dim, 7, dtype=torch.bfloat16).transpose(1, 2)
angles = torch.randn(7, 4)
rotary = angles.cos(), angles.sin()
mask = None
if mask_kind != "none":
mask_heads = heads if mask_kind == "per_head" else 1
mask = torch.rand(2, mask_heads, 7, 7) > 0.25
mask[..., 0] = True
if mask_kind == "3d":
mask = mask[:, 0]
captured = []
hook = baseline.to_out.register_forward_pre_hook(lambda module, args: captured.append(args[0].clone()))
expected = baseline(hidden, attention_mask=mask, image_rotary_emb=rotary)
hook.remove()
full_attention, full_mlp = captured[0].split([dim, 3 * dim], dim=-1)
weights = {name: param.detach().clone() for name, param in baseline.named_parameters()}
for rank in range(tp):
local = _attention(monkeypatch, tp, rank, heads=heads)
for name, param in local.named_parameters():
getattr(param, "weight_loader", default_weight_loader)(param, weights[name])
feature_calls: list[int] = []
output_calls: list[int] = []
def gather_features(value, dim=-1):
reference = (full_attention, full_mlp)[len(feature_calls)]
torch.testing.assert_close(value, reference.chunk(tp, dim=-1)[rank], rtol=0.02, atol=0.002)
feature_calls.append(value.shape[-1])
return reference
def gather_output(value, dim=-1):
torch.testing.assert_close(value, expected.chunk(tp, dim=-1)[rank], rtol=0.02, atol=0.002)
output_calls.append(value.shape[-1])
return expected
# Each outgoing tensor must match the independently computed dense
# result before the collective supplies the other ranks' slices.
monkeypatch.setattr(klein, "tensor_model_parallel_all_gather", gather_features)
monkeypatch.setattr(linear, "tensor_model_parallel_all_gather", gather_output)
actual = local(hidden, attention_mask=mask, image_rotary_emb=rotary)
torch.testing.assert_close(actual, expected)
assert feature_calls == [dim // tp, 3 * dim // tp]
assert output_calls == [dim // tp]
@pytest.mark.parametrize(
"kwargs",
[
{"tp": 1},
{"tp": 4, "local_tp": False},
{"tp": 4, "sp": 2},
{"tp": 4, "heads": 6},
{"tp": 4, "mlp_ratio": 2.1},
],
)
def test_ineligible_shapes_keep_original_projection(monkeypatch, kwargs):
attention = _attention(monkeypatch, rank=0, **kwargs)
assert not attention.local_tp
assert type(attention.to_qkv_mlp_proj) is linear.ColumnParallelLinear
assert attention.to_qkv_mlp_proj.gather_output
assert attention.query_num_heads == attention.heads
@pytest.mark.parametrize(
"overrides,parallel_overrides,eligible",
[
({}, {}, True),
({"dtype": torch.float32}, {}, False),
({"dtype": torch.float16}, {}, False),
({}, {"tensor_parallel_size": 1}, False),
({}, {"ulysses_degree": 2}, False),
({"quantization_config": "fp8"}, {}, False),
({"lora_path": "local-adapter"}, {}, False),
({"enable_cpu_offload": True}, {}, False),
({"enable_layerwise_offload": True}, {}, False),
({"diffusion_offload_config": {"mode": "module", "components": ["dit"]}}, {}, False),
({}, {"tensor_parallel_size": 1, "use_hsdp": True, "hsdp_shard_size": 4}, False),
],
)
def test_pipeline_eligibility_uses_resolved_config(tmp_path, overrides, parallel_overrides, eligible):
parallel = DiffusionParallelConfig(**({"tensor_parallel_size": 4} | parallel_overrides))
config = OmniDiffusionConfig(
**({"model": str(tmp_path), "dtype": torch.bfloat16} | overrides),
parallel_config=parallel,
)
assert klein._use_local_single_stream_tp(config) is eligible
@pytest.mark.parametrize("tp", [1, 2, 4, 8])
def test_host_restore_contract_and_model_weight_reload(monkeypatch, tp):
_set_tp(monkeypatch, tp, 0)
kwargs = dict(
in_channels=8,
num_layers=1,
num_single_layers=1,
attention_head_dim=8,
num_attention_heads=8,
joint_attention_dim=8,
timestep_guidance_channels=8,
axes_dims_rope=(2, 2, 2, 2),
guidance_embeds=False,
)
with _default_dtype(torch.bfloat16):
baseline = klein.Flux2Transformer2DModel(**kwargs)
model = klein.Flux2Transformer2DModel(**kwargs, local_tp=True)
assert baseline.host_weight_restore_contract.version == "1"
assert model.host_weight_restore_contract.version == ("2-local-single-stream-tp" if tp > 1 else "1")
model.validate_restored_host_weights()
name = "single_transformer_blocks.0.attn.to_qkv_mlp_proj.weight"
weights = torch.linspace(-1, 1, 576 * 64).reshape(576, 64).to(torch.bfloat16)
for canonical in (weights, weights.neg()):
assert name in model.load_weights([(name, canonical)])
param = dict(model.named_parameters())[name]
torch.testing.assert_close(param, _rank_rows(canonical, [64, 64, 64, 192, 192], tp, 0), rtol=0, atol=0)
with _default_dtype(torch.bfloat16):
restored = klein.Flux2Transformer2DModel(**kwargs, local_tp=True)
restored.load_state_dict(model.state_dict(), strict=True)
restored.validate_restored_host_weights()
torch.testing.assert_close(dict(restored.named_parameters())[name], param, rtol=0, atol=0)
@pytest.mark.parametrize("sizes", [(8, 24), (8, 8, 8, 24, 24)])
@pytest.mark.parametrize("tp", [1, 2, 4, 8])
@torch.inference_mode()
def test_fused_lora_global_rows_dynamic_activation(monkeypatch, sizes, tp):
from vllm.lora.lora_model import LoRAModel
from vllm.lora.lora_weights import LoRALayerWeights
from vllm.lora.peft_helper import PEFTHelper
from vllm.lora.request import LoRARequest
from vllm_omni.diffusion.lora.layers.column_parallel_linear import DiffusionMergedColumnParallelLinearWithLoRA
from vllm_omni.diffusion.lora.manager import DiffusionLoRAManager
torch.manual_seed(29)
weights = torch.randn(sum(sizes), 8)
lora_a = torch.randn(2, 8)
lora_b = torch.randn(sum(sizes), 2)
hidden = torch.randn(2, 3, 8)
name = "transformer.to_qkv_mlp_proj"
peft = PEFTHelper(r=2, lora_alpha=2, target_modules=["to_qkv_mlp_proj"])
request = LoRARequest(lora_name="test", lora_int_id=7, lora_path="unused-by-memory-loader")
for rank in range(tp):
_set_tp(monkeypatch, tp, rank)
projection = linear.MergedColumnParallelLinear(8, list(sizes), bias=False, gather_output=False)
projection.weight.weight_loader(projection.weight, weights)
pipeline = torch.nn.Module()
pipeline.transformer = torch.nn.Module()
pipeline.transformer.to_qkv_mlp_proj = projection
manager = DiffusionLoRAManager(pipeline, device=torch.device("cpu"), dtype=torch.float32)
adapter = LoRAModel(7, 2, {name: LoRALayerWeights(name, 2, 2, lora_a, lora_b)})
# Adapter disk I/O is outside this regression. Actual manager injection,
# five-/two-slice wrapper, rank slicing and activation all run below.
monkeypatch.setattr(manager, "_load_adapter", lambda request: (adapter, peft))
base_output = F.linear(hidden, _rank_rows(weights, sizes, tp, rank))
delta = F.linear(F.linear(hidden, lora_a), _rank_rows(lora_b, sizes, tp, rank))
for scale in (0.5, 1.0):
manager.set_active_adapter(request, lora_scale=scale)
wrapped = pipeline.transformer.to_qkv_mlp_proj
assert isinstance(wrapped, DiffusionMergedColumnParallelLinearWithLoRA)
assert wrapped.n_slices == len(sizes)
actual, _ = wrapped(hidden)
torch.testing.assert_close(actual, base_output + scale * delta, rtol=1e-5, atol=1e-5)
manager.set_active_adapter(None)
actual, _ = wrapped(hidden)
torch.testing.assert_close(actual, base_output, rtol=1e-5, atol=1e-5)
@pytest.mark.parametrize("image_count", [1, 2])
def test_native_editing_reaches_normal_input_validation(tmp_path, monkeypatch, image_count):
from PIL import Image
from vllm_omni.diffusion.models.flux2_klein.pipeline_flux2_klein import Flux2KleinPipeline
from vllm_omni.diffusion.request import OmniDiffusionRequest
from vllm_omni.diffusion.worker.request_batch import DiffusionRequestBatch
from vllm_omni.inputs.data import OmniDiffusionSamplingParams
class InputsReachedError(Exception):
pass
pipeline = object.__new__(Flux2KleinPipeline)
pipeline.od_config = OmniDiffusionConfig(
model=str(tmp_path),
dtype=torch.bfloat16,
parallel_config=DiffusionParallelConfig(tensor_parallel_size=4),
)
assert klein._use_local_single_stream_tp(pipeline.od_config)
images = [Image.new("RGB", (16, 16)) for _ in range(image_count)]
image_input = images[0] if image_count == 1 else images
request = OmniDiffusionRequest(
prompt={"prompt": "paint the boat red", "multi_modal_data": {"image": image_input}},
request_id="native-editing",
sampling_params=OmniDiffusionSamplingParams(height=512, width=512, num_inference_steps=4),
)
def check_inputs(**kwargs):
assert kwargs["prompt"] == "paint the boat red"
raise InputsReachedError
monkeypatch.setattr(pipeline, "check_inputs", check_inputs)
with pytest.raises(InputsReachedError):
pipeline.forward(DiffusionRequestBatch(requests=[request]))
assert request.prompt["multi_modal_data"]["image"] is image_input
+5
View File
@@ -693,6 +693,11 @@ class DiffusionLoRAManager:
)
b_splits = list(torch.split(deinterleaved, list(output_sizes), dim=0))
else:
base = getattr(lora_layer, "base_layer", None)
if isinstance(base, MergedColumnParallelLinear):
# Adapter B contains global rows. The LoRA layer
# slices each packed segment for its TP rank below.
output_slices = base.output_sizes
total = sum(output_slices)
if lora_weights.lora_b.shape[0] != total:
raise ValueError(
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
# Copyright 2025 Black Forest Labs, The HuggingFace Team and The InstantX Team. All rights reserved.
#
@@ -29,6 +29,7 @@ from diffusers.models.embeddings import (
)
from diffusers.models.modeling_outputs import Transformer2DModelOutput
from diffusers.models.normalization import AdaLayerNormContinuous
from vllm.distributed import get_tensor_model_parallel_world_size, tensor_model_parallel_all_gather
from vllm.logger import init_logger
from vllm.model_executor.layers.layernorm import RMSNorm
from vllm.model_executor.layers.linear import (
@@ -50,6 +51,7 @@ from vllm_omni.diffusion.distributed.sp_plan import (
from vllm_omni.diffusion.forward_context import get_forward_context
from vllm_omni.diffusion.layers.rope import RotaryEmbedding, apply_rope_to_qk
from vllm_omni.diffusion.models.host_weight_contract import FinalLayoutModelContract
from vllm_omni.diffusion.offloader.config import offload_enabled
logger = init_logger(__name__)
if TYPE_CHECKING:
@@ -70,6 +72,19 @@ def _join_prefix(prefix: str, name: str) -> str:
return f"{prefix}.{name}" if prefix else name
def _use_local_single_stream_tp(od_config: OmniDiffusionConfig) -> bool:
parallel = od_config.parallel_config
return (
od_config.dtype == torch.bfloat16
and parallel.tensor_parallel_size > 1
and parallel.sequence_parallel_size in (None, 1)
and not parallel.use_hsdp
and od_config.quantization_config is None
and od_config.lora_path is None
and not offload_enabled(od_config)
)
class Flux2SwiGLU(nn.Module):
"""SwiGLU activation used by Flux2."""
@@ -356,6 +371,7 @@ class Flux2ParallelSelfAttention(nn.Module):
mlp_mult_factor: int = 2,
quant_config: "QuantizationConfig | None" = None,
prefix: str = "",
local_tp: bool = False,
):
super().__init__()
self.parallel_config = parallel_config
@@ -370,14 +386,40 @@ class Flux2ParallelSelfAttention(nn.Module):
self.mlp_hidden_dim = int(query_dim * self.mlp_ratio)
self.mlp_mult_factor = mlp_mult_factor
self.to_qkv_mlp_proj = ColumnParallelLinear(
self.query_dim,
self.inner_dim * 3 + self.mlp_hidden_dim * self.mlp_mult_factor,
bias=bias,
gather_output=True,
quant_config=quant_config,
prefix=_join_prefix(prefix, "to_qkv_mlp_proj"),
self.local_tp = (
local_tp
and quant_config is None
and parallel_config.sequence_parallel_size in (None, 1)
and mlp_mult_factor == 2
)
tp_size = get_tensor_model_parallel_world_size() if self.local_tp else 1
self.local_tp = (
self.local_tp and tp_size > 1 and self.heads % tp_size == 0 and self.mlp_hidden_dim % tp_size == 0
)
tp_size = tp_size if self.local_tp else 1
self.query_num_heads = self.heads // tp_size
self.local_inner_dim = self.inner_dim // tp_size
self.local_mlp_hidden_dim = self.mlp_hidden_dim // tp_size
if self.local_tp:
# Split each packed segment independently so Q/K/V heads and
# SwiGLU gate/up channels stay paired on each TP rank.
self.to_qkv_mlp_proj = MergedColumnParallelLinear(
self.query_dim,
[self.inner_dim] * 3 + [self.mlp_hidden_dim] * 2,
bias=bias,
gather_output=False,
prefix=_join_prefix(prefix, "to_qkv_mlp_proj"),
)
else:
self.to_qkv_mlp_proj = ColumnParallelLinear(
self.query_dim,
self.inner_dim * 3 + self.mlp_hidden_dim * self.mlp_mult_factor,
bias=bias,
gather_output=True,
quant_config=quant_config,
prefix=_join_prefix(prefix, "to_qkv_mlp_proj"),
)
self.mlp_act_fn = Flux2SwiGLU()
self.norm_q = RMSNorm(dim_head, eps=eps)
@@ -393,7 +435,7 @@ class Flux2ParallelSelfAttention(nn.Module):
)
self.rope = RotaryEmbedding(is_neox_style=False)
self.attn = Attention(
num_heads=self.heads,
num_heads=self.query_num_heads,
head_size=self.head_dim,
softmax_scale=1.0 / (self.head_dim**0.5),
causal=False,
@@ -409,14 +451,14 @@ class Flux2ParallelSelfAttention(nn.Module):
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],
[3 * self.local_inner_dim, self.local_mlp_hidden_dim * self.mlp_mult_factor],
dim=-1,
)
query, key, value = qkv.chunk(3, dim=-1)
query = query.unflatten(-1, (self.heads, -1))
key = key.unflatten(-1, (self.heads, -1))
value = value.unflatten(-1, (self.heads, -1))
query = query.unflatten(-1, (self.query_num_heads, -1))
key = key.unflatten(-1, (self.query_num_heads, -1))
value = value.unflatten(-1, (self.query_num_heads, -1))
query = self.norm_q(query)
key = self.norm_k(key)
@@ -468,6 +510,10 @@ class Flux2ParallelSelfAttention(nn.Module):
if attention_mask is not None:
if attention_mask.dim() == 3:
attention_mask = attention_mask.unsqueeze(1)
if self.local_tp and attention_mask.dim() == 4 and attention_mask.shape[1] != 1:
attention_mask = attention_mask.narrow(
1, self.to_qkv_mlp_proj.tp_rank * self.query_num_heads, self.query_num_heads
)
attn_metadata = AttentionMetadata(attn_mask=attention_mask)
attn_output = self.attn(query, key, value, attn_metadata)
@@ -475,6 +521,9 @@ class Flux2ParallelSelfAttention(nn.Module):
attn_output = attn_output.flatten(2, 3).to(query.dtype)
mlp_hidden_states = self.mlp_act_fn(mlp_hidden_states)
if self.local_tp:
attn_output = tensor_model_parallel_all_gather(attn_output.contiguous())
mlp_hidden_states = tensor_model_parallel_all_gather(mlp_hidden_states.contiguous())
hidden_states = torch.cat([attn_output, mlp_hidden_states], dim=-1)
hidden_states, _ = self.to_out(hidden_states)
return hidden_states
@@ -492,6 +541,7 @@ class Flux2SingleTransformerBlock(nn.Module):
bias: bool = False,
quant_config: "QuantizationConfig | None" = None,
prefix: str = "",
local_tp: bool = False,
):
super().__init__()
self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
@@ -508,6 +558,7 @@ class Flux2SingleTransformerBlock(nn.Module):
mlp_mult_factor=2,
quant_config=quant_config,
prefix=_join_prefix(prefix, "attn"),
local_tp=local_tp,
)
def forward(
@@ -834,6 +885,7 @@ class Flux2Transformer2DModel(nn.Module):
guidance_embeds: bool = True,
od_config: OmniDiffusionConfig = None,
quant_config: "QuantizationConfig | None" = None,
local_tp: bool = False,
):
super().__init__()
# Warm HWR restoration skips load_weights(), so loader consumers such
@@ -911,10 +963,18 @@ class Flux2Transformer2DModel(nn.Module):
bias=False,
quant_config=quant_config,
prefix=f"single_transformer_blocks.{i}",
local_tp=local_tp,
)
for i in range(num_single_layers)
]
)
if any(block.attn.local_tp for block in self.single_transformer_blocks):
# Rank-local shapes are unchanged, but packed weight rows belong
# to different ranks. Do not restore the old final-layout cache.
self.host_weight_restore_contract = FinalLayoutModelContract(
implementation_id="flux2-klein-dit",
version="2-local-single-stream-tp",
)
self.norm_out = AdaLayerNormContinuous(
self.inner_dim,
@@ -43,6 +43,7 @@ from vllm_omni.diffusion.model_loader.diffusers_loader import DiffusersPipelineL
from vllm_omni.diffusion.model_loader.hub_prefetch import from_pretrained_with_prefetch, prefetch_subfolders
from vllm_omni.diffusion.models.flux2_klein.flux2_klein_transformer import (
Flux2Transformer2DModel,
_use_local_single_stream_tp,
)
from vllm_omni.diffusion.models.interface import SupportImageInput, SupportsComponentDiscovery
from vllm_omni.diffusion.profiler.diffusion_pipeline_profiler import DiffusionPipelineProfilerMixin
@@ -262,7 +263,11 @@ class Flux2KleinPipeline(
).to(self._execution_device)
transformer_kwargs = get_transformer_config_kwargs(od_config.tf_model_config, Flux2Transformer2DModel)
self.transformer = Flux2Transformer2DModel(quant_config=od_config.quantization_config, **transformer_kwargs)
self.transformer = Flux2Transformer2DModel(
quant_config=od_config.quantization_config,
local_tp=_use_local_single_stream_tp(od_config),
**transformer_kwargs,
)
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) if getattr(self, "vae", None) else 8
self.latent_channels = self.vae.config.latent_channels if hasattr(self.vae, "config") else 16