Chipwright
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
pip install -e .
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 whisper-small-en
model : whisper-small-en
target : htpv68,qnn2.38
▲ USE (warn) whisper-small-en-1.2.0 htpv68/w8a16/win30s
warning: artifact tested at qnn2.37.1; board runs 2.38.0 — inside compatible range >=2.34,<2.40
Then run it on the board and verify the NPU output against a CPU reference:
chipwright run zipformer-enin --input utterance.npy
model : zipformer-enin
target : htpv68,qnn2.38
✔ USE zipformer-enin-7.0.0 htpv68/w8a16/win39 qnn2.37.1 (exact match)
✔ VERIFIED NPU vs CPU-reference cosine_min = 0.98800 (threshold 0.95)
encoder: 0.98800
That 0.98800 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 you gave it, this run.
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. |
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
chipwrightCLI (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-coreholds 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-eninASR (streaming RNN-T) w8a16 0.988 mobilenet-v2image classification w8a16 0.956 quicksrnet-smallsuper-resolution w8a8 0.9999 fcn-resnet50semantic segmentation w8a8 0.998 depth-anything-v2depth estimation (DINOv2 ViT) w8a16 0.99996 movenetpose 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 allowzeroattribute, patched across 29 nodes) — the honest boundary, handled rather than papered over. -
✅ build — verified end-to-end:
chipwright build mobilenet-cvon a real x86_64-linux QAIRT host produced a w8a16 DLC (…+qnn2.34-htpv68-w8a16-img224.dlc) viaqairt-converter, andchipwright runthen verified that built artifact on the board — cosine 0.956. The fullresolve → build → run → verifyloop. Op pre-check, host preflight, declared graph adaptations, and a fidelity gate before publish are all in; on a machine without QAIRT the CLI says so plainly instead of pretending.
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
MIT — see LICENSE. Contributions welcome; start with CONTRIBUTING.md.
Metadata
Release files for chipwright 0.2.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| chipwright-0.2.1.tar.gz | 94.0 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| chipwright-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 201.8 kB
Release files / chipwright-0.2.1.tar.gz
| Download URL | chipwright-0.2.1.tar.gz |
|---|---|
| Size | 94.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e8e2a7c2114a1aeee529a6e85ece0aa6774d2513275eedae808f497ca7db8715
|
|
BLAKE2b-256 checksum How to use checksums |
d75300cba9eb821595522ed46e594c21abfd7463a14e1133eb52193692d2aba8
|
| 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.1-py3-none-any.whl
| Download URL | chipwright-0.2.1-py3-none-any.whl |
|---|---|
| Size | 107.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7eaaaaf2b37d11633c7325df1235c11993fdfaa7acc9ec9b11c19a219f1de961
|
|
BLAKE2b-256 checksum How to use checksums |
0511ec02043270f78b05523256065662578b0b6dbf9b62e682a737ed3c86aadd
|
| 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.1
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.