Skip to main content

Chipwright

python license verified on QCS6490

A verified package manager and model hub for NPUs. Chipwright reads your board, hands you an artifact that will actually load on your exact chip, and proves it against a CPU reference before you trust the output.

resolve · run · build — Qualcomm first, cross-vendor by design.

flowchart LR
    M([model]):::io
    B([your board<br/>arch · SDK · SoC]):::io
    M --> R{{resolve}}:::core
    B --> R
    R -->|exact · in-range| RUN[run on the NPU]:::step
    R -->|no artifact,<br/>recipe exists| BLD[build<br/>convert · quantize<br/>on-board context]:::step
    R -->|nothing fits| FAIL([refuse —<br/>name the axis]):::bad
    BLD --> RUN
    RUN --> V{{verify<br/>NPU vs CPU cosine}}:::core
    V -->|≥ threshold| OK([trusted artifact]):::good
    V -->|below| NO([caught, not shipped]):::bad

    classDef io fill:#1f2430,stroke:#8a919e,color:#e8eaee;
    classDef core fill:#7c3f1d,stroke:#b4632f,color:#fde8d7;
    classDef step fill:#222a36,stroke:#4b5563,color:#e2e8f0;
    classDef good fill:#14321f,stroke:#2d7d54,color:#86efac;
    classDef bad fill:#3b1a16,stroke:#b23a2c,color:#f5a3a3;

The problem

On an NPU, a model isn't a file. It's a context binary pinned to a precise tuple: architecture × SDK × quantization × shape × backend. Load it on the wrong chip, or the wrong SDK, and it fails at load — or worse, it loads and quietly returns a fast wrong answer.

A model hub tells you which model. It can't tell you which chip. The gap between "here is a checkpoint" and "here is something that runs correctly on the board in your hand" is where edge ML projects stall.

Chipwright closes that gap. Think of it as PyPI wheel tags for NPUs: the chip is the platform tag. You don't hand-pick a binary and hope — you ask the resolver, and it either gives you one that matches or tells you, by name, the axis that doesn't.


Quickstart

Offline — no board, in under a minute

pip install chipwright

Resolve a hosted model against a hand-named target. No board, no QAIRT SDK on the machine:

chipwright --target htpv68 resolve mobilenet-v2
model   : mobilenet-v2
target  : htpv68
▲ USE (warn)  mobilenet-v2-1.0.0  htpv68/w8a16/img224
  warning: board QAIRT unknown (offline) — SDK compatibility unverified

That offline-SDK warning is expected: with no board to probe, Chipwright confirms the arch/quant/shape match but can't check the exact SDK point, so it says so rather than pretend. Global flags — --target, --registry, --quant, --shape — go before the subcommand.

The run verb and its verification gate need a real board (set CW_BOARD_HOST / CW_BOARD_PASS, below), and the CV verify demo needs the CPU-reference runner:

pip install 'chipwright[cv]'   # onnxruntime + pillow for the ONNX reference

On a board

Point Chipwright at a board (over SSH) and probe it:

export CW_BOARD_HOST=192.168.1.50
export CW_BOARD_PASS=…

chipwright doctor
● board reachable  root@192.168.1.50:22
  SoC         : QCS6490 (soc_id 498)
  Hexagon arch: v68
  QNN libs    : libQnnHtp.so  (QAIRT 2.38.0)
  target key  : htpv68,qnn2.38

Resolve a model against that exact board — no guessing:

chipwright resolve mobilenet-v2
model   : mobilenet-v2
target  : htpv68,qnn2.38
▲ USE (warn)  mobilenet-v2-1.0.0  htpv68/w8a16/img224
  warning: board QAIRT 2.38 is in range >=2.34,<2.40 but the artifact was tested at 2.37.1

Then run it on the board and verify the NPU output against a CPU reference (the input ships with the variant, so no --input is needed):

chipwright run mobilenet-v2
model   : mobilenet-v2
target  : htpv68,qnn2.38
▲ USE (warn)  mobilenet-v2-1.0.0  htpv68/w8a16/img224
  pushing artifact to board (mobilenet) …
  running on NPU + CPU reference …

✔ VERIFIED  NPU vs CPU-reference  cosine_min = 0.95600  (threshold 0.95)

That 0.95600 is not a claim in a README. It is the number Chipwright measured on a live QCS6490 against an ONNX-Runtime reference, on the input in the variant, this run.

