Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ All notable changes to `tan` are documented here. Format follows

## [Unreleased]

### Added
- **`tan model` edge-AI lifecycle surface** (thin envelope wrappers over
`alp_cli model`, all emitting the stable `{command,ok,exitCode,project,data,
issues}` envelope):
- **`tan model check`** — offline static pre-flight fit/perf: per-SoM-backend
verdict (`fits` / `cpu-fallback` / `no-fit`) plus estimated SRAM, latency and
op-coverage, before you build. Single-model (`<model> --sku`) or board-mode
(`--board board.yaml [--model NAME]`).
- **`tan model zoo`** / **`tan model add`** — browse curated model-zoo entries
(marked `runs_here` for the SoM via `--sku`/`--board`) and one-click append a
chosen entry to `board.yaml` `models:` (source fetched + SHA-256 verified).
- **`tan model prep`** — license-free INT8 quantize + fp32-vs-int8 accuracy
report (`.onnx` or `.tflite` input).
- **`tan model run`** (repurposed from the streaming placeholder to a wrappable
host reference run, `backend: cpu-host`) and **`tan model ab`** (A/B two
models on the same input). Host-reference latency, NOT target-SoM performance.

## [0.3.0] — 2026-07-24

### Added
Expand Down
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ executes it — it is the single executor and the user command surface for
building, flashing, and inspecting Alp Lab E1M / E1M-X firmware.

`build` / `run` / `size` / `image` / `flash` / `clean` / `renode` are native
Rust; only `migrate` / `lock` / `quality` still forward to `west alp-*`, and
`model` / `monitor` / `new-som` / `faultdecode` to the SDK `alp` CLI. Licensed
Rust; `migrate` / `lock` / `quality` still forward to `west alp-*`, and
`monitor` / `new-som` / `faultdecode` to the SDK `alp` CLI. `model` is no longer
a bare forwarder — it is a fuller subcommand family, a thin envelope wrapper over
the SDK's `alp model <cmd>` (see [Model](#model)). Licensed
**Apache-2.0** (see [`LICENSE`](LICENSE); the SPDX identifier is also set in each
`Cargo.toml` and source header).

