Skip to content

Enforce per-dim upper bounds for DYNAMIC_BOUND inputs in set_input - #24203

Open
AxelNoun wants to merge 3 commits into
pytorch:mainfrom
AxelNoun:fix/20546-per-dim-upper-bound
Open

AxelNoun wants to merge 3 commits into
pytorch:mainfrom
AxelNoun:fix/20546-per-dim-upper-bound

Conversation

@AxelNoun

@AxelNoun AxelNoun commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

In the portable runtime, Method::set_input checks a DYNAMIC_BOUND input only against the total element count of its serialized bound shape: numel_bound_ is the product of those sizes, and the resize compares the incoming numel against it. A model bounded at (3, 4) therefore accepts (2, 6). program.fbs already states the per-dimension contract ("each dimension of the tensor at runtime should never exceed the corresponding dimension of the upper bound shape"). This PR enforces it in set_input, as @Gasoonjia suggested in #20546, rather than in resize_tensor, which is used throughout the runtime. The existing input_tensor_meta() lookup moves above the resize and its Result is now checked instead of being dereferenced directly.

DYNAMIC_UNBOUND is skipped, since its sizes carry no bound, and rank mismatches are left to resize_tensor. Minimum sizes and divisibility are not serialized, so they are not checked here: a dimension that was static at export is only bounded from above. The cross-input check in set_inputs() discussed in the issue is not part of this PR either. From reading the code, ATen mode changes more than the portable runtime: its resize_tensor_impl (tensor_util_aten.cpp) checks only the rank, so this is the first shape bound set_input applies there.

Part of #20546

I asked @JacobSzwejbka for a go-ahead on 4 Sep and have not heard back. I am opening this so the diff can be reviewed together with the questions below, and I will rework or close it if you would rather handle this differently. Three questions from that comment are still open:

  1. Bound violations now return InvalidArgument. In the portable runtime, a shape that also exceeds the numel bound used to get NotSupported from resize_tensor and now gets InvalidArgument from the new check first (SetInputRejectsOverflowingSizes still passes; the second commit updates its comment accordingly). Should that case keep NotSupported?
  2. Should there be an opt-out for callers that rely on the current lenient behaviour? On 4 Aug I said the first PR would include one, after @benITo47 and @Gasoonjia preferred the strict check on by default. This PR is on by default and has no opt-out yet, pending your answer.
  3. Does turning a silently accepted input into an error need a release note? I suggested one on 4 Aug.

Test plan

Five new tests in runtime/executor/test/method_test.cpp: SetInputAcceptsShapesWithinPerDimBound, SetInputRejectsPerDimOverflowWithinNumelBound, SetInputRejectsPerDimOverflowBeyondNumelBound, SetInputRejectsRankMismatchOnBoundedInput and SetInputStaticShapeBehaviorUnchanged.

Built with CMake on Ubuntu 24.04 (Release, EXECUTORCH_BUILD_TESTS=ON, EXECUTORCH_ENABLE_LOGGING=ON) on top of main at a519331:

ctest --test-dir cmake-out -R '^method_test$' -V

All 22 tests pass, at both commits and again after merging main at d6abe23. With method.cpp reverted to main and the new tests kept, SetInputRejectsPerDimOverflowWithinNumelBound and SetInputRejectsPerDimOverflowBeyondNumelBound fail and the other 20 pass. lintrunner --skip MYPY reports no issues on the two files. I have not run the ATen-mode or Buck builds, and I have not measured the binary size on this base.

I used Claude Code to write this PR.

cc @larryliu0820 @JacobSzwejbka @lucylq @nil-is-all

Method::set_input guards a DYNAMIC_BOUND input only against the total
element count of its serialized bound shape: TensorImpl initializes
numel_bound_ to the product of those sizes, and internal_resize_contiguous
compares the incoming numel against it. A model bounded at (3, 4)
therefore accepts (2, 6) -- the product fits, the per-dimension contract
does not -- and the method proceeds with geometry it was not planned for.

program.fbs already states that contract: for DYNAMIC_BOUND, "each
dimension of the tensor at runtime should never exceed the corresponding
dimension of the upper bound shape". Only the first half was enforced.

Add the per-dimension check in set_input. input_tensor_meta() already
exposes the serialized shape as a span straight over the flatbuffer, so
the bound is available for free and outlives the Method; the existing
lookup is moved up and reused rather than a second one added, and its
Result is now error-checked instead of being dereferenced directly.
Placed in set_input rather than resize_tensor, per review discussion:
resize_tensor is used throughout the system, while this constraint is a
property of method inputs.

DYNAMIC_UNBOUND is deliberately skipped, since program.fbs says its sizes
field carries no bound. Rank mismatches are left to resize_tensor. Lower
bounds and divisibility are not serialized today and are left to a
follow-up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pytorch-bot

pytorch-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24203

Note: Links to docs will display an error until the docs builds have been completed.

⏳ No Failures, 4 Pending

As of commit 4c110b8 with merge base d6abe23 (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 27, 2026
AxelNoun and others added 2 commits October 27, 2026 18:22
On 64-bit the new per-dimension check in set_input now rejects these
sizes before resize_tensor's numel bound check does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@executorch-triage executorch-triage Bot added the community: contribution PRs coming from community (excluding hardware partners) label Oct 27, 2026
@nil-is-all
nil-is-all requested a review from Gasoonjia October 27, 2026 19:37
@nil-is-all nil-is-all added module: runtime Issues related to the core runtime and code under runtime/ release notes: runtime Changes related to the core runtime which loads the program methods, initializes delegates, and runs labels Oct 27, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. community: contribution PRs coming from community (excluding hardware partners) module: runtime Issues related to the core runtime and code under runtime/ release notes: runtime Changes related to the core runtime which loads the program methods, initializes delegates, and runs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants