#!/usr/bin/env python3 """Install and verify the locked NPU-P0 workload reference assets.""" from __future__ import annotations import argparse import json import subprocess import sys import tarfile from pathlib import Path ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from scripts.dependency_lock import archive, source # noqa: E402 from scripts.setup_helpers import ( # noqa: E402 atomic_write, download_file_from_urls, ensure_git_repo, sha256, ) MLCOMMONS_TINY = "apu_mlperf_tiny" KWS_MFCC = "apu_kws_mfcc" VWW_DATASET = "npu_vww_dataset" ORACLE_SOURCES = ("apu_tensorflow", "apu_gemmlowp") KWS_MODEL = Path("benchmark/training/keyword_spotting/trained_models/kws_ref_model.tflite") KWS_MODEL_SHA256 = "aeea436800704fce17b17292e4412630ad856e9d777c044c64ef748a880bd0ae" VWW_MODEL = Path("benchmark/training/visual_wake_words/trained_models/vww_96_int8.tflite") VWW_MODEL_SHA256 = "597a384c8c2c8a1276f04702f25013b7838f2f814f1ca7c174d295b73e3d6b7b" VWW_LABELS = Path("benchmark/evaluation/datasets/vww01/y_labels.csv") KWS_FEATURES = Path("datasets/kws01") KWS_FEATURE_COUNT = 1000 VWW_CORPUS_COUNT = 1000 VWW_IMAGE_BYTES = 96 * 96 * 3 VWW_CLASS_DIRS = {1: "person", 0: "non_person"} VWW_CORPUS_TSV = ROOT / "docs/ip/npu-vww-corpus.tsv" KWS_MANIFEST = ROOT / "docs/ip/npu-kws-manifest.json" def tiny_root() -> Path: return ROOT / source(MLCOMMONS_TINY)["destination"] def mfcc_root() -> Path: return ROOT / source(KWS_MFCC)["destination"] def vww_corpus_root() -> Path: return ROOT / ".cache/retrosoc/sources/npu-vww-corpus" def vww_label_entries(tiny: Path) -> list[tuple[str, int]]: labels_path = tiny / VWW_LABELS if not labels_path.is_file(): raise RuntimeError(f"missing locked VWW label index: {labels_path}") entries: list[tuple[str, int]] = [] for line in labels_path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line: continue name, _classes, label = [part.strip() for part in line.split(",")] if not name.endswith(".bin") or len(name) != 16 or not name[:12].isdigit(): raise RuntimeError(f"unexpected VWW label entry name: {name}") entries.append((name, int(label))) if len(entries) != VWW_CORPUS_COUNT: raise RuntimeError(f"VWW label index must list {VWW_CORPUS_COUNT} entries: {len(entries)}") if len({name for name, _ in entries}) != VWW_CORPUS_COUNT: raise RuntimeError("VWW label index contains duplicate entries") person = sum(1 for _, label in entries if label == 1) if person != VWW_CORPUS_COUNT // 2: raise RuntimeError(f"VWW label index must be 500/500 balanced: person={person}") if any(label not in VWW_CLASS_DIRS for _, label in entries): raise RuntimeError("VWW label index carries a label outside {0, 1}") return entries def verify_models(tiny: Path) -> dict[str, str]: records = {} for relative, expected in ((KWS_MODEL, KWS_MODEL_SHA256), (VWW_MODEL, VWW_MODEL_SHA256)): model = tiny / relative if not model.is_file() or sha256(model) != expected: raise RuntimeError(f"NPU locked model hash mismatch: {model}") records[relative.as_posix()] = expected return records def verify_kws_features(mfcc: Path) -> list[str]: features = mfcc / KWS_FEATURES names = sorted(path.name for path in features.glob("tst_*.bin")) if len(names) != KWS_FEATURE_COUNT: raise RuntimeError(f"KWS feature set must hold {KWS_FEATURE_COUNT} bins: {len(names)}") return names def _extract_vww_jpegs(archive_path: Path, corpus: Path, entries: list[tuple[str, int]]) -> None: wanted = {} for name, label in entries: stem = name[:12] member = f"vw_coco2014_96/{VWW_CLASS_DIRS[label]}/COCO_val2014_{stem}.jpg" wanted[member] = corpus / VWW_CLASS_DIRS[label] / f"COCO_val2014_{stem}.jpg" remaining = dict(wanted) with tarfile.open(archive_path, "r:gz") as bundle: for item in bundle: target = remaining.pop(item.name, None) if target is None: continue if not item.isfile(): raise RuntimeError(f"unexpected non-file member: {item.name}") payload = bundle.extractfile(item) if payload is None: raise RuntimeError(f"unreadable member: {item.name}") target.parent.mkdir(parents=True, exist_ok=True) target.write_bytes(payload.read()) if not remaining: break if remaining: missing = sorted(remaining)[:5] raise RuntimeError(f"VWW dataset misses {len(remaining)} inputs, first: {missing}") def _decode_vww_bins(corpus: Path, entries: list[tuple[str, int]]) -> str: try: from PIL import Image except ImportError as error: raise RuntimeError("Pillow is required to decode the VWW corpus") from error for name, label in entries: stem = name[:12] source_jpg = corpus / VWW_CLASS_DIRS[label] / f"COCO_val2014_{stem}.jpg" target = corpus / VWW_CLASS_DIRS[label] / name if target.is_file() and target.stat().st_size == VWW_IMAGE_BYTES: continue with Image.open(source_jpg) as image: if image.size != (96, 96) or image.mode != "RGB": raise RuntimeError(f"unexpected VWW image geometry: {source_jpg}") target.write_bytes(image.tobytes()) import PIL return PIL.__version__ def _git_revision(path: Path) -> str: return subprocess.check_output( ["git", "-C", str(path), "rev-parse", "HEAD"], text=True ).strip() def install(build_dir: Path, *, update: bool) -> dict[str, object]: tiny = tiny_root() mfcc = mfcc_root() for name in (MLCOMMONS_TINY, KWS_MFCC, *ORACLE_SOURCES): spec = source(name) ensure_git_repo(spec["url"], ROOT / spec["destination"], spec["revision"], update=update) dataset = archive(VWW_DATASET) archive_path = ROOT / dataset["destination"] urls = (dataset["url"], *dataset.get("fallback_urls", ())) download_file_from_urls( urls, archive_path, dataset["sha256"], update=update, timeout=600 ) models = verify_models(tiny) kws_names = verify_kws_features(mfcc) entries = vww_label_entries(tiny) corpus = vww_corpus_root() jpegs = [ corpus / VWW_CLASS_DIRS[label] / f"COCO_val2014_{name[:12]}.jpg" for name, label in entries ] if update or not all(path.is_file() for path in jpegs): _extract_vww_jpegs(archive_path, corpus, entries) pillow_version = _decode_vww_bins(corpus, entries) records: dict[str, object] = { "target": "npu-p0", "sources": { name: {"revision": source(name)["revision"], "destination": source(name)["destination"]} for name in (MLCOMMONS_TINY, KWS_MFCC, *ORACLE_SOURCES) }, "archives": {VWW_DATASET: dataset["sha256"]}, "models": models, "kws_feature_count": len(kws_names), "vww_corpus_count": len(entries), "pillow_version": pillow_version, } build_dir.mkdir(parents=True, exist_ok=True) atomic_write(build_dir / "provenance.json", json.dumps(records, indent=2, sort_keys=True) + "\n") return records def doctor() -> None: tiny = tiny_root() mfcc = mfcc_root() for name, path in ((MLCOMMONS_TINY, tiny), (KWS_MFCC, mfcc)): if not (path / ".git").is_dir(): raise RuntimeError(f"missing NPU managed source: {path}") revision = _git_revision(path) if revision != source(name)["revision"]: raise RuntimeError(f"NPU source revision mismatch for {name}: {revision}") for name in ORACLE_SOURCES: path = ROOT / source(name)["destination"] if not (path / ".git").is_dir() or _git_revision(path) != source(name)["revision"]: raise RuntimeError(f"missing or stale NPU oracle source: {name}") if subprocess.check_output(["git", "-C", str(path), "status", "--porcelain"], text=True).strip(): raise RuntimeError(f"dirty NPU oracle source: {name}") dataset = archive(VWW_DATASET) archive_path = ROOT / dataset["destination"] if not archive_path.is_file() or sha256(archive_path) != dataset["sha256"]: raise RuntimeError(f"NPU VWW dataset archive mismatch: {archive_path}") verify_models(tiny) verify_kws_features(mfcc) entries = vww_label_entries(tiny) corpus = vww_corpus_root() for name, label in entries: stem = name[:12] for candidate in ( corpus / VWW_CLASS_DIRS[label] / f"COCO_val2014_{stem}.jpg", corpus / VWW_CLASS_DIRS[label] / name, ): if not candidate.is_file(): raise RuntimeError(f"missing VWW corpus file: {candidate}") if KWS_MANIFEST.is_file(): manifest = json.loads(KWS_MANIFEST.read_text(encoding="utf-8")) inputs = manifest.get("inputs", {}) if len(inputs) != KWS_FEATURE_COUNT: raise RuntimeError("NPU KWS manifest input count mismatch") for item in inputs: feature = mfcc / KWS_FEATURES / item["name"] if sha256(feature) != item["sha256"]: raise RuntimeError(f"NPU KWS feature hash mismatch: {item['name']}") if VWW_CORPUS_TSV.is_file(): for line in VWW_CORPUS_TSV.read_text(encoding="utf-8").splitlines(): index, name, jpg, _label, jpg_sha256, bin_sha256 = line.split("\t") del index jpg_path = corpus / jpg bin_path = jpg_path.parent / name if sha256(jpg_path) != jpg_sha256: raise RuntimeError(f"NPU VWW JPEG hash mismatch: {jpg}") if sha256(bin_path) != bin_sha256: raise RuntimeError(f"NPU VWW bin hash mismatch: {name}") def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--build-dir", type=Path, required=True) parser.add_argument("--doctor", action="store_true") parser.add_argument("--update", action="store_true") args = parser.parse_args() if args.doctor: doctor() print("NPU-P0 locked references verified") return 0 records = install(args.build_dir.resolve(), update=args.update) print(f"NPU-P0 KWS/VWW references: {args.build_dir.resolve() / 'provenance.json'}") print(f"KWS features: {records['kws_feature_count']}, VWW corpus: {records['vww_corpus_count']}") return 0 if __name__ == "__main__": raise SystemExit(main())