tonera commited on
Commit
3942107
·
verified ·
1 Parent(s): 0bc5cd0

Upload transformer_chroma.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. transformer_chroma.py +383 -693
transformer_chroma.py CHANGED
@@ -1,30 +1,19 @@
1
  from __future__ import annotations
2
 
 
3
  import json
4
- import math
5
  from dataclasses import dataclass
6
  from pathlib import Path
7
  from typing import Any, Optional
 
8
 
9
  import torch
 
10
  from diffusers.configuration_utils import ConfigMixin
11
  from diffusers.models.modeling_utils import ModelMixin
12
  from torch import nn
13
 
14
 
15
- # Keep the C++ additive-attention exception log one-shot to avoid
16
- # repeating the same fallback message for every transformer block.
17
- _CPP_ADDITIVE_ATTN_EXCEPTION_LOGGED = False
18
-
19
-
20
- def _log_cpp_additive_attn_exception(reason: str):
21
- global _CPP_ADDITIVE_ATTN_EXCEPTION_LOGGED
22
- if _CPP_ADDITIVE_ATTN_EXCEPTION_LOGGED:
23
- return
24
- _CPP_ADDITIVE_ATTN_EXCEPTION_LOGGED = True
25
- print(f"[nunchaku.chroma] cpp_additive_attn fallback: {reason}")
26
-
27
-
28
  @dataclass(frozen=True)
29
  class LoadReport:
30
  config: dict[str, Any]
@@ -32,198 +21,131 @@ class LoadReport:
32
  rank: int
33
 
34
 
35
- def _maybe_log(verbose: bool, *args):
36
- if verbose:
37
- print(*args)
38
 
 
 
 
 
 
 
39
 
40
- def _load_safetensors_state_dict(
41
- path: str | Path,
42
- *,
43
- device: str,
44
- ) -> tuple[dict[str, Any], dict[str, str]]:
45
- from nunchaku.utils import load_state_dict_in_safetensors
46
 
47
- sd, md = load_state_dict_in_safetensors(path, device=device, return_metadata=True)
48
- return sd, md
49
-
50
-
51
- def _convert_checkpoint_key_for_svdq_linear(k: str) -> str:
52
- # This safetensors uses nunchaku converter naming:
53
- # - lora_down/lora_up are actually SVD proj_down/proj_up
54
- # - smooth/smooth_orig are smooth_factor/smooth_factor_orig
55
- if ".lora_down" in k:
56
- k = k.replace(".lora_down", ".proj_down")
57
- if ".lora_up" in k:
58
- k = k.replace(".lora_up", ".proj_up")
59
- if ".smooth_orig" in k:
60
- k = k.replace(".smooth_orig", ".smooth_factor_orig")
61
- elif ".smooth" in k:
62
- k = k.replace(".smooth", ".smooth_factor")
63
  return k
64
 
65
 
66
  def _convert_checkpoint_state_dict(sd: dict[str, Any]) -> dict[str, Any]:
67
- return {_convert_checkpoint_key_for_svdq_linear(k): v for k, v in sd.items()}
68
 
69
 
70
  def _infer_rank_from_converted_state_dict(sd: dict[str, Any]) -> int:
71
- # Look for any SVDQW4A4Linear proj_down shaped [in_features, rank]
72
  for k, v in sd.items():
73
  if k.endswith(".proj_down") and getattr(v, "ndim", None) == 2:
74
  return int(v.shape[1])
75
  raise ValueError("Cannot infer SVD rank from checkpoint (missing any '*.proj_down' tensors).")
76
 
77
 
78
- def _build_attn_norms(*, head_dim: int, eps: float, with_added: bool, device, dtype):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  from diffusers.models.normalization import RMSNorm
80
 
81
- m = nn.Module()
82
- m.norm_q = RMSNorm(head_dim, eps=eps, elementwise_affine=True).to(device=device, dtype=dtype)
83
- m.norm_k = RMSNorm(head_dim, eps=eps, elementwise_affine=True).to(device=device, dtype=dtype)
 
84
  if with_added:
85
- m.norm_added_q = RMSNorm(head_dim, eps=eps, elementwise_affine=True).to(device=device, dtype=dtype)
86
- m.norm_added_k = RMSNorm(head_dim, eps=eps, elementwise_affine=True).to(device=device, dtype=dtype)
87
- return m
88
-
89
-
90
- def _should_use_cpp_additive_attn(*, attention_mask_1d, hidden_states, head_dim: int) -> bool:
91
- return attention_mask_1d is not None and hidden_states.is_cuda and int(head_dim) == 128
92
-
93
-
94
- def _pad_to_multiple(n: int, multiple: int) -> int:
95
- return int(math.ceil(n / multiple) * multiple)
96
-
97
-
98
- def _get_or_create_cpp_workspace(
99
- owner,
100
- cache_attr: str,
101
- *,
102
- batch_size: int,
103
- num_tokens_pad: int,
104
- heads: int,
105
- head_dim: int,
106
- device,
107
- out_dtype,
108
- ):
109
- key = (batch_size, num_tokens_pad, heads, head_dim, str(device), out_dtype)
110
- ws = getattr(owner, cache_attr, None)
111
- if ws is None or ws.get("key") != key:
112
- ws = {
113
- "key": key,
114
- "q": torch.empty((batch_size, heads, num_tokens_pad, head_dim), device=device, dtype=torch.float16),
115
- "k": torch.empty((batch_size, heads, num_tokens_pad, head_dim), device=device, dtype=torch.float16),
116
- "v": torch.empty((batch_size, heads, num_tokens_pad, head_dim), device=device, dtype=torch.float16),
117
- "m": torch.empty((batch_size, num_tokens_pad), device=device, dtype=torch.float16),
118
- "out": torch.empty((batch_size, num_tokens_pad, heads * head_dim), device=device, dtype=out_dtype),
119
- }
120
- setattr(owner, cache_attr, ws)
121
- return ws
122
-
123
-
124
- def _get_cpp_workspace_tensors(cpp_workspace: dict):
125
- return (
126
- cpp_workspace["q"],
127
- cpp_workspace["k"],
128
- cpp_workspace["v"],
129
- cpp_workspace["m"],
130
- cpp_workspace["out"],
131
- )
132
 
 
 
133
 
134
- def _run_cpp_additive_attention(q, k, v, mask, out, *, context: str) -> bool:
135
- try:
136
- from nunchaku._C.ops import chroma_additive_attention_packed_fp16
137
 
138
- chroma_additive_attention_packed_fp16(q, k, v, mask, out, 0.0)
139
- return True
140
- except Exception as e:
141
- _log_cpp_additive_attn_exception(f"exception in {context}: {type(e).__name__}: {e}")
142
- return False
 
143
 
144
 
145
- def _fused_qkv_heads(hidden_states, qkv_proj, norm_q, norm_k, rotary_emb, heads: int):
146
- from nunchaku.ops.fused import fused_qkv_norm_rottary
147
 
148
- qkv = fused_qkv_norm_rottary(hidden_states, qkv_proj, norm_q, norm_k, rotary_emb)
 
 
149
  query, key, value = qkv.chunk(3, dim=-1)
150
- return tuple(x.unflatten(-1, (heads, -1)) for x in (query, key, value))
151
-
152
-
153
- def _expand_batch_dim(x: torch.Tensor, batch_size: int) -> torch.Tensor:
154
- if batch_size != int(x.shape[0]):
155
- x = x.expand(batch_size, -1, -1).contiguous()
156
- return x
157
-
158
-
159
- def _prepare_cpp_context(owner, hidden_states, attention_mask, *, txt_tokens: int, img_tokens: int):
160
- heads = int(owner.config.num_attention_heads)
161
- head_dim = int(owner.config.attention_head_dim)
162
- batch_size = int(hidden_states.shape[0])
163
- device = hidden_states.device
164
- out_dtype = hidden_states.dtype
165
-
166
- pad_size = 256
167
- txt_pad = _pad_to_multiple(txt_tokens, pad_size)
168
- img_pad = _pad_to_multiple(img_tokens, pad_size)
169
- s_total = int(txt_tokens + img_tokens)
170
- s_pad = _pad_to_multiple(s_total, pad_size)
171
-
172
- ws_dual = _get_or_create_cpp_workspace(
173
- owner,
174
- "_nunchaku_cpp_ws_dual_shared",
175
- batch_size=batch_size,
176
- num_tokens_pad=txt_pad + img_pad,
177
- heads=heads,
178
- head_dim=head_dim,
179
- device=device,
180
- out_dtype=out_dtype,
181
- )
182
- ws_single = _get_or_create_cpp_workspace(
183
- owner,
184
- "_nunchaku_cpp_ws_single_shared",
185
- batch_size=batch_size,
186
- num_tokens_pad=s_pad,
187
- heads=heads,
188
- head_dim=head_dim,
189
- device=device,
190
- out_dtype=out_dtype,
191
- )
192
 
193
- attn_mask_fp16 = attention_mask.to(dtype=torch.float16)
 
 
 
 
 
 
 
 
 
 
194
 
195
- mask_single = ws_single["m"]
196
- mask_single.zero_()
197
- mask_single[:, :s_total] = attn_mask_fp16
198
 
