Skip to content

Commit 03b4206

Browse files
authored
feat: fuse qkv/norm/rottary for z image (#868)
* fuse qkv/norm/rottary for z image * add doc string * style: make linter happy
1 parent 3a3cc3d commit 03b4206

3 files changed

Lines changed: 81 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: 65 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,39 @@
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+
Hook class for caching and substition of packed `freqs_cis` tensor.
32+
"""
33+
34+
def __init__(self):
35+
self.packed_cache = {}
36+
37+
def __call__(self, module: nn.Module, input_args: tuple, input_kwargs: dict):
38+
freqs_cis: torch.Tensor = input_kwargs.get("freqs_cis", None)
39+
if freqs_cis is None:
40+
return None
41+
cache_key = freqs_cis.data_ptr()
42+
packed_freqs_cis = self.packed_cache.get(cache_key, None)
43+
if packed_freqs_cis is None:
44+
packed_freqs_cis = torch.view_as_real(freqs_cis).unsqueeze(3)
45+
packed_freqs_cis = torch.flip(packed_freqs_cis, dims=[-1])
46+
packed_freqs_cis = pack_rotemb(pad_tensor(packed_freqs_cis, 256, 1))
47+
self.packed_cache[cache_key] = packed_freqs_cis
48+
new_input_kwargs = input_kwargs.copy()
49+
new_input_kwargs["freqs_cis"] = packed_freqs_cis
50+
return input_args, new_input_kwargs
51+
52+
2753
class NunchakuZImageAttention(NunchakuBaseAttention):
2854
"""
2955
Nunchaku-optimized Attention module for ZImage with quantized and fused QKV projections.
@@ -198,6 +224,7 @@ def _convert_feed_forward(block_list: List[ZImageTransformerBlock]):
198224
for _, block in enumerate(block_list):
199225
block.feed_forward = _convert_z_image_ff(block.feed_forward)
200226

227+
self.skip_refiners = skip_refiners
201228
_patch_transformer_block(self.layers)
202229
if skip_refiners:
203230
_convert_feed_forward(self.noise_refiner)
@@ -207,6 +234,43 @@ def _convert_feed_forward(block_list: List[ZImageTransformerBlock]):
207234
_patch_transformer_block(self.context_refiner)
208235
return self
209236

237+
def register_rope_hook(self, rope_hook: NunchakuZImageRopeHook):
238+
self.rope_hook_handles = []
239+
for _, ly in enumerate(self.layers):
240+
self.rope_hook_handles.append(ly.attention.register_forward_pre_hook(rope_hook, with_kwargs=True))
241+
if not self.skip_refiners:
242+
for _, nr in enumerate(self.noise_refiner):
243+
self.rope_hook_handles.append(nr.attention.register_forward_pre_hook(rope_hook, with_kwargs=True))
244+
for _, cr in enumerate(self.context_refiner):
245+
self.rope_hook_handles.append(cr.attention.register_forward_pre_hook(rope_hook, with_kwargs=True))
246+
247+
def unregister_rope_hook(self):
248+
for h in self.rope_hook_handles:
249+
h.remove()
250+
self.rope_hook_handles.clear()
251+
252+
def forward(
253+
self,
254+
x: List[torch.Tensor],
255+
t,
256+
cap_feats: List[torch.Tensor],
257+
patch_size=2,
258+
f_patch_size=1,
259+
return_dict: bool = True,
260+
):
261+
"""
262+
Adapted from diffusers.models.transformers.transformer_z_image.ZImageTransformer2DModel#forward
263+
264+
Register pre-forward hooks for caching and substitution of packed `freqs_cis` tensor for all attention submodules and unregister after forwarding is done.
265+
"""
266+
rope_hook = NunchakuZImageRopeHook()
267+
self.register_rope_hook(rope_hook)
268+
try:
269+
return super().forward(x, t, cap_feats, patch_size, f_patch_size, return_dict)
270+
finally:
271+
self.unregister_rope_hook()
272+
del rope_hook
273+
210274
@classmethod
211275
@utils.validate_hf_hub_args
212276
def from_pretrained(cls, pretrained_model_name_or_path: str | os.PathLike[str], **kwargs):

nunchaku/ops/fused.py

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

55
import torch
6+
from diffusers.models.normalization import RMSNorm as DiffUsersRMSNorm
67
from torch.nn import RMSNorm
78

89
from nunchaku.models.linear import SVDQW4A4Linear
@@ -124,8 +125,12 @@ 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 (
129+
norm_q is None or isinstance(norm_q, RMSNorm) or (isinstance(norm_q, DiffUsersRMSNorm) and norm_q.bias is None)
130+
)
131+
assert (
132+
norm_k is None or isinstance(norm_k, RMSNorm) or (isinstance(norm_k, DiffUsersRMSNorm) and norm_k.bias is None)
133+
)
129134

130135
batch_size, seq_len, channels = x.shape
131136
x = x.view(batch_size * seq_len, channels)

0 commit comments

Comments
 (0)