Repository navigation
exir and MLX: fix folded-output clones, shared constants, and constant boolean masks - #24481
shoumikhin wants to merge 3 commits into
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24481
Note: Links to docs will display an error until the docs builds have been completed. ⏳ 5 Pending, 1 Unrelated FailureAs of commit 052ecdd with merge base a489dca ( BROKEN TRUNK - The following job failed but were present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 19m 19s —— View job Summary: Nothing blocks merging. The findings are three test gaps (the new per-tag grouping in Detailed reviewPR Review: #24481SummaryThe PR does two things:
Nothing blocks merging. The issues below are test gaps, one note for direct callers, and one optional simplification. Code Quality
Testing
Backward Compatibility
RecommendationApprove. Nothing blocks. The tests above are worth adding in this PR, most of all the case where a CPU op is the first user, because that is the path that re-tags the original placeholder. I did not run any tests: this job can't run Python. Everything above comes from reading the code. |
ef572cf to
f4ee3c0
Compare
f4ee3c0 to
460b697
Compare
|
Thanks for the review. The latest push changes 2 tests:
What I left as is:
About direct callers: a graph with no delegation tags now gets no copies. The docstring, the commit message and the PR description say this, so it will show up in the release notes. The only non-test caller in the tree runs after tagging. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 17m 26s —— View job Summary: Nothing blocks merging. There are three minor findings: one subtest of the new Detailed reviewPR Review: #24481SummaryThe PR makes two changes:
Nothing blocks merging. The findings are two test gaps and an Edge-only default in the same pass. That default predates the PR. Code Quality
Testing
RecommendationApprove. Nothing blocks. The I did not run any tests, because this job can't run Python. Everything above comes from reading the code. |
460b697 to
82171cc
Compare
|
Thanks, good catch on the test. The latest push fixes it:
What I left as is:
|
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 16m 19s —— View job Summary: Nothing blocks merging. There are two minor findings: no test has one Detailed reviewPR Review: #24481SummaryThe PR makes Testing
Backward Compatibility
RecommendationApprove. Nothing blocks. The test case is a few lines in the existing test. The dialect risk is latent, so a docstring line is enough. I did not run any tests, because this job can't run Python. Everything above comes from reading the code. |
82171cc to
1f5b7dd
Compare
|
Thanks. Both points are fixed in the latest push:
|
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 14m 9s —— View job Summary: This round found no new issues, and nothing blocks merging. Detailed reviewPR Review: #24481SummaryThe PR does two things:
This round found no new issues. Two points from earlier rounds came up again and are left out: choosing the clone by RecommendationApprove. Nothing blocks. I did not run any tests, because this job can't run Python. Everything above comes from reading the code. |
When constant_prop_pass folds a program output into a constant, it puts
a clone in front of the output. That clone was always an Edge dialect op
(exir_ops.edge.aten.clone or _clone_dim_order), even when the pass ran
on a program that was still ATen dialect, before to_edge.
Running the pass that early is sometimes needed, for example to fold a
constant op that is not Core ATen before to_edge_transform_and_lower
checks the graph. The Edge op in an ATen program then breaks any step
that verifies the program before to_edge. The MLX partitioner does that
in transform_for_pre_decomposition, so lowering fails with:
SpecViolationError: Operator '<EdgeOpOverload: aten.clone.default>:
schema = ...' is not an allowed operator type
The pass now checks the program's dialect. An Edge program gets the same
clone as before. Any other program, ATen or the training dialect that
torch.export.export returns, gets torch.ops.aten.clone.default, which
to_edge later turns into the right Edge op.
The new test folds a constant output in an ATen, a training and an Edge
program, and checks the clone op. For the ATen and training programs it
also checks that they verify and that to_edge turns the clone into the
Edge op.
When ops in different partitions, or in a partition and on the CPU, read the same constant, duplicate_constant_node gives every user but one its own copy of the placeholder, all with the original target. Two users in one partition then bring two placeholders of it into that delegate, and two users left on the CPU keep two in the owning program. A program that lists the same lifted tensor constant twice fails to unlift: ExportedProgram.module() raises "`<name>` is not a buffer". For a delegate, to_executorch raises it too, because it runs the delegate's program when it retraces the delegate call. For example, a constant read by an add and a mul in one delegate and by a sin on the CPU fails this way. So does a second partitioner that takes the users left on the CPU into one delegate. Users with the same delegation tag, or with none, now share one placeholder, and the first group keeps the original. So copies are made only for users in different programs. On a graph without tags none are made, and the tests that call duplicate_constant_node directly now tag their users. The new test lowers a constant that two delegates each read twice and that ops left in the owning program also read, through two to_backend calls, once with the ops left in the owning program reading it first and once last. It also lowers both delegates through one to_backend call. It checks that each program takes the constant once and that to_executorch passes.
The MLX partitioner takes aten.index.Tensor whatever the dtype of its
indices. The MLX runtime gathers only with integer indices, so x[mask]
with a boolean mask lowers, then fails when it runs:
MLX execute failed: [Gather] Boolean indices not supported
The mask is often a constant of the program, for example one that
constant_prop_pass folded before lowering. Its values are known at
lowering time, so the index handler now replaces it with its integer
positions, one index per mask dimension, and the gather stays in the
delegate.
Any other boolean index is refused, so the partitioner leaves it on the
CPU:
- a mask computed in the graph, such as x[x > 0]
- a mask that is a method input, or a buffer the program mutates
- a mask that is a parameter or a persistent buffer, because the
partitioner sees those as fake tensors and cannot read the mask
- a constant mask used together with other indices
- an all-false mask, because the result is empty and the MLX runtime
cannot write an empty output
The new tests run constant masks (1-D, 2-D, 3-D, a non-persistent
buffer, and a mask at axis 1), which must stay in one delegate. They
also run a mask computed in the graph, a mask passed as an input, a
constant mask used with another index, a persistent buffer, a buffer
the program mutates, and an all-false mask, none of which may lower the
index. A direct check on the fake program the partitioner sees confirms
that the handler itself refuses the persistent buffer and the all-false
mask, rather than failing on them.
1f5b7dd to
052ecdd
Compare
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 24m 41s —— View job Summary: Nothing blocks merging. There are two minor findings: an MLX builder comment still says shared constants are copied once per user, and the new constant-mask lowering makes one int64 index per mask dimension, so a multi-dimensional mask goes through a gather path that refuses dynamic dimensions. Detailed reviewPR Review: #24481SummaryThe PR makes three changes:
Nothing blocks merging. Both findings are minor, and both involve the MLX side. The mask handler ( Code Quality
Performance
RecommendationApprove. Nothing blocks. The comment is a quick fix. The mask layout can wait unless the target models have large multi-dimensional masks or dynamic shapes. I did not run any tests, because this job can't run Python. Everything above comes from reading the code. |
Problem
Some LeRobot policies fail with the MLX backend (Apple's MLX delegate):
SpecViolationError: Operator '<EdgeOpOverload: aten.clone.default> ...' is not an allowed operator type.to_executorchfails withAttributeError: `_prop_tensor_constant551` is not a buffer.[Gather] Boolean indices not supported.The clone error. When
constant_prop_passfolds a program output into a constant, it puts a clone in front of that output. The clone was always an Edge dialect op, even when the pass ran on an ATen program beforeto_edge. The MLX partitioner checks the ATen program intransform_for_pre_decomposition, and the Edge op fails that check.The "not a buffer" error. When ops in a delegate and ops outside it read the same constant,
duplicate_constant_nodegives every user but one its own copy of the placeholder. A program that lists a lifted constant twice cannot be unlifted, soto_executorchfails when it retraces the delegate call. Core ML hits the same bug earlier, asKeyError: '<buffer>'in coremltools.Fix
constant_prop_passchecks the program's dialect. An ATen or training program now getstorch.ops.aten.clone.default, whichto_edgelater turns into the right Edge op.duplicate_constant_nodegroups the users of a constant by delegation tag. Users with the same tag, or with none, share one placeholder, and only the other groups get a copy. Tests that call it directly now tag their users, since untagged users no longer get copies.constant_prop_pass) with its integer positions. Any other boolean index stays on the CPU.Testing
to_backendtest files have no new failures.KeyErrorbefore and lowers after.index.Tensor), a separate bug that Fix export of zero-stride constants and inputs, and of constants that delegates share #24404 fixes. With both, the full vla_jepa graph gets through MLX lowering andto_executorch.#24404 also edits
duplicate_constant_node, so whichever merges second needs a small rebase.