Conversation
PyTorch 2.5 added the enable_gqa argument to torch.nn.functional.scaled_dot_product_attention, which is how modern LLMs express grouped-query attention. The converter never read it, so key and value kept fewer heads than query and conversion failed with "query, key, value must have a same batch dimension" on the fused iOS18 op, or with a matmul shape mismatch on the decomposition path. Parse the argument from both the torch script positional input and the torch.export keyword input, and repeat the key and value heads to match the query heads before either lowering runs.
Collaborator
|
This change looks good. Nice unit test coverage. CI: https://gitlab.com/coremltools1/coremltools/-/pipelines/2848162955 @Yigtwxx - Can you give us some background on this change? Does this change allow us to convert a specific model which we previously could not? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PyTorch 2.5 added the
enable_gqaargument totorch.nn.functional.scaled_dot_product_attention. It is how grouped-query attention is expressed, where key and value carry fewer heads than query.The converter never read that argument, so key and value kept their original head count and conversion failed:
Below iOS18, where the op is decomposed rather than fused, the same model fails with
ValueError: Incompatible dim 1 in shapes (2, 8) vs. (2, 2). Both TorchScript and torch.export hit this.Implementation
enable_gqais keyword-only in torch, so TorchScript serializes it as the eighth positional input while torch.export leaves it in the node kwargs. Both are now parsed._scaled_dot_product_flash_attention_for_cpuandcoreml::sdpahave no such argument and are unchanged.Key and value heads are then repeated along dimension -3 with
expand_dims+tile+reshape, reproducingtorch.repeat_interleave(x, repeats, dim=-3). This is deliberately not a plain tile: for two repeats, repeat-interleave giveshead0, head0, head1, head1while a tile giveshead0, head1, head0, head1, and the two produce different attention outputs.The expansion runs before the fused/decomposed branch, so the iOS18
scaled_dot_product_attentionop and the earlier decomposition are both covered by one code path.Nothing is emitted when
enable_gqais false, or when query and key/value already have the same number of heads, so models that do not use grouped-query attention convert to exactly the same program as before.The head count has to be known at conversion time, since it decides how many repeats to emit; batch size and sequence lengths may still be dynamic. Head counts that are not divisible, key and value head counts that disagree, a rank below 3, and a non-constant
enable_gqaeach raise with a specific message.Tests
Two tests are added to
TestScaledDotProductAttention:test_enable_gqa, over query/key-value head counts(8, 2),(6, 3),(4, 1)and(4, 4), both compute units, both backends, all frontends, and bothNoneandiOS18deployment targets, so the fused op and the decomposition are each exercised. It also asserts that onetileis inserted per key and value, and none at all when the head counts already match.(6, 3)is the smallest case where a tile and a repeat-interleave disagree numerically, so it pins the expansion semantics.test_enable_gqa_dynamic_shapes, which keeps the head counts static while batch size and both sequence lengths are dynamic.Both are skipped below torch 2.5.0. The ExecuTorch frontend decomposes
scaled_dot_product_attentionbefore it reaches the converter, so there the tests serve as numerical regression coverage rather than exercising this translation.I do not have an Apple device, so the on-device comparison in
run_compare_torchdid not run locally. To check the lowering I evaluated the emitted MIL program in NumPy against PyTorch eager for every head count and deployment target above, includingis_causaland dynamic shapes, on the TorchScript and torch.export frontends; all matched. The rest ofTestScaledDotProductAttentionreports the same results before and after this change.