199
- mask_dual = ws_dual["m"]
200
- mask_dual.zero_()
201
- mask_dual[:, :txt_tokens] = attn_mask_fp16[:, :txt_tokens]
202
- mask_dual[:, txt_pad : txt_pad + img_tokens] = attn_mask_fp16[:, txt_tokens : txt_tokens + img_tokens]
203
 
204
- return ws_dual, ws_single, mask_dual, mask_single
 
 
205
 
206
 
207
  def _dispatch_attention(query, key, value, attention_mask):
208
- """
209
- Chroma attention dispatch.
210
 
211
- Performance note:
212
- This function must NOT call `.item()` on CUDA tensors (it would introduce a device sync per block).
213
  """
214
- from diffusers.models.transformers.transformer_flux import dispatch_attention_fn
215
 
216
- # No mask: allow fastest backend selection (FLASH where available).
217
  if attention_mask is None:
218
  return dispatch_attention_fn(query, key, value, attn_mask=None, backend=None)
219
 
220
- # Speed + quality path (Chroma-specific):
221
- # The Chroma pipeline provides a 2D mask `m` (values in {0,1}, dtype usually bf16/fp16), which diffusers expands
222
- # to a rank-1 outer-product bias `m_i * m_j` and passes as an additive SDPA mask.
223
- # This is *not* a boolean hard-mask, but an additive bias in SDPA.
224
- #
225
- # We can fold this outer-product bias into the QK dot-product by augmenting Q/K with extra dims, and then run
226
- # fast attention with attn_mask=None while preserving semantics closely.
227
  if attention_mask.ndim == 2 and query.shape[0] == 1:
228
  b, s = attention_mask.shape
229
  if b != 1:
@@ -233,313 +155,117 @@ def _dispatch_attention(query, key, value, attention_mask):
233
  f"Mask/sequence length mismatch: mask S={int(s)}, query S={int(query.shape[1])}, key S={int(key.shape[1])}"
234
  )
235
 
236
- # Expand to (B,S,H,1) and keep dtype aligned with Q/K.
237
  m1 = attention_mask.to(dtype=query.dtype)[:, :, None, None].expand(
238
  query.shape[0], query.shape[1], query.shape[2], 1
239
  )
240
-
241
  d = int(query.shape[-1])
242
  scale = float(d) ** -0.5
243
-
244
- # Keep extra dims minimal but aligned (multiple of 8) to reduce overhead.
245
- extra = 8
246
  sqrt_d = float(d) ** 0.5
 
247
 
248
- q_extra = torch.cat([m1 * sqrt_d, m1.new_zeros((*m1.shape[:-1], extra - 1))], dim=-1)
249
- k_extra = torch.cat([m1, m1.new_zeros((*m1.shape[:-1], extra - 1))], dim=-1)
250
- v_extra = value.new_zeros((*value.shape[:-1], extra))
251
-
252
- q_ext = torch.cat([query, q_extra], dim=-1)
253
- k_ext = torch.cat([key, k_extra], dim=-1)
254
- v_ext = torch.cat([value, v_extra], dim=-1)
255
 
256
- # Prefer native flash kernel when available; pass explicit scale to preserve original head_dim scaling.
257
  try:
258
  out_ext = dispatch_attention_fn(q_ext, k_ext, v_ext, attn_mask=None, backend="_native_flash", scale=scale)
259
  except TypeError:
260
- # Older diffusers may not expose `scale` in dispatch_attention_fn; fallback to correctness baseline.
261
  out_ext = None
262
  if out_ext is not None:
263
  return out_ext[..., :d]
264
 
265
- # Fallback: preserve diffusers Chroma mask semantics (outer-product additive bias) and use SDPA efficient.
266
- attn_mask_4d = NunchakuChromaTransformerBlockMixin._mask_to_4d(attention_mask)
267
  return dispatch_attention_fn(query, key, value, attn_mask=attn_mask_4d, backend="_native_efficient")
268
 
269
 
270
- class NunchakuChromaTransformerBlockMixin:
271
- @staticmethod
272
- def _mask_to_4d(attention_mask):
273
- # Match diffusers `transformer_chroma` behavior:
274
- # Expand a 2D mask to a full QK mask (outer product).
275
- #
276
- # IMPORTANT: do NOT cast to bool here. Chroma's pipeline may provide a non-bool mask (e.g. bf16 0/1),
277
- # and changing dtype/value semantics affects output quality.
278
- if attention_mask is None:
279
- return None
280
- if attention_mask.ndim == 4:
281
- return attention_mask
282
- if attention_mask.ndim != 2:
283
- raise ValueError(f"Unsupported attention_mask shape: {tuple(attention_mask.shape)}")
284
- return attention_mask[:, None, None, :] * attention_mask[:, None, :, None]
285
-
286
-
287
- class NunchakuChromaSingleTransformerBlock(nn.Module, NunchakuChromaTransformerBlockMixin):
288
- """
289
- Matches the checkpoint key layout under:
290
- single_transformer_blocks.<i>.{qkv_proj,out_proj,mlp_fc1,mlp_fc2,attn.norm_{q,k},norm,proj_out?}
291
- """
292
 
293
- def __init__(
294
- self,
295
- *,
296
- dim: int,
297
- num_attention_heads: int,
298
- attention_head_dim: int,
299
- mlp_ratio: float,
300
- rank: int,
301
- precision: str,
302
- device,
303
- dtype,
304
- eps: float = 1e-6,
305
- ):
306
  super().__init__()
307
  from diffusers.models.transformers.transformer_chroma import ChromaAdaLayerNormZeroSinglePruned
308
- from nunchaku.models.linear import SVDQW4A4Linear
309
 
310
- self.heads = int(num_attention_heads)
311
- self.head_dim = int(attention_head_dim)
312
- self.inner_dim = int(dim)
313
- self.mlp_hidden_dim = int(dim * mlp_ratio)
314
 
315
  self.norm = ChromaAdaLayerNormZeroSinglePruned(dim).to(device=device, dtype=dtype)
316
  self.attn = _build_attn_norms(head_dim=self.head_dim, eps=eps, with_added=False, device=device, dtype=dtype)
317
 
318
- self.qkv_proj = SVDQW4A4Linear(
319
- in_features=dim,
320
- out_features=3 * dim,
321
- rank=rank,
322
- bias=True,
323
- precision=precision,
324
- torch_dtype=dtype,
325
- device=device,
326
- )
327
- self.out_proj = SVDQW4A4Linear(
328
- in_features=dim,
329
- out_features=dim,
330
- rank=rank,
331
- bias=True,
332
- precision=precision,
333
- torch_dtype=dtype,
334
- device=device,
335
- )
336
- self.mlp_fc1 = SVDQW4A4Linear(
337
- in_features=dim,
338
- out_features=self.mlp_hidden_dim,
339
- rank=rank,
340
- bias=True,
341
- precision=precision,
342
- torch_dtype=dtype,
343
- device=device,
344
- )
345
- self.mlp_fc2 = SVDQW4A4Linear(
346
- in_features=self.mlp_hidden_dim,
347
- out_features=dim,
348
- rank=rank,
349
- bias=True,
350
- precision=precision,
351
- torch_dtype=dtype,
352
- device=device,
353
- )
354
-
355
- self.norm_q = self.attn.norm_q
356
- self.norm_k = self.attn.norm_k
357
-
358
- def forward(
359
- self,
360
- hidden_states,
361
- temb,
362
- image_rotary_emb=None,
363
- attention_mask_1d=None,
364
- cpp_workspace: dict | None = None,
365
- cpp_mask: torch.Tensor | None = None,
366
- ):
367
- from nunchaku.ops.fused import fused_gelu_mlp, fused_qkv_norm_rottary
368
 
 
369
  residual = hidden_states
370
  norm_hidden_states, gate = self.norm(hidden_states, emb=temb)
371
 
372
- mlp_out = fused_gelu_mlp(norm_hidden_states, self.mlp_fc1, self.mlp_fc2)
 
 
373
 
374
- # Optional C++/CUDA additive attention backend (exact Chroma semantics, B=1 only).
375
- use_cpp = _should_use_cpp_additive_attn(
376
- attention_mask_1d=attention_mask_1d,
377
- hidden_states=norm_hidden_states,
378
- head_dim=self.head_dim,
379
  )
380
- if use_cpp:
381
- assert cpp_workspace is not None and cpp_mask is not None
382
- _, s, _ = norm_hidden_states.shape
383
- q, k, v, _, out = _get_cpp_workspace_tensors(cpp_workspace)
384
- fused_qkv_norm_rottary(
385
- norm_hidden_states,
386
- self.qkv_proj,
387
- self.attn.norm_q,
388
- self.attn.norm_k,
389
- image_rotary_emb,
390
- output=(q, k, v),
391
- attn_tokens=int(s),
392
- )
393
- if _run_cpp_additive_attention(q, k, v, cpp_mask, out, context="single-block cpp path"):
394
- attn_out = out[:, :s, :]
395
- else:
396
- use_cpp = False
397
-
398
- if not use_cpp:
399
- query, key, value = _fused_qkv_heads(
400
- norm_hidden_states, self.qkv_proj, self.attn.norm_q, self.attn.norm_k, image_rotary_emb, self.heads
401
- )
402
- attn_out = _dispatch_attention(query, key, value, attention_mask_1d)
403
- attn_out = attn_out.flatten(2, 3).to(query.dtype)
404
 
