replace enable_gpa with repeat_interleave for krea2 - #14523
Conversation
Krea 2 always attends with a text padding mask, and no fused SDPA kernel takes a mask together with mismatched query/key head counts — flash rejects the mask, the memory-efficient kernel rejects the mismatch. Attention therefore fell back to the math backend, which materializes the full [batch_size, num_heads, seq_len, seq_len] score matrix with no error or warning. Repeating the key/value heads in the processor computes the same thing and keeps the memory-efficient kernel eligible: at 1024x1024 (48/12 heads, 4608 tokens) one attention call goes from 9.02 GiB / 26.7 ms to 0.16 GiB / 4.1 ms. It also unpins the model from the native backend, since cuDNN, flash, FA3, sage and the hub kernels all raise on `enable_gqa`. Fixes #14518 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
sayakpaul
left a comment
There was a problem hiding this comment.
Thanks! Just flagging the following table:
Claude derived it by using the following code and running it against this branch and main:
from diffusers import Krea2Transformer2DModel
device, dtype = "cuda", torch.bfloat16
model = Krea2Transformer2DModel.from_pretrained(
"krea/Krea-2-Raw", subfolder="transformer", torch_dtype=dtype
).to(device)
model.eval()
# 1024x1024 image -> 128x128 latents -> 64x64 grid of patch-2 tokens;
# 512 text positions with only the first 77 valid (padding mask exercised).
torch.manual_seed(0)
batch, text_seq_len, grid = 1, 512, 64
image_seq_len = grid * grid
hidden_states = torch.randn(batch, image_seq_len, 64, device=device, dtype=dtype)
encoder_hidden_states = torch.randn(batch, text_seq_len, 12, 2560, device=device, dtype=dtype)
timestep = torch.tensor([0.7], device=device)
encoder_attention_mask = torch.zeros(batch, text_seq_len, dtype=torch.bool, device=device)
encoder_attention_mask[:, :77] = True
text_ids = torch.zeros(text_seq_len, 3, device=device)
image_ids = torch.zeros(grid, grid, 3, device=device)
image_ids[..., 1] = torch.arange(grid, device=device)[:, None]
image_ids[..., 2] = torch.arange(grid, device=device)[None, :]
position_ids = torch.cat([text_ids, image_ids.reshape(-1, 3)], dim=0)
with torch.no_grad():
out = model(
hidden_states=hidden_states,
encoder_hidden_states=encoder_hidden_states,
timestep=timestep,
position_ids=position_ids,
encoder_attention_mask=encoder_attention_mask,
).sampleI think this is acceptable because now the kernel invocations internally have changed.
|
|
||
| ### Grouped-query attention | ||
|
|
||
| Fewer key/value heads than query heads can be spelled two ways. Either pass `enable_gqa=True` to `dispatch_attention_fn` and let the backend broadcast, or repeat the key/value heads in the processor after RoPE and pass no flag (`transformer_krea2.py`, `transformer_nucleusmoe_image.py`): |
There was a problem hiding this comment.
Either pass
enable_gqa=Truetodispatch_attention_fn
Can we also supplement a model file here for the reference?
|
|
||
| - **Compatibility.** Most backends do not implement `enable_gqa` yet — flash, FA3, sage, cuDNN and the hub kernels raise on it, as does the context-parallel path. Grep `enable_gqa` in `attention_dispatch.py` for the current list rather than trusting this one; it changes as support lands. The flag limits the model to whichever backends still accept it, while repeating works on all of them. | ||
|
|
||
| - **Performance.** Turns on whether the model passes a mask. With a mask, no fused kernel takes a mask *and* mismatched head counts, so SDPA falls back to math and materializes the full `[batch_size, num_heads, seq_len_q, seq_len_kv]` score matrix — no error, no warning, only memory. Without a mask, flash broadcasts inside the kernel and the flag saves the key/value copy. Both effects scale with sequence length and head count, so measure at the model's real shape; `torch.backends.cuda.can_use_flash_attention(params, debug=True)` and `can_use_efficient_attention` print why a kernel was rejected, which is the fastest way to see which one you actually got. |
There was a problem hiding this comment.
Turns on whether the model passes a mask.
Do we mean performance is turned on?
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
commit message:
the commit message isn't entirely correct: cuDNN worked with gqa and mask, and still does. I checked if this is a regression for cuDNN but it performs the same without gqa and the interleaving this PR introduces. batch size 2 with mask |
|
Yes, cuDNN is very underrated IMO. |


fix #14518