Repository navigation
Commit 4747ab7
Run attention on MLX wherever the fused kernel can compute it (pytorch#22419)
### The problem
Attention on rank-3 tensors exports without complaint and then fails
when you run the model:
```
MLX execute failed: [scaled_dot_product_attention] input with shape (2,16,64) expected to be rank 4
```
PyTorch accepts rank 2, 3, 4 and 5 here. The fused kernel takes rank 4
only, and has other requirements besides. The handler matched the
operator by name and checked none of them, so it claimed calls the
kernel cannot run.
### Adapting the shapes that can be adapted
`ExpandDimsNode` → `SdpaNode` → `SqueezeNode`, as suggested in review,
so rank 3 keeps the fused kernel. Rank 2 needs it too and takes two
added dimensions.
The dimensions go at the front. For a rank-3 input the first dimension
is already the head one, so inserting in the middle moves it into the
batch slot, which misaligns masks and grouped heads.
Admitting a lower rank also reaches code that was only ever given rank
4. The grouped key and value unwrap looks for a repeat on dimension 1,
which is the head dimension at rank 4 and the key sequence below it, so
at rank 3 it would absorb a repeat carrying real keys. Without a mask
the result still matches, because a duplicated key and its value give
the same weighted sum, which is what makes this easy to miss. With a
causal mask it is off by 5.1. Both unwraps now run only at rank 4.
### Three shapes left to decompose
Decomposed calls still run on this backend, which the tests assert
rather than assume.
| Call | Today | Here |
| --- | --- | --- |
| Rank 5 and above | fails at execute | decomposes, 3.6e-07 |
| Unequal batch at rank 4 | fails at execute | decomposes, 2.4e-07 |
| Causal at rank 2 or 3, query shorter than key | not reachable |
decomposes, 4.2e-07 |
The causal one needs a word. Torch anchors a causal mask at the top left
and MLX at the bottom right, so the two disagree whenever the lengths
differ, silently. Rank 4 already reaches the kernel today and keeps its
current behaviour: fixing that needs either a change to what the speech
example computes or a fix to an off-by-one in the decomposed path, so it
is filed separately as pytorch#22426. What this change does is avoid opening
two more ranks onto the same trap.
### Declining a call is not free
Preservation from decomposition is requested per operator, so one
claimed call keeps the operator whole for a declined one in the same
graph, and that call is then neither lowered nor decomposed:
```
RuntimeError: Missing out variants: {'aten::scaled_dot_product_attention'}
```
Give the whole operator back when any of its calls is unsupported.
The framework does offer a per-node filter that would keep the other
calls fused. It is deliberately not used, and the docstring says why: on
an attention block that reshapes its output, with a declined call in the
same graph, that path fails to lower at all with `Cannot view a tensor
with shape (1,16,4,16) and strides (1024,16,256,1)`. It trades the
fusion cost for a hard failure on a very ordinary shape. The cost of the
coarser choice is real and written down: one declined call unfuses the
operator's other calls in that graph.
This half is not specific to attention. Two calls to `torch.roll` in one
graph, one supported and one not, already fail at execute today for the
same reason.
### Test plan
Fifteen partitioner tests. **Eleven fail before this change**, all
fifteen pass after. They assert on serialized nodes, so they check which
path a call took rather than only that it answered, and the rejection
cases also assert the work stayed on this backend, so they cannot pass
when nothing is delegated at all. A rank-3 case was added to the
operator suite too, which runs the compiled runtime.
Ran every combination on an Apple Silicon Mac against eager: plain,
causal, explicit scale, float mask, boolean mask, grouped-query
attention, batch size 1, float16 and bfloat16, ranks 2 through 5.
- rank 3 matches to 3.6e-07 and stays fused in all seven variants; rank
2 to 3.6e-07
- rank 5 decomposes at 3.6e-07 where it previously failed; unequal batch
from failing to 2.4e-07
- the grouped key case measured both ways: absorbed and correct at rank
4, and off by 5.1 at rank 3 with a causal mask if absorbed, which is
what the guard prevents
- a zero key head count used to divide by zero and abort the whole
export; it now declines that node
- a mixed supported and unsupported graph exports and runs; two rolls in
one graph go from failing at execute to matching eager exactly
Exported the speech example and compared it against the same export
without this change: **identical serialized node counts and an identical
greedy token sequence over twelve decode steps**, so that model is
unaffected.
Ran the neighbouring backend test files, 71 tests, all passing.
Co-authored-by: PyTorch Bot <pytorchbot@users.noreply.github.com>1 parent a6b115b commit 4747ab7
4 files changed
Lines changed: 395 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
133 | 133 | | |
134 | 134 | | |
135 | 135 | | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
136 | 148 | | |
137 | 149 | | |
138 | 150 | | |
| |||
157 | 169 | | |
158 | 170 | | |
159 | 171 | | |
| 172 | + | |
160 | 173 | | |
161 | 174 | | |
162 | 175 | | |
| |||
166 | 179 | | |
167 | 180 | | |
168 | 181 | | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
169 | 186 | | |
170 | 187 | | |
171 | 188 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
47 | 48 | | |
48 | 49 | | |
49 | 50 | | |
| |||
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
| 56 | + | |
55 | 57 | | |
56 | 58 | | |
57 | 59 | | |
58 | 60 | | |
| 61 | + | |
59 | 62 | | |
60 | 63 | | |
61 | 64 | | |
| |||
527 | 530 | | |
528 | 531 | | |
529 | 532 | | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
530 | 588 | | |
531 | 589 | | |
532 | 590 | | |
| |||
535 | 593 | | |
536 | 594 | | |
537 | 595 | | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
538 | 599 | | |
539 | 600 | | |
540 | | - | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
541 | 606 | | |
542 | 607 | | |
543 | 608 | | |
544 | 609 | | |
545 | 610 | | |
546 | | - | |
| 611 | + | |
| 612 | + | |
547 | 613 | | |
548 | 614 | | |
549 | 615 | | |
| |||
563 | 629 | | |
564 | 630 | | |
565 | 631 | | |
566 | | - | |
| 632 | + | |
567 | 633 | | |
568 | 634 | | |
569 | 635 | | |
| |||
572 | 638 | | |
573 | 639 | | |
574 | 640 | | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
575 | 662 | | |
576 | 663 | | |
577 | 664 | | |
| |||
593 | 680 | | |
594 | 681 | | |
595 | 682 | | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
596 | 702 | | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
597 | 706 | | |
598 | 707 | | |
599 | 708 | | |
600 | | - | |
601 | | - | |
602 | | - | |
603 | | - | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
604 | 713 | | |
605 | 714 | | |
606 | 715 | | |
607 | 716 | | |
608 | 717 | | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
609 | 726 | | |
610 | 727 | | |
611 | 728 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6275 | 6275 | | |
6276 | 6276 | | |
6277 | 6277 | | |
| 6278 | + | |
| 6279 | + | |
| 6280 | + | |
| 6281 | + | |
| 6282 | + | |
| 6283 | + | |
| 6284 | + | |
| 6285 | + | |
| 6286 | + | |
| 6287 | + | |
| 6288 | + | |
| 6289 | + | |
| 6290 | + | |
| 6291 | + | |
| 6292 | + | |
| 6293 | + | |
| 6294 | + | |
| 6295 | + | |
| 6296 | + | |
| 6297 | + | |
| 6298 | + | |
| 6299 | + | |
| 6300 | + | |
| 6301 | + | |
6278 | 6302 | | |
6279 | 6303 | | |
6280 | 6304 | | |
| |||
0 commit comments