405
- proj = self.out_proj(attn_out) + mlp_out
406
- hidden_states = residual + gate.unsqueeze(1) * proj
407
 
408
  if hidden_states.dtype == torch.float16:
409
  hidden_states = hidden_states.clip(-65504, 65504)
410
  return hidden_states
411
 
412
 
413
- class NunchakuChromaTransformerBlock(nn.Module, NunchakuChromaTransformerBlockMixin):
414
- """
415
- Matches the checkpoint key layout under:
416
- transformer_blocks.<i>.{qkv_proj,qkv_proj_context,out_proj,out_proj_context,mlp_fc1,mlp_fc2,mlp_context_fc1,mlp_context_fc2,attn.*}
417
- """
418
-
419
- def __init__(
420
- self,
421
- *,
422
- dim: int,
423
- num_attention_heads: int,
424
- attention_head_dim: int,
425
- rank: int,
426
- precision: str,
427
- device,
428
- dtype,
429
- eps: float = 1e-6,
430
- ):
431
  super().__init__()
432
  from diffusers.models.transformers.transformer_chroma import ChromaAdaLayerNormZeroPruned
433
- from nunchaku.models.linear import SVDQW4A4Linear
434
 
435
- self.heads = int(num_attention_heads)
436
- self.head_dim = int(attention_head_dim)
437
- self.inner_dim = int(dim)
438
 
439
  self.norm1 = ChromaAdaLayerNormZeroPruned(dim).to(device=device, dtype=dtype)
440
  self.norm1_context = ChromaAdaLayerNormZeroPruned(dim).to(device=device, dtype=dtype)
441
-
442
  self.attn = _build_attn_norms(head_dim=self.head_dim, eps=eps, with_added=True, device=device, dtype=dtype)
443
 
444
- self.qkv_proj = SVDQW4A4Linear(
445
- in_features=dim,
446
- out_features=3 * dim,
447
- rank=rank,
448
- bias=True,
449
- precision=precision,
450
- torch_dtype=dtype,
451
- device=device,
452
- )
453
- self.qkv_proj_context = SVDQW4A4Linear(
454
- in_features=dim,
455
- out_features=3 * dim,
456
- rank=rank,
457
- bias=True,
458
- precision=precision,
459
- torch_dtype=dtype,
460
- device=device,
461
- )
462
- self.out_proj = SVDQW4A4Linear(
463
- in_features=dim,
464
- out_features=dim,
465
- rank=rank,
466
- bias=True,
467
- precision=precision,
468
- torch_dtype=dtype,
469
- device=device,
470
- )
471
- self.out_proj_context = SVDQW4A4Linear(
472
- in_features=dim,
473
- out_features=dim,
474
- rank=rank,
475
- bias=True,
476
- precision=precision,
477
- torch_dtype=dtype,
478
- device=device,
479
- )
480
 
481
  self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6).to(device=device, dtype=dtype)
482
  self.norm2_context = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6).to(device=device, dtype=dtype)
483
 
484
- self.mlp_fc1 = SVDQW4A4Linear(
485
- in_features=dim,
486
- out_features=4 * dim,
487
- rank=rank,
488
- bias=True,
489
- precision=precision,
490
- torch_dtype=dtype,
491
- device=device,
492
- )
493
- self.mlp_fc2 = SVDQW4A4Linear(
494
- in_features=4 * dim,
495
- out_features=dim,
496
- rank=rank,
497
- bias=True,
498
- precision=precision,
499
- torch_dtype=dtype,
500
- device=device,
501
- )
502
-
503
- self.mlp_context_fc1 = SVDQW4A4Linear(
504
- in_features=dim,
505
- out_features=4 * dim,
506
- rank=rank,
507
- bias=True,
508
- precision=precision,
509
- torch_dtype=dtype,
510
- device=device,
511
- )
512
- self.mlp_context_fc2 = SVDQW4A4Linear(
513
- in_features=4 * dim,
514
- out_features=dim,
515
- rank=rank,
516
- bias=True,
517
- precision=precision,
518
- torch_dtype=dtype,
519
- device=device,
520
- )
521
- # Chroma int4 compatibility:
522
- # the context-stream MLP down-projection also needs the signed
523
- # activation path for stable parity and image quality.
524
  self.mlp_context_fc2.act_unsigned = False
525
 
526
- self.norm_q = self.attn.norm_q
527
- self.norm_k = self.attn.norm_k
528
- self.norm_added_q = self.attn.norm_added_q
529
- self.norm_added_k = self.attn.norm_added_k
530
-
531
- def forward(
532
- self,
533
- hidden_states,
534
- encoder_hidden_states,
535
- temb,
536
- image_rotary_emb=None,
537
- attention_mask_1d=None,
538
- cpp_workspace: dict | None = None,
539
- cpp_mask: torch.Tensor | None = None,
540
- ):
541
- from nunchaku.ops.fused import fused_gelu_mlp, fused_qkv_norm_rottary
542
-
543
  temb_img, temb_txt = temb[:, :6], temb[:, 6:]
544
  norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(hidden_states, emb=temb_img)
545
  norm_encoder_hidden_states, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.norm1_context(
@@ -547,207 +273,228 @@ class NunchakuChromaTransformerBlock(nn.Module, NunchakuChromaTransformerBlockMi
547
  )
548
 
549
  rotary_img, rotary_txt = image_rotary_emb
550
- use_cpp = _should_use_cpp_additive_attn(
551
- attention_mask_1d=attention_mask_1d,
552
- hidden_states=norm_hidden_states,
553
- head_dim=self.head_dim,
554
- )
555
-
556
  txt_len = int(norm_encoder_hidden_states.shape[1])
557
- img_len = int(norm_hidden_states.shape[1])
558
-
559
- if use_cpp:
560
- assert cpp_workspace is not None and cpp_mask is not None
561
- txt_pad = _pad_to_multiple(txt_len, 256)
562
- q, k, v, _, out = _get_cpp_workspace_tensors(cpp_workspace)
563
- fused_qkv_norm_rottary(
564
- norm_hidden_states,
565
- self.qkv_proj,
566
- self.attn.norm_q,
567
- self.attn.norm_k,
568
- rotary_img,
569
- output=(q[:, :, txt_pad:], k[:, :, txt_pad:], v[:, :, txt_pad:]),
570
- attn_tokens=img_len,
571
- )
572
- fused_qkv_norm_rottary(
573
- norm_encoder_hidden_states,
574
- self.qkv_proj_context,
575
- self.attn.norm_added_q,
576
- self.attn.norm_added_k,
577
- rotary_txt,
578
- output=(q[:, :, :txt_pad], k[:, :, :txt_pad], v[:, :, :txt_pad]),
579
- attn_tokens=txt_len,
580
- )
581
- if _run_cpp_additive_attention(q, k, v, cpp_mask, out, context="dual-block cpp path"):
582
- context_attn_output = out[:, :txt_len, :]
583
- attn_output = out[:, txt_pad : txt_pad + img_len, :]
584
- else:
585
- use_cpp = False
586
-
587
- if not use_cpp:
588
- query, key, value = _fused_qkv_heads(
589
- norm_hidden_states, self.qkv_proj, self.attn.norm_q, self.attn.norm_k, rotary_img, self.heads
590
- )
591
- c_query, c_key, c_value = _fused_qkv_heads(
592
- norm_encoder_hidden_states,
593
- self.qkv_proj_context,
594
- self.attn.norm_added_q,
595
- self.attn.norm_added_k,
596
- rotary_txt,
597
- self.heads,
598
- )
599
 
600
- query = torch.cat([c_query, query], dim=1)
601
- key = torch.cat([c_key, key], dim=1)
602
- value = torch.cat([c_value, value], dim=1)
603
-
604
- attn_out = _dispatch_attention(query, key, value, attention_mask_1d)
605
- attn_out = attn_out.flatten(2, 3).to(query.dtype)
 
 
 
 
606
 
607
- context_attn_output, attn_output = attn_out.split_with_sizes([txt_len, attn_out.shape[1] - txt_len], dim=1)
 
 
608
 
609
  attn_output = self.out_proj(attn_output)
610
  context_attn_output = self.out_proj_context(context_attn_output)
611
 
612
  hidden_states = hidden_states + gate_msa.unsqueeze(1) * attn_output
613
- nh = self.norm2(hidden_states)
614
- nh = nh * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
615
- ff = fused_gelu_mlp(nh, self.mlp_fc1, self.mlp_fc2)
616
- hidden_states = hidden_states + gate_mlp.unsqueeze(1) * ff
617
 
618
  encoder_hidden_states = encoder_hidden_states + c_gate_msa.unsqueeze(1) * context_attn_output
619
- ne = self.norm2_context(encoder_hidden_states)
620
- ne = ne * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None]
621
- c_ff = fused_gelu_mlp(ne, self.mlp_context_fc1, self.mlp_context_fc2)
622
- encoder_hidden_states = encoder_hidden_states + c_gate_mlp.unsqueeze(1) * c_ff
623
 
624
  if encoder_hidden_states.dtype == torch.float16:
625
  encoder_hidden_states = encoder_hidden_states.clip(-65504, 65504)
626
  return encoder_hidden_states, hidden_states
627
 
628
 
 
 
 
 
 
629
  class NunchakuChromaTransformer2dModel(ModelMixin, ConfigMixin):
