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,430 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
"""Host-tier `tan model prep` (tan-cli#1287): runners and text renderers, kept
|
|
3
|
+
out of `model_cmd.py` (module-size budget); that file owns the typer surface
|
|
4
|
+
and `finish()`.
|
|
5
|
+
|
|
6
|
+
**`prep MODEL.onnx --calibration DIR [--out DIR] [--per-channel]`** validates
|
|
7
|
+
the calibration set (`.npy` samples, shape-matched to the model input), INT8
|
|
8
|
+
quantizes with onnxruntime QDQ static to `<out>/<stem>.int8.onnx`, and reports
|
|
9
|
+
the fp32-vs-int8 accuracy delta -- a silent INT8 accuracy cliff is the failure
|
|
10
|
+
this exists to catch. No vendor toolchain and no hardware; the vendor compile
|
|
11
|
+
(`tan model build`) is a separate step. ONNX input only: `.tflite` conversion
|
|
12
|
+
(tf2onnx/tensorflow) is deferred.
|
|
13
|
+
|
|
14
|
+
**`run MODEL.onnx [--runs N] [--input X.npy]`** is a host reference run
|
|
15
|
+
(onnxruntime CPU, `backend: cpu-host`): median latency over N runs and the
|
|
16
|
+
output argmax. **`ab MODEL.onnx --against OTHER.onnx`** runs both with the same
|
|
17
|
+
input and compares latency and file size. Both are FUNCTIONAL + host-latency
|
|
18
|
+
references, never SoM performance -- `peakSramKib`/`powerMj` stay `null` until
|
|
19
|
+
the bench-gated on-device tier fills the same schema. The input defaults to a
|
|
20
|
+
deterministic seeded sample shaped like the model's first input.
|
|
21
|
+
|
|
22
|
+
Every refusal is a plain `Issue(...)` return (never an exception) so the codes
|
|
23
|
+
stay visible to the issue-code registry gate.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import os
|
|
29
|
+
import shutil
|
|
30
|
+
import tempfile
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from tan.commands.build_output import ProjectContext
|
|
35
|
+
from tan.core.model_host import broken_extra_message, extra_missing_message, missing_extra_modules
|
|
36
|
+
from tan.core.publish import publish_exclusive
|
|
37
|
+
from tan.envelope import Issue, Project, SdkInfo
|
|
38
|
+
from tan.exit_codes import ExitCode
|
|
39
|
+
|
|
40
|
+
PREP_DATA_SCHEMA_VERSION = "1"
|
|
41
|
+
RUN_DATA_SCHEMA_VERSION = "1"
|
|
42
|
+
AB_DATA_SCHEMA_VERSION = "1"
|
|
43
|
+
|
|
44
|
+
Result = tuple[Project, SdkInfo | None, dict, list[Issue], ExitCode]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def prep_empty_data() -> dict[str, Any]:
|
|
48
|
+
return {
|
|
49
|
+
"schemaVersion": PREP_DATA_SCHEMA_VERSION,
|
|
50
|
+
"source": None,
|
|
51
|
+
"output": None,
|
|
52
|
+
"calibration": None,
|
|
53
|
+
"accuracy": None,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def run_empty_data() -> dict[str, Any]:
|
|
58
|
+
return {"schemaVersion": RUN_DATA_SCHEMA_VERSION, "model": None, "result": None}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def ab_empty_data() -> dict[str, Any]:
|
|
62
|
+
return {"schemaVersion": AB_DATA_SCHEMA_VERSION, "a": None, "b": None, "comparison": None}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def resolve_model_path(context: ProjectContext, raw: str | None, role: str) -> Path | Issue:
|
|
66
|
+
"""`raw` as an existing file (relative to the project root), or the
|
|
67
|
+
`model.model-source-missing` refusal."""
|
|
68
|
+
if not raw:
|
|
69
|
+
return Issue(
|
|
70
|
+
"model.model-source-missing",
|
|
71
|
+
"error",
|
|
72
|
+
f"`tan model {role}` needs a model file (.onnx) argument.",
|
|
73
|
+
)
|
|
74
|
+
path = Path(raw)
|
|
75
|
+
if not path.is_absolute():
|
|
76
|
+
path = Path(context.workspace_root) / path
|
|
77
|
+
if not path.is_file():
|
|
78
|
+
return Issue("model.model-source-missing", "error", f"Model file not found: {path}")
|
|
79
|
+
return path
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def format_refusal(path: Path, verb: str) -> Issue | None:
|
|
83
|
+
"""The `model.model-format-unsupported` refusal for a non-ONNX input."""
|
|
84
|
+
if path.suffix.lower() == ".onnx":
|
|
85
|
+
return None
|
|
86
|
+
hint = (
|
|
87
|
+
" TFLite -> ONNX conversion (tf2onnx/tensorflow) is not available yet."
|
|
88
|
+
if path.suffix.lower() == ".tflite"
|
|
89
|
+
else ""
|
|
90
|
+
)
|
|
91
|
+
return Issue(
|
|
92
|
+
"model.model-format-unsupported",
|
|
93
|
+
"error",
|
|
94
|
+
f"`tan model {verb}` takes an .onnx model; got {path.name}.{hint}",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _calibration_row(info: Any) -> dict[str, Any]:
|
|
99
|
+
return {"samples": info.samples, "inputName": info.input_name, "inputShape": list(info.input_shape)}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _accuracy_row(rep: Any) -> dict[str, Any]:
|
|
103
|
+
return {
|
|
104
|
+
"samples": rep.samples,
|
|
105
|
+
"top1AgreementPct": rep.top1_agreement_pct,
|
|
106
|
+
"meanCosine": rep.mean_cosine,
|
|
107
|
+
"maxAbsErr": rep.max_abs_err,
|
|
108
|
+
"verdict": rep.verdict,
|
|
109
|
+
"guidance": rep.guidance,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def run_prep(
|
|
114
|
+
*,
|
|
115
|
+
context: ProjectContext,
|
|
116
|
+
source: str | None,
|
|
117
|
+
calibration: str | None,
|
|
118
|
+
out: str,
|
|
119
|
+
per_channel: bool,
|
|
120
|
+
min_samples: int,
|
|
121
|
+
) -> Result:
|
|
122
|
+
project, sdk = context.project(), context.sdk
|
|
123
|
+
data = prep_empty_data()
|
|
124
|
+
bad_count = _bad_count("--min-samples", min_samples)
|
|
125
|
+
if bad_count is not None:
|
|
126
|
+
return project, sdk, data, [bad_count], ExitCode.VALIDATION_FAILURE
|
|
127
|
+
|
|
128
|
+
def refuse(issue: Issue, exit_code: ExitCode) -> Result:
|
|
129
|
+
return project, sdk, data, [issue], exit_code
|
|
130
|
+
|
|
131
|
+
path = resolve_model_path(context, source, "prep")
|
|
132
|
+
if isinstance(path, Issue):
|
|
133
|
+
return refuse(path, ExitCode.VALIDATION_FAILURE)
|
|
134
|
+
bad_format = format_refusal(path, "prep")
|
|
135
|
+
if bad_format is not None:
|
|
136
|
+
return refuse(bad_format, ExitCode.VALIDATION_FAILURE)
|
|
137
|
+
data["source"] = path.as_posix()
|
|
138
|
+
missing = missing_extra_modules("prep")
|
|
139
|
+
if missing:
|
|
140
|
+
extra = Issue("model.model-extra-missing", "error", extra_missing_message("prep", missing))
|
|
141
|
+
return refuse(extra, ExitCode.RUNTIME_FAILURE)
|
|
142
|
+
if not calibration:
|
|
143
|
+
need = Issue(
|
|
144
|
+
"model.prep-calibration-invalid",
|
|
145
|
+
"error",
|
|
146
|
+
"`tan model prep` needs --calibration DIR (a directory of .npy samples).",
|
|
147
|
+
)
|
|
148
|
+
return refuse(need, ExitCode.VALIDATION_FAILURE)
|
|
149
|
+
|
|
150
|
+
cal_dir = _under(context, calibration)
|
|
151
|
+
out_dir = _under(context, out)
|
|
152
|
+
done = _quantize(path, cal_dir, out_dir, per_channel, min_samples)
|
|
153
|
+
if isinstance(done, Issue):
|
|
154
|
+
code = ExitCode.VALIDATION_FAILURE
|
|
155
|
+
if done.code in ("model.prep-failed", "model.model-extra-missing"):
|
|
156
|
+
code = ExitCode.RUNTIME_FAILURE
|
|
157
|
+
return refuse(done, code)
|
|
158
|
+
info, quantized, report = done
|
|
159
|
+
data["calibration"] = _calibration_row(info)
|
|
160
|
+
data["output"] = quantized.as_posix()
|
|
161
|
+
data["accuracy"] = _accuracy_row(report)
|
|
162
|
+
issues: list[Issue] = []
|
|
163
|
+
if report.verdict != "good":
|
|
164
|
+
issues.append(Issue("model.prep-accuracy-degraded", "warning", report.guidance or "INT8 accuracy dropped."))
|
|
165
|
+
return project, sdk, data, issues, ExitCode.SUCCESS
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _bad_count(flag: str, value: int) -> Issue | None:
|
|
169
|
+
if value >= 1:
|
|
170
|
+
return None
|
|
171
|
+
return Issue("model.unexpected-argument", "error", f"{flag} must be at least 1 (got {value}).")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _under(context: ProjectContext, raw: str) -> Path:
|
|
175
|
+
path = Path(raw)
|
|
176
|
+
return path if path.is_absolute() else Path(context.workspace_root) / path
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _missing_ancestors(path: Path) -> list[Path]:
|
|
180
|
+
"""`path` and its parents that do not exist yet, outermost first -- the
|
|
181
|
+
directories a `mkdir(parents=True)` would create."""
|
|
182
|
+
missing = []
|
|
183
|
+
for p in (path, *path.parents):
|
|
184
|
+
if p.exists():
|
|
185
|
+
break
|
|
186
|
+
missing.append(p)
|
|
187
|
+
return missing[::-1]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _quantize(
|
|
191
|
+
path: Path, cal_dir: Path, out_dir: Path, per_channel: bool, min_samples: int
|
|
192
|
+
) -> tuple[Any, Path, Any] | Issue:
|
|
193
|
+
"""Validate calibration, quantize, measure -- or the refusing `Issue`.
|
|
194
|
+
Everything is built in a private staging directory inside `out_dir` and
|
|
195
|
+
published (never overwriting) only after the accuracy run succeeds; a
|
|
196
|
+
failure removes the staging directory and every directory this run created."""
|
|
197
|
+
final = out_dir / f"{path.stem}.int8.onnx"
|
|
198
|
+
if os.path.lexists(final):
|
|
199
|
+
return Issue("model.prep-output-exists", "error", f"{final} already exists; remove it or use --out.")
|
|
200
|
+
created = _missing_ancestors(out_dir)
|
|
201
|
+
staging: Path | None = None
|
|
202
|
+
done = False
|
|
203
|
+
try:
|
|
204
|
+
from tan.model.prep import PrepError, accuracy_delta, model_input, quantize, validate_calibration # noqa: PLC0415
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
model_input(path)
|
|
208
|
+
except PrepError as err:
|
|
209
|
+
return Issue("model.prep-failed", "error", str(err))
|
|
210
|
+
try:
|
|
211
|
+
info = validate_calibration(cal_dir, path, min_samples=min_samples)
|
|
212
|
+
except PrepError as err:
|
|
213
|
+
return Issue("model.prep-calibration-invalid", "error", str(err))
|
|
214
|
+
try:
|
|
215
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
216
|
+
staging = Path(tempfile.mkdtemp(dir=out_dir, prefix=".tan-prep-"))
|
|
217
|
+
staged = quantize(path, staging / "q.onnx", cal_dir, per_channel=per_channel)
|
|
218
|
+
report = accuracy_delta(path, staged, cal_dir)
|
|
219
|
+
except (PrepError, OSError) as err:
|
|
220
|
+
return Issue("model.prep-failed", "error", str(err))
|
|
221
|
+
try:
|
|
222
|
+
publish_exclusive(staged, final)
|
|
223
|
+
except FileExistsError:
|
|
224
|
+
return Issue("model.prep-output-exists", "error", f"{final} already exists; nothing was overwritten.")
|
|
225
|
+
except OSError as err:
|
|
226
|
+
return Issue("model.prep-failed", "error", f"could not write {final}: {err}")
|
|
227
|
+
done = True
|
|
228
|
+
return info, final, report
|
|
229
|
+
except ImportError as err:
|
|
230
|
+
return Issue("model.model-extra-missing", "error", broken_extra_message("prep", str(err)))
|
|
231
|
+
finally:
|
|
232
|
+
if staging is not None:
|
|
233
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
234
|
+
if not done:
|
|
235
|
+
for directory in reversed(created):
|
|
236
|
+
try:
|
|
237
|
+
directory.rmdir()
|
|
238
|
+
except OSError:
|
|
239
|
+
break
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def render_prep_text(data: dict) -> list[str]:
|
|
243
|
+
if not data.get("output"):
|
|
244
|
+
return []
|
|
245
|
+
cal, acc = data["calibration"], data["accuracy"]
|
|
246
|
+
return [
|
|
247
|
+
f"calibration: {cal['samples']} samples, input {cal['inputName']} {cal['inputShape']}",
|
|
248
|
+
f"quantized {data['source']} -> {data['output']}",
|
|
249
|
+
f"accuracy (fp32 vs int8): {acc['verdict']}; top-1 agreement {acc['top1AgreementPct']}%, "
|
|
250
|
+
f"mean cosine {acc['meanCosine']}, max abs err {acc['maxAbsErr']}",
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _result_row(result: Any, path: Path) -> dict[str, Any]:
|
|
255
|
+
return {
|
|
256
|
+
"model": path.as_posix(),
|
|
257
|
+
"backend": result.backend,
|
|
258
|
+
"tier": "host",
|
|
259
|
+
"latencyMs": result.latency_ms,
|
|
260
|
+
"outputArgmax": result.output_argmax,
|
|
261
|
+
"peakSramKib": result.peak_sram_kib,
|
|
262
|
+
"powerMj": result.power_mj,
|
|
263
|
+
"runs": result.runs,
|
|
264
|
+
"sizeBytes": path.stat().st_size,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _measure(path: Path, input_path: Path | None, runs: int, sample: Any) -> tuple[Any, Any] | Issue:
|
|
269
|
+
"""`(RunResult, input sample)` for `path`, or the `model.run-failed` refusal.
|
|
270
|
+
`sample` is reused across `ab`'s two models so both see the same input."""
|
|
271
|
+
from tan.model.measure import MeasureError, default_input, run_host # noqa: PLC0415
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
if sample is None:
|
|
275
|
+
if input_path is not None:
|
|
276
|
+
import numpy as np # noqa: PLC0415
|
|
277
|
+
|
|
278
|
+
sample = np.load(input_path, allow_pickle=False)
|
|
279
|
+
else:
|
|
280
|
+
sample = default_input(path)
|
|
281
|
+
return run_host(path, sample, runs=runs), sample
|
|
282
|
+
except ImportError as err:
|
|
283
|
+
return Issue("model.model-extra-missing", "error", broken_extra_message("run", str(err)))
|
|
284
|
+
except (MeasureError, OSError, ValueError, AttributeError, TypeError) as err:
|
|
285
|
+
return Issue("model.run-failed", "error", str(err))
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def _cast_warnings(path: Path, result: Any) -> list[Issue]:
|
|
289
|
+
"""`model.input-dtype-cast` (tan-cli#1497) when the sample (the user's
|
|
290
|
+
`--input` .npy, or tan's own generated sample reused for `ab`'s second
|
|
291
|
+
model) was cast to the model input dtype -- float to int truncates
|
|
292
|
+
silently otherwise."""
|
|
293
|
+
note = getattr(result, "input_cast", None)
|
|
294
|
+
if not note:
|
|
295
|
+
return []
|
|
296
|
+
return [Issue("model.input-dtype-cast", "warning", f"{path.name}: {note}")]
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _host_preflight(
|
|
300
|
+
context: ProjectContext, verb: str, raws: list[str | None], data: dict
|
|
301
|
+
) -> tuple[list[Path], None] | tuple[None, Issue]:
|
|
302
|
+
"""Resolve every model path, check format and the extra; the first refusal."""
|
|
303
|
+
paths: list[Path] = []
|
|
304
|
+
for raw in raws:
|
|
305
|
+
path = resolve_model_path(context, raw, verb)
|
|
306
|
+
if isinstance(path, Issue):
|
|
307
|
+
return None, path
|
|
308
|
+
bad = format_refusal(path, verb)
|
|
309
|
+
if bad is not None:
|
|
310
|
+
return None, bad
|
|
311
|
+
paths.append(path)
|
|
312
|
+
missing = missing_extra_modules(verb)
|
|
313
|
+
if missing:
|
|
314
|
+
return None, Issue("model.model-extra-missing", "error", extra_missing_message(verb, missing))
|
|
315
|
+
return paths, None
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _refusal_exit(issue: Issue) -> ExitCode:
|
|
319
|
+
return ExitCode.RUNTIME_FAILURE if issue.code in ("model.model-extra-missing", "model.run-failed") else ExitCode.VALIDATION_FAILURE
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def run_run(
|
|
323
|
+
*, context: ProjectContext, source: str | None, runs: int, input_file: str | None
|
|
324
|
+
) -> Result:
|
|
325
|
+
project, sdk = context.project(), context.sdk
|
|
326
|
+
data = run_empty_data()
|
|
327
|
+
bad_count = _bad_count("--runs", runs)
|
|
328
|
+
if bad_count is not None:
|
|
329
|
+
return project, sdk, data, [bad_count], ExitCode.VALIDATION_FAILURE
|
|
330
|
+
paths, issue = _host_preflight(context, "run", [source], data)
|
|
331
|
+
if issue is not None:
|
|
332
|
+
return project, sdk, data, [issue], _refusal_exit(issue)
|
|
333
|
+
assert paths is not None
|
|
334
|
+
measured = _measure(paths[0], _under(context, input_file) if input_file else None, runs, None)
|
|
335
|
+
if isinstance(measured, Issue):
|
|
336
|
+
return project, sdk, data, [measured], _refusal_exit(measured)
|
|
337
|
+
data["model"] = paths[0].as_posix()
|
|
338
|
+
data["result"] = _result_row(measured[0], paths[0])
|
|
339
|
+
return project, sdk, data, _cast_warnings(paths[0], measured[0]), ExitCode.SUCCESS
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def run_ab(
|
|
343
|
+
*,
|
|
344
|
+
context: ProjectContext,
|
|
345
|
+
source: str | None,
|
|
346
|
+
against: str | None,
|
|
347
|
+
runs: int,
|
|
348
|
+
input_file: str | None,
|
|
349
|
+
) -> Result:
|
|
350
|
+
from tan.model.measure import compare # noqa: PLC0415
|
|
351
|
+
|
|
352
|
+
project, sdk = context.project(), context.sdk
|
|
353
|
+
data = ab_empty_data()
|
|
354
|
+
bad_count = _bad_count("--runs", runs)
|
|
355
|
+
if bad_count is not None:
|
|
356
|
+
return project, sdk, data, [bad_count], ExitCode.VALIDATION_FAILURE
|
|
357
|
+
paths, issue = _host_preflight(context, "ab", [source, against], data)
|
|
358
|
+
if issue is not None:
|
|
359
|
+
return project, sdk, data, [issue], _refusal_exit(issue)
|
|
360
|
+
assert paths is not None
|
|
361
|
+
a_path, b_path = paths
|
|
362
|
+
input_path = _under(context, input_file) if input_file else None
|
|
363
|
+
first = _measure(a_path, input_path, runs, None)
|
|
364
|
+
if isinstance(first, Issue):
|
|
365
|
+
return project, sdk, data, [first], _refusal_exit(first)
|
|
366
|
+
second = _measure(b_path, input_path, runs, first[1])
|
|
367
|
+
if isinstance(second, Issue):
|
|
368
|
+
return project, sdk, data, [second], _refusal_exit(second)
|
|
369
|
+
a_res, b_res = first[0], second[0]
|
|
370
|
+
cmp = compare(a_res, b_res, size_a=a_path.stat().st_size, size_b=b_path.stat().st_size)
|
|
371
|
+
data["a"] = _result_row(a_res, a_path)
|
|
372
|
+
data["b"] = _result_row(b_res, b_path)
|
|
373
|
+
data["comparison"] = {
|
|
374
|
+
"faster": cmp.faster,
|
|
375
|
+
"latencyRatio": cmp.latency_ratio,
|
|
376
|
+
"aLatencyMs": cmp.a_latency_ms,
|
|
377
|
+
"bLatencyMs": cmp.b_latency_ms,
|
|
378
|
+
"sizeDeltaBytes": cmp.size_delta_bytes,
|
|
379
|
+
}
|
|
380
|
+
return (project, sdk, data,
|
|
381
|
+
[*_cast_warnings(a_path, a_res), *_cast_warnings(b_path, b_res)],
|
|
382
|
+
ExitCode.SUCCESS)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _device_summary(r: dict) -> str:
|
|
386
|
+
"""tan-cli#1518: one measured on-device row, labelled with the scope of its
|
|
387
|
+
latency figure and the energy/SRAM it carries -- never as a host reference."""
|
|
388
|
+
parts = [f"{r.get('latencyMs')} ms/inference ({r.get('latencyScope')})"]
|
|
389
|
+
if r.get("peakSramKib") is not None:
|
|
390
|
+
parts.append(f"peak SRAM {r['peakSramKib']} KiB")
|
|
391
|
+
energy = r.get("energy")
|
|
392
|
+
if energy:
|
|
393
|
+
parts.append(f"energy {energy['valueMjPerInference']} mJ/inference ({energy['scope']})")
|
|
394
|
+
return f"{r.get('backend')}: " + ", ".join(parts)
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def render_run_text(data: dict) -> list[str]:
|
|
398
|
+
r = data.get("result")
|
|
399
|
+
if not r:
|
|
400
|
+
return []
|
|
401
|
+
if r.get("tier") == "device":
|
|
402
|
+
return [f"{r.get('model') or '(no model file)'}: {_device_summary(r)}",
|
|
403
|
+
"measured on the device (tier: device)"]
|
|
404
|
+
return [
|
|
405
|
+
f"{r['model']}: {r['backend']} median {r['latencyMs']} ms over {r['runs']} runs, "
|
|
406
|
+
f"output argmax {r['outputArgmax']}",
|
|
407
|
+
"host reference only -- not SoM performance",
|
|
408
|
+
]
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def render_ab_text(data: dict) -> list[str]:
|
|
412
|
+
c = data.get("comparison")
|
|
413
|
+
if not c:
|
|
414
|
+
return []
|
|
415
|
+
a, b = data["a"], data["b"]
|
|
416
|
+
if a.get("tier") == "device" and b.get("tier") == "device":
|
|
417
|
+
size = c["sizeDeltaBytes"]
|
|
418
|
+
return [
|
|
419
|
+
f"A {a.get('model') or '(no model file)'}: {_device_summary(a)}",
|
|
420
|
+
f"B {b.get('model') or '(no model file)'}: {_device_summary(b)}",
|
|
421
|
+
f"faster: {c['faster']}; B/A latency ratio {c['latencyRatio']}"
|
|
422
|
+
+ ("" if size is None else f"; size delta {size} bytes"),
|
|
423
|
+
"measured on the device (tier: device)",
|
|
424
|
+
]
|
|
425
|
+
return [
|
|
426
|
+
f"A {a['model']}: {c['aLatencyMs']} ms, {a['sizeBytes']} bytes",
|
|
427
|
+
f"B {b['model']}: {c['bLatencyMs']} ms, {b['sizeBytes']} bytes",
|
|
428
|
+
f"faster: {c['faster']}; B/A latency ratio {c['latencyRatio']}; size delta {c['sizeDeltaBytes']} bytes",
|
|
429
|
+
"host reference only -- not SoM performance",
|
|
430
|
+
]
|