#!/usr/bin/env bash # download-frank-dragoon-npu.sh — stage the NPU half of frank-dragoon: the # vendor-precompiled AIE programs halogen-flash-server runs on the XDNA2 NPU # when HALOGEN_NPU_MODELS names them. These are NOT model weights you can # quantize yourself: XDNA2 executes only programs compiled by AMD's AIE # compiler, so the only source is peonist-ai's per-model NPU repo, and the # engine checks every file's size and sha256 against its own catalog at boot # (/opt/halogen/npu/models.txt inside the image) and refuses to load a model # whose files do not match. # # The manifest below IS that catalog block (dumped from the pinned 0.18.20 # image on 2026-10-30; the 0.16.2 block this replaced named different device # programs — 10 `u*.elf` and a 5,086,496 B consts.hnpb — so a version bump # re-fetches the set or the entrypoint refuses the boot), so the files land in the layout the entrypoint # expects — //devices/*, //.hnpw and # //tokenizer/tokenizer.json — and the endpoint can keep # /models read-only and open no outbound connection at boot (HALOGEN_DOWNLOAD # stays unset; the vendor's own README: with it unset the container makes no # outbound connections at all). # # The fetch law, same as every sibling script: curl -f (an access error must # not become a "successful" 153-byte artifact), the exact byte count and the # sha256 from the manifest, --verify-only to re-check a staged set. # # Adding a second NPU model: read its block out of the image's catalog and add # a case below — # podman exec frank-dragoon-halogen grep "^file " /opt/halogen/npu/models.txt # podman exec frank-dragoon-halogen grep "^model " /opt/halogen/npu/models.txt # and add the model id to HALOGEN_NPU_MODELS in frank-dragoon.env. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ENV_FILE="${FRANK_DRAGOON_ENV_FILE:-$SCRIPT_DIR/frank-dragoon.env}" # The env file owns the staging path (it must sit inside the payload dir, which # is the container's only /models mount); an exported FRANK_DRAGOON_NPU_DIR wins # so the bats seam can stage somewhere disposable. DEST="${FRANK_DRAGOON_NPU_DIR:-}" if [ -z "$DEST" ] && [ -f "$ENV_FILE" ]; then DEST="$(sed -nE 's/^FRANK_DRAGOON_NPU_DIR=(.*)$/\1/p' "$ENV_FILE" | tail -1)" fi : "${DEST:?FRANK_DRAGOON_NPU_DIR is not set and $ENV_FILE names no FRANK_DRAGOON_NPU_DIR}" HF="https://huggingface.co" MODELS="${HALOGEN_NPU_MODELS:-}" if [ -z "$MODELS" ] && [ -f "$ENV_FILE" ]; then MODELS="$(sed -nE 's/^HALOGEN_NPU_MODELS=(.*)$/\1/p' "$ENV_FILE" | tail -1)" fi VERIFY_ONLY=0 case "${1:-}" in --verify-only) VERIFY_ONLY=1 ;; "") ;; *) echo "usage: ${0##*/} [--verify-only] (models come from HALOGEN_NPU_MODELS)" >&2; exit 2 ;; esac [ -n "$MODELS" ] || { echo "no NPU model named — set HALOGEN_NPU_MODELS or run with it in the environment" >&2; exit 1; } # manifest -> repo revision + one "path bytes sha256" per file. manifest() { case "$1" in qwen3-embedding-0.6b) NPU_REPO="peonist-ai/halogen-npu-qwen3-embedding-0.6b" NPU_REV="8fd44c9f18861618a509bea75c51ff8a37e3bd5d" FILES=( "devices/NOTICE.md 3248 0f267b3d2523ddf14997ee58660a492a9eaf10ccf6b08bf19dfdc85b9a478b91" "devices/consts.hnpb 8495512 5af7c1f2f24d45a9054872bba882461cf7f6ffda832e785b700643d6467ad88b" "devices/devices.hnpm 35465 85d870f6b900de1829659955f0dc701d57984580f5e5ec251add5d6969f03e3d" "devices/u0.elf 2975008 f74df12eb8c79e4e90cbedcc1dd8f142219772d400e3a142fde8a95378b105eb" "devices/u1.elf 3266848 ac5a3f89f934951b315b62ab53e1d0a74dbe6396134192641c405d08da332754" "devices/u2.elf 4015328 5e4f0d3342f262816289866d7e8c966f56a9f938c2e12e7341718cc7312aa480" "devices/u3.elf 4326976 94cf5d269fa286d91c26bf4654cedf101c047d23c4495b74c9c129cdc33b210f" "devices/u4.elf 4953792 cbd2796050d4c675f5cb3da117111087d510173e26f262f1b36aab66dd49dc91" "devices/u5.elf 4940112 7bf6a4470546bb888da02af395c07d92a24fb6d43aef27eb2151b3a9eda19e4d" "devices/u6.elf 6174544 10b9d50b635993e1f9c23775c0b94901d922c1e433cd10910e9f97174235f5ac" "devices/u7.elf 2983776 e0b06063b7379d05c85b3f435b819d96fda1223d8025d6d15958854b5711ace9" "devices/u8.elf 3298656 3bad204f782a68b97e27c1172530b13d7fbb0b495fa95f9504530a1048560644" "devices/u9.elf 2983776 875e313280358ce116e2a51a29a2c42794b5a9fadb3d48db702c90d18d838c2c" "devices/u10.elf 3298656 ef2e0f399f149a5476955bcf233c6fa2e896fcef1f86ecb0d8113c952c41a8b2" "devices/u11.elf 3298656 68a2519eee2d6a61ccc4ca2007e3186d44c9eb4e81162d742fe2f1756ec5a528" "devices/u12.elf 3298656 2bdc6abfa58cac1d5795a3ef52fe8a9ab4e7c2a4792822d615499a913401f728" "devices/u13.elf 3298656 4a3ce14f7ba15987b9bcbb4a010083e8aef68cd87c20cbe43403a98263179fe2" "qwen3-embedding-0.6b.hnpw 813032248 b9f9901429df7b46ec17153aa3899f738adbea8e4356080da0c6e6586fb750d1" "tokenizer/tokenizer.json 11423705 def76fb086971c7867b829c23a26261e38d9d74e02139253b38aeb9df8b4b50a" ) ;; *) echo "unknown NPU model '$1' — add its catalog block to manifest() in this script" >&2 return 1 ;; esac } for MODEL in ${MODELS//,/ }; do manifest "$MODEL" || exit 1 echo "== $MODEL ($NPU_REPO@$NPU_REV) ==" for entry in "${FILES[@]}"; do read -r f want sha <<<"$entry" path="$DEST/$MODEL/$f" mkdir -p "$(dirname "$path")" if [ "$VERIFY_ONLY" -eq 0 ]; then if [ -f "$path" ] && [ "$(stat -c %s "$path")" = "$want" ]; then echo "HAVE $f ($want bytes) — skipping fetch" else echo "FETCH $f -> $path" # HF answers /resolve/ with a 302 to the Xet CDN and curl # DROPS the Range header across redirects, so resolve the # effective URL first and resume against the CDN (the sibling # 62 GiB fetch hit this on 2026-10-18). url=$(curl -sfIL -o /dev/null -w '%{url_effective}' "$HF/$NPU_REPO/resolve/$NPU_REV/$f") \ || { echo "cannot resolve $NPU_REPO/$f" >&2; exit 1; } curl -fL --retry 5 -C - -o "$path" "$url" fi fi [ -f "$path" ] || { echo "MISSING $path (run without --verify-only)" >&2; exit 1; } if [ -L "$path" ]; then echo "SYMLINK $path — the serve gate refuses symlinks; replace with a real file" >&2 exit 1 fi got="$(stat -c %s "$path")" [ "$got" = "$want" ] || { echo "SIZE $path: $got != $want" >&2; exit 1; } got="$(sha256sum "$path" | cut -d' ' -f1)" [ "$got" = "$sha" ] || { echo "SHA256 $path: $got != $sha" >&2; exit 1; } echo "OK $f ($want bytes, sha256)" done done echo "DOWNLOAD_DONE $DEST ($MODELS)"