Skip to content

Commit df483db

Browse files
authored
Merge branch 'main' into tosa_tools_update_1
2 parents 1d0b591 + 3600d4f commit df483db

File tree

108 files changed

+2372
-1825
lines changed

Some content is hidden

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

108 files changed

+2372
-1825
lines changed

.github/scripts/extract_benchmark_results.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,7 @@ def transform(
360360
"app_type": app_type,
361361
# Just keep a copy of the benchmark config here
362362
"benchmark_config": json.dumps(benchmark_config),
363+
"job_conclusion": "SUCCESS",
363364
},
364365
},
365366
"model": {
@@ -455,7 +456,7 @@ def transform_failure_record(
455456
},
456457
"metric": {
457458
"name": "FAILURE_REPORT",
458-
"benchmark_values": 0,
459+
"benchmark_values": [0],
459460
"target_value": 0,
460461
"extra_info": {
461462
"method": "",

.github/scripts/run_nm.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import re
8+
import subprocess
9+
import sys
10+
from dataclasses import dataclass
11+
from typing import Dict, List, Optional, Union
12+
13+
14+
@dataclass
15+
class Symbol:
16+
name: str
17+
addr: int
18+
size: int
19+
symbol_type: str
20+
21+
22+
class Parser:
23+
def __init__(self, elf: str, toolchain_prefix: str = "", filter=None):
24+
self.elf = elf
25+
self.toolchain_prefix = toolchain_prefix
26+
self.symbols: Dict[str, Symbol] = self._get_nm_output()
27+
self.filter = filter
28+
29+
@staticmethod
30+
def run_nm(
31+
elf_file_path: str, args: Optional[List[str]] = None, nm: str = "nm"
32+
) -> str:
33+
"""
34+
Run the nm command on the specified ELF file.
35+
"""
36+
args = [] if args is None else args
37+
cmd = [nm] + args + [elf_file_path]
38+
try:
39+
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
40+
return result.stdout
41+
except FileNotFoundError:
42+
print(f"Error: 'nm' command not found. Please ensure it's installed.")
43+
sys.exit(1)
44+
except subprocess.CalledProcessError as e:
45+
print(f"Error running nm on {elf_file_path}: {e}")
46+
print(f"stderr: {e.stderr}")
47+
sys.exit(1)
48+
49+
def _get_nm_output(self) -> Dict[str, Symbol]:
50+
args = [
51+
"--print-size",
52+
"--size-sort",
53+
"--reverse-sort",
54+
"--demangle",
55+
"--format=bsd",
56+
]
57+
output = Parser.run_nm(
58+
self.elf,
59+
args,
60+
nm=self.toolchain_prefix + "nm" if self.toolchain_prefix else "nm",
61+
)
62+
lines = output.splitlines()
63+
symbols = []
64+
symbol_pattern = re.compile(
65+
r"(?P<addr>[0-9a-fA-F]+)\s+(?P<size>[0-9a-fA-F]+)\s+(?P<type>\w)\s+(?P<name>.+)"
66+
)
67+
68+
def parse_line(line: str) -> Optional[Symbol]:
69+
70+
match = symbol_pattern.match(line)
71+
if match:
72+
addr = int(match.group("addr"), 16)
73+
size = int(match.group("size"), 16)
74+
type_ = match.group("type").strip().strip("\n")
75+
name = match.group("name").strip().strip("\n")
76+
return Symbol(name=name, addr=addr, size=size, symbol_type=type_)
77+
return None
78+
79+
for line in lines:
80+
symbol = parse_line(line)
81+
if symbol:
82+
symbols.append(symbol)
83+
84+
assert len(symbols) > 0, "No symbols found in nm output"
85+
if len(symbols) != len(lines):
86+
print(
87+
"** Warning: Not all lines were parsed, check the output of nm. Parsed {len(symbols)} lines, given {len(lines)}"
88+
)
89+
if any(symbol.size == 0 for symbol in symbols):
90+
print("** Warning: Some symbols have zero size, check the output of nm.")
91+
92+
# TODO: Populate the section and module fields from the linker map if available (-Wl,-Map=linker.map)
93+
return {symbol.name: symbol for symbol in symbols}
94+
95+
def print(self):
96+
print(f"Elf: {self.elf}")
97+
98+
def print_table(filter=None, filter_name=None):
99+
print("\nAddress\t\tSize\tType\tName")
100+
# Apply filter and sort symbols
101+
symbols_to_print = {
102+
name: sym
103+
for name, sym in self.symbols.items()
104+
if not filter or filter(sym)
105+
}
106+
sorted_symbols = sorted(
107+
symbols_to_print.items(), key=lambda x: x[1].size, reverse=True
108+
)
109+
110+
# Print symbols and calculate total size
111+
size_total = 0
112+
for name, sym in sorted_symbols:
113+
print(f"{hex(sym.addr)}\t\t{sym.size}\t{sym.symbol_type}\t{sym.name}")
114+
size_total += sym.size
115+
116+
# Print summary
117+
symbol_percent = len(symbols_to_print) / len(self.symbols) * 100
118+
print("-----")
119+
print(f"> Total bytes: {size_total}")
120+
print(
121+
f"Counted: {len(symbols_to_print)}/{len(self.symbols)}, {symbol_percent:0.2f}% (filter: '{filter_name}')"
122+
)
123+
print("=====\n")
124+
125+
# Print tables with different filters
126+
def is_executorch_symbol(s):
127+
return "executorch" in s.name or s.name.startswith("et")
128+
129+
FILTER_NAME_TO_FILTER_AND_LABEL = {
130+
"all": (None, "All"),
131+
"executorch": (is_executorch_symbol, "ExecuTorch"),
132+
"executorch_text": (
133+
lambda s: is_executorch_symbol(s) and s.symbol_type.lower() == "t",
134+
"ExecuTorch .text",
135+
),
136+
}
137+
138+
filter_func, label = FILTER_NAME_TO_FILTER_AND_LABEL.get(
139+
self.filter, FILTER_NAME_TO_FILTER_AND_LABEL["all"]
140+
)
141+
print_table(filter_func, label)
142+
143+
144+
if __name__ == "__main__":
145+
import argparse
146+
147+
parser = argparse.ArgumentParser(
148+
description="Process ELF file and linker map file."
149+
)
150+
parser.add_argument(
151+
"-e", "--elf-file-path", required=True, help="Path to the ELF file"
152+
)
153+
parser.add_argument(
154+
"-f",
155+
"--filter",
156+
required=False,
157+
default="all",
158+
help="Filter symbols by pre-defined filters",
159+
choices=["all", "executorch", "executorch_text"],
160+
)
161+
parser.add_argument(
162+
"-p",
163+
"--toolchain-prefix",
164+
required=False,
165+
default="",
166+
help="Optional toolchain prefix for nm",
167+
)
168+
169+
args = parser.parse_args()
170+
p = Parser(args.elf_file_path, args.toolchain_prefix, filter=args.filter)
171+
p.print()

.github/workflows/trunk.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,60 @@ jobs:
231231
# Run arm unit tests using the simulator
232232
backends/arm/test/test_arm_baremetal.sh test_pytest_ethosu_fvp
233233
234+
test-arm-cortex-m-size-test:
235+
name: test-arm-cortex-m-size-test
236+
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
237+
permissions:
238+
id-token: write
239+
contents: read
240+
with:
241+
runner: linux.2xlarge
242+
docker-image: executorch-ubuntu-22.04-arm-sdk
243+
submodules: 'true'
244+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
245+
timeout: 90
246+
script: |
247+
# The generic Linux job chooses to use base env, not the one setup by the image
248+
CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]")
249+
conda activate "${CONDA_ENV}"
250+
251+
source .ci/scripts/utils.sh
252+
install_executorch "--use-pt-pinned-commit"
253+
.ci/scripts/setup-arm-baremetal-tools.sh
254+
source examples/arm/ethos-u-scratch/setup_path.sh
255+
256+
# User baremetal toolchain
257+
arm-none-eabi-c++ --version
258+
toolchain_cmake=examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake
259+
toolchain_cmake=$(realpath ${toolchain_cmake})
260+
261+
# Build and test size test
262+
bash test/build_size_test.sh "-DCMAKE_TOOLCHAIN_FILE=${toolchain_cmake} -DEXECUTORCH_BUILD_ARM_BAREMETAL=ON"
263+
elf="cmake-out/test/size_test"
264+
265+
# Dump basic info
266+
ls -al ${elf}
267+
arm-none-eabi-size ${elf}
268+
269+
# Dump symbols
270+
python .github/scripts/run_nm.py -e ${elf}
271+
python .github/scripts/run_nm.py -e ${elf} -f "executorch" -p "arm-none-eabi-"
272+
python .github/scripts/run_nm.py -e ${elf} -f "executorch_text" -p "arm-none-eabi-"
273+
274+
# Add basic guard - TODO: refine this!
275+
arm-none-eabi-strip ${elf}
276+
output=$(ls -la ${elf})
277+
arr=($output)
278+
size=${arr[4]}
279+
threshold="102400" # 100KiB
280+
echo "size: $size, threshold: $threshold"
281+
if [[ "$size" -le "$threshold" ]]; then
282+
echo "Success $size <= $threshold"
283+
else
284+
echo "Fail $size > $threshold"
285+
exit 1
286+
fi
287+
234288
test-coreml-delegate:
235289
name: test-coreml-delegate
236290
uses: pytorch/test-infra/.github/workflows/macos_job.yml@main

CMakeLists.txt

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,18 @@ target_link_options_shared_lib(executorch)
645645
# Real integrations should supply their own YAML file that only lists the
646646
# operators necessary for the models that will run.
647647
#
648+
if(EXECUTORCH_BUILD_KERNELS_OPTIMIZED)
649+
# find pytorch lib here to make it available to all
650+
# sub-directories. Find it before including portable so that
651+
# optimized_portable_kernels can use it.
652+
find_package_torch_headers()
653+
endif()
654+
648655
if(BUILD_EXECUTORCH_PORTABLE_OPS)
649656
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/kernels/portable)
650657
endif()
651658

652659
if(EXECUTORCH_BUILD_KERNELS_OPTIMIZED)
653-
# find pytorch lib here to make it available to all sub-directories
654-
find_package_torch_headers()
655660
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/kernels/optimized)
656661
endif()
657662

