Spaces:
Runtime error
Runtime error
File size: 9,055 Bytes
52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d 4504378 52ec15d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 | """
Paired with a good language model. Thanks!
FA3 is currently broken on Blackwell (sm_100) GPUs; this module detects that
at import time and falls back to PyTorch scaled-dot-product attention (SDPA)
automatically. The public class name / call signature are unchanged.
"""
import torch
import torch.nn.functional as F
from typing import Optional, Tuple
from diffusers.models.transformers.transformer_qwenimage import apply_rotary_emb_qwen
# ---------------------------------------------------------------------------
# FA3 availability check
# ---------------------------------------------------------------------------
def _is_blackwell() -> bool:
"""Return True when the current default CUDA device is an sm_100 (Blackwell) GPU."""
if not torch.cuda.is_available():
return False
cap = torch.cuda.get_device_capability()
# Blackwell β compute capability 10.x (sm_100)
return cap[0] >= 10
_fa3_available: bool = False
_fa3_unavailable_reason: str = ""
_flash_attn_func = None
if _is_blackwell():
_fa3_unavailable_reason = (
"FlashAttention-3 is not yet supported on Blackwell (sm_100) GPUs. "
"Falling back to scaled-dot-product attention (SDPA)."
)
else:
try:
from kernels import get_kernel
_k = get_kernel("kernels-community/vllm-flash-attn3")
_flash_attn_func = _k.flash_attn_func
_fa3_available = True
except Exception as e:
_fa3_unavailable_reason = (
"FlashAttention-3 via Hugging Face `kernels` is unavailable. "
f"Tried `get_kernel('kernels-community/vllm-flash-attn3')` and failed with:\n{e}\n"
"Falling back to scaled-dot-product attention (SDPA)."
)
# ---------------------------------------------------------------------------
# FA3 custom op (registered only when the kernel loaded successfully)
# ---------------------------------------------------------------------------
if _fa3_available:
@torch.library.custom_op("flash::flash_attn_func", mutates_args=())
def flash_attn_func(
q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, causal: bool = False
) -> torch.Tensor:
# _flash_attn_func returns (output, softmax_lse); we only need output.
output, _lse = _flash_attn_func(q, k, v, causal=causal)
return output
@flash_attn_func.register_fake
def _flash_attn_func_fake(q, k, v, causal=False):
# output shape mirrors q: (batch, seq_len, num_heads, head_dim)
return torch.empty_like(q).contiguous()
else:
# Provide a stub so call-sites that import the symbol don't break at
# module load; the processor will route around it at runtime.
def flash_attn_func(
q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, causal: bool = False
) -> torch.Tensor:
raise RuntimeError(_fa3_unavailable_reason)
# ---------------------------------------------------------------------------
# SDPA fallback helper
# ---------------------------------------------------------------------------
def _sdpa_attention(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
causal: bool = False,
) -> torch.Tensor:
"""
Scaled dot-product attention using torch.nn.functional.scaled_dot_product_attention.
Input / output layout: (B, S, H, D_h) β same as the FA3 kernel.
"""
# SDPA expects (B, H, S, D_h)
q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
out = F.scaled_dot_product_attention(q, k, v, is_causal=causal)
# Back to (B, S, H, D_h)
return out.transpose(1, 2)
# ---------------------------------------------------------------------------
# Attention processor
# ---------------------------------------------------------------------------
class QwenDoubleStreamAttnProcessorFA3:
"""
Attention processor for the Qwen double-stream architecture.
Preferred backend: vLLM FlashAttention-3 via Hugging Face ``kernels``.
Automatic fallback: PyTorch ``scaled_dot_product_attention`` (SDPA) when
FA3 is unavailable β e.g. on Blackwell (sm_100) GPUs where FA3 is not yet
supported, or when the ``kernels`` package is absent.
Notes / limitations
-------------------
- Arbitrary attention masks are not supported on the FA3 path. Pass
``attention_mask=None`` (the default) to stay on the fast path.
- On the SDPA path, ``attention_mask`` is likewise ignored; add explicit
support here if you need it.
- ``encoder_hidden_states`` (text stream) is required.
"""
_attention_backend: str # set in __init__ after capability detection
def __init__(self):
if _fa3_available:
self._attention_backend = "fa3"
else:
import warnings
warnings.warn(
f"QwenDoubleStreamAttnProcessorFA3: {_fa3_unavailable_reason}",
stacklevel=2,
)
self._attention_backend = "sdpa"
def _attend(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
causal: bool = False,
) -> torch.Tensor:
"""Dispatch to FA3 or SDPA depending on what is available."""
if self._attention_backend == "fa3":
return flash_attn_func(q, k, v, causal=causal)
return _sdpa_attention(q, k, v, causal=causal)
@torch.no_grad()
def __call__(
self,
attn,
hidden_states: torch.FloatTensor, # (B, S_img, D_model)
encoder_hidden_states: torch.FloatTensor = None, # (B, S_txt, D_model)
encoder_hidden_states_mask: torch.FloatTensor = None, # unused
attention_mask: Optional[torch.FloatTensor] = None, # unsupported on FA3 path
image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,
) -> Tuple[torch.FloatTensor, torch.FloatTensor]:
if encoder_hidden_states is None:
raise ValueError(
"QwenDoubleStreamAttnProcessorFA3 requires encoder_hidden_states (text stream)."
)
if attention_mask is not None and self._attention_backend == "fa3":
raise NotImplementedError(
"attention_mask is not supported on the FA3 path. "
"Either drop the mask or let the processor fall back to SDPA."
)
B, S_img, _ = hidden_states.shape
S_txt = encoder_hidden_states.shape[1]
# ---- QKV projections ----
img_q = attn.to_q(hidden_states)
img_k = attn.to_k(hidden_states)
img_v = attn.to_v(hidden_states)
txt_q = attn.add_q_proj(encoder_hidden_states)
txt_k = attn.add_k_proj(encoder_hidden_states)
txt_v = attn.add_v_proj(encoder_hidden_states)
# ---- Reshape to (B, S, H, D_h) ----
H = attn.heads
img_q = img_q.unflatten(-1, (H, -1))
img_k = img_k.unflatten(-1, (H, -1))
img_v = img_v.unflatten(-1, (H, -1))
txt_q = txt_q.unflatten(-1, (H, -1))
txt_k = txt_k.unflatten(-1, (H, -1))
txt_v = txt_v.unflatten(-1, (H, -1))
# ---- Q/K normalization ----
if getattr(attn, "norm_q", None) is not None:
img_q = attn.norm_q(img_q)
if getattr(attn, "norm_k", None) is not None:
img_k = attn.norm_k(img_k)
if getattr(attn, "norm_added_q", None) is not None:
txt_q = attn.norm_added_q(txt_q)
if getattr(attn, "norm_added_k", None) is not None:
txt_k = attn.norm_added_k(txt_k)
# ---- RoPE (Qwen variant) ----
if image_rotary_emb is not None:
img_freqs, txt_freqs = image_rotary_emb
img_q = apply_rotary_emb_qwen(img_q, img_freqs, use_real=False)
img_k = apply_rotary_emb_qwen(img_k, img_freqs, use_real=False)
txt_q = apply_rotary_emb_qwen(txt_q, txt_freqs, use_real=False)
txt_k = apply_rotary_emb_qwen(txt_k, txt_freqs, use_real=False)
# ---- Joint attention over [text, image] along sequence axis ----
q = torch.cat([txt_q, img_q], dim=1) # (B, S_txt + S_img, H, D_h)
k = torch.cat([txt_k, img_k], dim=1)
v = torch.cat([txt_v, img_v], dim=1)
out = self._attend(q, k, v, causal=False) # (B, S_total, H, D_h)
# ---- Back to (B, S, D_model) ----
out = out.flatten(2, 3).to(q.dtype)
# ---- Split text / image segments ----
txt_attn_out = out[:, :S_txt, :]
img_attn_out = out[:, S_txt:, :]
# ---- Output projections ----
img_attn_out = attn.to_out[0](img_attn_out)
if len(attn.to_out) > 1:
img_attn_out = attn.to_out[1](img_attn_out) # dropout if present
txt_attn_out = attn.to_add_out(txt_attn_out)
return img_attn_out, txt_attn_out |