Skip to content

Commit 94eefdc

Browse files
committed
Merge remote-tracking branch 'upstream/main' into gemma-ondevice-sampling
2 parents 373b79d + 20944fd commit 94eefdc

150 files changed

Lines changed: 6210 additions & 690 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,11 @@ target_link_libraries(executorch_core PRIVATE program_schema)
519519
if(ANDROID)
520520
target_link_libraries(executorch_core PUBLIC log)
521521
endif()
522-
if(EXECUTORCH_USE_DL)
522+
# Skip when cross-compiling: find_library() resolves the host libdl even for a
523+
# bare-metal target (e.g. arm-none-eabi), which then links -ldl into the
524+
# baremetal runner and fails ("cannot find -ldl"). dladdr() isn't used on those
525+
# targets anyway. Per-target presets also set EXECUTORCH_USE_DL OFF explicitly.
526+
if(EXECUTORCH_USE_DL AND NOT CMAKE_CROSSCOMPILING)
523527
# Check if dl exists for this toolchain and only then link it.
524528
find_library(DL_LIBRARY_EXISTS NAMES dl)
525529
# Check if the library was found

backends/arm/_passes/aten_to_tosa_tensor_operators.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,20 @@ def rewrite_argmax(node: Node, pass_: AtenToDialectPass) -> DialectNodeSpec:
2626
)
2727

2828

29+
def rewrite_rfft2(node: Node, pass_: AtenToDialectPass) -> DialectNodeSpec | None:
30+
fft_size = node.args[1] if len(node.args) > 1 else node.kwargs.get("s")
31+
fft_dims = node.args[2] if len(node.args) > 2 else node.kwargs.get("dim", [-2, -1])
32+
norm = node.args[3] if len(node.args) > 3 else node.kwargs.get("norm")
33+
if fft_size is not None or fft_dims not in ([-2, -1], (-2, -1)) or norm is not None:
34+
return None
35+
36+
return DialectNodeSpec(
37+
exir_ops.backend.tosa.RFFT2D.default,
38+
(node.args[0],),
39+
{},
40+
)
41+
42+
2943
def rewrite_binary_operator(
3044
node: Node, pass_: AtenToDialectPass
3145
) -> DialectNodeSpec | None:

backends/arm/_passes/decompose_div_tensor_mode.py

Lines changed: 209 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,49 @@
44
# LICENSE file in the root directory of this source tree.
55

66

7-
from typing import Set, Type
7+
from typing import cast, Literal, Set, Type
88

99
import torch
1010
from executorch.backends.arm._passes.arm_pass import ArmOpTargetedPass
1111
from executorch.backends.arm._passes.decompose_div_pass import DecomposeDivPass
12+
from executorch.backends.arm.tosa.specification import get_context_spec
1213
from executorch.exir.dialects._ops import ops as exir_ops
1314
from executorch.exir.pass_base import ExportPass
1415

1516
edge_div_mode_ops = (exir_ops.edge.aten.div.Tensor_mode,)
1617
aten_div_mode_ops = (torch.ops.aten.div.Tensor_mode,)
18+
RoundingMode = Literal["trunc", "floor"]
1719

1820
edge_unary = {
1921
"div": exir_ops.edge.aten.div.Tensor,
2022
"floor": exir_ops.edge.aten.floor.default,
2123
"ceil": exir_ops.edge.aten.ceil.default,
24+
"eq": exir_ops.edge.aten.eq.Tensor,
2225
"full": exir_ops.edge.aten.full.default,
2326
"gt": exir_ops.edge.aten.gt.Tensor,
27+
"logical_and": exir_ops.edge.aten.logical_and.default,
28+
"logical_not": exir_ops.edge.aten.logical_not.default,
29+
"logical_xor": exir_ops.edge.aten.logical_xor.default,
30+
"intdiv": exir_ops.backend.tosa.INTDIV.default,
31+
"mul": exir_ops.edge.aten.mul.Tensor,
32+
"sub": exir_ops.edge.aten.sub.Tensor,
33+
"to": exir_ops.edge.dim_order_ops._to_dim_order_copy.default,
2434
"where": exir_ops.edge.aten.where.self,
2535
}
2636