630
- """
631
- A Chroma-faithful transformer that loads the exact DeepCompressor/nunchaku-ext safetensors layout.
632
- """
633
 
634
- def __init__(
635
- self,
636
- *,
637
- config: dict[str, Any],
638
- rank: int,
639
- precision: str,
640
- device,
641
- dtype,
642
- ):
643
  super().__init__()
644
  from diffusers.models.transformers.transformer_chroma import (
645
  ChromaAdaLayerNormContinuousPruned,
646
  ChromaApproximator,
647
  ChromaCombinedTimestepTextProjEmbeddings,
648
  )
649
- from nunchaku.models.embeddings import NunchakuFluxPosEmbed
 
 
 
 
 
 
 
 
 
 
 
 
 
650
 
651
  self.register_to_config(
652
- patch_size=int(config["patch_size"]),
653
- in_channels=int(config["in_channels"]),
654
- out_channels=config.get("out_channels", None),
655
- num_layers=int(config["num_layers"]),
656
- num_single_layers=int(config["num_single_layers"]),
657
- attention_head_dim=int(config["attention_head_dim"]),
658
- num_attention_heads=int(config["num_attention_heads"]),
659
- joint_attention_dim=int(config["joint_attention_dim"]),
660
- axes_dims_rope=tuple(config.get("axes_dims_rope", (16, 56, 56))),
661
- approximator_num_channels=int(config.get("approximator_num_channels", 64)),
662
- approximator_hidden_dim=int(config.get("approximator_hidden_dim", 5120)),
663
- approximator_layers=int(config.get("approximator_layers", 5)),
664
  )
665
  self.nunchaku_precision = str(precision)
666
  self.nunchaku_rank = int(rank)
667
 
668
- patch_size = int(self.config.patch_size)
669
- in_channels = int(self.config.in_channels)
670
- out_channels = int(getattr(self.config, "out_channels", None) or in_channels)
671
- num_layers = int(self.config.num_layers)
672
- num_single_layers = int(self.config.num_single_layers)
673
- attention_head_dim = int(self.config.attention_head_dim)
674
- num_attention_heads = int(self.config.num_attention_heads)
675
- joint_attention_dim = int(self.config.joint_attention_dim)
676
- axes_dims_rope = tuple(self.config.axes_dims_rope)
677
-
678
  self.out_channels = out_channels
679
- self.inner_dim = num_attention_heads * attention_head_dim
 
680
 
681
- self.pos_embed = NunchakuFluxPosEmbed(dim=self.inner_dim, theta=10000, axes_dim=list(axes_dims_rope)).to(
682
- device=device, dtype=dtype
683
- )
684
 
685
- self.time_text_embed = ChromaCombinedTimestepTextProjEmbeddings(
686
- num_channels=int(self.config.approximator_num_channels) // 4,
 
687
  out_dim=3 * num_single_layers + 2 * 6 * num_layers + 2,
688
- ).to(device=device, dtype=dtype)
689
-
690
- self.distilled_guidance_layer = ChromaApproximator(
691
- in_dim=int(self.config.approximator_num_channels),
692
- out_dim=self.inner_dim,
693
- hidden_dim=int(self.config.approximator_hidden_dim),
694
- n_layers=int(self.config.approximator_layers),
695
- ).to(device=device, dtype=dtype)
696
-
697
- self.context_embedder = nn.Linear(joint_attention_dim, self.inner_dim, bias=True).to(device=device, dtype=dtype)
698
- self.x_embedder = nn.Linear(in_channels, self.inner_dim, bias=True).to(device=device, dtype=dtype)
699
-
700
- self.transformer_blocks = nn.ModuleList(
701
- [
702
- NunchakuChromaTransformerBlock(
703
- dim=self.inner_dim,
704
- num_attention_heads=num_attention_heads,
705
- attention_head_dim=attention_head_dim,
706
- rank=rank,
707
- precision=precision,
708
- device=device,
709
- dtype=dtype,
710
- )
711
- for _ in range(num_layers)
712
- ]
713
- )
714
- self.single_transformer_blocks = nn.ModuleList(
715
- [
716
- NunchakuChromaSingleTransformerBlock(
717
- dim=self.inner_dim,
718
- num_attention_heads=num_attention_heads,
719
- attention_head_dim=attention_head_dim,
720
- mlp_ratio=4.0,
721
- rank=rank,
722
- precision=precision,
723
- device=device,
724
- dtype=dtype,
725
- )
726
- for _ in range(num_single_layers)
727
- ]
728
- )
729
-
730
- self.norm_out = ChromaAdaLayerNormContinuousPruned(
731
- self.inner_dim, self.inner_dim, elementwise_affine=False, eps=1e-6
732
- ).to(device=device, dtype=dtype)
733
- self.proj_out = nn.Linear(self.inner_dim, patch_size * patch_size * out_channels, bias=True).to(
734
- device=device, dtype=dtype
735
- )
736
 
737
  self.encoder_hid_proj = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
 
739
  @classmethod
740
  def from_pretrained(
741
- cls,
742
- pretrained_model_name_or_path: str | Path,
743
- *,
744
- device: str = "cuda",
745
- torch_dtype: Any = None,
746
- precision: str | None = None,
747
- rank: int | None = None,
748
- verbose: bool = True,
749
- return_report: bool = False,
750
  ):
 
 
 
751
  ckpt = Path(pretrained_model_name_or_path)
752
  if not ckpt.exists():
753
  raise FileNotFoundError(str(ckpt))
@@ -755,100 +502,75 @@ class NunchakuChromaTransformer2dModel(ModelMixin, ConfigMixin):
755
  if torch_dtype is None:
756
  torch_dtype = torch.bfloat16
757
 
758
- sd_raw, md = _load_safetensors_state_dict(ckpt, device="cpu")
759
- if "config" not in md:
760
- raise ValueError("Missing required safetensors metadata: 'config'")
761
- if "quantization_config" not in md:
762
- raise ValueError("Missing required safetensors metadata: 'quantization_config'")
763
 
764
  config = json.loads(md["config"])
765
- if config.get("_class_name", None) != "ChromaTransformer2DModel":
766
- raise ValueError(f"Unexpected config._class_name={config.get('_class_name')!r} (expected 'ChromaTransformer2DModel')")
767
-
768
- quant_cfg = json.loads(md["quantization_config"])
769
- from nunchaku.utils import get_precision_from_quantization_config
770
-
771
- inferred_precision = get_precision_from_quantization_config(quant_cfg)
772
 
 
773
  sd = _convert_checkpoint_state_dict(sd_raw)
774
  inferred_rank = _infer_rank_from_converted_state_dict(sd)
775
 
776
  if precision is not None and str(precision) != str(inferred_precision):
777
- raise ValueError(
778
- f"precision mismatch: got precision={precision!r}, but checkpoint says {inferred_precision!r} "
779
- f"(from safetensors metadata 'quantization_config')."
780
- )
781
  if rank is not None and int(rank) != int(inferred_rank):
782
- raise ValueError(
783
- f"rank mismatch: got rank={int(rank)}, but checkpoint implies rank={int(inferred_rank)} "
784
- f"(from '*.proj_down' tensors)."
785
- )
786
-
787
- model = cls(
788
- config=config,
789
- rank=int(inferred_rank),
790
- precision=str(inferred_precision),
791
- device=torch.device(device),
792
- dtype=torch_dtype,
793
- )
794
 
795
- from nunchaku.models.transformers.utils import patch_scale_key
 
796
 
797
  patch_scale_key(model, sd)
798
-
799
  wanted = set(model.state_dict().keys())
800
  sd_filtered = {k: v for k, v in sd.items() if k in wanted}
801
  model.load_state_dict(sd_filtered, strict=True)
802
 
803
  if str(inferred_precision) == "int4":
804
- # Chroma int4 compatibility:
805
- # several dual-stream layers match the exported model much better
806
- # when the runtime consumes `smooth_factor_orig` instead of
807
- # `smooth_factor`. This is intentionally scoped to Chroma int4.
808
  for block in model.transformer_blocks:
809
- block.qkv_proj.smooth_factor.data.copy_(block.qkv_proj.smooth_factor_orig.data)
810
- block.qkv_proj_context.smooth_factor.data.copy_(block.qkv_proj_context.smooth_factor_orig.data)
811
- block.mlp_context_fc2.smooth_factor.data.copy_(block.mlp_context_fc2.smooth_factor_orig.data)
812
-
813
- _maybe_log(verbose, "[nunchaku.chroma] loaded:", str(ckpt))
814
- # _maybe_log(verbose, "[nunchaku.chroma] precision:", inferred_precision, "rank:", inferred_rank, "dtype:", torch_dtype)
815
- # _maybe_log(
816
- # verbose,
817
- # "[nunchaku.chroma] config.num_layers:",
818
- # int(config["num_layers"]),
819
- # "num_single_layers:",
820
- # int(config["num_single_layers"]),
821
- # )
822
 
823
  if return_report:
824
- return model, LoadReport(config=config, precision=str(inferred_precision), rank=int(inferred_rank))
825
  return model
826
 
 
 
 
 
