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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions