Onise commited on
Commit
e944e94
·
verified ·
1 Parent(s): 99581ee

Update qwenimage/qwen_fa3_processor.py

Browse files
Files changed (1) hide show
  1. qwenimage/qwen_fa3_processor.py +85 -101
qwenimage/qwen_fa3_processor.py CHANGED
@@ -1,138 +1,116 @@
1
  """
2
- QwenDoubleStreamAttnProcessorFA3 - Optimized lazy loading + Blackwell support
3
  """
4
 
5
  import torch
6
- import torch.nn.functional as F
7
- import warnings
8
  from typing import Optional, Tuple
9
  from diffusers.models.transformers.transformer_qwenimage import apply_rotary_emb_qwen
10
 
11
- # Global lazy cache
12
- _fa3_available: bool = False
13
- _flash_attn_func = None
14
- _fa3_unavailable_reason: str = ""
15
-
16
-
17
- def _ensure_fa3_loaded():
18
- """Lazy-load FA3 kernel only the first time it's needed."""
19
- global _fa3_available, _flash_attn_func, _fa3_unavailable_reason
20
-
21
- if _fa3_available or _fa3_unavailable_reason:
22
- return
23
-
24
- # Blackwell (sm_100+) not supported yet by vllm-flash-attn3
25
- if torch.cuda.is_available():
26
- cap = torch.cuda.get_device_capability()
27
- if cap[0] >= 10:
28
- _fa3_unavailable_reason = (
29
- "FlashAttention-3 is not yet supported on Blackwell (sm_100) GPUs. "
30
- "Falling back to scaled-dot-product attention (SDPA)."
31
- )
32
- return
33
 
34
- try:
35
- from kernels import get_kernel
 
 
 
 
 
 
 
 
 
 
 
36
  _k = get_kernel("kernels-community/vllm-flash-attn3")
37
  _flash_attn_func = _k.flash_attn_func
38
- _fa3_available = True
39
- except Exception as e:
40
- _fa3_unavailable_reason = (
41
- f"FlashAttention-3 via Hugging Face kernels unavailable.\n"
42
- f"get_kernel('kernels-community/vllm-flash-attn3') failed with:\n{e}\n"
43
- "Falling back to SDPA."
44
- )
45
-
46
-
47
- # ---------------------------------------------------------------------------
48
- # Custom op (only registered once FA3 is confirmed available)
49
- # ---------------------------------------------------------------------------
50
- def _register_fa3_op():
51
- if not _fa3_available:
52
- return
53
 
54
- @torch.library.custom_op("flash::flash_attn_func", mutates_args=())
55
- def flash_attn_func(
56
- q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, causal: bool = False
57
- ) -> torch.Tensor:
58
- output, _ = _flash_attn_func(q, k, v, causal=causal)
59
- return output
60
 
61
- @flash_attn_func.register_fake
62
- def _fake(q, k, v, causal=False):
63
- return torch.empty_like(q).contiguous()
64
 
65
 
66
- # Only register if FA3 is actually available
67
- if _fa3_available:
68
- _register_fa3_op()
69
-
70
-
71
- # ---------------------------------------------------------------------------
72
- # SDPA fallback
73
- # ---------------------------------------------------------------------------
74
- def _sdpa_attention(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
75
- # (B, S, H, D_h) → SDPA format
76
  q = q.transpose(1, 2)
77
  k = k.transpose(1, 2)
78
  v = v.transpose(1, 2)
79
  out = F.scaled_dot_product_attention(q, k, v, is_causal=False)
80
- return out.transpose(1, 2)
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
 
83
- # ---------------------------------------------------------------------------
84
- # Main processor
85
- # ---------------------------------------------------------------------------
86
  class QwenDoubleStreamAttnProcessorFA3:
87
- def __init__(self):
88
- _ensure_fa3_loaded()
89
- self._backend = "fa3" if _fa3_available else "sdpa"
 
90
 
91
- if not _fa3_available:
92
- warnings.warn(f"QwenDoubleStreamAttnProcessorFA3: {_fa3_unavailable_reason}", stacklevel=2)
 
 
 
93
 
94
- def _attend(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
95
- if self._backend == "fa3":
96
- return torch.ops.flash.flash_attn_func(q, k, v, False) # custom op
97
- return _sdpa_attention(q, k, v)
98
 
99
- @torch.inference_mode()
 
 
 
 
100
  def __call__(
101
  self,
102
- attn,
103
- hidden_states: torch.FloatTensor,
104
- encoder_hidden_states: torch.FloatTensor = None,
105
- encoder_hidden_states_mask: torch.FloatTensor = None,
106
- attention_mask: Optional[torch.FloatTensor] = None,
107
- image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
108
  ) -> Tuple[torch.FloatTensor, torch.FloatTensor]:
109
  if encoder_hidden_states is None:
110
- raise ValueError("QwenDoubleStreamAttnProcessorFA3 requires encoder_hidden_states.")
111
-
112
- if attention_mask is not None and self._backend == "fa3":
113
- raise NotImplementedError("attention_mask not supported on FA3 path.")
114
 
115
  B, S_img, _ = hidden_states.shape
116
  S_txt = encoder_hidden_states.shape[1]
117
 
118
- # QKV projections
119
- img_q = attn.to_q(hidden_states)
120
  img_k = attn.to_k(hidden_states)
121
  img_v = attn.to_v(hidden_states)
122
- txt_q = attn.add_q_proj(encoder_hidden_states)
 
 
123
  txt_k = attn.add_k_proj(encoder_hidden_states)
124
  txt_v = attn.add_v_proj(encoder_hidden_states)
125
 
126
- # Reshape
127
  H = attn.heads
128
  img_q = img_q.unflatten(-1, (H, -1))
129
  img_k = img_k.unflatten(-1, (H, -1))
130
  img_v = img_v.unflatten(-1, (H, -1))
 
131
  txt_q = txt_q.unflatten(-1, (H, -1))
132
  txt_k = txt_k.unflatten(-1, (H, -1))
133
  txt_v = txt_v.unflatten(-1, (H, -1))
134
 
135
- # Norms
136
  if getattr(attn, "norm_q", None) is not None:
137
  img_q = attn.norm_q(img_q)
138
  if getattr(attn, "norm_k", None) is not None:
@@ -142,32 +120,38 @@ class QwenDoubleStreamAttnProcessorFA3:
142
  if getattr(attn, "norm_added_k", None) is not None:
143
  txt_k = attn.norm_added_k(txt_k)
144
 
145
- # RoPE
146
  if image_rotary_emb is not None:
147
  img_freqs, txt_freqs = image_rotary_emb
 
148
  img_q = apply_rotary_emb_qwen(img_q, img_freqs, use_real=False)
149
  img_k = apply_rotary_emb_qwen(img_k, img_freqs, use_real=False)
150
  txt_q = apply_rotary_emb_qwen(txt_q, txt_freqs, use_real=False)
151
  txt_k = apply_rotary_emb_qwen(txt_k, txt_freqs, use_real=False)
152
 
153
- # Joint attention
 
154
  q = torch.cat([txt_q, img_q], dim=1)
155
  k = torch.cat([txt_k, img_k], dim=1)
156
  v = torch.cat([txt_v, img_v], dim=1)
157
 
158
- out = self._attend(q, k, v)
 
 
 
159
 
160
- # Back to (B, S, D)
161
  out = out.flatten(2, 3).to(q.dtype)
162
 
163
- # Split & output projections
164
- txt_out = out[:, :S_txt, :]
165
- img_out = out[:, S_txt:, :]
166
 
167
- img_out = attn.to_out[0](img_out)
 
168
  if len(attn.to_out) > 1:
169
- img_out = attn.to_out[1](img_out)
170
 
171
- txt_out = attn.to_add_out(txt_out)
172
 
173
- return img_out, txt_out
 
1
  """
2
+ Paired with a good language model. Thanks!
3
  """
4
 
5
  import torch
 
 
6
  from typing import Optional, Tuple
7
  from diffusers.models.transformers.transformer_qwenimage import apply_rotary_emb_qwen
8
 
9
+ import torch.nn.functional as F
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ _flash_attn_func = None
12
+ _kernels_err = None
13
+
14
+ try:
15
+ from kernels import get_kernel
16
+ # Blackwell (sm_120+) is not yet supported by the vllm-flash-attn3 kernel binary
17
+ _cap = torch.cuda.get_device_capability() if torch.cuda.is_available() else (0, 0)
18
+ if _cap >= (12, 0):
19
+ _kernels_err = RuntimeError(
20
+ f"GPU compute capability sm_{_cap[0]}{_cap[1]} (Blackwell+) is not supported "
21
+ "by the kernels-community/vllm-flash-attn3 binary; using SDPA fallback."
22
+ )
23
+ else:
24
  _k = get_kernel("kernels-community/vllm-flash-attn3")
25
  _flash_attn_func = _k.flash_attn_func
26
+ except Exception as e:
27
+ _flash_attn_func = None
28
+ _kernels_err = e
 
 
 
 
 
 
 
 
 
 
 
 
29
 
 
 
 
 
 
 
30
 
31
+ def _fa3_available() -> bool:
32
+ return _flash_attn_func is not None
 
33
 
34
 
35
+ def _sdpa_fallback(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
36
+ # q/k/v: (B, S, H, D_h) → SDPA expects (B, H, S, D_h)
 
 
 
 
 
 
 
 
37
  q = q.transpose(1, 2)
38
  k = k.transpose(1, 2)
39
  v = v.transpose(1, 2)
40
  out = F.scaled_dot_product_attention(q, k, v, is_causal=False)
41
+ return out.transpose(1, 2) # back to (B, S, H, D_h)
42
+
43
+
44
+ @torch.library.custom_op("flash::flash_attn_func", mutates_args=())
45
+ def flash_attn_func(
46
+ q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, causal: bool = False
47
+ ) -> torch.Tensor:
48
+ outputs, lse = _flash_attn_func(q, k, v, causal=causal)
49
+ return outputs
50
+
51
+ @flash_attn_func.register_fake
52
+ def _(q, k, v, **kwargs):
53
+ return torch.empty_like(q).contiguous()
54
 
55
 
 
 
 
56
  class QwenDoubleStreamAttnProcessorFA3:
57
+ """
58
+ FA3-based attention processor for Qwen double-stream architecture.
59
+ Computes joint attention over concatenated [text, image] streams using vLLM FlashAttention-3
60
+ accessed via Hugging Face `kernels`.
61
 
62
+ Notes / limitations:
63
+ - General attention masks are not supported here (FA3 path). `is_causal=False` and no arbitrary mask.
64
+ - Optional windowed attention / sink tokens / softcap can be plumbed through if you use those features.
65
+ - Expects an available `apply_rotary_emb_qwen` in scope (same as your non-FA3 processor).
66
+ """
67
 
68
+ _attention_backend = "fa3" # for parity with your other processors, not used internally
 
 
 
69
 
70
+ def __init__(self):
71
+ if not _fa3_available():
72
+ print(f"[QwenDoubleStreamAttnProcessorFA3] FA3 unavailable, using SDPA fallback. Reason: {_kernels_err}")
73
+
74
+ @torch.no_grad()
75
  def __call__(
76
  self,
77
+ attn, # Attention module with to_q/to_k/to_v/add_*_proj, norms, to_out, to_add_out, and .heads
78
+ hidden_states: torch.FloatTensor, # (B, S_img, D_model) image stream
79
+ encoder_hidden_states: torch.FloatTensor = None, # (B, S_txt, D_model) text stream
80
+ encoder_hidden_states_mask: torch.FloatTensor = None, # unused in FA3 path
81
+ attention_mask: Optional[torch.FloatTensor] = None, # unused in FA3 path
82
+ image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, # (img_freqs, txt_freqs)
83
  ) -> Tuple[torch.FloatTensor, torch.FloatTensor]:
84
  if encoder_hidden_states is None:
85
+ raise ValueError("QwenDoubleStreamAttnProcessorFA3 requires encoder_hidden_states (text stream).")
86
+ if attention_mask is not None:
87
+ # FA3 kernel path here does not consume arbitrary masks; fail fast to avoid silent correctness issues.
88
+ raise NotImplementedError("attention_mask is not supported in this FA3 implementation.")
89
 
90
  B, S_img, _ = hidden_states.shape
91
  S_txt = encoder_hidden_states.shape[1]
92
 
93
+ # ---- QKV projections (image/sample stream) ----
94
+ img_q = attn.to_q(hidden_states) # (B, S_img, D)
95
  img_k = attn.to_k(hidden_states)
96
  img_v = attn.to_v(hidden_states)
97
+
98
+ # ---- QKV projections (text/context stream) ----
99
+ txt_q = attn.add_q_proj(encoder_hidden_states) # (B, S_txt, D)
100
  txt_k = attn.add_k_proj(encoder_hidden_states)
101
  txt_v = attn.add_v_proj(encoder_hidden_states)
102
 
103
+ # ---- Reshape to (B, S, H, D_h) ----
104
  H = attn.heads
105
  img_q = img_q.unflatten(-1, (H, -1))
106
  img_k = img_k.unflatten(-1, (H, -1))
107
  img_v = img_v.unflatten(-1, (H, -1))
108
+
109
  txt_q = txt_q.unflatten(-1, (H, -1))
110
  txt_k = txt_k.unflatten(-1, (H, -1))
111
  txt_v = txt_v.unflatten(-1, (H, -1))
112
 
113
+ # ---- Q/K normalization (per your module contract) ----
114
  if getattr(attn, "norm_q", None) is not None:
115
  img_q = attn.norm_q(img_q)
116
  if getattr(attn, "norm_k", None) is not None:
 
120
  if getattr(attn, "norm_added_k", None) is not None:
121
  txt_k = attn.norm_added_k(txt_k)
122
 
123
+ # ---- RoPE (Qwen variant) ----
124
  if image_rotary_emb is not None:
125
  img_freqs, txt_freqs = image_rotary_emb
126
+ # expects tensors shaped (B, S, H, D_h)
127
  img_q = apply_rotary_emb_qwen(img_q, img_freqs, use_real=False)
128
  img_k = apply_rotary_emb_qwen(img_k, img_freqs, use_real=False)
129
  txt_q = apply_rotary_emb_qwen(txt_q, txt_freqs, use_real=False)
130
  txt_k = apply_rotary_emb_qwen(txt_k, txt_freqs, use_real=False)
131
 
132
+ # ---- Joint attention over [text, image] along sequence axis ----
133
+ # Shapes: (B, S_total, H, D_h)
134
  q = torch.cat([txt_q, img_q], dim=1)
135
  k = torch.cat([txt_k, img_k], dim=1)
136
  v = torch.cat([txt_v, img_v], dim=1)
137
 
138
+ if _fa3_available():
139
+ out = flash_attn_func(q, k, v, causal=False) # out: (B, S_total, H, D_h)
140
+ else:
141
+ out = _sdpa_fallback(q, k, v) # out: (B, S_total, H, D_h)
142
 
143
+ # ---- Back to (B, S, D_model) ----
144
  out = out.flatten(2, 3).to(q.dtype)
145
 
146
+ # Split back to text / image segments
147
+ txt_attn_out = out[:, :S_txt, :]
148
+ img_attn_out = out[:, S_txt:, :]
149
 
150
+ # ---- Output projections ----
151
+ img_attn_out = attn.to_out[0](img_attn_out)
152
  if len(attn.to_out) > 1:
153
+ img_attn_out = attn.to_out[1](img_attn_out) # dropout if present
154
 
155
+ txt_attn_out = attn.to_add_out(txt_attn_out)
156
 
157
+ return img_attn_out, txt_attn_out