AI Hub Model Run
Apps & AutomationDownload pre-exported models from Qualcomm AI Hub and run inference on-device. Supports QNN_CONTEXT_BINARY, QNN_DLC, VOICE_AI, ONNX, TFLITE formats. All on-device (NPU/HTP) inference runs through qai_appbuilder (QNNContext), loading the QNN context binary (.bin) or .dlc. Standard onnxruntime is used only for an optional CPU baseline comparison.
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/qualcomm/qai-appbuilder/blob/HEAD/tools/qaimodelbuilder/skills/aihub-model-run/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/ai-hub-model-run, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
AI Hub Model Download & Inference
π¨ READ THIS ENTIRE FILE BEFORE TAKING ANY ACTION
β Pre-Flight Self-Check (MANDATORY GATE β answer all questions before downloading)
Skipping this table (especially question 2) is the root cause of repeated pitfalls β most "dependency trial-and-error / I/O mismatch / wrong tool" problems already have ready answers in the corresponding model's
NOTES.md.
| # | Must answer | How to do it |
|---|---|---|
| 1 | Detected the chipset suffix? | Step 0 (Get-WmiObject, NOT Get-PnpDeviceProperty) |
| 2 | Read this model's NOTES.md? | read skills/aihub-model-run/models/<model_id>/NOTES.md; if it exists β must read (contains download links / I/O / known pitfalls / ready-made infer_*.py, lets you skip most of Step 1~5); if it does not exist β you must create it at the end of the task |
| 3 | Inference tool = qai_appbuilder? | NPU inference always uses QNNContext to load .bin/.dlc; onnxruntime is only for the optional CPU baseline comparison |
| 4 | Locating the model package uses fixed paths only? | Model packages go via webfetchβcurl downloaded to the fixed C:/WoS_AI/<model>/; NEVER Get-ChildItem -Recurse to scan all of C:\/C:\Shared/C:\Users/C:\WoS_AI (it hangs for 30+ minutes, see Issue 18) |
NOTES already collected:
beitmelotts_zhresnet50zipformer(see the Model Notes Directory at the end of this file).
π€ Sub-Agent Dispatch Template (MANDATORY when dispatching a sub-agent to execute this skill)
Background lesson (2026-06): The main agent saw only the one-line
Use forsummary of this skill in its system prompt, did not read this full SKILL.md, and was contaminated by the path mindset of themodel-builderskill that was also present β so it wrote a sub-agent instruction to "searchC:\Shared/C:\WoS_AIfor.bin/metadata.json". The sub-agent had a brand-new blank context, did not inherit the main agent's system prompt, and could only execute the prompt as written β triggering the full-disk recursion of Issue 18 and hanging for 30+ minutes.
If the main agent is going to dispatch a sub-agent to execute this skill, it MUST follow these rules:
- Before dispatching, the main agent MUST first
readthe full SKILL.md (do not assemble a prompt based only on the one-lineUse forsummary in the catalog). - The sub-agent prompt's first instruction MUST be "read the full SKILL.md before acting" β the sub-agent does not inherit the main agent's context, so all the constraints of this SKILL (Issue 18 anti-recursion, fixed-path download, qai_appbuilder inference, etc.) can only be learned by reading it itself.
- The sub-agent prompt MUST NOT carry any path / script / toolchain reference from the
model-builderskill (factory\chat_features\model-builder\scripts\*.py,run_pipeline.py,qnn-onnx-converter,qairt_sdk_rootconversion tools, etc.) β those apply only to "custom ONNX conversion", are useless for AI Hub pre-compiled packages, and induce wrong full-disk searches. - The sub-agent prompt MUST NOT contain any "search / look for" combined with a large directory (
C:\,C:\Shared,C:\Users,C:\WoS_AI) (see Issue 18). Locate the model package using fixed paths only:qai_hubdirect download /curldownload toC:/WoS_AI/<model>/.
Reusable standard sub-agent prompt template:
You will execute an aihub-model-run skill task: <specific task, e.g. "download the Inception v3 pre-compiled package and run inference on a test image on the NPU, printing Top-5">.
[Step 1, mandatory] First use the read tool to fully read:
${APP_ROOT}\skills\aihub-model-run\SKILL.md
Follow it strictly (including the Pre-Flight Self-Check GATE, fixed-path download, Issue 18 anti-recursion, qai_appbuilder inference rule).
(${APP_ROOT} = QAIModelBuilder repo root; do not hardcode the absolute path of any particular machine.)
[Step 0.5, mandatory β NOTES.md two-step check]
The directory name under skills/aihub-model-run/models/ may differ from the model name in this prompt
(e.g. prompt says "inceptionv3" but directory is "inception_v3"; prompt says "BEiT" but directory is "beit").
NEVER guess the path or rely on a single glob β always do BOTH steps:
1. list "skills/aihub-model-run/models/" to see the exact directory names present.
2. read the matching NOTES.md using the exact name found in step 1.
If a matching NOTES.md exists it contains download links, I/O shapes, known issues, and a ready-made
inference script β use them directly and skip most of Steps 1~5.
[Locating the model package β fixed paths only, NEVER full-disk search]
- If qai_hub is installed (import qai_hub can print the version) β download directly with qai_hub; or per SKILL Step 1/3 use webfetch to get the link, curl download to C:/WoS_AI/<model>/.
- You may only check these two fixed shallow directories (Get-ChildItem WITHOUT -Recurse): C:\WoS_AI\<model>\, C:\Users\<user>\.qaihub\.
- π« NEVER run -Recurse / glob **/* full-disk recursion on the UNBOUNDED roots C:\, C:\Shared, C:\Users, or C:\WoS_AI WITHOUT a <model> subdir (it hangs for 30+ minutes). Recursing inside a specific bounded dir like C:\WoS_AI\<model>\ is fine.
[Environment] Read python_arm64_venv from ${APP_ROOT}\data\config\qairt_env.json; inference always uses qai_appbuilder / QNNContext.
[Forbidden] Do not use any script/path from the model-builder skill (run_pipeline.py, qnn-onnx-converter, qairt conversion tools, etc.) β this task is a pre-compiled package, no conversion.
When done, report: chipset detection result / model package path and contents / inference script path / Top-5 results.
Decision Prerequisites
Default to this skill: download the AI Hub pre-compiled package and run inference directly with qai_appbuilder (QNNContext). Only python_arm64_venv is needed; no VS 2022 / QAIRT SDK conversion toolchain required.
π¨ Inference tool selection rule (MANDATORY)
NPU (HTP) inference always uses
qai_appbuilder(QNNContext), loading a QNN context binary (.bin) or.dlc.In this skill, standard
onnxruntimeis only used for the optional CPU baseline comparison (CPUExecutionProvider, for accuracy/performance comparison against the NPU result), and is never used to run a model on the NPU.
Switch to the model-builder skill when: β you have a custom ONNX/PyTorch model and AI Hub has no corresponding pre-compiled package; β‘ you need to re-quantize/compile a custom NPU .bin.
Trigger Phrases
- "download model from aihub and run inference"
- "run <ModelName> on device" / "run inference for <ModelName>" / "infer <ModelName> for me"
Environment
βΉοΈ
${APP_ROOT}= the root directory of this repo (QAIModelBuilder). This file uses${APP_ROOT}to refer to the repo root, avoiding hardcoding the absolute path of any particular machine. At runtime, just resolve it to the actual repo root location (the current working directory ofexecis usually the repo root; if unsure, infer it from this machine's install path, or work backwards from an absolute path that appears inqairt_env.json).
Read paths from ${APP_ROOT}\data\config\qairt_env.json:
| Key | Role |
|---|---|
python_arm64_venv | ARM64 Python 3.13 β ALL inference runs here |
qairt_sdk_root | QAIRT SDK root (reference only) |
Working directory: C:/WoS_AI/<model_name>/
β οΈ
read/skilltool path rendering warning β do NOT trust the displayed path literally when editing files: Thereadandskilltools automatically expand placeholder variables (e.g.$β{APP_ROOT}) into the actual absolute path of the current machine before displaying content. This means:
- The displayed path may look like a hardcoded absolute path, but the file actually contains
$β{APP_ROOT}(or another placeholder). Do NOT copy the displayed absolute path back into aneditcall asoldTextβ the match will fail because the file does not contain that string.- Do NOT replace
$β{APP_ROOT}with an absolute path when editing. If you seeC:\Work\AppBuilder\...in the displayed output and want to edit that line, use Python to read the raw file content first (open(...).read()) to confirm what the file actually contains, then use the real content asoldText.- Quick check: run
python -c "print(open(r'<file>').read()[<idx>:<idx+100>])"to inspect the raw bytes around the target line before writing anedit.
Step 0 β Detect Platform (MANDATORY)
Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" |
Where-Object { $_.PSChildName -like "qcadsp*" } |
Get-ItemProperty | Select-Object PSChildName, ImagePath
Read the 4-digit SoC code from the INF filename in ImagePath.
| Driver name | Chipset | Download suffix |
|---|---|---|
qcadsp*_8380 | Snapdragon X Elite | qualcomm_snapdragon_x_elite |
qcadsp*_8480 | Snapdragon X2 Elite | qualcomm_snapdragon_x2_elite |
qcadsp*_8380 (8-core) | Snapdragon X Plus 8-Core | qualcomm_snapdragon_x_plus_8_core |
β οΈ Wrong chipset package β wrong inference results. Always detect first. β οΈ Do NOT use
Get-PnpDevicePropertyβ it wakes the DSP and blocks 300β400 s. β οΈ Do NOT useGet-WmiObject Win32_SystemDriverβ WMI provider throttling causes this query to hang indefinitely on busy systems.
Step 0.5 β Check Model Notes (MANDATORY β Before Any Other Step)
π¨ MANDATORY β before executing Step 1 you must first check the local NOTES.md (skipping this step is the root cause of repeated pitfalls).
β οΈ The directory name under
models/may differ from the model_id you were given (e.g. task saysinceptionv3but directory isinception_v3; task saysBEiTbut directory isbeit). NEVER rely on a single glob/path-exists check alone. The mandatory two-step procedure is:
- Always
listthemodels/directory first to see the exact directory names present.- Then
readthe matching NOTES.md using the exact directory name from step 1. Skipping step 1 and guessing the path is the direct cause of "NOTES.md not found" false negatives that waste hours of unnecessary work.
# Step 1: list to find the exact directory name
list "skills/aihub-model-run/models/" β always do this first
# Step 2: read NOTES.md using the exact name from the listing
read "skills/aihub-model-run/models/<exact_dir_name>/NOTES.md"
# Only after the two steps above β if truly no matching directory exists:
# β continue to Step 1, and create NOTES.md in this directory to record new findings when done
The content in NOTES.md lets you directly skip most of the work in Step 1~4:
| If NOTES.md has | Steps you can skip |
|---|---|
| Download links | The webfetch lookup in Step 1 |
| Sub-models I/O table | Reading metadata.json in Step 4 |
| Known Issues (e.g. input order, dtype) | Avoid hitting the pitfall before looking it up |
infer_<model>.py inference script | Skip Step 5 entirely, run it directly |
Step 1 β Look Up the Model
Method A β Parse AI Hub page HTML (preferred, always works without login)
The AI Hub model page (https://aihub.qualcomm.com/models/<model_id>) embeds all download links directly in the HTML source (inside Next.js data islands). webfetch cannot see them (returns only visible text), but Python urllib + regex extracts them reliably:
import urllib.request, ssl, re
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE # required on WoS β see Issue 19
req = urllib.request.Request(
'https://aihub.qualcomm.com/models/<model_id>',
headers={'User-Agent': 'Mozilla/5.0'}
)
html = urllib.request.urlopen(req, context=ctx, timeout=15).read().decode('utf-8', errors='ignore')
# Extract all S3 zip download links
s3_links = re.findall(r'https://qaihub-public-assets\.s3[^\s<>"]+\.zip', html)
seen = set()
for u in s3_links:
u = u.rstrip('\\').rstrip('"').rstrip("'")
if u not in seen:
seen.add(u)
print(u)
This returns the full versioned list of all available formats and precisions, e.g.:
https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/inception_v3/releases/v0.57.3/inception_v3-qnn_dlc-float.zip
https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/inception_v3/releases/v0.57.3/inception_v3-qnn_dlc-w8a8.zip
https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/inception_v3/releases/v0.57.3/inception_v3-onnx-float.zip
...
Note: The AI Hub page URL uses
https://aihub.qualcomm.com/models/<model_id>(no/compute/prefix).
Method B β webfetch HuggingFace (fallback when Method A fails)
Fetch both URLs with webfetch:
https://aihub.qualcomm.com/models/<model_id>https://huggingface.co/qualcomm/<ModelName>
The HuggingFace page has download links, metadata.json I/O shapes, and performance tables.
β οΈ Do NOT use
qai_hub.get_models(name=...)to search models by name β that API does not accept thenamekeyword (raisesunexpected keyword argument). The correct way to get download links is Method A above or the HuggingFace model page. For models that already have aNOTES.md(see Step 0.5), just use the fixed S3 link recorded in NOTES β you can even skip this step entirely.
Fallback: Construct S3 URL from GitHub (when webfetch/HuggingFace is blocked)
When both the AI Hub page and HuggingFace return 401/404 (gated model or network restriction), use this fallback to find and probe the S3 URL directly:
Step A β Get the exact model_id from GitHub:
https://github.com/qualcomm/ai-hub-models/tree/main/src/qai_hub_models/models/
The directory name IS the model_id (e.g. inception_v3, mobilenet_v2, resnet50). Do NOT guess β fetch the directory listing to confirm.
Step B β Read release-assets.yaml from GitHub to get available formats/precisions:
https://raw.githubusercontent.com/qualcomm/ai-hub-models/main/src/qai_hub_models/models/<model_id>/release-assets.yaml
This file lists all available formats (qnn_dlc, onnx, tflite) and precisions (float, w8a8, w8a16).
β οΈ If
s3_keystarts withpre_release_assets/gh_actions/...β that path is private (AccessDenied). Use the public versioned path in Step C instead.
Step C β Construct and probe the public versioned S3 URL:
URL template:
https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/<model_id>/releases/v<ver>/<model_id>-<runtime>-<precision>.zip
- Start with the latest known working version (
v0.56.0verified 2026-06; tryv0.55.0as fallback). - The DLC assets are universal (no chipset suffix in the filename for
qnn_dlc). - Probe with Python
urllibGET (not HEAD/Range β S3 returns 403 for those even on public objects):
import urllib.request, ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
candidates = [
"https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/<model_id>/releases/v0.56.0/<model_id>-qnn_dlc-float.zip",
"https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/<model_id>/releases/v0.56.0/<model_id>-qnn_dlc-w8a8.zip",
"https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/<model_id>/releases/v0.55.0/<model_id>-qnn_dlc-w8a8.zip",
]
for u in candidates:
try:
r = urllib.request.urlopen(urllib.request.Request(u), context=ctx, timeout=8)
print(f"OK {r.headers.get('Content-Length','?'):>12} {u}")
r.close()
except urllib.error.HTTPError as e:
print(f"{e.code} {u}")
except Exception as e:
print(f"ERR {e} {u}")
Known limitations of this fallback:
- Not all models/precisions are publicly accessible at every version β 403 or timeout is normal; try the next candidate.
floatprecision is sometimes inaccessible (timeout) whilew8a8works, or vice versa.- Some models use a chipset suffix in the filename (e.g. BEiT:
beit-qualcomm_snapdragon_x_elite-qnn_dlc-w8a16.zip); checkrelease-assets.yamlfor the exact filename pattern. - Version
v0.56.0was verified working forinception_v3,mobilenet_v2,resnet50(2026-06); update this note when a newer version is confirmed.
Step 2 β Choose Format
π¨ Format selection rule (MANDATORY):
- NPU inference always uses
qai_appbuilder.QNNContext, loading a QNN context binary (.bin) or.dlc.- Prefer
QNN_CONTEXT_BINARY(.bin) orQNN_DLC(.dlc) β the vast majority of AI Hub models provide one of them, loadable directly byqai_appbuilder. For multi-platform cases, prefer the context binary matching this machine's chipset (X Elite / X2 Elite, see Step 0 detection).- The
ONNXformat is only used for the CPU baseline comparison withonnxruntime(CPUExecutionProvider), to compare accuracy/performance against the NPU result, and is never used for NPU inference.
| Priority | Format | How to run | When to use |
|---|---|---|---|
| 1st β | QNN_CONTEXT_BINARY | qai_appbuilder.QNNContext (load .bin, see Issue 9) | Default first choice, context binary matching this machine's chipset |
| 2nd β | QNN_DLC | qai_appbuilder.QNNContext (load .dlc) | When no .bin; .dlc is portable across HTP versions |
| 3rd | ONNX | onnxruntime CPUExecutionProvider | Only for CPU baseline comparison |
| 4th | VOICE_AI | custom pipeline (see Issue 13) | TTS/multi-model pipeline (.bin inside the package) |
| 5th | TFLITE | fallback only | Last resort |
Download URL pattern:
https://qaihub-public-assets.s3.us-west-2.amazonaws.com/qai-hub-models/models/<model_id>/releases/v<ver>/<model_id>-<runtime>-<precision>-<chipset_suffix>.zip
Step 3 β Download & Extract
# Download (model ZIPs can be 200 MB+, use long timeout)
# -k disables SSL certificate verification (required on WoS β see Issue 19)
exec('curl -k -L "<url>" -o "C:/WoS_AI/<model>/<file>.zip" --create-dirs', timeout=300)
# Extract β ALWAYS zipfile, NEVER tar (tar does not support ZIP)
import zipfile
zipfile.ZipFile("C:/WoS_AI/<model>/<file>.zip").extractall("C:/WoS_AI/<model>/")
ZIP typically contains: model files (.dlc/.bin/.onnx), metadata.json, optionally labels.txt / config.json.
Step 4 β Read metadata.json (MANDATORY)
Always read before writing inference code. Contains:
- Input/output tensor names, shapes, dtypes
- Quantization params (scale + zero_point) for quantized models
- Input layout (NHWC vs NCHW)
β οΈ Do NOT rely on
model.getInputDataType()alone β QNN reports quantized uint16 asufp16. See Issue 5.
Step 5 β Run Inference
Templates (at ${APP_ROOT}/factory/chat_features/model-builder/scripts/inference/)
| Model type | Template |
|---|---|
| Image classification | infer_classify.py |
| Object detection | infer_detect.py |
| Segmentation | infer_segment.py |
| Super resolution | infer_sr.py |
| Generic / I/O inspection | infer_generic.py |
| TTS / multi-model pipeline | custom script (see model NOTES.md) |
Run command
& "<python_arm64_venv>\Scripts\python.exe" `
"<template_path>\infer_classify.py" `
--model "C:\WoS_AI\<model>\<folder>\<model>.dlc" `
--input "C:\path\to\test_image.jpg" --labels "...\labels.txt" --topk 5
Test image priority
- Repo built-in (first choice):
${APP_ROOT}\samples\images\(flower.jpg/tabletop.jpg) - Reuse existing:
C:\WoS_AI\<model>\test_image.jpg - Synthesize locally (shape-specific only):
PIL.Image.fromarray(...) - Download (last resort):
curl --max-time 20 -L -o x.jpg <URL>
Standard image preprocessing (classification)
Use unless metadata.json says otherwise:
from PIL import Image
import numpy as np
def preprocess(path, size=224):
"""β float32 NHWC (1,H,W,C), [0,1]. Resize shortest-side, center-crop."""
img = Image.open(path).convert("RGB")
w, h = img.size
scale = size / min(w, h)
nw, nh = int(round(w*scale)), int(round(h*scale))
img = img.resize((nw, nh), Image.LANCZOS)
l, t = (nw-size)//2, (nh-size)//2
arr = np.array(img.crop((l,t,l+size,t+size)), dtype=np.float32) / 255.0
return arr[np.newaxis] # (1,size,size,3)
βΉοΈ Check
metadata.json"value_range":[0,1]β /255 only (AI Hub default, norm baked in);[-1,1]β/127.5-1;[0,255]β cast float32 directly. For ONNX: transpose β Issue 15.
π HTP BURST performance mode β lifecycle rule (MANDATORY)
PerfProfile.SetPerfProfileGlobal(PerfProfile.BURST) raises the HTP to its
highest clock. Getting the lifecycle right is critical for stable, fast
inference β especially for streaming / session-based workloads (voice
input/ASR, TTS, any task that runs many inferences over a session).
Rules:
-
BURST only takes effect AFTER at least one model (QNNContext) is loaded in the process. Calling it before any context exists silently does nothing (you will see
setPowerConfig error 0x32c9β "no BURST permission"). -
Set BURST ONCE at the start of the whole task and hold it for the ENTIRE session; release it ONCE only when the whole task is finished. Do NOT Set/Release around every single inference call.
- Voice input / streaming ASR: raise HTP to BURST the moment recording starts (the model begins working), keep it held through all interim + final inference chunks, and release only after the entire voice-input session ends (voice output finished). Never Set/Rel per audio chunk.
- TTS / multi-model pipeline: Set BURST once before the first NPU stage (BERT/encoder/flow/decoderβ¦), keep it across all cooperating models, and release once after the last stage completes.
- One-shot single inference (classify one image, etc.): Set β infer β Release is fine because there is only one inference.
-
Release BURST BEFORE destroying the model contexts (
RelPerfProfileGlobal()must run while models are still loaded), otherwise you getYou should set perf profile before you release it!.
Correct pattern (session / streaming):
# 1. Load model(s) first.
enc = QNNContext("encoder", "encoder.bin")
dec = QNNContext("decoder", "decoder.bin")
# 2. Session starts (e.g. user starts recording) β raise HTP ONCE.
PerfProfile.SetPerfProfileGlobal(PerfProfile.BURST)
try:
# 3. Run MANY inferences over the whole session β BURST stays held.
for audio_chunk in session: # interim chunks + final chunk
run_one_inference(enc, dec, audio_chunk)
finally:
# 4. Session ends (voice output done) β release ONCE.
PerfProfile.RelPerfProfileGlobal()
# 5. Only now destroy the contexts.
del enc, dec
β Anti-pattern (causes HTP to ramp its clock up/down every call β slow, jittery latency):
for audio_chunk in session: PerfProfile.SetPerfProfileGlobal(PerfProfile.BURST) # WRONG: per-chunk run_one_inference(...) PerfProfile.RelPerfProfileGlobal() # WRONG: per-chunk
ONNX (CPU) + QNN DLC (NPU) in the same process
Standard onnxruntime with CPUExecutionProvider does NOT conflict with qai_appbuilder
(only the NPU/HTP device is exclusive β the CPU baseline runs fine alongside it). Use this pattern for format comparison:
QNNConfig.Config(Runtime.HTP, LogLevel.WARN, ProfilingLevel.OFF) # 2.47 signature: (runtime, log_level, profiling_level)
qnn = QNNContext("m", "model.dlc")
ort_sess = ort.InferenceSession("model.onnx", providers=["CPUExecutionProvider"])
nhwc = preprocess("img.jpg")
qnn_out = np.array(qnn.Inference([nhwc])[0]).flatten()
onnx_out = np.array(ort_sess.run(None, {"image_tensor": nhwc.transpose(0,3,1,2)})[0]).flatten()
cos = float(np.dot(qnn_out, onnx_out) / (np.linalg.norm(qnn_out)*np.linalg.norm(onnx_out)))
print(f"Cosine similarity: {cos:.6f}") # expect >0.9999 for float models
Step 6 β Interpret Results
On first load of a .dlc/.bin, WARNINGs such as warmup_parallel_stl / input_data_type: float can be ignored; cold start is ~5β60 s (graph compilation).
The following HTP logs are all non-fatal, ignore them (results are correct, do not fall back to CPU based on them):
setPowerConfig error 0x32c9(no BURST permission),Error 0x200: failed to close queue(queue close timing at teardown),m_CFBCallbackInfoObj is not initialized(v81 callback init order),Failed to create context with file mapping(ORT retries automatically and succeeds).
β οΈ Known Issues (All Models)
Issue 1: ZIP extraction fails
Symptom: tar: This does not look like a tar archive
Cause: Using tar -xf to extract a .zip.
Fix: Always use Python zipfile:
import zipfile; zipfile.ZipFile("model.zip").extractall("out/")
Issue 2: Unix tools not found in exec
Symptom: [hint] Detected Unix tool ls. Possible cause: PortableGit not installed
Fix: Explicitly specify shell='sh':
exec("ls C:/WoS_AI/", shell='sh')
exec("grep -r pattern dir/", shell='sh')
PortableGit is at %LOCALAPPDATA%\QAIModelBuilder\git\, available under shell='sh'.
β οΈ Do not count/filter file numbers with
dir | find /c(PortableGit's Unixfindintercepts it and reportsNo such file or directory). Use PowerShell(Get-ChildItem <path>\*.raw).Countor Pythonglobinstead.
Issue 3: Download timeout
Symptom: [process killed: timeout after 30.0s]
Fix: Use timeout=300 or omit it (0 = no limit):
exec('curl -k -L "<url>" -o "out.zip" --create-dirs', timeout=300) # -k: disable SSL verify (see Issue 19)
Issue 4: Chinese print causes UnicodeEncodeError
Symptom: UnicodeEncodeError: 'charmap' codec can't encode characters
Fix: Add at the top of the script:
import sys; sys.stdout.reconfigure(encoding="utf-8", errors="replace")
Double safety: use ASCII directly for log symbols (
->forβ,[OK]forβ), not depending on terminal encoding.
Issue 5: QNN native dtype trap (uint16 reported as ufp16)
Symptom: Output is NaN or absurd values.
Cause: With output_data_type='native', QNN wraps a quantized uint16 in a float16 container and reports it as ufp16, while the bits are actually a uint16 integer.
Detection: "dtype":"uint16" in metadata.json + presence of "quantization_parameters" β hit.
scale/zero_point MUST be taken from metadata.json, do not compute them yourself.
def quantize(x, scale, zp): # float32 β uint16 viewed as float16
return np.clip(np.round(x / scale) + zp, 0, 65535).astype(np.uint16).view(np.float16)
def dequantize(raw, scale, zp): # QNN native out β float32
return (raw.view(np.uint16).astype(np.float32) - zp) * scale
model = QNNContext("m", "m.bin", input_data_type="native", output_data_type="native")
out = dequantize(np.array(model.Inference([quantize(x, scale=1e-4, zp=32768)])[0]),
scale=3e-4, zp=32329)
Issue 6: ARM64 Windows missing native packages (torchaudio / numba / MeCab)
All mocks must be placed before the model import.
torchaudio mock:
import sys, types, importlib.util
_ta = types.ModuleType("torchaudio")
_ta.__spec__ = importlib.util.spec_from_loader("torchaudio", loader=None)
_ta.__version__ = "0.0.0"
sys.modules.setdefault("torchaudio", _ta)
numba mock:
_nb = types.ModuleType("numba")
def _jit(*a, **kw): return (lambda f: f) if not (len(a)==1 and callable(a[0])) else a[0]
# β οΈ Setting only int32/float32 to None is not enough: melo internally has
# type-indexing syntax like `numba.int32[:, ::1]`; None does not support __getitem__
# and will crash at import. Use a placeholder type that supports chained indexing.
class _NumbaType:
def __getitem__(self, _key): return self # supports chained indexing like int32[:, ::1] / [:, :, ::1]
_nb_type = _NumbaType()
_nb.jit = _jit
_nb.void = None
_nb.int32 = _nb.int64 = _nb.float32 = _nb.float64 = _nb_type
sys.modules.setdefault("numba", _nb)
MeCab (Japanese): patch melo/text/japanese.py, wrapping the module-level import in try/except. See models/melotts_zh/NOTES.md Β§ Dependency Patch.
python-mecab-ko (Korean): melo/text/korean.py also imports python-mecab-ko at the module top level (fails to compile on ARM64). Handle the same as japanese: before import melo, inject melo.text.korean into sys.modules as a stub, or wrap its top-level import in try/except. When running only Chinese TTS this module is not used, so a stub is enough.
βΉοΈ Install all dependencies for VOICE_AI/TTS models in one go (including
--no-depsitems and the mock list); see the correspondingmodels/<id>/NOTES.md Β§ Quick Start. Install everything at once per the list, do not trial-and-error package by package.
Issue 7: When the model I/O shape disagrees with metadata.json, trust getInputName() / getInputShapes()
metadata.json sometimes describes the I/O of the ONNX model, while the actually loaded QNN .bin may, after compilation, have subtle differences in input order or shape.
Always confirm the actual order and shape with model.getInputName() and model.getInputShapes(), then build the input list.
Issue 8: For NPU inference use only qai_appbuilder to load .bin / .dlc; do not use onnxruntime to run the NPU
Principle: All on-device (NPU/HTP) inference in this skill goes through qai_appbuilder.QNNContext loading a QNN context binary (.bin) or .dlc. The downloaded .bin filename varies by model (e.g. encoder.bin / model.bin); trust metadata.json and the actual extracted files.
from qai_appbuilder import QNNContext, QNNConfig, Runtime, LogLevel, ProfilingLevel
QNNConfig.Config(Runtime.HTP, LogLevel.WARN, ProfilingLevel.OFF) # 2.47 signature: (runtime, log_level, profiling_level)
model = QNNContext("name", "encoder.bin") # load the model's .bin, infer on the NPU
outputs = model.Inference([input_tensor])
βΉοΈ A
.binis bound to the QAIRT version it was compiled with; a version mismatch reportsError code: 5000. The QNN runtime bundled withqai_appbuilderis usually backward-compatible with.binfiles compiled by older versions (e.g. a.bincompiled with 2.45 can be loaded byqai_appbuilder2.46). βΉοΈ Only the plainONNXformat usesonnxruntime'sCPUExecutionProviderfor the CPU baseline comparison; never run the model on the NPU with it.
Issue 9: qai_appbuilder.QNNContext crash (0xC0000005), caused by not calling QNNConfig.Config() first
Symptom: The Python process exits directly with exit code 3221225477 (0xC0000005, ACCESS_VIOLATION), with no Python traceback.
Cause: The global state of the C extension appbuilder.pyd (log level, profiling level, HTP backend) must be initialized by QNNConfig.Config(), otherwise the constructor dereferences an uninitialized pointer.
Fix: the first qai_appbuilder call in every process must be QNNConfig.Config():
from qai_appbuilder import QNNContext, QNNConfig, LogLevel, Runtime, ProfilingLevel
QNNConfig.Config(Runtime.HTP, LogLevel.ERROR, ProfilingLevel.OFF) # 2.47 signature: (runtime, log_level, profiling_level) β no lib-dir arg
model = QNNContext("name", "model.bin") # only then can you load the model
Omitting qnn_lib_path automatically uses the package's libs/ directory; no need to specify the DLL path manually.
β Version compatibility: a
.bincompiled by an older QAIRT (e.g. 2.45) can be loaded normally by a newerqai_appbuilder(e.g. 2.46).
Issue 10: When numerically comparing NPU inference against an ONNX CPU baseline, separate processes are more reliable
Note: Multiple .bin/.dlc contexts can be loaded simultaneously within the same qai_appbuilder process (e.g. zipformer's encoder/decoder/joiner trio); this is normal usage and does not conflict.
For a "NPU result vs ONNX CPU baseline" numerical comparison, it is recommended to run them in separate processes, each saving its output as .npy then comparing them uniformly, to avoid the two runtimes/dependencies interfering with each other in the same process:
# Process A: load .bin with qai_appbuilder, infer on NPU, save outputs/qnn/*.npy
# Process B: run a plain .onnx baseline with onnxruntime + CPUExecutionProvider, save outputs/cpu/*.npy
# Process C: load both sets of .npy, compute cosine similarity
β A float model's NPU output vs CPU baseline is usually cosine > 0.999; for quantized models judge by the accuracy threshold (β₯0.95).
Issue 11: The Failed to create context with file mapping warning can be ignored
[W] Failed to create context with file mapping enabled. ... Retrying with feature disabled.
ORT retries automatically and succeeds; this is not an error and needs no handling.
Issue 12: The input list of QNNContext.Inference() must match the order of getInputName() exactly
Symptom: Inference() hangs forever or produces garbled output.
Cause: Inputs are matched by index, not by name. The input registration order of the compiled graph may differ from the ONNX order.
print(model.getInputName()) # confirm order
print(model.getInputShapes()) # confirm shapes
inputs = [t0, t1, ...] # strictly in getInputName() order
outputs = model.Inference(inputs)
out_dict = dict(zip(model.getOutputName(), outputs)) # also look up outputs by name
result = out_dict["output_name"]
β οΈ A wrong order causes a silent hang without raising an exception. Multi-input models (such as the cache tensors of streaming ASR) are especially dangerous.
Issue 13: VOICE_AI format needs a custom multi-model pipeline
Cause: The VOICE_AI ZIP directly contains .bin context binary files, loadable directly with qai_appbuilder.QNNContext, no Voice AI SDK needed.
encoder.bin / flow.bin / decoder.bin / bert_wrapper.bin
config.json β sample rate, speaker ID, etc.
metadata.json β each sub-model's I/O shapes, quantization params
Fix: Call the sub-models in pipeline order one by one. Full example in models/melotts_zh/NOTES.md.
β οΈ The VOICE_AI
.binis a proprietary packaging format, loadable only withqai_appbuilder.QNNContext; never run it with command-line tools likeqnn-net-run/snpe-net-runβ those CLIs expect a standard QNN context binary and will fail or produce garbage on VOICE_AI sub-model.binfiles. Quantized sub-models (such as flow/decoder) also need their I/O handled per the native dtype rule in Issue 5.
Issue 14: An ONNX ZIP may contain an external weights file; the two files must be in the same directory
PyTorch 2.x torch.onnx.export splits a large model into <m>.onnx (the graph, a few KB) + <m>.data (the weights, tens to hundreds of MB), with the .onnx referencing .data by relative path.
Always extract the whole ZIP and keep the two files in the same directory. Copying the .onnx alone will fail to load because it cannot find .data.
Issue 15: QNN DLC uses NHWC, ONNX uses NCHW
AI Hub QNN DLC image models take input [1,H,W,C], corresponding to ONNX [1,C,H,W]; they are not interchangeable.
nhwc = preprocess(img) # (1,H,W,C) β QNN DLC
nchw = nhwc.transpose(0, 3, 1, 2) # (1,C,H,W) β ONNX
Before writing preprocessing, check the
shapeinmetadata.jsonto confirm the layout.
Issue 16: Multi-sub-model ASR/Transducer (encoder/decoder/joiner)
- Sub-models cannot be mixed across release packages: encoder/decoder/joiner must come from the same AI Hub package. Even with the same name (e.g. Zipformer), different versions have different hidden dimensions (e.g. 512-dim vs the 320-dim of sherpa-onnx 14M); mixing reports
Got invalid dimensions: Got 512 Expected 320. - Debug order encoder β decoder β joiner (not in reverse): when recognition outputs only 1~2 tokens, first check whether the encoder output L2 norm is abnormal (normally usually <20; a value of ~50 means numerical explosion, which will overflow the downstream joiner's FP16), rather than diving straight into the joiner.
- Do a numerical health check after loading: use a clip of real speech to inspect the encoder output norm distribution; if abnormal, stop and investigate (it may be a difference between the compile-time QAIRT version and runtime HTP behavior, see below).
βΉοΈ A QNN context binary (
.bin) is sensitive to the compile-time QAIRT version: on the same chipset, a graph compiled with an older version occasionally has numerical precision issues on a newer HTP. Before starting, comparetool_versions.qairtinmetadata.jsonwith_versionin this machine'sqairt_env.json; if they differ, watch out for the norm health check in advance.
Issue 17: Invalid test audio/input causes a false "empty result" judgment
Synthetic audio (sine wave/silence) makes ASR inference results empty, easily misjudged as a code bug. Verify input validity before inference:
energy = float(np.sqrt(np.mean(waveform.astype(np.float32)**2)))
if energy < 1e-3:
print("[WARN] audio looks like silence/synthetic β empty result is expected")
Same for image tasks: prefer real images from ${APP_ROOT}\samples\images\; Top-K on synthetic images is meaningless.
Issue 18: π¨ NEVER full-disk recursively scan to find a model package (it hangs for 30+ minutes)
Symptom: Get-ChildItem -Recurse runs for tens of minutes without returning, the sub-agent times out, the terminal appears frozen.
Root cause: Trying to "find a locally cached model package" by doing -Recurse full-disk searches for *.bin/*.dlc/metadata.json over an unbounded root directory (C:\, C:\Shared, C:\WoS_AI, C:\Users). Under C:\Shared are the entire project repo + the V1/v0.5 sister repos + node_modules + .venv + vendor/whl + data/, hundreds of thousands of files; recursively stat-ing them inevitably takes tens of minutes or even hangs. The cost depends on "how many files were traversed", regardless of "how many matched"; -ErrorAction SilentlyContinue does not speed it up.
Iron rule: this skill locates model packages with fixed paths and NEVER does a full-disk search:
- The "find a local cache" step is not needed: the normal flow of this skill is Step 1 (webfetch to get the link) β Step 3 (curl download to the fixed directory
C:/WoS_AI/<model>/). The model package location is always a known fixed path; there is no "search locally by luck" step. - If
qai_hubis installed, download with it directly (installed ifqai_hub.__version__can print), do not fall back to searching the local cache. - If you really must check existing downloads: only
Get-ChildItem(without-Recurse) the following fixed shallow directories, each returning in 0.x seconds:C:\WoS_AI\<model>\(this skill's working directory, where download/extract happens)C:\Users\<user>\.qaihub\(qai_hub's official cache, a shallow directory)
- Always forbidden: doing
-Recurseon root/large directories likeC:\,C:\Shared,C:\Users,C:\WoS_AI(without a<model>subdirectory). When you need to confirm the contents of a particular model subdirectory, only recurse down to the level ofC:\WoS_AI\<specific model name>\.
β οΈ Also applies when dispatching a sub-agent to execute this skill: if the sub-agent task prompt contains phrasing like "search
C:\Sharedfor packages" / "look for any existing.binfiles", it must be rewritten into the fixed shallow-directory check in point 3 above, otherwise the sub-agent will faithfully execute it as a full-disk recursion and hang.
π¨ MANDATORY β before inference you must first execute Step 0.5 (read
NOTES.md), which contains known issues, I/O shapes, and a ready-made inference script.
skills/aihub-model-run/models/
βββ beit/ NOTES.md + infer_beit.py (QNN_DLC w8a16, image classification)
βββ melotts_zh/ NOTES.md + infer_melotts_zh.py (VOICE_AI, TTS)
βββ resnet50/ NOTES.md (QNN_DLC + ONNX float, comparison verified)
βββ zipformer/ NOTES.md + infer_zipformer.py (QNN_CONTEXT_BINARY, qai_appbuilder, streaming ASR)
After completing a new model, you must create the corresponding NOTES.md, recording:
- Download links, chipset suffix, file structure
- Sub-models I/O table (name, shape, dtype)
- Performance data (latency, RTF)
- Model-specific known issues (in Issue Z-x format)
- Inference command
Issue 19: SSL certificate verification failure on WoS (curl exit code 35 / Python urllib SSL error)
Symptom: curl exits with code 35 (SSL connect error) or Python urllib raises ssl.SSLError / certificate verify failed when downloading from qaihub-public-assets.s3.us-west-2.amazonaws.com.
Cause: The Windows on Snapdragon (WoS) environment's system CA bundle sometimes does not include the intermediate certificate for the S3 endpoint, causing TLS handshake failure.
Fix β always use -k with curl and disable cert verification in Python urllib:
# curl: add -k to skip SSL certificate verification
exec('curl -k -L "<url>" -o "C:/WoS_AI/<model>/<file>.zip" --create-dirs', timeout=300)
# Python urllib: create an unverified SSL context
import ssl
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
urllib.request.urlopen(req, context=ctx, timeout=15)
β οΈ Always apply
-kproactively β do not wait for a 35/SSL error to appear. The S3 host is a known Qualcomm public asset server; skipping cert verification here is safe. β οΈ S3 returns 403 for HEAD and Range-GET even on publicly readable objects β always use a plain GET (optionally read just the first few bytes and close) to probe URL existence. HEAD/Range probing will give misleading 403 results.
Quick Reference
0. detect Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" | Where qcadsp* β ImagePath β SoC code
0.5 NOTES read skills/aihub-model-run/models/<model_id>/NOTES.md β MANDATORY FIRST
(contains download URL, I/O shapes, known issues, ready-to-run infer script)
1. webfetch https://huggingface.co/qualcomm/<ModelName> β download URL + I/O (skip if NOTES.md has it)
2. curl download ZIP to C:/WoS_AI/<model>/ β -k -L, timeout=300 (Issue 19: always -k)
3. python zipfile.ZipFile(...).extractall(...) β NEVER tar
4. read metadata.json β shapes, dtypes, quant (skip if NOTES.md has it)
5. check Issue list above β avoid known pitfalls
6. python "<python_arm64_venv>\Scripts\python.exe" infer_*.py
7. report results; add NOTES.md for new model-specific issues