Repository navigation
Preserve ExportedProgram dynamic input shape constraints in .pte for runtime/backend validation #20066
Description
🚀 The feature, motivation and pitch
Problem
torch.export can represent a rich input shape contract for dynamic-shape models. This contract is enforced when running:
exported_program.module(check_guards=True)because the returned module includes guard logic, such as _guards_fn, that validates real inputs before executing the graph.
However, after lowering to ExecuTorch and serializing to .pte, only a much weaker form of shape metadata appears to survive:
- Tensor.sizes
- Tensor.shape_dynamism
- Tensor.dim_order
For DYNAMIC_BOUND, Tensor.sizes stores the upper-bound shape. The richer input shape contract from ExportedProgram does not appear to have an equivalent runtime representation in the .pte.
Example
Given:
half_height = torch.export.Dim("half_height", min=4, max=8)
height = 2 * half_height
dynamic_shapes = (
{
2: height,
3: torch.export.Dim("width", min=8, max=16),
},
)The exported program contains symbolic placeholder shapes such as:
x: [1, 3, 2*s87, s46]
and range constraints such as:
2*s87: min=8, max=16
s87: min=4, max=8
s46: min=8, max=16
When executing the exported module eagerly, the guard logic rejects invalid inputs. For example, an input with height 9 is rejected because 9 cannot be represented as 2*s87 for an integer s87:
Expected input *args[0].shape[2] = 9 to be of the form 2*s87, where s87 is an integer. However after serializing to .pte, the input tensor metadata contains only:
sizes=[1, 3, 16, 16]
shape_dynamism=DYNAMIC_BOUND
This preserves the upper bound, but not the fact that height must be even, nor the lower bound, symbolic expression, or root-symbol range.
Why this matters
Backends that support dynamic shapes may need to validate input shapes before compilation, specialization, or execution.
If the .pte only exposes upper-bound tensor sizes, a backend cannot reliably recover the original valid input shape domain. This can lead to late failures, poor error messages, or backend/compiler crashes when an invalid dynamic input shape is accepted by ExecuTorch but rejected deeper in backend-specific code.
This is not specific to one backend. Any delegate backend that consumes a dynamic-shape lowered graph may need access to the same input shape contract that ExportedProgram.module(check_guards=True) enforces in eager execution.
Question
What is the intended mechanism for preserving and enforcing the ExportedProgram input shape contract after lowering to ExecuTorch?
Should ExecuTorch:
- Serialize a normalized input shape-constraint representation into the .pte?
- Preserve
ExportedProgram.range_constraintsplus symbolic placeholder shape expressions in the runtime program? - Provide a standard backend API for extracting and serializing this contract into delegate payloads?
- Provide runtime utilities for validating actual inputs against the exported shape contract before delegate execution?
- Intentionally expose only
Tensor.shape_dynamismand upper-boundTensor.sizes, leaving full validation to individual backends?
Desired outcome
It would be helpful to clarify or standardize how dynamic shape constraints are preserved across:
torch.export.ExportedProgram -> ExecuTorch lowering -> .pte serialization -> runtime/backend execution
Ideally, .pte or backend delegate metadata would preserve enough information to validate the same input shape contract enforced by ExportedProgram.module(check_guards=True), including:
- rank checks
- static dimensions
- min/max range constraints
- symbolic equality relationships
- derived symbolic expressions such as
2*s - guard-derived constraints that affect valid input shapes
This does not necessarily mean serializing _guards_fn itself. A normalized, runtime-friendly shape-constraint representation may be more appropriate.
Alternatives
No response
Additional context
No response
RFC (Optional)
No response
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTo triage
Activity
mergennachin commentedon Jun 5, 2026
Hi @oscarandersson8218
Backend's
preprocess()receivesExportedProgram, so a backend can technically inspectrange_constraints(and other metadata). If it needs runtime validation, it can serialize the metadata into delegate payload and the runtime can do further validation.That works as a backend specific workaround and currently, there's no standard way but we’d be open to a more backend-agnostic design in EXIR/runtime.
Since you need it, perhaps you can start with the specific backend and then promote it to be more agnostic later. I’d suggest keeping the metadata backend-neutral from the start called, ShapeContract, which can be easily serialized/deserialized
numel ≤ upper_bound#20546 as a duplicate of this issuenumel ≤ upper_bound#20546 as a duplicate of this issue