827
  def forward(
828
- self,
829
- hidden_states,
830
- encoder_hidden_states=None,
831
- timestep=None,
832
- img_ids=None,
833
- txt_ids=None,
834
- attention_mask=None,
835
  joint_attention_kwargs: Optional[dict[str, Any]] = None,
836
- controlnet_block_samples=None,
837
- controlnet_single_block_samples=None,
838
- return_dict: bool = True,
839
- controlnet_blocks_repeat: bool = False,
840
  ):
841
  del controlnet_blocks_repeat
842
 
843
  from diffusers.models.modeling_outputs import Transformer2DModelOutput
844
- from nunchaku.models.embeddings import pack_rotemb
845
- from nunchaku.utils import pad_tensor
846
 
847
  if controlnet_block_samples is not None or controlnet_single_block_samples is not None:
848
  raise NotImplementedError("ControlNet is not supported in NunchakuChromaTransformer2dModel")
849
  if joint_attention_kwargs:
850
  raise NotImplementedError("joint_attention_kwargs is not supported in NunchakuChromaTransformer2dModel")
851
 
 
 
 
 
 
 
 
852
  if txt_ids.ndim == 3:
853
  txt_ids = txt_ids[0]
854
  if img_ids.ndim == 3:
@@ -858,83 +580,51 @@ class NunchakuChromaTransformer2dModel(ModelMixin, ConfigMixin):
858
  timestep = timestep.to(hidden_states.dtype) * 1000
859
  batch_size = int(hidden_states.shape[0])
860
 
861
- input_vec = self.time_text_embed(timestep)
862
- pooled_temb = self.distilled_guidance_layer(input_vec)
863
-
864
  encoder_hidden_states = self.context_embedder(encoder_hidden_states)
 
865
  ids = torch.cat((txt_ids, img_ids), dim=0)
866
  image_rotary_emb = self.pos_embed(ids)
867
 
868
  txt_tokens = int(encoder_hidden_states.shape[1])
869
  img_tokens = int(hidden_states.shape[1])
870
  attn_mask_1d = attention_mask
871
- assert image_rotary_emb.ndim == 6
872
- assert image_rotary_emb.shape[0] == 1
873
- assert image_rotary_emb.shape[1] == 1
874
- assert image_rotary_emb.shape[2] == 1 * (txt_tokens + img_tokens)
875
- image_rotary_emb = image_rotary_emb.reshape([1, txt_tokens + img_tokens, *image_rotary_emb.shape[3:]])
876
- rotary_emb_txt = pack_rotemb(pad_tensor(image_rotary_emb[:, :txt_tokens, ...], 256, 1))
877
- rotary_emb_img = pack_rotemb(pad_tensor(image_rotary_emb[:, txt_tokens:, ...], 256, 1))
878
- rotary_emb_single = pack_rotemb(pad_tensor(image_rotary_emb, 256, 1))
879
-
880
- rotary_emb_txt = _expand_batch_dim(rotary_emb_txt, batch_size)
881
- rotary_emb_img = _expand_batch_dim(rotary_emb_img, batch_size)
882
- rotary_emb_single = _expand_batch_dim(rotary_emb_single, batch_size)
883
-
884
- use_cpp_ws = _should_use_cpp_additive_attn(
885
- attention_mask_1d=attn_mask_1d,
886
- hidden_states=hidden_states,
887
- head_dim=int(self.config.attention_head_dim),
888
- )
889
- ws_dual: dict | None = None
890
- ws_single: dict | None = None
891
- mask_dual: torch.Tensor | None = None
892
- mask_single: torch.Tensor | None = None
893
- if use_cpp_ws:
894
- ws_dual, ws_single, mask_dual, mask_single = _prepare_cpp_context(
895
- self, hidden_states, attention_mask, txt_tokens=txt_tokens, img_tokens=img_tokens
896
- )
897
 
898
  num_layers = len(self.transformer_blocks)
899
  num_single = len(self.single_transformer_blocks)
900
  img_offset = 3 * num_single
901
  txt_offset = img_offset + 6 * num_layers
902
 
903
- for i, block in enumerate(self.transformer_blocks):
 
 
 
904
  img_mod = img_offset + 6 * i
905
  txt_mod = txt_offset + 6 * i
906
  temb = torch.cat(
907
- (pooled_temb[:, img_mod : img_mod + 6], pooled_temb[:, txt_mod : txt_mod + 6]),
908
- dim=1,
909
  )
910
  encoder_hidden_states, hidden_states = block(
911
- hidden_states=hidden_states,
912
- encoder_hidden_states=encoder_hidden_states,
913
- temb=temb,
914
- image_rotary_emb=(rotary_emb_img, rotary_emb_txt),
915
- attention_mask_1d=attn_mask_1d,
916
- cpp_workspace=ws_dual,
917
- cpp_mask=mask_dual,
918
  )
919
 
920
  hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1)
921
 
922
- for i, block in enumerate(self.single_transformer_blocks):
923
- start = 3 * i
924
- temb = pooled_temb[:, start : start + 3]
925
  hidden_states = block(
926
- hidden_states=hidden_states,
927
- temb=temb,
928
- image_rotary_emb=rotary_emb_single,
929
  attention_mask_1d=attn_mask_1d,
930
- cpp_workspace=ws_single,
931
- cpp_mask=mask_single,
932
  )
933
 
934
  hidden_states = hidden_states[:, encoder_hidden_states.shape[1] :, ...]
935
-
936
- temb = pooled_temb[:, -2:]
937
- hidden_states = self.norm_out(hidden_states, temb)
938
  output = self.proj_out(hidden_states)
939
 
940
  if not return_dict:
 
1
  from __future__ import annotations
2
 
3
+ import gc
4
  import json
 
5
  from dataclasses import dataclass
6
  from pathlib import Path
7
  from typing import Any, Optional
8
+ from warnings import warn
9
 
10
  import torch
11
+ import torch.nn.functional as F
12
  from diffusers.configuration_utils import ConfigMixin
13
  from diffusers.models.modeling_utils import ModelMixin
14
  from torch import nn
15
 
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  @dataclass(frozen=True)
18
  class LoadReport:
19
  config: dict[str, Any]
 
21
  rank: int
22
 
23
 
24
+ # ---------------------------------------------------------------------------
25
+ # Checkpoint key conversion
26
+ # ---------------------------------------------------------------------------
27
 
28
+ _KEY_RENAMES = [
29
+ (".lora_down", ".proj_down"),
30
+ (".lora_up", ".proj_up"),
31
+ (".smooth_orig", ".smooth_factor_orig"),
32
+ (".smooth", ".smooth_factor"),
33
+ ]
34
 
 
 
 
 
 
 
35
 
36
+ def _convert_checkpoint_key(k: str) -> str:
37
+ """Rename nunchaku-converter keys to runtime parameter names."""
38
+ for old, new in _KEY_RENAMES:
39
+ if old in k:
40
+ return k.replace(old, new)
 
 
 
 
 
 
 
 
 
 
 
41
  return k
42
 
43
 
44
  def _convert_checkpoint_state_dict(sd: dict[str, Any]) -> dict[str, Any]:
45
+ return {_convert_checkpoint_key(k): v for k, v in sd.items()}
46
 
47
 
48
  def _infer_rank_from_converted_state_dict(sd: dict[str, Any]) -> int:
 
49
  for k, v in sd.items():
50
  if k.endswith(".proj_down") and getattr(v, "ndim", None) == 2:
51
  return int(v.shape[1])
52
  raise ValueError("Cannot infer SVD rank from checkpoint (missing any '*.proj_down' tensors).")
53
 
54
 
55
+ # ---------------------------------------------------------------------------
56
+ # SVDQ linear helper
57
+ # ---------------------------------------------------------------------------
58
+
59
+
60
+ def _make_svdq_linear(in_features: int, out_features: int, *, rank: int, precision: str, device, dtype):
61
+ from nunchaku.models.linear import SVDQW4A4Linear
62
+
63
+ return SVDQW4A4Linear(
64
+ in_features=in_features,
65
+ out_features=out_features,
66
+ rank=rank,
67
+ bias=True,
68
+ precision=precision,
69
+ torch_dtype=dtype,
70
+ device=device,
71
+ )
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Attention norms helper
76
+ # ---------------------------------------------------------------------------
77
+
78
+
79
+ def _build_attn_norms(*, head_dim: int, eps: float, with_added: bool, device, dtype) -> nn.ModuleDict:
80
  from diffusers.models.normalization import RMSNorm
81
 
82
+ def _rms():
83
+ return RMSNorm(head_dim, eps=eps, elementwise_affine=True).to(device=device, dtype=dtype)
84
+
85
+ d: dict[str, nn.Module] = {"norm_q": _rms(), "norm_k": _rms()}
86
  if with_added:
