Repository navigation
[Refactor] Make some flash-attention utils more readable - #49071
Conversation
|
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. |
vasqu
left a comment
There was a problem hiding this comment.
Not completely opposed just wondering in combination with my other FA PR and how that fits in
| return pad_fn(out, indices_q, query_states.size(0), query_length) | ||
|
|
||
| return out | ||
| return out.view(query_states.size(0), -1, out.size(-2), out.size(-1)) |
There was a problem hiding this comment.
Thinking of #45153 and how this would play into your refactor here because we would need to support base FA via varlen (it's less accurate using varlen which is why I dont want to drop the base FA completely)
Also opinionated but how is this more readable? I know before we repeated quite a bit but now we have quite a few branches we need to remember like the last pad on the attention mask 😅 maybe we can adopt one function per scenario as in the other PR so we see what does what?
There was a problem hiding this comment.
we would need to support base FA via varlen (it's less accurate using varlen which is why I dont want to drop the base FA completely)
We still do? For that path the behavior has not changed, we still run base FA and exit if this is not varlen
Also opinionated but how is this more readable?
Well we just have a block to process the sequence defining variables, then the call, then the post processing. On the opposite I think it would make the code less readable to have 3 fused paths (4 with paged attention) and it will lead to repetitions inside the code. Plus we will still pass a lot of duplicate kwargs around. I think an if / else on the out is ok for users!
There was a problem hiding this comment.
We still do? For that path the behavior has not changed, we still run base FA and exit if this is not varlen
No I think you misunderstood, torch varlen only has FA varlen so there is no base FA and we need a workaround for that
There was a problem hiding this comment.
Ha ok! I don't understand how it relates to this PR though, sorry
There was a problem hiding this comment.
Hmm I was trying to be preemptive here: So we would introduce another prepare fa varlen path right? But yea no worries
There was a problem hiding this comment.
So we would introduce another prepare fa varlen path right
Depends on what arguments are given! I guess the torch varlen will have similar arguments, no?
There was a problem hiding this comment.
Yep we basically dummy create them because you can just infer them based on the bsz / seq len but should be easy to integrate 🫡
vasqu
left a comment
There was a problem hiding this comment.
Should be fine, I dont want to fight against it and I feel like im just becoming an opinionated slug :D
626eed2 to
2f83e48
Compare
9fe211e to
a052c9f
Compare
a052c9f to
45bfb18
Compare
45bfb18 to
f27d79a
Compare
f27d79a to
3409c56
Compare
CI recapDashboard: View test results in Grafana |
This PR refactors two functions present in
modeling_flash_attention_utils.py, not to change their behaviour but to make them more readable. It also fixes one bug._lazy_importsfa_fallback_versionto make the fallback system more readable: we fall back to the highest version of FA avaiableif/elif/elsechain in that function: it used to be that in theelse, there was anotherif/elif/elsebranch. Now everything is in the same branchis_npu_availableis True,is_flash_attention_x_availableif Falsetuple[Callable, ...]return type hint_flash_attention_forwardThis is the change that matters the most IMO. This function is mainly about preparing the sequence-defining attributes
(cu_seq_lens_q, cu_seq_lens_k, max_length_q, max_length_k)before calling the right flavor of flash. Currently, there are 4 cases:attention_mask, which we use to unpad the sequences and compute the sequence-defining attributespositions_idsflashand notflash_varlenCases 1, 2 and 3 only differ in the way they pre-process and post-process the input / output of
flash_varlen. Yet the currently, the main body of those cases is not shared: we duplicate somempscheck, the call to the function, and checking for tuple.Furthermore, the check for case 3. is always run. If
positions_idsis passed, the check is expensive: GPU ops + D2H sync (incompatible with cuda graph), while totally avoidable if we are in case 1 or 2.Finally, there is a "hole" in the checks: though we check if all sequence_defining attributes are passed, we only compute them from position_ids if the
cu_seqlensare missing: this means that if a user does not passmax_length, theflash_varlencall will crash.To fix all this:
flashand post-processing into 3 different blocks. The only special case is 4.: since there is not pre or post processing to do, we return early in the pre-processing blockis_fa_with_position_idsand only compute it case 1. and 2. fail.flash_varlenandtuplestripping blockflash_kwargstoflash_kwargs_fnbecause it's not a dict and it makes everything fit to one line when we call itSide note: the diff on GH is quite ugly, I would recommend checking it out with Gitlens or smt. We can see there is much less change there.