@@ -764,10 +769,6 @@ if(EXECUTORCH_BUILD_EXTENSION_MODULE)
764769
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extension/module)
765770
endif()
766771

767-
if(EXECUTORCH_BUILD_EXTENSION_TRAINING)
768-
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extension/training)
769-
endif()
770-
771772
if(EXECUTORCH_BUILD_EXTENSION_RUNNER_UTIL)
772773
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extension/runner_util)
773774
endif()
@@ -872,34 +873,13 @@ if(EXECUTORCH_BUILD_PYBIND)
872873

873874
if(EXECUTORCH_BUILD_EXTENSION_TRAINING)
874875

875-
set(_pybind_training_dep_libs
876-
${TORCH_PYTHON_LIBRARY}
877-
etdump
878-
executorch
879-
util
880-
torch
881-
extension_training
882-
)
883-
884-
if(EXECUTORCH_BUILD_XNNPACK)
885-
# need to explicitly specify XNNPACK and microkernels-prod
886-
# here otherwise uses XNNPACK and microkernel-prod symbols from libtorch_cpu
887-
list(APPEND _pybind_training_dep_libs xnnpack_backend XNNPACK microkernels-prod)
888-
endif()
889-
890-
# pybind training
891-
pybind11_add_module(_training_lib SHARED extension/training/pybindings/_training_lib.cpp)
892-
893-
target_include_directories(_training_lib PRIVATE ${TORCH_INCLUDE_DIRS})
894-
target_compile_options(_training_lib PUBLIC ${_pybind_compile_options})
895-
target_link_libraries(_training_lib PRIVATE ${_pybind_training_dep_libs})
896-
897-
install(TARGETS _training_lib
898-
LIBRARY DESTINATION executorch/extension/training/pybindings
899-
)
900876
endif()
901877
endif()
902878

