Fast-WAM action-expert QNN HTP context binary + resident runtime + README
Browse files- README.md +176 -0
- ctx/action_step_socid77_archv73.bin +3 -0
- golden/action_step_io.npz +3 -0
- golden/convert_report.json +66 -0
- golden/trace.json +30 -0
- golden/video_prefill_io.npz +3 -0
- runtime/profile_device.sh +40 -0
- runtime/resident_run.py +176 -0
- runtime/resident_worker.py +102 -0
README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: ZibinDong/fastwam_libero_uncond_2cam224
|
| 4 |
+
tags:
|
| 5 |
+
- fast-wam
|
| 6 |
+
- world-model
|
| 7 |
+
- mot
|
| 8 |
+
- vla
|
| 9 |
+
- robotics
|
| 10 |
+
- libero
|
| 11 |
+
- qnn
|
| 12 |
+
- qualcomm
|
| 13 |
+
- htp
|
| 14 |
+
- hexagon
|
| 15 |
+
- npu
|
| 16 |
+
- edge
|
| 17 |
+
pipeline_tag: robotics
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
# Fast-WAM (LIBERO) — QNN / HTP context binary (action expert)
|
| 21 |
+
|
| 22 |
+
Offline-compiled **Qualcomm QNN (HTP) context binary** for the MoT **action expert** of
|
| 23 |
+
[`ZibinDong/fastwam_libero_uncond_2cam224`](https://huggingface.co/ZibinDong/fastwam_libero_uncond_2cam224),
|
| 24 |
+
ready to run **resident on-device** on a **Qualcomm Dragonwing IQ‑9075 (QCS9075, Hexagon v73, soc_id 77)**.
|
| 25 |
+
|
| 26 |
+
This is the **runtime deployment bundle**: the prebuilt `action_step` `.bin` context, the golden
|
| 27 |
+
reference tensors (inputs + parity reference, including the video KV cache), and the resident
|
| 28 |
+
runtime. The conversion pipeline (`PyTorch → ONNX → QNN DLC → HTP context binary`) lives in the
|
| 29 |
+
[`nota-github/xpu-molmoact2-qnn-htp`](https://github.com/nota-github/xpu-molmoact2-qnn-htp) repo
|
| 30 |
+
(`fastwam_qnn/`, branch `support_Fast-WAM`, PR #2).
|
| 31 |
+
|
| 32 |
+
> **What's on the NPU.** Fast-WAM is a Mixture-of-Transformers (MoT) world model: a frozen Wan2.2
|
| 33 |
+
> video VAE + UMT5 text encoder feed a **video DiT** (world model) and an **action DiT** (action
|
| 34 |
+
> expert). This bundle ships the **action expert** denoise step — after a **real-valued-RoPE +
|
| 35 |
+
> constant-mask surgery** it converts to HTP with **cosine 1.000000** CPU-backend parity. The
|
| 36 |
+
> **video world-model prefill** (which produces the video KV cache the action expert cross-attends
|
| 37 |
+
> to) converts to a QNN DLC with cosine 1.0 too, but its fp16 context is ~9.7 GB (> the device's
|
| 38 |
+
> ~2 GB/session limit) so it needs a MolmoAct2-style layer-split before it can go on-NPU; for now
|
| 39 |
+
> it runs **host-side** and the runtime feeds its output (the golden video KV cache). The frozen
|
| 40 |
+
> Wan2.2 VAE stays host-side (Conv3d 5D exceeds QNN's rank-5 limit). See [Remaining](#remaining).
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
|
| 44 |
+
## What's in here
|
| 45 |
+
|
| 46 |
+
```
|
| 47 |
+
ctx/ 1 HTP context binary (soc_id 77 / Hexagon v73), fp16 weights
|
| 48 |
+
action_step_socid77_archv73.bin 2.06 GB
|
| 49 |
+
golden/ reference I/O (PyTorch fp32): action-step inputs (incl. the video KV cache) + parity ref
|
| 50 |
+
action_step_io.npz video_prefill_io.npz trace.json convert_report.json
|
| 51 |
+
runtime/ resident runtime
|
| 52 |
+
resident_run.py orchestrator (host glue: flow-match Euler loop, parity, latency)
|
| 53 |
+
resident_worker.py one NPU session per process (loads the action_step context once)
|
| 54 |
+
profile_device.sh one-command host→device profiler (SSH; reads creds from IQ9_info.txt)
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
You also need, **from the QAIRT 2.47.x SDK** (not redistributed here):
|
| 58 |
+
`qnn_libs/` (aarch64 QNN runtime `.so`s) and `dsp_libs/` (Hexagon **v73** skel libs).
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## Architecture — 1 on-NPU component
|
| 63 |
+
|
| 64 |
+
```
|
| 65 |
+
image ─┐ Wan2.2 VAE ┌───────────────┐ video KV cache ┌────────────────────┐
|
| 66 |
+
├───(host)────────▶│ video DiT │ ────────────────▶ │ action DiT (NPU) │─▶ actions
|
| 67 |
+
text ───┘ UMT5 (host) │ world model │ [30,1,98,3072] │ flow-matching │ [1,32,7]
|
| 68 |
+
│ prefill (host)│ (×2 k/v) │ step ×N (resident)│
|
| 69 |
+
└───────────────┘ └────────────────────┘
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
- The **action expert** (`action_step`) is a single HTP context, **looped N× on the host** for
|
| 73 |
+
flow-matching denoising. Only `latents_action` and `timestep` change across the loop; the video
|
| 74 |
+
KV cache + context are constant per chunk.
|
| 75 |
+
- The **video world-model prefill** and the **VAE** run host-side for now (see [Remaining](#remaining));
|
| 76 |
+
the runtime feeds their outputs from `golden/`.
|
| 77 |
+
|
| 78 |
+
### I/O contract
|
| 79 |
+
|
| 80 |
+
| context | inputs (name: shape, f32) | output (name: shape, f32) |
|
| 81 |
+
|---|---|---|
|
| 82 |
+
| `action_step` | `latents_action: [1,32,7]`, `timestep: [1]`, `video_keys: [30,1,98,3072]`, `video_values: [30,1,98,3072]`, `context: [1,129,4096]`, `context_mask: [1,129]` | `pred_noise: [1,32,7]` |
|
| 83 |
+
|
| 84 |
+
Host loop (Wan continuous flow-match): schedule `sigma = linspace(1,0,N+1)[:N]; sigma = shift·sigma/(1+(shift−1)·sigma)`,
|
| 85 |
+
`timesteps = sigma·1000`, `deltas = sigma_next − sigma`; for step `i`, feed `latents_action` + `timesteps[i]`,
|
| 86 |
+
take `pred_noise`, update `latents_action += pred_noise · deltas[i]`. (shift = 5.0.)
|
| 87 |
+
|
| 88 |
+
---
|
| 89 |
+
|
| 90 |
+
## Inference guide
|
| 91 |
+
|
| 92 |
+
### 1. Device workspace
|
| 93 |
+
|
| 94 |
+
On the IQ‑9075, create `/root/fastwam_workspace/` with:
|
| 95 |
+
|
| 96 |
+
```
|
| 97 |
+
/root/fastwam_workspace/
|
| 98 |
+
ctx/ ← action_step_socid77_archv73.bin from this repo
|
| 99 |
+
golden/ ← the golden/ folder from this repo
|
| 100 |
+
runtime/ ← resident_run.py, resident_worker.py (profile_device.sh pushes these for you)
|
| 101 |
+
qnn_libs/ ← aarch64 QNN runtime .so from QAIRT 2.47.x: lib/aarch64-oe-linux-gcc11.2/*.so
|
| 102 |
+
dsp_libs/ ← Hexagon v73 skel from QAIRT 2.47.x: lib/hexagon-v73/unsigned/*.so
|
| 103 |
+
tmp/ ← created automatically
|
| 104 |
+
```
|
| 105 |
+
|
| 106 |
+
The runtime loads them via `LD_LIBRARY_PATH=$WS/qnn_libs` and `ADSP_LIBRARY_PATH=$WS/dsp_libs`.
|
| 107 |
+
Device Python needs **`qai_appbuilder`** (QAI AppBuilder for QNN) + `numpy`.
|
| 108 |
+
|
| 109 |
+
> **action_step is 2.06 GB** — right at the device's ~2 GB single-session fastrpc weights-buffer
|
| 110 |
+
> limit. **Reboot before a clean resident run** (crashed cycles leak DSP sessions); the worker ends
|
| 111 |
+
> with `os._exit()` to dodge an appbuilder↔libs teardown double-free.
|
| 112 |
+
|
| 113 |
+
### 2a. Run directly on the device
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
cd /root/fastwam_workspace
|
| 117 |
+
python3 runtime/resident_run.py # FASTWAM_STEPS=10 (default), FASTWAM_SHIFT=5.0
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
It spawns the worker, keeps it **resident**, runs the N-step flow-match loop, prints the
|
| 121 |
+
**single-step pred cosine** vs the golden reference, then a latency summary (avg of 5) plus the
|
| 122 |
+
pure per-step `[infer-ms]` (excl. TCP) in `tmp/w_action_step.log`.
|
| 123 |
+
|
| 124 |
+
### 2b. Or profile from the host (one command)
|
| 125 |
+
|
| 126 |
+
```bash
|
| 127 |
+
runtime/profile_device.sh
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
Copies the runtime scripts to an ephemeral `/tmp` workspace, symlinks the heavy assets, runs, prints
|
| 131 |
+
the latency table + per-step `[infer-ms]`, then deletes the temp dir. Device creds are read from
|
| 132 |
+
`IQ9_info.txt` (`IP:` / `passwd:`; override with `IQ9_INFO=...`).
|
| 133 |
+
|
| 134 |
+
### fp16 / quantized A/B switch
|
| 135 |
+
|
| 136 |
+
```bash
|
| 137 |
+
ACTION_CTX_SUFFIX=_w4a16 python3 runtime/resident_run.py # if you add a quantized bin
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
---
|
| 141 |
+
|
| 142 |
+
## Validated parity (host, QNN CPU backend vs PyTorch fp32)
|
| 143 |
+
|
| 144 |
+
Verified device-free on the QNN **CPU** backend against the PyTorch golden. **Cosine is the gate**
|
| 145 |
+
(≥ 0.9999).
|
| 146 |
+
|
| 147 |
+
| stage | output | cosine |
|
| 148 |
+
|---|---|---|
|
| 149 |
+
| ONNX (ORT CPU) — reference | — | action expert exports clean after real-RoPE surgery |
|
| 150 |
+
| QNN DLC (CPU backend) vs PyTorch | pred_noise | **1.000000** |
|
| 151 |
+
| video_prefill DLC (CPU backend) vs PyTorch | video_keys / video_values | 1.000000 / 1.000000 |
|
| 152 |
+
|
| 153 |
+
The on-device (HTP fp16) single-step pred cosine + latency are what `resident_run.py` reports.
|
| 154 |
+
|
| 155 |
+
---
|
| 156 |
+
|
| 157 |
+
## Remaining
|
| 158 |
+
|
| 159 |
+
- **video world-model prefill — layer-split.** The 30-layer video DiT converts to a QNN DLC with
|
| 160 |
+
cosine 1.0, but its fp16 context (~9.7 GB) exceeds the device's ~2 GB/session limit. Split it into
|
| 161 |
+
layer-range contexts (MolmoAct2-LLM pattern) to run on-NPU; until then the runtime uses the
|
| 162 |
+
host-computed video KV cache from `golden/`.
|
| 163 |
+
- **Wan2.2 VAE — host-side.** The frozen `AutoencoderKLWan` is Conv3d (5D); an internal transpose
|
| 164 |
+
exceeds QNN HTP's rank-5 limit. Keep it host-side (feed latents), or refactor the T=1 path to rank-4.
|
| 165 |
+
|
| 166 |
+
---
|
| 167 |
+
|
| 168 |
+
## Build provenance
|
| 169 |
+
|
| 170 |
+
- Source policy: [`ZibinDong/fastwam_libero_uncond_2cam224`](https://huggingface.co/ZibinDong/fastwam_libero_uncond_2cam224).
|
| 171 |
+
- Action expert: MoT action DiT (30 layers, hidden 1024, 24 heads × 128), flow-matching, action chunk `[1,32,7]`.
|
| 172 |
+
- Surgery for ONNX/HTP: complex `view_as_complex`/`view_as_real` RoPE → real rotate-half (`.freqs`
|
| 173 |
+
stored as real cos/sin); MoT boolean attention masks precomputed as constant buffers.
|
| 174 |
+
- Toolchain: QAIRT **2.47.0**, opset 20, float DLC (no quant), offline HTP context-binary for
|
| 175 |
+
soc_id 77 / dsp_arch v73, `O3`.
|
| 176 |
+
- Conversion code: `nota-github/xpu-molmoact2-qnn-htp` → `fastwam_qnn/`, `scripts_fastwam/`, `fastwam_qnn/surgery.py` (branch `support_Fast-WAM`, PR #2).
|
ctx/action_step_socid77_archv73.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:97ca5d72e1c5b79af2bfef17c006b091ecbb9385a47c9e3f55f8e6fd82e1ee51
|
| 3 |
+
size 2064179200
|
golden/action_step_io.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:894e8dd3bf10ac8cafaf74deda1acdc4b3cbba916c960398f4ef58c7e3c0ac83
|
| 3 |
+
size 74370681
|
golden/convert_report.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"vae_encoder": {
|
| 3 |
+
"status": "BLOCKED",
|
| 4 |
+
"in": [
|
| 5 |
+
"pixels"
|
| 6 |
+
],
|
| 7 |
+
"out": [
|
| 8 |
+
"latent"
|
| 9 |
+
],
|
| 10 |
+
"error": "CalledProcessError: Command '['/ssd6/geonmin.kim/qairt/2.47.0.260601/bin/x86_64-linux-clang/qairt-converter', '--input_network', '/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/outputs_fastwam/onnx/vae_encoder/vae_encoder.onnx', '-o', '/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/outputs_fastwam/qnn/vae_encoder/vae_encoder.dlc', '--enable_tensor_deduplication']' returned non-zero exit status 1.",
|
| 11 |
+
"trace": "Traceback (most recent call last):\n File \"/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/scripts_fastwam/02_convert_qnn.py\", line 54, in convert_and_verify\n qnn.to_dlc(s, onnx_path, dlc)\n File \"/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/fastwam_qnn/qnn.py\", line 45, in to_dlc\n _run(cmd)\n File \"/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/fastwam_qnn/qnn.py\", line 28, in _run\n subprocess.run(cmd, check=True)\n File \"/usr/lib/python3.12/subprocess.py\", line 571, in run\n raise CalledProcessError(retcode, process.args,\nsubprocess.CalledProcessError: Command '['/ssd6/geonmin.kim/qairt/2.47.0.260601/bin/x86_64-linux-clang/qairt-converter', '--input_network', '/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/outputs_fastwam/onnx/vae_encoder/vae_encoder.onnx', '-o', '/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/outputs_fastwam/qnn/vae_encoder/vae_encoder.dlc', '--enable_tensor_deduplication']' returned non-zero exit status 1.\n"
|
| 12 |
+
},
|
| 13 |
+
"video_prefill": {
|
| 14 |
+
"status": "OK",
|
| 15 |
+
"in": [
|
| 16 |
+
"first_frame_latent",
|
| 17 |
+
"context",
|
| 18 |
+
"context_mask"
|
| 19 |
+
],
|
| 20 |
+
"out": [
|
| 21 |
+
"video_keys",
|
| 22 |
+
"video_values"
|
| 23 |
+
],
|
| 24 |
+
"dlc": "/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/outputs_fastwam/qnn/video_prefill/video_prefill.dlc",
|
| 25 |
+
"parity": {
|
| 26 |
+
"video_keys": {
|
| 27 |
+
"cosine": 0.9999999982130974,
|
| 28 |
+
"max_abs": 0.0009984970092773438,
|
| 29 |
+
"max_rel": 407.65616383779053,
|
| 30 |
+
"within_tol": 1.0
|
| 31 |
+
},
|
| 32 |
+
"video_values": {
|
| 33 |
+
"cosine": 0.9999999892041477,
|
| 34 |
+
"max_abs": 0.001104697585105896,
|
| 35 |
+
"max_rel": 66029.9847368151,
|
| 36 |
+
"within_tol": 0.9999998892786282
|
| 37 |
+
}
|
| 38 |
+
},
|
| 39 |
+
"cpu_verify": "OK"
|
| 40 |
+
},
|
| 41 |
+
"action_step": {
|
| 42 |
+
"status": "OK",
|
| 43 |
+
"in": [
|
| 44 |
+
"latents_action",
|
| 45 |
+
"timestep",
|
| 46 |
+
"video_keys",
|
| 47 |
+
"video_values",
|
| 48 |
+
"context",
|
| 49 |
+
"context_mask"
|
| 50 |
+
],
|
| 51 |
+
"out": [
|
| 52 |
+
"pred_noise"
|
| 53 |
+
],
|
| 54 |
+
"dlc": "/ssd6/geonmin.kim/xpu-molmoact2-qnn-htp/outputs_fastwam/qnn/action_step/action_step.dlc",
|
| 55 |
+
"parity": {
|
| 56 |
+
"pred_noise": {
|
| 57 |
+
"cosine": 0.9999999855798641,
|
| 58 |
+
"max_abs": 0.0006463229656219482,
|
| 59 |
+
"max_rel": 0.007894328519392351,
|
| 60 |
+
"within_tol": 1.0
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"cpu_verify": "OK",
|
| 64 |
+
"context_binary_mb": 2064.2
|
| 65 |
+
}
|
| 66 |
+
}
|
golden/trace.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"vae": {
|
| 3 |
+
"out": [
|
| 4 |
+
[
|
| 5 |
+
1,
|
| 6 |
+
48,
|
| 7 |
+
1,
|
| 8 |
+
14,
|
| 9 |
+
28
|
| 10 |
+
],
|
| 11 |
+
"torch.float32"
|
| 12 |
+
]
|
| 13 |
+
},
|
| 14 |
+
"video_pre": {
|
| 15 |
+
"out": "<class 'dict'>"
|
| 16 |
+
},
|
| 17 |
+
"video_prefill": {
|
| 18 |
+
"out": "<class 'list'>"
|
| 19 |
+
},
|
| 20 |
+
"action_step": {
|
| 21 |
+
"out": [
|
| 22 |
+
[
|
| 23 |
+
1,
|
| 24 |
+
32,
|
| 25 |
+
7
|
| 26 |
+
],
|
| 27 |
+
"torch.float32"
|
| 28 |
+
]
|
| 29 |
+
}
|
| 30 |
+
}
|
golden/video_prefill_io.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a5e8267ac75d67687de913105df5e3c259c262b8811499950c3f6ae340aa0f97
|
| 3 |
+
size 74443669
|
runtime/profile_device.sh
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Profile the resident Fast-WAM runtime on the IQ-9075 — from the host, one command.
|
| 3 |
+
#
|
| 4 |
+
# Copies the LATEST runtime/*.py from this repo to an EPHEMERAL workspace under the
|
| 5 |
+
# device's /tmp, symlinks the heavy assets (ctx/ golden/ qnn_libs/ dsp_libs/) from the
|
| 6 |
+
# persistent /root/fastwam_workspace (no multi-GB copy), runs resident_run.py, prints the
|
| 7 |
+
# latency table + per-step pure [infer-ms], then deletes the temp dir. Nothing persistent
|
| 8 |
+
# is modified.
|
| 9 |
+
#
|
| 10 |
+
# Usage (fp16 default; A/B a quantized action bin via env):
|
| 11 |
+
# runtime/profile_device.sh
|
| 12 |
+
# runtime/profile_device.sh ACTION_CTX_SUFFIX=_w4a16
|
| 13 |
+
#
|
| 14 |
+
# Device creds are read from IQ9_info.txt (override path with IQ9_INFO=...).
|
| 15 |
+
set -euo pipefail
|
| 16 |
+
|
| 17 |
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
| 18 |
+
INFO="${IQ9_INFO:-$HERE/../../IQ9_info.txt}"
|
| 19 |
+
[ -f "$INFO" ] || { echo "device info not found: $INFO (set IQ9_INFO=...)" >&2; exit 1; }
|
| 20 |
+
IP=$(awk -F'[: ]+' '/^IP/{print $2}' "$INFO")
|
| 21 |
+
PW=$(awk -F'[: ]+' '/passwd/{print $2}' "$INFO")
|
| 22 |
+
PERSIST=/root/fastwam_workspace
|
| 23 |
+
ENVS="$*"
|
| 24 |
+
|
| 25 |
+
SSH="sshpass -p $PW ssh -o StrictHostKeyChecking=no -o ConnectTimeout=15 root@$IP"
|
| 26 |
+
SCP="sshpass -p $PW scp -o StrictHostKeyChecking=no"
|
| 27 |
+
|
| 28 |
+
echo "[profile] device $IP envs: ${ENVS:-<fp16>}"
|
| 29 |
+
|
| 30 |
+
TMP=$($SSH "mktemp -d /tmp/fastwam_run.XXXXXX")
|
| 31 |
+
cleanup() { $SSH "rm -rf $TMP" >/dev/null 2>&1 || true; echo "[profile] cleaned $TMP"; }
|
| 32 |
+
trap cleanup EXIT
|
| 33 |
+
$SSH "mkdir -p $TMP/runtime $TMP/tmp && for d in ctx golden qnn_libs dsp_libs; do ln -sfn $PERSIST/\$d $TMP/\$d; done"
|
| 34 |
+
|
| 35 |
+
$SCP "$HERE"/resident_run.py "$HERE"/resident_worker.py root@"$IP":"$TMP"/runtime/ >/dev/null
|
| 36 |
+
|
| 37 |
+
$SSH "cd $TMP && FASTWAM_WS=$TMP $ENVS python3 -u runtime/resident_run.py"
|
| 38 |
+
|
| 39 |
+
echo "[profile] pure ctx.Inference times ([infer-ms], excl. TCP):"
|
| 40 |
+
$SSH "grep -h infer-ms $TMP/tmp/w_*.log 2>/dev/null | awk '{a[\$2]+=\$3;n[\$2]++} END{for(k in a) printf \" %-12s %.1f ms\n\", k, a[k]/n[k]}' | sort" || true
|
runtime/resident_run.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Launch the resident Fast-WAM runtime and run the action-expert denoise loop on the NPU
|
| 3 |
+
WITHOUT reload, verify parity vs the PyTorch golden, and measure latency.
|
| 4 |
+
|
| 5 |
+
Fast-WAM's on-device unit here is the MoT **action expert** (`action_step`), which cross-
|
| 6 |
+
attends to the video KV cache produced by the video world-model DiT (`video_prefill`).
|
| 7 |
+
The video prefill runs host-side for now (its context binary needs a MolmoAct2-style
|
| 8 |
+
layer-split, see README), so the host:
|
| 9 |
+
1. loads the video KV cache + context (here: from golden/, i.e. the video_prefill output),
|
| 10 |
+
2. runs the flow-matching Euler loop: for each of num_inference_steps, feed the current
|
| 11 |
+
latents_action + timestep -> pred_noise (NPU), then update latents with the Wan
|
| 12 |
+
continuous flow-match step latents += pred * delta,
|
| 13 |
+
3. verifies the single-step output vs golden (parity gate), reports latency.
|
| 14 |
+
|
| 15 |
+
Pipeline (on-device portion): action_step ×N (resident). No per-inference reload.
|
| 16 |
+
"""
|
| 17 |
+
import sys, os, socket, struct, subprocess, time
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
WS = os.environ.get("FASTWAM_WS", "/root/fastwam_workspace")
|
| 21 |
+
RT = f"{WS}/runtime"
|
| 22 |
+
ROLES = ["action_step"]
|
| 23 |
+
PORT0 = 5801
|
| 24 |
+
STEPS = int(os.environ.get("FASTWAM_STEPS", "10")) # num_inference_steps
|
| 25 |
+
SHIFT = float(os.environ.get("FASTWAM_SHIFT", "5.0")) # action_scheduler infer_shift
|
| 26 |
+
NUM_TRAIN = int(os.environ.get("FASTWAM_NUM_TRAIN_TIMESTEPS", "1000"))
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def recvall(s, n):
|
| 30 |
+
b = bytearray()
|
| 31 |
+
while len(b) < n:
|
| 32 |
+
c = s.recv(min(n - len(b), 8 << 20))
|
| 33 |
+
if not c:
|
| 34 |
+
raise ConnectionError()
|
| 35 |
+
b += c
|
| 36 |
+
return bytes(b)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def recv_tensors(s):
|
| 40 |
+
n = struct.unpack("<I", recvall(s, 4))[0]
|
| 41 |
+
out = {}
|
| 42 |
+
for _ in range(n):
|
| 43 |
+
nl = struct.unpack("<I", recvall(s, 4))[0]; name = recvall(s, nl).decode()
|
| 44 |
+
dl = struct.unpack("<I", recvall(s, 4))[0]; dt = recvall(s, dl).decode()
|
| 45 |
+
nd = struct.unpack("<I", recvall(s, 4))[0]; shape = struct.unpack(f"<{nd}I", recvall(s, nd * 4))
|
| 46 |
+
nb = struct.unpack("<Q", recvall(s, 8))[0]; data = recvall(s, nb)
|
| 47 |
+
out[name] = np.frombuffer(data, dtype=dt).reshape(shape).copy()
|
| 48 |
+
return out
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def send_tensors(s, d):
|
| 52 |
+
parts = [struct.pack("<I", len(d))]
|
| 53 |
+
for name, a in d.items():
|
| 54 |
+
a = np.ascontiguousarray(a, np.float32)
|
| 55 |
+
nb = name.encode(); dt = str(a.dtype).encode()
|
| 56 |
+
parts.append(struct.pack("<I", len(nb)) + nb)
|
| 57 |
+
parts.append(struct.pack("<I", len(dt)) + dt)
|
| 58 |
+
parts.append(struct.pack("<I", a.ndim) + struct.pack(f"<{a.ndim}I", *a.shape))
|
| 59 |
+
parts.append(struct.pack("<Q", a.nbytes) + a.tobytes())
|
| 60 |
+
s.sendall(b"".join(parts))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def call(sock, d):
|
| 64 |
+
send_tensors(sock, d)
|
| 65 |
+
return recv_tensors(sock)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def cos(r, g):
|
| 69 |
+
r = np.asarray(r, np.float64).ravel(); g = np.asarray(g, np.float64).ravel()
|
| 70 |
+
return float(r @ g / (np.linalg.norm(r) * np.linalg.norm(g) + 1e-12))
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def build_schedule(steps, shift, num_train):
|
| 74 |
+
"""Wan continuous flow-match inference schedule (vendored from video_dit.py):
|
| 75 |
+
sigma = linspace(1,0,steps+1)[:steps]; sigma = shift*sigma/(1+(shift-1)*sigma).
|
| 76 |
+
timesteps = sigma*num_train; deltas = sigma_next - sigma (sigma_next[-1]=0)."""
|
| 77 |
+
sigma = np.linspace(1, 0, steps + 1)[:steps]
|
| 78 |
+
sigma = shift * sigma / (1 + (shift - 1) * sigma)
|
| 79 |
+
timesteps = (sigma * num_train).astype(np.float32)
|
| 80 |
+
sigma_next = np.concatenate([sigma[1:], np.zeros(1)])
|
| 81 |
+
deltas = (sigma_next - sigma).astype(np.float32)
|
| 82 |
+
return timesteps, deltas
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def main():
|
| 86 |
+
os.makedirs(f"{WS}/tmp", exist_ok=True)
|
| 87 |
+
for r in ROLES:
|
| 88 |
+
try: os.remove(f"{WS}/tmp/w_{r}.ready")
|
| 89 |
+
except OSError: pass
|
| 90 |
+
env = os.environ.copy()
|
| 91 |
+
env["LD_LIBRARY_PATH"] = f"{WS}/qnn_libs:" + env.get("LD_LIBRARY_PATH", "")
|
| 92 |
+
env["ADSP_LIBRARY_PATH"] = f"{WS}/dsp_libs"
|
| 93 |
+
|
| 94 |
+
procs, ports = {}, {}
|
| 95 |
+
t0 = time.time()
|
| 96 |
+
for i, r in enumerate(ROLES):
|
| 97 |
+
ports[r] = PORT0 + i
|
| 98 |
+
logf = open(f"{WS}/tmp/w_{r}.log", "w")
|
| 99 |
+
procs[r] = subprocess.Popen(["python3", "-u", f"{RT}/resident_worker.py", r, str(ports[r])],
|
| 100 |
+
env=env, stdout=logf, stderr=subprocess.STDOUT)
|
| 101 |
+
print(f"[launch] {len(ROLES)} worker spawning ...", flush=True)
|
| 102 |
+
for r in ROLES:
|
| 103 |
+
for _ in range(240):
|
| 104 |
+
if os.path.exists(f"{WS}/tmp/w_{r}.ready"):
|
| 105 |
+
break
|
| 106 |
+
if procs[r].poll() is not None:
|
| 107 |
+
print(f"[ERR] worker {r} died during load (rc={procs[r].returncode}); see tmp/w_{r}.log")
|
| 108 |
+
return 3
|
| 109 |
+
time.sleep(0.5)
|
| 110 |
+
else:
|
| 111 |
+
print(f"[ERR] worker {r} not ready"); return 3
|
| 112 |
+
print(f"[launch] worker RESIDENT in {time.time()-t0:.1f}s (1 NPU session)", flush=True)
|
| 113 |
+
|
| 114 |
+
socks = {}
|
| 115 |
+
for r in ROLES:
|
| 116 |
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 117 |
+
s.connect(("127.0.0.1", ports[r])); s.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
| 118 |
+
socks[r] = s
|
| 119 |
+
|
| 120 |
+
g = np.load(f"{WS}/golden/action_step_io.npz")
|
| 121 |
+
vk = g["video_keys"].astype(np.float32) # [30,1,98,3072] (video world-model KV cache)
|
| 122 |
+
vv = g["video_values"].astype(np.float32)
|
| 123 |
+
ctx = g["context"].astype(np.float32) # [1,129,4096]
|
| 124 |
+
cmask = g["context_mask"].astype(np.float32) # [1,129]
|
| 125 |
+
la0 = g["latents_action"].astype(np.float32) # [1,32,7] initial noise
|
| 126 |
+
ts0 = g["timestep"].astype(np.float32) # [1] step-0 timestep
|
| 127 |
+
pred_gold = g["pred"].astype(np.float32) # [1,32,7] one-step reference
|
| 128 |
+
|
| 129 |
+
const = {"video_keys": vk, "video_values": vv, "context": ctx, "context_mask": cmask}
|
| 130 |
+
|
| 131 |
+
# ---- parity: single step with golden inputs vs golden pred ----
|
| 132 |
+
out1 = call(socks["action_step"], {**const, "latents_action": la0, "timestep": ts0})
|
| 133 |
+
key = "pred_noise" if "pred_noise" in out1 else list(out1)[0]
|
| 134 |
+
c = cos(pred_gold, out1[key])
|
| 135 |
+
print(f"[verify] action_step single-step pred cos={c:.8f} (PASS={c>=0.999})", flush=True)
|
| 136 |
+
|
| 137 |
+
# ---- full N-step flow-match loop (latency) ----
|
| 138 |
+
timesteps, deltas = build_schedule(STEPS, SHIFT, NUM_TRAIN)
|
| 139 |
+
|
| 140 |
+
def run_loop(timing=None):
|
| 141 |
+
la = la0.copy()
|
| 142 |
+
for i in range(STEPS):
|
| 143 |
+
ts = np.array([timesteps[i]], np.float32)
|
| 144 |
+
s = time.time()
|
| 145 |
+
o = call(socks["action_step"], {**const, "latents_action": la, "timestep": ts})
|
| 146 |
+
if timing is not None:
|
| 147 |
+
timing["action_step"] = timing.get("action_step", 0) + time.time() - s
|
| 148 |
+
pred = np.ascontiguousarray(o[key], np.float32).reshape(la.shape)
|
| 149 |
+
la = la + pred * deltas[i] # Wan continuous flow-match step: x += pred*delta
|
| 150 |
+
return la
|
| 151 |
+
|
| 152 |
+
run_loop() # warmup
|
| 153 |
+
N = 5
|
| 154 |
+
timing = {}
|
| 155 |
+
t0 = time.time()
|
| 156 |
+
for _ in range(N):
|
| 157 |
+
run_loop(timing)
|
| 158 |
+
total = (time.time() - t0) / N
|
| 159 |
+
print(f"\n=== RESIDENT LATENCY (avg of {N}, 1 session, NO reload, {STEPS}-step flow-match) ===", flush=True)
|
| 160 |
+
print(f" action_step (x{STEPS}) {timing['action_step']/N*1000:7.1f} ms (incl. video-KV TCP each step)", flush=True)
|
| 161 |
+
print(f" {'per-step (wall)':18} {timing['action_step']/N/STEPS*1000:7.1f} ms", flush=True)
|
| 162 |
+
print(f" {'TOTAL':18} {total*1000:7.1f} ms", flush=True)
|
| 163 |
+
print(" (pure NPU ctx.Inference per step -> tmp/w_action_step.log [infer-ms])", flush=True)
|
| 164 |
+
|
| 165 |
+
for s in socks.values():
|
| 166 |
+
try: s.close()
|
| 167 |
+
except OSError: pass
|
| 168 |
+
time.sleep(1)
|
| 169 |
+
for p in procs.values():
|
| 170 |
+
try: p.terminate()
|
| 171 |
+
except OSError: pass
|
| 172 |
+
return 0
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
if __name__ == "__main__":
|
| 176 |
+
sys.exit(main())
|
runtime/resident_worker.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Persistent resident QNN worker for Fast-WAM (one NPU session per process).
|
| 3 |
+
|
| 4 |
+
Loads the `action_step` HTP context ONCE, stays alive, serves inference over a TCP
|
| 5 |
+
socket (no per-inference reload). Fast-WAM's on-device unit here is the MoT **action
|
| 6 |
+
expert** denoise step — it cross-attends to the video KV cache produced by the video
|
| 7 |
+
world-model DiT (`video_prefill`), which runs host-side for now (see README).
|
| 8 |
+
|
| 9 |
+
role:
|
| 10 |
+
action_step : one flow-matching denoise step of the action expert.
|
| 11 |
+
inputs : latents_action [1,32,7], timestep [1], video_keys [30,1,98,3072],
|
| 12 |
+
video_values [30,1,98,3072], context [1,129,4096], context_mask [1,129]
|
| 13 |
+
output : pred_noise [1,32,7] (host applies the Euler/flow-match update)
|
| 14 |
+
|
| 15 |
+
Usage: resident_worker.py <role> <port>
|
| 16 |
+
"""
|
| 17 |
+
import sys, os, socket, struct, time
|
| 18 |
+
import numpy as np
|
| 19 |
+
|
| 20 |
+
WS = os.environ.get("FASTWAM_WS", "/root/fastwam_workspace")
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def recvall(s, n):
|
| 24 |
+
b = bytearray()
|
| 25 |
+
while len(b) < n:
|
| 26 |
+
c = s.recv(min(n - len(b), 8 << 20))
|
| 27 |
+
if not c:
|
| 28 |
+
raise ConnectionError()
|
| 29 |
+
b += c
|
| 30 |
+
return bytes(b)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def recv_tensors(s):
|
| 34 |
+
n = struct.unpack("<I", recvall(s, 4))[0]
|
| 35 |
+
out = {}
|
| 36 |
+
for _ in range(n):
|
| 37 |
+
nl = struct.unpack("<I", recvall(s, 4))[0]; name = recvall(s, nl).decode()
|
| 38 |
+
dl = struct.unpack("<I", recvall(s, 4))[0]; dt = recvall(s, dl).decode()
|
| 39 |
+
nd = struct.unpack("<I", recvall(s, 4))[0]; shape = struct.unpack(f"<{nd}I", recvall(s, nd * 4))
|
| 40 |
+
nb = struct.unpack("<Q", recvall(s, 8))[0]; data = recvall(s, nb)
|
| 41 |
+
out[name] = np.frombuffer(data, dtype=dt).reshape(shape).copy()
|
| 42 |
+
return out
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def send_tensors(s, d):
|
| 46 |
+
parts = [struct.pack("<I", len(d))]
|
| 47 |
+
for name, a in d.items():
|
| 48 |
+
a = np.ascontiguousarray(a)
|
| 49 |
+
nb = name.encode(); dt = str(a.dtype).encode()
|
| 50 |
+
parts.append(struct.pack("<I", len(nb)) + nb)
|
| 51 |
+
parts.append(struct.pack("<I", len(dt)) + dt)
|
| 52 |
+
parts.append(struct.pack("<I", a.ndim) + struct.pack(f"<{a.ndim}I", *a.shape))
|
| 53 |
+
parts.append(struct.pack("<Q", a.nbytes) + a.tobytes())
|
| 54 |
+
s.sendall(b"".join(parts))
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def feed(ctx, m):
|
| 58 |
+
"""Order inputs to match the context; QNN consumes float32 raws (bool mask too)."""
|
| 59 |
+
out = []
|
| 60 |
+
for nm, shp in zip(ctx.getInputName(), ctx.getInputShapes()):
|
| 61 |
+
a = np.asarray(m[nm], np.float32)
|
| 62 |
+
if tuple(a.shape) != tuple(shp) and a.size == int(np.prod(shp)):
|
| 63 |
+
a = a.reshape(shp)
|
| 64 |
+
out.append(np.ascontiguousarray(a, np.float32))
|
| 65 |
+
return out
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def named(ctx, o):
|
| 69 |
+
return {nm: np.asarray(o[i], np.float32) for i, nm in enumerate(ctx.getOutputName())}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def main():
|
| 73 |
+
role, port = sys.argv[1], int(sys.argv[2])
|
| 74 |
+
from qai_appbuilder import QNNConfig, QNNContext, PerfProfile, Runtime, LogLevel
|
| 75 |
+
QNNConfig.Config(qnn_lib_path=f"{WS}/qnn_libs", runtime=Runtime.HTP, log_level=LogLevel.ERROR)
|
| 76 |
+
|
| 77 |
+
suffix = os.environ.get("ACTION_CTX_SUFFIX", "")
|
| 78 |
+
ctx = QNNContext(role, f"{WS}/ctx/{role}{suffix}_socid77_archv73.bin")
|
| 79 |
+
PerfProfile.SetPerfProfileGlobal(PerfProfile.BURST)
|
| 80 |
+
|
| 81 |
+
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
| 82 |
+
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
| 83 |
+
srv.bind(("127.0.0.1", port)); srv.listen(1)
|
| 84 |
+
open(f"{WS}/tmp/w_{role}.ready", "w").write("1")
|
| 85 |
+
conn, _ = srv.accept()
|
| 86 |
+
conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
| 87 |
+
|
| 88 |
+
while True:
|
| 89 |
+
try:
|
| 90 |
+
req = recv_tensors(conn)
|
| 91 |
+
except (ConnectionError, struct.error):
|
| 92 |
+
break
|
| 93 |
+
_t = time.time()
|
| 94 |
+
o = ctx.Inference(feed(ctx, req))
|
| 95 |
+
print(f"[infer-ms] {role} {(time.time()-_t)*1000:.1f}", flush=True)
|
| 96 |
+
send_tensors(conn, named(ctx, o))
|
| 97 |
+
|
| 98 |
+
os._exit(0) # dodge an appbuilder<->libs teardown double-free (see README device gotchas)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
if __name__ == "__main__":
|
| 102 |
+
main()
|