feat(frank-dragoon): run the XDNA2 NPU lane beside the flash model
halogen-flash-server already ships an NPU path on the pin we run: for each
model named in HALOGEN_NPU_MODELS it loads the vendor's precompiled AIE
programs (XDNA2 executes only AIE-compiled graphs) and answers them on
/v1/embeddings inside the SAME container behind port 7770. That gives the
fleet an in-house embedding endpoint without touching the iGPU trunk.
The lane is opt-in and one line wide: HALOGEN_NPU_MODELS blank is the
pre-lane endpoint byte for byte (no device, no mounts, no engine env).
The three host preconditions the vendor names are gated in the serve script
and mirrored in the deployer (the 2026-10-01 twin law), each refused by name:
- the staged model files. /models is read-only and HALOGEN_DOWNLOAD stays
unset so the container opens no outbound connection at boot, so
scripts/download-frank-dragoon-npu.sh stages them from the pinned
revision with the catalog's own byte counts and sha256s (the engine
re-checks both at boot);
- the distro XRT's libxrt_coreutil/libxrt_core/libxrt_driver_xdna, each
mounted twice — where the image looks for XRT and at its own path —
because those libraries find each other where they were installed and
an /opt/xilinx/xrt of symlinks dangles inside the container;
- the GPU fabric clock HELD. On Strix Halo the iGPU and the NPU co-running
hang the machine (and corrupt NPU results) while FCLK changes speed, and
halogen refuses to start in that state; it cannot read host sysfs from
inside, so the hold is proven host-side. dankclaw/host/ carries the
vendor's halogen-fabric-clock{,.service} verbatim at tag v0.16.2 (sha256
a3a4f7f4e0142e71… / 5f9f1cc4c85a1c02…); the deployer installs, enables
and gates it only while the lane is named, and releases the clock plus
drops the boot hook when it is not.
After health, the deployer's lane gate is a real embedding through
/v1/embeddings — a 200 on /health proves nothing about the NPU programs.
Planner: dankclaw/host/halogen-fabric-clock* re-dispatches frank-dragoon
(the deployer installs it); dankclaw/host/grub and the fetch script plan
nothing.
Seam: 11 new tests — the off arm really renders nothing lane-specific, the
on arm renders the device/mounts/env, every missing precondition refuses by
name, the deployer twins and the host unit shape are pinned.
Co-Authored-By: omp <omp@dankclaw>
This commit is contained in:
@@ -1337,6 +1337,17 @@ LLM_DRAGOON_VISION="${LLM_DRAGOON_VISION:-/home/dank/models/halogen-v2-abliterat
|
||||
LLM_DRAGOON_VISION_BYTES="${LLM_DRAGOON_VISION_BYTES:-897916416}"
|
||||
LLM_DRAGOON_TOK_DIR="${LLM_DRAGOON_TOK_DIR:-/home/dank/models/halogen-v2-abliterated/tokenizer}"
|
||||
LLM_DRAGOON_TOK_FILES="${LLM_DRAGOON_TOK_FILES:-tokenizer.json chat_template.jinja tokenizer_config.json merges.txt vocab.json generation_config.json}"
|
||||
# NPU lane (2026-10-19). HALOGEN_NPU_MODELS is the whole switch and the parity
|
||||
# twin of the env file's line: empty here AND in the env = the pre-lane
|
||||
# endpoint. The gates below mirror serve-frank-dragoon.sh, and the vendor's
|
||||
# fabric-clock host unit (dankclaw/host/, verbatim at tag v0.16.2) is installed
|
||||
# ONLY while the lane is named — on Strix Halo the iGPU and the NPU co-running
|
||||
# hang the machine while the fabric clock moves, so halogen refuses to boot with
|
||||
# the lane armed unless that clock is held.
|
||||
LLM_DRAGOON_NPU_MODELS="${LLM_DRAGOON_NPU_MODELS:-qwen3-embedding-0.6b}"
|
||||
LLM_DRAGOON_NPU_DIR="${LLM_DRAGOON_NPU_DIR:-/home/dank/models/halogen-npu}"
|
||||
LLM_DRAGOON_DEV_ACCEL="${LLM_DRAGOON_DEV_ACCEL:-/dev/accel/accel0}"
|
||||
LLM_DRAGOON_XRT_DIR="${LLM_DRAGOON_XRT_DIR:-/usr/lib/x86_64-linux-gnu}"
|
||||
|
||||
retire_halogen_dragoon_quadlet() {
|
||||
local trash="$HOME/.AGENTTRASH/20260919-orca-cutover/quadlet"
|
||||
@@ -1415,6 +1426,47 @@ deploy_llm_dragoon() {
|
||||
fi
|
||||
done
|
||||
|
||||
# --- NPU lane: the same three predicates the serve script checks ---------
|
||||
if [[ -n "$LLM_DRAGOON_NPU_MODELS" ]]; then
|
||||
if [[ ! -e "$LLM_DRAGOON_DEV_ACCEL" ]]; then
|
||||
echo "ERROR: NPU node $LLM_DRAGOON_DEV_ACCEL absent — amdxdna is not bound (is iommu=pt still on the boot line?)" >&2
|
||||
return 1
|
||||
fi
|
||||
for xl in libxrt_coreutil.so.2 libxrt_core.so.2 libxrt_driver_xdna.so.2; do
|
||||
if [[ ! -e "$LLM_DRAGOON_XRT_DIR/$xl" ]]; then
|
||||
echo "ERROR: XRT NPU library $LLM_DRAGOON_XRT_DIR/$xl absent (Ubuntu libxrt2 + libxrt-npu2)" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
for nm in ${LLM_DRAGOON_NPU_MODELS//,/ }; do
|
||||
if [[ ! -d "$LLM_DRAGOON_NPU_DIR/$nm" ]]; then
|
||||
echo "ERROR: NPU model $nm not staged in $LLM_DRAGOON_NPU_DIR" >&2
|
||||
echo "frank-dragoon: stage it: bash scripts/download-frank-dragoon-npu.sh" >&2
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
# The hold must exist BEFORE the engine starts and survive every boot
|
||||
# (a resume hands the clock back too — the unit's WantedBy covers it).
|
||||
run_privileged install -m 0755 dankclaw/host/halogen-fabric-clock \
|
||||
/usr/local/sbin/halogen-fabric-clock
|
||||
run_privileged install -m 0644 dankclaw/host/halogen-fabric-clock.service \
|
||||
/etc/systemd/system/halogen-fabric-clock.service
|
||||
run_privileged systemctl daemon-reload
|
||||
run_privileged systemctl enable --now halogen-fabric-clock.service
|
||||
if ! run_privileged /usr/local/sbin/halogen-fabric-clock status; then
|
||||
echo "ERROR: fabric clock is not held — halogen refuses to start with HALOGEN_NPU_MODELS set" >&2
|
||||
echo "frank-dragoon: journalctl -u halogen-fabric-clock.service -n 40" >&2
|
||||
return 1
|
||||
fi
|
||||
elif [[ -x /usr/local/sbin/halogen-fabric-clock ]]; then
|
||||
# Lane rolled back: hand the clock back to the driver and drop the boot
|
||||
# hook. `disable` deletes the INSTALLED copy of this unit — unlike the
|
||||
# tracked LLM unit symlinks the fleet's disable law protects, this file
|
||||
# is repo-owned and the next lane-on deploy reinstalls it.
|
||||
run_privileged systemctl disable halogen-fabric-clock.service 2>/dev/null || true
|
||||
run_privileged /usr/local/sbin/halogen-fabric-clock release || true
|
||||
fi
|
||||
|
||||
retire_halogen_dragoon_quadlet
|
||||
# The predecessor on a re-deploy is THIS unit: stop it (ExecStop stops
|
||||
# the container) before waiting on the port. The stray sweep is keyed
|
||||
@@ -1434,6 +1486,33 @@ deploy_llm_dragoon() {
|
||||
run_privileged systemctl enable frank-dragoon.service
|
||||
run_privileged systemctl restart frank-dragoon.service
|
||||
wait_for_health frank-dragoon http://127.0.0.1:7770/health 1200
|
||||
# The lane's own gate. /health proves the engine is up, nothing about the
|
||||
# NPU programs; one real embedding is the observable output. The first
|
||||
# request after a boot is a warmup artifact (fleet law), so retry before
|
||||
# calling a booted lane dead.
|
||||
if [[ -n "$LLM_DRAGOON_NPU_MODELS" ]]; then
|
||||
for nm in ${LLM_DRAGOON_NPU_MODELS//,/ }; do
|
||||
lane_ok=0
|
||||
for _try in 1 2 3 4 5; do
|
||||
if curl -fsS -m 60 -H 'content-type: application/json' \
|
||||
-d "{\"model\":\"$nm\",\"input\":[\"frank-dragoon npu lane gate\"]}" \
|
||||
http://127.0.0.1:7770/v1/embeddings 2>/dev/null \
|
||||
| python3 -c 'import json,sys
|
||||
v = json.load(sys.stdin)["data"][0]["embedding"]
|
||||
raise SystemExit(0 if isinstance(v, list) and len(v) and all(isinstance(x, (int, float)) for x in v) else 1)'; then
|
||||
lane_ok=1
|
||||
break
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
if [[ "$lane_ok" != 1 ]]; then
|
||||
echo "ERROR: NPU model $nm answered no vector on /v1/embeddings" >&2
|
||||
echo "frank-dragoon: read the boot ledger: tail -100 /home/dank/logs/frank_dragoon.log" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "frank-dragoon: NPU lane verified — $nm embedded"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# deploy_llm_zealot — frank-zealot is llama.cpp (PrismML fork build) on the
|
||||
|
||||
Reference in New Issue
Block a user