879+
if(EXECUTORCH_BUILD_EXTENSION_TRAINING)
880+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extension/training)
881+
endif()
882+
903883
if(EXECUTORCH_BUILD_KERNELS_CUSTOM)
904884
# TODO: move all custom kernels to ${CMAKE_CURRENT_SOURCE_DIR}/kernels/custom
905885
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/extension/llm/custom_ops)

backends/apple/coreml/test/test_coreml_quantizer.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def quantize_and_compare(
3232
) -> None:
3333
assert quantization_type in {"PTQ", "QAT"}
3434

35-
pre_autograd_aten_dialect = export_for_training(model, example_inputs).module()
35+
pre_autograd_aten_dialect = export_for_training(
36+
model, example_inputs, strict=True
37+
).module()
3638

3739
quantization_config = LinearQuantizerConfig.from_dict(
3840
{

backends/apple/mps/test/test_mps_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def lower_module_and_test_output(
207207
expected_output = model(*sample_inputs)
208208

209209
model = torch.export.export_for_training(
210-
model, sample_inputs, dynamic_shapes=dynamic_shapes
210+
model, sample_inputs, dynamic_shapes=dynamic_shapes, strict=True
211211
).module()
212212

213213
edge_program = export_to_edge(

backends/arm/operators/op_table.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def define_node(
4040

4141
if inputs[0].dtype not in (ts.DType.INT8, ts.DType.INT16):
4242
raise ValueError(
43-
f"TOSA.TABLE only supports int8 or int16 inputs, got {ts.DTypeNames[inputs[0]]}"
43+
f"TOSA.TABLE only supports int8 or int16 inputs, got {ts.DTypeNames[inputs[0].dtype]}"
4444
)
4545

4646
table = self._exported_program.state_dict[node.name] # type: ignore[union-attr]

backends/arm/test/misc/test_debug_feats.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,13 @@ def test_collate_tosa_BI_tests(self):
192192
.to_edge_transform_and_lower()
193193
.to_executorch()
194194
)
195+
196+
test_collate_dir = "test_collate_tosa_tests/tosa-bi/TestCollateTosaTests/test_collate_tosa_BI_tests"
195197
# test that the output directory is created and contains the expected files
196-
assert os.path.exists(
197-
"test_collate_tosa_tests/tosa-bi/TestCollateTosaTests/test_collate_tosa_BI_tests"
198-
)
199-
assert os.path.exists(
200-
"test_collate_tosa_tests/tosa-bi/TestCollateTosaTests/test_collate_tosa_BI_tests/output_tag6_TOSA-0.80+BI.tosa"
201-
)
202-
assert os.path.exists(
203-
"test_collate_tosa_tests/tosa-bi/TestCollateTosaTests/test_collate_tosa_BI_tests/desc_tag6_TOSA-0.80+BI.json"
204-
)
198+
assert os.path.exists(test_collate_dir)
199+
200+
for file in os.listdir(test_collate_dir):
201+
assert file.endswith(("TOSA-0.80+BI.json", "TOSA-0.80+BI.tosa"))
205202

206203
os.environ.pop("TOSA_TESTCASES_BASE_PATH")
207204
shutil.rmtree("test_collate_tosa_tests", ignore_errors=True)

backends/arm/test/misc/test_partition_decomposed_quantized_ops.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ def test_linear_residaul_tosa_MI(test_data: input_t1):
145145
pipeline.run()
146146

147147

148-
@common.parametrize("test_data", test_data)
148+
@common.parametrize(
149+
"test_data",
150+
test_data,
151+
{"3d_rand": "MLETORCH-855: Issue with Quantization folding."},
152+
strict=False,
153+
)
149154
def test_linear_residual_tosa_BI(test_data: input_t1):
150155
pipeline = TosaPipelineBI[input_t1](
151156
LinearResidualModule(),

0 commit comments

Comments
 (0)