Repository navigation
Remove no-op copies so mutated inputs are reinplaced after to_edge_transform_and_lower - #23583
IlyasMoutawwakil wants to merge 10 commits into
Conversation
…ansform_and_lower to_edge_transform_and_lower functionalizes before decomposing, so index_copy_ into an input decomposes into index_put followed by aten.copy nodes into that input. Those copies take the input's sizes, strides and dtype, which already match, so they are no-ops, but they make the input look read after the update and keep the reinplace pass from rewriting index_put in place. RemoveNoopPass now drops them, so a static KV cache passed in as an input is updated with a single index_put_ and no copy or write-back, for XNNPACK and any other flow through to_edge_transform_and_lower. Authored with Claude Code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/23583
Note: Links to docs will display an error until the docs builds have been completed.
|
|
Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
This PR needs a
|
There was a problem hiding this comment.
🟡 Changes recommended
The copy-elision checks can incorrectly remove copies between quantized tensors with different quantization parameters.
1 open finding
What changed in this PR
Removes redundant functional copies so mutated inputs can be reinplaced after edge lowering.
Changes:
- Detects and removes matching
aten.copyoperations. - Adds copy-removal and KV-cache reinplacement tests.
| File | Description |
|---|---|
exir/passes/remove_noop_pass.py |
Adds copy elimination logic. |
exir/tests/test_passes.py |
Tests copy removal and dtype conversion. |
exir/tests/test_reinplace_pass.py |
Tests mutated KV-cache reinplacement. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
Tensor metadata cannot show that two tensors share quantization parameters: ExportPass dequantizes legacy quantized values when it builds fake tensors, so two qint8 tensors with different scales can look identical, and removing a copy between them would skip a requantization. Remove a copy only when it writes index_put(dst, ...) back into dst. index_put keeps its self's representation, quantization included, so that copy is a no-op, and it is the one that keeps a mutated input's update from being reinplaced. Authored with Claude Code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
The narrowly scoped rewrite is guarded by metadata checks and covered by focused and end-to-end tests.
0 open findings
1 resolved since last review
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
|
Thanks @IlyasMoutawwakil, could you update the branch? |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
|
@nil-is-all merged main, should be good now |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 38m 2s —— View job Request Changes: the new rule matches on the Detailed reviewPR Review: #23583SummaryThis PR makes
All findings come from reading the code. I couldn't run anything, because Python execution isn't allowed in this job. Code Quality
Testing
Backward Compatibility
RecommendationRequest Changes The output reordering that this PR newly exposes needs a test and a fix before it lands. The copy should also be matched through the graph signature rather than by checking for Review steps
Not done: I didn't run any tests or exports, because Python execution isn't allowed in this job. All findings come from reading the code. |
…amic shapes - `_insert_copy` put every inserted copy_ first and the other outputs after, and the output signature was then renamed by position. With one mutated input updated in place and another written back, their outputs swapped. Each copy_ now replaces the output it writes back, and each mutation spec is renamed from its own position. - The copy rule compares strides with `statically_known_true` instead of requiring static shapes, so an input with a dynamic dim is reinplaced too. - The comment names where the copy comes from: the write-back of every mutated user input that unlifting adds on each run_decompositions. - Tests: a copy of another tensor's update is kept, a dynamic dim, exact copy counts, and two mutated inputs where one is reinplaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review. Addressed in 97f6d52:
I kept the rule narrow ( |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
CI on 97f6d52, for the record:
None of these touch the changed code. I merged main to pick up the current CI setup. |
|
The failures on the latest run are all the same checkout error: |
|
@IlyasMoutawwakil could you update the branch with main? Recently we put up fixes to resolve CI failures due to dependencies and this might also get resolved if you rebase. Do give a ping after updating the branch. |
|
@nil-is-all done |

Summary
to_edge_transform_and_lowerrunsrun_decompositionsagain on an already functional program, and each run unlifts it, which writes every mutated user input back withcopy_(input, new_value). Refunctionalized, that isaten.copy(input, new_value). Upstream removes this copy for buffers but not for user inputs, so a static KV cache passed in as an input and updated withindex_copy_(copy(cache, index_put(cache, ...))) looks read after the update, and the reinplace pass can't rewriteindex_putin place.RemoveNoopPassnow dropsaten.copy(dst, index_put(dst, ...))when the strides match (compared withstatically_known_true, so dynamic dims are covered).index_putkeeps its self's representation, quantization included, so copying its result back into the same tensor is a no-op. Other copies are kept: their metadata alone can't show that two tensors share quantization parameters.With
run_reinplace_pass=True, the cache is then updated in place, with no copy of it.Making user inputs reinplaceable here exposed an ordering bug in
insert_write_back_for_buffers_pass:_insert_copyput every insertedcopy_first and the other outputs after, and the output signature was renamed by position, so with one mutated input updated in place and another written back, their outputs swapped. Eachcopy_now replaces the output it writes back.Test plan
test_redundant_copy_removal: the copy back of an index update is removed, also with a dynamic dim; a copy of another tensor's update, a plain copy and a casting copy are kept (exact counts).test_mutated_input_cache_reinplaced_after_transform_and_lower: a cache input updated withindex_copy_ends up as oneindex_put_with no copy, and the program returns the right values.test_reinplaced_and_written_back_inputs_keep_their_outputs: two mutated inputs, one reinplaced and one written back, keep their output positions and signature targets.Authored with Claude Code.
cc @JacobSzwejbka @angelayi @nil-is-all