87
+ d["norm_added_q"] = _rms()
88
+ d["norm_added_k"] = _rms()
89
+ return nn.ModuleDict(d)
90
+
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Pure-Python QKV + Norm + Rotary helper
94
+ # ---------------------------------------------------------------------------
95
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ def _apply_rotary_emb(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
98
+ """Apply rotary positional embedding.
99
 
100
+ Uses the same convention as diffusers' ``apply_rotary_emb`` from
101
+ ``diffusers.models.embeddings``.
 
102
 
103
+ Args:
104
+ x: (B, S, H, D) query or key tensor.
105
+ freqs_cis: (S, D/2, 2) or broadcastable rotary embedding (sin, cos stacked).
106
+ """
107
+ from diffusers.models.embeddings import apply_rotary_emb
108
+ return apply_rotary_emb(x, freqs_cis, sequence_dim=1)
109
 
110
 
111
+ def _qkv_norm_rotary(hidden_states, qkv_proj, norm_q, norm_k, rotary_emb, heads: int):
112
+ """Pure-Python replacement for fused_qkv_norm_rottary.
113
 
114
+ Steps: QKV projection chunk → RMSNorm(Q, K) → reshape to multi-head → rotary.
115
+ """
116
+ qkv = qkv_proj(hidden_states)
117
  query, key, value = qkv.chunk(3, dim=-1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
+ # (B, S, dim) → (B, S, H, D)
120
+ query = query.unflatten(-1, (heads, -1))
121
+ key = key.unflatten(-1, (heads, -1))
122
+ value = value.unflatten(-1, (heads, -1))
123
+
124
+ query = norm_q(query)
125
+ key = norm_k(key)
126
+
127
+ if rotary_emb is not None:
128
+ query = _apply_rotary_emb(query, rotary_emb)
129
+ key = _apply_rotary_emb(key, rotary_emb)
130
 
131
+ return query, key, value
 
 
132
 
 
 
 
 
133
 
134
+ # ---------------------------------------------------------------------------
135
+ # Attention dispatch
136
+ # ---------------------------------------------------------------------------
137
 
138
 
139
  def _dispatch_attention(query, key, value, attention_mask):
140
+ """Chroma attention dispatch (pure-Python path).
 
141
 
142
+ ``query``/``key``/``value`` are (B, S, H, D).
 
143
  """
144
+ from diffusers.models.attention_dispatch import dispatch_attention_fn
145
 
 
146
  if attention_mask is None:
147
  return dispatch_attention_fn(query, key, value, attn_mask=None, backend=None)
148
 
 
 
 
 
 
 
 
149
  if attention_mask.ndim == 2 and query.shape[0] == 1:
150
  b, s = attention_mask.shape
151
  if b != 1:
 
155
  f"Mask/sequence length mismatch: mask S={int(s)}, query S={int(query.shape[1])}, key S={int(key.shape[1])}"
156
  )
157
 
 
158
  m1 = attention_mask.to(dtype=query.dtype)[:, :, None, None].expand(
159
  query.shape[0], query.shape[1], query.shape[2], 1
160
  )
 
161
  d = int(query.shape[-1])
162
  scale = float(d) ** -0.5
 
 
 
163
  sqrt_d = float(d) ** 0.5
164
+ extra = 8
165
 
166
+ zeros_qk = m1.new_zeros((*m1.shape[:-1], extra - 1))
167
+ q_ext = torch.cat([query, m1 * sqrt_d, zeros_qk], dim=-1)
168
+ k_ext = torch.cat([key, m1, zeros_qk], dim=-1)
169
+ v_ext = torch.cat([value, value.new_zeros((*value.shape[:-1], extra))], dim=-1)
 
 
 
170
 
 
171
  try:
172
  out_ext = dispatch_attention_fn(q_ext, k_ext, v_ext, attn_mask=None, backend="_native_flash", scale=scale)
173
  except TypeError:
 
174
  out_ext = None
175
  if out_ext is not None:
176
  return out_ext[..., :d]
177
 
178
+ attn_mask_4d = _mask_to_4d(attention_mask)
 
179
  return dispatch_attention_fn(query, key, value, attn_mask=attn_mask_4d, backend="_native_efficient")
180
 
181
 
182
+ def _mask_to_4d(attention_mask):
183
+ """Expand a 2D mask to a full QK mask (outer product), matching diffusers Chroma semantics."""
184
+ if attention_mask is None:
185
+ return None
186
+ if attention_mask.ndim == 4:
187
+ return attention_mask
188
+ if attention_mask.ndim != 2:
189
+ raise ValueError(f"Unsupported attention_mask shape: {tuple(attention_mask.shape)}")
190
+ return attention_mask[:, None, None, :] * attention_mask[:, None, :, None]
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
+
193
+ # ===========================================================================
194
+ # Transformer blocks
195
+ # ===========================================================================
196
+
197
+
198
+ class NunchakuChromaSingleTransformerBlock(nn.Module):
199
+ def __init__(self, *, dim: int, num_attention_heads: int, attention_head_dim: int, mlp_ratio: float,
200
+ rank: int, precision: str, device, dtype, eps: float = 1e-6):
 
 
 
 
201
  super().__init__()
202
  from diffusers.models.transformers.transformer_chroma import ChromaAdaLayerNormZeroSinglePruned
 
203
 
204
+ self.heads = num_attention_heads
205
+ self.head_dim = attention_head_dim
206
+ mlp_hidden_dim = int(dim * mlp_ratio)
 
207
 
208
  self.norm = ChromaAdaLayerNormZeroSinglePruned(dim).to(device=device, dtype=dtype)
209
  self.attn = _build_attn_norms(head_dim=self.head_dim, eps=eps, with_added=False, device=device, dtype=dtype)
210
 
211
+ lin = dict(rank=rank, precision=precision, device=device, dtype=dtype)
212
+ self.qkv_proj = _make_svdq_linear(dim, 3 * dim, **lin)
213
+ self.out_proj = _make_svdq_linear(dim, dim, **lin)
214
+ self.mlp_fc1 = _make_svdq_linear(dim, mlp_hidden_dim, **lin)
215
+ self.mlp_fc2 = _make_svdq_linear(mlp_hidden_dim, dim, **lin)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
 
217
+ def forward(self, hidden_states, temb, image_rotary_emb=None, attention_mask_1d=None):
218
  residual = hidden_states
219
  norm_hidden_states, gate = self.norm(hidden_states, emb=temb)
220
 
221
+ mlp_hidden = self.mlp_fc1(norm_hidden_states)
222
+ mlp_hidden = F.gelu(mlp_hidden, approximate="tanh")
223
+ mlp_out = self.mlp_fc2(mlp_hidden)
224
 
225
+ query, key, value = _qkv_norm_rotary(
226
+ norm_hidden_states, self.qkv_proj, self.attn["norm_q"], self.attn["norm_k"],
227
+ image_rotary_emb, self.heads,
 
 
228
  )
229
+ attn_out = _dispatch_attention(query, key, value, attention_mask_1d)
230
+ attn_out = attn_out.flatten(2, 3).to(query.dtype)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
+ hidden_states = residual + gate.unsqueeze(1) * (self.out_proj(attn_out) + mlp_out)
 
233
 
234
  if hidden_states.dtype == torch.float16:
235
  hidden_states = hidden_states.clip(-65504, 65504)
236
  return hidden_states
237
 
238
 
239
+ class NunchakuChromaTransformerBlock(nn.Module):
240
+ def __init__(self, *, dim: int, num_attention_heads: int, attention_head_dim: int,
241
+ rank: int, precision: str, device, dtype, eps: float = 1e-6):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  super().__init__()
243
  from diffusers.models.transformers.transformer_chroma import ChromaAdaLayerNormZeroPruned
 
244
 
245
+ self.heads = num_attention_heads
246
+ self.head_dim = attention_head_dim
 
247
 
248
  self.norm1 = ChromaAdaLayerNormZeroPruned(dim).to(device=device, dtype=dtype)
249
  self.norm1_context = ChromaAdaLayerNormZeroPruned(dim).to(device=device, dtype=dtype)
 
250
  self.attn = _build_attn_norms(head_dim=self.head_dim, eps=eps, with_added=True, device=device, dtype=dtype)
251
 
252
+ lin = dict(rank=rank, precision=precision, device=device, dtype=dtype)
253
+ self.qkv_proj = _make_svdq_linear(dim, 3 * dim, **lin)
254
+ self.qkv_proj_context = _make_svdq_linear(dim, 3 * dim, **lin)
255
+ self.out_proj = _make_svdq_linear(dim, dim, **lin)
256
+ self.out_proj_context = _make_svdq_linear(dim, dim, **lin)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
258
  self.norm2 = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6).to(device=device, dtype=dtype)
259
  self.norm2_context = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6).to(device=device, dtype=dtype)
260
 
261
+ self.mlp_fc1 = _make_svdq_linear(dim, 4 * dim, **lin)
262
+ self.mlp_fc2 = _make_svdq_linear(4 * dim, dim, **lin)
263
+ self.mlp_context_fc1 = _make_svdq_linear(dim, 4 * dim, **lin)
264
+ self.mlp_context_fc2 = _make_svdq_linear(4 * dim, dim, **lin)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  self.mlp_context_fc2.act_unsigned = False
266
 
