A version bump re-cuts the device programs, so a staged file of a different size is stale rather than partial. `curl -C -` against it appended the new tail to the old bytes and produced the right BYTE COUNT with the wrong content — the sha256 gate caught it (consts.hnpb, 2026-10-30), and the failure reads as a corrupt program rather than a fetch fault. Resume only when the staged file is smaller than the manifest expects.
135 lines
7.4 KiB
Bash
Executable File
135 lines
7.4 KiB
Bash
Executable File
#!/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 — <dir>/<model-id>/devices/*, <dir>/<model-id>/<model-id>.hnpw and
|
|
# <dir>/<model-id>/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 <model-id> " /opt/halogen/npu/models.txt
|
|
# podman exec frank-dragoon-halogen grep "^model <model-id> " /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 <model-id> -> 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; }
|
|
# Resume ONLY into a genuinely partial file. A staged file of a
|
|
# DIFFERENT size is an artifact of another revision (a version
|
|
# bump re-cuts the device programs), and `-C -` against it
|
|
# appends the tail to the old bytes: the result is the right
|
|
# BYTE COUNT with the wrong content, which reads as a corrupt
|
|
# program, not as a fetch fault (measured 2026-10-30 on
|
|
# consts.hnpb). Fetch those from zero.
|
|
resume=()
|
|
if [ -f "$path" ] && [ "$(stat -c %s "$path")" -lt "$want" ]; then
|
|
resume=(-C -)
|
|
fi
|
|
curl -fL --retry 5 "${resume[@]+"${resume[@]}"}" -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)"
|