Skip to content

Commit 8faf57d

Browse files
authored
Fall back to trace hint when eval_upper_bound returns int_oo (pytorch#20834)
When bound_sympy cannot compute a finite upper bound for a symbolic expression (returns int_oo), fall back to eval_expr for the trace hint before returning int_oo. This allows models with unbounded dynamic shapes (e.g. Dim('batch', min=1) with no max) to pass through ConstraintBasedSymShapeEvalPass without raising. Previously, int_oo was returned directly, causing ConstraintBasedSymShapeEvalPass to raise RuntimeError because it requires concrete int upper bounds for memory planning. Downstream exporters had to monkey-patch eval_upper_bound to work around this.
1 parent 432353a commit 8faf57d

2 files changed

Lines changed: 48 additions & 1 deletion

File tree

exir/sym_util.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,13 @@ def eval_upper_bound(maybe_symint: Union[int, torch.SymInt]) -> int:
6363
), f"Expect upper bound to be a concrete int but got {concrete_upper}"
6464
return concrete_upper
6565
elif int_oo is not None and upper_bound is int_oo:
66-
return int_oo
66+
hint = eval_expr(maybe_symint)
67+
if hint is not None:
68+
return hint
69+
raise RuntimeError(
70+
f"Cannot evaluate a finite upper bound for symbolic expression {expr} "
71+
"(int_oo) and no trace hint is available."
72+
)
6773
else:
6874
raise RuntimeError(
6975
f"Expect upper bound to be sympy.Integer or int_oo. but got {upper_bound}"

exir/tests/test_passes.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,47 @@ def combine_fn(carry, x):
894894
self.assertEqual(spec[1].shape_dynamism, TensorShapeDynamism.DYNAMIC_BOUND)
895895
self.assertEqual(spec[1].shape[1], 3) # Second dim is static
896896

897+
def test_eval_upper_bound_int_oo_falls_back_to_hint(self) -> None:
898+
"""When bound_sympy returns int_oo (no finite upper bound from
899+
constraints), eval_upper_bound should fall back to the trace hint
900+
for backed symbols rather than returning int_oo."""
901+
902+
class SimpleModel(torch.nn.Module):
903+
def forward(self, x):
904+
return x + 1
905+
906+
m = SimpleModel()
907+
dim0 = torch.export.Dim("batch", min=1)
908+
ep = export(
909+
m,
910+
(torch.randn(4, 8),),
911+
dynamic_shapes={"x": {0: dim0}},
912+
)
913+
edge = to_edge(ep)
914+
915+
gm = edge.exported_program().graph_module
916+
x_node = next(
917+
n for n in gm.graph.nodes if n.op == "placeholder" and n.name == "x"
918+
)
919+
sym_dim = x_node.meta["val"].shape[0]
920+
self.assertIsInstance(sym_dim, torch.SymInt)
921+
922+
try:
923+
from torch.utils._sympy.numbers import int_oo
924+
except ImportError:
925+
self.skipTest("int_oo not available in this torch version")
926+
from torch.utils._sympy.value_ranges import bound_sympy
927+
928+
raw_upper = bound_sympy(
929+
sym_dim.node.expr, sym_dim.node.shape_env.var_to_range
930+
).upper
931+
self.assertIs(raw_upper, int_oo)
932+
933+
self.assertEqual(eval_upper_bound(sym_dim), 4)
934+
935+
et = edge.to_executorch()
936+
self.assertIsNotNone(et)
937+
897938
def test_compile_fix_broken_ops(self) -> None:
898939
class ExportableLoop(nn.Module):
899940
def __init__(self, hidden_size, out_channels):

0 commit comments

Comments
 (0)