267
+ def forward(self, hidden_states, encoder_hidden_states, temb, image_rotary_emb=None,
268
+ attention_mask_1d=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  temb_img, temb_txt = temb[:, :6], temb[:, 6:]
270
  norm_hidden_states, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.norm1(hidden_states, emb=temb_img)
271
  norm_encoder_hidden_states, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.norm1_context(
 
273
  )
274
 
275
  rotary_img, rotary_txt = image_rotary_emb
 
 
 
 
 
 
276
  txt_len = int(norm_encoder_hidden_states.shape[1])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
 
278
+ query, key, value = _qkv_norm_rotary(
279
+ norm_hidden_states, self.qkv_proj, self.attn["norm_q"], self.attn["norm_k"], rotary_img, self.heads
280
+ )
281
+ c_query, c_key, c_value = _qkv_norm_rotary(
282
+ norm_encoder_hidden_states, self.qkv_proj_context,
283
+ self.attn["norm_added_q"], self.attn["norm_added_k"], rotary_txt, self.heads,
284
+ )
285
+ query = torch.cat([c_query, query], dim=1)
286
+ key = torch.cat([c_key, key], dim=1)
287
+ value = torch.cat([c_value, value], dim=1)
288
 
289
+ attn_out = _dispatch_attention(query, key, value, attention_mask_1d)
290
+ attn_out = attn_out.flatten(2, 3).to(query.dtype)
291
+ context_attn_output, attn_output = attn_out.split_with_sizes([txt_len, attn_out.shape[1] - txt_len], dim=1)
292
 
293
  attn_output = self.out_proj(attn_output)
294
  context_attn_output = self.out_proj_context(context_attn_output)
295
 
296
  hidden_states = hidden_states + gate_msa.unsqueeze(1) * attn_output
297
+ nh = self.norm2(hidden_states) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
298
+ mlp_h = F.gelu(self.mlp_fc1(nh), approximate="tanh")
299
+ hidden_states = hidden_states + gate_mlp.unsqueeze(1) * self.mlp_fc2(mlp_h)
 
300
 
301
  encoder_hidden_states = encoder_hidden_states + c_gate_msa.unsqueeze(1) * context_attn_output
302
+ ne = self.norm2_context(encoder_hidden_states) * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None]
303
+ mlp_e = F.gelu(self.mlp_context_fc1(ne), approximate="tanh")
304
+ encoder_hidden_states = encoder_hidden_states + c_gate_mlp.unsqueeze(1) * self.mlp_context_fc2(mlp_e)
 
305
 
306
  if encoder_hidden_states.dtype == torch.float16:
307
  encoder_hidden_states = encoder_hidden_states.clip(-65504, 65504)
308
  return encoder_hidden_states, hidden_states
309
 
310
 
311
+ # ===========================================================================
312
+ # Top-level model
313
+ # ===========================================================================
314
+
315
+
316
  class NunchakuChromaTransformer2dModel(ModelMixin, ConfigMixin):
317
+ """Chroma transformer that loads the DeepCompressor/nunchaku-ext safetensors layout."""
 
 
318
 
319
+ def __init__(self, *, config: dict[str, Any], rank: int, precision: str, device, dtype):
 
 
 
 
 
 
 
 
320
  super().__init__()
321
  from diffusers.models.transformers.transformer_chroma import (
322
  ChromaAdaLayerNormContinuousPruned,
323
  ChromaApproximator,
324
  ChromaCombinedTimestepTextProjEmbeddings,
325
  )
326
+ from diffusers.models.transformers.transformer_flux import FluxPosEmbed
327
+
328
+ patch_size = int(config["patch_size"])
329
+ in_channels = int(config["in_channels"])
330
+ out_channels = int(config.get("out_channels") or in_channels)
331
+ num_layers = int(config["num_layers"])
332
+ num_single_layers = int(config["num_single_layers"])
333
+ attention_head_dim = int(config["attention_head_dim"])
334
+ num_attention_heads = int(config["num_attention_heads"])
335
+ joint_attention_dim = int(config["joint_attention_dim"])
336
+ axes_dims_rope = tuple(config.get("axes_dims_rope", (16, 56, 56)))
337
+ approx_channels = int(config.get("approximator_num_channels", 64))
338
+ approx_hidden = int(config.get("approximator_hidden_dim", 5120))
339
+ approx_layers = int(config.get("approximator_layers", 5))
340
 
341
  self.register_to_config(
342
+ patch_size=patch_size, in_channels=in_channels, out_channels=out_channels,
343
+ num_layers=num_layers, num_single_layers=num_single_layers,
344
+ attention_head_dim=attention_head_dim, num_attention_heads=num_attention_heads,
345
+ joint_attention_dim=joint_attention_dim, axes_dims_rope=axes_dims_rope,
346
+ approximator_num_channels=approx_channels, approximator_hidden_dim=approx_hidden,
347
+ approximator_layers=approx_layers,
 
 
 
 
 
 
348
  )
349
  self.nunchaku_precision = str(precision)
350
  self.nunchaku_rank = int(rank)
351
 
 
 
 
 
 
 
 
 
 
 
352
  self.out_channels = out_channels
353
+ inner_dim = num_attention_heads * attention_head_dim
354
+ self.inner_dim = inner_dim
355
 
356
+ def _to(m):
357
+ return m.to(device=device, dtype=dtype)
 
358
 
359
+ self.pos_embed = _to(FluxPosEmbed(theta=10000, axes_dim=axes_dims_rope))
360
+ self.time_text_embed = _to(ChromaCombinedTimestepTextProjEmbeddings(
361
+ num_channels=approx_channels // 4,
362
  out_dim=3 * num_single_layers + 2 * 6 * num_layers + 2,
363
+ ))
364
+ self.distilled_guidance_layer = _to(ChromaApproximator(
365
+ in_dim=approx_channels, out_dim=inner_dim, hidden_dim=approx_hidden, n_layers=approx_layers,
366
+ ))
367
+ self.context_embedder = _to(nn.Linear(joint_attention_dim, inner_dim, bias=True))
368
+ self.x_embedder = _to(nn.Linear(in_channels, inner_dim, bias=True))
369
+
370
+ block_kw = dict(num_attention_heads=num_attention_heads, attention_head_dim=attention_head_dim,
371
+ rank=rank, precision=precision, device=device, dtype=dtype)
372
+ self.transformer_blocks = nn.ModuleList([
373
+ NunchakuChromaTransformerBlock(dim=inner_dim, **block_kw) for _ in range(num_layers)
374
+ ])
375
+ self.single_transformer_blocks = nn.ModuleList([
376
+ NunchakuChromaSingleTransformerBlock(dim=inner_dim, mlp_ratio=4.0, **block_kw) for _ in range(num_single_layers)
377
+ ])
378
+
379
+ self.norm_out = _to(ChromaAdaLayerNormContinuousPruned(inner_dim, inner_dim, elementwise_affine=False, eps=1e-6))
380
+ self.proj_out = _to(nn.Linear(inner_dim, patch_size * patch_size * out_channels, bias=True))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
  self.encoder_hid_proj = None
383
+ self.offload = False
384
+ self.transformer_block_offload_manager = None
385
+ self.single_transformer_block_offload_manager = None
386
+
387
+ # ---- offload management ------------------------------------------------
388
+
389
+ def set_offload(self, offload: bool, **kwargs):
390
+ from nunchaku.models.utils import CPUOffloadManager
391
+
392
+ if offload == self.offload:
393
+ return
394
+ self.offload = offload
395
+ if offload:
396
+ use_pin_memory = kwargs.get("use_pin_memory", True)
397
+ num_blocks_on_gpu = kwargs.get("num_blocks_on_gpu", 1)
398
+ on_gpu = [self.pos_embed, self.time_text_embed, self.distilled_guidance_layer,
399
+ self.context_embedder, self.x_embedder, self.norm_out, self.proj_out]
400
+ self.transformer_block_offload_manager = CPUOffloadManager(
401
+ self.transformer_blocks, use_pin_memory=use_pin_memory,
402
+ on_gpu_modules=on_gpu, num_blocks_on_gpu=num_blocks_on_gpu,
403
+ )
404
+ self.single_transformer_block_offload_manager = CPUOffloadManager(
405
+ self.single_transformer_blocks, use_pin_memory=use_pin_memory,
406
+ num_blocks_on_gpu=num_blocks_on_gpu,
407
+ )
408
+ else:
409
+ self.transformer_block_offload_manager = None
410
+ self.single_transformer_block_offload_manager = None
411
+ gc.collect()
412
+ if torch.cuda.is_available():
413
+ torch.cuda.empty_cache()
414
+
415
+ def to(self, *args, **kwargs):
416
+ device_arg_or_kwarg_present = any(isinstance(arg, torch.device) for arg in args) or "device" in kwargs
417
+ dtype_present = "dtype" in kwargs or any(isinstance(arg, torch.dtype) for arg in args)
418
+ for arg in args:
419
+ if isinstance(arg, str):
420
+ try:
421
+ torch.device(arg)
422
+ device_arg_or_kwarg_present = True
423
+ except RuntimeError:
424
+ pass
425
+ if dtype_present:
426
+ raise ValueError(
427
+ "Casting a quantized model to a new `dtype` is unsupported. Use the `torch_dtype` "
428
+ "argument in `from_pretrained` instead."
429
+ )
430
+ if getattr(self, "offload", False) and device_arg_or_kwarg_present:
431
+ warn("Skipping moving the model to GPU as offload is enabled", UserWarning)
432
+ return self
433
+ return super(type(self), self).to(*args, **kwargs)
434
+
435
+ def _iter_blocks(self, blocks, offload_manager):
436
+ """Yield (index, block) with transparent offload management."""
437
+ if offload_manager is not None:
438
+ compute_stream = torch.cuda.current_stream()
439
+ offload_manager.initialize(compute_stream)
440
+ for i in range(len(blocks)):
441
+ with torch.cuda.stream(compute_stream):
442
+ yield i, offload_manager.get_block(i)
443
+ offload_manager.step(compute_stream)
444
+ else:
445
+ yield from enumerate(blocks)
446
+
447
+ def _strip_accelerate_hooks_if_needed(self):
448
+ """Neutralise accelerate per-module hooks that break quantized kernels."""
449
+ try:
450
+ from accelerate.hooks import remove_hook_from_module
451
+ except ImportError:
452
+ return
453
+
454
+ children = [m for m in self.modules() if m is not self]
455
+ has_hooks = any(getattr(m, "_hf_hook", None) is not None for m in children)
456
+ if not has_hooks:
457
+ return
458
+
459
+ if not self._sequential_offload_warned:
460
+ self._sequential_offload_warned = True
461
+ warn(
462
+ "Detected accelerate sequential hooks on sub-modules of "
463
+ "NunchakuChromaTransformer2dModel. These are incompatible with "
464
+ "quantized CUDA kernels and will be removed. "
465
+ "Use transformer.set_offload(True) and "
466
+ "pipe._exclude_from_cpu_offload.append('transformer') instead.",
467
+ UserWarning,
468
+ )
469
+
470
+ for m in children:
471
+ if getattr(m, "_hf_hook", None) is not None:
472
+ remove_hook_from_module(m)
473
+
474
+ top_hook = getattr(self, "_hf_hook", None)
475
+ if top_hook is not None:
476
+ top_hook.execution_device = None
477
+ top_hook.offload = False
478
+ if hasattr(top_hook, "io_same_device"):
479
+ top_hook.io_same_device = False
480
+ if hasattr(top_hook, "weights_map"):
481
+ top_hook.weights_map = None
482
+
483
+ if not self.offload:
484
+ self.set_offload(True)
485
+
486
+ # ---- loading -----------------------------------------------------------
487
 
