chipwright 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {chipwright-0.2.0 → chipwright-0.2.2}/PKG-INFO +49 -16
- {chipwright-0.2.0 → chipwright-0.2.2}/README.md +48 -15
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/__init__.py +1 -1
- chipwright-0.2.2/chipwright/cache.py +111 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/cli.py +85 -15
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/data/index.yaml +50 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/device.py +16 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/registry.py +6 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_cache.py +6 -6
- chipwright-0.2.2/chipwright/tests/test_cli_ux.py +133 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_llm_modality.py +4 -0
- chipwright-0.2.2/chipwright/tests/test_security.py +135 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright.egg-info/PKG-INFO +49 -16
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright.egg-info/SOURCES.txt +2 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/pyproject.toml +1 -1
- chipwright-0.2.0/chipwright/cache.py +0 -65
- {chipwright-0.2.0 → chipwright-0.2.2}/LICENSE +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/adapt.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/build.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/config.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/data/behavior_prompts.yaml +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/device_adb.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/env.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/errors.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/llm_reference.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/modalities/__init__.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/modalities/asr_encoder.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/modalities/cv.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/modalities/llm.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/recipe.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/resolver.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/run.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/sdk.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tags.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/task_eval.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/conftest.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_adapt.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_build.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_device_adb.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_env.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_env_adb.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_packaging.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_precision_support.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_registry.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_resolver.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_run_llm.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_tags.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_task_eval.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_verify.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/tests/test_verify_llm.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/verify.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright/verify_llm.py +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright.egg-info/dependency_links.txt +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright.egg-info/entry_points.txt +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright.egg-info/requires.txt +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/chipwright.egg-info/top_level.txt +0 -0
- {chipwright-0.2.0 → chipwright-0.2.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chipwright
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A verified package manager for Qualcomm NPUs — resolve the artifact that loads on your exact chip, run it, and prove it against a CPU reference.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/nameissakthi25/chipwright
|
|
@@ -79,10 +79,35 @@ Chipwright closes that gap. Think of it as **PyPI wheel tags for NPUs**: the chi
|
|
|
79
79
|
|
|
80
80
|
## Quickstart
|
|
81
81
|
|
|
82
|
+
### Offline — no board, in under a minute
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install chipwright
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Resolve a hosted model against a hand-named target. No board, no QAIRT SDK on the machine:
|
|
89
|
+
|
|
82
90
|
```bash
|
|
83
|
-
|
|
91
|
+
chipwright --target htpv68 resolve mobilenet-v2
|
|
84
92
|
```
|
|
85
93
|
|
|
94
|
+
```text
|
|
95
|
+
model : mobilenet-v2
|
|
96
|
+
target : htpv68
|
|
97
|
+
▲ USE (warn) mobilenet-v2-1.0.0 htpv68/w8a16/img224
|
|
98
|
+
warning: board QAIRT unknown (offline) — SDK compatibility unverified
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
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.
|
|
102
|
+
|
|
103
|
+
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:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
pip install 'chipwright[cv]' # onnxruntime + pillow for the ONNX reference
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### On a board
|
|
110
|
+
|
|
86
111
|
Point Chipwright at a board (over SSH) and probe it:
|
|
87
112
|
|
|
88
113
|
```bash
|
|
@@ -103,32 +128,38 @@ chipwright doctor
|
|
|
103
128
|
Resolve a model against that exact board — no guessing:
|
|
104
129
|
|
|
105
130
|
```bash
|
|
106
|
-
chipwright resolve
|
|
131
|
+
chipwright resolve mobilenet-v2
|
|
107
132
|
```
|
|
108
133
|
|
|
109
134
|
```text
|
|
110
|
-
model :
|
|
135
|
+
model : mobilenet-v2
|
|
111
136
|
target : htpv68,qnn2.38
|
|
112
|
-
▲ USE (warn)
|
|
113
|
-
warning:
|
|
137
|
+
▲ USE (warn) mobilenet-v2-1.0.0 htpv68/w8a16/img224
|
|
138
|
+
warning: board QAIRT 2.38 is in range >=2.34,<2.40 but the artifact was tested at 2.37.1
|
|
114
139
|
```
|
|
115
140
|
|
|
116
|
-
Then run it on the board and verify the NPU output against a CPU reference:
|
|
141
|
+
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):
|
|
117
142
|
|
|
118
143
|
```bash
|
|
119
|
-
chipwright run
|
|
144
|
+
chipwright run mobilenet-v2
|
|
120
145
|
```
|
|
121
146
|
|
|
122
147
|
```text
|
|
123
|
-
model :
|
|
148
|
+
model : mobilenet-v2
|
|
124
149
|
target : htpv68,qnn2.38
|
|
125
|
-
|
|
150
|
+
▲ USE (warn) mobilenet-v2-1.0.0 htpv68/w8a16/img224
|
|
151
|
+
pushing artifact to board (mobilenet) …
|
|
152
|
+
running on NPU + CPU reference …
|
|
126
153
|
|
|
127
|
-
✔ VERIFIED NPU vs CPU-reference cosine_min = 0.
|
|
128
|
-
encoder: 0.98800
|
|
154
|
+
✔ VERIFIED NPU vs CPU-reference cosine_min = 0.95600 (threshold 0.95)
|
|
129
155
|
```
|
|
130
156
|
|
|
131
|
-
That `0.
|
|
157
|
+
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.
|
|
158
|
+
|
|
159
|
+
### Targets and registries
|
|
160
|
+
|
|
161
|
+
- `--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 …`).
|
|
162
|
+
- `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`.
|
|
132
163
|
|
|
133
164
|
---
|
|
134
165
|
|
|
@@ -138,7 +169,7 @@ That `0.98800` is not a claim in a README. It is the number Chipwright measured
|
|
|
138
169
|
| --- | --- |
|
|
139
170
|
| **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. |
|
|
140
171
|
| **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. |
|
|
141
|
-
| **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. |
|
|
172
|
+
| **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](#status); not yet a working end-to-end verb.)* |
|
|
142
173
|
|
|
143
174
|
---
|
|
144
175
|
|
|
@@ -241,7 +272,9 @@ registry/
|
|
|
241
272
|
| `movenet` | pose estimation (17 keypoints) | w8a16 | **0.916** |
|
|
242
273
|
|
|
243
274
|
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.
|
|
244
|
-
|
|
275
|
+
|
|
276
|
+
**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.
|
|
277
|
+
- ⚠️ **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**.
|
|
245
278
|
|
|
246
279
|
**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.
|
|
247
280
|
|
|
@@ -265,4 +298,4 @@ The specification lives in a separate repo, the **spec kit** (`chipwright-specki
|
|
|
265
298
|
|
|
266
299
|
## License
|
|
267
300
|
|
|
268
|
-
MIT — see [LICENSE](LICENSE). Contributions welcome; start with [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
301
|
+
The Chipwright **framework** is MIT — see [LICENSE](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](CONTRIBUTING.md).
|
|
@@ -43,10 +43,35 @@ Chipwright closes that gap. Think of it as **PyPI wheel tags for NPUs**: the chi
|
|
|
43
43
|
|
|
44
44
|
## Quickstart
|
|
45
45
|
|
|
46
|
+
### Offline — no board, in under a minute
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install chipwright
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Resolve a hosted model against a hand-named target. No board, no QAIRT SDK on the machine:
|
|
53
|
+
|
|
46
54
|
```bash
|
|
47
|
-
|
|
55
|
+
chipwright --target htpv68 resolve mobilenet-v2
|
|
48
56
|
```
|
|
49
57
|
|
|
58
|
+
```text
|
|
59
|
+
model : mobilenet-v2
|
|
60
|
+
target : htpv68
|
|
61
|
+
▲ USE (warn) mobilenet-v2-1.0.0 htpv68/w8a16/img224
|
|
62
|
+
warning: board QAIRT unknown (offline) — SDK compatibility unverified
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
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.
|
|
66
|
+
|
|
67
|
+
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:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install 'chipwright[cv]' # onnxruntime + pillow for the ONNX reference
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### On a board
|
|
74
|
+
|
|
50
75
|
Point Chipwright at a board (over SSH) and probe it:
|
|
51
76
|
|
|
52
77
|
```bash
|
|
@@ -67,32 +92,38 @@ chipwright doctor
|
|
|
67
92
|
Resolve a model against that exact board — no guessing:
|
|
68
93
|
|
|
69
94
|
```bash
|
|
70
|
-
chipwright resolve
|
|
95
|
+
chipwright resolve mobilenet-v2
|
|
71
96
|
```
|
|
72
97
|
|
|
73
98
|
```text
|
|
74
|
-
model :
|
|
99
|
+
model : mobilenet-v2
|
|
75
100
|
target : htpv68,qnn2.38
|
|
76
|
-
▲ USE (warn)
|
|
77
|
-
warning:
|
|
101
|
+
▲ USE (warn) mobilenet-v2-1.0.0 htpv68/w8a16/img224
|
|
102
|
+
warning: board QAIRT 2.38 is in range >=2.34,<2.40 but the artifact was tested at 2.37.1
|
|
78
103
|
```
|
|
79
104
|
|
|
80
|
-
Then run it on the board and verify the NPU output against a CPU reference:
|
|
105
|
+
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):
|
|
81
106
|
|
|
82
107
|
```bash
|
|
83
|
-
chipwright run
|
|
108
|
+
chipwright run mobilenet-v2
|
|
84
109
|
```
|
|
85
110
|
|
|
86
111
|
```text
|
|
87
|
-
model :
|
|
112
|
+
model : mobilenet-v2
|
|
88
113
|
target : htpv68,qnn2.38
|
|
89
|
-
|
|
114
|
+
▲ USE (warn) mobilenet-v2-1.0.0 htpv68/w8a16/img224
|
|
115
|
+
pushing artifact to board (mobilenet) …
|
|
116
|
+
running on NPU + CPU reference …
|
|
90
117
|
|
|
91
|
-
✔ VERIFIED NPU vs CPU-reference cosine_min = 0.
|
|
92
|
-
encoder: 0.98800
|
|
118
|
+
✔ VERIFIED NPU vs CPU-reference cosine_min = 0.95600 (threshold 0.95)
|
|
93
119
|
```
|
|
94
120
|
|
|
95
|
-
That `0.
|
|
121
|
+
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.
|
|
122
|
+
|
|
123
|
+
### Targets and registries
|
|
124
|
+
|
|
125
|
+
- `--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 …`).
|
|
126
|
+
- `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`.
|
|
96
127
|
|
|
97
128
|
---
|
|
98
129
|
|
|
@@ -102,7 +133,7 @@ That `0.98800` is not a claim in a README. It is the number Chipwright measured
|
|
|
102
133
|
| --- | --- |
|
|
103
134
|
| **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. |
|
|
104
135
|
| **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. |
|
|
105
|
-
| **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. |
|
|
136
|
+
| **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](#status); not yet a working end-to-end verb.)* |
|
|
106
137
|
|
|
107
138
|
---
|
|
108
139
|
|
|
@@ -205,7 +236,9 @@ registry/
|
|
|
205
236
|
| `movenet` | pose estimation (17 keypoints) | w8a16 | **0.916** |
|
|
206
237
|
|
|
207
238
|
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.
|
|
208
|
-
|
|
239
|
+
|
|
240
|
+
**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.
|
|
241
|
+
- ⚠️ **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**.
|
|
209
242
|
|
|
210
243
|
**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.
|
|
211
244
|
|
|
@@ -229,4 +262,4 @@ The specification lives in a separate repo, the **spec kit** (`chipwright-specki
|
|
|
229
262
|
|
|
230
263
|
## License
|
|
231
264
|
|
|
232
|
-
MIT — see [LICENSE](LICENSE). Contributions welcome; start with [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
265
|
+
The Chipwright **framework** is MIT — see [LICENSE](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](CONTRIBUTING.md).
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Content-addressed artifact cache at ~/.cache/chipwright/.
|
|
2
|
+
|
|
3
|
+
Addressed by sha256 so two models that share a build never store it twice (brief §"The manifest and
|
|
4
|
+
the registry"). Verifies the hash on fetch and on read — a corrupted or truncated download is caught
|
|
5
|
+
here, not at load on the board where the error reads like file corruption.
|
|
6
|
+
|
|
7
|
+
Hardening (spec 001, "refuse rather than guess", at the fetch boundary): every sha is validated as
|
|
8
|
+
64-hex before it is trusted (a url with no/!bad sha is refused by name, never fetched); a reference's
|
|
9
|
+
filename is confined to its cache dir (a registry-supplied `name` with a separator or `..` cannot write
|
|
10
|
+
outside the cache); and a remote url must be https unless CW_ALLOW_INSECURE=1 (so an http CW_REGISTRY /
|
|
11
|
+
artifact cannot be silently MITM'd — the sha only protects you over a trusted transport).
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import urllib.request
|
|
19
|
+
from urllib.parse import urlparse
|
|
20
|
+
|
|
21
|
+
CACHE_DIR = os.environ.get("CW_CACHE", os.path.expanduser("~/.cache/chipwright"))
|
|
22
|
+
|
|
23
|
+
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _require_sha(sha256, what: str = "artifact") -> str:
|
|
27
|
+
"""A fetch target MUST carry a 64-hex sha256. Refuse by name rather than fetch unverified bytes."""
|
|
28
|
+
if not isinstance(sha256, str) or not _HEX64.match(sha256):
|
|
29
|
+
raise RuntimeError(
|
|
30
|
+
f"refusing to fetch an unverified {what}: expected a 64-hex sha256, got {sha256!r}")
|
|
31
|
+
return sha256
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _safe_name(name: str) -> str:
|
|
35
|
+
"""A reference filename must be a plain basename inside its cache dir — no separator, no `..`, not
|
|
36
|
+
absolute. A registry-supplied name that escapes the cache is refused (it could otherwise land
|
|
37
|
+
sha-matching bytes at an arbitrary host path during a normal run)."""
|
|
38
|
+
if (not isinstance(name, str) or name in ("", ".", "..")
|
|
39
|
+
or os.path.isabs(name) or os.path.basename(name) != name):
|
|
40
|
+
raise RuntimeError(f"unsafe reference filename {name!r}: must be a plain name with no path separator or '..'")
|
|
41
|
+
return name
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _check_scheme(url: str) -> None:
|
|
45
|
+
"""A remote fetch must be https (the sha pins integrity only over a transport you trust; over http a
|
|
46
|
+
MITM can serve a matching (url, sha) pair). Local paths pass; opt out with CW_ALLOW_INSECURE=1."""
|
|
47
|
+
scheme = urlparse(url).scheme.lower()
|
|
48
|
+
if scheme in ("", "file"):
|
|
49
|
+
return
|
|
50
|
+
if scheme != "https" and os.environ.get("CW_ALLOW_INSECURE") != "1":
|
|
51
|
+
raise RuntimeError(
|
|
52
|
+
f"refusing an insecure {scheme or 'no'}-scheme url ({url}): remote artifacts/references must be "
|
|
53
|
+
f"https (set CW_ALLOW_INSECURE=1 to override)")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _sha256(path: str, buf: int = 1 << 20) -> str:
|
|
57
|
+
h = hashlib.sha256()
|
|
58
|
+
with open(path, "rb") as f:
|
|
59
|
+
for b in iter(lambda: f.read(buf), b""):
|
|
60
|
+
h.update(b)
|
|
61
|
+
return h.hexdigest()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def path_for(sha256: str) -> str:
|
|
65
|
+
return os.path.join(CACHE_DIR, "blobs", sha256[:2], sha256)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def have(sha256: str) -> bool:
|
|
69
|
+
p = path_for(sha256)
|
|
70
|
+
return os.path.exists(p) and _sha256(p) == sha256
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _ensure(url: str, sha256: str, dst: str) -> str:
|
|
74
|
+
"""Download url to dst (verified by sha256), skipping if dst already matches. Returns dst."""
|
|
75
|
+
_require_sha(sha256, "artifact")
|
|
76
|
+
_check_scheme(url)
|
|
77
|
+
if os.path.exists(dst) and _sha256(dst) == sha256:
|
|
78
|
+
return dst
|
|
79
|
+
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
80
|
+
tmp = dst + ".part"
|
|
81
|
+
urllib.request.urlretrieve(url, tmp)
|
|
82
|
+
got = _sha256(tmp)
|
|
83
|
+
if got != sha256:
|
|
84
|
+
os.remove(tmp)
|
|
85
|
+
raise RuntimeError(f"sha256 mismatch for {url}: got {got}, expected {sha256}")
|
|
86
|
+
os.replace(tmp, dst)
|
|
87
|
+
return dst
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def fetch(url: str, sha256: str) -> str:
|
|
91
|
+
"""Download url into the content-addressed cache, verify the hash, return the local path."""
|
|
92
|
+
_require_sha(sha256, "artifact")
|
|
93
|
+
return _ensure(url, sha256, path_for(sha256))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def fetch_named(url: str, sha256: str, name: str, sidecars=None) -> str:
|
|
97
|
+
"""Fetch a reference file (verified) into a per-reference dir, KEEPING its original filename, so an
|
|
98
|
+
ONNX's external-data sidecar (`model.data` beside `model.onnx`) resolves. `sidecars` = an iterable of
|
|
99
|
+
(url, sha256, name) fetched into the same dir. Returns the local path to `name`.
|
|
100
|
+
|
|
101
|
+
The content-addressed `fetch` names a blob by its hash, which breaks a model that loads a neighbour
|
|
102
|
+
by filename — so a run reference (cpu_onnx / input / meta) uses this instead. Every `name` is confined
|
|
103
|
+
to the cache dir (`_safe_name`) so a registry entry cannot write outside it.
|
|
104
|
+
"""
|
|
105
|
+
_require_sha(sha256, "reference")
|
|
106
|
+
d = os.path.join(CACHE_DIR, "refs", sha256[:12])
|
|
107
|
+
main = _ensure(url, sha256, os.path.join(d, _safe_name(name)))
|
|
108
|
+
for s_url, s_sha, s_name in (sidecars or []):
|
|
109
|
+
_require_sha(s_sha, "reference sidecar")
|
|
110
|
+
_ensure(s_url, s_sha, os.path.join(d, _safe_name(s_name)))
|
|
111
|
+
return main
|
|
@@ -11,7 +11,9 @@ plainly rather than guessing.
|
|
|
11
11
|
from __future__ import annotations
|
|
12
12
|
|
|
13
13
|
import argparse
|
|
14
|
+
import importlib.util
|
|
14
15
|
import os
|
|
16
|
+
import re
|
|
15
17
|
import sys
|
|
16
18
|
|
|
17
19
|
from . import cache
|
|
@@ -21,6 +23,55 @@ from .resolver import Outcome, resolve
|
|
|
21
23
|
|
|
22
24
|
GREEN, YELLOW, RED, DIM, RST = "\033[32m", "\033[33m", "\033[31m", "\033[2m", "\033[0m"
|
|
23
25
|
|
|
26
|
+
# A bare Hexagon arch (what `list` and FAIL messages print, e.g. "v68") looks like a target but is
|
|
27
|
+
# missing the mandatory `htp` prefix. Used to turn a malformed --target into a helpful suggestion.
|
|
28
|
+
_ARCH_LIKE = re.compile(r"^v\d+$", re.IGNORECASE)
|
|
29
|
+
|
|
30
|
+
# Modality extras → the key top-level imports that must resolve for that verify path to work. The
|
|
31
|
+
# package list behind each extra is in pyproject.toml ([project.optional-dependencies]); here we name
|
|
32
|
+
# the IMPORT each pip package provides (pillow → PIL). `doctor` reports these so a user learns an extra
|
|
33
|
+
# is missing up front, not when a run fails.
|
|
34
|
+
_MODALITY_EXTRAS = {
|
|
35
|
+
"cv": ["onnxruntime", "PIL"], # image classifiers (onnxruntime + pillow)
|
|
36
|
+
"asr": ["onnxruntime"], # streaming encoder/decoder/joiner
|
|
37
|
+
"tts": ["onnxruntime"], # text-to-speech reference
|
|
38
|
+
"llm": ["onnxruntime", "tokenizers"], # CPU-reference decoder + genie retokenise
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _can_import(module: str) -> bool:
|
|
43
|
+
"""True if `module` is importable — a read-only probe (find_spec, never actually imports)."""
|
|
44
|
+
try:
|
|
45
|
+
return importlib.util.find_spec(module) is not None
|
|
46
|
+
except Exception:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _manual_target(spec: str) -> TargetKey:
|
|
51
|
+
"""Turn --target into a TargetKey, refusing a malformed spec cleanly instead of a traceback.
|
|
52
|
+
|
|
53
|
+
The tool prints bare archs (`v68`) in `list` and in FAIL messages, so a user naturally copies
|
|
54
|
+
`v68` into --target. Rather than crash, we name the axis and — when the token looks like a known
|
|
55
|
+
arch missing its `htp` prefix — suggest the corrected form. Exits 2, matching `refuse rather than
|
|
56
|
+
guess`.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
return TargetKey.manual(spec)
|
|
60
|
+
except ValueError:
|
|
61
|
+
suggestion = None
|
|
62
|
+
for tok in spec.replace(" ", "").split(","):
|
|
63
|
+
if _ARCH_LIKE.match(tok):
|
|
64
|
+
suggestion = "htp" + tok.lower()
|
|
65
|
+
break
|
|
66
|
+
if suggestion:
|
|
67
|
+
msg = (f"target {spec!r} — did you mean {suggestion!r}? "
|
|
68
|
+
f"(an arch must start with 'htp', e.g. htpv68,qnn2.37)")
|
|
69
|
+
else:
|
|
70
|
+
msg = (f"target {spec!r} — expected an arch starting with 'htp', "
|
|
71
|
+
f"e.g. htpv68,qnn2.37")
|
|
72
|
+
print(f"{RED}✗ {msg}{RST}", file=sys.stderr)
|
|
73
|
+
sys.exit(2)
|
|
74
|
+
|
|
24
75
|
|
|
25
76
|
def _board_from(args) -> dict:
|
|
26
77
|
return {
|
|
@@ -40,28 +91,47 @@ def _get_target(args) -> TargetKey:
|
|
|
40
91
|
return t
|
|
41
92
|
print(f"{YELLOW}board {b['host']} not reachable{RST}", file=sys.stderr)
|
|
42
93
|
if args.target:
|
|
43
|
-
return
|
|
94
|
+
return _manual_target(args.target)
|
|
44
95
|
print(f"{RED}no board reachable and no --target given{RST}", file=sys.stderr)
|
|
45
96
|
sys.exit(2)
|
|
46
97
|
|
|
47
98
|
|
|
99
|
+
def _print_extras_report():
|
|
100
|
+
"""Report, per modality extra, whether its verify-path imports resolve. Read-only and safe with
|
|
101
|
+
nothing installed: base `pip install chipwright` ships no onnxruntime/pillow/tokenizers, so a user
|
|
102
|
+
told the product `proves it against a CPU reference` learns here — not mid-run — which extra to add.
|
|
103
|
+
"""
|
|
104
|
+
print(f"\n{DIM}modality extras (the verify / CPU-reference path needs these){RST}")
|
|
105
|
+
for extra in ("cv", "asr", "tts", "llm"):
|
|
106
|
+
mods = _MODALITY_EXTRAS[extra]
|
|
107
|
+
missing = [m for m in mods if not _can_import(m)]
|
|
108
|
+
if missing:
|
|
109
|
+
print(f" {RED}✗{RST} {extra:3s} missing {', '.join(missing)} "
|
|
110
|
+
f"— pip install chipwright[{extra}]")
|
|
111
|
+
else:
|
|
112
|
+
print(f" {GREEN}✓{RST} {extra:3s} installed ({', '.join(mods)})")
|
|
113
|
+
|
|
114
|
+
|
|
48
115
|
def cmd_doctor(args):
|
|
49
116
|
b = _board_from(args)
|
|
117
|
+
rc = 0
|
|
50
118
|
if not (b["host"] and b["pw"]):
|
|
51
119
|
print(f"{YELLOW}no board configured{RST} — set CW_BOARD_HOST / CW_BOARD_PASS (or --host/--pw)")
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
120
|
+
else:
|
|
121
|
+
t = probe(b["host"], b["user"], b["pw"], b["port"])
|
|
122
|
+
if not t.reachable:
|
|
123
|
+
print(f"{RED}● unreachable{RST} {b['user']}@{b['host']}:{b['port']}")
|
|
124
|
+
rc = 1
|
|
125
|
+
else:
|
|
126
|
+
print(f"{GREEN}● board reachable{RST} {b['user']}@{b['host']}:{b['port']}")
|
|
127
|
+
print(f" SoC : {t.soc_name or '?'}" + (f" (soc_id {t.soc_id})" if t.soc_id else ""))
|
|
128
|
+
print(f" Hexagon arch: {t.htp_arch}")
|
|
129
|
+
print(f" QNN libs : {', '.join(t.qnn_libs) or '(none found)'}")
|
|
130
|
+
if t.thermal_c is not None:
|
|
131
|
+
print(f" temperature : {t.thermal_c} °C")
|
|
132
|
+
print(f" target key : {t}")
|
|
133
|
+
_print_extras_report()
|
|
134
|
+
return rc
|
|
65
135
|
|
|
66
136
|
|
|
67
137
|
def _print_decision(model, target, d):
|
|
@@ -144,7 +214,7 @@ def cmd_list(args):
|
|
|
144
214
|
if t.reachable:
|
|
145
215
|
target = t
|
|
146
216
|
if target is None and args.target:
|
|
147
|
-
target =
|
|
217
|
+
target = _manual_target(args.target)
|
|
148
218
|
|
|
149
219
|
head = f"{target}" if target else "no target — pass --target or configure a board to resolve"
|
|
150
220
|
print(f"{DIM}catalogue · {len(reg.models())} models · {head}{RST}")
|
|
@@ -174,11 +174,21 @@ models:
|
|
|
174
174
|
sdk_tested: 2.37.1
|
|
175
175
|
sdk_compatible: '>=2.34,<2.40'
|
|
176
176
|
ext: dlc
|
|
177
|
+
url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/mobilenet-v2/mobilenet_w8a16.dlc
|
|
178
|
+
sha256: 674e275ab45e27d90f0493216717f8fae4de4a864b0ff463573a2bbe7b01cff3
|
|
177
179
|
run:
|
|
178
180
|
modality: cv
|
|
179
181
|
board_name: mobilenet
|
|
180
182
|
meta: ''
|
|
181
183
|
threshold: 0.95
|
|
184
|
+
cpu_onnx: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/mobilenet-v2/mobilenet_v2.onnx
|
|
185
|
+
cpu_onnx_sha256: 8d0b30c659317596eedd46e30b65eac658a293c181bc0d8dff27c5d29ae5f284
|
|
186
|
+
cpu_onnx_sidecars:
|
|
187
|
+
- url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/mobilenet-v2/mobilenet_v2.data
|
|
188
|
+
sha256: da61b048c0d696bb8bc0260e644f71f998c6c5b203746258d1e7fc41b329a36f
|
|
189
|
+
name: mobilenet_v2.data
|
|
190
|
+
input: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/mobilenet-v2/input.npy
|
|
191
|
+
input_sha256: 93f8247982c540dc1bda3942ec3671cefef9a26921221e53aba0da9fba6918f2
|
|
182
192
|
verified:
|
|
183
193
|
'on': QCS6490 (soc_id 498)
|
|
184
194
|
date: 2026-10-09
|
|
@@ -202,11 +212,21 @@ models:
|
|
|
202
212
|
sdk_tested: 2.37.1
|
|
203
213
|
sdk_compatible: '>=2.34,<2.40'
|
|
204
214
|
ext: dlc
|
|
215
|
+
url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/quicksrnet-small/quicksrnet_w8a8.dlc
|
|
216
|
+
sha256: 9ca46d230bd79205f103144df526eeedc47c505b7558d97d157a6ec46f493115
|
|
205
217
|
run:
|
|
206
218
|
modality: cv
|
|
207
219
|
board_name: quicksrnet
|
|
208
220
|
meta: ''
|
|
209
221
|
threshold: 0.9
|
|
222
|
+
cpu_onnx: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/quicksrnet-small/quicksrnetsmall.onnx
|
|
223
|
+
cpu_onnx_sha256: 8ef99aecf6ea37c3da18eef6bcac94327cc3ead7069e55743425eb912dc2d941
|
|
224
|
+
cpu_onnx_sidecars:
|
|
225
|
+
- url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/quicksrnet-small/quicksrnetsmall.data
|
|
226
|
+
sha256: e377c1bfc040561b28b4931e7c4b27e6df39d8848addb25ebdeee3071b45dd64
|
|
227
|
+
name: quicksrnetsmall.data
|
|
228
|
+
input: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/quicksrnet-small/input.npy
|
|
229
|
+
input_sha256: 28eb37c45cfcc4c3b8f22e982af24d4df33f2f06ec0701fc535150973fdc5850
|
|
210
230
|
verified:
|
|
211
231
|
'on': QCS6490 (soc_id 498)
|
|
212
232
|
date: 2026-10-09
|
|
@@ -230,11 +250,21 @@ models:
|
|
|
230
250
|
sdk_tested: 2.37.1
|
|
231
251
|
sdk_compatible: '>=2.34,<2.40'
|
|
232
252
|
ext: dlc
|
|
253
|
+
url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/fcn-resnet50/fcn_w8a8.dlc
|
|
254
|
+
sha256: 98c0a2700f48f800859972d580b931d058d58a4ae699e1b4612c994fc947c65a
|
|
233
255
|
run:
|
|
234
256
|
modality: cv
|
|
235
257
|
board_name: fcn
|
|
236
258
|
meta: ''
|
|
237
259
|
threshold: 0.9
|
|
260
|
+
cpu_onnx: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/fcn-resnet50/fcn_resnet50.onnx
|
|
261
|
+
cpu_onnx_sha256: 05eaf5a4d40915f3681aac3ae7ae0119ba9e8c5e9e753f1f1d9369023762b00f
|
|
262
|
+
cpu_onnx_sidecars:
|
|
263
|
+
- url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/fcn-resnet50/fcn_resnet50.data
|
|
264
|
+
sha256: 95e4a28fc598875a2cd836f96e808441954bea5df942b385e30304564ca8e8e1
|
|
265
|
+
name: fcn_resnet50.data
|
|
266
|
+
input: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/fcn-resnet50/input.npy
|
|
267
|
+
input_sha256: 08f15b114922848ef54c41f6ea240a99ca9a4f5c4708c256065c7f7c437c5b6a
|
|
238
268
|
verified:
|
|
239
269
|
'on': QCS6490 (soc_id 498)
|
|
240
270
|
date: 2026-10-09
|
|
@@ -287,11 +317,21 @@ models:
|
|
|
287
317
|
sdk_tested: 2.37.1
|
|
288
318
|
sdk_compatible: '>=2.34,<2.40'
|
|
289
319
|
ext: dlc
|
|
320
|
+
url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/depth-anything-v2/depth_w8a16.dlc
|
|
321
|
+
sha256: 051e9ff31c35cc093fae4601275337d6d3f6dcc7f9d06d3e219e5042c92936e3
|
|
290
322
|
run:
|
|
291
323
|
modality: cv
|
|
292
324
|
board_name: depth
|
|
293
325
|
meta: ''
|
|
294
326
|
threshold: 0.9
|
|
327
|
+
cpu_onnx: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/depth-anything-v2/depth_anything_v2.onnx
|
|
328
|
+
cpu_onnx_sha256: 70f3afea5df1a1115f066d75ff40f35c95ec710e6609fb06533dc928f6769696
|
|
329
|
+
cpu_onnx_sidecars:
|
|
330
|
+
- url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/depth-anything-v2/depth_anything_v2.data
|
|
331
|
+
sha256: 1cd4366ce17bae5da226e58a3c0618d22ea128c12831cd6538aebe9fc5a3905d
|
|
332
|
+
name: depth_anything_v2.data
|
|
333
|
+
input: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/depth-anything-v2/input.npy
|
|
334
|
+
input_sha256: 8415c599c901a49cb5e1ad12d594b0d3c8e909af27ba0208183f2d161ff79418
|
|
295
335
|
verified:
|
|
296
336
|
"on": QCS6490 (soc_id 498)
|
|
297
337
|
date: 2026-10-09
|
|
@@ -312,11 +352,21 @@ models:
|
|
|
312
352
|
sdk_tested: 2.37.1
|
|
313
353
|
sdk_compatible: '>=2.34,<2.40'
|
|
314
354
|
ext: dlc
|
|
355
|
+
url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/movenet/pose_w8a16.dlc
|
|
356
|
+
sha256: 061187349f6890b9af6445f3bae998570ccfcd6516e93e5c41346b20e62cf33b
|
|
315
357
|
run:
|
|
316
358
|
modality: cv
|
|
317
359
|
board_name: pose
|
|
318
360
|
meta: ''
|
|
319
361
|
threshold: 0.9
|
|
362
|
+
cpu_onnx: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/movenet/movenet.onnx
|
|
363
|
+
cpu_onnx_sha256: 77668290aba4913b0ffa58b6b526df2dddb871d695889428295182ca03ebcb0d
|
|
364
|
+
cpu_onnx_sidecars:
|
|
365
|
+
- url: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/movenet/movenet.data
|
|
366
|
+
sha256: 0032a7960d9730794c02ea8c7e2ab3483db5f8f9474c0e5c3f5bb4cef6384272
|
|
367
|
+
name: movenet.data
|
|
368
|
+
input: https://huggingface.co/nameissakthi/chipwright-cv/resolve/main/movenet/input.npy
|
|
369
|
+
input_sha256: 6fd2bba24a2c5fe0662930ff0cef5472b2fb4bd8a7413853ff40d560bd1689b2
|
|
320
370
|
verified:
|
|
321
371
|
"on": QCS6490 (soc_id 498)
|
|
322
372
|
date: 2026-10-09
|
|
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import glob
|
|
11
11
|
import os
|
|
12
|
+
import re
|
|
12
13
|
import shutil
|
|
13
14
|
import subprocess
|
|
14
15
|
import tempfile
|
|
@@ -16,6 +17,20 @@ import time
|
|
|
16
17
|
|
|
17
18
|
import numpy as np
|
|
18
19
|
|
|
20
|
+
_SAFE_NAME = re.compile(r"^[A-Za-z0-9._-]+$")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _safe_board_name(name):
|
|
24
|
+
"""`board_name` comes from the registry and is interpolated into on-board shell commands (and used
|
|
25
|
+
as a board path), so it MUST be a plain identifier. A name with a shell metacharacter, space, slash
|
|
26
|
+
or `..` is refused — otherwise a crafted registry entry could run arbitrary commands as root on the
|
|
27
|
+
board (`board_name = 'x; curl … | sh'`)."""
|
|
28
|
+
if not isinstance(name, str) or name in (".", "..") or not _SAFE_NAME.match(name):
|
|
29
|
+
raise RuntimeError(
|
|
30
|
+
f"unsafe board_name {name!r}: must match [A-Za-z0-9._-]+ (it is interpolated into on-board "
|
|
31
|
+
f"shell commands — a separator or shell metacharacter is refused)")
|
|
32
|
+
return name
|
|
33
|
+
|
|
19
34
|
|
|
20
35
|
def _require_sshpass():
|
|
21
36
|
"""`sshpass` is a system binary (not a pip package) the SSH transport shells out to.
|
|
@@ -74,6 +89,7 @@ class RemoteQNN:
|
|
|
74
89
|
into a context binary ON the board (2 MB VTCM) — regenerate, don't ship brittle. A prebuilt
|
|
75
90
|
context binary (`.bin`/`.qnnctx`) is pushed as-is.
|
|
76
91
|
"""
|
|
92
|
+
name = _safe_board_name(name) # registry-sourced; confine before it reaches any shell string
|
|
77
93
|
self._retry(lambda: _ssh(self.pw, self.port, self.dest, f"mkdir -p {self.board_dir}", timeout=40))
|
|
78
94
|
if local_path.endswith(".dlc"):
|
|
79
95
|
dlc_board = f"{self.board_dir}/{name}"
|
|
@@ -22,6 +22,12 @@ DEFAULT_INDEX = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data",
|
|
|
22
22
|
|
|
23
23
|
def _load_yaml(src: str) -> dict:
|
|
24
24
|
if src.startswith(("http://", "https://")):
|
|
25
|
+
# The index is the root of trust — it carries every artifact's sha256 — so a plaintext fetch is
|
|
26
|
+
# MITM-able (swap the index, swap the shas). Require https unless explicitly opted out.
|
|
27
|
+
if src.startswith("http://") and os.environ.get("CW_ALLOW_INSECURE") != "1":
|
|
28
|
+
raise RuntimeError(
|
|
29
|
+
f"refusing to load the registry index over insecure http ({src}): it carries the shas "
|
|
30
|
+
f"that pin every artifact (set CW_ALLOW_INSECURE=1 to override)")
|
|
25
31
|
with urllib.request.urlopen(src, timeout=30) as r:
|
|
26
32
|
return yaml.safe_load(r.read())
|
|
27
33
|
with open(src) as f:
|