Skip to content

Commit cc5359e

Browse files
authored
Merge branch 'main' into arm-baremetal-disable-dl
2 parents 2edbd8a + 825bd30 commit cc5359e

119 files changed

Lines changed: 4399 additions & 706 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.

.ci/scripts/setup-windows.ps1

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
param (
2-
[string]$editable = "false"
2+
[string]$editable = "false",
3+
[string]$cpuOnly = "false"
34
)
45

56
conda create --yes --quiet -n et python=3.12
@@ -13,6 +14,16 @@ conda activate et
1314
# Install test dependencies
1415
pip install -r .ci/docker/requirements-ci.txt
1516

17+
# The Windows CI image ships CUDA toolkits on PATH, so install_executorch
18+
# (setup.py) auto-enables EXECUTORCH_BUILD_CUDA whenever the detected nvcc
19+
# version is in SUPPORTED_CUDA_VERSIONS. CPU-only jobs install CPU torch, so a
20+
# CUDA build of _portable_lib then fails to load its CUDA DLLs at import time
21+
# ("DLL load failed while importing _portable_lib"). Force a CPU-only build
22+
# when the caller asks for it.
23+
if ($cpuOnly -eq 'true') {
24+
$env:CMAKE_ARGS = "$env:CMAKE_ARGS -DEXECUTORCH_BUILD_CUDA=OFF"
25+
}
26+
1627
if ($editable -eq 'true') {
1728
install_executorch.bat --editable
1829
} else {

.ci/scripts/wheel/pre_build_script.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ if [[ $UNAME_S == *"MINGW"* || $UNAME_S == *"MSYS"* ]]; then
5050
echo "Enabling symlinks on Windows"
5151
git config core.symlinks true
5252
git checkout -f HEAD
53+
54+
# Windows wheels are CPU-only (build-wheels-windows.yml sets
55+
# with-cuda: disabled), but the Windows CI image ships a CUDA toolkit on
56+
# PATH, which makes setup.py auto-enable EXECUTORCH_BUILD_CUDA. That bakes a
57+
# CUDA _portable_lib into the CPU wheel, which then fails its DLL load in the
58+
# smoke test ("DLL load failed while importing _portable_lib"). Force a
59+
# CPU-only build.
60+
export CMAKE_ARGS="${CMAKE_ARGS:-} -DEXECUTORCH_BUILD_CUDA=OFF"
61+
echo "CMAKE_ARGS=${CMAKE_ARGS}" >> "${GITHUB_ENV}"
5362
fi
5463

5564
# Manually install build requirements because `python setup.py bdist_wheel` does

.github/workflows/_unittest.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272
\$ErrorActionPreference = 'Stop'
7373
\$PSNativeCommandUseErrorActionPreference = \$true
7474
75-
.ci/scripts/setup-windows.ps1 -editable "${{ inputs.editable }}"
75+
.ci/scripts/setup-windows.ps1 -editable "${{ inputs.editable }}" -cpuOnly true
7676
if (\$LASTEXITCODE -ne 0) {
7777
Write-Host "Setup failed. Exit code: \$LASTEXITCODE."
7878
exit \$LASTEXITCODE

.github/workflows/cuda.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
# This workflow tests whether ExecuTorch can be successfully built with CUDA support
33
# across different CUDA versions (12.6, 13.0) using the command:
44
# ./install_executorch.sh
5+
#
6+
# Intentionally skipped CUDA version 13.2 check due to ci image unsupported.
57
#
68
# Note: ExecuTorch automatically detects the system CUDA version using nvcc and
79
# installs the appropriate PyTorch wheel. No manual CUDA/PyTorch installation needed.

.github/workflows/mlx.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ jobs:
8484
backends/mlx/test/test_partitioner.py \
8585
backends/mlx/test/test_serialization_dedup.py \
8686
backends/mlx/test/test_slot_recycling.py \
87+
backends/mlx/test/test_sample.py \
8788
examples/models/gemma4_31b/quant/tests/test_pack_mlx.py \
8889
examples/models/gemma4_31b/tests/test_mlx_pipeline.py \
8990
-v

.lintrunner.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,41 @@ command = [
683683
'@{{PATHSFILE}}',
684684
]
685685

686+
[[linter]]
687+
code = 'RUFF_DOCS'
688+
include_patterns = [
689+
'backends/arm/__init__.py',
690+
'backends/arm/common/arm_compile_spec.py',
691+
'backends/arm/ethosu/**/*.py',
692+
'backends/arm/quantizer/__init__.py',
693+
'backends/arm/quantizer/arm_quantizer.py',
694+
'backends/arm/tosa/partitioner.py',
695+
'backends/arm/vgf/**/*.py',
696+
]
697+
exclude_patterns = [
698+
'third-party/**',
699+
'**/third-party/**',
700+
]
701+
command = [
702+
'python',
703+
'-m',
704+
'lintrunner_adapters',
705+
'run',
706+
'ruff_linter',
707+
'--config=pyproject.toml',
708+
'--',
709+
'@{{PATHSFILE}}',
710+
]
711+
init_command = [
712+
'python',
713+
'-m',
714+
'lintrunner_adapters',
715+
'run',
716+
'pip_init',
717+
'--dry-run={{DRYRUN}}',
718+
'--requirement=requirements-lintrunner.txt',
719+
]
720+
686721
[[linter]]
687722
code = 'DOCFORMATTER'
688723
include_patterns = ['backends/arm/**/*.py']

backends/aoti/aoti_backend.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,21 @@ def codesign_so(cls, so_path: str, compile_specs: List[CompileSpec]) -> None:
112112
"""
113113
return
114114

115+
@classmethod
116+
def move_program_to_device(
117+
cls,
118+
edge_program: ExportedProgram,
119+
device: str,
120+
compile_specs: List[CompileSpec],
121+
) -> ExportedProgram:
122+
"""Move the exported program to the target device for compilation.
123+
124+
Default implementation moves everything (params, buffers, constants) via
125+
``move_to_device_pass``. Concrete backends may override to keep large
126+
non-parameter tensors off the device during a low-memory export.
127+
"""
128+
return move_to_device_pass(edge_program, device)
129+
115130
@classmethod
116131
def release_moved_tensors(
117132
cls,
@@ -196,9 +211,13 @@ def preprocess(
196211
decomposition_table = cls.get_decomposition_table()
197212
options = cls.get_aoti_compile_options(compile_specs)
198213

199-
# Move the edge_program to the target device
200-
device_edge_program = move_to_device_pass(
201-
edge_program, device_name if device_name != "metal" else "mps"
214+
# Move the edge_program to the target device. Routed through a hook so
215+
# backends can keep large non-parameter tensors (e.g. KV-cache buffers)
216+
# off the device during a low-memory export.
217+
device_edge_program = cls.move_program_to_device(
218+
edge_program,
219+
device_name if device_name != "metal" else "mps",
220+
compile_specs,
202221
)
203222

204223
# Replace view_copy with view

backends/arm/_passes/arm_pass_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,6 @@ def _tosa_pipeline(
627627
DecomposePermuteForU55Pass(),
628628
RewriteSlicePass(),
629629
InsertConstShapesPass(),
630-
ExirToTosaPass(exported_program),
631630
]
632631
)
633632

@@ -636,6 +635,7 @@ def _tosa_pipeline(
636635
[
637636
CastInt64BuffersToInt32Pass(exported_program),
638637
FuseEqualPlaceholdersPass(exported_program),
638+
ExirToTosaPass(exported_program),
639639
SymbolicToTosaShapesPass(),
640640
InsertDynamicPaddingPass(),
641641
FuseConsecutiveConcatShapesPass(),

backends/arm/_passes/aten_to_tosa_tensor_operators.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,47 @@ def rewrite_argmax(node: Node, pass_: AtenToDialectPass) -> DialectNodeSpec:
2424
(input_node, dim),
2525
{},
2626
)
27+
28+
29+
def rewrite_binary_operator(
30+
node: Node, pass_: AtenToDialectPass
31+
) -> DialectNodeSpec | None:
32+
match node.target:
33+
case exir_ops.edge.aten.add.Tensor:
34+
target = exir_ops.backend.tosa.ADD.default
35+
case exir_ops.edge.aten.bitwise_and.Tensor:
36+
target = exir_ops.backend.tosa.BITWISE_AND.default
37+
case exir_ops.edge.aten.bitwise_left_shift.Tensor:
38+
target = exir_ops.backend.tosa.LOGICAL_LEFT_SHIFT.default
39+
case exir_ops.edge.aten.bitwise_or.Tensor:
40+
target = exir_ops.backend.tosa.BITWISE_OR.default
41+
case exir_ops.edge.aten.bitwise_right_shift.Tensor:
42+
target = exir_ops.backend.tosa.ARITHMETIC_RIGHT_SHIFT.default
43+
case exir_ops.edge.aten.bitwise_xor.Tensor:
44+
target = exir_ops.backend.tosa.BITWISE_XOR.default
45+
case exir_ops.edge.aten.eq.Tensor:
46+
target = exir_ops.backend.tosa.EQUAL.default
47+
case exir_ops.edge.aten.ge.Tensor:
48+
target = exir_ops.backend.tosa.GREATER_EQUAL.default
49+
case exir_ops.edge.aten.gt.Tensor:
50+
target = exir_ops.backend.tosa.GREATER.default
51+
case exir_ops.edge.aten.logical_and.default:
52+
target = exir_ops.backend.tosa.LOGICAL_AND.default
53+
case exir_ops.edge.aten.logical_or.default:
54+
target = exir_ops.backend.tosa.LOGICAL_OR.default
55+
case exir_ops.edge.aten.logical_xor.default:
56+
target = exir_ops.backend.tosa.LOGICAL_XOR.default
57+
case exir_ops.edge.aten.maximum.default:
58+
target = exir_ops.backend.tosa.MAXIMUM.default
59+
case exir_ops.edge.aten.minimum.default:
60+
target = exir_ops.backend.tosa.MINIMUM.default
61+
case exir_ops.edge.aten.mul.Tensor:
62+
target = exir_ops.backend.tosa.MUL.default
63+
case exir_ops.edge.aten.pow.Tensor_Tensor:
64+
target = exir_ops.backend.tosa.POW.default
65+
case exir_ops.edge.aten.sub.Tensor:
66+
target = exir_ops.backend.tosa.SUB.default
67+
case _:
68+
return None
69+
70+
return DialectNodeSpec(target, node.args, dict(node.kwargs))

backends/arm/_passes/exir_to_tosa_pass.py

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,24 @@
33
# This source code is licensed under the BSD-style license found in the
44
# LICENSE file in the root directory of this source tree.
55

6+
from collections.abc import Callable
7+
68
import executorch.backends.arm.tosa.dialect # noqa: F401
79
from executorch.backends.arm._passes.aten_to_tosa_activation_functions import (
810
get_activation_replacement,
911
)
10-
from executorch.backends.arm._passes.aten_to_tosa_tensor_operators import rewrite_argmax
12+
from executorch.backends.arm._passes.aten_to_tosa_tensor_operators import (
13+
rewrite_argmax,
14+
rewrite_binary_operator,
15+
)
1116
from executorch.backends.transforms.aten_to_dialect_pass import (
1217
AtenToDialectPass,
1318
DialectNodeSpec,
19+
SubstitutionFn,
1420
)
1521
from executorch.exir.dialects._ops import ops as exir_ops
1622
from torch.fx import Node
23+
from torch.fx.node import Target
1724

1825

1926
class ExirToTosaPass(AtenToDialectPass):
@@ -25,17 +32,55 @@ class ExirToTosaPass(AtenToDialectPass):
2532
"""
2633

2734

35+
def register_dialect_substitutions(
36+
*targets: Target,
37+
) -> Callable[[SubstitutionFn], SubstitutionFn]:
38+
def decorator(func: SubstitutionFn) -> SubstitutionFn:
39+
for target in targets:
40+
ExirToTosaPass.register_dialect_substitution(target)(func)
41+
return func
42+
43+
return decorator
44+
45+
2846
@ExirToTosaPass.register_dialect_substitution(exir_ops.edge.aten.argmax.default)
2947
def _get_tensor_operators_replacement(
3048
node: Node, pass_: AtenToDialectPass
3149
) -> DialectNodeSpec:
3250
return rewrite_argmax(node, pass_)
3351

3452

35-
@ExirToTosaPass.register_dialect_substitution(exir_ops.edge.aten.clamp.default)
36-
@ExirToTosaPass.register_dialect_substitution(exir_ops.edge.aten.erf.default)
37-
@ExirToTosaPass.register_dialect_substitution(exir_ops.edge.aten.sigmoid.default)
38-
@ExirToTosaPass.register_dialect_substitution(exir_ops.edge.aten.tanh.default)
53+
@register_dialect_substitutions(
54+
exir_ops.edge.aten.add.Tensor,
55+
exir_ops.edge.aten.bitwise_and.Tensor,
56+
exir_ops.edge.aten.bitwise_left_shift.Tensor,
57+
exir_ops.edge.aten.bitwise_or.Tensor,
58+
exir_ops.edge.aten.bitwise_right_shift.Tensor,
59+
exir_ops.edge.aten.bitwise_xor.Tensor,
60+
exir_ops.edge.aten.eq.Tensor,
61+
exir_ops.edge.aten.ge.Tensor,
62+
exir_ops.edge.aten.gt.Tensor,
63+
exir_ops.edge.aten.logical_and.default,
64+
exir_ops.edge.aten.logical_or.default,
65+
exir_ops.edge.aten.logical_xor.default,
66+
exir_ops.edge.aten.maximum.default,
67+
exir_ops.edge.aten.minimum.default,
68+
exir_ops.edge.aten.mul.Tensor,
69+
exir_ops.edge.aten.pow.Tensor_Tensor,
70+
exir_ops.edge.aten.sub.Tensor,
71+
)
72+
def _get_binary_operator_replacement(
73+
node: Node, pass_: AtenToDialectPass
74+
) -> DialectNodeSpec | None:
75+
return rewrite_binary_operator(node, pass_)
76+
77+
78+
@register_dialect_substitutions(
79+
exir_ops.edge.aten.clamp.default,
80+
exir_ops.edge.aten.erf.default,
81+
exir_ops.edge.aten.sigmoid.default,
82+
exir_ops.edge.aten.tanh.default,
83+
)
3984
def _get_activation_replacement(
4085
node: Node, pass_: AtenToDialectPass
4186
) -> DialectNodeSpec | None:

0 commit comments

Comments
 (0)