488
  @classmethod
489
  def from_pretrained(
490
+ cls, pretrained_model_name_or_path: str | Path, *,
491
+ device: str = "cuda", torch_dtype: Any = None, precision: str | None = None,
492
+ rank: int | None = None, pin_memory: bool | str = "auto",
493
+ offload: bool = False, verbose: bool = True, return_report: bool = False,
 
 
 
 
 
494
  ):
495
+ from nunchaku.models.transformers.utils import patch_scale_key
496
+ from nunchaku.utils import get_precision_from_quantization_config, load_state_dict_in_safetensors
497
+
498
  ckpt = Path(pretrained_model_name_or_path)
499
  if not ckpt.exists():
500
  raise FileNotFoundError(str(ckpt))
 
502
  if torch_dtype is None:
503
  torch_dtype = torch.bfloat16
504
 
505
+ sd_raw, md = load_state_dict_in_safetensors(ckpt, device="cpu", return_metadata=True)
506
+ for required_key in ("config", "quantization_config"):
507
+ if required_key not in md:
508
+ raise ValueError(f"Missing required safetensors metadata: {required_key!r}")
 
509
 
510
  config = json.loads(md["config"])
511
+ if config.get("_class_name") != "ChromaTransformer2DModel":
512
+ raise ValueError(f"Unexpected config._class_name={config.get('_class_name')!r}")
 
 
 
 
 
513
 
514
+ inferred_precision = get_precision_from_quantization_config(json.loads(md["quantization_config"]))
515
  sd = _convert_checkpoint_state_dict(sd_raw)
516
  inferred_rank = _infer_rank_from_converted_state_dict(sd)
517
 
518
  if precision is not None and str(precision) != str(inferred_precision):
519
+ raise ValueError(f"precision mismatch: {precision!r} vs checkpoint {inferred_precision!r}")
 
 
 
520
  if rank is not None and int(rank) != int(inferred_rank):
521
+ raise ValueError(f"rank mismatch: {rank} vs checkpoint {inferred_rank}")
 
 
 
 
 
 
 
 
 
 
 
522
 
523
+ model = cls(config=config, rank=inferred_rank, precision=str(inferred_precision),
524
+ device=torch.device(device), dtype=torch_dtype)
525
 
526
  patch_scale_key(model, sd)
 
527
  wanted = set(model.state_dict().keys())
528
  sd_filtered = {k: v for k, v in sd.items() if k in wanted}
529
  model.load_state_dict(sd_filtered, strict=True)
530
 
531
  if str(inferred_precision) == "int4":
 
 
 
 
532
  for block in model.transformer_blocks:
533
+ for attr in ("qkv_proj", "qkv_proj_context", "mlp_context_fc2"):
534
+ layer = getattr(block, attr, None)
535
+ if layer is not None and hasattr(layer, "smooth_factor_orig"):
536
+ layer.smooth_factor.data.copy_(layer.smooth_factor_orig.data)
537
+
538
+ if verbose:
539
+ print("[nunchaku.chroma] loaded:", str(ckpt))
540
+
541
+ model.set_offload(offload)
 
 
 
 
542
 
543
  if return_report:
544
+ return model, LoadReport(config=config, precision=str(inferred_precision), rank=inferred_rank)
545
  return model
546
 
547
+ # ---- forward -----------------------------------------------------------
548
+
549
+ _sequential_offload_warned: bool = False
550
+
551
  def forward(
552
+ self, hidden_states, encoder_hidden_states=None, timestep=None,
553
+ img_ids=None, txt_ids=None, attention_mask=None,
 
 
 
 
 
554
  joint_attention_kwargs: Optional[dict[str, Any]] = None,
555
+ controlnet_block_samples=None, controlnet_single_block_samples=None,
556
+ return_dict: bool = True, controlnet_blocks_repeat: bool = False,
 
 
557
  ):
558
  del controlnet_blocks_repeat
559
 
560
  from diffusers.models.modeling_outputs import Transformer2DModelOutput
 
 
561
 
562
  if controlnet_block_samples is not None or controlnet_single_block_samples is not None:
563
  raise NotImplementedError("ControlNet is not supported in NunchakuChromaTransformer2dModel")
564
  if joint_attention_kwargs:
565
  raise NotImplementedError("joint_attention_kwargs is not supported in NunchakuChromaTransformer2dModel")
566
 
567
+ self._strip_accelerate_hooks_if_needed()
568
+
569
+ if self.offload:
570
+ device = hidden_states.device
571
+ self.transformer_block_offload_manager.set_device(device)
572
+ self.single_transformer_block_offload_manager.set_device(device)
573
+
574
  if txt_ids.ndim == 3:
575
  txt_ids = txt_ids[0]
576
  if img_ids.ndim == 3:
 
580
  timestep = timestep.to(hidden_states.dtype) * 1000
581
  batch_size = int(hidden_states.shape[0])
582
 
583
+ pooled_temb = self.distilled_guidance_layer(self.time_text_embed(timestep))
 
 
584
  encoder_hidden_states = self.context_embedder(encoder_hidden_states)
585
+
586
  ids = torch.cat((txt_ids, img_ids), dim=0)
587
  image_rotary_emb = self.pos_embed(ids)
588
 
589
  txt_tokens = int(encoder_hidden_states.shape[1])
590
  img_tokens = int(hidden_states.shape[1])
591
  attn_mask_1d = attention_mask
592
+
593
+ freqs_cos, freqs_sin = image_rotary_emb
594
+ rotary_emb_txt = (freqs_cos[:txt_tokens], freqs_sin[:txt_tokens])
595
+ rotary_emb_img = (freqs_cos[txt_tokens:], freqs_sin[txt_tokens:])
596
+ rotary_emb_single = image_rotary_emb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
  num_layers = len(self.transformer_blocks)
599
  num_single = len(self.single_transformer_blocks)
600
  img_offset = 3 * num_single
601
  txt_offset = img_offset + 6 * num_layers
602
 
603
+ dual_mgr = self.transformer_block_offload_manager if self.offload else None
604
+ single_mgr = self.single_transformer_block_offload_manager if self.offload else None
605
+
606
+ for i, block in self._iter_blocks(self.transformer_blocks, dual_mgr):
607
  img_mod = img_offset + 6 * i
608
  txt_mod = txt_offset + 6 * i
609
  temb = torch.cat(
610
+ (pooled_temb[:, img_mod : img_mod + 6], pooled_temb[:, txt_mod : txt_mod + 6]), dim=1,
 
611
  )
612
  encoder_hidden_states, hidden_states = block(
613
+ hidden_states=hidden_states, encoder_hidden_states=encoder_hidden_states, temb=temb,
614
+ image_rotary_emb=(rotary_emb_img, rotary_emb_txt), attention_mask_1d=attn_mask_1d,
 
 
 
 
 
615
  )
616
 
617
  hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1)
618
 
619
+ for i, block in self._iter_blocks(self.single_transformer_blocks, single_mgr):
620
+ temb = pooled_temb[:, 3 * i : 3 * i + 3]
 
621
  hidden_states = block(
622
+ hidden_states=hidden_states, temb=temb, image_rotary_emb=rotary_emb_single,
 
 
623
  attention_mask_1d=attn_mask_1d,
 
 
624
  )
625
 
626
  hidden_states = hidden_states[:, encoder_hidden_states.shape[1] :, ...]
627
+ hidden_states = self.norm_out(hidden_states, pooled_temb[:, -2:])
 
 
628
  output = self.proj_out(hidden_states)
629
 
630
  if not return_dict: