Skip to content

Dynamic-shape input validation only enforces total numel ≀ upper_boundΒ #20546

Description

@benITo47

πŸ› Describe the bug

For a method with DYNAMIC_BOUND inputs, the only shape validation performed at set_input is a single scalar check, numel(new_sizes) <= numel_bound_. The per-dimension lower bounds (Dim(min=…)) and divisibility / derived-dim constraints (e.g. 32*Dim(...)) declared at torch.export time are not enforced at runtime. Inputs that violate them are silently accepted and produce wrong-shaped / silently-incorrect outputs - no error is raised.

Where it happens - runtime/core/portable_type/tensor_impl.cpp, TensorImpl::internal_resize_contiguous(), the DYNAMIC_BOUND case:

case TensorShapeDynamism::DYNAMIC_BOUND:
case TensorShapeDynamism::DYNAMIC_UNBOUND: {
  auto new_numel = safe_numel(new_sizes.data(), dim_).get();
  ET_CHECK_OR_RETURN_ERROR(
      static_cast<size_t>(new_numel) <= numel_bound_,   // <-- the ONLY guard
      NotSupported,
      "Attempted to resize a bounded tensor with a maximum capacity of %zu elements to %zu elements.",
      numel_bound_, new_numel);
  ...
}
  • DYNAMIC_UNBOUND falls through to the same case (the // TODO(T175194371) treats unbounded as upper-bounded), so even "unbounded" dynamic tensors are silently numel-capped and never per-dim validated.

Root cause - schema/program.fbs, table Tensor, serializes only the upper-bound sizes for a dynamic dim (field comment: "dynamism == DYNAMIC_BOUND: sizes field represents the upper bound shape") plus a shape_dynamism enum. The Dim lower bound and divisibility are dropped during AOT serialization, so the runtime has nothing to validate against except the precomputed numel_bound_ ceiling.

Reproduction - a model exported with a dynamic square input H, W = 32*Dim(min=8, max=40) (valid set = multiples of 32 in [256, 1280]); detect output is [1, 134, H/4, W/4]. Observed at set_input/execute:

Input Expected Actual
1280Γ—1280 (max, valid) OK βœ… OK β†’ [1,134,320,320]
640Γ—640 (valid) OK βœ… OK β†’ [1,134,160,160]
256Γ—256 (min, valid) OK βœ… OK β†’ [1,134,64,64]
96Γ—96 (below min) reject ⚠️ runs β†’ [1,134,24,24]
300Γ—300 (not Γ·32) reject ⚠️ runs β†’ [1,134,80,80] - note 80 β‰  300/4 = 75; the internal /4 downsample ratio is broken, so downstream geometry (coordinate decode, grid_sample, etc.) is silently wrong
1312Γ—1312 (above max) reject βœ… set_input() returns Error::NotSupported - but only because numel exceeds the bound, not because a per-dim max is checked

So in practice only the upper limit is guarded. The off-divisor case returns a normal-looking tensor whose dim/divisor relationship is wrong, and no error surfaces.

Expected behavior: set_input / resize_tensor should reject (or the framework should provide a supported way to reject) shapes that violate the declared per-dim min/max or divisibility, instead of silently accepting any shape whose total numel fits the bound. (Closely related: there is currently no API to query these bounds either)

Versions

PyTorch version: 2.12.0
Is debug build: False
CUDA used to build PyTorch: None
ROCM used to build PyTorch: N/A

OS: macOS 26.2 (arm64)
GCC version: Could not collect
Clang version: 21.0.0 (clang-2100.1.1.101)
CMake version: version 3.31.10
Libc version: N/A

Python version: 3.10.19 (main, Oct 27 2025, 17:05:40) [Clang 17.0.0 (clang-1700.3.19.1)] (64-bit runtime)
Python platform: macOS-26.2-arm64-arm-64bit
Is CUDA available: False
CUDA runtime version: No CUDA
CUDA_MODULE_LOADING set to: N/A
GPU models and configuration: No CUDA
Nvidia driver version: No CUDA
cuDNN version: No CUDA
Is XPU available: False
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: True
Caching allocator config: N/A

CPU:
Apple M4 Pro

Versions of relevant libraries:
[pip3] executorch==1.3.1+10f535e
[pip3] flake8==6.1.0
[pip3] flake8-breakpoint==1.1.0
[pip3] flake8-bugbear==24.4.26
[pip3] flake8-comprehensions==3.14.0
[pip3] flake8-plugin-utils==1.3.3
[pip3] flake8-pyi==23.5.0
[pip3] mypy==1.14.1
[pip3] mypy_extensions==1.1.0
[pip3] numpy==2.2.6
[pip3] onnx==1.20.1
[pip3] onnx-ir==0.2.0
[pip3] onnxruntime==1.23.2
[pip3] onnxscript==0.6.2
[pip3] optree==0.19.0
[pip3] pytorch_tokenizers==1.3.0
[pip3] tf2onnx==1.17.0
[pip3] torch==2.12.0
[pip3] torchao==0.17.0+git02105d46c
[pip3] torchaudio==2.11.0
[pip3] torchdata==0.11.0+cpu
[pip3] torchsr==1.0.4
[pip3] torchtune==0.0.0
[pip3] torchvision==0.27.0

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Fields

No fields configured for Bug.

Projects

Status
To triage

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions