Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 10 additions & 11 deletions src/diffusers/models/attention_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,13 +599,12 @@ def _prepare_for_flash_attn_or_sage_varlen_without_mask(
):
seqlens_q = torch.full((batch_size,), seq_len_q, dtype=torch.int32, device=device)
seqlens_k = torch.full((batch_size,), seq_len_kv, dtype=torch.int32, device=device)
cu_seqlens_q = torch.zeros(batch_size + 1, dtype=torch.int32, device=device)
cu_seqlens_k = torch.zeros(batch_size + 1, dtype=torch.int32, device=device)
cu_seqlens_q[1:] = torch.cumsum(seqlens_q, dim=0)
cu_seqlens_k[1:] = torch.cumsum(seqlens_k, dim=0)
max_seqlen_q = seqlens_q.max().item()
max_seqlen_k = seqlens_k.max().item()
return (seqlens_q, seqlens_k), (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k)
# Built with arange instead of cumsum(full(...)): inductor rewrites that pattern into
# `arange * fill_value`, which raises under dynamic shapes because the fill value is a
# symbolic sequence length. The lengths are uniform here, so arange is also cheaper.
cu_seqlens_q = torch.arange(0, (batch_size + 1) * seq_len_q, seq_len_q, dtype=torch.int32, device=device)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavioral edge case worth noting: torch.arange with step=0 raises RuntimeError: step must be nonzero, so a zero-length query/key sequence now errors where the previous cumsum(zeros) path returned an all-zero cu_seqlens. I don't think any dispatcher path can reach here with seq_len == 0, so this is informational rather than blocking — just flagging it since the PR claims strict numerical/behavioral equivalence.

cu_seqlens_k = torch.arange(0, (batch_size + 1) * seq_len_kv, seq_len_kv, dtype=torch.int32, device=device)
return (seqlens_q, seqlens_k), (cu_seqlens_q, cu_seqlens_k), (seq_len_q, seq_len_kv)


def _prepare_for_flash_attn_or_sage_varlen_with_mask(
Expand All @@ -616,13 +615,13 @@ def _prepare_for_flash_attn_or_sage_varlen_with_mask(
):
seqlens_q = torch.full((batch_size,), seq_len_q, dtype=torch.int32, device=device)
seqlens_k = attn_mask.sum(dim=1, dtype=torch.int32)
cu_seqlens_q = torch.zeros(batch_size + 1, dtype=torch.int32, device=device)
# Queries are uniform, so arange (see the no-mask helper: cumsum(full(...)) breaks inductor
# under dynamic shapes). Keys are data-dependent and keep the cumsum.
cu_seqlens_q = torch.arange(0, (batch_size + 1) * seq_len_q, seq_len_q, dtype=torch.int32, device=device)
cu_seqlens_k = torch.zeros(batch_size + 1, dtype=torch.int32, device=device)
cu_seqlens_q[1:] = torch.cumsum(seqlens_q, dim=0)
cu_seqlens_k[1:] = torch.cumsum(seqlens_k, dim=0)
max_seqlen_q = seqlens_q.max().item()
max_seqlen_k = seqlens_k.max().item()
return (seqlens_q, seqlens_k), (cu_seqlens_q, cu_seqlens_k), (max_seqlen_q, max_seqlen_k)
return (seqlens_q, seqlens_k), (cu_seqlens_q, cu_seqlens_k), (seq_len_q, max_seqlen_k)
Comment on lines -621 to +624

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not return max_seqlen_q like other?

Cc: @zhtmike do you have any comments here?

@zhtmike zhtmike Aug 26, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using seq_len_q is fine , since query is always a full sequence.

But according to the PR description, the mask path is still broken under torch compile?

Plus, I think we need a test to guard this

@ShivamShrirao ShivamShrirao Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. queries are always full length, so max_seqlen_q is seq_len_q, the caller already has it as an int.

The masked path compiles too and is not broken. The remaining .item() is on the key side only, and it doesn't block fullgraph=True. On torch 2.13.0+cu129 with this PR:

no-mask: PASS cu_q=[0, 77, 154] cu_k=[0, 77, 154] max=(77, 77)
masked:  PASS cu_q=[0, 77, 154] cu_k=[0, 77, 137] max=(77, 77)

The description only meant that the key-side .item() stays, since padded lengths are data-dependent.



def _prepare_for_flash_attn_or_sage_varlen(
Expand Down
Loading