Repository navigation
OpenVINO: keep copies of constants in the partition, and copy inputs a whole program returns - #24506
shoumikhin wants to merge 12 commits into
Conversation
The partitioner moves a copy out of an OpenVINO partition when the copy would return one of the partition's inputs unchanged, because OpenVINO gives stale values for such an output. It also did this for copies of weights and buffers, but those never had the problem: they are stored inside the partition as constants, not passed in as inputs. Moving them out cost something. A buffer that is returned as a copy and also used inside the partition was stored twice in the program, such a buffer marked no_copy failed to export, and with dim order turned off a clone(memory_format=torch.preserve_format) of a buffer failed at run time in the portable clone kernel, which accepts only contiguous. Now the partitioner leaves copies of constants in place. Copies of real inputs, including mutated buffers, still move out, so such a clone of an input still fails in that kernel. execute_layer_test now returns the program, so a test can check what ended up outside the delegate.
When a program returns a copy of its input, like `return x.clone()`, the OpenVINO converter turns the copy into nothing, so the converted model returns its input directly. The CPU plugin does not write such an output, and it can overwrite the input, so the program gives stale values. The partitioner already avoids this by running such copies on the CPU. Lowering a whole program with to_backend, without a partitioner, has no CPU part to run them on, so it still gave wrong values. Now preprocess puts an OpenVINO Identity, which copies its input, between such an input and the output. OpenVINO's shared graph passes do not remove an operation that connects an input straight to an output, and the CPU plugin keeps the Identity as a real copy for every type and shape tried. A Convert to the same type is not safe: the CPU plugin removes it for some types and for dynamic shapes.
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24506
Note: Links to docs will display an error until the docs builds have been completed. ❗ 1 Active SEVsThere are 1 currently active SEVs. If your PR is affected, please view them below: ❌ 1 New Failure, 1 Unrelated FailureAs of commit e84a900 with merge base a3528d9 ( NEW FAILURE - The following job has failed:
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 23m 8s —— View job Request Changes: a channels-last copy of a constant now stays in the OpenVINO partition and comes back with its values permuted. The review also asks why the PR keeps two fixes for the same aliasing bug, and it lists gaps in the new tests. Detailed reviewPR Review: #24506SummaryThe PR keeps copies of parameters, unmutated buffers and lifted tensor constants inside OpenVINO partitions. It also puts an
Limits of this review: all four changed files were reviewed, by reading the code only, because Python could not run in this job. The checkout is shallow and Code Quality
Testing
Backward Compatibility
RecommendationRequest Changes The channels-last regression at
|
"OpenVINO: keep copies of constants inside the partition" kept every copy of a constant inside the OpenVINO partition. That is wrong in the cases below, and the copy now moves out to the CPU again for them: - The copy changes the dim order, like `buffer.clone(memory_format=torch.channels_last)`. OpenVINO writes every output in the default dim order, so the values came back permuted. - The CPU also reads the constant, for example through an operator the partitioner does not take. The CPU stores the constant anyway, and a copy kept in the partition made the partition store it a second time. In these cases the costs of a copy made on the CPU come back for it: a no_copy constant read by both sides fails to export, and with dim order turned off a preserve_format clone fails in the portable clone kernel. A copy left out that is not in the default dim order is still read wrong by ops inside the partition, the same limit inputs already have. The mutated buffer test now also runs the program and checks the copy against the call before it. The constant test runs a second input.
The Identity that preprocess puts between an input and an output fixes a problem seen only in the CPU plugin, and it was only checked there. Other devices now get the converted model unchanged, as before, so a device without Identity cannot start failing. This follows the CPU check preprocess already makes for the inference precision. The partitioner path still runs such copies on the CPU, which is exact on every device. The whole program test now covers float, int64 and bool inputs, a dynamic shape, and a second call on the same loaded method, and it loads the program from a file like the other tests. Its input also feeds a reshape, so the copied output is not the input's only user.
|
Thanks for the review. The latest push addresses it:
|
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 32m 19s —— View job Request Changes: a chain of copies of a constant can now be split across the OpenVINO partition boundary, so export fails with a dependency cycle. The tests also no longer cover the static-shape whole-program path, and on CPU they no longer check where copies of real inputs run. Detailed reviewPR Review: #24506SummaryThe PR keeps a returned copy of a constant inside the OpenVINO partition when every reader of the constant is in the partition and the copy keeps the default dim order. For the CPU device, it also puts an
Limits of this review: all four changed files were reviewed by reading the code only, because Python could not run in this job. The checkout is shallow and Code Quality
Testing
Backward Compatibility
RecommendationRequest Changes The cycle at
|
|
Thanks. The latest push addresses this:
|
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 25m 28s —— View job Approve: I found no problems in the partitioner or Detailed reviewPR Review: #24506SummaryThe PR keeps a returned copy of a constant inside the OpenVINO partition when OpenVINO can return it exactly. On the CPU device, it also puts an
Limits of this review: I reviewed all four changed files by reading the code only, because Python could not run in this job. The checkout is shallow and Code Quality
Testing
RecommendationApprove Nothing here blocks merging. Each fix above is a small change to the tests, so they can go into this PR or a follow-up.
|
A copy of a constant stayed in the partition when only the partition read the constant and the copy kept the default dim order. That still broke in these cases: - A chain like `buffer.clone().clone(memory_format=torch.channels_last)` whose result is also read inside. The outer copy left, but the inner one stayed, so the partition fed the copy taken out and also read it. Export failed with "Invalid partition, found dependency cycles". Now a copy that leaves takes the copies it reads with it. Such a model now exports as on main. A channels-last copy read inside is still read in the default dim order, the limit every partition input has. - A copy of an int64 or float64 constant. The CPU plugin computes 64-bit types in 32 bits, so the copy came back changed. Such copies now run on the CPU again. The test for copies that run outside covers both, and the case where the CPU also reads the constant now checks that atan runs there.
On the CPU, preprocess now copies an input that a partition returns unchanged, so a copy of an input that wrongly stayed in the partition would still give right values. test_copy_of_input_as_output now also checks that each copy runs on the CPU, which is the only safeguard on other devices. The whole program test runs each input type with static shapes too. For a static output the runtime binds ExecuTorch's own buffer, which is the case the bug hit, while a dynamic output goes through a tensor OpenVINO owns.
a3c5add to
a04ec62
Compare
Two copy tests repeated the block from execute_layer_test that writes the program to a file, loads it, runs inputs on one method and releases the file for Windows. A later fix to one copy, like the Windows one, was easy to miss in the others. The block is now BaseOpenvinoOpTest.execute_program, and all three places call it.
With dim order off, `buffer.clone(memory_format=torch.preserve_format)` stays aten.clone. Run on the CPU, as before this change, it failed in the portable clone kernel, which accepts only contiguous_format. Now it stays in the partition. test_copy_of_constant_as_output covers it, and the case fails on main with the kernel's error. The whole program test now skips devices other than the CPU, since preprocess adds the copy only there, so elsewhere it would not test the fix. And when the CPU also reads the constant, the test checks that atan runs there before it checks the clone, so a release that delegates atan fails with that reason.
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 26m 46s —— View job Approve: one minor problem is left: a copy of a constant that still runs on the CPU still makes the program store the constant twice, or fail to export with Detailed reviewPR Review: #24506SummaryThe PR keeps a returned copy of a constant inside the OpenVINO partition when OpenVINO can return it exactly. On the CPU device, it also puts an
Limits of this review: I reviewed all four changed files by reading the code only, because Python could not run in this job. The checkout is shallow and Code Quality
RecommendationApprove Nothing blocks merging. Updating the description is enough. Narrowing the two conditions is optional.
|
…s it twice
When one partition returned 2 copies of the same constant, both stayed
in the partition. OpenVINO's PyTorch frontend then cannot convert 2
outputs of one constant, and export failed with "Tensor name is not a
number". For example:
return self.b.clone(), self.b.clone(), x + self.b
A copy of a constant now stays only when it is the only partition
output that returns that constant, counting copies of copies. Otherwise
the copies run on the CPU, as on main. The test for copies that run
outside covers a copy and a copy of it returned together.
A uint16 copy of a constant stayed in the partition, but the runtime cannot bind a uint16 output, so the program failed at run time with "unsupported scalar type 27". A copy of a constant now stays only in a type the runtime binds, leaving out the 64-bit types the CPU plugin computes in 32 bits: bool, uint8, int8, int16, int32, float16, bfloat16 and float32. This list replaces the check for 64-bit types. The test for copies that run outside covers uint16.
The CPU plugin has Identity from OpenVINO 2025.4, while the backend allows OpenVINO 2025.1. With an older release, lowering a whole program that returns an input unchanged failed with "Unsupported operation of type: Identity", an op the program never had. preprocess now raises an error that says which OpenVINO release it needs and which one it found. The minimum version stays as it is. OpenVINO's Linux aarch64 builds need glibc 2.35 from 2025.3 on, so a higher minimum would stop installing on older aarch64 systems, for a case that mostly whole programs lowered with to_backend reach. The whole program test skips releases before 2025.4, and a new test checks the error.
|
Thanks. The latest push updates the description and fixes a few more cases:
One known limit: a float32 copy of a constant that stays in the partition loses subnormals and the sign of zero, so |
|
@claude review this PR |
|
Claude finished @shoumikhin's task in 4m 8s —— View job Approve: I found no blockers, but two minor problems are left: the whole-program int64 test uses only small values, so it can't catch the 32-bit truncation the partitioner already guards against, and a kept float32 copy of a constant can come back rounded when a lower Detailed reviewPR Review: #24506SummaryThe PR keeps a returned copy of a constant inside the OpenVINO partition when OpenVINO can return it exactly. On the CPU device, it also puts an
Limits of this review: I reviewed all four changed files by reading the code only, because Python and OpenVINO could not run in this job. So every claim below about what an OpenVINO plugin does is suspected, not confirmed. The checkout is shallow and Code Quality
Backward Compatibility
RecommendationApprove Nothing blocks merging. The int64 test change is small and would show whether the description's claim about 64-bit whole programs holds. The precision question needs one run to settle.
|
With inference_precision set to f16 or bf16, OpenVINO can return a float copy of a constant rounded: on an ARM CPU at f16, 3.14159265 came back as 3.140625. The README suggests f16 on ARM CPUs. A float copy of a constant now stays in the partition only when OpenVINO computes in float32, which is the default on the CPU. Otherwise it runs on the CPU, as on main. Integer and bool copies are not affected. On other devices the backend sets no default precision, so unless f32 is set, float copies of constants run on the CPU there too, and test_copy_of_constant_as_output skips them. The partitioner and preprocess share the rule that picks the precision. At f16, the tests check that a float copy runs on the CPU and an int32 copy stays.
|
Thanks. Answers below.
|
|
@claude review this PR |
|
Claude finished @shoumikhin's task in 4m 54s —— View job Approve: I found nothing that blocks merging. One minor test gap is left: six of the eight types whose constant copies now stay in the partition never run in a test. Detailed reviewPR Review: #24506SummaryThe PR keeps a returned copy of a constant inside the OpenVINO partition when OpenVINO can return it exactly. On the CPU device, it also puts an Limits of this review: I reviewed all four changed files by reading the code only, because Python and OpenVINO could not run in this job. The checkout is shallow and I left out points the author has already answered in this thread: constant copies on GPU and NPU, a partition with no inputs, the AUTO, HETERO and MULTI devices, and version parsing. Testing
RecommendationApprove Nothing blocks merging. The extra subtests are small and can go into this PR or a follow-up.
|
What is wrong today
OpenVINO turns a copy like
x.clone()into nothing, so a model returning such a copy returns its input directly, and the CPU plugin gives wrong values for it. A recent change made the partitioner run these copies on the CPU instead.It also moved copies of weights and buffers to the CPU. Most did not need it, because a constant is stored inside the partition, not passed in as an input. That had costs:
no_copy, export failed.self.buf.clone(memory_format=torch.preserve_format)failed at run time in the portable clone kernel.Also, lowering a whole program with
to_backend, without a partitioner, still gave wrong values for programs likereturn x.clone().What this change does
f32, the CPU default. Atf16, OpenVINO on an ARM CPU rounded such a copy.preprocessputs an OpenVINOIdentitybetween an input and an output when the converted model would return the input directly. This fixes whole programs lowered withto_backend. The CPU plugin has noIdentitybefore OpenVINO 2025.4, so with those releases such a program fails to lower, with an error naming the version it needs.Not changed: a copy of a constant that still runs on the CPU can still have the costs above, as on main. With dim order off,
x.clone(memory_format=torch.preserve_format)of a real input still fails. A whole program's int64 output outside the int32 range still comes back wrong, as for every int64 tensor on the CPU plugin (see the README).Testing
backends/openvino/tests/ops/test_copy.pycheck where copies run (including dim order off, uint16, f16 precision and 2 copies of one constant), mutated buffers, the error for OpenVINO before 2025.4 (version check mocked), and whole programs on the CPU with float, int64 and bool inputs and static and dynamic shapes.test_copy_of_constant_as_outputandtest_whole_program_returning_copy_of_inputfail on main.