Skip to content

Commit 6d8b373

Browse files
committed
fuse qkv/norm/rottary for z image
1 parent 3a3cc3d commit 6d8b373

3 files changed

Lines changed: 69 additions & 24 deletions

File tree

nunchaku/models/attention_processors/zimage.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from diffusers.models.attention_dispatch import dispatch_attention_fn
99
from diffusers.models.transformers.transformer_z_image import ZSingleStreamAttnProcessor
1010

11+
from ...ops.fused import fused_qkv_norm_rottary
12+
1113

1214
class NunchakuZSingleStreamAttnProcessor(ZSingleStreamAttnProcessor):
1315
"""
@@ -31,32 +33,18 @@ def __call__(
3133
"""
3234
Forward pass of the attention module. Adapted from diffusers.models.transformers.transformer_z_image.ZSingleStreamAttnProcessor#__call__.
3335
"""
34-
35-
qkv = attn.to_qkv(hidden_states)
36+
qkv = fused_qkv_norm_rottary(
37+
hidden_states,
38+
attn.to_qkv,
39+
attn.norm_q,
40+
attn.norm_k,
41+
freqs_cis,
42+
)
3643
query, key, value = qkv.chunk(3, dim=-1)
37-
3844
query = query.unflatten(-1, (attn.heads, -1))
3945
key = key.unflatten(-1, (attn.heads, -1))
4046
value = value.unflatten(-1, (attn.heads, -1))
4147

42-
# Apply Norms
43-
if attn.norm_q is not None:
44-
query = attn.norm_q(query)
45-
if attn.norm_k is not None:
46-
key = attn.norm_k(key)
47-
48-
# Apply RoPE
49-
def apply_rotary_emb(x_in: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
50-
with torch.amp.autocast("cuda", enabled=False):
51-
x = torch.view_as_complex(x_in.float().reshape(*x_in.shape[:-1], -1, 2))
52-
freqs_cis = freqs_cis.unsqueeze(2)
53-
x_out = torch.view_as_real(x * freqs_cis).flatten(3)
54-
return x_out.type_as(x_in) # todo
55-
56-
if freqs_cis is not None:
57-
query = apply_rotary_emb(query, freqs_cis)
58-
key = apply_rotary_emb(key, freqs_cis)
59-
6048
# Cast to correct dtype
6149
dtype = query.dtype
6250
query, key = query.to(dtype), key.to(dtype)

nunchaku/models/transformers/transformer_zimage.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import List, Optional
99

1010
import torch
11+
import torch.nn as nn
1112
from diffusers.models.attention import FeedForward
1213
from diffusers.models.attention_processor import Attention
1314
from diffusers.models.transformers.transformer_z_image import FeedForward as ZImageFeedForward
@@ -16,14 +17,36 @@
1617

1718
from nunchaku.models.unets.unet_sdxl import NunchakuSDXLFeedForward
1819

19-
from ...utils import get_precision
20+
from ...utils import get_precision, pad_tensor
2021
from ..attention import NunchakuBaseAttention
2122
from ..attention_processors.zimage import NunchakuZSingleStreamAttnProcessor
23+
from ..embeddings import pack_rotemb
2224
from ..linear import SVDQW4A4Linear
2325
from ..utils import fuse_linears
2426
from .utils import NunchakuModelLoaderMixin, patch_scale_key
2527

2628

29+
class NunchakuZImageRopeHook:
30+
31+
def __init__(self):
32+
self.packed_cache = {}
33+
34+
def __call__(self, module: nn.Module, input_args: tuple, input_kwargs: dict):
35+
freqs_cis: torch.Tensor = input_kwargs.get("freqs_cis", None)
36+
if freqs_cis is None:
37+
return None
38+
cache_key = freqs_cis.data_ptr()
39+
packed_freqs_cis = self.packed_cache.get(cache_key, None)
40+
if packed_freqs_cis is None:
41+
packed_freqs_cis = torch.view_as_real(freqs_cis).unsqueeze(3)
42+
packed_freqs_cis = torch.flip(packed_freqs_cis, dims=[-1])
43+
packed_freqs_cis = pack_rotemb(pad_tensor(packed_freqs_cis, 256, 1))
44+
self.packed_cache[cache_key] = packed_freqs_cis
45+
new_input_kwargs = input_kwargs.copy()
46+
new_input_kwargs["freqs_cis"] = packed_freqs_cis
47+
return input_args, new_input_kwargs
48+
49+
2750
class NunchakuZImageAttention(NunchakuBaseAttention):
2851
"""
2952
Nunchaku-optimized Attention module for ZImage with quantized and fused QKV projections.
@@ -198,6 +221,7 @@ def _convert_feed_forward(block_list: List[ZImageTransformerBlock]):
198221
for _, block in enumerate(block_list):
199222
block.feed_forward = _convert_z_image_ff(block.feed_forward)
200223

