π 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
π Describe the bug
For a method with
DYNAMIC_BOUNDinputs, the only shape validation performed atset_inputis 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 attorch.exporttime 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(), theDYNAMIC_BOUNDcase:DYNAMIC_UNBOUNDfalls 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-boundsizesfor a dynamic dim (field comment: "dynamism == DYNAMIC_BOUND: sizes field represents the upper bound shape") plus ashape_dynamismenum. TheDimlower bound and divisibility are dropped during AOT serialization, so the runtime has nothing to validate against except the precomputednumel_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 atset_input/execute:[1,134,320,320][1,134,160,160][1,134,64,64][1,134,24,24][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 wrongset_input()returnsError::NotSupported- but only becausenumelexceeds the bound, not because a per-dim max is checkedSo in practice only the upper limit is guarded. The off-divisor case returns a normal-looking tensor whose
dim/divisorrelationship is wrong, and no error surfaces.Expected behavior:
set_input/resize_tensorshould 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 totalnumelfits 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