Targets and registries

  • --target <arch[,sdk]> — resolve offline against a hand-named target instead of a probed board, e.g. --target htpv68 or --target htpv68,qnn2.37. The arch is required; the SDK is optional — omit it and the SDK axis is reported unverified rather than guessed. It is a global flag, so it goes before the subcommand (chipwright --target htpv68 resolve …).
  • CW_REGISTRY — point the client at an alternate index (a local path or a URL) instead of the public index bundled in the wheel. Use it to resolve/run against a dev or private registry; the author's development overlay is registry/index.dev.yaml.

The three verbs

Verb What it does
resolve Match a registry variant to your board's target key. If nothing matches, name the axis that failed — never silently downgrade to a near-miss.
run resolve → fetch (or locate) the artifact → push and execute on the board → compare the NPU output to a CPU reference and report the cosine.
build Produce the artifact from a recipe when no hosted variant matches your chip — op pre-check, host preflight, convert → quantize → on-board context, then gate on the verification before publishing. (Specified and partial in 0.2.x — see Status; not yet a working end-to-end verb.)

The four resolve outcomes

The resolver is deliberately honest. Every resolution lands in exactly one of four outcomes:

  • USE — an exact match on arch, quant, and shape, with the board's SDK inside the compatible range. Run it.

  • USE-with-warning — a match, but the board's SDK sits outside the tested point while staying inside the compatible range. You get the artifact and the caveat, both.

  • BUILD — no hosted artifact for your chip, but a recipe exists that can produce one.

  • FAIL — nothing fits, and the message names the axis:

    ✗ FAIL  axis=htp_arch: no variant for v68; registry has [v73]
    

The arch axis is hard — a mismatch fails at load, so the resolver refuses. The SDK axis is a range, not a point — so outside the tested point the resolver warns rather than refuses. That distinction is the whole discipline: never downgrade to a near-miss artifact silently.


Artifact tags

An artifact's identity is its name. Chipwright names the five axes a resolver matches on in the filename and expands the rest in the manifest — exactly how a PyPI wheel names cp312-manylinux_x86_64 and leaves the rest to metadata:

whisper-small-en-1.2.0+qnn2.37-htpv68-w8a16-win30s.qnnctx
└──────┬───────┘ └─┬─┘ └──┬──┘ └──┬──┘ └─┬─┘ └─┬─┘ └─┬──┘
     name       version  sdk    arch   quant  shape  ext

Parsing is strict: a tag that doesn't parse is a bug, not a near-miss to paper over.


Tested is a record, not a badge

Chipwright does not stamp a green checkmark and ask you to believe it. Every registry variant carries a verification record — which board it ran on, what it was compared to, and the numbers — so a reader who distrusts the claim can check the inputs.

The record for zipformer-enin, from a live run:

verified:
  on: "QCS6490 (soc_id 498)"
  date: 2026-10-08
  loads: true
  vs_reference:
    backend: onnxruntime-cpu
    metric: encoder_cosine
    value: 0.988           # threshold 0.95 — VERIFIED

A high cosine is necessary, not sufficient — a cosine of 0.99 can still cost task accuracy — so the record keeps the numerical check separate from the task metric. The point is not the badge. The point is that the evidence travels with the artifact.


Architecture

One front door today, one core, three targets.

  • The front door — the chipwright CLI (doctor · resolve · run · build), a thin surface over the core. The core is deliberately UI-agnostic so other surfaces (a desktop app, editor integrations) can call the same logic later without reimplementing it; today the CLI is what ships.
  • One core — chipwright-core holds all the logic: tags, board probe, resolver, registry client, cache, device transport, verification, and the run loop.
  • Three targets — the core reaches the registry (the index of verified variants), the board (probe and execute), and the build-host (produce an artifact from a recipe).

See docs/architecture.md for the diagram and the module map.


Repo layout

chipwright/
  cli.py          the `chipwright` CLI — doctor · resolve · run · build
  tags.py         wheel-tag grammar for NPU artifacts (parse / format)
  env.py          board probe → TargetKey (arch × SDK × SoC)
  resolver.py     match variants to a target; the four outcomes
  registry.py     read the index (a git-repo file), list a model's variants
  cache.py        content-addressed artifact cache (~/.cache/chipwright)
  device.py       remote QNN runner — push + execute over SSH
  verify.py       NPU-vs-CPU cosine; the check that catches a fast wrong answer
  run.py          the run verb — ties resolve · device · modality · verify
  modalities/     per-task glue (frontend, decoders, CPU reference)
registry/
  index.yaml      the registry index — one reviewable file, variants by hash

