Skill v1.0.1
Automated scan100/100+2 new
version: "1.0.1" name: onboard description: Onboard a new model to Qualcomm AI Hub Models. Guides authoring a standalone folder (model.py, app.py, demo.py, test.py, manifest.yaml) that the qai-hub-models CLI can install, export, and evaluate. Ships supported_precisions: [float]; quantization is a separate later skill. Use when adding a new model recipe.
Model Onboarding
Target: a self-contained recipe folder driven by the qai-hub-models CLI via manifest.yaml + model.py. There are no generated export.py / evaluate.py scripts. Ship [float] only — add-quantization handles quantized precisions later.
Intake
Ask for two things up front (and only these):
- Model name or source — HF repo, GitHub URL, PyPI package, or a Googleable name.
- Dataset and metric — the accuracy dataset (ImageNet, COCO, WIDER FACE, …) and metric (Top-1, mAP, PSNR, WER, …). If the user doesn't offer one, derive it from the model card / task family and propose it — same as you derive license and task type. Then wire it: author the dataset class (either shared under
qai_hub_models/datasets/and listed underdatasets:inmanifest.yaml, or as a localdataset.pyin the recipe folder — either is fine; put it underqai_hub_models/datasets/if another recipe is likely to reuse it), and addget_evaluator()/get_eval_dataset_classes()/get_calibration_dataset_cls()tomodel.py. Skip only when the user explicitly declines or the model has no accuracy notion (a generative model with no standard eval benchmark, etc.) — call that out in the final summary.
Dataset+evaluator wiring is part of onboarding, NOT part of add-quantization. It's what lets you torch-verify the float recipe against a reference number, independent of any quantization intent. add-quantization hard-requires it and will send the user back here if it's missing.
Do NOT ask about license, task type, input shape, precisions, or multi-component structure. Derive them.
Derive before asking
- License — HF:
https://huggingface.co/api/models/<owner>/<repo>(cardData.license,tags). GitHub:gh api repos/<owner>/<repo>(license.spdx_id). Record the license inmanifest.yaml(license_type,license) and proceed. Never hard-block on license for a standalone / external recipe. When the license is non-commercial (CC-BY-NC, RAIL, "research only") or copyleft (AGPL, GPL), warn the user in the final summary so they can make the call — but keep authoring. Dataset licenses get the same treatment: record and warn, never block. - Task type — model card / config / tags. Match against classification, detection, segmentation, depth, pose, super-res, ASR, TTS, LLM, VLM.
- Reference forward-pass snippet — from the model card / README / PyPI docs. Use for numerical verification against
test_task. - Input spec —
preprocessor_config.json/config.json/weights.transforms(). - Sample input — from the card, or a generic one for the task.
- Multi-component structure — obvious encoder/decoder splits, autoregressive / iterative pipelines → CollectionModel (see below).
- Similar repo model — closest sibling to inherit from. Copy its patterns. The sibling's location is not what you copy — the new recipe still goes in the user's CWD as `<model_id>/`, not under `src/qai_hub_models/models/`.
- Precisions — always
supported_precisions: [float]. Never declarew8a8/w8a16here — that'sadd-quantization.
Ask the user only for ambiguous task/architecture or derivations that genuinely failed. Batch questions into one message. Do NOT ask upfront for license acknowledgement — surface any concerns in the closing summary instead.
At the end, announce what you inferred (license, task, precisions, sibling reference) so the user has one place to correct anything.
Shell rules
- Never inline Python with comments in bash — write to
${TMPDIR:-/tmp}/claude/<name>.py, run withpython. - Never chain shell commands — no
&&,||,;, pipes, redirects. Each command is a separate Bash call. - Long-running jobs (compile/profile, 5–15 min) go into a script file and run as a background subagent task.
Design rules
- Modify PyTorch to get the model onto device. Architecture changes are fine if they preserve numerics.
- CPU-only in torch. No
.cuda(. Grep siblings for GPU-stripping tricks. - Upstream code always goes through `external_repos:`. Not pip install, not
SourceAsRoot, not inlined intomodel.py. - Never `sys.modules` / monkey-patch torch to unpickle a checkpoint. With
external_repos:on the import path, whole-object pickles resolve against the real upstream module. Extractstate_dictinsidefrom_pretrainedand discard the rest. If you're writingsys.modules['models'] = ...or patchingSGD.__setstate__, stop. When a pickle references classes by their upstream-relative name (yolov6/v7-style),repo_in_sys_pathfromqai_hub_models.utils.asset_loaderscan help — see "Weights" below. - Merge pre/postprocessing into the model where possible — simpler on-device story.
- Follow existing I/O conventions for the task family.
- Reuse `qai_hub_models/utils/` before writing preprocessing from scratch. Look at similar models'
app.pyimports.
External Repos
Upstream code (backbones, custom layers, tokenizers) is declared in manifest.yaml and shallow-cloned into <model_folder>/external_repos/<repo_name>/ at import time. Replaces SourceAsRoot and pip-installed forks.
Manifest
external_repos:<repo_name>:repo_url: https://github.com/<owner>/<repo>.gitcommit_sha: <full 40-char SHA> # pin to a commit, never branch/tagpatches_filename: <repo_name>_patches.diff # optional
<repo_name> is both the folder name under external_repos/ and the Python import path segment. Pick something short that matches the upstream project. Look at existing recipes in src/qai_hub_models/models/ for shape.
Bootstrap
external_repos/__init__.py is generated — do NOT hand-write. Run python qai_hub_models/scripts/run_codegen.py -m <model_id> (in-tree) or qai-hub-models generate-files <path> (standalone). Uses filelock for concurrent-safe imports, populates EXTERNAL_REPO_PATHS.
Importing upstream code
Always package-relative:
from .external_repos.<repo_name>.<upstream_module> import <SomeClass>from .external_repos import EXTERNAL_REPO_PATHS # if you need on-disk paths
The recipe folder is imported by its top-level folder name when standalone, and as qai_hub_models.models.<id> when inside the installed package tree — the package-relative form works in both cases.
Never from qai_hub_models.models.<id>.external_repos.<repo_name>... in a standalone recipe — that path only exists inside the installed package, and standalone folders are imported by their folder name, so it doesn't resolve.
The first from .external_repos.<repo_name>... import triggers the fetch, patch, and setup automatically.
If you need the on-disk path to the fetched repo (e.g. to load a bundled Hydra config or supplemental YAML), import the auto-populated dict:
from .external_repos import EXTERNAL_REPO_PATHSconfig_dir = EXTERNAL_REPO_PATHS["<repo_name>"] / "configs"
Patches
Fix broken upstream (broken imports, GPU-only code paths, hard-coded absolute paths in the upstream loader, non-tracing ops, optional deps that make a module unimportable) via a <repo_name>_patches.diff file next to external_repos/__init__.py, and set patches_filename: in the manifest.
Generate the diff by hand-editing the cloned repo, then git diff > <repo>_patches.diff — a standard git diff with one diff --git a/… b/… block per file changed. Applied once at fetch time, so subsequent imports see the patched code.
Always prefer patches over runtime find_replace_in_repo (legacy runtime helper). Patches are declarative, reviewable in the PR diff, and applied once instead of every import.
Weights
Weights are separate from code. CachedWebModelAsset + load_torch:
DEFAULT_WEIGHTS = CachedWebModelAsset.from_asset_store(MODEL_ID, MODEL_ASSET_VERSION, "weights.pt")@classmethoddef from_pretrained(cls, weights=DEFAULT_WEIGHTS):checkpoint = load_torch(weights)net = SourceModel()net.load_state_dict(checkpoint)return cls(net)
Whole-object pickle from upstream (torch.save({'model': DataParallel(...), 'optimizer': SGD(...)}, ...))? The resolver finds upstream classes via external_repos: — extract state_dict and discard the rest:
checkpoint = load_torch(weights)state_dict = checkpoint['model'].module.state_dict()net.load_state_dict(state_dict)
No sys.modules hacks. If the upstream doesn't ship a state_dict at all, prefer finding a state_dict-only weights source over unpickling optimizer state — if none exists, add a small patch that saves a clean state_dict once and reference that.
If the pickle references classes by their upstream-relative name (yolov6/v7 style), qai_hub_models.utils.asset_loaders.repo_in_sys_path is a context manager that temporarily adds a directory to sys.path around torch.load — useful here, and the correct alternative to sys.modules[...] = ....
Avoid Google Drive links. They gate on interactive consent screens, throttle on quota, expire, and break in CI. If the upstream README's only checkpoint pointer is a Google Drive URL, treat it as "no downloadable weights" and follow the fallback order below.
No downloadable weights (upstream is a training recipe with no checkpoint URL — README says "run python train.py", or only offers a Google Drive link)? In order of preference:
- Find a mirror on a direct-download host — HuggingFace, GitHub Releases, PapersWithCode, or a downstream fork. HuggingFace is best (stable, CDN-backed, no consent screen); GitHub Releases is a solid second (raw
https://github.com/.../releases/download/...URLs work withCachedWebModelAsset). - Point at a user-supplied checkpoint — make
from_pretrainedrequire an explicitcheckpointarg (no default). Document it in the manifest description and demo--help. - Upload once, cache forever — if you have redistribution permission, upload to the shared asset store under a stable
MODEL_ASSET_VERSION. One-time out-of-band step by an internal maintainer.
Never run train.py at import time or synthesize weights — the recipe will fail on every fresh install. Never hard-code a Google Drive link as the default weights URL.
Skip external_repos only when the architecture is fully available from torch.nn primitives and torchvision.models / transformers — i.e. no upstream research repo is involved (a common example: a stock classifier torchvision ships directly; the constructor gives you the whole thing).
If you're tempted to reimplement a model architecture inline in model.py (backbone + block classes when an upstream repo already has them), stop and use external_repos:. Inlining is banned — it duplicates upstream, drifts silently, and can create license issues.
Terminology
- model_id — folder name, lowercase snake_case.
- model_name — display name in manifest, dashes, no spaces / underscores.
- template — reusable base under
qai_hub_models/models/templates/<name>/, declared inmanifest.yamltemplates:and resolved transitively.
Folder layout
Standalone folder contains everything the CLI needs. No generated files ship in the folder.
Required
- `__init__.py` — exports
App,Model,MODEL_ID. Copy any sibling's shape.
- `model.py` —
BaseModelsubclass.
ALL IMPORTS AT THE TOP. Every import at the top of the file, immediately after the license header. No inline imports inside functions/methods — not for "heavy" imports, not for "only used in one place". Only exception: circular-import avoidance. This is the single most-violated rule in the repo — check before validating.
❌ def from_pretrained(cls): from .external_repos... import UpstreamModel — NO ✅ from .external_repos... import UpstreamModel at the top of the file.
Required methods:
from_pretrained(cls)— classmethod to load pretrained weights; all args have defaults.get_input_spec()— staticmethod,InputSpecdict of{input_name: (shape, dtype)}.get_output_spec()— list of outputs.
Optional overrides:
_sample_inputs_impl()— real sample inputs instead of random data.get_hub_compile_options()/get_hub_profile_options()/get_hub_quantize_options()— custom AI Hub flags.get_hub_quantize_options()is required when the model needs a specific range scheme (e.g.--range_scheme min_max). Grep existing detectors for a working pattern.get_unsupported_reason()— mark device attributes (e.g. Hexagon version) that can't be supported.get_eval_dataset_classes()— classmethod,Sequence[type[BaseDataset]]. Default().get_evaluator()— instance method, returns aBaseEvaluatorinstance (not a class). Bind hyperparameters (score/IoU thresholds, class count) inside — evaluator is instantiated once and reused.get_calibration_dataset_cls()— instance method,type[BaseDataset] | None. DefaultNone.get_hub_litemp_percentage(precision)— percentage (0–100) of sensitive layers to keep in higher precision for mixed-precision quantization (e.g.w8a8_mixed_int16).
`MODEL_ASSET_VERSION` — models shipping test assets (sample inputs, expected outputs) declare MODEL_ASSET_VERSION = 1 at module scope. demo.py and test.py reference it when building asset paths. Bump when uploading a new asset set.
`SerializationSettings(use_pt2=False, check_trace=False)` — pass when your model uses a template base whose default trace-check is too strict. Some detector-family templates require this.
- `app.py` — end-to-end app with pre/postprocessing.
Appclass takes aCallable(works with torch or on-device inference), exposespredict(). Useapp_to_net_image_inputs()fromimage_processing.pyfor standard image input conversion;draw.py/bounding_box_processing.pyfor postprocessing overlays. Skip this file when a template's App matches your preprocessing exactly — re-export the template's app class from__init__.py. Before reusing a template's App, check itsforward()/ preprocessing — templates often bake in a specific normalization or channel order. If your preprocessing diverges (different mean/std,do_normalize=False, different resize/interpolation), write your ownapp.pyor subclass and override. New-task-shaped models with no template match — write from scratch.
- `demo.py` — parses args (
model_from_cli_args()fromargs.py), loads sample input (load_image()fromasset_loaders.py), runs the app, displays / saves output (display_or_save_image()).
- `test.py` —
test_task(PyTorch accuracy on a sample input),test_demo(demo runs without error).
- `manifest.yaml` — metadata + build/export options + dependency graph, one unified schema.
- Website metadata (
name,id,headline,description,use_case,domain,license_type,research_paper,source_repo, …) alongside build/export (supported_precisions,is_collection_model,use_pt2,has_on_target_demo, …). - Never author `technical_details` by hand. Populated by
python qai_hub_models/scripts/autofill_manifest_yaml.py -m <model_id>after the first successful compile. Omit the key entirely at onboarding time — even partial values drift. - Never set `status:` for standalone / external recipes. The field is reserved for in-tree (see
onboard-internal). Omit it — notstatus: unset, notstatus: draft, no key at all. use_case,tags,domain,license_typeare validated enums — seeqai_hub_models/configs/_info_yaml_enums.py.- Dependency-graph fields:
templates:— template bases underqai_hub_models/models/templates/<name>/, transitive deps resolved automatically.datasets:— datasets underqai_hub_models/datasets/<name>/.models:— direct deps on other model folders.external_repos:— upstream research repos (see above). Primary way to pull in upstream code, not pip install.pre_pip_install_commands:/post_pip_install_commands:— free-formpip …wrapped asPipCommand, gate withmachine: cpu/machine: gpu. Reserve for genuine env-level needs. Never for research repos — those go inexternal_repos:.
Optional
- `requirements.txt` — model-specific pip deps, pinned versions required.
Not part of the folder
export.py, evaluate.py, perf.yaml, numerics.yaml, release-assets.yaml — not authored, not required. Export / evaluate are CLI-driven; perf and numerics are CI-populated.
README.md is auto-generated by qai-hub-models generate-files. For external recipes (with status: omitted), the CLI renders an external-flavored README whose commands take the recipe folder path — safe to publish alongside the recipe (e.g. on HuggingFace) without misleading a reader.
Architecture patterns
Base classes (qai_hub_models/utils/base_model.py): BaseModel (standard), CollectionModel (multi-component), PrecompiledWorkbenchModel (pre-compiled assets only).
Collection Models
Encoder + decoder splits, autoregressive decoding, iterative refinement — the top-level class inherits from WorkbenchModelCollection (not BaseModel); each component is its own BaseModel subclass; super().__init__({"encoder": encoder, "decoder": decoder}) registers them. CLI compiles each to a separate artifact.
- Manifest:
is_collection_model: true. - App: takes components as separate callables —
App(encoder, decoder, version), not a singleCallable. - Demo: use
demo_model_components_from_cli_args(notmodel_from_cli_args). - Copy the closest sibling in the same task family. Details, KV cache handling, and templates in
.claude/docs/collection-models.md.
Templates
qai_hub_models/models/templates/<name>/. Reusable base for a family of models (classifiers, detectors, segmenters, encoder-decoder pairs, etc.). When your model lists a template in its templates: field, the CLI walks the template's own dependencies and installs everything in leaf-first order.
Any folder under templates/ listed in templates: is valid — `manifest.yaml` is NOT required. Two flavors:
- With `manifest.yaml` — declares transitive dependencies (other templates, datasets) that the CLI walks; may also carry an
App,Model, and manifest-metadata inheritance. - With only `requirements.txt` — pure "code + pip deps" module. CLI walker handles this natively: template node yields empty transitive deps and just installs its
requirements.txt.
Do NOT reject a template because it lacks a manifest — list it in your recipe's templates: and the CLI will install its requirements.txt. grep -l '- <template_name>' src/qai_hub_models/models/*/manifest.yaml shows how neighbors reference it.
Prefer inheriting from a template over rebuilding preprocessing / evaluators from scratch. Browse qai_hub_models/models/templates/ for what's available.
Key utilities
Browse before writing preprocessing from scratch. The fastest way to find the right function is to look at a similar model's app.py / demo.py imports.
- `image_processing.py` — image → tensor conversion and back:
app_to_net_image_inputs()— PIL/numpy → model input tensors inapp.py; handles batching, channel order,[0,1]normalization.preprocess_PIL_image()— PIL → float32[0,1]tensor.normalize_image_torchvision()— ImageNet mean/std normalization.pil_resize_pad()/pil_undo_resize_pad()— aspect-preserving resize with padding, and inverse.torch_tensor_to_PIL_image()— model output → PIL.- `args.py` — demo CLI:
get_model_cli_parser()/model_from_cli_args()— standard demo arg parsing and model instantiation.get_on_device_demo_parser()/validate_on_device_demo_args()— on-device demo args.add_output_dir_arg()— adds--output-dirto any parser.- `asset_loaders.py` — asset fetching:
CachedWebModelAsset— download + cache weights from URLs.load_image()— load a PIL image from URL or local path.SourceAsRoot— legacy — do not use for new recipes. Making an upstream GitHub repo importable now goes throughexternal_repos:.- `display.py`, `draw.py` — demo output:
display_or_save_image()— show in GUI/notebook or save to disk (used in everydemo.py).draw_box_from_xyxy(),draw_points(),create_color_map()— overlays for detection / pose / segmentation.- `compare.py` —
compute_psnr()between two arrays (denoising / super-res evaluators). - `bounding_box_processing.py` —
batched_nms(),get_iou(),box_xywh_to_xyxy()for detection postprocessing.
Workflow
- Create
<model_id>/in the user's CWD. Standalone recipes don't live inside the installed package. - Author
__init__.py,model.py,app.py,demo.py,test.py,manifest.yaml. - Add
requirements.txtif needed. - Declare deps in
manifest.yaml:templates:,datasets:,external_repos:, pre/post pip commands. qai-hub-models generate-files <model_id>— writesREADME.mdandexternal_repos/__init__.py.qai-hub-models install <model_id>, then runvalidate(see below).
CLI
Every command takes a <target>: either a folder name or path (my_model) or a bare model id (installed under qai_hub_models). Display names are not accepted. Ids are lowercase snake_case.
| Command | What it does | |
|---|---|---|
qai-hub-models install <target> | Walks the dep graph, installs each node's requirements.txt and pre/post pip commands leaf-first. --dry-run previews. | |
qai-hub-models generate-files <target> | Writes auto-generated files (README.md, external_repos/__init__.py). Run after editing manifest. | |
qai-hub-models export <target> [args...] | Compile + profile on AI Hub. Flags: --target-runtime, --device, --precision, … --help for the full list. | |
qai-hub-models evaluate <target> [args...] | Accuracy eval using get_evaluator() and get_eval_dataset_classes(). | |
python -m <target_folder_name>.demo | Runs demo.py. There's no qai-hub-models demo subcommand — invoke the module directly, from the folder containing the recipe folder. |
Legacy python -m qai_hub_models.models.<id>.export / .evaluate paths are being phased out.
Authoring correctness — qai-hub-models validate
qai-hub-models validate <model_id> is the authoring gate. Not "done" until N passed, 0 failed. Exits nonzero on any FAIL. WARN rows are informational (missing demo / evaluator etc.) and don't need clearing.
Categories:
- Folder shape — required:
__init__.py,model.py,manifest.yaml; optional:demo.py,test.py.__init__.pyexportsMODEL_IDandModel;Appoptional.external_repos/__init__.pyrequired when manifest declaresexternal_repos:. Nofrom qai_hub_models.models.<id>self-imports. - Manifest — universal Pydantic schema (
idshape,license_type↔licenseURL, python-version reason coupling,orchestrator_runtimessanity, LLM ↔llm_detailsconsistency, no mixed precision on non-collection models).idmatches folder name. Standalone recipes omitstatus:. Everyexternal_repos.<name>.commit_shais a full 40-char SHA. - Requirements — every entry version-pinned, no conflict with base
global_requirements.txt. Same forpre_pip_install_commands/post_pip_install_commands. - Model code —
perform_runtime_model_validation(I/O naming, mixed-precision litemp, eval-dataset wiring). Torchfrom_pretrained()+ forward pass produces the expected number of outputs, all finite, matching declared shapes, deterministic under fixed seed.model.py/app.pyhave no unguarded.cuda(.model.pyhas nosys.modules[...]monkey-patches. All imports at the top of every file — the single most-violated rule. - App — if declared,
App(model)orApp(**model.components)instantiates cleanly and exposespredict/__call__. - Datasets / evaluator — WARN if
get_eval_dataset_classes()empty (still valid forexport). - URL reachability — HEAD-check
license,source_repo,research_paper, everyexternal_repos.<name>.repo_url. Cached 24h.
Pre-validate self-check
Before your first validate run, verify:
- All imports at the top? Open each
.py— noimportinside a function/method. Most-violated rule. - No self-referential imports? No
from qai_hub_models.models.<id>...in a standalone recipe. Use relative (.external_repos,.templates). - External-repos bootstrap present? If manifest declares
external_repos:,external_repos/__init__.pyexists —qai-hub-models generate-files <path>writes it. - No `status:` in manifest.yaml?
grep '^status:' manifest.yamlreturns nothing. - `supported_precisions` is `[float]`? No
w8a8/w8a16here — that'sadd-quantization. - No `technical_details:` in manifest.yaml?
grep '^technical_details:' manifest.yamlreturns nothing. - Dataset `__init__` takes `input_spec`? Never raw
input_height/input_width/image_size/resolution. - Dataset + evaluator wired? Unless the user declined or the model has no accuracy notion:
model.pyimplementsget_evaluator()/get_eval_dataset_classes()/get_calibration_dataset_cls(). If not, wire it now — do NOT defer toadd-quantization, which only flips the precision list.
Iterate-until-green
Read each FAIL row's detail, apply a targeted fix, rerun with --no-install. Repeat until 0 failed.
Progress rule (anti-runaway): two consecutive attempts on the same FAIL row producing the same detail → stop. Usually means an incompatible base-pin conflict, a genuinely broken upstream URL, or a decision only the user can make. Report what was tried and the persistent row.
No bypassing. No commenting out check code, no # noqa in the recipe, no stub swaps. If a check only passes with a suppression, that IS the dead-end — report it.
What validate does NOT check
- Numerical parity with the reference. Forward pass confirms shapes and no-crash, not correctness. Run the reference on a fixed input and compare against your
model.py. - License classification. The validator confirms
license_typematches itslicenseURL. Any license is accepted — document it underlicense_type/licenseand warn the user in the final summary if it's non-commercial or copyleft. Do not hard-block. Same treatment applies to dataset licenses. - Qualitative demo correctness. Classifier picks the right class, detector boxes align, mask matches the scene, restored image is visibly cleaner.
- `technical_details`. Autofilled post-compile — do not hand-write.
Operational validation of a written recipe (install cold+warm, pytest, pre-commit, export, on-device evaluate) is out of scope for this skill — this skill teaches authoring, not operating. See /ai-hub-models:validate-on-device.
Preprocessing verification
Verify the model's expected input range before writing any code. The single most common source of silently-wrong results — model will trace, export, and produce plausible-looking output with the wrong normalization.
- Read
weights.transforms()source or the official demo code. - Common ranges:
[0, 1],[-1, 1](mean=0.5, std=0.5),[0, 255], ImageNet mean/std. - Verify input resolution against the model's default config.
- Image inputs to the model at the App boundary are `[0, 1]`. Convert to native format inside
forward(). - After implementing, numerically compare your output against the reference on the same input. "Looks reasonable" is not a check.
On-device deployment
Compilation / profiling / on-device eval are NOT part of this skill. Once qai-hub-models validate reports 0 failed, hand off to /ai-hub-models:validate-on-device.
Iterative/recurrent models likely need CollectionModel up front — profiling failures downstream will confirm, but the pattern belongs in the recipe from day one. See .claude/docs/collection-models.md.
Adding a dataset + evaluator
Do this during onboarding as the default path — see intake item 2. Skip only when the user explicitly declines or the model has no accuracy notion. This is NOT the quantization step — quantization is a separate skill (add-quantization) and never author w8a8 / w8a16 here. The reason to wire the dataset+evaluator now is torch accuracy verification against a reference, which is orthogonal to quantization.
Dataset
Two acceptable homes for the dataset class, both inheriting BaseDataset:
- Shared — under
qai_hub_models/datasets/<name>/. Use this when another recipe is likely to reuse the dataset. List it in the model'smanifest.yamlunderdatasets:so the CLI resolves any deps it declares. - Local — as
dataset.pyin the recipe folder. Fine for model-specific datasets. No manifest entry needed.
Either way:
- `__init__` takes `input_spec: InputSpec | None = None` — never raw
input_height/input_width/image_size/resolution. Derive width/height from the tensor shape ininput_spec. WhenNone, fall back to a defaultInputSpecinside__init__and extract dims from that. Hard rule — raw dim parameters diverge from the model's declared shape and break calibration/eval, which passesinput_specfrom the model automatically. - Do NOT override `configure()` unless the dataset genuinely needs external setup (files behind a license wall, large archives requiring manual download). Default raises a helpful
NotImplementedErrorfor auto-downloadable data. Aconfigure()that just forwards to__init__or re-runs_download_data()'s work is boilerplate.
Evaluator
Under the model folder (single-consumer) or qai_hub_models/models/templates/<name>/ (multi-consumer). Inherit the base evaluator; implement task-appropriate metrics.
Model.py wiring
@classmethoddef get_eval_dataset_classes(cls) -> Sequence[type[BaseDataset]]:return [YourDataset]def get_calibration_dataset_cls(self) -> type[BaseDataset] | None:return YourCalibrationDatasetdef get_evaluator(self) -> BaseEvaluator:return YourEvaluator(num_classes=80, score_threshold=0.05)
get_evaluator() returns an instance with hyperparameters bound — evaluator is instantiated once and reused across batches.
Reinstall (deps may have changed): qai-hub-models install <target>. Torch-verify: qai-hub-models evaluate <target> --precision float --num-samples 100 --skip-device-accuracy.
See .claude/docs/onboarding/datasets-and-evaluators.md for extended guidance.
Model assets
Three patterns, pick whichever fits:
A. Shared asset store (what most repo models do). Fetches on demand via CachedWebModelAsset.from_asset_store(MODEL_ID, MODEL_ASSET_VERSION, "test_images/input.jpg"), resolving to a URL under Qualcomm's public S3 bucket. Weights and test fixtures are uploaded there separately (out of scope for this skill); the model folder stays tiny. Declare MODEL_ASSET_VERSION = 1 at module scope; reference assets by relative path. Bump when uploading a new asset set — old versions stay pinned.
B. Ship inside the folder (fine for standalone recipes — folder is self-contained on a fresh checkout without depending on an asset-store upload). Put in <model_folder>/assets/, load by relative path in from_pretrained() / demo.py / test.py. Small fixtures (test images, tiny reference outputs) commit directly; large binaries (multi-MB checkpoints, video) that can't be regenerated from a public source → host at a public URL and load via CachedWebModelAsset URL form (not from_asset_store). Do NOT invent a MODEL_ASSET_VERSION here — the constant only makes sense with from_asset_store.
C. Reuse a sibling's fixture (bootstrap shortcut). For a new detector, any detection image works; new classifier, any classification image. Instead of uploading a fresh asset, point at the sibling's existing fixture:
DEMO_IMAGE = CachedWebModelAsset.from_asset_store("<sibling_id>", <version>, "<sibling_filename>")
Prefer C over uploading a fresh asset for the initial recipe. Swap in a follow-up.
Requirements
- Pinned exact versions in
requirements.txt(e.g.torch==2.0.1). - Check
global_requirements.txtbefore adding new deps. - Upstream research repos →
external_repos:, neverrequirements.txtor pip commands. pre_pip_install_commands/post_pip_install_commandsonly for env-level needs (CPU vs GPU wheels,pip uninstallbefore an incompatible upgrade). Gate withmachine: cpu/machine: gpu.