224+
self.skip_refiners = skip_refiners
201225
_patch_transformer_block(self.layers)
202226
if skip_refiners:
203227
_convert_feed_forward(self.noise_refiner)
@@ -207,6 +231,38 @@ def _convert_feed_forward(block_list: List[ZImageTransformerBlock]):
207231
_patch_transformer_block(self.context_refiner)
208232
return self
209233

234+
def register_rope_hook(self, rope_hook: NunchakuZImageRopeHook):
235+
self.rope_hook_handles = []
236+
for _, ly in enumerate(self.layers):
237+
self.rope_hook_handles.append(ly.attention.register_forward_pre_hook(rope_hook, with_kwargs=True))
238+
if not self.skip_refiners:
239+
for _, nr in enumerate(self.noise_refiner):
240+
self.rope_hook_handles.append(nr.attention.register_forward_pre_hook(rope_hook, with_kwargs=True))
241+
for _, cr in enumerate(self.context_refiner):
242+
self.rope_hook_handles.append(cr.attention.register_forward_pre_hook(rope_hook, with_kwargs=True))
243+
244+
def unregister_rope_hook(self):
245+
for h in self.rope_hook_handles:
246+
h.remove()
247+
self.rope_hook_handles.clear()
248+
249+
def forward(
250+
self,
251+
x: List[torch.Tensor],
252+
t,
253+
cap_feats: List[torch.Tensor],
254+
patch_size=2,
255+
f_patch_size=1,
256+
return_dict: bool = True,
257+
):
258+
rope_hook = NunchakuZImageRopeHook()
259+
self.register_rope_hook(rope_hook)
260+
try:
261+
return super().forward(x, t, cap_feats, patch_size, f_patch_size, return_dict)
262+
finally:
263+
self.unregister_rope_hook()
264+
del rope_hook
265+
210266
@classmethod
211267
@utils.validate_hf_hub_args
212268
def from_pretrained(cls, pretrained_model_name_or_path: str | os.PathLike[str], **kwargs):

nunchaku/ops/fused.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import torch
66
from torch.nn import RMSNorm
7+
from diffusers.models.normalization import RMSNorm as DiffUsersRMSNorm
78

89
from nunchaku.models.linear import SVDQW4A4Linear
910

@@ -124,8 +125,8 @@ def fused_qkv_norm_rottary(
124125
- C_in: input features
125126
- C_out: output features
126127
"""
127-
assert norm_q is None or isinstance(norm_q, RMSNorm)
128-
assert norm_k is None or isinstance(norm_k, RMSNorm)
128+
assert norm_q is None or isinstance(norm_q, RMSNorm) or (isinstance(norm_q, DiffUsersRMSNorm) and norm_q.bias is None)
129+
assert norm_k is None or isinstance(norm_k, RMSNorm) or (isinstance(norm_k, DiffUsersRMSNorm) and norm_k.bias is None)
129130

130131
batch_size, seq_len, channels = x.shape
131132
x = x.view(batch_size * seq_len, channels)

0 commit comments

Comments
 (0)