-
Notifications
You must be signed in to change notification settings - Fork 34.1k
[qwen2-vl] fix FA2 inference #39121
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[qwen2-vl] fix FA2 inference #39121
Changes from 6 commits
4301faa
2cb7f21
7823ffc
5640fe1
ebb46c3
f2248b7
c567fe6
3a3afe3
0e1d686
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -634,14 +634,17 @@ def forward( | |
| value_states = value_states.transpose(0, 1).unsqueeze(0) | ||
| max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max().item() | ||
|
|
||
| attention_mask = torch.full( | ||
| [1, 1, seq_length, key_states.shape[-2]], | ||
| torch.finfo(query_states.dtype).min, | ||
| device=query_states.device, | ||
| dtype=query_states.dtype, | ||
| ) | ||
| for i in range(1, len(cu_seqlens)): | ||
| attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = 0 | ||
| # Flash Attention 2 doesn't need a 4D mask and relies on `cu_seqlens/max_seqlen` | ||
| attention_mask = None | ||
| if self.config._attn_implementation != "flash_attention_2": | ||
| attention_mask = torch.full( | ||
| [1, 1, seq_length, key_states.shape[-2]], | ||
| torch.finfo(query_states.dtype).min, | ||
| device=query_states.device, | ||
| dtype=query_states.dtype, | ||
| ) | ||
| for i in range(1, len(cu_seqlens)): | ||
| attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = 0 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably also fixes #39067? Might be nice to change the flash attention integration path in the future to prioritize pos+cu_seq (even with an attention mask).
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it doesn't, that issue was there before refactor and I found it's related to FA2 using ragged inputs which the SDPA/eager path doesn't support in the same way I will comment under #39067 when I have a clear fix. Let's keep this PR open until then, better to fix it once and forever |
||
|
|
||
| attention_interface: Callable = eager_attention_forward | ||
| if self.config._attn_implementation != "eager": | ||
|
|
@@ -652,13 +655,13 @@ def forward( | |
| query_states, | ||
| key_states, | ||
| value_states, | ||
| attention_mask, | ||
| dropout=0.0 if not self.training else self.dropout, | ||
| attention_mask=attention_mask, | ||
| dropout=0.0, | ||
| scaling=self.scaling, | ||
| cu_seqlens_q=cu_seqlens, # pass cu seq lens for FA2 | ||
| cu_seqlens_k=cu_seqlens, | ||
| max_seqlen_q=max_seqlen, | ||
| max_seqlen_k=max_seqlen, | ||
| cu_seq_lens_q=cu_seqlens, # pass cu seq lens for FA2 | ||
| cu_seq_lens_k=cu_seqlens, | ||
| max_length_q=max_seqlen, | ||
| max_length_k=max_seqlen, | ||
| is_causal=False, | ||
| **kwargs, | ||
| ) | ||
|
|
@@ -928,6 +931,7 @@ def __init__(self, config: Qwen2_5OmniVisionEncoderConfig = None) -> None: | |
| self.scaling = self.head_dim**-0.5 | ||
| self.num_key_value_groups = 1 # needed for eager attention | ||
| self.config = config | ||
| self.is_causal = False | ||
|
|
||
| def forward( | ||
| self, | ||
|
|
@@ -943,18 +947,21 @@ def forward( | |
| query_states = apply_rotary_pos_emb_vision(query_states.unsqueeze(0), rotary_pos_emb).squeeze(0) | ||
| key_states = apply_rotary_pos_emb_vision(key_states.unsqueeze(0), rotary_pos_emb).squeeze(0) | ||
|
|
||
| attention_mask = torch.full( | ||
| [1, 1, seq_length, seq_length], | ||
| torch.finfo(query_states.dtype).min, | ||
| device=query_states.device, | ||
| dtype=query_states.dtype, | ||
| ) | ||
| for i in range(1, len(cu_seqlens)): | ||
| attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = 0 | ||
| # Flash Attention 2 doesn't need a 4D mask and relies on `cu_seqlens/max_seqlen` | ||
| attention_mask = None | ||
| if self.config._attn_implementation != "flash_attention_2": | ||
| attention_mask = torch.full( | ||
| [1, 1, seq_length, seq_length], | ||
| torch.finfo(value_states.dtype).min, | ||
| device=value_states.device, | ||
| dtype=value_states.dtype, | ||
| ) | ||
| for i in range(1, len(cu_seqlens)): | ||
| attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = 0 | ||
|
|
||
| query_states = query_states.transpose(0, 1).unsqueeze(0) # unsqueeze batch_dim | ||
| key_states = key_states.transpose(0, 1).unsqueeze(0) # unsqueeze batch_dim | ||
| value_states = value_states.transpose(0, 1).unsqueeze(0) # unsqueeze batch_dim | ||
| query_states = query_states.transpose(0, 1).unsqueeze(0) | ||
| key_states = key_states.transpose(0, 1).unsqueeze(0) | ||
| value_states = value_states.transpose(0, 1).unsqueeze(0) | ||
| max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max().item() | ||
|
|
||
| attention_interface: Callable = eager_attention_forward | ||
|
|
@@ -966,13 +973,13 @@ def forward( | |
| query_states, | ||
| key_states, | ||
| value_states, | ||
| attention_mask, | ||
| attention_mask=attention_mask, | ||
| dropout=0.0, | ||
| scaling=self.scaling, | ||
| cu_seqlens_q=cu_seqlens, # pass cu seq lens for FA2 | ||
| cu_seqlens_k=cu_seqlens, | ||
| max_seqlen_q=max_seqlen, | ||
| max_seqlen_k=max_seqlen, | ||
| cu_seq_lens_q=cu_seqlens, # pass cu seq lens for FA2 | ||
| cu_seq_lens_k=cu_seqlens, | ||
| max_length_q=max_seqlen, | ||
| max_length_k=max_seqlen, | ||
| is_causal=False, | ||
| **kwargs, | ||
| ) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unsure about this since we would allow
cu_seqandmax_seqonly but on most models we also have RoPE so it's breaking those models silently if we eff up not passingposition_ids(due to RoPE positions being bound toposition_idsas well). We should imo add at least a warning on only varlen kwargs to give some discretion here.On another note, what do the integration tests use? Are they still working as expected 👀 seems a bit sus
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The integration tests always use
position_idswhich is the firstis_fa2_with_position_ids, I just copied it from existing code and moved up here. The second case is added for Qwen only, afaik no other model passes pre-computedcu_lensform attention layersIn qwen we don't need any position ids, because they are 3D and won't help at all in inferring
cu_lensUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant the general integration tests like e.g.
transformers/tests/models/qwen2_vl/test_modeling_qwen2_vl.py
Line 507 in d53518c
For why, I'm concerned about the second case on qwen is future model additions and general usability, not the validity of qwen. For developers,
is_fa2_with_varlen_kwargsindicates that this suffices for varlen - before, we (unintentionally) checked for the existence of (correct flattened) position ids that RoPE models need when using varlen. Maybe #35941 helps for reference on what I mean.Imo, it would help to add at least comments that for varlen most models need correct flattened position ids (from e.g. a collator), especially RoPE models which make up the majority of newer models.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, am I right that users might be passing only
cu_lenswithout correctposition_ids? I believe that would be users' responsibility to take care that RoPE is applied correctly, but I will add a comment in code explaining it, sureIn slow integration tests we don't pass
position_ids, not that of I know. For most LLMs the fa2 path integration tests fallback to inferringcu_lensfrom the mask, and in Qwen theposition_idsare constructed on-the-fly during forward call. The model has a requirement for adding rope deltas on top of 3D positions and I don't think users would be doing all that manuallyUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Hmm, am I right that users might be passing only cu_lens without correct position_ids?" - Yes, not only users but possibly us as well because it's something that's harder to figure out when done wrong imo :D