tan-cli 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tan/__init__.py +0 -0
- tan/__main__.py +120 -0
- tan/cli.py +939 -0
- tan/command_table.py +265 -0
- tan/commands/__init__.py +8 -0
- tan/commands/bootstrap_cmd.py +4856 -0
- tan/commands/bootstrap_patches.py +164 -0
- tan/commands/bootstrap_toolchain_fetch.py +155 -0
- tan/commands/bootstrap_toolchain_pin.py +36 -0
- tan/commands/build/__init__.py +1 -0
- tan/commands/build/configure_inputs.py +277 -0
- tan/commands/build/execute.py +1852 -0
- tan/commands/build/host_python.py +59 -0
- tan/commands/build/link_stale.py +78 -0
- tan/commands/build/manifest.py +428 -0
- tan/commands/build/materialise.py +81 -0
- tan/commands/build/token_substitution.py +381 -0
- tan/commands/build/toolchain.py +482 -0
- tan/commands/build/user_defines_wipe.py +115 -0
- tan/commands/build/workspace_patches.py +65 -0
- tan/commands/build_cmd.py +2236 -0
- tan/commands/build_output.py +331 -0
- tan/commands/clean_cmd.py +1183 -0
- tan/commands/completion_cmd.py +786 -0
- tan/commands/debug_config_cmd.py +2425 -0
- tan/commands/diff_cmd.py +940 -0
- tan/commands/doctor_cmd.py +4767 -0
- tan/commands/examples_cmd.py +644 -0
- tan/commands/explain_cmd.py +1319 -0
- tan/commands/faultdecode_cmd.py +931 -0
- tan/commands/flash_atoc_guard.py +240 -0
- tan/commands/flash_cmd.py +5731 -0
- tan/commands/flash_mram_guard.py +150 -0
- tan/commands/flash_ram.py +578 -0
- tan/commands/flash_raw.py +583 -0
- tan/commands/generate_cmd.py +1456 -0
- tan/commands/image_cmd.py +753 -0
- tan/commands/init_cmd.py +1740 -0
- tan/commands/inspect_cmd.py +377 -0
- tan/commands/kconfig_cmd.py +734 -0
- tan/commands/model_cmd.py +1514 -0
- tan/commands/model_device_cmd.py +351 -0
- tan/commands/model_device_live.py +234 -0
- tan/commands/model_host_cmd.py +430 -0
- tan/commands/model_zoo_cmd.py +363 -0
- tan/commands/monitor_actions.py +125 -0
- tan/commands/monitor_cmd.py +626 -0
- tan/commands/monitor_session.py +505 -0
- tan/commands/new_som_cmd.py +1522 -0
- tan/commands/pinmux_cmd.py +568 -0
- tan/commands/presets_cmd.py +1123 -0
- tan/commands/probe_cmd.py +421 -0
- tan/commands/reset_cmd.py +354 -0
- tan/commands/reset_confirm.py +168 -0
- tan/commands/run_cmd.py +637 -0
- tan/commands/scaffold_cmd.py +490 -0
- tan/commands/sdk_cmd.py +1723 -0
- tan/commands/size_cmd.py +1020 -0
- tan/commands/support_bundle_cmd.py +1073 -0
- tan/commands/trace_cmd.py +341 -0
- tan/commands/validate_cmd.py +2009 -0
- tan/commands/west_forward_cmd.py +736 -0
- tan/commands/workspace_patch_check.py +296 -0
- tan/core/__init__.py +1 -0
- tan/core/alp_conf_pregen.py +100 -0
- tan/core/artifact_provenance.py +240 -0
- tan/core/atoc_guard.py +276 -0
- tan/core/atoc_guard_messages.py +232 -0
- tan/core/atoc_replacement.py +118 -0
- tan/core/atomic_write.py +178 -0
- tan/core/board_context.py +351 -0
- tan/core/board_diagnostic.py +111 -0
- tan/core/board_files.py +221 -0
- tan/core/board_validator.py +542 -0
- tan/core/board_validator_compat.py +196 -0
- tan/core/board_validator_run.py +129 -0
- tan/core/board_validator_schema.py +186 -0
- tan/core/board_validator_skew.py +46 -0
- tan/core/board_yaml_edit.py +295 -0
- tan/core/board_yaml_pos.py +118 -0
- tan/core/bootstrap.py +2572 -0
- tan/core/build_plan.py +625 -0
- tan/core/consent.py +104 -0
- tan/core/console_filter.py +245 -0
- tan/core/debug_launch.py +1803 -0
- tan/core/devicetree_lint.py +168 -0
- tan/core/dir_removal.py +229 -0
- tan/core/doctor_git.py +172 -0
- tan/core/doctor_libraries.py +253 -0
- tan/core/doctor_render.py +185 -0
- tan/core/doctor_scope.py +66 -0
- tan/core/doctor_setools.py +237 -0
- tan/core/doctor_stale.py +180 -0
- tan/core/document_guards.py +723 -0
- tan/core/dp_id.py +97 -0
- tan/core/elf_load.py +63 -0
- tan/core/error_catalog.py +171 -0
- tan/core/example_catalog.py +499 -0
- tan/core/example_facets.py +151 -0
- tan/core/faultdecode.py +521 -0
- tan/core/flash_plan.py +2501 -0
- tan/core/flow_d_report.py +381 -0
- tan/core/flow_d_uptime.py +164 -0
- tan/core/fs_confine.py +130 -0
- tan/core/global_flags.py +230 -0
- tan/core/host_python.py +210 -0
- tan/core/image_bundle.py +247 -0
- tan/core/inert.py +147 -0
- tan/core/jlink_binary.py +128 -0
- tan/core/jlink_probe.py +481 -0
- tan/core/jsonc_splice.py +404 -0
- tan/core/launch_provenance.py +257 -0
- tan/core/link_refusal.py +76 -0
- tan/core/metadata_schema.py +376 -0
- tan/core/model_check.py +227 -0
- tan/core/model_device.py +449 -0
- tan/core/model_doctor.py +198 -0
- tan/core/model_host.py +48 -0
- tan/core/model_list.py +112 -0
- tan/core/model_zoo.py +353 -0
- tan/core/module_template.py +340 -0
- tan/core/mram_link.py +87 -0
- tan/core/new_som_ospi.py +94 -0
- tan/core/os_class.py +159 -0
- tan/core/patch_paths.py +87 -0
- tan/core/pending.py +40 -0
- tan/core/plain_zephyr_plan.py +138 -0
- tan/core/plan_exec.py +373 -0
- tan/core/plan_tokens.py +600 -0
- tan/core/probe.py +155 -0
- tan/core/probe_plan.py +237 -0
- tan/core/proxy.py +168 -0
- tan/core/publish.py +31 -0
- tan/core/python_floor.py +178 -0
- tan/core/ram_run.py +694 -0
- tan/core/ram_watch.py +231 -0
- tan/core/raw_write.py +160 -0
- tan/core/reset_plan.py +56 -0
- tan/core/run.py +161 -0
- tan/core/scaffold.py +2203 -0
- tan/core/scaffold_selftest_identity.py +225 -0
- tan/core/sdk_default_registry.py +565 -0
- tan/core/sdk_discovery.py +996 -0
- tan/core/sdk_removal.py +465 -0
- tan/core/serial_actions.py +175 -0
- tan/core/serial_capture.py +185 -0
- tan/core/serial_url.py +45 -0
- tan/core/setools.py +608 -0
- tan/core/setools_scratch.py +243 -0
- tan/core/shapes.py +194 -0
- tan/core/size.py +610 -0
- tan/core/som_buildability.py +204 -0
- tan/core/som_schema_version.py +62 -0
- tan/core/subprocess_env.py +201 -0
- tan/core/sysbuild_image.py +94 -0
- tan/core/system_manifest.py +827 -0
- tan/core/text_layout.py +73 -0
- tan/core/timestamp.py +182 -0
- tan/core/tool_lookup.py +383 -0
- tan/core/toolchain_pin.py +117 -0
- tan/core/toolchain_provision.py +1224 -0
- tan/core/trusted_path.py +134 -0
- tan/core/uboot_breakin.py +154 -0
- tan/core/uri_reference.py +415 -0
- tan/core/user_defines.py +152 -0
- tan/core/venv.py +332 -0
- tan/core/west_patches.py +143 -0
- tan/core/west_workspace_refusal.py +159 -0
- tan/core/zephyr_env.py +124 -0
- tan/env.py +185 -0
- tan/envelope.py +647 -0
- tan/exit_codes.py +12 -0
- tan/lazy_typer.py +185 -0
- tan/model/__init__.py +2 -0
- tan/model/_gen_fixture.py +132 -0
- tan/model/adapters/__init__.py +116 -0
- tan/model/adapters/cpu.py +26 -0
- tan/model/adapters/deepx.py +94 -0
- tan/model/adapters/drpai.py +242 -0
- tan/model/adapters/ethos_u.py +951 -0
- tan/model/adapters/executorch.py +55 -0
- tan/model/analyze.py +694 -0
- tan/model/build.py +304 -0
- tan/model/check.py +682 -0
- tan/model/manifest.py +625 -0
- tan/model/measure.py +216 -0
- tan/model/npu_ops_validate.py +130 -0
- tan/model/package.py +125 -0
- tan/model/perf.py +635 -0
- tan/model/perf_apply.py +721 -0
- tan/model/prep.py +193 -0
- tan/model/sram_fit.py +502 -0
- tan/model/targets.py +374 -0
- tan/model/tensorio.py +287 -0
- tan/net.py +73 -0
- tan/output_format.py +213 -0
- tan/planner/__init__.py +219 -0
- tan/planner/aperture.py +202 -0
- tan/planner/buildplan.py +1092 -0
- tan/planner/camera_owner.py +192 -0
- tan/planner/cameras.py +132 -0
- tan/planner/carveout.py +755 -0
- tan/planner/cli.py +274 -0
- tan/planner/headers.py +315 -0
- tan/planner/kconfig.py +2435 -0
- tan/planner/kconfig_symbols.py +473 -0
- tan/planner/libraries.py +677 -0
- tan/planner/link_target.py +237 -0
- tan/planner/linux_ownership.py +216 -0
- tan/planner/loader.py +1525 -0
- tan/planner/manifest.py +116 -0
- tan/planner/memory.py +161 -0
- tan/planner/memregion.py +56 -0
- tan/planner/models.py +533 -0
- tan/planner/orchestrator.py +874 -0
- tan/planner/ownership.py +203 -0
- tan/planner/partition.py +1160 -0
- tan/planner/paths.py +32 -0
- tan/planner/plain_slice.py +139 -0
- tan/planner/project_emit/__init__.py +60 -0
- tan/planner/project_emit/bom_netlist.py +375 -0
- tan/planner/project_emit/dts.py +580 -0
- tan/planner/project_emit/hw_info.py +221 -0
- tan/planner/project_emit/native_sim.py +136 -0
- tan/planner/project_emit/west_libs.py +213 -0
- tan/planner/project_loader.py +264 -0
- tan/planner/sdk_capability.py +203 -0
- tan/planner/sdk_compat.py +395 -0
- tan/planner/secure.py +350 -0
- tan/planner/slugs.py +394 -0
- tan/planner/som_metadata.py +325 -0
- tan/planner/strict_loaders.py +131 -0
- tan/planner/template.py +1037 -0
- tan/planner/template_pins.py +535 -0
- tan/planner/template_rewrite.py +714 -0
- tan/planner/topology.py +208 -0
- tan/planner/validate.py +365 -0
- tan/planner/whole_device_alias.py +67 -0
- tan/planner/zephyr_board.py +3510 -0
- tan/planner_cli.py +97 -0
- tan/planner_emit.py +617 -0
- tan/planner_root.py +119 -0
- tan/soc_ref.py +58 -0
- tan/templates/__init__.py +34 -0
- tan/templates/vendored/MANIFEST.md +1862 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-AEN801/testcase.yaml +53 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/board.yaml +38 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.conf +11 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/boards/native_sim_native_64.overlay +29 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/src/main.c +266 -0
- tan/templates/vendored/diagnostics/E1M-V2N101/testcase.yaml +53 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/board.yaml +38 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-AEN801/testcase.yaml +62 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/CMakeLists.txt +10 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/README.md +75 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/board.yaml +37 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.c +371 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/cold_chain.h +178 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/src/main.c +373 -0
- tan/templates/vendored/edge-ai/E1M-V2N101/testcase.yaml +62 -0
- tan/templates/vendored/iot/E1M-AEN801/CMakeLists.txt +14 -0
- tan/templates/vendored/iot/E1M-AEN801/README.md +103 -0
- tan/templates/vendored/iot/E1M-AEN801/board.yaml +69 -0
- tan/templates/vendored/iot/E1M-AEN801/prj.conf +15 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.c +196 -0
- tan/templates/vendored/iot/E1M-AEN801/src/cc3501e_bridge.h +173 -0
- tan/templates/vendored/iot/E1M-AEN801/src/main.c +262 -0
- tan/templates/vendored/iot/E1M-AEN801/testcase.yaml +68 -0
- tan/templates/vendored/minimal/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/README.md +108 -0
- tan/templates/vendored/minimal/E1M-AEN801/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-AEN801/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-AEN801/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-AEN801/testcase.yaml +49 -0
- tan/templates/vendored/minimal/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/README.md +108 -0
- tan/templates/vendored/minimal/E1M-V2N101/board.yaml +32 -0
- tan/templates/vendored/minimal/E1M-V2N101/prj.conf +8 -0
- tan/templates/vendored/minimal/E1M-V2N101/src/main.c +111 -0
- tan/templates/vendored/minimal/E1M-V2N101/testcase.yaml +49 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/README.md +120 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/boards/native_sim_native_64.overlay +40 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/CMakeLists.txt +13 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/main.c +171 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/prj.conf +17 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/peer/testcase.yaml +23 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/prj.conf +22 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/src/main.c +185 -0
- tan/templates/vendored/multicore-mailbox/E1M-AEN801/testcase.yaml +26 -0
- tan/templates/vendored/sensor/E1M-AEN801/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-AEN801/README.md +112 -0
- tan/templates/vendored/sensor/E1M-AEN801/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-AEN801/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-AEN801/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-AEN801/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-AEN801/testcase.yaml +52 -0
- tan/templates/vendored/sensor/E1M-V2N101/CMakeLists.txt +8 -0
- tan/templates/vendored/sensor/E1M-V2N101/README.md +112 -0
- tan/templates/vendored/sensor/E1M-V2N101/board.yaml +57 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.conf +10 -0
- tan/templates/vendored/sensor/E1M-V2N101/boards/native_sim_native_64.overlay +28 -0
- tan/templates/vendored/sensor/E1M-V2N101/prj.conf +5 -0
- tan/templates/vendored/sensor/E1M-V2N101/src/main.c +186 -0
- tan/templates/vendored/sensor/E1M-V2N101/testcase.yaml +52 -0
- tan/version.py +102 -0
- tan_cli-0.7.0.dist-info/METADATA +125 -0
- tan_cli-0.7.0.dist-info/RECORD +330 -0
- tan_cli-0.7.0.dist-info/WHEEL +5 -0
- tan_cli-0.7.0.dist-info/entry_points.txt +2 -0
- tan_cli-0.7.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Arm Ethos-U (Vela) compiler adapter.
|
|
3
|
+
|
|
4
|
+
Wraps the `vela` CLI from `ethos-u-vela` (the `model-compile` optional
|
|
5
|
+
dependency). is_available() is True when `vela` is on PATH; compile() shells out
|
|
6
|
+
for the given accelerator-config and reads back `<stem>_vela.tflite`. The
|
|
7
|
+
arena/peak-SRAM footprint is parsed from vela's summary CSV for THAT run, and a
|
|
8
|
+
successful compile that placed operators on the NPU but reports no SRAM working
|
|
9
|
+
set is a hard error, never a silent zero (see `_refuse_zero_sram_footprint`).
|
|
10
|
+
|
|
11
|
+
compile() NEVER raises on a clean vela exit *for placement reasons*, including a
|
|
12
|
+
full CPU fallback -- vela's own exit code is 0 whether it placed every operator
|
|
13
|
+
on the NPU or none of them (measured: `vela float32_fc.tflite
|
|
14
|
+
--accelerator-config ethos-u85-256` on a float32 FULLY_CONNECTED model prints
|
|
15
|
+
"NPU operators = 0 (0.0%)" and still exits 0). `_parse_vela_placement` reads
|
|
16
|
+
vela's own per-run placement summary (always printed to stdout,
|
|
17
|
+
`ethosu.vela.stats_writer.print_performance_metrics_common`'s "CPU/NPU
|
|
18
|
+
operators = N (P%)" lines) so a caller never has to infer placement from the
|
|
19
|
+
mere absence of an exception -- see `tan.model.check`'s
|
|
20
|
+
`_report_from_vela_compile`, the actual consumer of `Blob.npu_op_count`/
|
|
21
|
+
`cpu_op_count`.
|
|
22
|
+
|
|
23
|
+
THE MEMORY PROFILE (alp-sdk #1470): this adapter passes `--memory-mode` when
|
|
24
|
+
its caller resolved one, and today every caller does -- it is a SILICON fact,
|
|
25
|
+
published per part in the SoC spec's `npu_toolchain.vela.memory_mode`
|
|
26
|
+
(`Sram_Only` on the Alif Ensemble parts) and
|
|
27
|
+
carried here as `TargetSpec.vela_memory_mode`. It is never guessed: a caller
|
|
28
|
+
that resolved none gets the flagless invocation, byte for byte.
|
|
29
|
+
|
|
30
|
+
That flag is the one that matters, and the reason is measured. On the committed
|
|
31
|
+
`tests/fixtures/models/tiny_int8.tflite` at `ethos-u85-256`, `ethos-u-vela`
|
|
32
|
+
5.1.0 reports:
|
|
33
|
+
|
|
34
|
+
* with no profile flags -- `sram_memory_used = 0.0`,
|
|
35
|
+
`dram_memory_used = 0.265625` (under `Ethos_U85_SYS_DRAM_Mid` /
|
|
36
|
+
`Dedicated_Sram_384KB`, both of which vela warns it defaulted)
|
|
37
|
+
* with `--memory-mode Sram_Only` -- `sram_memory_used = 0.03125`,
|
|
38
|
+
`dram_memory_used = 0.0`, `on_chip_flash_memory_used = 0.234375`
|
|
39
|
+
|
|
40
|
+
Both exit 0. The first is the shape tan-cli#789 had to REFUSE: vela's built-in
|
|
41
|
+
default profile is DRAM-backed (`Ethos_U85_SYS_DRAM_Mid` /
|
|
42
|
+
`Dedicated_Sram_384KB`, `weights_storage_area=DRAM` in the summary CSV) and an
|
|
43
|
+
Alif Ensemble E-series module is MRAM + SRAM with no DRAM at all, so the whole
|
|
44
|
+
working set landed in memory the part does not have and the SRAM figure alp-sdk
|
|
45
|
+
sizes an arena from came back zero. Adding `--system-config
|
|
46
|
+
Ethos_U85_SYS_Flash_High` on top of `--memory-mode Sram_Only` changes NO memory
|
|
47
|
+
figure (measured on `tiny_int8.tflite` AND on the 44-op
|
|
48
|
+
`person_detect_int8.tflite`, both at `ethos-u85-256`, against the default
|
|
49
|
+
`Ethos_U85_SYS_DRAM_Mid`).
|
|
50
|
+
|
|
51
|
+
THAT INVARIANCE IS SCOPED TO `Sram_Only` AND IS NOT A GENERAL RULE. The two
|
|
52
|
+
flags compose in two levels, verbatim from vela 5.1.0's own `vela.ini`: a
|
|
53
|
+
`Memory_Mode` section assigns const/arena/cache to AXI PORTS
|
|
54
|
+
(`[Memory_Mode.Sram_Only]` -> `const_mem_area` = `arena_mem_area` =
|
|
55
|
+
`cache_mem_area` = `Axi0`, `vela.ini:235-238`), and a `System_Config` section
|
|
56
|
+
maps those ports to MEMORY AREAS (`axi0_port=`/`axi1_port=`, `vela.ini:60-117`).
|
|
57
|
+
Under `Sram_Only` all three sit on `Axi0` and all 11 `System_Config` sections
|
|
58
|
+
vela 5.1.0 ships set `axi0_port=Sram`, so no system config can move anything --
|
|
59
|
+
which is the only reason those two measurements matched. `Shared_Sram` sets
|
|
60
|
+
`const_mem_area=Axi1` (`vela.ini:242-245`); under it the system config decides
|
|
61
|
+
the const region's memory area outright. Measured, `person_detect_int8.tflite` at `ethos-u65-256 --memory-mode
|
|
62
|
+
Shared_Sram`, changing ONLY `--system-config` (KiB, from vela's own summary):
|
|
63
|
+
|
|
64
|
+
* `Ethos_U65_Embedded` (`axi1_port=OffChipFlash`) -- `sram 72.734375`,
|
|
65
|
+
`dram 0.0`, `off_chip_flash 228.265625`
|
|
66
|
+
* `Ethos_U65_Mid_End` (`axi1_port=Dram`) -- `sram 72.734375`,
|
|
67
|
+
`dram 228.3125`, `off_chip_flash 0.0`
|
|
68
|
+
* `Ethos_U65_Client_Server` (`axi1_port=Dram`) -- `sram 72.734375`,
|
|
69
|
+
`dram 228.25`, `off_chip_flash 0.0`
|
|
70
|
+
|
|
71
|
+
228 KiB of weights moves on the system config alone. So the honest rule is:
|
|
72
|
+
under `Sram_Only` the memory mode decides placement outright and the system
|
|
73
|
+
config is bandwidth/latency only; under an `Axi1` const mode the system config
|
|
74
|
+
decides placement too.
|
|
75
|
+
|
|
76
|
+
`--system-config` is therefore still not passed by default, and that is
|
|
77
|
+
deliberate rather than pending. The names alp-sdk's own examples use
|
|
78
|
+
(`Ethos_U85_SRAM_Only`, `RTSS_HE_SRAM_Only`;
|
|
79
|
+
`examples/aen/aen-npu-inference-alp/CMakeLists.txt:42-43` and its `-u55`
|
|
80
|
+
sibling) live ONLY in Alif's PROPRIETARY `ensemble_vela.ini`, which alp-sdk does
|
|
81
|
+
not redistribute, and handing vela a section it cannot resolve is a hard rc=1,
|
|
82
|
+
verbatim: `Section System_Config.Ethos_U85_SRAM_Only not found in Vela config
|
|
83
|
+
file`. So the SoC spec flags those as vendor-gated and `resolve_targets`
|
|
84
|
+
withholds them from the field this adapter passes freely
|
|
85
|
+
(`tan.model.targets._vela_profile`); vela's own default system config is used
|
|
86
|
+
and SAID SO through `_default_profile_caveats`, which now distinguishes the two
|
|
87
|
+
flags rather than blaming the whole profile on vela. Never invent a profile
|
|
88
|
+
here; a wrong one compiles a command stream for hardware the module does not
|
|
89
|
+
have.
|
|
90
|
+
|
|
91
|
+
A LICENSED CUSTOMER CAN SUPPLY THAT FILE, through `ALP_VELA_CONFIG` and never
|
|
92
|
+
through `board.yaml` (the path is environment, not hardware). It buys the
|
|
93
|
+
vendor-tuned profile only as a complete set: `--config` + the part's vendor
|
|
94
|
+
`System_Config` + its memory mode, all three or none -- see `compile()`, where
|
|
95
|
+
each half of that rule is pinned to a measured `ethos-u-vela` 5.1.0 rc. No SoC
|
|
96
|
+
spec names a vendor `System_Config` today, so the mechanism is wired and does
|
|
97
|
+
nothing, which is the only correct thing it can do: passing `--config` alone is
|
|
98
|
+
rc=1 and naming a profile nobody published would be an invention.
|
|
99
|
+
|
|
100
|
+
WHO THE OLD DEFAULT HIT (tan-cli#789 review), i.e. what the memory mode fixes:
|
|
101
|
+
the DRAM-backed built-in default hit every U85 part. Measured, real
|
|
102
|
+
`ethos-u-vela` 5.1.0 over the committed fixture, this refused with no memory
|
|
103
|
+
mode:
|
|
104
|
+
|
|
105
|
+
* `ethos-u85-256` -> `Ethos_U85_SYS_DRAM_Mid` / `Dedicated_Sram_384KB`
|
|
106
|
+
(E1M-AEN401 / E1M-AEN601 / E1M-AEN801, Alif Ensemble E4/E6/E8)
|
|
107
|
+
|
|
108
|
+
while every `ethos-u55-*` config (E1M-AEN301/501/701 and the U55 targets of the
|
|
109
|
+
three AEN SKUs above) resolved to the SRAM-backed
|
|
110
|
+
`Ethos_U55_High_End_Embedded` and reported a real footprint. The refusal is
|
|
111
|
+
kept, not deleted: a part whose spec carries no profile, or a compile that
|
|
112
|
+
reports no SRAM even under its module's own memory mode, must still be refused
|
|
113
|
+
rather than shipped as a zero. `_refuse_zero_sram_footprint` names the profile
|
|
114
|
+
the run ITSELF reported (`_parse_vela_profile`), never a hardcoded Alif one --
|
|
115
|
+
a user of another vendor's part must not read an error blaming
|
|
116
|
+
`Ethos_U85_SYS_DRAM_Mid`.
|
|
117
|
+
|
|
118
|
+
...AND NEITHER IS THE REMEDY (tan-cli#789 review (g)). Naming the profile per
|
|
119
|
+
run only half-closed that: the REMEDY sentence still told every reader the
|
|
120
|
+
profile "lives in the proprietary ensemble_vela.ini", which is an Alif fact.
|
|
121
|
+
A part whose vela invocation carries no proprietary `.ini` would have been sent
|
|
122
|
+
after another vendor's file, which is not what its silicon needs and not where
|
|
123
|
+
its profile comes from. The memory mode tan passes comes straight from the SoC
|
|
124
|
+
spec's own `npu_toolchain.vela` block, so there is nothing to advise a reader
|
|
125
|
+
to do by hand.
|
|
126
|
+
|
|
127
|
+
BOTH HALVES OF THE REFUSAL'S EVIDENCE NOW COME FROM METADATA, not from prose in
|
|
128
|
+
this file: WHICH vendor file (if any) to name is the SoC spec's own
|
|
129
|
+
`npu_toolchain.vela.vendor_config_filename` (`@vela_vendor_config_filename`),
|
|
130
|
+
and WHY a DRAM placement is wrong here is `@soc_declares_dram`, resolved from
|
|
131
|
+
its `external_memory_interfaces[]` -- `alif:ensemble:e8` lists exactly `HexSPI`
|
|
132
|
+
and `SD/eMMC`, `renesas:rzv2n:n44` lists `LPDDR4/4X`. Each is then right for every
|
|
133
|
+
part automatically, including one nobody has looked at yet.
|
|
134
|
+
|
|
135
|
+
Neither is read from `metadata/` HERE: `resolve_targets` already has the SoC
|
|
136
|
+
spec open, an adapter that reads the SDK's fact tree would be a second
|
|
137
|
+
unversioned reader of it, and a fact resolved once and threaded cannot disagree
|
|
138
|
+
with itself between the target and the artifact it produced."""
|
|
139
|
+
from __future__ import annotations
|
|
140
|
+
import csv
|
|
141
|
+
import math
|
|
142
|
+
import os
|
|
143
|
+
import re
|
|
144
|
+
import shutil
|
|
145
|
+
import subprocess
|
|
146
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
147
|
+
from pathlib import Path
|
|
148
|
+
from typing import NoReturn
|
|
149
|
+
|
|
150
|
+
from tan.core.subprocess_env import spawn_env
|
|
151
|
+
from . import CompilerAdapter, Blob
|
|
152
|
+
|
|
153
|
+
_VELA_TIMEOUT_S = 600 # vela compiles are minutes at most; never unbounded in CI
|
|
154
|
+
|
|
155
|
+
# ethosu.vela.stats_writer.print_performance_metrics_common (always printed to
|
|
156
|
+
# stdout, f=sys.stdout is its own default) emits exactly one "CPU operators ="
|
|
157
|
+
# and one "NPU operators =" line per run, e.g. "NPU operators = 0 (0.0%)". The
|
|
158
|
+
# regex intentionally ignores the printed percentage -- _parse_vela_placement
|
|
159
|
+
# recomputes it from the two integer counts so it never inherits vela's own
|
|
160
|
+
# text-formatting rounding.
|
|
161
|
+
_PLACEMENT_RE = re.compile(r"^(CPU|NPU) operators = (\d+)", re.MULTILINE)
|
|
162
|
+
|
|
163
|
+
# The same network-summary block names the profile vela ACTUALLY resolved --
|
|
164
|
+
# "System configuration Ethos_U85_SYS_DRAM_Mid" / "Memory mode
|
|
165
|
+
# Dedicated_Sram_384KB" -- whether it came from a supplied .ini or from vela's
|
|
166
|
+
# built-in default. Read rather than assumed: it is both the summary CSV's own
|
|
167
|
+
# filename suffix and the only honest way to say WHICH profile a figure
|
|
168
|
+
# describes.
|
|
169
|
+
_PROFILE_RE = re.compile(r"^(System configuration|Memory mode) +(\S+) *$", re.MULTILINE)
|
|
170
|
+
|
|
171
|
+
# vela's verbatim warning when it falls back to its own built-in profile.
|
|
172
|
+
# Deliberately NOT matched on the "No configuration file specified" sibling:
|
|
173
|
+
# that one interpolates an absolute path into the host's site-packages, which
|
|
174
|
+
# must never be echoed into a customer-facing envelope.
|
|
175
|
+
_DEFAULTED_RE = re.compile(
|
|
176
|
+
r"^Warning: No (system configuration|memory mode) specified\.", re.MULTILINE)
|
|
177
|
+
|
|
178
|
+
# vela's own names for the two profile flags, as they appear in that warning.
|
|
179
|
+
# Read PER FLAG, not as one boolean: since alp-sdk #1470 tan supplies the
|
|
180
|
+
# memory mode from SoC metadata and still supplies no system config, so a run
|
|
181
|
+
# is routinely half-defaulted -- measured, `--memory-mode Sram_Only` at
|
|
182
|
+
# `ethos-u85-256` still prints "Warning: No system configuration specified.
|
|
183
|
+
# Using a default of Ethos_U85_SYS_DRAM_Mid." and nothing else. Calling that
|
|
184
|
+
# whole run "vela's built-in default profile" would credit vela with the
|
|
185
|
+
# memory mode tan just passed, and would tell a customer the SRAM figure
|
|
186
|
+
# describes a memory model that is not theirs when it now describes exactly
|
|
187
|
+
# theirs.
|
|
188
|
+
_SYSTEM_CONFIG_FLAG = "system configuration"
|
|
189
|
+
_MEMORY_MODE_FLAG = "memory mode"
|
|
190
|
+
|
|
191
|
+
# The memory modes whose CONST/weights area sits on the same AXI port as the
|
|
192
|
+
# arena (`Axi0`), read off vela 5.1.0's own `vela.ini`: `[Memory_Mode.Sram_Only]`
|
|
193
|
+
# is `const_mem_area=arena_mem_area=cache_mem_area=Axi0` (`vela.ini:235-238`),
|
|
194
|
+
# and it is the only shipped section in that shape -- `Shared_Sram` and all four
|
|
195
|
+
# `Dedicated_Sram*` sections set `const_mem_area=Axi1` (`vela.ini:242-269`).
|
|
196
|
+
#
|
|
197
|
+
# It matters for the CAVEAT, not the command line: a `System_Config` maps ports
|
|
198
|
+
# to memory AREAS, so on an `Axi0` const mode a defaulted system config cannot
|
|
199
|
+
# move the const region anywhere (all 11 sections set `axi0_port=Sram`) and the
|
|
200
|
+
# caveat is honestly bandwidth-only, while on an `Axi1` const mode that same
|
|
201
|
+
# default DECIDED where the const region lives -- measured on
|
|
202
|
+
# `person_detect_int8.tflite` at `ethos-u65-256 --memory-mode Shared_Sram`,
|
|
203
|
+
# 228 KiB of weights moves between `off_chip_flash` and `dram` on the system
|
|
204
|
+
# config alone. An UNRECOGNISED name is treated as NOT-Axi0 on purpose: the
|
|
205
|
+
# under-claiming caveat is the safe direction.
|
|
206
|
+
_AXI0_CONST_MEMORY_MODES = frozenset({"Sram_Only"})
|
|
207
|
+
|
|
208
|
+
# `write_summary_metrics_csv_common` writes one `<mem_area>_memory_used` column
|
|
209
|
+
# per memory area the accelerator config declares (stats_writer.py's
|
|
210
|
+
# `area.identifier_name() + "_memory_used"`), so the area names are data, not a
|
|
211
|
+
# fixed list -- matched by shape so a vela version that adds an area is read,
|
|
212
|
+
# not silently dropped.
|
|
213
|
+
_MEM_USED_RE = re.compile(r"^(.+)_memory_used$")
|
|
214
|
+
|
|
215
|
+
# A directory component built from an accel_config that comes out of SoM
|
|
216
|
+
# metadata; anything outside this set is folded to "_" so a malformed
|
|
217
|
+
# accel_config can never walk out of @out_dir.
|
|
218
|
+
_UNSAFE_DIR_CHARS = re.compile(r"[^A-Za-z0-9._-]")
|
|
219
|
+
|
|
220
|
+
# vela's own memory-area name for external DRAM, as it appears in its summary
|
|
221
|
+
# CSV columns (`dram_memory_used`) and therefore as `_parse_vela_summary` keys
|
|
222
|
+
# it -- the area a working set must NOT land in on a part whose SoC spec
|
|
223
|
+
# declares no DRAM interface.
|
|
224
|
+
_DRAM_AREA = "dram"
|
|
225
|
+
|
|
226
|
+
# What the refusal appends to that area's figure when the caller resolved
|
|
227
|
+
# `soc_declares_dram=False` -- i.e. the SoC spec's `external_memory_interfaces`
|
|
228
|
+
# names no DDR/DRAM kind at all (`tan.model.targets._soc_declares_dram`; on
|
|
229
|
+
# `alif:ensemble:e8` it lists exactly `HexSPI` and `SD/eMMC`). Machine-checked
|
|
230
|
+
# per part rather than asserted in prose, so it is silent for a part that
|
|
231
|
+
# declares DRAM (`renesas:rzv2n:n44` -- LPDDR4/4X) or declares nothing.
|
|
232
|
+
#
|
|
233
|
+
# Kept SHORT deliberately: it lands inside a one-line note bounded by
|
|
234
|
+
# `tan.model.check._VELA_REFUSAL_NOTE_BUDGET` (700), against which the maximal
|
|
235
|
+
# refusal measures 691 with this marker and a 17-character
|
|
236
|
+
# `vendor_config_filename` in it (`tests/model/test_check.py::
|
|
237
|
+
# test_the_refusal_note_budget_covers_a_maximal_refusal`, which renders the
|
|
238
|
+
# real template rather than a copy). That filename is the one variable-length
|
|
239
|
+
# input in the whole template, so those 9 characters are the headroom a longer
|
|
240
|
+
# future one has to fit inside.
|
|
241
|
+
_NO_DRAM_MARKER = " (no DRAM interface on this SoC)"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
#: Where a licensed customer names their vendor vela config `.ini` -- an
|
|
245
|
+
#: ENVIRONMENT fact, never a hardware one, so it is read here and never from
|
|
246
|
+
#: `board.yaml`. alp-sdk's own schema says why: `models[].compile` is for a
|
|
247
|
+
#: per-model config "the SDK cannot derive", and a vela profile IS derivable
|
|
248
|
+
#: from the SKU; a local absolute path in a committed board file would also
|
|
249
|
+
#: travel to every other machine that opens it. Same shape as the other two
|
|
250
|
+
#: toolchain env vars tan already reads in adapters (`ALP_DRPAI_TVM_HOME`,
|
|
251
|
+
#: `ALP_DEEPX_SDK_HOME`).
|
|
252
|
+
_VELA_CONFIG_ENV = "ALP_VELA_CONFIG"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _vendor_config_path() -> Path | None:
|
|
256
|
+
"""The vendor vela `.ini` this host has, or `None`.
|
|
257
|
+
|
|
258
|
+
`None` for unset, empty, and for a value that does not name a readable
|
|
259
|
+
FILE -- vela would fail on a path it cannot open, and an env var pointing
|
|
260
|
+
at a stale location must degrade to "no vendor config" (Arm's built-in
|
|
261
|
+
profile, which is what makes the arena figures correct in the first place)
|
|
262
|
+
rather than break a build that worked yesterday."""
|
|
263
|
+
raw = os.environ.get(_VELA_CONFIG_ENV)
|
|
264
|
+
if not raw:
|
|
265
|
+
return None
|
|
266
|
+
path = Path(raw).expanduser()
|
|
267
|
+
return path if path.is_file() else None
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class VelaFootprintRefused(RuntimeError):
|
|
271
|
+
"""vela ran CLEANLY and tan refused the footprint it reported.
|
|
272
|
+
|
|
273
|
+
A distinct type because the two callers must tell it apart from a real
|
|
274
|
+
vela failure, and neither can do that by inspecting a message:
|
|
275
|
+
|
|
276
|
+
* `tan.model.build.build_model` skips THIS target and keeps building the
|
|
277
|
+
SKU's others (tan-cli#789 review BLOCKER 1: one refused
|
|
278
|
+
`ethos-u85-256` used to abort the whole `.alpmodel`, taking the
|
|
279
|
+
`ethos-u55-256` / `ethos-u55-128` / `cpu` targets that compiled fine
|
|
280
|
+
down with it). A genuine `adapter.compile()` failure still fails the
|
|
281
|
+
build loudly -- that is deliberate and unchanged.
|
|
282
|
+
* `tan.model.check`'s `--exact` opens its note "vela compiled cleanly
|
|
283
|
+
for <accel-config> ...", NOT "--exact compile with vela failed (...)"
|
|
284
|
+
(tan-cli#789 review NIT 8) -- vela exited 0; it was the footprint that
|
|
285
|
+
was refused. It also passes the message through at a wider note budget
|
|
286
|
+
because -- unlike a vela subprocess re-raising its own raw stderr --
|
|
287
|
+
this text is authored here, one line, and bounded.
|
|
288
|
+
|
|
289
|
+
The message is a single line by contract (both callers put it in a
|
|
290
|
+
one-line surface): a report note and an `.alpmodel` coverage reason.
|
|
291
|
+
|
|
292
|
+
WHERE THE FIRST BULLET IS ACTUALLY ENFORCED, so nobody re-derives it the
|
|
293
|
+
hard way: the four tests that pin "one refusal costs ONE target" live in
|
|
294
|
+
`tests/model/test_build.py`, which carries a MODULE-LEVEL
|
|
295
|
+
`pytest.mark.skipif` on `ALP_SDK_ROOT` (`test_build.py:44`). So in the bare
|
|
296
|
+
install CI's `gates` job uses -- `pip install -e ./python`, no
|
|
297
|
+
`ALP_SDK_ROOT` -- replacing `build_model`'s `except VelaFootprintRefused`
|
|
298
|
+
body with a bare `raise` passes the whole suite green. The guard is real,
|
|
299
|
+
but it lives in `parity.yml`, which binds `ALP_SDK_ROOT` from
|
|
300
|
+
`PINNED_SDK_TAG` on every `pull_request`/`push`; `ci.yml`'s `gates` job
|
|
301
|
+
binds it only on the release path (its `sdk_parity` input). Do not read a
|
|
302
|
+
green bare run as having exercised this."""
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _vela_version() -> str:
|
|
306
|
+
try:
|
|
307
|
+
return f"vela {version('ethos-u-vela')}"
|
|
308
|
+
except PackageNotFoundError:
|
|
309
|
+
return "vela"
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _run_dir(out_dir: Path, accel_config: str) -> Path:
|
|
313
|
+
"""A per-accel-config subdirectory of @out_dir for one vela run.
|
|
314
|
+
|
|
315
|
+
vela names its summary `<stem>_summary_<system_config>.csv`, and the
|
|
316
|
+
system-config is a property of the SILICON FAMILY, not of the accelerator
|
|
317
|
+
config: `ethos-u55-256` and `ethos-u55-128` both resolve to
|
|
318
|
+
`Ethos_U55_High_End_Embedded` and therefore write the SAME filename
|
|
319
|
+
(measured, vela 5.1.0). `build_model` reuses one `out_dir` across every
|
|
320
|
+
config a SoM declares, so a shared directory means one compile's summary
|
|
321
|
+
silently answers for another's -- a u85 target inheriting a u55 compile's
|
|
322
|
+
arena. One directory per run makes that impossible, and keeps vela's
|
|
323
|
+
intermediate output from landing beside the `.alpmodel` package."""
|
|
324
|
+
return out_dir / f"vela-{_UNSAFE_DIR_CHARS.sub('_', accel_config)}"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _parse_vela_profile(stdout: str) -> tuple[str | None, str | None]:
|
|
328
|
+
"""(system_config, memory_mode) vela resolved for this run, from its own
|
|
329
|
+
network-summary block. Either is None when the line is absent (an
|
|
330
|
+
unexpected vela output shape) -- never guessed at."""
|
|
331
|
+
found = dict(_PROFILE_RE.findall(stdout))
|
|
332
|
+
return found.get("System configuration"), found.get("Memory mode")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _summary_csv(out_dir: Path, stem: str, system_config: str | None) -> Path | None:
|
|
336
|
+
"""vela's summary CSV for THIS run: the exact `<stem>_summary_<system_
|
|
337
|
+
config>.csv` name when the run's own system-config was readable, else the
|
|
338
|
+
sole glob match. `None` for no match AND for an ambiguous one -- picking
|
|
339
|
+
`sorted(matches)[0]` out of several is only ever right by accident of sort
|
|
340
|
+
order, and a footprint attributed to the wrong compile is worse than no
|
|
341
|
+
footprint at all (which the caller refuses loudly)."""
|
|
342
|
+
if system_config:
|
|
343
|
+
exact = out_dir / f"{stem}_summary_{system_config}.csv"
|
|
344
|
+
if exact.is_file():
|
|
345
|
+
return exact
|
|
346
|
+
matches = sorted(out_dir.glob(f"{stem}_summary_*.csv"))
|
|
347
|
+
return matches[0] if len(matches) == 1 else None
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def _parse_vela_summary(out_dir: Path, stem: str,
|
|
351
|
+
*, system_config: str | None = None) -> dict[str, float]:
|
|
352
|
+
"""Every `<mem_area>_memory_used` figure vela reported for this run, keyed
|
|
353
|
+
by lower-cased memory-area name ("sram", "dram", "on_chip_flash", ...), in
|
|
354
|
+
KiB.
|
|
355
|
+
|
|
356
|
+
The values are ALREADY KiB in the CSV, not bytes (`memory_used[...] /
|
|
357
|
+
1024.0`, ethosu/vela/stats_writer.py:123 in
|
|
358
|
+
write_summary_metrics_csv_common) -- despite looking byte-scale at a glance
|
|
359
|
+
(e.g. `72.734375`). `arena_cache_size` (`arch.arena_cache_size / 1024`,
|
|
360
|
+
stats_writer.py:107) is a DIFFERENT column -- the accelerator config's
|
|
361
|
+
configured cache capacity, a build-time knob unrelated to any one model's
|
|
362
|
+
footprint -- and is deliberately not among these.
|
|
363
|
+
|
|
364
|
+
`{}` when the summary is missing or unparseable; a memory area vela did
|
|
365
|
+
not write is simply absent, and one it wrote as 0.0 is present as 0.0 (the
|
|
366
|
+
two are different facts, and `_refuse_zero_sram_footprint` says which)."""
|
|
367
|
+
path = _summary_csv(out_dir, stem, system_config)
|
|
368
|
+
if path is None:
|
|
369
|
+
return {}
|
|
370
|
+
with open(path, newline="", encoding="utf-8") as fh:
|
|
371
|
+
rows = list(csv.DictReader(fh))
|
|
372
|
+
if not rows:
|
|
373
|
+
return {}
|
|
374
|
+
used: dict[str, float] = {}
|
|
375
|
+
for key, val in rows[0].items():
|
|
376
|
+
m = _MEM_USED_RE.match(key.lower()) if key else None
|
|
377
|
+
if m is None:
|
|
378
|
+
continue
|
|
379
|
+
try:
|
|
380
|
+
used[m.group(1)] = float(val)
|
|
381
|
+
except (TypeError, ValueError):
|
|
382
|
+
continue
|
|
383
|
+
return used
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _footprint(used: dict[str, float]) -> tuple[int, int]:
|
|
387
|
+
"""(arena_bytes, req_sram_kib) from @used's SRAM working set.
|
|
388
|
+
|
|
389
|
+
The model's arena requirement is its SRAM working set, `sram_memory_used`:
|
|
390
|
+
the scratch region alp-sdk's `alp_model_open()` `arena`/`arena_bytes` sizes
|
|
391
|
+
at runtime. req_sram_kib is rounded UP (ceil, never floor/truncate): the
|
|
392
|
+
device-side fit gate (`t->req_sram_kib <= e->arena_sram_kib`,
|
|
393
|
+
src/backends/inference/alp_model_select.c) must never under-report a
|
|
394
|
+
model's requirement, or a model that doesn't actually fit could pass the
|
|
395
|
+
gate as if it did.
|
|
396
|
+
|
|
397
|
+
(0, 0) only when vela reported no SRAM at all -- correct for a full CPU
|
|
398
|
+
fallback (measured: `float32_fc.tflite` at `ethos-u85-256` reports 0.0 for
|
|
399
|
+
EVERY memory area), and refused by the caller for anything else.
|
|
400
|
+
|
|
401
|
+
THE CONTRACT DECISION (tan-cli#1011), made once and recorded here rather
|
|
402
|
+
than re-litigated at the call site: under `--memory-mode Sram_Only` this
|
|
403
|
+
reads the ARENA ONLY, on purpose, and that is the CORRECT scope, not an
|
|
404
|
+
open gap. `e->arena_sram_kib` -- what `req_sram_kib` is checked against
|
|
405
|
+
(`t->req_sram_kib <= e->arena_sram_kib`, `alp_model_select.c`) -- is
|
|
406
|
+
sourced from the SoC spec's `inference_arena_sram_kib`, whose own schema
|
|
407
|
+
description (`metadata/schemas/soc-spec-v1.schema.json`) states its scope
|
|
408
|
+
verbatim: "Usable SRAM budget (KiB) for an NPU tensor arena on this SoC."
|
|
409
|
+
An ARENA budget, not a whole-model-footprint budget. And the const/weights
|
|
410
|
+
region this function deliberately leaves out is not sized by
|
|
411
|
+
`req_sram_kib` on the consumer side either: `alp_inference_open_alpmodel()`
|
|
412
|
+
hands it to the engine as `model_data`/`model_size`
|
|
413
|
+
(`src/common/alp_model_loader.c`), entirely separate from
|
|
414
|
+
`arena`/`arena_bytes` -- a `Sram_Only` integration provisions its storage
|
|
415
|
+
independently, the way `examples/aen/aen-npu-inference-alp/src/main.c`
|
|
416
|
+
sizes its own `model_sram[NETWORK_MODEL_LEN]
|
|
417
|
+
__attribute__((section("SRAM0")))` off the blob's own byte length, never
|
|
418
|
+
off `req_sram_kib`.
|
|
419
|
+
|
|
420
|
+
vela's own bookkeeping obscures this at a glance, which is why the
|
|
421
|
+
reasoning is recorded rather than assumed correct: it FILES the
|
|
422
|
+
const/weights region under `on_chip_flash_memory_used`, and on a
|
|
423
|
+
`Sram_Only` part that is a pure BOOKKEEPING RENAME rather than a placement
|
|
424
|
+
-- verbatim from `ethosu/vela/architecture_features.py`, when the const
|
|
425
|
+
area's port maps to SRAM and const/arena/cache are the same area: `"Info:
|
|
426
|
+
Changing const_mem_area from Sram to OnChipFlash. This will use the same
|
|
427
|
+
characteristics as Sram."`, after which `memory_clock_scales`,
|
|
428
|
+
`memory_burst_length`, `memory_ports_used` and `memory_latency` for
|
|
429
|
+
`OnChipFlash` are all assigned from `Sram`. It happens because
|
|
430
|
+
`[Memory_Mode.Sram_Only]` puts `const_mem_area`, `arena_mem_area` and
|
|
431
|
+
`cache_mem_area` all on `Axi0` (vela 5.1.0's own `vela.ini`) while vela's
|
|
432
|
+
validity check forbids naming SRAM as a const area.
|
|
433
|
+
|
|
434
|
+
On an Alif Ensemble module that renamed region really IS SRAM-resident too
|
|
435
|
+
(`src/backends/inference/ethos_u_aen.cpp` pins every NPU access to the
|
|
436
|
+
SRAM AXI port for exactly this reason -- verbatim, "everything a
|
|
437
|
+
SRAM_Only model touches is SRAM0-resident"), which is real information a
|
|
438
|
+
board integrator needs -- just not through THIS field. Measured with
|
|
439
|
+
`ethos-u-vela` 5.1.0 on the real 44-op `person_detect_int8.tflite` at
|
|
440
|
+
`ethos-u85-256`: `sram_memory_used = 72.0` (the arena; what `req_sram_kib`
|
|
441
|
+
reports) and `on_chip_flash_memory_used = 235.265625` (the const/weights
|
|
442
|
+
region; carried in the blob payload, sized by the integrator, never by
|
|
443
|
+
this function).
|
|
444
|
+
|
|
445
|
+
Summing the columns into `req_sram_kib` would be WRONG, not merely
|
|
446
|
+
unnecessary: both figures this function returns come from the same
|
|
447
|
+
`sram_kib` value, so summing would inflate `arena_bytes` right along with
|
|
448
|
+
it, oversizing the caller's scratch buffer for memory the model needs no
|
|
449
|
+
scratch space for, and it would double-book SRAM the const-storage buffer
|
|
450
|
+
already reserves on its own. And it is not even a `Sram_Only`-only wrong
|
|
451
|
+
answer: for an integration whose const region lands on a different AXI
|
|
452
|
+
port (`Shared_Sram` / `Dedicated_Sram*` -- see the module docstring's
|
|
453
|
+
`ethos-u65-256` sweep, where 228 KiB of weights moves between
|
|
454
|
+
`off_chip_flash` and `dram` on the system config alone), summing would
|
|
455
|
+
OVER-report `req_sram_kib` and refuse a board the model actually fits."""
|
|
456
|
+
sram_kib = used.get("sram", 0.0)
|
|
457
|
+
if sram_kib <= 0:
|
|
458
|
+
return 0, 0
|
|
459
|
+
return round(sram_kib * 1024), math.ceil(sram_kib)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _defaulted_flags(stdout: str) -> frozenset[str]:
|
|
463
|
+
"""Which profile flags vela fell back to a built-in for, by vela's OWN
|
|
464
|
+
names for them (`_SYSTEM_CONFIG_FLAG` / `_MEMORY_MODE_FLAG`), read from its
|
|
465
|
+
own warnings. Empty when it defaulted neither."""
|
|
466
|
+
return frozenset(_DEFAULTED_RE.findall(stdout))
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _profile_clause(system_config: str | None, memory_mode: str | None,
|
|
470
|
+
defaulted: frozenset[str]) -> str:
|
|
471
|
+
"""The profile THIS run resolved, named as the run itself reported it
|
|
472
|
+
(`_parse_vela_profile`) -- never a hardcoded `Ethos_U85_*`, which would
|
|
473
|
+
blame an Alif memory model for a refusal on a non-Alif part
|
|
474
|
+
(tan-cli#789 review MAJOR 3).
|
|
475
|
+
|
|
476
|
+
@defaulted is vela's OWN verdict (`_defaulted_flags` over its stdout), not
|
|
477
|
+
an assumption, and it is per-flag: the blanket "vela's BUILT-IN default
|
|
478
|
+
profile" tail is used only when vela defaulted EVERY name in the clause.
|
|
479
|
+
When it defaulted some but not all -- the shape tan produces now that it
|
|
480
|
+
passes a metadata-sourced `--memory-mode` and no `--system-config` -- each
|
|
481
|
+
name says for itself which it was, so a supplied flag is never attributed
|
|
482
|
+
to vela and a defaulted one is never presented as the module's."""
|
|
483
|
+
parts = [(label, name) for label, name in
|
|
484
|
+
((_SYSTEM_CONFIG_FLAG, system_config), (_MEMORY_MODE_FLAG, memory_mode)) if name]
|
|
485
|
+
if not parts:
|
|
486
|
+
return "an unreported profile"
|
|
487
|
+
named = " / ".join(name for _, name in parts)
|
|
488
|
+
if all(label in defaulted for label, _ in parts):
|
|
489
|
+
return f"{named}, vela's BUILT-IN default profile"
|
|
490
|
+
if not any(label in defaulted for label, _ in parts):
|
|
491
|
+
return named
|
|
492
|
+
return " / ".join(f"{name} (vela's built-in default)" if label in defaulted else name
|
|
493
|
+
for label, name in parts)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
# THE VENDOR CLAUSE IN `_refusal_remedy` NAMES THE `System_Config`
|
|
497
|
+
# SPECIFICALLY, and must keep doing so (tan-cli#789 review MINOR 4). It used to
|
|
498
|
+
# read "it lives in the proprietary ensemble_vela.ini", whose antecedent is the
|
|
499
|
+
# "module vela profile" of the sentence before -- and since alp-sdk #1470 that
|
|
500
|
+
# is false for the load-bearing half of the profile: the memory mode
|
|
501
|
+
# (`Sram_Only` on every Alif Ensemble part) is an ARM BUILT-IN tan passes with
|
|
502
|
+
# no `.ini` at all. Only the tuned `System_Config` names (`Ethos_U85_SRAM_Only`,
|
|
503
|
+
# `RTSS_HE_SRAM_Only`) live in Alif's file. And since that branch is now reached
|
|
504
|
+
# only when the part's SoC spec publishes no `npu_toolchain.vela.memory_mode`,
|
|
505
|
+
# the reader's REAL fix is a metadata entry in alp-sdk, not a vendor download --
|
|
506
|
+
# the vendor clause survives only to explain why tan cannot name a complete
|
|
507
|
+
# profile there, never as the remedy. (Stating the metadata fix in the shipped
|
|
508
|
+
# string does not fit: `_VELA_REFUSAL_NOTE_BUDGET` is 700 and the maximal
|
|
509
|
+
# refusal already measures 691 with this clause in it --
|
|
510
|
+
# `tests/model/test_check.py::test_the_refusal_note_budget_covers_a_maximal_
|
|
511
|
+
# refusal` is what holds that line.)
|
|
512
|
+
#
|
|
513
|
+
# WHICH FILE IT NAMES IS NOW METADATA'S ANSWER, NOT A VENDOR PREFIX MATCH. The
|
|
514
|
+
# gate used to be `silicon_ref.startswith("alif:ensemble:")` with the filename
|
|
515
|
+
# `ensemble_vela.ini` hardcoded beside it -- a standing claim about one vendor
|
|
516
|
+
# that no data could correct. It is now emitted iff this part's own SoC spec
|
|
517
|
+
# declares `npu_toolchain.vela.vendor_config_filename`, and names exactly that.
|
|
518
|
+
# So "a non-Alif refusal never names an Alif file" holds for the stronger
|
|
519
|
+
# reason that no part is ever handed another part's file at all
|
|
520
|
+
# (a part whose spec declares none gets none). `silicon_ref` was the gate's only reader
|
|
521
|
+
# and is therefore gone from this adapter's interface.
|
|
522
|
+
#
|
|
523
|
+
# `System_Config` and NOT `--system-config`: that is vela's own INI section name
|
|
524
|
+
# (`Section System_Config.<name> not found in Vela config file`), so it
|
|
525
|
+
# identifies the right half of the profile without spelling a CLI flag into a
|
|
526
|
+
# message whose whole point is that it prescribes nothing tan can pass
|
|
527
|
+
# (tan-cli#789 review BLOCKER 2, pinned by
|
|
528
|
+
# `test_the_refusal_prescribes_nothing_tan_cannot_actually_do`).
|
|
529
|
+
#
|
|
530
|
+
# The MEMORY-MODE gate is also why that sentence no longer says tan "cannot pass
|
|
531
|
+
# one yet". It could not, when the text was written; since #1470 it does, for
|
|
532
|
+
# every part whose SoC spec publishes a `npu_toolchain.vela.memory_mode`.
|
|
533
|
+
# Reaching the branch now means THIS part's spec publishes none -- a part still
|
|
534
|
+
# marked TBD, which the sourcing rule leaves unset rather than guessing -- so the
|
|
535
|
+
# true statement is that none was resolved for it and vela therefore chose the
|
|
536
|
+
# placement itself.
|
|
537
|
+
def _refusal_remedy(defaulted: frozenset[str], vendor_config_filename: str | None) -> str:
|
|
538
|
+
"""What the reader can actually DO -- which today is: nothing to this
|
|
539
|
+
target, and the rest of the SKU still builds.
|
|
540
|
+
|
|
541
|
+
The message this replaced ended "Compile against a --system-config/
|
|
542
|
+
--memory-mode matching this module's memory model instead", which
|
|
543
|
+
prescribed an action tan cannot perform (tan-cli#789 review BLOCKER 2):
|
|
544
|
+
`compile()` never reads `opts`, nothing under `tan/` passes either flag,
|
|
545
|
+
and alp-sdk's `board.schema.json` declares `models[].compile` as
|
|
546
|
+
`additionalProperties: false` over `deepx_dxm1`/`drpai` -- there is no
|
|
547
|
+
`ethos_u` key to route a profile through. Adding one is an alp-sdk schema
|
|
548
|
+
change (ADR-0028 leaves `metadata/schemas/` with alp-sdk), so this states
|
|
549
|
+
the real position instead of promising a flag that does not exist.
|
|
550
|
+
|
|
551
|
+
The vendor-file pointer is likewise stated only where it is TRUE
|
|
552
|
+
(tan-cli#789 review (g)). It was unconditional, so a refusal on a
|
|
553
|
+
part whose vela invocation involves no proprietary `.ini` sent that reader
|
|
554
|
+
hunting an Alif file. Gated now on @vendor_config_filename, this part's own
|
|
555
|
+
SoC-spec declaration, so `None` (the part declares none, or the caller
|
|
556
|
+
resolved no spec) gets the two clauses that hold for ALL parts and no
|
|
557
|
+
vendor file at all. The first sentence stays part-neutral by construction:
|
|
558
|
+
it says no profile was resolved for THIS part, which is a fact about that
|
|
559
|
+
part's metadata, not about anyone's silicon.
|
|
560
|
+
|
|
561
|
+
Gated on the MEMORY MODE specifically, not on "vela defaulted something":
|
|
562
|
+
the memory mode is the flag that decides placement, and since alp-sdk
|
|
563
|
+
#1470 tan passes it from SoC metadata while still passing no
|
|
564
|
+
`--system-config`. A run that got its module's memory mode and STILL
|
|
565
|
+
reported no SRAM has not been failed by a missing profile, so telling its
|
|
566
|
+
reader "no module vela profile was resolved" would be false. See the
|
|
567
|
+
comment block above this function for what that gate implies for the
|
|
568
|
+
vendor clause and for the retired "cannot pass one yet" wording."""
|
|
569
|
+
if _MEMORY_MODE_FLAG not in defaulted:
|
|
570
|
+
return "`tan model build` skips this target and still builds the SKU's others."
|
|
571
|
+
where = ""
|
|
572
|
+
if vendor_config_filename:
|
|
573
|
+
where = (f"; its System_Config lives in the proprietary {vendor_config_filename} "
|
|
574
|
+
f"alp-sdk does not redistribute")
|
|
575
|
+
return (f"No module vela profile was resolved for this part, so vela chose its "
|
|
576
|
+
f"own{where}. `tan model build` skips this target and still builds the "
|
|
577
|
+
f"SKU's others.")
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def _no_dram_marker(area: str, soc_declares_dram: bool | None) -> str:
|
|
581
|
+
"""`_NO_DRAM_MARKER` for the DRAM area on a part whose SoC spec declares no
|
|
582
|
+
DRAM interface; `""` for every other area and for every other answer.
|
|
583
|
+
|
|
584
|
+
`is False` and never a truthiness test: `None` means the spec declared no
|
|
585
|
+
`external_memory_interfaces` at all (`tan.model.targets._soc_declares_dram`),
|
|
586
|
+
and an unknown must not be rendered as evidence that a part has no DRAM.
|
|
587
|
+
The refusal is a string a customer sizes hardware from; the safe direction
|
|
588
|
+
is silence."""
|
|
589
|
+
if area == _DRAM_AREA and soc_declares_dram is False:
|
|
590
|
+
return _NO_DRAM_MARKER
|
|
591
|
+
return ""
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _refuse_zero_sram_footprint(*, accel_config: str, npu_ops: int, cpu_ops: int | None,
|
|
595
|
+
used: dict[str, float], system_config: str | None,
|
|
596
|
+
memory_mode: str | None, defaulted: frozenset[str],
|
|
597
|
+
vendor_config_filename: str | None = None,
|
|
598
|
+
soc_declares_dram: bool | None = None) -> NoReturn:
|
|
599
|
+
"""A successful compile that placed operators on the NPU but reports no
|
|
600
|
+
SRAM working set is a refusal, not a zero.
|
|
601
|
+
|
|
602
|
+
`req_sram_kib == 0` does not read as "this model needs no arena" on the
|
|
603
|
+
device side: alp-sdk's selector reads it as *fits any envelope*
|
|
604
|
+
(`return e->arena_sram_kib == 0u || t->req_sram_kib <= e->arena_sram_kib;`,
|
|
605
|
+
src/backends/inference/alp_model_select.c) and then hands the caller
|
|
606
|
+
`arena_bytes = 0`. A board would trust that. Measured, real `ethos-u-vela`
|
|
607
|
+
5.1.0: `keyword_scrambled_8bit.tflite` at `ethos-u85-256` places 6 of 15
|
|
608
|
+
operators on the NPU, exits 0, and reports `sram_memory_used = 0.0`
|
|
609
|
+
alongside `dram_memory_used = 5.359375` -- because vela's own default
|
|
610
|
+
profile is DRAM-backed. The footprint is real; it is just not expressed in
|
|
611
|
+
a memory area this module has. Refusing loudly is the only honest answer
|
|
612
|
+
available without inventing a profile (see the module docstring).
|
|
613
|
+
|
|
614
|
+
Raises `VelaFootprintRefused`, not a bare `RuntimeError`: this refusal
|
|
615
|
+
costs the caller ONE target, never the whole package (see that type).
|
|
616
|
+
|
|
617
|
+
THE EVIDENCE FOR "a memory area this module does not have" IS SOURCED, NOT
|
|
618
|
+
ASSERTED. @soc_declares_dram is this part's own SoC spec answering whether
|
|
619
|
+
it declares any external DRAM interface at all, resolved by
|
|
620
|
+
`tan.model.targets._soc_declares_dram` from `external_memory_interfaces[]`
|
|
621
|
+
and threaded here on `TargetSpec` -- so the clause is right for every part
|
|
622
|
+
automatically, including one nobody has looked at yet, and absent for a part
|
|
623
|
+
that does have DRAM. It is deliberately NOT resolved by reading a SoC JSON
|
|
624
|
+
from inside this adapter: a compiler adapter that opens `metadata/` would be
|
|
625
|
+
a second, unversioned reader of the SDK's fact tree, and the caller already
|
|
626
|
+
has the spec open."""
|
|
627
|
+
total = npu_ops + (cpu_ops or 0)
|
|
628
|
+
elsewhere = ", ".join(f"{area} {kib:.2f} KiB{_no_dram_marker(area, soc_declares_dram)}"
|
|
629
|
+
for area, kib in sorted(used.items()) if kib > 0)
|
|
630
|
+
where = (f"its working set went to {elsewhere}" if elsewhere
|
|
631
|
+
else "it reported no memory use in any area")
|
|
632
|
+
# NOTE the wording of the selector clause: it says "accepts ... against ANY
|
|
633
|
+
# arena size", NEVER the retired "fits" vocabulary. This string reaches a
|
|
634
|
+
# `basis: "static-screen"` report note through `tan.model.check`'s
|
|
635
|
+
# `_footprint_refused_note`, and a static screen must never emit "fits" in
|
|
636
|
+
# any form. Until this round the clause was there but the 200-character
|
|
637
|
+
# note budget happened to cut it off; widening that budget would have
|
|
638
|
+
# walked it straight into the envelope.
|
|
639
|
+
#
|
|
640
|
+
# THE ONE TEST THAT ENFORCES THAT is
|
|
641
|
+
# `test_a_refused_footprint_is_not_reported_as_a_failed_compile`
|
|
642
|
+
# (`tests/model/test_check.py`), and only because it renders its note by
|
|
643
|
+
# CALLING this function. It used to raise a hand-copied literal of this
|
|
644
|
+
# template, which enforced nothing: the copy was byte-identical to the
|
|
645
|
+
# template it came from, so rewording this clause to "reads req_sram_kib
|
|
646
|
+
# == 0 as fits any envelope" left the whole suite green while that phrase
|
|
647
|
+
# reached the customer note and the JSON envelope (tan-cli#789 review
|
|
648
|
+
# BLOCKER). The two static-screen guards this comment used to name --
|
|
649
|
+
# `test_the_word_fits_never_appears_in_a_static_screen_report`
|
|
650
|
+
# (`tests/model/test_analyze.py`) and `test_the_retired_word_never_leaks_
|
|
651
|
+
# into_the_clis_rendered_static_screen_output`
|
|
652
|
+
# (`tests/commands/test_model_check_command.py`) -- do NOT reach this
|
|
653
|
+
# template and never did: the first runs live reports that never provoke a
|
|
654
|
+
# refusal, and the second monkeypatches `check_model_backends` with
|
|
655
|
+
# fabricated reports whose notes are hand-written literals. Naming them
|
|
656
|
+
# here claimed an enforcement that did not exist, which is exactly what
|
|
657
|
+
# stops the next reader from adding a real one.
|
|
658
|
+
raise VelaFootprintRefused(
|
|
659
|
+
f"vela compiled cleanly for {accel_config} ({npu_ops}/{total} operators on the NPU) "
|
|
660
|
+
f"but reported 0 KiB SRAM: {where} under "
|
|
661
|
+
f"{_profile_clause(system_config, memory_mode, defaulted)}. Refused because alp-sdk's "
|
|
662
|
+
f"on-device selector accepts req_sram_kib == 0 against ANY arena size "
|
|
663
|
+
f"(src/backends/inference/alp_model_select.c). "
|
|
664
|
+
f"{_refusal_remedy(defaulted, vendor_config_filename)}")
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
# `_default_profile_caveats` EMITS THREE SHAPES, because the two profile flags
|
|
668
|
+
# carry different weight, since alp-sdk #1470 they routinely differ, and how
|
|
669
|
+
# much a DEFAULTED system config cost depends on the memory mode beside it:
|
|
670
|
+
#
|
|
671
|
+
# * NO MEMORY MODE -- the hard caveat, unchanged. vela chose the placement,
|
|
672
|
+
# its built-in default is DRAM-backed, and the arena/SRAM figures therefore
|
|
673
|
+
# describe a memory model the module may not have at all.
|
|
674
|
+
# * MEMORY MODE SUPPLIED WITH `Axi0` CONST (`Sram_Only`), SYSTEM CONFIG
|
|
675
|
+
# DEFAULTED -- the shape every Alif Ensemble compile takes. Here the default
|
|
676
|
+
# really is bandwidth-only: const, arena and cache are all on `Axi0` and all
|
|
677
|
+
# 11 Arm `System_Config` sections map `axi0_port=Sram`, so nothing the
|
|
678
|
+
# system config says can move a byte. Measured, `--memory-mode Sram_Only`
|
|
679
|
+
# alone versus `--system-config Ethos_U85_SYS_Flash_High --memory-mode
|
|
680
|
+
# Sram_Only`, on `tests/fixtures/models/tiny_int8.tflite` AND on the 44-op
|
|
681
|
+
# `person_detect_int8.tflite`, both at `ethos-u85-256`: identical
|
|
682
|
+
# `sram_memory_used`/`on_chip_flash_memory_used`. So this caveat does NOT
|
|
683
|
+
# repeat the "figures describe the wrong memory model" sentence, which would
|
|
684
|
+
# be false.
|
|
685
|
+
# * MEMORY MODE SUPPLIED WITH `Axi1` CONST (`Shared_Sram`; also every
|
|
686
|
+
# `Dedicated_Sram*`), SYSTEM CONFIG DEFAULTED --
|
|
687
|
+
# calling THAT bandwidth-only would be false, which is why it is its own
|
|
688
|
+
# shape (tan-cli#789 review MAJOR 2). The const/weights region is on the
|
|
689
|
+
# other port, and it is the system config that maps ports to memory areas,
|
|
690
|
+
# so vela's default picked where the weights live. Measured on
|
|
691
|
+
# `person_detect_int8.tflite` at `ethos-u65-256 --memory-mode Shared_Sram`,
|
|
692
|
+
# changing only `--system-config`: `Ethos_U65_Embedded` files 228.265625 KiB
|
|
693
|
+
# under `off_chip_flash`, `Ethos_U65_Mid_End` 228.3125 KiB under `dram`,
|
|
694
|
+
# `Ethos_U65_Client_Server` (vela's own default for this accel config)
|
|
695
|
+
# 228.25 KiB under `dram` -- same `sram 72.734375` throughout. This text
|
|
696
|
+
# ships inside the customer's `.alpmodel`, so it must say that placement was
|
|
697
|
+
# vela's, not the module's.
|
|
698
|
+
#
|
|
699
|
+
# A memory mode this module cannot classify (`_AXI0_CONST_MEMORY_MODES`) takes
|
|
700
|
+
# the third shape too -- under-claiming is the safe direction for a string a
|
|
701
|
+
# customer sizes hardware from. A run whose summary block named NO memory mode
|
|
702
|
+
# at all gets a fourth, narrower sentence: it says tan cannot tell which of the
|
|
703
|
+
# two happened, rather than picking one.
|
|
704
|
+
#
|
|
705
|
+
# `_default_profile_caveats` returns `()` -- no caveat at all -- ONLY when
|
|
706
|
+
# vela defaulted NEITHER flag, i.e. `compile()` passed a fully module-authored
|
|
707
|
+
# profile straight through. That does not mean the arena-only scope statement
|
|
708
|
+
# stops applying under `Sram_Only`: it is a CONTRACT about what `req_sram_kib`
|
|
709
|
+
# means, true whether or not vela had a flag to default (tan-cli#1021 review
|
|
710
|
+
# MINOR). `compile()` falls back to `_arena_only_scope_caveat` for exactly
|
|
711
|
+
# that case, so the sentence still reaches the fully-specified path -- the one
|
|
712
|
+
# issue #1011 named as live on every Alif Ensemble part.
|
|
713
|
+
def _arena_only_scope_clause(memory_mode: str) -> str:
|
|
714
|
+
"""The `req_sram_kib`/arena-only contract sentence (tan-cli#1011), shared
|
|
715
|
+
verbatim between `_default_profile_caveats`' defaulted-profile branch and
|
|
716
|
+
`_arena_only_scope_caveat`'s standalone one -- so a fully-specified
|
|
717
|
+
compile and a defaulted one never carry two different spellings of the
|
|
718
|
+
same contract.
|
|
719
|
+
|
|
720
|
+
tan-cli#1021 review NIT: says only "provisioned separately", never
|
|
721
|
+
"reported ... separately" -- nothing in `tan` reports the const/weights
|
|
722
|
+
region's own size anywhere (`check.py`'s sizing note carries arena+SRAM
|
|
723
|
+
only, `manifest.Target` carries a blob index with no length, and
|
|
724
|
+
`model_cmd.py` prints no byte count for it); the blob PAYLOAD carries the
|
|
725
|
+
bytes, not a reported figure."""
|
|
726
|
+
return (f"That SRAM figure is the tensor arena only -- this module's const/weights region "
|
|
727
|
+
f"also lands in SRAM under {memory_mode} but is provisioned separately, never "
|
|
728
|
+
f"folded into it.")
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
def _arena_only_scope_caveat(memory_mode: str | None) -> tuple[str, ...]:
|
|
732
|
+
"""The arena-only contract (tan-cli#1011), standalone -- for a compile
|
|
733
|
+
that used a FULLY module-authored profile, where `_default_profile_
|
|
734
|
+
caveats` returns `()` because vela had nothing left to default.
|
|
735
|
+
|
|
736
|
+
tan-cli#1021 review MINOR: the scope statement is a CONTRACT
|
|
737
|
+
(`req_sram_kib` always means the tensor arena under a `Sram_Only`-shaped
|
|
738
|
+
memory mode, whether or not vela also had to default a flag), not an
|
|
739
|
+
artefact of a defaulted profile -- so it must reach a fully-specified
|
|
740
|
+
`Sram_Only` compile too. That is the path issue #1011 named as live on
|
|
741
|
+
every Alif Ensemble part; gating the sentence behind `defaulted` being
|
|
742
|
+
non-empty left it unreachable there. `()` for any other memory mode --
|
|
743
|
+
only `_AXI0_CONST_MEMORY_MODES` carries this quirk (see the module
|
|
744
|
+
docstring's `_AXI0_CONST_MEMORY_MODES` note)."""
|
|
745
|
+
if memory_mode not in _AXI0_CONST_MEMORY_MODES:
|
|
746
|
+
return ()
|
|
747
|
+
return (_arena_only_scope_clause(memory_mode),)
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def _default_profile_caveats(defaulted: frozenset[str], system_config: str | None,
|
|
751
|
+
memory_mode: str | None) -> tuple[str, ...]:
|
|
752
|
+
"""vela's own "may be invalid or non-optimal" verdict, surfaced as a report
|
|
753
|
+
caveat for whichever profile flags it had to default -- so a customer
|
|
754
|
+
cannot mistake a default-profile figure for one authored for this module.
|
|
755
|
+
`()` when vela defaulted neither (the caller then falls back to
|
|
756
|
+
`_arena_only_scope_caveat` for the fully-specified case).
|
|
757
|
+
|
|
758
|
+
THREE SHAPES -- see the comment block above this function for what each
|
|
759
|
+
says and the measurements behind it.
|
|
760
|
+
|
|
761
|
+
Takes @defaulted rather than re-searching stdout: `compile()` resolves it
|
|
762
|
+
once (`_defaulted_flags`) and hands the SAME fact to this and to
|
|
763
|
+
`_refuse_zero_sram_footprint`, so the caveat and the refusal can never
|
|
764
|
+
disagree about which flags vela defaulted."""
|
|
765
|
+
if not defaulted:
|
|
766
|
+
return ()
|
|
767
|
+
named = ", ".join(f"{cli} {name}" for label, cli, name in
|
|
768
|
+
((_SYSTEM_CONFIG_FLAG, "system-config", system_config),
|
|
769
|
+
(_MEMORY_MODE_FLAG, "memory-mode", memory_mode))
|
|
770
|
+
if name and label in defaulted)
|
|
771
|
+
if _MEMORY_MODE_FLAG in defaulted:
|
|
772
|
+
return (f"vela used its BUILT-IN default profile ({named or 'unreported'}), not one "
|
|
773
|
+
f"authored for this module -- vela's own warning for that is \"Compilation "
|
|
774
|
+
f"may be invalid or non-optimal\". The arena/SRAM figures and the compiled "
|
|
775
|
+
f"command stream describe that default memory model, not this module's.",)
|
|
776
|
+
default_of = named or "system-config unreported"
|
|
777
|
+
if memory_mode in _AXI0_CONST_MEMORY_MODES:
|
|
778
|
+
return (f"vela used its BUILT-IN default {default_of} for "
|
|
779
|
+
f"bandwidth/latency estimates -- no module-authored one is available -- so its "
|
|
780
|
+
f"scheduling is tuned for that system, not this module's. The arena/SRAM figures "
|
|
781
|
+
f"are unaffected: they follow --memory-mode {memory_mode}, which came from this "
|
|
782
|
+
f"module's SoC metadata, whose const/arena/cache areas are all one AXI port every "
|
|
783
|
+
f"system config maps to SRAM. " + _arena_only_scope_clause(memory_mode),)
|
|
784
|
+
if memory_mode is None:
|
|
785
|
+
return (f"vela used its BUILT-IN default {default_of} -- no module-authored one is "
|
|
786
|
+
f"available -- so its scheduling is tuned for that system, not this module's. "
|
|
787
|
+
f"This run reported no memory mode, so tan cannot say whether that default also "
|
|
788
|
+
f"chose the memory area the const/weights region landed in.",)
|
|
789
|
+
return (f"vela used its BUILT-IN default {default_of} -- no module-authored one is "
|
|
790
|
+
f"available -- so its scheduling is tuned for that system, not this module's, and "
|
|
791
|
+
f"under --memory-mode {memory_mode} it was NOT bandwidth-only: that mode puts the "
|
|
792
|
+
f"const/weights region on a different AXI port from the arena, and it is the system "
|
|
793
|
+
f"config that maps ports to memory areas, so vela's default also chose which memory "
|
|
794
|
+
f"the weights land in. Only the arena/SRAM figures follow --memory-mode "
|
|
795
|
+
f"{memory_mode}, which came from this module's SoC metadata.",)
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _parse_vela_placement(stdout: str) -> tuple[int, int] | None:
|
|
799
|
+
"""(cpu_op_count, npu_op_count) from vela's own "CPU/NPU operators = N
|
|
800
|
+
(P%)" summary lines -- vela's REAL per-run placement verdict, not
|
|
801
|
+
inferred from its exit code (0 either way). None when either line is
|
|
802
|
+
absent (an unexpected vela output shape, e.g. a future version that
|
|
803
|
+
changes this text): a caller must not fabricate a placement it can't
|
|
804
|
+
actually read."""
|
|
805
|
+
counts = {k: int(v) for k, v in _PLACEMENT_RE.findall(stdout)}
|
|
806
|
+
if "CPU" not in counts or "NPU" not in counts:
|
|
807
|
+
return None
|
|
808
|
+
return counts["CPU"], counts["NPU"]
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
class VelaAdapter(CompilerAdapter):
|
|
812
|
+
backend = "ethos_u"
|
|
813
|
+
|
|
814
|
+
def is_available(self) -> bool:
|
|
815
|
+
return shutil.which("vela") is not None
|
|
816
|
+
|
|
817
|
+
def accepts(self, src_format: str) -> bool:
|
|
818
|
+
return src_format == "tflite"
|
|
819
|
+
|
|
820
|
+
def compile(self, source: Path, *, accel_config: str, out_dir: Path,
|
|
821
|
+
opts: dict | None = None,
|
|
822
|
+
vela_memory_mode: str | None = None,
|
|
823
|
+
vela_system_config: str | None = None,
|
|
824
|
+
vela_vendor_system_config: str | None = None,
|
|
825
|
+
vela_vendor_config_filename: str | None = None,
|
|
826
|
+
soc_declares_dram: bool | None = None) -> Blob:
|
|
827
|
+
# @vela_vendor_config_filename / @soc_declares_dram reach ONLY the
|
|
828
|
+
# refusal wording (`_refusal_remedy`, `_no_dram_marker`) -- the vela
|
|
829
|
+
# command line below is byte-identical with and without them, so no
|
|
830
|
+
# part can get a different artifact out of a diagnostic fact.
|
|
831
|
+
# @vela_memory_mode / @vela_system_config / @vela_vendor_system_config
|
|
832
|
+
# are the opposite: they are the SILICON's own profile
|
|
833
|
+
# (`TargetSpec.vela_*`, out of the SoC spec's `npu_toolchain.vela`) and
|
|
834
|
+
# they DO change the artifact -- that is the point. All stay optional
|
|
835
|
+
# and default to None, so a caller that resolved no profile gets
|
|
836
|
+
# byte-for-byte the flagless invocation this adapter has always issued.
|
|
837
|
+
run_dir = _run_dir(out_dir, accel_config)
|
|
838
|
+
run_dir.mkdir(parents=True, exist_ok=True)
|
|
839
|
+
cmd = ["vela", str(source), "--accelerator-config", accel_config,
|
|
840
|
+
"--output-dir", str(run_dir)]
|
|
841
|
+
# The memory mode is the load-bearing flag because it assigns const,
|
|
842
|
+
# arena and cache to AXI PORTS; the system config only maps those ports
|
|
843
|
+
# to memory areas. Under `Sram_Only` all three land on `Axi0` and every
|
|
844
|
+
# Arm `System_Config` section maps `axi0_port=Sram`, so the system
|
|
845
|
+
# config cannot move a byte -- measured, adding `--system-config
|
|
846
|
+
# Ethos_U85_SYS_Flash_High` alongside `--memory-mode Sram_Only` on
|
|
847
|
+
# `tiny_int8.tflite` and on `person_detect_int8.tflite` at
|
|
848
|
+
# `ethos-u85-256` changes no memory figure. That is a `Sram_Only` fact,
|
|
849
|
+
# NOT a general one: under `Shared_Sram` (`const_mem_area=Axi1`) the
|
|
850
|
+
# system config alone moves
|
|
851
|
+
# 228 KiB of weights between areas -- see the module docstring's
|
|
852
|
+
# measured `ethos-u65-256` sweep. Every memory-mode value that reaches
|
|
853
|
+
# here is an Arm built-in, so this works with no vendor .ini.
|
|
854
|
+
#
|
|
855
|
+
# SIDE EFFECT WORTH KNOWING, recorded not acted on: `Sram_Only` names
|
|
856
|
+
# no `arena_cache_size`, so vela schedules against an effectively
|
|
857
|
+
# unbounded SRAM budget. Measured on `person_detect_int8.tflite` at
|
|
858
|
+
# `ethos-u85-256`, vela's summary column moves `arena_cache_size`
|
|
859
|
+
# 384.0 -> 1073741824.0 and `total_npu_encoded_weights` 205472 ->
|
|
860
|
+
# 212096. Nothing in tan reads either column (`_parse_vela_summary`
|
|
861
|
+
# takes `*_memory_used` only, and tan-cli#789 stopped reading
|
|
862
|
+
# `arena_cache_size`), so this changes no shipped figure today.
|
|
863
|
+
if vela_memory_mode:
|
|
864
|
+
cmd += ["--memory-mode", vela_memory_mode]
|
|
865
|
+
# An ARM BUILT-IN system config is safe on its own -- `resolve_targets`
|
|
866
|
+
# only ever puts one here (`targets._vela_profile` withholds a
|
|
867
|
+
# vendor-gated name from this field), because a section vela cannot
|
|
868
|
+
# resolve is rc=1, not a degradation.
|
|
869
|
+
if vela_system_config:
|
|
870
|
+
cmd += ["--system-config", vela_system_config]
|
|
871
|
+
# THE VENDOR PROFILE: ALL THREE FLAGS OR NONE OF THEM. A licensed
|
|
872
|
+
# customer names their `.ini` in `ALP_VELA_CONFIG`, and only then does
|
|
873
|
+
# this part's vendor-gated `System_Config` become legal to pass.
|
|
874
|
+
#
|
|
875
|
+
# It is a TRIPLE, not the pair the design first assumed, and that is
|
|
876
|
+
# measured against real `ethos-u-vela` 5.1.0 rather than reasoned
|
|
877
|
+
# (`architecture_features.py:588-608`): supplying `--config` REPLACES
|
|
878
|
+
# vela's built-in `vela.ini` outright -- `self.vela_config_files =
|
|
879
|
+
# vela_config_files`, no merge -- and vela then refuses any default
|
|
880
|
+
# left beside it. Each of these was run:
|
|
881
|
+
#
|
|
882
|
+
# * `--config <ini>` alone -> rc=1, verbatim `Error: Incorrect
|
|
883
|
+
# argument to CLI option --config=['fake_vendor.ini']: Specifying a
|
|
884
|
+
# configuration file is not allowed when using a default system
|
|
885
|
+
# configuration`
|
|
886
|
+
# * `--config <ini> --system-config <name>` -> rc=1, `... Specifying a
|
|
887
|
+
# configuration file is not allowed when using a default memory
|
|
888
|
+
# mode`
|
|
889
|
+
# * the full triple, against an .ini defining BOTH sections -> rc=0,
|
|
890
|
+
# `System configuration Ethos_U85_SRAM_Only` / `Memory mode
|
|
891
|
+
# Sram_Only` in vela's own summary block
|
|
892
|
+
#
|
|
893
|
+
# so the vendor `.ini` must also define the `[Memory_Mode.<mode>]` this
|
|
894
|
+
# part declares -- Arm's built-in one is no longer being read. alp-sdk's
|
|
895
|
+
# own Alif recipe passes exactly this triple, and only
|
|
896
|
+
# `if(AEN_NPU_VELA_CONFIG)`
|
|
897
|
+
# (`examples/aen/aen-npu-inference-alp/CMakeLists.txt`).
|
|
898
|
+
#
|
|
899
|
+
# TODAY THIS BRANCH NEVER FIRES, and doing nothing is the correct
|
|
900
|
+
# behaviour rather than a gap: no SoC spec carries a `system_config` at
|
|
901
|
+
# all, because an Alif `System_Config` is per CORE SUBSYSTEM, not per
|
|
902
|
+
# SoC (one Ensemble die sources both `Ethos_U85_SRAM_Only` and
|
|
903
|
+
# `RTSS_HE_SRAM_Only`), so a per-SoC scalar would be right for one of
|
|
904
|
+
# its two or three Ethos-U accelerators and wrong for the rest. With
|
|
905
|
+
# `ALP_VELA_CONFIG` set and no vendor name resolved, passing `--config`
|
|
906
|
+
# anyway would be rc=1 and inventing a profile name would compile a
|
|
907
|
+
# command stream for hardware nobody described. The mechanism goes live
|
|
908
|
+
# the day metadata can name one per accelerator; until then it is wired
|
|
909
|
+
# and silent.
|
|
910
|
+
elif vela_memory_mode and vela_vendor_system_config:
|
|
911
|
+
vendor_config = _vendor_config_path()
|
|
912
|
+
if vendor_config is not None:
|
|
913
|
+
cmd += ["--config", str(vendor_config),
|
|
914
|
+
"--system-config", vela_vendor_system_config]
|
|
915
|
+
try:
|
|
916
|
+
proc = subprocess.run(
|
|
917
|
+
cmd, capture_output=True, text=True, timeout=_VELA_TIMEOUT_S, env=spawn_env()
|
|
918
|
+
)
|
|
919
|
+
except subprocess.TimeoutExpired as exc:
|
|
920
|
+
raise RuntimeError(f"vela timed out after {exc.timeout}s for {accel_config}") from exc
|
|
921
|
+
if proc.returncode != 0:
|
|
922
|
+
raise RuntimeError(f"vela failed for {accel_config}: {proc.stderr.strip()}")
|
|
923
|
+
produced = run_dir / f"{source.stem}_vela.tflite"
|
|
924
|
+
if not produced.is_file():
|
|
925
|
+
raise RuntimeError(f"vela produced no output at {produced}")
|
|
926
|
+
system_config, memory_mode = _parse_vela_profile(proc.stdout)
|
|
927
|
+
defaulted = _defaulted_flags(proc.stdout)
|
|
928
|
+
used = _parse_vela_summary(run_dir, source.stem, system_config=system_config)
|
|
929
|
+
arena, sram_kib = _footprint(used)
|
|
930
|
+
placement = _parse_vela_placement(proc.stdout)
|
|
931
|
+
cpu_ops, npu_ops = placement if placement is not None else (None, None)
|
|
932
|
+
# Only a CONFIRMED NPU placement refuses: 0 NPU operators is a real,
|
|
933
|
+
# legitimate 0 KiB footprint (full CPU fallback), and an unreadable
|
|
934
|
+
# placement summary already degrades through `tan.model.check`'s
|
|
935
|
+
# `_vela_placement_unreadable` rather than claiming anything.
|
|
936
|
+
if npu_ops and sram_kib <= 0:
|
|
937
|
+
_refuse_zero_sram_footprint(
|
|
938
|
+
accel_config=accel_config, npu_ops=npu_ops, cpu_ops=cpu_ops, used=used,
|
|
939
|
+
system_config=system_config, memory_mode=memory_mode, defaulted=defaulted,
|
|
940
|
+
vendor_config_filename=vela_vendor_config_filename,
|
|
941
|
+
soc_declares_dram=soc_declares_dram)
|
|
942
|
+
# tan-cli#1021 review MINOR: a FULLY module-authored profile (nothing
|
|
943
|
+
# defaulted) still gets the arena-only scope caveat under
|
|
944
|
+
# `_AXI0_CONST_MEMORY_MODES` -- `_default_profile_caveats` returns
|
|
945
|
+
# `()` there, since it exists to flag DEFAULTED flags only.
|
|
946
|
+
caveats = (_default_profile_caveats(defaulted, system_config, memory_mode)
|
|
947
|
+
or _arena_only_scope_caveat(memory_mode))
|
|
948
|
+
return Blob(format="vela_tflite", payload=produced.read_bytes(),
|
|
949
|
+
arena_bytes=arena, compiler_version=_vela_version(),
|
|
950
|
+
req_sram_kib=sram_kib, cpu_op_count=cpu_ops, npu_op_count=npu_ops,
|
|
951
|
+
caveats=caveats)
|