Repository navigation
Fix two crashes caused by constant_prop_pass - #24182
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24182
Note: Links to docs will display an error until the docs builds have been completed. ✅ No FailuresAs of commit 0d11cea with merge base 8739d28 ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@claude review |
|
Claude finished @shoumikhin's task in 3m 55s —— View job I found nothing blocking: both fixes look correct from reading the code, and the remaining points are minor test gaps and one comment whose stated reason may be wrong. Detailed reviewPR Review: #24182SummaryThe PR makes I couldn't execute anything here, because Python and pytest need approval in this environment. Every finding below comes from reading the code. Code Quality
Testing
RecommendationApprove. Nothing blocks merging. The |
14b48e7 to
1afc883
Compare
|
Thanks for the review. Here is what I did with each point.
Both tests still fail without their fixes. |
constant_prop_pass also folds nodes whose value is not a tensor, such as
the int from `.item()` on a buffer and the bools that `torch._check`
turns into `_assert_scalar` checks. That is useful while every user of
the value is folded too. But when a node that stays in the graph reads
such a value, the pass tried to turn it into a constant tensor input and
crashed:
n = self.n.item() # self.n is a buffer
torch._check(n >= 1)
return x[:n] * self.w[:n]
TypeError: cannot create weak reference to 'bool' object
Only tensors can become constant inputs. So now a non-tensor value that
a node outside the fold reads stays in the graph, together with the
non-tensor values it is computed from. Tensors computed from it still
fold, so `self.w[:n]` above still becomes one constant. Programs that
constant_prop_pass handled before are unchanged.
Tested on macOS arm64: a new test in exir/tests/test_passes.py (fails
before this change with the same kind of TypeError), and the rest of
that file.
When the pass folds a view of a parameter, such as `self.w.t()` or one
chunk of a packed weight, the result keeps `requires_grad=True`. The
pass runs under `torch.no_grad()`, but that does not clear the flag on
a view. For some views, such as a transposed one, `to_edge` then
copies the constant into a tensor that is no longer a leaf, and
deep-copying the program fails. Lowering to a backend deep-copies it,
so this breaks `to_edge_transform_and_lower`:
RuntimeError: Only Tensors created explicitly by the user (graph
leaves) support the deepcopy protocol at the moment.
An op can also return the parameter itself, for example
`self.w.to(torch.float32)` on a float32 parameter. The pass then
stores the `nn.Parameter` as a constant, and `to_edge` fails:
AttributeError: `_prop_tensor_constant0` is not a buffer
A folded constant must be a plain tensor that does not require grad,
so the pass now detaches each folded tensor.
Tested on macOS arm64: a new test in exir/tests/test_passes.py that
covers both cases and fails before this change, and the rest of that
file.
1afc883 to
0d11cea
Compare
|
@pytorchbot cherry-pick --onto release/1.6 -c critical |
Cherry picking #24182The cherry pick PR is at #24224 and it is recommended to link a critical cherry pick PR with an issue. Details for Dev Infra teamRaised by workflow job |
## What is wrong today Programs that go through `constant_prop_pass` can crash in two ways: one inside the pass, and one later when the program is lowered. **1. A folded value that is not a tensor.** The pass also folds values such as the int from `.item()` on a buffer, and the bools that `torch._check` turns into runtime checks. When a node that stays in the graph reads such a value, the pass tries to turn it into a constant tensor input and fails: ```python n = self.n.item() # self.n is a buffer torch._check(n >= 1) torch._check(n <= x.shape[0]) return x[:n] * self.w[:n] ``` ``` TypeError: cannot create weak reference to 'bool' object ``` **2. A folded parameter that still requires grad.** Folding `self.w.t()`, or one chunk of a packed weight, gives a view that still has `requires_grad=True`, because `torch.no_grad()` does not clear that flag on a view. For some views, such as a transposed one, `to_edge` then copies the constant into a non-leaf tensor, and lowering to a backend can fail when it deep-copies the program: ``` RuntimeError: Only Tensors created explicitly by the user (graph leaves) support the deepcopy protocol at the moment. ``` An op can also return the parameter itself, like `self.w.to(torch.float32)` on a float32 parameter. Then the pass stores the `nn.Parameter` as a constant, and `to_edge` fails with `AttributeError: ... is not a buffer`. ## What this change does Two commits, one per crash: 1. Only tensors can become constant inputs. So a non-tensor value that a node outside the fold reads now stays in the graph, together with the non-tensor values it is computed from. Tensors computed from it still fold: `self.w[:n]` still becomes one constant. Programs that `constant_prop_pass` handled before are unchanged. 2. A folded constant must be a plain tensor that does not require grad, so the pass now detaches every folded tensor. ## What was tested On macOS arm64: - Two new tests in `exir/tests/test_passes.py`, one per commit. Each fails before its commit and passes after: the first with `TypeError: cannot create weak reference to 'int' object`, the second with the `AttributeError` above. - The first covers a scalar read at runtime, one returned as an output, one whose first reader is folded, and one read only by folded nodes (it folds away). The program also lowers with `to_edge(...).to_executorch()`. - The second covers the transposed view and the `to` case, and checks that the output matches eager. - The rest of `exir/tests/test_passes.py`, `exir/emit/test/test_emit.py` and `exir/tests/test_quant_fusion_pass.py`. Five tests in those files fail the same way with and without this change, because they need the quantized and custom op libraries.
What is wrong today
Programs that go through
constant_prop_passcan crash in two ways: one inside the pass, and one later when the program is lowered.1. A folded value that is not a tensor. The pass also folds values such as the int from
.item()on a buffer, and the bools thattorch._checkturns into runtime checks. When a node that stays in the graph reads such a value, the pass tries to turn it into a constant tensor input and fails:2. A folded parameter that still requires grad. Folding
self.w.t(), or one chunk of a packed weight, gives a view that still hasrequires_grad=True, becausetorch.no_grad()does not clear that flag on a view. For some views, such as a transposed one,to_edgethen copies the constant into a non-leaf tensor, and lowering to a backend can fail when it deep-copies the program:An op can also return the parameter itself, like
self.w.to(torch.float32)on a float32 parameter. Then the pass stores thenn.Parameteras a constant, andto_edgefails withAttributeError: ... is not a buffer.What this change does
Two commits, one per crash:
self.w[:n]still becomes one constant. Programs thatconstant_prop_passhandled before are unchanged.What was tested
On macOS arm64:
exir/tests/test_passes.py, one per commit. Each fails before its commit and passes after: the first withTypeError: cannot create weak reference to 'int' object, the second with theAttributeErrorabove.to_edge(...).to_executorch().tocase, and checks that the output matches eager.exir/tests/test_passes.py,exir/emit/test/test_emit.pyandexir/tests/test_quant_fusion_pass.py. Five tests in those files fail the same way with and without this change, because they need the quantized and custom op libraries.