Skip to content

Commit f003293

Browse files
authored
Merge branch 'main' into vgf_mem
2 parents 9b37e38 + 82a605d commit f003293

11 files changed

Lines changed: 333 additions & 148 deletions

backends/arm/_passes/arm_pass_manager.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
InsertConstShapesPass,
118118
InsertControlFlowRescalesPass,
119119
InsertDataLayoutCastsPass,
120+
InsertDynamicPaddingPass,
120121
InsertInt32CastsAfterInt64PlaceholdersPass,
121122
InsertRescaleInt32Pass,
122123
InsertRescalePass,
@@ -632,6 +633,7 @@ def _tosa_pipeline(
632633
CastInt64BuffersToInt32Pass(exported_program),
633634
FuseEqualPlaceholdersPass(exported_program),
634635
SymbolicToTosaShapesPass(),
636+
InsertDynamicPaddingPass(),
635637
FuseConsecutiveConcatShapesPass(),
636638
EnsureUniqueOutputNodesPass(),
637639
RemoveNoopPass(),

backends/arm/_passes/insert_dynamic_padding.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class InsertDynamicPaddingPass(ArmOpTargetedPass):
2929
_passes_required_after: Set[Type[ExportPass]] = set()
3030
target_ops = (
3131
exir_ops.backend.tosa.CONV2D.default,
32+
exir_ops.backend.tosa.CONV3D.default,
3233
exir_ops.backend.tosa.DEPTHWISE_CONV2D.default,
3334
exir_ops.backend.tosa.MAX_POOL2D.default,
3435
exir_ops.backend.tosa.AVG_POOL2D.default,
@@ -57,11 +58,12 @@ def call_operator(self, op, args, kwargs, meta, updated=False) -> ProxyValue:
5758
if not self._is_dynamic_padding(padding):
5859
return super().call_operator(op, args, kwargs, meta, updated)
5960

60-
# Create a pad op before conv2d
61+
# Create a pad op before the convolution/pool op.
6162
input_tensor = args[0]
6263

6364
zero_padding_pair = [0, 0]
64-
zero_spatial_padding = [0, 0, 0, 0]
65+
spatial_rank = 3 if op == exir_ops.backend.tosa.CONV3D.default else 2
66+
zero_spatial_padding = [0] * (spatial_rank * 2)
6567
N_padding = super().call_shape_operator(
6668
exir_ops.backend.tosa.CONST_SHAPE.default,
6769
(zero_padding_pair,),
@@ -93,7 +95,7 @@ def call_operator(self, op, args, kwargs, meta, updated=False) -> ProxyValue:
9395
meta,
9496
True,
9597
)
96-
new_conv2d_args = list(args)
97-
new_conv2d_args[0] = pad_res
98-
new_conv2d_args[padding_index] = zero_spatial_padding
99-
return super().call_operator(op, tuple(new_conv2d_args), kwargs, meta, updated)
98+
new_args = list(args)
99+
new_args[0] = pad_res
100+
new_args[padding_index] = zero_spatial_padding
101+
return super().call_operator(op, tuple(new_args), kwargs, meta, updated)

backends/arm/_passes/rewrite_conv_pass.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -97,23 +97,25 @@ def _adjust_pad_if_needed(
9797

9898
if isinstance(mod_remainder, torch.SymInt):
9999
shape_env = get_context_shape_env()
100-
exact_values = evaluate_symbolic_expr_values(
101-
mod_remainder.node.expr, shape_env
102-
)
100+
exact_values = evaluate_symbolic_expr_values(mod_remainder, shape_env)
103101
if exact_values is not None:
104102
mod_remainder_upper = max(exact_values)
103+
if len(exact_values) == 1:
104+
mod_remainder = int(next(iter(exact_values)))
105+
elif mod_remainder_upper == 0:
106+
mod_remainder = 0
107+
else:
108+
return pad - mod_remainder
105109
else:
106-
value_ranges = shape_env.bound_sympy(mod_remainder.node.expr)
107-
mod_remainder_upper = int(value_ranges.upper)
108-
if mod_remainder_upper == 0:
109-
mod_remainder = 0
110-
else:
111-
mod_remainder_upper = mod_remainder
112-
113-
if mod_remainder_upper > pad:
110+
# SizeAdjustInputPass already trims symbolic remainder classes
111+
# that would force negative padding. Keep the symbolic
112+
# expression here instead of asking ShapeEnv to normalize it.
113+
return pad - mod_remainder
114+
if mod_remainder > pad:
114115
raise RuntimeError(
115-
"This case should be handled by the SizeAdjustInputPass, is it enabled?\n"
116+
"This case should be handled by SizeAdjustInputPass, is it enabled?\n"
116117
)
118+
117119
return pad - mod_remainder
118120

119121
def _is_depthwise_conv2d(self, node: torch.fx.Node) -> bool:

backends/arm/_passes/size_adjust_input_pass.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,41 @@ def _greater_than(input: SymIntLike, other: int) -> bool | torch.SymBool:
6262
return input > other
6363

6464

65+
def _get_slice_adjustment(
66+
remainder: SymIntLike,
67+
pad: int,
68+
stride: int,
69+
) -> SymIntLike | None:
70+
"""Return the amount to slice from the end of a conv dimension.
71+
72+
The required trim is ``max(remainder - pad, 0)``. For symbolic shapes we
73+
encode that clamp using only integer arithmetic that the TOSA shape
74+
materializer already supports: a sum of floor-div terms over the possible
75+
residue classes.
76+
77+
"""
78+
if not isinstance(remainder, torch.SymInt):
79+
return remainder - pad if remainder > pad else None
80+
81+
shape_env = get_context_shape_env()
82+
exact_values = evaluate_symbolic_expr_values(remainder.node.expr, shape_env)
83+
if exact_values is not None:
84+
adjustments = {max(value - pad, 0) for value in exact_values}
85+
if len(adjustments) == 1:
86+
adjustment = next(iter(adjustments))
87+
return adjustment if adjustment > 0 else None
88+
89+
if pad >= stride - 1:
90+
return None
91+
92+
adjustment: SymIntLike | None = None # type: ignore[no-redef]
93+
for threshold in range(pad + 1, stride):
94+
term = (remainder + stride - threshold) // stride
95+
adjustment = term if adjustment is None else adjustment + term
96+
97+
return adjustment
98+
99+
65100
def get_slices_convolution(conv_node: torch.fx.Node) -> Slices:
66101
slices: Slices = []
67102

@@ -85,8 +120,12 @@ def get_slices_convolution(conv_node: torch.fx.Node) -> Slices:
85120
remainder = conv_remainder(
86121
input_shape[dim], pad, dilation, weight_shape[dim], stride
87122
)
88-
if _greater_than(remainder, pad):
89-
adjustment = remainder - pad
123+
adjustment = _get_slice_adjustment(
124+
remainder,
125+
pad,
126+
stride,
127+
)
128+
if adjustment is not None:
90129
args = (dim, 0, input_shape[dim] - adjustment)
91130
slices.append(args)
92131

backends/arm/_passes/symbolic_value_range.py

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,70 @@ def _symbol_values(symbol: sympy.Symbol, shape_env: ShapeEnv) -> _ExactValues:
3939
return frozenset(sympy.Integer(value) for value in range(lower, upper + 1))
4040

4141

42+
def _expr_symbols_to_values(
43+
expr: sympy.Basic,
44+
shape_env: ShapeEnv,
45+
) -> dict[sympy.Symbol, _ExactValues]:
46+
return {symbol: _symbol_values(symbol, shape_env) for symbol in expr.free_symbols}
47+
48+
49+
def _try_expr_to_int(expr: sympy.Basic) -> Optional[int]:
50+
integer_value = _expr_to_int(expr)
51+
if integer_value is not None:
52+
return integer_value
53+
54+
try:
55+
return _expr_to_int(sympy.simplify(expr))
56+
except (RecursionError, TypeError):
57+
return None
58+
59+
60+
def _constant_expr_values(expr: sympy.Basic) -> Optional[set[int]]:
61+
if expr.free_symbols:
62+
return None
63+
64+
integer_value = _try_expr_to_int(expr)
65+
return {integer_value} if integer_value is not None else None
66+
67+
68+
def _evaluate_exact_values(
69+
expr: sympy.Basic,
70+
shape_env: ShapeEnv,
71+
) -> _ExactValues:
72+
try:
73+
return sympy_interp(
74+
_ExactValueAnalysis,
75+
_expr_symbols_to_values(expr, shape_env),
76+
expr,
77+
missing_handler=lambda symbol: _symbol_values(symbol, shape_env),
78+
)
79+
except (RecursionError, TypeError):
80+
return None
81+
82+
83+
def _exact_values_to_ints(exact_values: _ExactValues) -> Optional[set[int]]:
84+
if exact_values is None:
85+
return None
86+
87+
result: set[int] = set()
88+
for value in exact_values:
89+
integer_value = _try_expr_to_int(value)
90+
if integer_value is None:
91+
return None
92+
result.add(integer_value)
93+
return result
94+
95+
4296
def _map_values(values: _ExactValues, fn) -> _ExactValues:
4397
if values is None:
4498
return None
4599

46-
result = {sympy.simplify(fn(value)) for value in values}
100+
result = set()
101+
for value in values:
102+
try:
103+
result.add(fn(value))
104+
except (RecursionError, TypeError):
105+
return None
47106
if len(result) > _MAX_SET_SIZE:
48107
return None
49108
return frozenset(result)
@@ -55,7 +114,13 @@ def _combine_values(lhs: _ExactValues, rhs: _ExactValues, fn) -> _ExactValues:
55114
if len(lhs) * len(rhs) > _MAX_SET_SIZE * _MAX_SET_SIZE:
56115
return None
57116

58-
result = {sympy.simplify(fn(a, b)) for a in lhs for b in rhs}
117+
result = set()
118+
for a in lhs:
119+
for b in rhs:
120+
try:
121+
result.add(fn(a, b))
122+
except (RecursionError, TypeError):
123+
return None
59124
if len(result) > _MAX_SET_SIZE:
60125
return None
61126
return frozenset(result)
@@ -80,6 +145,12 @@ def mod(lhs: _ExactValues, rhs: _ExactValues) -> _ExactValues:
80145
return None
81146
return _combine_values(lhs, rhs, lambda a, b: sympy.Mod(a, b))
82147

148+
@staticmethod
149+
def floordiv(lhs: _ExactValues, rhs: _ExactValues) -> _ExactValues:
150+
if rhs is None or any(value == 0 for value in rhs):
151+
return None
152+
return _combine_values(lhs, rhs, lambda a, b: sympy.floor(a / b))
153+
83154
@staticmethod
84155
def pow(lhs: _ExactValues, rhs: _ExactValues) -> _ExactValues:
85156
return _combine_values(lhs, rhs, lambda a, b: a**b)
@@ -104,35 +175,15 @@ def evaluate_symbolic_expr_values(
104175
) -> Optional[set[int]]:
105176
"""Return a best-effort finite set of possible integer values.
106177
107-
The helper first relies on ``bound_sympy`` for cheap singleton detection.
108-
When interval bounds are not precise enough, it falls back to a small
109-
exact-set analysis over bounded symbols using ``sympy_interp``.
178+
The helper avoids ShapeEnv bound queries here because some exported dynamic
179+
expressions trigger very deep SymPy normalization. Instead, it relies on a
180+
small exact-set analysis over bounded symbols using ``sympy_interp``.
110181
111182
"""
112-
root_expr = sympy.simplify(
113-
expr.node.expr if isinstance(expr, torch.SymInt) else expr
114-
)
115-
value_range = shape_env.bound_sympy(root_expr)
116-
if value_range.is_int and value_range.is_singleton():
117-
singleton = _expr_to_int(value_range.lower)
118-
return {singleton} if singleton is not None else None
119-
120-
exact_values = sympy_interp(
121-
_ExactValueAnalysis,
122-
{
123-
symbol: _symbol_values(symbol, shape_env)
124-
for symbol in root_expr.free_symbols
125-
},
126-
root_expr,
127-
missing_handler=lambda symbol: _symbol_values(symbol, shape_env),
128-
)
129-
if exact_values is None:
130-
return None
183+
root_expr = expr.node.expr if isinstance(expr, torch.SymInt) else expr
131184

132-
result: set[int] = set()
133-
for value in exact_values:
134-
integer_value = _expr_to_int(sympy.simplify(value))
135-
if integer_value is None:
136-
return None
137-
result.add(integer_value)
138-
return result
185+
constant_values = _constant_expr_values(root_expr)
186+
if constant_values is not None:
187+
return constant_values
188+
189+
return _exact_values_to_ints(_evaluate_exact_values(root_expr, shape_env))

0 commit comments

Comments
 (0)