[diffusion] add tp support for qwen-image and refactor some tests (#830)

Signed-off-by: zjy0516 <riverclouds.zhu@qq.com>
This commit is contained in:
Jiangyun Zhu authored and GitHub committed 2026-01-19 14:20:31 +08:00
1 parent 5e7035e183
commit 3fc4f988ea
6 files changed
+228 -150

No files matched your search

@@ -23,13 +23,23 @@ The following table shows which models are currently supported by parallelism me
| **LongCat-Image** | `meituan-longcat/LongCat-Image` | ✅ | ✅ | ❌ | ❌ |
| **LongCat-Image-Edit** | `meituan-longcat/LongCat-Image-Edit` | ✅ | ✅ | ❌ | ❌ |
| **Ovis-Image** | `OvisAI/Ovis-Image` | ❌ | ❌ | ❌ | ❌ |
| **Qwen-Image** | `Qwen/Qwen-Image` | ✅ | ✅ | ✅ | ❌ |
| **Qwen-Image** | `Qwen/Qwen-Image` | ✅ | ✅ | ✅ | ✅ |
| **Qwen-Image-Edit** | `Qwen/Qwen-Image-Edit` | ✅ | ✅ | ✅ | ❌ |
| **Qwen-Image-Edit-2509** | `Qwen/Qwen-Image-Edit-2509` | ✅ | ✅ | ✅ | ❌ |
| **Qwen-Image-Layered** | `Qwen/Qwen-Image-Layered` | ✅ | ✅ | ✅ | ❌ |
| **Z-Image** | `Tongyi-MAI/Z-Image-Turbo` | ❌ | ❌ | ❌ | ✅ (TP=2 only) |
| **Stable-Diffusion3.5** | `stabilityai/stable-diffusion-3.5` | ❌ | ❌ | ❌ | ❌ |
!!! note "TP Limitations for Diffusion Models"
We currently implement Tensor Parallelism (TP) only for the DiT (Diffusion Transformer) blocks. This is because the `text_encoder` component in vLLM-Omni uses the original Transformers implementation, which does not yet support TP.
- Good news: The text_encoder typically has minimal impact on overall inference performance.
- Bad news: When TP is enabled, every TP process retains a full copy of the text_encoder weights, leading to significant GPU memory waste.
We are actively refactoring this design to address this. For details and progress, please refer to [Issue #771](https://github.com/vllm-project/vllm-omni/issues/771).
!!! note "Why Z-Image is TP=2 only"
Z-Image Turbo is currently limited to `tensor_parallel_size` of **1 or 2** due to model shape divisibility constraints.
For example, the model has `n_heads=30` and a final projection out dimension of `64`, so valid TP sizes must divide both 30 and 64; the only common divisors are **1 and 2**.
@@ -181,6 +181,12 @@ def parse_args() -> argparse.Namespace:
default=1,
help="Number of GPUs used for ring sequence parallelism.",
)
parser.add_argument(
"--tensor_parallel_size",
type=int,
default=1,
help="Number of GPUs used for tensor parallelism (TP) inside the DiT.",
)
parser.add_argument("--layers", type=int, default=4, help="Number of layers to decompose the input image into.")
parser.add_argument(
"--resolution",
@@ -301,7 +307,10 @@ def main():
vae_use_slicing = is_npu()
vae_use_tiling = is_npu()
parallel_config = DiffusionParallelConfig(
ulysses_degree=args.ulysses_degree, ring_degree=args.ring_degree, cfg_parallel_size=args.cfg_parallel_size
ulysses_degree=args.ulysses_degree,
ring_degree=args.ring_degree,
cfg_parallel_size=args.cfg_parallel_size,
tensor_parallel_size=args.tensor_parallel_size,
)
# Configure cache based on backend type
@@ -351,7 +360,7 @@ def main():
else:
print(f" Input image size: {input_image.size}")
print(
f" Parallel configuration: ulysses_degree={args.ulysses_degree}, ring_degree={args.ring_degree}, cfg_parallel_size={args.cfg_parallel_size}"
f" Parallel configuration: ulysses_degree={args.ulysses_degree}, ring_degree={args.ring_degree}, cfg_parallel_size={args.cfg_parallel_size}, tensor_parallel_size={args.tensor_parallel_size}"
)
print(f"{'=' * 60}\n")
@@ -1,11 +1,10 @@
import sys
import threading
import time
from pathlib import Path
import pytest
import torch
from tests.utils import GPUMemoryMonitor
from vllm_omni.utils.platform_utils import is_npu, is_rocm
# ruff: noqa: E402
@@ -15,39 +14,6 @@ if str(REPO_ROOT) not in sys.path:
from vllm_omni import Omni
class GPUMemoryMonitor:
"""Poll global device memory usage via CUDA APIs."""
def __init__(self, device_index: int, interval: float = 0.05):
self.device_index = device_index
self.interval = interval
self.peak_used_mb = 0.0
self._stop_event = threading.Event()
self._thread: threading.Thread | None = None
def start(self) -> None:
def monitor_loop() -> None:
while not self._stop_event.is_set():
try:
with torch.cuda.device(self.device_index):
free_bytes, total_bytes = torch.cuda.mem_get_info()
used_mb = (total_bytes - free_bytes) / (1024**2)
self.peak_used_mb = max(self.peak_used_mb, used_mb)
except Exception:
pass
time.sleep(self.interval)
self._thread = threading.Thread(target=monitor_loop, daemon=True)
self._thread.start()
def stop(self) -> None:
if self._thread is None:
return
self._stop_event.set()
self._thread.join(timeout=2.0)
models = ["riverclouds/qwen_image_random"]
@@ -73,13 +39,7 @@ def test_cpu_offload_diffusion_model(model_name: str):
generator=torch.Generator("cuda").manual_seed(42),
)
monitor.stop()
torch.cuda.synchronize(device_index)
fallback_alloc = torch.cuda.max_memory_allocated(device=device_index) / (1024**2)
fallback_reserved = torch.cuda.max_memory_reserved(device=device_index) / (1024**2)
peak_memory_mb = max(monitor.peak_used_mb, fallback_alloc, fallback_reserved)
return peak_memory_mb
return monitor.peak_used_mb
offload_peak_memory = inference(offload=True)
no_offload_peak_memory = inference(offload=False)
@@ -17,6 +17,7 @@ REPO_ROOT = Path(__file__).resolve().parents[2]
if str(REPO_ROOT) not in sys.path:
sys.path.insert(0, str(REPO_ROOT))
from tests.utils import GPUMemoryMonitor
from vllm_omni import Omni
from vllm_omni.diffusion.data import DiffusionParallelConfig
from vllm_omni.outputs import OmniRequestOutput
@@ -66,7 +67,12 @@ def _extract_single_image(outputs) -> Image.Image:
def _run_zimage_generate(
*, tp_size: int, height: int, width: int, num_inference_steps: int, seed: int
) -> tuple[Image.Image, float]:
) -> tuple[Image.Image, float, float]:
torch.cuda.empty_cache()
device_index = torch.cuda.current_device()
monitor = GPUMemoryMonitor(device_index=device_index, interval=0.02)
monitor.start()
m = Omni(
model=_get_zimage_model(),
parallel_config=DiffusionParallelConfig(tensor_parallel_size=tp_size),
@@ -107,7 +113,10 @@ def _run_zimage_generate(
pass
median_time_s = float(np.median(per_request_times_s))
return _extract_single_image([last_output]), median_time_s
peak_memory_mb = monitor.peak_used_mb
return _extract_single_image([last_output]), median_time_s, peak_memory_mb
finally:
m.close()
cleanup_dist_env_and_memory()
@@ -125,14 +134,14 @@ def test_zimage_tensor_parallel_tp2(tmp_path: Path):
num_inference_steps = 2
seed = 42
tp1_img, tp1_time_s = _run_zimage_generate(
tp1_img, tp1_time_s, tp1_peak_mem = _run_zimage_generate(
tp_size=1,
height=height,
width=width,
num_inference_steps=num_inference_steps,
seed=seed,
)
tp2_img, tp2_time_s = _run_zimage_generate(
tp2_img, tp2_time_s, tp2_peak_mem = _run_zimage_generate(
tp_size=2,
height=height,
width=width,
@@ -164,3 +173,8 @@ def test_zimage_tensor_parallel_tp2(tmp_path: Path):
print(f"Z-Image TP perf (lower is better): tp1_time_s={tp1_time_s:.6f}, tp2_time_s={tp2_time_s:.6f}")
assert tp2_time_s < tp1_time_s, f"Expected TP=2 to be faster than TP=1 (tp1={tp1_time_s}, tp2={tp2_time_s})"
print(f"Z-Image TP peak memory (MB): tp1_peak_mem={tp1_peak_mem:.2f}, tp2_peak_mem={tp2_peak_mem:.2f}")
assert tp2_peak_mem < tp1_peak_mem, (
f"Expected TP=2 to use less peak memory than TP=1 (tp1={tp1_peak_mem}, tp2={tp2_peak_mem})"
)
+43
View File
@@ -7,6 +7,7 @@ import signal
import subprocess
import sys
import tempfile
import threading
import time
from collections.abc import Callable
from contextlib import ExitStack, contextmanager, suppress
@@ -14,6 +15,7 @@ from typing import Any, Literal
import cloudpickle
import pytest
import torch
from typing_extensions import ParamSpec
from vllm.platforms import current_platform
from vllm.utils.torch_utils import cuda_device_count_stateless
@@ -474,3 +476,44 @@ def hardware_test(*, res: dict[str, str], num_cards: int | dict[str, int] = 1):
return func
return wrapper
class GPUMemoryMonitor:
"""Poll global device memory usage via CUDA APIs."""
def __init__(self, device_index: int, interval: float = 0.05):
self.device_index = device_index
self.interval = interval
self._peak_used_mb = 0.0
self._stop_event = threading.Event()
self._thread: threading.Thread | None = None
def start(self) -> None:
def monitor_loop() -> None:
while not self._stop_event.is_set():
try:
with torch.cuda.device(self.device_index):
free_bytes, total_bytes = torch.cuda.mem_get_info()
used_mb = (total_bytes - free_bytes) / (1024**2)
self._peak_used_mb = max(self._peak_used_mb, used_mb)
except Exception:
pass
time.sleep(self.interval)
self._thread = threading.Thread(target=monitor_loop, daemon=True)
self._thread.start()
def stop(self) -> None:
if self._thread is None:
return
self._stop_event.set()
self._thread.join(timeout=2.0)
@property
def peak_used_mb(self) -> float:
fallback_alloc = torch.cuda.max_memory_allocated(device=self.device_index) / (1024**2)
fallback_reserved = torch.cuda.max_memory_reserved(device=self.device_index) / (1024**2)
return max(self._peak_used_mb, fallback_alloc, fallback_reserved)
def __del__(self):
self.stop()
@@ -8,7 +8,7 @@ from typing import Any
import torch
import torch.nn as nn
from diffusers.models.attention import FeedForward
import torch.nn.functional as F
# TODO replace this with vLLM implementation
from diffusers.models.embeddings import TimestepEmbedding, Timesteps
@@ -16,7 +16,11 @@ from diffusers.models.modeling_outputs import Transformer2DModelOutput
from diffusers.models.normalization import AdaLayerNormContinuous
from vllm.logger import init_logger
from vllm.model_executor.layers.layernorm import RMSNorm
from vllm.model_executor.layers.linear import QKVParallelLinear, ReplicatedLinear
from vllm.model_executor.layers.linear import (
ColumnParallelLinear,
QKVParallelLinear,
RowParallelLinear,
)
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
from vllm_omni.diffusion.attention.backends.abstract import (
@@ -287,79 +291,133 @@ class QwenEmbedRope(nn.Module):
return freqs.clone().contiguous()
class ColumnParallelApproxGELU(nn.Module):
def __init__(self, dim_in: int, dim_out: int, *, approximate: str, bias: bool = True):
super().__init__()
self.proj = ColumnParallelLinear(
dim_in,
dim_out,
bias=bias,
gather_output=False,
return_bias=False,
)
self.approximate = approximate
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.proj(x)
return F.gelu(x, approximate=self.approximate)
class FeedForward(nn.Module):
def __init__(
self,
dim: int,
dim_out: int | None = None,
mult: int = 4,
activation_fn: str = "gelu-approximate",
inner_dim: int | None = None,
bias: bool = True,
) -> None:
super().__init__()
assert activation_fn == "gelu-approximate", "Only gelu-approximate is supported."
inner_dim = inner_dim or int(dim * mult)
dim_out = dim_out or dim
layers: list[nn.Module] = [
ColumnParallelApproxGELU(dim, inner_dim, approximate="tanh", bias=bias),
nn.Identity(), # placeholder for weight loading
RowParallelLinear(
inner_dim,
dim_out,
input_is_parallel=True,
return_bias=False,
),
]
self.net = nn.ModuleList(layers)
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
for module in self.net:
hidden_states = module(hidden_states)
return hidden_states
class QwenImageCrossAttention(nn.Module):
def __init__(
self,
dim: int, # query_dim
num_heads: int,
head_dim: int,
window_size=(-1, -1),
added_kv_proj_dim: int = None,
added_kv_proj_dim: int,
window_size: tuple[int, int] = (-1, -1),
out_bias: bool = True,
qk_norm=True, # rmsnorm
eps=1e-6,
pre_only=False,
qk_norm: bool = True,
eps: float = 1e-6,
pre_only: bool = False,
context_pre_only: bool = False,
parallel_attention=False,
out_dim: int = None,
out_dim: int | None = None,
) -> None:
assert dim % num_heads == 0
super().__init__()
assert dim % num_heads == 0
self.dim = dim
self.num_heads = num_heads
self.head_dim = dim // num_heads
self.head_dim = head_dim
self.total_num_heads = num_heads
self.window_size = window_size
self.qk_norm = qk_norm
self.eps = eps
self.parallel_attention = parallel_attention
# layers
# self.to_q = ReplicatedLinear(dim, dim)
# self.to_k = ReplicatedLinear(dim, dim)
# self.to_v = ReplicatedLinear(dim, dim)
self.to_qkv = QKVParallelLinear(
hidden_size=dim,
head_size=self.head_dim,
total_num_heads=num_heads,
disable_tp=True,
)
self.query_num_heads = self.to_qkv.num_heads
self.kv_num_heads = self.to_qkv.num_kv_heads
self.norm_q = RMSNorm(head_dim, eps=eps) if qk_norm else nn.Identity()
self.norm_k = RMSNorm(head_dim, eps=eps) if qk_norm else nn.Identity()
self.inner_dim = out_dim if out_dim is not None else head_dim * num_heads
self.inner_kv_dim = self.inner_dim
if added_kv_proj_dim is not None:
assert context_pre_only is not None
# self.add_k_proj = ReplicatedLinear(added_kv_proj_dim, self.inner_kv_dim, bias=True)
# self.add_v_proj = ReplicatedLinear(added_kv_proj_dim, self.inner_kv_dim, bias=True)
# self.add_q_proj = ReplicatedLinear(
# added_kv_proj_dim, self.inner_dim, bias=True
# )
self.add_kv_proj = QKVParallelLinear(
added_kv_proj_dim,
head_size=self.inner_kv_dim // self.num_heads,
total_num_heads=self.num_heads,
disable_tp=True,
)
if context_pre_only is not None and not context_pre_only:
self.to_add_out = ReplicatedLinear(self.inner_dim, self.dim, bias=out_bias)
else:
self.to_add_out = None
self.inner_dim = out_dim if out_dim is not None else head_dim * self.total_num_heads
if not pre_only:
self.to_out = nn.ModuleList([])
self.to_out.append(ReplicatedLinear(self.inner_dim, self.dim, bias=out_bias))
else:
self.to_out = None
assert context_pre_only is not None
self.add_kv_proj = QKVParallelLinear(
hidden_size=added_kv_proj_dim,
head_size=head_dim,
total_num_heads=num_heads,
)
self.add_query_num_heads = self.add_kv_proj.num_heads
self.add_kv_num_heads = self.add_kv_proj.num_kv_heads
assert not context_pre_only
self.to_add_out = RowParallelLinear(
self.inner_dim,
self.dim,
bias=out_bias,
input_is_parallel=True,
return_bias=False,
)
assert not pre_only
self.to_out = RowParallelLinear(
self.inner_dim,
self.dim,
bias=out_bias,
input_is_parallel=True,
return_bias=False,
)
self.norm_added_q = RMSNorm(head_dim, eps=eps)
self.norm_added_k = RMSNorm(head_dim, eps=eps)
self.attn = Attention(
num_heads=num_heads,
num_heads=self.query_num_heads,
head_size=self.head_dim,
softmax_scale=1.0 / (self.head_dim**0.5),
causal=False,
num_kv_heads=self.kv_num_heads,
)
self.rope = RotaryEmbedding(is_neox_style=False)
@@ -377,61 +435,55 @@ class QwenImageCrossAttention(nn.Module):
txt_freqs: torch.Tensor,
hidden_states_mask: torch.Tensor | None = None,
encoder_hidden_states_mask: torch.Tensor | None = None,
):
# if mask is all true, set it to None
) -> tuple[torch.Tensor, torch.Tensor]:
if hidden_states_mask is not None and hidden_states_mask.all():
hidden_states_mask = None
if encoder_hidden_states_mask is not None and encoder_hidden_states_mask.all():
encoder_hidden_states_mask = None
seq_len_txt = encoder_hidden_states.shape[1]
# Compute QKV for image stream (sample projections)
qkv, _ = self.to_qkv(hidden_states)
img_query, img_key, img_value = qkv.chunk(3, dim=-1)
img_qkv, _ = self.to_qkv(hidden_states)
q_size = self.query_num_heads * self.head_dim
kv_size = self.kv_num_heads * self.head_dim
img_query, img_key, img_value = img_qkv.split([q_size, kv_size, kv_size], dim=-1)
# Compute QKV for text stream (context projections)
qkv, _ = self.add_kv_proj(encoder_hidden_states)
txt_query, txt_key, txt_value = qkv.chunk(3, dim=-1)
txt_qkv, _ = self.add_kv_proj(encoder_hidden_states)
add_q_size = self.add_query_num_heads * self.head_dim
add_kv_size = self.add_kv_num_heads * self.head_dim
txt_query, txt_key, txt_value = txt_qkv.split([add_q_size, add_kv_size, add_kv_size], dim=-1)
# Reshape for multi-head attention
img_query = img_query.unflatten(-1, (self.num_heads, -1))
img_key = img_key.unflatten(-1, (self.num_heads, -1))
img_value = img_value.unflatten(-1, (self.num_heads, -1))
img_query = img_query.unflatten(-1, (self.query_num_heads, self.head_dim))
img_key = img_key.unflatten(-1, (self.kv_num_heads, self.head_dim))
img_value = img_value.unflatten(-1, (self.kv_num_heads, self.head_dim))
txt_query = txt_query.unflatten(-1, (self.num_heads, -1))
txt_key = txt_key.unflatten(-1, (self.num_heads, -1))
txt_value = txt_value.unflatten(-1, (self.num_heads, -1))
txt_query = txt_query.unflatten(-1, (self.add_query_num_heads, self.head_dim))
txt_key = txt_key.unflatten(-1, (self.add_kv_num_heads, self.head_dim))
txt_value = txt_value.unflatten(-1, (self.add_kv_num_heads, self.head_dim))
# Apply QK normalization
img_query = self.norm_q(img_query)
img_key = self.norm_k(img_key)
txt_query = self.norm_added_q(txt_query)
txt_key = self.norm_added_k(txt_key)
# Apply RoPE
img_cos = vid_freqs.real.to(img_query.dtype)
img_sin = vid_freqs.imag.to(img_query.dtype)
txt_cos = txt_freqs.real.to(txt_query.dtype)
txt_sin = txt_freqs.imag.to(txt_query.dtype)
img_query = self.rope(img_query, img_cos, img_sin)
img_key = self.rope(img_key, img_cos, img_sin)
txt_query = self.rope(txt_query, txt_cos, txt_sin)
txt_key = self.rope(txt_key, txt_cos, txt_sin)
# Concatenate for joint attention
# Order: [text, image]
seq_len_txt = encoder_hidden_states.shape[1]
joint_query = torch.cat([txt_query, img_query], dim=1)
joint_key = torch.cat([txt_key, img_key], dim=1)
joint_value = torch.cat([txt_value, img_value], dim=1)
# Compute joint attention
if (
self.parallel_config is not None
and self.parallel_config.sequence_parallel_size > 1
and not get_forward_context().split_text_embed_in_sp
):
# if using sequence parallel, but not splitting text embed,
# we need to pass text embedding to attention layer as joint qkv
attn_metadata = AttentionMetadata(
joint_query=txt_query,
joint_key=txt_key,
@@ -443,22 +495,17 @@ class QwenImageCrossAttention(nn.Module):
if encoder_hidden_states_mask is not None:
attn_metadata.joint_attn_mask = encoder_hidden_states_mask
joint_hidden_states = self.attn(
img_query,
img_key,
img_value,
attn_metadata,
)
joint_hidden_states = self.attn(img_query, img_key, img_value, attn_metadata)
else:
attn_metadata = None
if hidden_states_mask is not None or encoder_hidden_states_mask is not None:
mask_list = []
mask_list: list[torch.Tensor] = []
if encoder_hidden_states_mask is not None:
mask_list.append(encoder_hidden_states_mask)
else:
mask_list.append(
torch.ones(
[encoder_hidden_states.shape[0], encoder_hidden_states.shape[1]],
encoder_hidden_states.shape[:2],
dtype=torch.bool,
device=encoder_hidden_states.device,
)
@@ -468,34 +515,22 @@ class QwenImageCrossAttention(nn.Module):
else:
mask_list.append(
torch.ones(
[hidden_states.shape[0], hidden_states.shape[1]],
hidden_states.shape[:2],
dtype=torch.bool,
device=hidden_states.device,
)
)
joint_mask = (
None if len(mask_list) == 0 else torch.cat(mask_list, dim=1) if len(mask_list) > 1 else mask_list[0]
)
joint_mask = torch.cat(mask_list, dim=1) if len(mask_list) > 1 else mask_list[0]
attn_metadata = AttentionMetadata(attn_mask=joint_mask)
joint_hidden_states = self.attn(
joint_query,
joint_key,
joint_value,
attn_metadata,
)
joint_hidden_states = joint_hidden_states.flatten(2, 3)
joint_hidden_states = joint_hidden_states.to(joint_query.dtype)
# Split attention outputs back
txt_attn_output = joint_hidden_states[:, :seq_len_txt, :] # Text part
img_attn_output = joint_hidden_states[:, seq_len_txt:, :] # Image part
joint_hidden_states = self.attn(joint_query, joint_key, joint_value, attn_metadata)
# Apply output projections
img_attn_output, _ = self.to_out[0](img_attn_output)
if len(self.to_out) > 1:
(img_attn_output,) = self.to_out[1](img_attn_output) # dropout
joint_hidden_states = joint_hidden_states.flatten(2, 3).to(joint_query.dtype)
txt_attn_output = joint_hidden_states[:, :seq_len_txt, :]
img_attn_output = joint_hidden_states[:, seq_len_txt:, :]
txt_attn_output, _ = self.to_add_out(txt_attn_output)
img_attn_output = self.to_out(img_attn_output)
txt_attn_output = self.to_add_out(txt_attn_output)
return img_attn_output, txt_attn_output
@@ -530,7 +565,7 @@ class QwenImageTransformerBlock(nn.Module):
head_dim=attention_head_dim,
)
self.img_norm2 = AdaLayerNorm(dim, elementwise_affine=False, eps=eps)
self.img_mlp = FeedForward(dim=dim, dim_out=dim, activation_fn="gelu-approximate")
self.img_mlp = FeedForward(dim=dim, dim_out=dim)
# Text processing modules
self.txt_mod = nn.Sequential(
@@ -540,7 +575,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, activation_fn="gelu-approximate")
self.txt_mlp = FeedForward(dim=dim, dim_out=dim)
self.zero_cond_t = zero_cond_t
@@ -892,6 +927,8 @@ class QwenImageTransformer2DModel(CachedTransformer):
if original_seq_len is not None:
output = output[:, :original_seq_len, :]
torch.cuda.empty_cache()
return Transformer2DModelOutput(sample=output)
def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
@@ -916,17 +953,22 @@ class QwenImageTransformer2DModel(CachedTransformer):
loaded_params: set[str] = set()
for name, loaded_weight in weights:
original_name = name
lookup_name = name
for param_name, weight_name, shard_id in stacked_params_mapping:
if weight_name not in name:
if weight_name not in original_name:
continue
name = name.replace(weight_name, param_name)
param = params_dict[name]
lookup_name = original_name.replace(weight_name, param_name)
param = params_dict[lookup_name]
weight_loader = param.weight_loader
weight_loader(param, loaded_weight, shard_id)
break
else:
param = params_dict[name]
if lookup_name not in params_dict and ".to_out.0." in lookup_name:
lookup_name = lookup_name.replace(".to_out.0.", ".to_out.")
param = params_dict[lookup_name]
weight_loader = getattr(param, "weight_loader", default_weight_loader)
weight_loader(param, loaded_weight)
loaded_params.add(name)
loaded_params.add(original_name)
loaded_params.add(lookup_name)
return loaded_params