Status

  • ✅ resolve — done, with the four honest outcomes, offline targets, and axis-named failures.

  • ✅ run — done and verified on hardware, six real models pulled from Hugging Face, on a live QCS6490 / RB3 Gen2:

    model task quant cosine
    zipformer-enin ASR (streaming RNN-T) w8a16 0.988
    mobilenet-v2 image classification w8a16 0.956
    quicksrnet-small super-resolution w8a8 0.9999
    fcn-resnet50 semantic segmentation w8a8 0.998
    depth-anything-v2 depth estimation (DINOv2 ViT) w8a16 0.99996
    movenet pose estimation (17 keypoints) w8a16 0.916

    Two input modalities, five CV tasks, two quant profiles — the quant axis is real, and every run ends in a measured number. The gate earns its keep: a random-noise input to MobileNet correctly FAILs at 0.776, and a MediaPipe face detector surfaced a genuine w8a8 fidelity loss on its secondary score head (0.80 vs 0.98 elsewhere) — caught, not shipped. The cosine spread is itself the signal: a ViT depth head converts near-perfectly (0.99996), while a pose keypoint+confidence head shows real quant softness (0.916) yet still clears the bar — the number tells you which is which. Getting that Depth-Anything ViT to convert at all needed a recipe adaptation (an unsupported Reshape allowzero attribute, patched across 29 nodes) — the honest boundary, handled rather than papered over.

    What runs on a clean pip install: mobilenet-v2 is hosted in the bundled public registry — artifact, CPU reference and input all fetched by hash — so chipwright run mobilenet-v2 works end-to-end on any board out of the box. The other five rows ship their verification records in the public index; their artifacts live in the author's development registry (point at it with CW_REGISTRY). The records are what the resolver and list surface; the artifacts travel separately.

  • ⚠️ build — specified and partial, not yet end-to-end in 0.2.x. The build verb resolves a model, reports host preflight, and loads the recipe, but on a machine without the QAIRT toolchain it stops plainly — e.g. chipwright build laya-intent prints ▲ build host not ready — missing: qairt-converter, qairt-quantizer, qnn-context-binary-generator and exits on the preflight axis instead of pretending. The convert → quantize → on-board-context pipeline, op pre-check, declared graph adaptations and the pre-publish fidelity gate are designed and in the recipes, but building a fresh artifact still requires an x86_64-linux QAIRT host and is not wired as a turnkey command yet. mobilenet-cv-built in the registry is the record of one artifact produced this way and then verified on the board (cosine 0.956); reproducing that build from a clean checkout is not a one-liner today. What is solid in 0.2.x is resolve + run + verify.

SDK-range in practice: that same board runs QAIRT 2.38.0 while the artifact was tested at 2.37.1, both inside the compatible range >=2.34,<2.40. It ran correctly — and resolve says so plainly (USE-with-warning), rather than pretending the tested point and the board agree.

Modality roadmap

ASR  →  TTS  →  VLM  →  LLM  →  VLA
 ▲
 verified on hardware today

Vendor-neutrality

Chipwright is Qualcomm first because that is where the hardware and the proof are today. It is cross-vendor by design: the tag grammar, the resolver's axis model, the verification record, and the device transport are not tied to one silicon vendor. Adding a backend means adding a target adapter and its arch axis — not reworking the framework.

The specification lives in a separate repo, the spec kit (chipwright-speckit, in GitHub Spec Kit format). Changes are spec-first: the spec kit moves before the code.


License

The Chipwright framework is MIT — see LICENSE. The models it resolves and runs carry their own upstream licenses, recorded per entry in the registry index (for example the Qualcomm AI Hub CV models are bsd-3-clause, Whisper is mit, and the LLMs are apache-2.0); the MIT license covers this tool, not the model weights. Contributions welcome; start with CONTRIBUTING.md.

Metadata

Release files for chipwright 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for chipwright 0.2.2
File Size Uploaded Provenance
chipwright-0.2.2.tar.gz 103.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for chipwright 0.2.2
File Interpreter ABI Platform
chipwright-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 220.3 kB

Release files / chipwright-0.2.2.tar.gz

Download URL chipwright-0.2.2.tar.gz
Size 103.4 kB
Tags Source
SHA-256 checksum
How to use checksums
ae37799cfa457397b115a262b253196580a5982647bb94487a199f97095e2b9d
BLAKE2b-256 checksum
How to use checksums
95a5520f8e7606685458ed52e5efcb45886002a67d971f32bc215ce959e17b20
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Release files / chipwright-0.2.2-py3-none-any.whl

Download URL chipwright-0.2.2-py3-none-any.whl
Size 116.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
42db5fd3d946cf36f3e2fed5700d7bd8e18a8514bb5e79deb6774511c077fbb3
BLAKE2b-256 checksum
How to use checksums
94b1e27517683f335f8b7f224cbc1e343c1703abf101d32836a18ac97afb0fe0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.9

Security for chipwright 0.2.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page