Expand Down Expand Up @@ -126,7 +128,8 @@ script.
| **Configure & verify** | `validate` · `generate` · `diff` · `inspect` · `trace` · `doctor` · `debug-config` · `support-bundle` · `kconfig` |
| **Build & run** (native) | `build` · `run` · `flash` · `image` · `size` · `clean` · `renode` |
| **Environment** | `bootstrap` · `sdk` · `completion` |
| **Forwarders** | `migrate` · `lock` · `quality` → `west alp-*`; `model` · `monitor` · `new-som` · `faultdecode` → `python -m alp_cli` |
| **Model** | `model build` · `list` · `info` · `doctor` · `check` · `zoo` · `add` · `prep` · `run` · `ab` (envelope wrapper over `alp model` — see [Model](#model)) |
| **Forwarders** | `migrate` · `lock` · `quality` → `west alp-*`; `monitor` · `new-som` · `faultdecode` → `python -m alp_cli` |

`tan <command> --help` for flags. Global flags apply to every command:

Expand All @@ -144,6 +147,29 @@ script.
alp-sdk-vscode extension consumes. Text output is for humans and may change;
the envelope is the API.

### Model

`tan model <cmd>` mirrors the alp-sdk `alp model <cmd>` surface — a thin envelope
wrapper emitting `{command, ok, exitCode, project, data, issues}`. The
pre-existing subcommands compile the `board.yaml` `models:` list and inspect
toolchains; `check` / `zoo` / `add` / `prep` / `run` / `ab` are the
model-lifecycle add-ons.

| Subcommand | What it does |
| --- | --- |
| `model build` / `list` / `info` / `doctor` | Compile `board.yaml` models: → `.alpmodel`; list; decode; report toolchains. |
| `model check <model.tflite\|.onnx> --sku <SKU>` (or `--board board.yaml [--model NAME]`) `[--format human\|json]` | Static **pre-flight** fit/perf, offline, no toolchain. Per SoM-backend verdict `fits` / `cpu-fallback` / `no-fit`, plus est SRAM (vs the SoC arena budget), est latency, op-coverage %, unsupported ops. Labelled `source:static` (tier-1, biased **conservative** — never over-promises "fits"); verified on silicon later. |
| `model zoo [--sku <SKU> \| --board board.yaml] [--format]` | Browse curated model-zoo entries (`metadata/model_zoo/<id>.yaml`), each marked `runs_here` for the SoM via `validated_soms`. Link + fetch + layer, no weight redistribution. |
| `model add <zoo-id> [--board board.yaml] [--name NAME] [--models-dir DIR]` | Fetch the source (URL sha256-verified, or bundled) and append `{name, source}` to `board.yaml` models:. Non-destructive (duplicate name errors). |
| `model prep <model.onnx\|.tflite> --calibration <dir> [--out] [--per-channel] [--min-samples N]` | License-free INT8 quantize (onnxruntime QDQ) + fp32-vs-int8 accuracy report (top1 agreement %, mean cosine, max-abs-err, verdict `good` / `degraded` + guidance). `.tflite` is converted to ONNX first via tf2onnx. |
| `model run <model.onnx> [--input FILE.npy] [--expected LABEL] [--runs N]` | **Host** reference run, backend `cpu-host`: functional + host-latency + accuracy. `peak_sram_kib` / `power_mj` are null on host. |
| `model ab <a.onnx> <b.onnx> [--input] [--runs]` | A/B two models on the same input (host reference): latency + size delta. |

Honest caveats: `run` / `ab` are a **host reference, not target-SoM perf**;
`check` is a **conservative static pre-flight estimate**, verified on silicon
later; power + on-device measurement are **HW-gated** (they need the EVK
power-topology + Yocto NPU runtimes).

## Where it sits (three repos, one executor)

```
Expand Down
9 changes: 7 additions & 2 deletions crates/tan-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,10 @@ Ported commands: `validate`, `generate`, `init`, `scaffold`, `examples`,
Rust;
only `migrate`/`lock`/`quality` still forward to `west alp-*`. `kconfig`
wraps the SDK's `--emit kconfig` (needs a bootstrapped Zephyr workspace).
Plus `model`/`monitor`/`new-som`/`faultdecode` forwarders to the SDK `alp`
CLI (`python -m alp_cli <sub>`).
Plus `monitor`/`new-som`/`faultdecode` forwarders to the SDK `alp` CLI
(`python -m alp_cli <sub>`). `model` mirrors `alp model`
(`build`/`list`/`info`/`doctor`/`check`/`zoo`/`add`/`prep`/`run`/`ab`),
surfacing the JSON envelope (`{command,ok,exitCode,project,data,issues}`)
rather than being an opaque passthrough — `check` is a conservative offline
pre-flight (fit/perf estimate, verified on silicon later); `run`/`ab` are
host-reference runs, not target-SoM performance.
196 changes: 195 additions & 1 deletion crates/tan-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ pub enum Command {
/// Run the board.yaml quality checks (`west alp-quality`).
Quality(WestForwardArgs),
/// Compile + package board.yaml `models:` into `.alpmodel` (`alp model`).
Model(WestForwardArgs),
Model(ModelArgs),
/// Open a serial console to the board (`alp monitor`).
Monitor(WestForwardArgs),
/// Scaffold a new SoM's metadata skeleton (`alp new-som`).
Expand Down Expand Up @@ -320,6 +320,200 @@ pub struct WestForwardArgs {
pub args: Vec<String>,
}

/// Args for `model`: a real clap subcommand group (not a trailing-var-arg
/// passthrough), so `--format`/other globals parse correctly regardless of
/// where they're placed on the command line — a passthrough would otherwise
/// swallow a global placed AFTER `model` (e.g. `tan model list --format
/// json`, the shape the vscode extension calls) into the forwarded argv
/// instead of parsing it.
#[derive(Debug, Args)]
pub struct ModelArgs {
#[command(subcommand)]
pub sub: ModelSub,
}

/// The `alp_cli model` subcommands. All mirror `alp_cli`'s own option surface
/// (`--board`/`--out`/`--metadata-root`/…) so tan can wrap their `--format
/// json` payload in its envelope.
#[derive(Debug, Subcommand)]
pub enum ModelSub {
/// Compile board.yaml `models:` into `.alpmodel` packages.
Build(ModelBuildArgs),
/// List board.yaml `models:` + built `.alpmodel` status.
List(ModelListArgs),
/// Decode a built `.alpmodel`: targets, requires, coverage matrix.
Info(ModelInfoArgs),
/// Static pre-flight fit/perf check for a model on a SoM (offline, no toolchain).
Check(ModelCheckArgs),
/// Browse curated model-zoo entries (and which run on a SoM).
Zoo(ModelZooArgs),
/// Add a model-zoo entry to board.yaml (fetch source + append models:).
Add(ModelAddArgs),
/// License-free INT8 quantize + fp32-vs-int8 accuracy report.
Prep(ModelPrepArgs),
/// Report installed NPU compiler toolchains.
Doctor,
/// Host reference run of a model: deterministic (or supplied) input,
/// optional top-1 accuracy check, optional timed median-latency run.
Run(ModelRunArgs),
/// A/B two models on the same input.
Ab(ModelAbArgs),
}

/// Args for `model build`.
#[derive(Debug, Args)]
pub struct ModelBuildArgs {
/// Path to board.yaml (default: `board.yaml`).
#[arg(long)]
pub board: Option<String>,
/// Output directory for built `.alpmodel` packages (default: `build/models`).
#[arg(long)]
pub out: Option<String>,
/// Path to the metadata/ root (default: the SDK's own `metadata/`).
#[arg(long = "metadata-root")]
pub metadata_root: Option<String>,
/// Build only the named `models:` entry (default: build all declared models).
#[arg(long, value_name = "NAME")]
pub model: Option<String>,
}

/// Args for `model list`.
#[derive(Debug, Args)]
pub struct ModelListArgs {
/// Path to board.yaml (default: `board.yaml`).
#[arg(long)]
pub board: Option<String>,
/// Build output directory (default: `build/models`).
#[arg(long)]
pub out: Option<String>,
}

/// Args for `model info`.
#[derive(Debug, Args)]
pub struct ModelInfoArgs {
/// Name of the built model to decode.
#[arg(value_name = "NAME")]
pub name: String,
/// Build output directory (default: `build/models`).
#[arg(long)]
pub out: Option<String>,
/// board.yaml — enables the SoM coverage matrix.
#[arg(long)]
pub board: Option<String>,
/// Path to the metadata/ root (default: the SDK's own `metadata/`).
#[arg(long = "metadata-root")]
pub metadata_root: Option<String>,
}

/// Args for `model check`. Two modes: a single `MODEL --sku`, or `--board`
/// (check every board.yaml `models:` entry against `som.sku`).
#[derive(Debug, Args)]
pub struct ModelCheckArgs {
/// Model file to check (`.tflite`). Mutually exclusive with `--board`.
#[arg(value_name = "MODEL")]
pub model: Option<String>,
/// SoM SKU, e.g. `E1M-AEN801`. Required unless `--board` supplies it.
#[arg(long, value_name = "SKU")]
pub sku: Option<String>,
/// Check every (or one, with `--model`) `models:` entry in this board.yaml.
#[arg(long, value_name = "PATH")]
pub board: Option<String>,
/// With `--board`, check only the named `models:` entry.
#[arg(long = "model", value_name = "NAME")]
pub select: Option<String>,
/// Path to the metadata/ root (default: the SDK's own `metadata/`).
#[arg(long = "metadata-root")]
pub metadata_root: Option<String>,
}

/// Args for `model zoo`.
#[derive(Debug, Args)]
pub struct ModelZooArgs {
/// Mark which entries run on this SoM (via validated_soms).
#[arg(long, value_name = "SKU")]
pub sku: Option<String>,
/// Read the SoM SKU from this board.yaml (som.sku) to mark which entries run here.
#[arg(long, value_name = "PATH")]
pub board: Option<String>,
/// Path to the metadata/ root (default: the SDK's own `metadata/`).
#[arg(long = "metadata-root")]
pub metadata_root: Option<String>,
}

/// Args for `model add`.
#[derive(Debug, Args)]
pub struct ModelAddArgs {
/// Zoo entry id to add.
#[arg(value_name = "ZOO_ID")]
pub zoo_id: String,
/// board.yaml to append to (default: `board.yaml`).
#[arg(long, value_name = "PATH")]
pub board: Option<String>,
/// models: entry name (default: the zoo id).
#[arg(long, value_name = "NAME")]
pub name: Option<String>,
/// Directory (relative to board.yaml) to cache the fetched model.
#[arg(long = "models-dir", value_name = "DIR")]
pub models_dir: Option<String>,
/// Path to the metadata/ root (default: the SDK's own `metadata/`).
#[arg(long = "metadata-root")]
pub metadata_root: Option<String>,
}

/// Args for `model prep`.
#[derive(Debug, Args)]
pub struct ModelPrepArgs {
/// Raw model to quantize (`.onnx`).
#[arg(value_name = "RAW")]
pub raw: String,
/// Directory of .npy calibration samples matching the model input.
#[arg(long, value_name = "DIR")]
pub calibration: String,
/// Output INT8 .onnx (default: `<raw>.int8.onnx`).
#[arg(long, value_name = "FILE")]
pub out: Option<String>,
/// Per-channel weight quantization (often recovers accuracy).
#[arg(long = "per-channel")]
pub per_channel: bool,
/// Minimum calibration samples.
#[arg(long = "min-samples", value_name = "N")]
pub min_samples: Option<u32>,
}

/// Args for `model run` (host reference run).
#[derive(Debug, Args)]
pub struct ModelRunArgs {
/// Model to run (`.onnx`).
#[arg(value_name = "MODEL")]
pub model: String,
/// Input sample .npy (default: deterministic random matching the model input).
#[arg(long, value_name = "FILE")]
pub input: Option<String>,
/// Expected class label (top-1 accuracy check).
#[arg(long, value_name = "LABEL")]
pub expected: Option<i64>,
/// Timed inference count (median latency).
#[arg(long, value_name = "N")]
pub runs: Option<u32>,
}

/// Args for `model ab` (A/B two models on the same input).
#[derive(Debug, Args)]
pub struct ModelAbArgs {
/// First model (`.onnx`).
#[arg(value_name = "MODEL_A")]
pub model_a: String,
/// Second model (`.onnx`).
#[arg(value_name = "MODEL_B")]
pub model_b: String,
/// Shared input sample .npy (default: deterministic random matching model_a).
#[arg(long, value_name = "FILE")]
pub input: Option<String>,
/// Timed inference count.
#[arg(long, value_name = "N")]
pub runs: Option<u32>,
}

/// Args for `run`: build the project, then run it. Thin orchestrator over the
/// native `build` + `flash` commands — it reuses their engines, never
/// re-derives them. The host-vs-hardware target is read from what the build
Expand Down
3 changes: 3 additions & 0 deletions crates/tan-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub mod inspect;
/// `tan kconfig` — board-scoped Kconfig symbol menu for one core (the vscode
/// `prj.conf` LSP's live feed); wraps the SDK's `--emit kconfig`.
pub mod kconfig;
/// `tan model` — captures + wraps `alp_cli model <build|list|info|doctor>
/// --format json` in tan's envelope (`model run` still streams via `sdk_cli`).
pub mod model;
/// `tan pinmux` — the E1M pinmux capability table (E1M pad → silicon function) for a SoM family.
pub mod pinmux;
/// `tan presets` — list SDK presets (SKUs/SoMs) + built-in catalogue defaults.
Expand Down
Loading
Loading