2737
aten_unary = {
2838
"div": torch.ops.aten.div.Tensor,
2939
"floor": torch.ops.aten.floor.default,
3040
"ceil": torch.ops.aten.ceil.default,
41+
"eq": torch.ops.aten.eq.Tensor,
3142
"full": torch.ops.aten.full.default,
3243
"gt": torch.ops.aten.gt.Tensor,
44+
"logical_and": torch.ops.aten.logical_and.default,
45+
"logical_not": torch.ops.aten.logical_not.default,
46+
"logical_xor": torch.ops.aten.logical_xor.default,
47+
"mul": torch.ops.aten.mul.Tensor,
48+
"sub": torch.ops.aten.sub.Tensor,
49+
"to": torch.ops.aten.to.dtype,
3350
"where": torch.ops.aten.where.self,
3451
}
3552

@@ -43,9 +60,9 @@ def _get_opset(op):
4360

4461

4562
class DecomposeDivTensorModePass(ArmOpTargetedPass):
46-
"""Rewrites aten.div.Tensor_mode into.
63+
"""Rewrites aten.div.Tensor_mode into supported arithmetic ops.
4764
48-
Example:
65+
Floating-point flow:
4966
rounding_mode=None -> div(a, b)
5067
rounding_mode="floor" -> floor(div(a, b))
5168
rounding_mode="trunc" -> where(
@@ -54,48 +71,213 @@ class DecomposeDivTensorModePass(ArmOpTargetedPass):
5471
floor(div(a, b)),
5572
)
5673
74+
Integer flow:
75+
During transform-for-annotation, keep div.Tensor_mode intact, don't quantize it.
76+
During backend lowering, rewrite the div to a TOSA INTDIV (corresponding to trunc rounding_mode)
77+
+ correcting factor for floor mode.
78+
5779
"""
5880

5981
_passes_required_after: Set[Type[ExportPass]] = {DecomposeDivPass}
6082
target_ops = edge_div_mode_ops + aten_div_mode_ops
6183
check_allowed_to_transform = True
6284

85+
def _is_integer_tensor(self, arg) -> bool:
86+
data = getattr(arg, "data", None)
87+
if data is not None:
88+
return arg.data.dtype in {
89+
torch.uint8,
90+
torch.int8,
91+
torch.int16,
92+
torch.int32,
93+
torch.int64,
94+
}
95+
return isinstance(arg, int)
96+
97+
def _cast(self, opset, arg, dtype: torch.dtype, meta):
98+
if isinstance(arg, int):
99+
if dtype.is_floating_point:
100+
return float(arg)
101+
else:
102+
return arg
103+
if isinstance(arg, float):
104+
if dtype.is_floating_point:
105+
return arg
106+
else:
107+
return int(arg)
108+
data = getattr(arg, "data", None)
109+
if data is not None and data.dtype == dtype:
110+
return arg
111+
return super().call_operator(
112+
opset["to"],
113+
(arg,),
114+
{"dtype": dtype},
115+
meta,
116+
updated=True,
117+
)
118+
119+
def _full(self, opset, value, dtype: torch.dtype, meta):
120+
return super().call_operator(
121+
opset["full"],
122+
args=((1,) * len(meta["val"].size()), value),
123+
kwargs={"dtype": dtype, "device": meta["val"].device},
124+
meta=meta,
125+
updated=True,
126+
)
127+
128+
def _correct_intdiv_floor(
129+
self, opset, numerator, denominator, trunced_quotient, meta
130+
):
131+
"""Apply a correcting factor for converting the truncated division to
132+
floored division.
133+
134+
Done by subtracting one from the result when, elementwise,
135+
- The remainder is nonzero (otherwise the division is even and the rounding trivial)
136+
- The numerator and denominator have different signs (causing a negative quotient)
137+
The sign of the quotient can't be checked directly, there are cases when it is 0 and still needs correction.
138+
139+
"""
140+
# Condition 1: non-zero remainder
141+
product = super().call_operator(
142+
opset["mul"], (trunced_quotient, denominator), {}, meta, updated=True
143+
)
144+
remainder = super().call_operator(
145+
opset["sub"], (numerator, product), {}, meta, updated=True
146+
)
147+
zero = self._full(opset, 0, torch.int32, meta)
148+
remainder_is_zero = super().call_operator(
149+
opset["eq"], (remainder, zero), {}, meta, updated=True
150+
)
151+
remainder_is_nonzero = super().call_operator(
152+
opset["logical_not"], (remainder_is_zero,), {}, meta, updated=True
153+
)
154+
# Condition 2: un-rounded quotient is negative
155+
a_is_negative = super().call_operator(
156+
opset["gt"], (zero, numerator), {}, meta, updated=True
157+
)
158+
b_is_negative = super().call_operator(
159+
opset["gt"], (zero, denominator), {}, meta, updated=True
160+
)
161+
signs_differ = super().call_operator(
162+
opset["logical_xor"],
163+
(a_is_negative, b_is_negative),
164+
{},
165+
meta,
166+
updated=True,
167+
)
168+
# Use conditions to correct quotient.
169+
needs_correction = super().call_operator(
170+
opset["logical_and"],
171+
(remainder_is_nonzero, signs_differ),
172+
{},
173+
meta,
174+
updated=True,
175+
)
176+
# (TOSA spec enforces that int(bool_var) == 1 ? bool_var : 0)
177+
correction = self._cast(opset, needs_correction, torch.int32, meta)
178+
return super().call_operator(
179+
opset["sub"], (trunced_quotient, correction), {}, meta, updated=True
180+
)
181+
182+
def _call_integer_div(self, opset, a, b, rounding_mode: RoundingMode, meta):
183+
"""Cast inputs to int32, do TOSA INTDIV, and apply correcting factor for
184+
floor rounding mode.
185+
"""
186+
187+
a_int32 = self._cast(opset, a, torch.int32, meta)
188+
b_int32 = self._cast(opset, b, torch.int32, meta)
189+
intdiv = super().call_operator(
190+
opset["intdiv"],
191+
(a_int32, b_int32),
192+
{},
193+
meta,
194+
updated=True,
195+
)
196+
if rounding_mode == "floor":
197+
intdiv = self._correct_intdiv_floor(opset, a_int32, b_int32, intdiv, meta)
198+
199+
output_dtype = meta["val"].dtype
200+
return self._cast(opset, intdiv, output_dtype, meta)
201+
202+
def _call_fp_div(self, opset, a, b, rounding_mode: RoundingMode | None, meta):
203+
q = super().call_operator(opset["div"], (a, b), {}, meta, updated=True)
204+
205+
match rounding_mode:
206+
case None:
207+
return q
208+
case "floor":
209+
return super().call_operator(
210+
opset["floor"], (q,), {}, meta, updated=True
211+
)
212+
case "trunc":
213+
zero = self._full(opset, 0.0, torch.float32, meta)
214+
is_neg = super().call_operator(
215+
opset["gt"], (zero, q), {}, meta, updated=True
216+
)
217+
ceilq = super().call_operator(
218+
opset["ceil"], (q,), {}, meta, updated=True
219+
)
220+
floorq = super().call_operator(
221+
opset["floor"], (q,), {}, meta, updated=True
222+
)
223+
return super().call_operator(
224+
opset["where"], (is_neg, ceilq, floorq), {}, meta, updated=True
225+
)
226+
63227
def call_operator(self, op, args, kwargs, meta):
64228
if op not in self.target_ops or not self.allowed_to_transform(meta):
65229
return super().call_operator(op, args, kwargs, meta)
66230

67231
opset = _get_opset(op)
68232

69233
a, b = args[0], args[1]
234+
a_is_int = self._is_integer_tensor(a)
235+
b_is_int = self._is_integer_tensor(b)
70236
rounding_mode = kwargs.get("rounding_mode", None)
71237
if rounding_mode is None and len(args) > 2:
72238
rounding_mode = args[2]
239+
if rounding_mode not in ("floor", "trunc", None):
240+
raise RuntimeError(
241+
"Integer div.Tensor_mode requires rounding_mode floor, trunc, or None."
242+
f"got {rounding_mode!r}"
243+
)
244+
rounding_mode = cast(RoundingMode | None, rounding_mode)
73245

74-
q = super().call_operator(opset["div"], (a, b), {}, meta, updated=True)
246+
int_operation = rounding_mode is not None and a_is_int and b_is_int
247+
sufficient_int_support = (
248+
rounding_mode == "trunc" or get_context_spec().support_integer()
249+
)
250+
sufficient_int_support &= not get_context_spec().is_U55_subset
75251

76-
if rounding_mode is None:
77-
return q
252+
if int_operation and sufficient_int_support:
253+
"""Integer operation and necessary int ops supported -> pure integer
254+
path.
255+
"""
256+
if self.is_tfa_pass:
257+
# No quantization neccessary, so don't do anything in TFA.
258+
return super().call_operator(op, args, kwargs, meta)
259+
return self._call_integer_div(opset, a, b, rounding_mode, meta)
260+
else:
261+
"""Otherwise floating point operation -> do fp path.
78262
79-
if rounding_mode == "floor":
80-
return super().call_operator(opset["floor"], (q,), {}, meta, updated=True)
81-
82-
if rounding_mode == "trunc":
83-
zero = super().call_operator(
84-
opset["full"],
85-
args=((1,) * len(meta["val"].size()), 0.0),
86-
kwargs={"dtype": torch.float32, "device": meta["val"].device},
87-
meta=meta,
88-
updated=True,
89-
)
90-
is_neg = super().call_operator(
91-
opset["gt"], (zero, q), {}, meta, updated=True
92-
)
93-
ceilq = super().call_operator(opset["ceil"], (q,), {}, meta, updated=True)
94-
floorq = super().call_operator(opset["floor"], (q,), {}, meta, updated=True)
95-
return super().call_operator(
96-
opset["where"], (is_neg, ceilq, floorq), {}, meta, updated=True
263+
Cast to and from fp if neccessary.
264+
265+
"""
266+
if a_is_int:
267+
a = self._cast(opset, a, torch.float32, meta)
268+
if b_is_int:
269+
b = self._cast(opset, b, torch.float32, meta)
270+
271+
result = self._call_fp_div(
272+
opset,
273+
a,
274+
b,
275+
rounding_mode,
276+
meta,
97277
)
98278

99-
raise RuntimeError(
100-
f"Unsupported rounding_mode for div.Tensor_mode: {rounding_mode!r}"
101-
)
279+
output_dtype = meta["val"].dtype
280+
if output_dtype != torch.float32:
281+
result = self._cast(opset, result, output_dtype, meta)
282+
283+
return result

backends/arm/_passes/exir_to_tosa_pass.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from executorch.backends.arm._passes.aten_to_tosa_tensor_operators import (
1313
rewrite_argmax,
1414
rewrite_binary_operator,
15+
rewrite_rfft2,
1516
)
1617
from executorch.backends.transforms.aten_to_dialect_pass import (
1718
AtenToDialectPass,
@@ -43,13 +44,24 @@ def decorator(func: SubstitutionFn) -> SubstitutionFn:
4344
return decorator
4445

4546

46-
@ExirToTosaPass.register_dialect_substitution(exir_ops.edge.aten.argmax.default)
47+
@register_dialect_substitutions(
48+
exir_ops.edge.aten.argmax.default,
49+
)
4750
def _get_tensor_operators_replacement(
4851
node: Node, pass_: AtenToDialectPass
49-
) -> DialectNodeSpec:
52+
) -> DialectNodeSpec | None:
5053
return rewrite_argmax(node, pass_)
5154

5255

56+
@register_dialect_substitutions(
57+
exir_ops.edge.aten.fft_rfft2.default,
58+
)
59+
def _get_fft_replacement(
60+
node: Node, pass_: AtenToDialectPass
61+
) -> DialectNodeSpec | None:
62+
return rewrite_rfft2(node, pass_)
63+
64+
5365
@register_dialect_substitutions(
5466
exir_ops.edge.aten.add.Tensor,
5567
exir_ops.edge.aten.bitwise_and.Tensor,

0 commit comments

Comments
 (0)