55# This source code is licensed under the BSD-style license found in the
66# LICENSE file in the root directory of this source tree.
77
8- # Generates the ExecuTorch ESP-IDF component and a kernel bundle for the smoke
9- # model, builds the idf_component_smoke consumer for ESP32-C6 with that bundle,
10- # plus the Cadence kernels, and for ESP32-S3 with one the build generates
11- # itself, and runs the ESP32-S3 build on the emulator. Also checks that stale or
12- # mismatched bundles fail at configure time, and that an app declaring nothing
13- # links to the same size as one without the component.
8+ # Generates the ExecuTorch ESP-IDF component and runs the idf_component_smoke
9+ # consumer on the emulator with three models: the portable-kernel smoke model, a
10+ # mul on the esp_nn:: op library and an ESP-NN delegate program. An ESP32-S3
11+ # build runs all three with a kernel bundle the build makes itself; an ESP32-C6
12+ # build runs the first two, with the Cadence kernels too, and must not link the
13+ # delegate; two more ESP32-S3 builds run the delegate alone, from a bundle with
14+ # no kernel libraries and from executorch_add_backend with no bundle. Also
15+ # checks that stale or mismatched bundles fail at configure time, and that an
16+ # app declaring nothing links to the same size as one without the component.
1417
1518set -euo pipefail
1619
@@ -39,16 +42,38 @@ bash examples/espressif/setup.sh --minimal --targets=esp32s3,esp32c6 2>&1 \
3942# shellcheck source=/dev/null
4043source " ${ESPRESSIF_TOOLS_DIR} /setup_path.sh"
4144
42- python examples/espressif/export_smoke_model.py \
43- --output " ${build_dir} /smoke.bpte" --pte-output " ${build_dir} /smoke.pte" \
45+ python examples/espressif/export_smoke_model.py --output " ${build_dir} /smoke.bpte" \
4446 2>&1 | tee " ${build_dir} /export.log"
47+ python examples/espressif/idf_component_smoke/export_op_library_model.py \
48+ --output " ${build_dir} /mul.bpte" 2>&1 | tee -a " ${build_dir} /export.log"
49+ python examples/espressif/export_espnn_test_programs.py --output-dir " ${build_dir} /espnn" \
50+ --only conv-act-pool-linear 2>&1 | tee -a " ${build_dir} /export.log"
51+ cp " ${build_dir} /espnn/conv-act-pool-linear.bpte" " ${build_dir} /delegate.bpte"
52+ split=examples/espressif/idf_component_smoke/split_bundled_program.py
53+ python " ${split} " " ${build_dir} /smoke.bpte" --atol 0 --rtol 0
54+ # The tolerance test_mul.py uses.
55+ python " ${split} " " ${build_dir} /mul.bpte" --atol 0 --rtol 1.2e-7
56+ read -r atol rtol < <( python -c '
57+ import json, sys
58+ (entry,) = json.load(open(sys.argv[1]))
59+ print(entry["atol"], entry["rtol"])
60+ ' " ${build_dir} /espnn/manifest.json" )
61+ python " ${split} " " ${build_dir} /delegate.bpte" --atol " ${atol} " --rtol " ${rtol} "
62+ cpu_models=" ${build_dir} /smoke.pte;${build_dir} /mul.pte"
63+ models=" ${cpu_models} ;${build_dir} /delegate.pte"
4564
4665component_dir=" ${build_dir} /components/executorch"
4766kernels_dir=" ${build_dir} /smoke_kernels"
4867bash examples/espressif/build_idf_component.sh --out " ${component_dir} " \
4968 2>&1 | tee " ${build_dir} /generate.log"
50- python " ${component_dir} /tools/gen_kernels.py" --pte " ${build_dir} /smoke.pte" \
51- --out " ${kernels_dir} " 2>&1 | tee " ${build_dir} /gen_kernels.log"
69+ gen_kernels () {
70+ local out=$1
71+ shift
72+ python " ${component_dir} /tools/gen_kernels.py" " $@ " --out " ${out} " \
73+ 2>&1 | tee -a " ${build_dir} /gen_kernels.log"
74+ }
75+ gen_kernels " ${kernels_dir} " --pte " ${build_dir} /smoke.pte" --pte " ${build_dir} /mul.pte" \
76+ --pte " ${build_dir} /delegate.pte"
5277# The ESP32-C6 build also registers the ops of a Cadence-quantized streaming
5378# wake word model, so CI compiles and links the Cadence generic kernels.
5479wakeword_ops=aten::_softmax.out,aten::_to_copy.out,aten::cat.out,aten::copy_
@@ -58,9 +83,17 @@ wakeword_ops+=,cadence::dequantize_per_tensor.out,cadence::quantize_per_tensor.o
5883wakeword_ops+=,cadence::quantized_conv2d_nhwc.per_tensor_out
5984wakeword_ops+=,cadence::quantized_fully_connected.per_tensor_out
6085wakeword_ops+=,cadence::quantized_relu.per_tensor_out
61- python " ${component_dir} /tools/gen_kernels.py" --pte " ${build_dir} /smoke.pte" \
62- --ops " ${wakeword_ops} " --out " ${build_dir} /wakeword_kernels" \
63- 2>&1 | tee -a " ${build_dir} /gen_kernels.log"
86+ gen_kernels " ${build_dir} /cpu_kernels" --pte " ${build_dir} /smoke.pte" \
87+ --pte " ${build_dir} /mul.pte" --ops " ${wakeword_ops} "
88+ gen_kernels " ${build_dir} /delegate_kernels" --pte " ${build_dir} /delegate.pte"
89+ python - " ${build_dir} /delegate_kernels/kernels.json" << 'PY '
90+ import json
91+ import sys
92+
93+ libraries = json.load(open(sys.argv[1]))["libraries"]
94+ if libraries:
95+ sys.exit(f"FAIL: the delegate-only bundle has kernel libraries {sorted(libraries)}")
96+ PY
6497
6598project=examples/espressif/idf_component_smoke
6699
@@ -94,7 +127,8 @@ mkdir -p "${build_dir}/stale"
94127cp " ${build_dir} /smoke.pte" " ${build_dir} /stale/smoke.pte"
95128printf ' \0' >> " ${build_dir} /stale/smoke.pte"
96129expect_configure_error stale-model " smoke.pte: changed since the bundle was made" \
97- -DET_SMOKE_PTE=" ${build_dir} /stale/smoke.pte" -DET_SMOKE_KERNELS=" ${kernels_dir} "
130+ -DET_SMOKE_MODELS=" ${build_dir} /stale/smoke.pte;${build_dir} /mul.pte;${build_dir} /delegate.pte" \
131+ -DET_SMOKE_KERNELS=" ${kernels_dir} "
98132
99133rm -rf " ${build_dir} /mismatch_kernels"
100134cp -r " ${kernels_dir} " " ${build_dir} /mismatch_kernels"
@@ -107,7 +141,7 @@ bundle["component_version"] = "0.0.0"
107141json.dump(bundle, open(sys.argv[1], "w"))
108142PY
109143expect_configure_error version-mismatch component_version \
110- -DET_SMOKE_PTE =" ${build_dir} /smoke.pte " \
144+ -DET_SMOKE_MODELS =" ${models} " \
111145 -DET_SMOKE_KERNELS=" ${build_dir} /mismatch_kernels"
112146
113147# A bundle made by a component of the same version from another commit, as two
@@ -123,23 +157,23 @@ bundle["component_commit"] = "0" * 40
123157json.dump(bundle, open(sys.argv[1], "w"))
124158PY
125159expect_configure_error commit-mismatch component_commit \
126- -DET_SMOKE_PTE =" ${build_dir} /smoke.pte " \
160+ -DET_SMOKE_MODELS =" ${models} " \
127161 -DET_SMOKE_KERNELS=" ${build_dir} /mismatch_kernels"
128162
129163# Without an ExecuTorch Python to generate one, a missing bundle is an error.
130164python -m venv --without-pip --clear " ${build_dir} /bare_python"
131165EXECUTORCH_PYTHON=" ${build_dir} /bare_python/bin/python" expect_configure_error \
132- no-bundle " cannot import executorch" -DET_SMOKE_PTE =" ${build_dir} /smoke.pte "
166+ no-bundle " cannot import executorch" -DET_SMOKE_MODELS =" ${models} "
133167expect_configure_error relative-python " must be the absolute path" \
134- -DET_SMOKE_PTE =" ${build_dir} /smoke.pte " -DEXECUTORCH_PYTHON=python
168+ -DET_SMOKE_MODELS =" ${models} " -DEXECUTORCH_PYTHON=python
135169
136170# A kernel registry smaller than the bundle; the emulator run below uses one
137171# larger than it.
138172printf ' CONFIG_ET_MAX_KERNEL_NUM=1\n' > " ${build_dir} /small_registry.defaults"
139173printf ' CONFIG_ET_MAX_KERNEL_NUM=64\n' > " ${build_dir} /large_registry.defaults"
140174expect_configure_error small-registry " CONFIG_ET_MAX_KERNEL_NUM=1 is below" \
141175 " -DSDKCONFIG_DEFAULTS=sdkconfig.defaults;${build_dir} /small_registry.defaults" \
142- -DET_SMOKE_PTE =" ${build_dir} /smoke.pte " -DET_SMOKE_KERNELS=" ${kernels_dir} "
176+ -DET_SMOKE_MODELS =" ${models} " -DET_SMOKE_KERNELS=" ${kernels_dir} "
143177
144178# An app that declares nothing must not grow because the component is in the
145179# build. The baseline swaps the component for an empty one of the same name.
170204printf ' CONFIG_LIBC_NEWLIB=y\nCONFIG_LIBC_NEWLIB_NANO_FORMAT=y\n' > " ${build_dir} /nano.defaults"
171205consumer nano esp32s3 -DET_COMPONENT_DIR=" ${component_dir} " \
172206 " -DSDKCONFIG_DEFAULTS=sdkconfig.defaults;${build_dir} /nano.defaults" \
173- -DET_SMOKE_PTE =" ${build_dir} /smoke.pte " -DET_SMOKE_KERNELS=" ${kernels_dir} " reconfigure
207+ -DET_SMOKE_MODELS =" ${models} " -DET_SMOKE_KERNELS=" ${kernels_dir} " reconfigure
174208if ! tr -s ' \n' ' ' < " ${build_dir} /firmware-nano.log" \
175209 | grep -qF " ExecuTorch logging is DISABLED" ; then
176210 echo " FAIL: no nano printf warning; see ${build_dir} /firmware-nano.log" >&2
177211 exit 1
178212fi
179213
180- consumer smoke-esp32c6 esp32c6 -DET_COMPONENT_DIR=" ${component_dir} " \
181- -DET_SMOKE_PTE=" ${build_dir} /smoke.pte" -DET_SMOKE_KERNELS=" ${build_dir} /wakeword_kernels" build
214+ # Runs a build on the emulator and checks its output: build name, chip, then
215+ # the per-model markers.
216+ run_firmware () {
217+ local name=$1 chip=$2
218+ shift 2
219+ local firmware_dir=" ${build_dir} /firmware-${name} "
220+ IDF_TARGET=" ${chip} " idf.py -C " ${project} " -B " ${firmware_dir} " merge-bin \
221+ > " ${build_dir} /merge-${name} .log" 2>&1
222+ timeout 130s esp-emu --chip " ${chip} " \
223+ --firmware " ${firmware_dir} /merged-binary.bin" \
224+ --elf " ${firmware_dir} /et_component_smoke.elf" \
225+ --net user --timeout 120s --exit-on ' Program complete.' \
226+ 2>&1 | tee " ${build_dir} /emulator-${name} .log"
227+ for marker in " $@ " " ET_COMPONENT_SMOKE: PASS" " Program complete." ; do
228+ if ! grep -qF " ${marker} " " ${build_dir} /emulator-${name} .log" ; then
229+ echo " FAIL: ${name} printed no '${marker} '; see emulator-${name} .log" >&2
230+ exit 1
231+ fi
232+ done
233+ echo " OK: ${name} passed on the ${chip} emulator"
234+ }
235+
182236# No bundle declared: the build makes one with its own Python, which has
183237# executorch here, and it must match the one gen_kernels.py made above.
184- consumer smoke -esp32s3 esp32s3 -DET_COMPONENT_DIR=" ${component_dir} " \
238+ consumer all -esp32s3 esp32s3 -DET_COMPONENT_DIR=" ${component_dir} " \
185239 " -DSDKCONFIG_DEFAULTS=sdkconfig.defaults;${build_dir} /large_registry.defaults" \
186- -DET_SMOKE_PTE=" ${build_dir} /smoke.pte" build
187- diff -r " ${build_dir} /firmware-smoke-esp32s3/executorch_kernels" " ${kernels_dir} "
188-
189- firmware_dir=" ${build_dir} /firmware-smoke-esp32s3"
190- idf.py -C " ${project} " -B " ${firmware_dir} " merge-bin \
191- 2>&1 | tee " ${build_dir} /firmware-merge.log"
192- timeout 130s esp-emu --chip esp32s3 \
193- --firmware " ${firmware_dir} /merged-binary.bin" \
194- --elf " ${firmware_dir} /et_component_smoke.elf" \
195- --net user --timeout 120s --exit-on ' Program complete.' \
196- 2>&1 | tee " ${build_dir} /emulator.log"
197- for marker in " ET_COMPONENT_SMOKE: PASS" " Program complete." ; do
198- if ! grep -qF " ${marker} " " ${build_dir} /emulator.log" ; then
199- echo " FAIL: missing '${marker} '; see ${build_dir} /emulator.log" >&2
200- exit 1
201- fi
202- done
203- echo " PASS: generated ESP-IDF component ran the smoke model on the ESP32-S3 emulator."
240+ -DET_SMOKE_MODELS=" ${models} " build
241+ diff -r " ${build_dir} /firmware-all-esp32s3/executorch_kernels" " ${kernels_dir} "
242+ run_firmware all-esp32s3 esp32s3 " smoke: PASS" " mul: PASS" " delegate: PASS"
243+
244+ # A project may provide esp-nn as its own component instead of the managed one.
245+ local_project=" ${build_dir} /local-esp-nn"
246+ rm -rf " ${local_project} "
247+ mkdir -p " ${local_project} /components"
248+ cp -r " ${project} /CMakeLists.txt" " ${project} /main" " ${project} /sdkconfig.defaults" \
249+ " ${local_project} /"
250+ cp -r " ${project} /managed_components/espressif__esp-nn" " ${local_project} /components/esp-nn"
251+ project=" ${local_project} " consumer local-esp-nn esp32s3 -DET_COMPONENT_DIR=" ${component_dir} " \
252+ -DET_SMOKE_MODELS=" ${build_dir} /delegate.pte" \
253+ -DET_SMOKE_KERNELS=" ${build_dir} /delegate_kernels" reconfigure
254+ echo " OK: a project with its own components/esp-nn configures"
255+
256+ consumer cpu-esp32c6 esp32c6 -DET_COMPONENT_DIR=" ${component_dir} " \
257+ -DET_SMOKE_MODELS=" ${cpu_models} " -DET_SMOKE_KERNELS=" ${build_dir} /cpu_kernels" build
258+ # The esp_nn:: op library must not pull in the delegate.
259+ if riscv32-esp-elf-nm -C " ${build_dir} /firmware-cpu-esp32c6/et_component_smoke.elf" \
260+ | grep -q EspNNBackend; then
261+ echo " FAIL: cpu-esp32c6 links EspNNBackend, which none of its models uses" >&2
262+ exit 1
263+ fi
264+ run_firmware cpu-esp32c6 esp32c6 " smoke: PASS" " mul: PASS"
265+
266+ consumer delegate-esp32s3 esp32s3 -DET_COMPONENT_DIR=" ${component_dir} " \
267+ -DET_SMOKE_MODELS=" ${build_dir} /delegate.pte" \
268+ -DET_SMOKE_KERNELS=" ${build_dir} /delegate_kernels" build
269+ run_firmware delegate-esp32s3 esp32s3 " delegate: PASS"
270+
271+ # Models not known at build time: only the delegate is declared.
272+ consumer backend-esp32s3 esp32s3 -DET_COMPONENT_DIR=" ${component_dir} " \
273+ -DET_SMOKE_MODELS=" ${build_dir} /delegate.pte" -DET_SMOKE_BACKENDS=EspNNBackend build
274+ run_firmware backend-esp32s3 esp32s3 " delegate: PASS"
275+ # Without a bundle nothing registers kernels, so the registry reserves no room.
276+ registry=$( xtensa-esp32s3-elf-nm -S -C " ${build_dir} /firmware-backend-esp32s3/et_component_smoke.elf" \
277+ | awk ' /registered_kernels_data/ {print $2}' )
278+ if (( 16 #${registry:- 0} > 64 )) ; then
279+ echo " FAIL: backend-esp32s3 reserves $(( 16 #${registry} )) B for kernels it never registers" >&2
280+ exit 1
281+ fi
282+
283+ echo " PASS: generated ESP-IDF component ran every build on the emulator."
0 commit comments