diff --git a/dankclaw/authentik/quadlet/authentik-outpost-ldap.container b/dankclaw/authentik/quadlet/authentik-outpost-ldap.container index 2fefc43..ca3922d 100644 --- a/dankclaw/authentik/quadlet/authentik-outpost-ldap.container +++ b/dankclaw/authentik/quadlet/authentik-outpost-ldap.container @@ -23,7 +23,7 @@ Network=dankclaw_default # Image=ghcr.io/goauthentik/ldap:2026.8.3 (amd64 digest; podman 5.7.0 refuses # the tag's nested manifest list, so pin the single-platform digest). EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env -Image=ghcr.io/goauthentik/ldap@sha256:2e7b259eb8c37faac916946ddf48ec681ee9d3a9f724fe8521bf161973f13cf9 +Image=ghcr.io/goauthentik/ldap:2026.8.5@sha256:4c75bd27918418d2d6fbbfe4581bbd5a199fe38942d24559c4483edd64a1ea87 Environment=AUTHENTIK_HOST=https://auth.franksdank.cloud Environment=AUTHENTIK_INSECURE=false Environment=AUTHENTIK_TOKEN=${AUTHENTIK_LDAP_OUTPOST_TOKEN} diff --git a/dankclaw/authentik/quadlet/authentik-outpost.container b/dankclaw/authentik/quadlet/authentik-outpost.container index 20c242e..49b7685 100644 --- a/dankclaw/authentik/quadlet/authentik-outpost.container +++ b/dankclaw/authentik/quadlet/authentik-outpost.container @@ -20,7 +20,7 @@ Wants=authentik-server.service [Container] ContainerName=authentik-outpost -Image=ghcr.io/goauthentik/server:2026.8.3@sha256:ab9b4e8cc4ab3f8d1198d2db6aeea66bafea1963b3f2843589e0d163f97d9849 +Image=ghcr.io/goauthentik/server:2026.8.5@sha256:602227a86455d6772e3d16b7d852713c4f864f40df5df894abd1285bb53979a5 Exec=worker Network=dankclaw_default EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env diff --git a/dankclaw/authentik/quadlet/authentik-postgres.container b/dankclaw/authentik/quadlet/authentik-postgres.container index 53c43d0..9673f16 100644 --- a/dankclaw/authentik/quadlet/authentik-postgres.container +++ b/dankclaw/authentik/quadlet/authentik-postgres.container @@ -14,7 +14,7 @@ After=network.target [Container] ContainerName=authentik-postgres -Image=docker.io/library/postgres:16-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea +Image=docker.io/library/postgres:16-alpine@sha256:0cefaabd882e42bcb2a2f3dc56d3ce4770560ba62fd7f197f1a2e008fd5731ab Network=dankclaw_default EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env Environment=POSTGRES_DB=authentik diff --git a/dankclaw/authentik/quadlet/authentik-server.container b/dankclaw/authentik/quadlet/authentik-server.container index d71c234..35116bc 100644 --- a/dankclaw/authentik/quadlet/authentik-server.container +++ b/dankclaw/authentik/quadlet/authentik-server.container @@ -19,7 +19,7 @@ Wants=authentik-postgres.service [Container] ContainerName=authentik-server -Image=ghcr.io/goauthentik/server:2026.8.3@sha256:ab9b4e8cc4ab3f8d1198d2db6aeea66bafea1963b3f2843589e0d163f97d9849 +Image=ghcr.io/goauthentik/server:2026.8.5@sha256:602227a86455d6772e3d16b7d852713c4f864f40df5df894abd1285bb53979a5 Exec=server Network=dankclaw_default EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env diff --git a/dankclaw/authentik/quadlet/authentik-worker.container b/dankclaw/authentik/quadlet/authentik-worker.container index 81a572f..5d634cc 100644 --- a/dankclaw/authentik/quadlet/authentik-worker.container +++ b/dankclaw/authentik/quadlet/authentik-worker.container @@ -16,7 +16,7 @@ Wants=authentik-server.service [Container] ContainerName=authentik-worker -Image=ghcr.io/goauthentik/server:2026.8.3@sha256:ab9b4e8cc4ab3f8d1198d2db6aeea66bafea1963b3f2843589e0d163f97d9849 +Image=ghcr.io/goauthentik/server:2026.8.5@sha256:602227a86455d6772e3d16b7d852713c4f864f40df5df894abd1285bb53979a5 Exec=worker Network=dankclaw_default EnvironmentFile=/home/dank/serverAppConfigs/dankclaw/.env diff --git a/dankclaw/gitea/quadlet/gitea-postgres.container b/dankclaw/gitea/quadlet/gitea-postgres.container index 663c162..1d7e10a 100644 --- a/dankclaw/gitea/quadlet/gitea-postgres.container +++ b/dankclaw/gitea/quadlet/gitea-postgres.container @@ -15,7 +15,7 @@ After=network.target [Container] ContainerName=gitea-postgres Network=gitea.network -Image=docker.io/library/postgres:16-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea +Image=docker.io/library/postgres:16-alpine@sha256:0cefaabd882e42bcb2a2f3dc56d3ce4770560ba62fd7f197f1a2e008fd5731ab EnvironmentFile=/home/dank/Containers/gitea/env Environment=POSTGRES_USER=gitea Environment=POSTGRES_PASSWORD=${GITEA_DB_PASSWORD} diff --git a/dankclaw/hermes/quadlet/chrome-devtools-protocol.container b/dankclaw/hermes/quadlet/chrome-devtools-protocol.container index 2204bf7..732b3a9 100644 --- a/dankclaw/hermes/quadlet/chrome-devtools-protocol.container +++ b/dankclaw/hermes/quadlet/chrome-devtools-protocol.container @@ -25,7 +25,7 @@ Description=Chrome DevTools Protocol daemon (headless-shell) for hermes [Container] ContainerName=chrome-devtools-protocol Network=host -Image=docker.io/chromedp/headless-shell@sha256:2d349b544a1ea6b5b5fd7c0fe99215ff662339c57407ee2e8c0a11af93516b04 +Image=docker.io/chromedp/headless-shell@sha256:7bee201478b2f6266aae01243740ee85c16281e32f42d3aed9fdf04f4eace7a1 Entrypoint=/headless-shell/headless-shell Exec=--no-sandbox --disable-gpu --use-gl=angle --use-angle=swiftshader --remote-debugging-address=127.0.0.1 --remote-debugging-port=9222 ShmSize=1073741824 diff --git a/dankclaw/marreta/quadlet/marreta-browser.container b/dankclaw/marreta/quadlet/marreta-browser.container index f2f40dc..3229d53 100644 --- a/dankclaw/marreta/quadlet/marreta-browser.container +++ b/dankclaw/marreta/quadlet/marreta-browser.container @@ -23,7 +23,7 @@ After=gluetun.service network.target [Container] ContainerName=marreta_browser Network=gluetun.container -Image=docker.io/lightpanda/browser:nightly@sha256:979c36ed869f1016b8dcf22939a0633c262f2c30e892a03a9f720eba2ff1931e +Image=docker.io/lightpanda/browser:nightly@sha256:e72730e364d1fbf352c1149f29ffa4b278f3b1a55ff0dec3262298ebe624ec7a [Service] TimeoutStartSec=300 diff --git a/dankclaw/observability/quadlet/alertmanager.container b/dankclaw/observability/quadlet/alertmanager.container index a21ebb3..78fcad6 100644 --- a/dankclaw/observability/quadlet/alertmanager.container +++ b/dankclaw/observability/quadlet/alertmanager.container @@ -33,7 +33,7 @@ Wants=network-online.target [Container] ContainerName=alertmanager -Image=docker.io/prom/alertmanager:latest@sha256:e9733bafb1bdef9b00e25a21f8f99dc26a22224bf16641ad754d1649f4c3357a +Image=docker.io/prom/alertmanager:latest@sha256:39d8bb3966c694a124428a3a88fbcffd40bd069129ecd47c059f8a3f17c55f3b Network=dankclaw_default User=0:0 Exec=--config.file=/etc/alertmanager/alertmanager.yml --storage.path=/alertmanager diff --git a/dankclaw/observability/quadlet/grafana.container b/dankclaw/observability/quadlet/grafana.container index 17770ec..162d64f 100644 --- a/dankclaw/observability/quadlet/grafana.container +++ b/dankclaw/observability/quadlet/grafana.container @@ -34,7 +34,7 @@ Wants=network-online.target prometheus.service [Container] ContainerName=grafana -Image=docker.io/grafana/grafana:latest@sha256:dbbc38ea21103cefb50396340cd88afec779c7e070b0a7bf5a75b4ce46651d3f +Image=docker.io/grafana/grafana:latest@sha256:488984b716c0862639f91a698adbe2862677a5a07bad42da5f03f3030883b205 Network=dankclaw_default User=0:0 PublishPort=127.0.0.1:3002:3000 diff --git a/dankclaw/open-webui/quadlet/open-webui.container b/dankclaw/open-webui/quadlet/open-webui.container index 51105e1..f728193 100644 --- a/dankclaw/open-webui/quadlet/open-webui.container +++ b/dankclaw/open-webui/quadlet/open-webui.container @@ -27,7 +27,7 @@ After=network.target [Container] ContainerName=open-webui Network=open-webui.network -Image=ghcr.io/open-webui/open-webui:main@sha256:4860ba005ae8f19f08d1e12b0868855b7b339ea0dad9927f196820fc0de04575 +Image=ghcr.io/open-webui/open-webui:main@sha256:0b399a8ba34558e181d316750455f2a95376a38cd60a61b9c942e138898944bd PublishPort=3005:8080 Environment=OPENAI_API_BASE_URL=http://192.168.0.143:7770 Environment=ANONYMIZED_TELEMETRY=False diff --git a/dankclaw/searxng/quadlet/gluetun.container b/dankclaw/searxng/quadlet/gluetun.container index a226e08..2c6360f 100644 --- a/dankclaw/searxng/quadlet/gluetun.container +++ b/dankclaw/searxng/quadlet/gluetun.container @@ -16,7 +16,7 @@ After=searxng-network.service network.target [Container] ContainerName=gluetun Network=searxng.network -Image=docker.io/qmcgaw/gluetun:latest@sha256:c2ee4191762fca6b6a3c3359a731198a95d9c9af4db32d466e223bf3a84e779b +Image=docker.io/qmcgaw/gluetun:latest@sha256:4af0c804003481e100180e1f8d4d5e9c48163fedc0f88d061881b615aef9d183 AddCapability=NET_ADMIN AddDevice=/dev/net/tun:/dev/net/tun Environment=VPN_SERVICE_PROVIDER=${VPN_SERVICE_PROVIDER} diff --git a/dankclaw/searxng/quadlet/searxng-valkey.container b/dankclaw/searxng/quadlet/searxng-valkey.container index 9a58f0e..6c1a055 100644 --- a/dankclaw/searxng/quadlet/searxng-valkey.container +++ b/dankclaw/searxng/quadlet/searxng-valkey.container @@ -16,7 +16,7 @@ After=searxng-network.service network.target ContainerName=searxng-valkey Network=searxng.network NetworkAlias=valkey -Image=docker.io/valkey/valkey:9-alpine@sha256:48332870af354a799964c0012ae1194a0bf2bf894eb508f945810596dc2d8d11 +Image=docker.io/valkey/valkey:9-alpine@sha256:bf09d58e96d6b4524a15396b65b242bbe18beebbcc15a04d8ee75ddac62fae0b Volume=/opt/searxng/valkey:/data [Service] diff --git a/dankclaw/searxng/quadlet/searxng.container b/dankclaw/searxng/quadlet/searxng.container index 7961486..2f539a6 100644 --- a/dankclaw/searxng/quadlet/searxng.container +++ b/dankclaw/searxng/quadlet/searxng.container @@ -16,7 +16,7 @@ After=gluetun.service searxng-valkey.service network.target [Container] ContainerName=searxng Network=gluetun.container -Image=ghcr.io/searxng/searxng:latest@sha256:5ed0323b5f5f8701264ef65e239a2bd83cb57098f2b971fa30bf79c06eeae271 +Image=ghcr.io/searxng/searxng:latest@sha256:f4aa660c54d062dedb268be84e821ea24e39f48c7a496af8dd02cc9eedb93847 Environment=SEARXNG_SECRET=${SEARXNG_SECRET} Environment=SEARXNG_BASE_URL=${SEARXNG_BASE_URL} Volume=/opt/searxng/config:/etc/searxng diff --git a/dankclaw/seerr/quadlet/seerr.container b/dankclaw/seerr/quadlet/seerr.container index f2adf47..aa77d7f 100644 --- a/dankclaw/seerr/quadlet/seerr.container +++ b/dankclaw/seerr/quadlet/seerr.container @@ -29,7 +29,7 @@ Wants=network-online.target [Container] ContainerName=seerr -Image=ghcr.io/seerr-team/seerr:v3.4.1@sha256:f4768de5f616248d723e05891f3345a1402123775d03bf0890dbfedc0831bda1 +Image=ghcr.io/seerr-team/seerr:v3.5.1@sha256:d8fab675b55ad293ded98fd71efcfd46054cbad9746534ae1cc4794a88c0b3be Network=dankclaw_default PublishPort=127.0.0.1:5055:5055 Volume=/home/dank/Containers/seerr/data:/app/config diff --git a/dankserver/calibre-server/quadlet/calibre-server.container b/dankserver/calibre-server/quadlet/calibre-server.container index e9d3762..bd526a3 100644 --- a/dankserver/calibre-server/quadlet/calibre-server.container +++ b/dankserver/calibre-server/quadlet/calibre-server.container @@ -43,7 +43,7 @@ After=network.target [Container] ContainerName=calibre-server -Image=lscr.io/linuxserver/calibre:9.15.0@sha256:59e4b0e036e62f228f8bcfae1ade42f47047719885fe454ddbad38e4597b987b +Image=lscr.io/linuxserver/calibre:9.17.0@sha256:b198fc03a6abc6be7e251a9958e50e74634b00e447c7b25e92c191c8077ba378 Network=pasta EnvironmentFile=/home/dank/Containers/calibre-server/env Environment=PUID=1000 diff --git a/dankserver/calibre-web/quadlet/calibre-web.container b/dankserver/calibre-web/quadlet/calibre-web.container index 75a3c46..4177268 100644 --- a/dankserver/calibre-web/quadlet/calibre-web.container +++ b/dankserver/calibre-web/quadlet/calibre-web.container @@ -16,7 +16,7 @@ After=network.target [Container] ContainerName=calibre-web -Image=lscr.io/linuxserver/calibre-web:0.6.27-ls402@sha256:802729966faed5e32d671cd4f92ba6196e9e754a6c0f740ce1ee9acd86b10e55 +Image=lscr.io/linuxserver/calibre-web:0.6.27-ls407@sha256:470b4ed9a7d6ea1475fef17dbcf957cb60880910296d226bd0dca652c05f68ca Network=pasta Environment=DOCKER_MODS=linuxserver/mods:universal-calibre Environment=LANG=en_US.UTF-8 diff --git a/dankserver/jellyfin/quadlet/jellyfin.container b/dankserver/jellyfin/quadlet/jellyfin.container index 88bc693..afa45a5 100644 --- a/dankserver/jellyfin/quadlet/jellyfin.container +++ b/dankserver/jellyfin/quadlet/jellyfin.container @@ -16,7 +16,7 @@ After=network.target [Container] ContainerName=jellyfin -Image=ghcr.io/jellyfin/jellyfin:latest@sha256:c3efa69a22f2691fc88d6bb035e87a8560e395a6047f062fb381c61c3185e8f5 +Image=ghcr.io/jellyfin/jellyfin:latest@sha256:fea863d0cd9ec445985d9c75fc6ed64e38a1d2766cf0c250a33d553f4fc1b771 Network=dankserver_appnet Environment=DEBIAN_FRONTEND=noninteractive Environment=DOTNET_SYSTEM_IO_DISABLEFILELOCKING=true diff --git a/dankserver/plex/quadlet/plex.container b/dankserver/plex/quadlet/plex.container index 1a72e56..60a36d6 100644 --- a/dankserver/plex/quadlet/plex.container +++ b/dankserver/plex/quadlet/plex.container @@ -4,7 +4,7 @@ After=network-online.target Wants=network-online.target [Container] -Image=lscr.io/linuxserver/plex:1.43.5.11029-6bf8af3f0-ls329@sha256:8f655bad7dcbc5398a78f70a5af12600b4ed3424f901566ed1eaa6710e9e2334 +Image=lscr.io/linuxserver/plex:1.43.5.11079-b3b13e506-ls330@sha256:01a53d9c7adcbb3701fbb0a4230c0a37c85453759b6b05a67356c3c7a09de08d ContainerName=plex Network=dankserver_appnet:alias=plex Environment=PUID=0 diff --git a/dankserver/romm/quadlet/romm-mariadb.container b/dankserver/romm/quadlet/romm-mariadb.container index 130ebf8..1540065 100644 --- a/dankserver/romm/quadlet/romm-mariadb.container +++ b/dankserver/romm/quadlet/romm-mariadb.container @@ -14,7 +14,7 @@ After=network.target [Container] ContainerName=romm-mariadb -Image=docker.io/library/mariadb:11@sha256:f16d7a344413e519dff518f27bdb221388f84b0d274c97c4fb5f2cb0dc1b34fa +Image=docker.io/library/mariadb:11@sha256:c4124d1afa875e47f2892edf26b482fa54f796e2f342d3717a04ca091a22a9bd Network=dankserver_appnet:alias=mariadb EnvironmentFile=/home/dank/Containers/romm/env Environment=MARIADB_DATABASE=romm diff --git a/dankserver/romm/quadlet/romm-redis.container b/dankserver/romm/quadlet/romm-redis.container index e935b37..3fd15a9 100644 --- a/dankserver/romm/quadlet/romm-redis.container +++ b/dankserver/romm/quadlet/romm-redis.container @@ -12,7 +12,7 @@ After=network.target [Container] ContainerName=romm-redis -Image=docker.io/library/redis:7-alpine@sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499 +Image=docker.io/library/redis:7-alpine@sha256:8da19c7399dca900c6822a1cf30f9d2285585e2c4b457ff4095b0f4500054d3f Network=dankserver_appnet:alias=redis Volume=/home/dank/Containers/romm/redis:/data HealthCmd=redis-cli ping diff --git a/dankserver/romm/quadlet/romm.container b/dankserver/romm/quadlet/romm.container index 858fc31..19c78a6 100644 --- a/dankserver/romm/quadlet/romm.container +++ b/dankserver/romm/quadlet/romm.container @@ -18,7 +18,7 @@ After=romm-mariadb.service romm-redis.service network.target [Container] ContainerName=romm -Image=ghcr.io/rommapp/romm:latest@sha256:03295f95a6290e752641f9553ac4198b0e5b71b879d57f7b2371b98f608feb54 +Image=ghcr.io/rommapp/romm:latest@sha256:8677fb7b81b1d3db20b849e989a1dadc2a11159e51eb34a2acdcb01102274edf Network=dankserver_appnet EnvironmentFile=/home/dank/Containers/romm/env Environment=DB_HOST=mariadb diff --git a/dankserver/tautulli/quadlet/tautulli.container b/dankserver/tautulli/quadlet/tautulli.container index 8ab4ae3..001b0de 100644 --- a/dankserver/tautulli/quadlet/tautulli.container +++ b/dankserver/tautulli/quadlet/tautulli.container @@ -4,7 +4,7 @@ After=network-online.target Wants=network-online.target [Container] -Image=lscr.io/linuxserver/tautulli:v2.18.1-ls245@sha256:c66d3afa8f7a701a48dd1b1f57b22eca0483d895d670fe11ecff783f6a9bbd73 +Image=lscr.io/linuxserver/tautulli:v2.18.2-ls250@sha256:529c7eabcc40554c1dc48f33f1f3cc3b1438462d73e0ec2dac8ac0b66aeeb8d4 ContainerName=tautulli Network=dankserver_appnet:alias=tautulli Environment=PUID=0 diff --git a/scripts/census-frank-dragoon.sh b/scripts/census-frank-dragoon.sh index 61287d5..b8191a4 100755 --- a/scripts/census-frank-dragoon.sh +++ b/scripts/census-frank-dragoon.sh @@ -21,7 +21,7 @@ # deployed knob has since dropped to 262144. set -euo pipefail -IMG=ghcr.io/peonist-ai/halogen-flash-server@sha256:0c61bf84ac22308a53f5d1ca6b86806702d7039e5ebc51cae4c66621b92fe04a +IMG=ghcr.io/peonist-ai/halogen-flash-server@sha256:e35133b206da3094cc4d70c78800b26235bd9124b9aeb0e029d4a7c153b9cff0 CUR=/home/dank/models/halogen-flashnext-v2 NEW=/home/dank/models/halogen-v2-abliterated CUR_CK=qwen38-flash-next-v2-swift15-abliterated.hgn diff --git a/scripts/deploy-remote.sh b/scripts/deploy-remote.sh index e1e5444..d24fba9 100755 --- a/scripts/deploy-remote.sh +++ b/scripts/deploy-remote.sh @@ -1482,7 +1482,7 @@ deploy_anubis() { # artifact would leave a restart-looping unit and a dark endpoint. The # port gate is the same as every cutover since 2026-09-16: nothing # starts until 7770 is released. -LLM_DRAGOON_IMAGE="${LLM_DRAGOON_IMAGE:-ghcr.io/peonist-ai/halogen-flash-server@sha256:0c61bf84ac22308a53f5d1ca6b86806702d7039e5ebc51cae4c66621b92fe04a}" +LLM_DRAGOON_IMAGE="${LLM_DRAGOON_IMAGE:-ghcr.io/peonist-ai/halogen-flash-server@sha256:e35133b206da3094cc4d70c78800b26235bd9124b9aeb0e029d4a7c153b9cff0}" # The payload dir is its own twin because the NPU lane's containment gate below # reads it; every artifact path under it stays a full literal as usual. LLM_DRAGOON_MODEL_DIR="${LLM_DRAGOON_MODEL_DIR:-/home/dank/models/halogen-v2-abliterated}" diff --git a/scripts/download-frank-dragoon-npu.sh b/scripts/download-frank-dragoon-npu.sh index f18b914..ca7dfab 100755 --- a/scripts/download-frank-dragoon-npu.sh +++ b/scripts/download-frank-dragoon-npu.sh @@ -8,8 +8,10 @@ # (/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.16.2 -# image on 2026-10-19), so the files land in the layout the entrypoint +# 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 @@ -57,21 +59,25 @@ manifest() { case "$1" in qwen3-embedding-0.6b) NPU_REPO="peonist-ai/halogen-npu-qwen3-embedding-0.6b" - NPU_REV="4960fd9d4423dd15373ad21c83db48dbdb3b2798" + NPU_REV="8fd44c9f18861618a509bea75c51ff8a37e3bd5d" FILES=( "devices/NOTICE.md 3248 0f267b3d2523ddf14997ee58660a492a9eaf10ccf6b08bf19dfdc85b9a478b91" - "devices/consts.hnpb 5086496 ae430e41545a0b1012938cda373b0c450364d3be631111cca3a8c43679f93336" - "devices/devices.hnpm 24047 4e9bca9d95d084fb89ce5b99f579a00666c8ca1efae2613c53e3a4878cc9f174" - "devices/u0.elf 4931968 b38f165886cb037b7d3951d9cb878473eaba58a432acd45416ff85fd88aa4e46" - "devices/u1.elf 5456000 b1811feee851b8af833b37721da92099d89772110815bbbe2c3fa1c276f0ff42" - "devices/u2.elf 6505552 406ea9a95b470628a7382e376f4a72cdf4d7aa4c59b4ddf0a4bdd139d63d6ff2" - "devices/u3.elf 6817216 bc205d51b523d770151a0c61968f7c70bf624412827580bb6fe0aaf4650dc2ee" - "devices/u4.elf 7444032 a5a91338a2603acdcd5c11df532033fda2d3f5479ea44ae87f98fafbdff9a62d" - "devices/u5.elf 7432416 6c2bf41835569e0eba25ded7c05c7264ae69c6700bdeb4791057d884f2a4c126" - "devices/u6.elf 4941232 919d0038ca088eafddf195f3466ff38db27d3635be1cffa5d4d4b5140902a68e" - "devices/u7.elf 5488304 d904cf8afae9428dd167960549e18cce5441745f18c62f8c50975dffc077d3c2" - "devices/u8.elf 4941232 db3aef69b17825854ee0dd2df475125a3f5c872f95c686793d6af1ee83596fa7" - "devices/u9.elf 5488304 d182727e551084db8fb9600441d8aa373c7f09a3d267ae0a5e921ef92326db1c" + "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" ) ;; @@ -99,7 +105,18 @@ for MODEL in ${MODELS//,/ }; do # 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" + # 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; } diff --git a/scripts/frank-dragoon.env b/scripts/frank-dragoon.env index 89548dd..5caaee7 100644 --- a/scripts/frank-dragoon.env +++ b/scripts/frank-dragoon.env @@ -10,8 +10,11 @@ # The .hgn format is the lock: it loads ONLY in the flash-server line, so the # engine moves with the payload, never ahead of it. # Image digest-pinned (a tag pull silently re-points; the digest is what was -# qualified — 0.16.2 is the version the model card names as its quickstart pin). -FRANK_DRAGOON_IMAGE=ghcr.io/peonist-ai/halogen-flash-server@sha256:0c61bf84ac22308a53f5d1ca6b86806702d7039e5ebc51cae4c66621b92fe04a +# qualified). 0.18.20 since 2026-10-30 (was 0.16.2, the model card's quickstart +# pin) — the vendor has moved two minor versions and the payload lock is what +# the deploy's health gate plus the boot ledger decide; rollback is the +# previous digest, 0c61bf84…, on this same line. +FRANK_DRAGOON_IMAGE=ghcr.io/peonist-ai/halogen-flash-server@sha256:e35133b206da3094cc4d70c78800b26235bd9124b9aeb0e029d4a7c153b9cff0 # Container identity kept from the 27B era: the unit's ExecStop names it. FRANK_DRAGOON_CONTAINER=frank-dragoon-halogen # Predecessor identity the serve script clears if it is still holding the GPUs. diff --git a/scripts/serve-frank-dragoon.sh b/scripts/serve-frank-dragoon.sh index e2fea17..5f43012 100755 --- a/scripts/serve-frank-dragoon.sh +++ b/scripts/serve-frank-dragoon.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# serve-frank-dragoon.sh — frank-dragoon endpoint: halogen-flash-server 0.16.2 +# serve-frank-dragoon.sh — frank-dragoon endpoint: halogen-flash-server 0.18.20 # (closed-source engine, image ghcr.io/peonist-ai/halogen-flash-server, # digest-pinned in frank-dragoon.env) serving Quat3rnion's NATIVE v2 # conversion (2026-10-11) of the abliterated Qwen3.8-Flash-Next on Dankclaw's diff --git a/serverAppConfigs-docs/LLM/Radiance Architecture Plugins.md b/serverAppConfigs-docs/LLM/Radiance Architecture Plugins.md new file mode 100644 index 0000000..983ce7b --- /dev/null +++ b/serverAppConfigs-docs/LLM/Radiance Architecture Plugins.md @@ -0,0 +1,287 @@ +--- +type: "howto" +title: "Radiance Architecture Plugins — declaring a model" +description: "Writing a radiance architecture plugin: declaring weights, buffers, KV groups and ops, then issuing the step." +tags: + - llm + - radiance + - plugins + - howto +timestamp: 2026-10-30 +--- + +# Radiance architecture plugins + +An architecture plugin declares a model in two phases: `rad_arch_declare` names every weight, +buffer, KV group and op **once**, against the model's metadata; `rad_arch_step` issues the ops for +one step. The engine picks the plugin by the exact `(rad_arch_id, rad_arch_quant)` pair — no +fallback — so one family at two weight formats is two `.so` files, and branching on a quant +descriptor inside one plugin is the anti-pattern the split exists to prevent. + +The in-tree plugins are the templates: `arch/llama_fp8/llama_fp8.cpp` (the smallest complete +decoder), `arch/gemma3`, `arch/qwen35_bf16`, and the header-only blocks in `arch/common/` +(`rad_block_attn*.h`, `rad_block_mlp*.h`, `rad_block_moe_fp8.h`, `rad_block_gdn*.h`, +`rad_block_dflash2.h`, `rad_block_mtp*.h`, `rad_block_vit.h`, `rad_block_ple.h`, +`rad_block_glm_*.h`, `rad_qsa.h`, …). Include those by `` — they are installed +beside the ABI and the in-tree plugins include them by that spelling. + +## Exports + +```c +const char* rad_arch_id(void); /* "llama", "qwen35", ... */ +const char* rad_arch_quant(void); /* "" unquantised, else the descriptor served */ +int rad_arch_serves_imports(void); /* optional: 0 declines an imported quantisation */ +int rad_arch_probe(const RadModelMeta* meta, RadArchProbe* out); /* optional */ +int rad_arch_declare(RadBuilder* b, const RadModelMeta* meta, const RadBuildCtx* ctx); +void rad_arch_step(struct RadCtx* c, const struct RadBatch* batch); +const RadChatFormat* rad_arch_chat_format(const RadModelMeta* meta); /* optional */ +const RadAudioFormat* rad_arch_audio_format(const RadModelMeta* meta); /* optional */ +``` + +Everything but declare/step is optional. `RAD_ARCH_PLUGIN(NS, ID, QUANT, VERSION, DESC)` in +`arch/common/rad_arch.h` emits the whole export block, including `rad_plugin_info` (kind +`RAD_PLUGIN_ARCH`). The plugin's **name must be unique**; the architecture id may repeat. + +`rad_arch_serves_imports` exists because a checkpoint whose quantisation was imported weight by +weight (Quark, ParoQuant, GPTQ, NVFP4) names its producing tool, which no plugin claims: the family's +one quantised plugin serves it, and two are refused by name. A second quantised plugin that reads +only its own containers returns 0. + +## Declare: the metadata + +`RadModelMeta` is what the loader read from the container's header before any plugin ran: `arch_id`, +`name`, `quant`, `n_layers`, `n_embd`, `n_head`, `n_head_kv`, **`head_dim`**, `n_ff`, `n_vocab`, +`n_ctx_train`, expert counts, `rms_eps`, `rope_theta`, and a free-form `kv_key`/`kv_val` table for +everything else (`rad_meta_geti/getf/gets`). Read dimensions from here — one plugin covers every size +in a family. + +**`head_dim` is read, never derived.** On the Qwen hybrids it is 256 while `hidden / heads` is 213; +a derived value silently breaks projection widths, KV page size and the attention kernel's band. + +`RadBuildCtx` is the deployment the plugin must declare against: + +| field | meaning | +|---|---| +| `rank`, `world_size` | declare runs once per rank; **dimensions are already divided** — the core inserts no collectives | +| `max_tok`, `max_seqs`, `max_ctx`, `max_spec` | the step bounds: buffer sizing, kernel bands, the scheduler's window | +| `max_mixed_tok` | the most a multi-sequence step may carry; a wider step is one sequence (prefill span) | +| `tp_wire_lossy`, `tp_wire_min_bytes` | invert into `all_reduce`'s `exact`; below the floor a message must go exact | +| `kv_dtype`, `kv_quant` | the attention cache width and, under fp8, its narrow layout (`RAD_KVQ_V4`, `RAD_KVQ_K6V4`) | +| `attn_prefill_fp8` | `RAD_ATTN_FP8_*` bits to pass as `attn_paged`'s `prefill_fp8` | +| `kv_resident_blocks`, `kv_stream_ok` | KV streaming: the ring depth, and permission to declare the streamed-only ops | +| `state_dtype` | `RAD_F16` when `--state-cache-dtype f16` is served by this plugin | +| `is_draft`, `scope` | a drafter/encoder declare; `scope` prefixes declared names (`draft`, `vision`, `audio`) | +| `shape_probe` | a **sizing** declare at a smaller `max_tok`; must not touch state the real declare wrote | +| `max_enc_patches`, `max_audio_frames` | the encoder pass sizes | +| `max_out_rows` | logits rows the KL mode needs (0 = the plugin's own rule) | +| `cvec` | the loaded control vector, or null | +| `tp_attn_rows` | row-split prefill attention requested | +| `draft_coupling`, `draft_tree` | declare the coupled/tree forms | +| `weight_prefetch_off` | declare no weight prefetch beside collectives | + +## Declare: weights + +```c +static const RadWeightDecl d = { + .dtype = RAD_BF16, .rank = 2, .shape = {n_embd, n_ff}, + .access = RAD_ACCESS_PER_TOKEN, .shard = RAD_SHARD_ROW, + .group = grp_layer(l), .optional = 0, + .n_row_parts = 0, .source = nullptr, .planes = nullptr, +}; +rad_weight w = rad_decl_weight(b, "blk.0.mlp.gate.weight", &d); +``` + +- **`access`** tells the placement planner how the weight is read: `PER_TOKEN` (dense), + `PER_REQUEST` (an encoder tower, read once per request), `CONDITIONAL` (routed experts), + `RARE`, `VOCAB` (lm_head/embedding: read every token but huge, and one is a gather). +- **`shard`**: `NONE` (replicated), `ROW` (split dim 0), `COL` (split dim 1, needs a following + all-reduce). The plugin says; the core does not infer. Uneven splits use + `rad_weight_shard_span`/`_parts` (a width that does not divide into whole 128-column blocks; the + delta net's `[q|k|v|z]` of unequal part widths). +- **`group`** is the unit of movement (`layer`, `expert`, `slot`); an expert's gate/up/down share one, + because the unit of transfer is an expert, not a tensor. +- **`row_parts`** describes what a `SHARD_ROW` weight stacks along dim 0 (`[gate|up]`, + `[q|k|v|z]`). **A tensor-parallel rank owns a slice of each stacked projection**, never a + contiguous slice of the stack; the parts cannot be inferred from a fused container tensor, and a + contiguous slice produces fluent wrong text. +- **`source`/`planes`** view an existing stored weight: a block-fp8 linear's `codes,scale` are one + logical weight taken by a GEMM as two operands. Null `source` is the weight of its own name; null + `planes` takes every plane. +- A dtype other than the plane's is legal only as an exact widening (a bf16 norm read as f32). + +The **name map** (`rad_decl_name_map`) is the plugin's map from checkpoint tensor names to declared +weight names, and it is the only place convert-time fusions live: `COPY`, `CONCAT`, `WNORM` (a +weight-normalised pair folded to the plain weight), `ROWS`, `TRANSPOSE`, `EMA` (an EnCodec codebook), +`TAP`, `COLS`. `rad-convert` reads this map from the plugin, so it needs no knowledge of the model. +`RAD_MAP_COPY` with several sources means "first present wins" (a tied lm_head); `CONCAT` requires +all. Ask for a weight's encoding with `rad_weight_encoding` **after** its map entry when the graph +depends on the format (fp8 GEMM vs plain, rotated expert kernels). + +## Declare: buffers and KV groups + +`rad_decl_buffer` names every activation: `RAD_BUF_TRANSIENT` (liveness-analysed, may share bytes), +`RAD_BUF_PERSIST` (across steps: a calibration accumulator, a routing histogram), or +`RAD_BUF_DERIVED` (computed once per step by the core from the batch — `derive` names the +derivation, e.g. `conv_state_index`). Buffers live in one arena sized once, not an allocator per +step. + +`rad_decl_kv_group` declares each state kind: `RAD_KV_FULL` (paged per-token, growing), +`RAD_KV_WINDOW` (bounded by a window), `RAD_KV_LINEAR` (per-sequence fixed state, GDN), +`RAD_KV_CONV` (a rolling window of `width-1+num_spec`), `RAD_KV_RING` (window floor set at runtime). +The **block size is not declared** — it comes from the resolved attention kernel's constraints; read +it back with `rad_kv_block_size`. `rad_bind_layer_kv` binds each layer so the block manager knows how +many state instances a sequence owns; `rad_kv_group_zero` (a rank with no heads), +`rad_kv_group_replicated` (every rank holds the same bytes), and `rad_kv_group_dense_read` (read over +the whole history every step, so it cannot stream) are the three refinements. + +## Declare: ops + +```c +rad_op h = RAD_OP(b, "gemm_w4a8_nt", + RAD_PARAMS(RAD_RANGE("M", 1, max_tok), RAD_INT("N", 8192), RAD_INT("K", 5120)), + RAD_WEIGHTS(w)); +rad_op_reads (b, h, RAD_BUFS(b_x), n); +rad_op_writes(b, h, RAD_BUFS(b_y), n); +``` + +- Parameter names and operand order come from `docs/OPS.md`; a new name is allowed only when the + vocabulary has none, and the first plugin to declare it fixes its schema. +- `RAD_RANGE` on exactly one parameter per op gives the selector its bucket table. The core carries + every parameter opaquely except `RAD_PROLE_SEQ_CHUNK` (cut steps on it) and `RAD_PROLE_CAPACITY`. +- `rad_op_reads`/`rad_op_writes` are the buffer planner's liveness. An undeclared use is assumed live + for the whole program: sound but wasteful. +- **Fusion is selection.** Ask for the fused op; if `rad_op_resolved` is false, emit the chain. A + fusion's `replaces` hook is a checkable equivalence, not a rewrite. +- `rad_op_declared(b, "op")` asks whether any library declares an optional op; use it before + emitting a fused form only an optional library defines. +- Buffers touched from the **second lane** go through `rad_buf_concurrent`, and an architecture whose + lane-1 work lies in one stretch declares `rad_lane1_window_begin/end` around it. The runtime + refuses a lane-1 op outside the window; the window lets buffers used only outside it share bytes. + +Step output: `rad_declare_logits` names the buffer the sampler reads (its dim 0 bounds `n_out`; a +[n_tok, vocab] logits buffer at a large vocabulary is gigabytes — declare the gather + +`logits_gemm` shape instead). An embedding program declares `rad_declare_pooled` instead (one row a +sequence, no sampler, no generation). `rad_declare_scores` opts into host-side scored steps. + +## Declare: drafters, encoders, generators + +- `rad_declare_drafter(RadDrafterDecl)` — depth, block, proposal shape. `rad_drafter_shared_kv` (a + drafter with no cache of its own), `rad_drafter_tree` (a tree over the proposals), + `rad_drafter_short_block` (a second, shorter pass) are refinements. Declare the drafter **last**. +- `rad_declare_encoder(RadEncoderDecl)` — a vision/audio tower, media preprocessed by the core. +- `rad_declare_generator` / `rad_declare_audio_generator` / `rad_declare_video_generator` — image, + audio and video generation programs; the engine sizes the step from the probe's `gen_*` fields + before declare. +- Optional format exports let a model state what it knows and the server learn nothing new: + `rad_arch_chat_format` (a data description of reasoning/tool-call markup, replacing template + analysis), `rad_arch_audio_format` (how a speech model hears: clip tokens, prompt, language, + transcript start), `rad_arch_text_prepare` (rewrite request text into training form), + `rad_arch_video_positions`/`rad_arch_video_pack` (video pass geometry and packing), + `rad_arch_encode_geom` (encoder pass geometry). Each struct grows only at its end behind + `struct_size`, so adding a field does not move the ABI. + +**Media preprocessing is the core's**, not the plugin's: resizing, patching, placeholder expansion +and positions in six processor families (Qwen-VL, Gemma 4, LFM2-VL, Unlimited-OCR, GLM-5.3, Pixtral +for Nemotron), chosen by `vision_config.model_type` plus owning-model checks; audio through Gemma 4's +extractor or Qwen3-ASR's Whisper one, chosen by `audio_config.model_type`. A vision model with another +pipeline needs a core change. + +## Step: issuing the graph + +`RadBatch` is built once per step and shared by every block: `phase` (`PREFILL`/`DECODE`/`MIXED`), +`n_tok`, `n_seq`, `token_ids`, `positions`, `cu_seqlens`, `seq_ids`, `lora_slot`, `n_out`/`out_ids`, +`q_lens`/`ctx_lens`/`max_q_len`/`max_ctx_len`, the KV-group array, the speculative fields +(`n_spec`, `draft_pass`, …). + +```c +static void step(RadCtx* c, const RadBatch* batch) { + if (batch->draft_pass > 0) { m.ds.step(c, batch); return; } /* query pass */ + if (batch->draft_pass < 0) { m.ds.context(c, batch); return; } /* context pass */ + RAD_ISSUE(c, m.op_embed, praw(batch->token_ids, RAD_I32, batch->n_tok), + RAD_W(m.w_tok), brows(m.b_x, batch->n_tok)); + for (auto& layer : m.layers) { layer.attn.step(c, batch); layer.mlp.step(c, batch); } + /* ...heads; the lm_head only over batch->n_out rows... */ +} +``` + +- `rad_issue(c, op, operands…, n)` / `RAD_ISSUE` (n = the batch's ranged extent) / + `RAD_ISSUE_N` (an explicit row count). `rad_step_fail(c, what)` sets the status helpers return. +- **A mixed step is the trap.** Continuous batching produces steps with a prefill chunk beside decode + rows. A block that issues one path for the whole step sends its decode rows down the chunked path, + which commits recurrent state directly and takes no `num_accepted`; a rejected draft cannot be + rolled back, and the model stays fluent while drifting. Issue the recurrent path over + `RadBatch::n_seq_decode` sequences and the chunked path over the rest (the core orders steps + decode-first, so each half is contiguous; `cu_seqlens` holds absolute offsets, so pass + `cu + n_seq_decode` with the token operands at their base). +- A step issued on lane 1 outside the declared window fails by name. An op the core appends behind a + declared one (runtime LoRA) counts at that op's position. +- **Zero rows is not a call**: issuing an op over zero rows (a zero ranged extent, or all-empty + sequences) returns `RAD_E_SHAPE` and writes nothing. The caller skips the issue, as the + architectures' decode and prefill halves do. +- `n_out` is 0 for a chunk that finishes no sequence — do not run the lm_head. + +## Probe: what the plugin measured + +`rad_arch_probe(meta, out)` runs before declare. Its fields are the engine's permissions: + +| field | effect | +|---|---| +| `draft_depth`, `draft_depth_fixed` | the container's spec depth; a block drafter's trained block is the only depth it serves | +| `shape_probe_ok` | the plugin tolerates a sizing declare, so the activation arena can be lent | +| `enc_merge`, `audio_frames` | encoder pass granularity; longest audio clip | +| `gen_rows`, `gen_patches`, `gen_audio`, `gen_ctx`, `gen_video` | generator sizing before declare | +| `kv_quant_ok` | bits of the narrow KV layouts this model's kernels serve | +| `chain_pays`, `tree_pays` | the plugin measured chained launches / draft trees faster | +| `kv_stream_ok` | the architecture can stream a KV group (sparse, block-addressed, with `kv_resolve`) | +| `control_vector_ok` | the plugin applies `RadBuildCtx::cvec` | +| `tp_attn_rows_ok` | the plugin serves row-split prefill attention | +| `state_f16_ok` | the plugin declares recurrent state at f16 and its kernels read it | + +A flag whose probe bit is 0 is **refused at startup with the plugin named** (`--kv-cache-dtype +fp8_v4`, `--control-vector`, `--tp-attn-split rows`, `--state-cache-dtype f16`, `--kv-resident`). +That is the design: a deployment switch the plugin cannot honour is an error, never a silent +substitution. + +## Worked pattern: the smallest complete plugin + +`arch/llama_fp8/llama_fp8.cpp` builds twice from one source — `llama_fp8` (quant `fp8_e4m3`) and +`llama_dense_fp8` (quant `""`), differing only in the descriptor and the graph-dump text. It shows +the whole shape: + +1. `struct Layer { AttnFP8 attn; MlpFP8 mlp; }`, `struct Model` holding `Names`, `Geom`, buffer and + op handles. +2. `declare()`: name map entries first, then weights (with `rad_weight_encoding` where the head's + format decides the kernel), buffers, KV group, the embedding and its all-reduce, then each layer's + `declare()` from the shared blocks, the final norm, gather and lm_head, **the drafter last**, and + `rad_note` lines explaining the shape. +3. `step()`: draft passes first (sign selects query/context), then embedding (+ exact all-reduce + when needed), each layer's `step()`, the final norm, and the head only over `n_out`. +4. `probe()`: reads `draft.block_size` from the metadata and refuses any other depth for a block + drafter. +5. `RAD_ARCH_PLUGIN(...)` at the bottom. + +Add a new family by copying a neighbour, replacing the name map and geometry, and composing the +blocks you need; add a new *format* by cloning the plugin with another `(id, quant)` pair and the +ops that format needs. + +## Verifying a new plugin + +```sh +radiance --model m.rad --debug-graph # every op, its resolved kernel, and why +radiance --model m.rad --debug-placement # the plan +rad-convert $SRC -o m.rad --plan-only -v # a convert that plans will boot +rad-kbench -m m.rad --kernels mylib,libr4d,libref --report k.md --bench +rad-tune -m m.rad --tp 2 # only at the shapes you serve +``` + +`tests/arch_test.cpp` drives a plugin's builder the way the core does, including lane-window +liveness, so the pinned graphs and steps are the ones the core plans. A plugin that declares a weight +form no kernel `layout` accepts is a declare-time error naming the weight, encoding and kernel — the +same check `rad-convert` runs before writing. + +## Related + +- [[Radiance Plugins]] — kernel libraries, encodings, the build path, ABI law. +- [[Radiance Conversion]] — how the name map drives a convert, and `--set`/descriptor selection. +- [[Radiance Model Families]] — the existing `(id, quant)` pairs and their recipes. +- [[Radiance Tools]] — `rad-kbench`, `rad-tune`, `rad-info --plugins`. diff --git a/serverAppConfigs-docs/LLM/Radiance Conversion.md b/serverAppConfigs-docs/LLM/Radiance Conversion.md new file mode 100644 index 0000000..f95b0c2 --- /dev/null +++ b/serverAppConfigs-docs/LLM/Radiance Conversion.md @@ -0,0 +1,354 @@ +--- +type: "howto" +title: "Radiance Conversion — checkpoints into .rad containers" +description: "Turning a checkpoint into a .rad container: rad-convert, the recipe language, quantisers, reuse and export." +tags: + - llm + - radiance + - quantization + - howto +timestamp: 2026-10-30 +--- + +# Radiance conversion + +`rad-convert` turns a Hugging Face checkpoint into a `.rad` container, choosing each weight's +encoding from a **recipe**. `rad-export` wraps it for finetunes (a merged checkpoint or a PEFT +adapter) with the stock model's recipe. Neither opens a GPU: conversion is CPU-only and safe beside a +running server. + +Fetch checkpoints with `scripts/hfget.sh ` (resumable; reads `.safetensors`, +skips `.bin`/ONNX/GGUF when safetensors exist; `HF_TOKEN` for gated repos; writes a +`.hfget-manifest` of content keys). `radiance --model ` runs the same fetch itself. + +## What a conversion does + +1. Read `config.json` (or GGUF metadata), and the shard index if present. +2. Load the architecture, kernel and quantiser plugins and run **declare**, as the engine does. +3. Resolve every op and ask each resolved kernel's `layout` hook about the planned encoding. A + recipe no kernel can serve is refused **here**, before any I/O. +4. Write each logical weight once: the checkpoint's bytes where no rule matches, otherwise the + planes the matching quantiser writes, a block of rows at a time. +5. Measure every quantised weight against its source through the core's decoder and print the worst. +6. Bake the tokeniser, chat template, metadata, recipe and any draft head. + +```sh +build/bin/rad-convert /srv/models/hf/Qwen/Qwen3.8-27B -o qwen3.8-27b-bf16.rad \ + --recipe data/recipes/q38-27b-bf16.recipe +build/bin/rad-convert /srv/models/hf/Qwen/Qwen3.8-Flash-Next -o flashnext-bf16.rad --max-ctx 4096 +``` + +## Every `rad-convert` option + +| option | meaning | +|---|---| +| `` | a `.gguf`, a `.safetensors`, an `index.json`, or a checkpoint directory | +| `-o, --out FILE` | the container to write | +| `--recipe FILE` | one rule a line: `PATTERN QUANTISER key=value …`; repeatable; first match decides | +| `--quant RULE` | `PATTERN=QUANTISER[:key=value,…]`, placed **ahead of** the recipe's rules; repeatable | +| `--list-quantizers` | every loaded quantiser and the options it takes, then exit | +| `--arch ID` | override the architecture id read from the checkpoint | +| `--home DIR` | `$RADIANCE_HOME` (kernels, architectures, quantizers) | +| `--kernels A:B` | kernel libraries to load, first wins; declare checks the recipe against them | +| `--tokenizer F` | `tokenizer.json`; its declared chain is baked instead of reconstructed from a pre-tokenizer name. Required for a GGUF, without it the chain is a guess | +| `--tokenizer2 F` | a second `tokenizer.json` baked as the second vocab (a generator reading its prompt through two tokenisers); keep `tokenizer_config.json` beside it | +| `--add FILE` | merge one more `.safetensors`, tensor names kept; repeatable; a name in two files is refused | +| `--config DIR` | read `config.json`, `generation_config.json`, `tokenizer.json` from DIR for a single-file checkpoint | +| `--chat-template F` | a Jinja template baked in, overriding the checkpoint's (or its absence); trailing newlines stripped; without one `/v1/chat/completions` answers 501 | +| `--imatrix F` | llama.cpp imatrix: expert-popularity profile, per-column importances, error weighting; required by `ggml` types `iq2_xxs`, `iq2_xs`, `iq1_s` | +| `--set K=V` | a metadata key, seen by declare **and** written into the container; repeatable | +| `--max-tok N` | the `max_tok` declare runs at (default 8192, or a generator's largest pass) | +| `--max-ctx N` | the context declare runs at (default: the checkpoint's training context) | +| `--max-spec N` | the `max_spec` declare runs at (default 4, or the block a drafter states); `0` leaves a draft head out | +| `--draft-model DIR` | a drafter shipped as its own repository, merged under `draft.` | +| `--kv-cache-dtype T` | `bf16` or `fp8`: the cache width declare runs at; the container is identical at either | +| `--reuse FILE` | copy every weight an earlier container holds with the same encoding, quantiser, options and shape | +| `--changed FILE` | with `--reuse`: checkpoint tensors (one name a line) that differ; every weight reading one is converted | +| `--in-place` | with `--reuse`: extend that container; new weights go past its end, the header last; keeps `.pre-append` | +| `--lora FILE\|DIR` | merge a PEFT adapter into the checkpoint tensors before converting (`W + strength · scale · B·A`); repeatable, merged in order; each merged tensor joins `--changed` | +| `--lora-strength F` | strength for every `--lora` (default 1) | +| `--lora-map FILE` | JSON map for adapters whose module names differ: `{"strip":[prefix…],"modules":{"":[target…]}}`; one `*` matches a block index; several targets mean a fused module (rows split in order) | +| `--plan-only` | declare, plan every encoding, print the plan, stop before writing | +| `--report N` | print the N worst quantisation errors (default 20) | +| `-v, --verbose` | per-weight detail | + +`--reuse` matches a weight on its **encoding, quantiser, options and shape — not on its source +bytes** — and carries whatever calibration made the old planes. Use it only when source and +calibration are unchanged, or name the changed tensors with `--changed`. `--in-place` refuses to +change a weight the container holds or to add an expert. + +## The recipe language + +``` +# PATTERN QUANTISER OPTIONS +blk.*.ffn_*_exps.*.weight gptq codes=i4 group=128 scale=bf16 transform=fwht128 calib=$CALIB +blk.*.ple_ngram.weight rtn codes=fp8_e4m3 block=*x* scale=bf16 rule=fixed scale_value=1.99317932128906e-4 +output.weight rtn codes=fp8_e4m3 block=128x128 scale=bf16 +blk.*.weight rtn codes=fp8_e4m3 block=128x128 scale=bf16 +``` + +- One rule a line: `PATTERN QUANTISER [key=value …]`. `#` starts a comment. +- `PATTERN` is a glob over the **whole logical weight name**: `*` crosses dots, `?` is one character. +- **The first matching rule decides.** No rule → the checkpoint's own encoding, which must be one + the engine serves as is: `bf16`, `f16`, `f32`, or block-scaled FP8 (`X.weight` with + `X.weight_scale_inv`). Anything else is named and refused. +- `$NAME` / `${NAME}` in a value comes from the environment; an **unset variable is refused**, never + expanded empty (so an empty `$CALIB` cannot silently produce an uncalibrated convert). +- A quantiser may **decline** a weight (`RAD_E_UNSUPPORTED`), which keeps the checkpoint's encoding + — `rtn` declines a rank-1 tensor, so a pattern that also matches a norm leaves the norm alone. +- An option the quantiser does not declare is refused, by name, before anything is quantised. +- Two plugins naming the same quantiser are refused, both named. + +## Quantisers + +| quantiser | what it does | +|---|---| +| `rtn` | round to nearest on a grid; each block's scale chosen by `rule` | +| `gptq` | error feedback against the layer's input Hessian (`calib=DIR`, damped by `damp` = 0.01 of the mean diagonal by default); `act_order=1` quantises columns by descending input energy and stores the permutation; `cd=N` adds coordinate-descent sweeps | +| `awq` | per-input-column scaling to a searched power, then a per-block clip search, both against the Hessian; column scales are the last scale level | +| `autoround` | sign-gradient descent on a rounding offset per value and a range factor per block; `iters` steps; slow (a 27B dense model takes most of a working day at defaults; `iters=50` a quarter) | +| `paroquant` | channel scaling plus `stages` Givens rotations learned against the Hessian, then AutoRound on the rotated weight; the `givens` transform | +| `m2xfp4` | M2XFP's weight-side Sg-EM: MXFP4 plus one metadata byte per group of 32; its own `m2xfp4` scheme; `adaptive=0` pins the bias, `wide=1` widens it | +| `ggml` | a llama.cpp format (`q4_0`, `q4_1`, `q5_0`, `q5_1`, `q8_0`, `q2_K`…`q6_K`, `iq4_nl`, `iq4_xs`, `iq2_xxs`, `iq2_xs`, `iq2_s`, `iq3_xxs`, `iq3_s`, `iq1_s`, `iq1_m`), by llama.cpp's own search, imatrix-weighted, codes byte for byte | +| `cast` | a float weight in another float dtype (`dtype=` f32, bf16, f16, fp8_e4m3, fp8_e5m2), no scale | +| `import` | rad-convert's own, not a plugin: write the numbers a quantised release ships, in the spelling the kernels read, instead of quantising again | + +Every quantiser except `m2xfp4`, `ggml`, `cast` and `import` takes the grid options: + +| option | values | +|---|---| +| `codes` | `i2`…`i8`, `i16`, `i32` symmetric; `u1`…`u8`, `u16` with `zero=`; `fp8_e4m3`, `fp8_e5m2`, `fp6_e2m3`, `fp6_e3m2`, `fp4_e2m1` | +| `table` | a codebook the codes index: `nf4`, `iq4nl`, `w4nl`, `w5nl`, `binary`, `ternary`, or `;`-separated values | +| `group=N` / `block=RxC` | where the scales sit; `*` is a whole extent | +| `scale` | f32, bf16, f16, fp8_e4m3, e8m0; under `scale2` also an integer sub-scale `u4`…`u8`, `i6`, `i8` | +| `scale2`, `group2` / `block2` | a second scale level the first is relative to (default the whole tensor) | +| `scale2_value` | the second level fixed at this value, exact in `scale2`'s dtype | +| `zero` | an integer zero point per block — an asymmetric grid | +| `transform=fwhtN` | rotate each row by the Hadamard first | +| `rule` | `absmax`, `mx` (OCP shared exponent), `fixed` (`scale_value=`), `search` (least squared error over candidates and refits, imatrix-weighted) | +| `clamp` | a signed code's range: `full` or `sym` | + +Named grids: MXFP4 = `codes=fp4_e2m1 group=32 scale=e8m0`; NVFP4 = +`codes=fp4_e2m1 group=16 scale=fp8_e4m3 scale2=f32`; NF4 = `table=nf4 group=64`; a K-quant-like grid += `codes=i4 group=32 scale=u6 scale2=f16 group2=256`; a ternary BitNet weight = `table=ternary`. + +**A container can hold only what some kernel reads.** rad-convert checks every planned encoding +against the `layout` hooks of the kernels declare resolved and refuses one no kernel accepts. libr4d +reads the plain floats; block-scaled fp8 and fp8 rows; int8 block rows plain or rotated; routed +experts as fp8, int4 (`i4*bf16[1x128]/fwht128`) or the `w4nl`/`w5nl` codebooks under E4M3 scales; +the dense w4, w8 and mxfp4 GEMM rows; and the 2-bit draft head. See [[Radiance Plugins]] for the +encoding model. + +## Importing a release someone else quantised + +These forms are read weight by weight and written in the spelling the kernels read; a recipe rule +re-quantises from the exact dequantisation where the family's kernels want another form. + +| `quantization_config` | read as | notes | +|---|---|---| +| `quark` MXFP4 (fp4, group 32, e8m0) | `fp4_e2m1*e8m0[1x32]` | byte for byte; also servable as it stands | +| `paroquant_mxfp4` | `fp4_e2m1*e8m0[1x32]/givens` | codes/exponents byte for byte plus learned rotations as `givens` tables | +| `paroquant`, `int5-bitplane`, plain `paroquant` | `u5*f16[1x128]-u8[1x128]/givens`, `u4*…` | AWQ-packed nibbles re-laid as a code bit stream; f16 scales, integer zeros, rotations | +| `compressed-tensors` `pack-quantized`, 4-bit `int`, group, act-order none/static/weight | `i4*[1xG]` | `weight_packed` made signed beside `weight_scale`; group/dynamic reorder is refused | +| same with `symmetric:false` (AWQ) | `u4*[1xG]-u4[1xG]` | nibbles plus `weight_zero_point`, bit for bit | +| `auto-round` `auto_round:auto_gptq`, `gptq` v1 | `i4*bf16[1x128]` or `i8*bf16[1x128]` | scales rounded to bf16; convert-time; `import` keeps shipped f16 | +| `nvfp4-pack-quantized` | `fp4_e2m1*f32[1x16]` | `weight_scale / weight_global_scale`; **no kernel serves NVFP4**, so it always takes a rule | +| `modelopt` NVFP4 / FP8 / MIXED_PRECISION | NVFP4 `fp4_e2m1*f32[1x16]`; FP8 `fp8_e4m3*f32[1x*]` | ModelOpt's second scale **multiplies**; `input_scale` is not read | +| compressed-tensors all-FP8 groups | `fp8_e4m3*[1x*]` or `[RxC]` | nothing imported: stock FP8 with its scale; a per-tensor scale is written once a row | +| GGUF `Q8_0`, `Q4_K`, `Q6_K` | `i8*f16[1x32]`, affine u4/u6…, `i6*i8…` | blocks split bit for bit; **not servable as they stand** | + +The width is read **per tensor**, so a mixed release imports int4 experts and int8 projections +together. What an import cannot read (an asymmetric GPTQ zero point, a group-reordering act-order +index, a GPTQ group under 128, a missing/zero global scale, an unlisted format) is refused by name. +The release's `quantization_config` names the tool, not a served form, so it is kept as +`imported_quantization_config.*`. + +`import` options: `codes=uN` makes a symmetric grid unsigned under a zero point of `2^(N-1)`; +`zero=` the zero plane's dtype (`u4`, `u8`); `scale=` f16/bf16/f32, each shipped scale rounded to +nearest; `block=RxC` repeats an FP8 scale shipped per row/tensor over that grid. It reads an imported +integer grid or FP8 with a per-row/per-tensor scale and keeps a plain float weight as it is. + +**NVFP4 to MXFP4 rounds twice** (e4m3 per 16 → power of two per 32), so it loses more than MXFP4 made +from bf16. Prefer a calibrated MXFP4 container when one exists; the quality verdict is a KL gate, not +a file size. + +## The descriptor law (`--set`) + +The architecture plugin is chosen by the exact `(arch id, quant descriptor)` pair. A convert-time +choice that must also hold at serve time is stated once with `--set`, which hands it to declare and +writes it into the container. Storage format is not such a choice (the encoding is in the container +and the plugin reads it there), but the **serving plugin** is: a `quantization_config.*` key +re-derives the descriptor. + +```sh +rad-convert -o q38-27b-fp8.rad \ + --recipe data/recipes/q38-27b-fp8block-df2.recipe \ + --set quantization_config.quant_method=fp8 --set quantization_config.fmt=e4m3 \ + --set "quantization_config.weight_block_size=128 128" \ + --set quantization_config.activation_scheme=dynamic +``` + +A bf16 checkpoint whose recipe writes Qwen's block FP8 must therefore be declared *as that format*; +otherwise the selection pair does not match any plugin and the convert (or the boot) is refused. The +same law bites the other way when a release's own `quant_method` names a pair no plugin claims. + +## Inspect and validate the result + +```sh +rad-info out.rad # architecture, source quant, recipe, sections +rad-info --recipe out.rad # the expanded rules — did the intended one win? +rad-info -v out.rad | head -40 # every tensor, its encoding, and what made it +rad-kbench -m out.rad --home $RADIANCE_HOME --report kbench.md +radiance --model out.rad --debug-graph # every op resolves +``` + +The converter's own error report (the worst `--report N` weights) measures each quantised weight +against its source through the core decoder. `rad-kbench` adds the artifact test: weight operands +read from the container and passed through each kernel's `layout`/`relayout` hooks. Both are on +[[Radiance Tools]]. + +## Calibration (GPTQ, AWQ, AutoRound, ParoQuant) + +`calib=DIR` is a directory an engine run wrote with `RADIANCE_CALIB_DIR` set: each layer's **Gram +matrices of its input**, in the domain the kernel multiplies it in. A block that characterises an +activation declares an f32 `[K,K]` PERSIST buffer and a `gram_accum` op; at `RADIANCE_CALIB_TOKENS` +(default 65536) the run writes the files. + +Three-step bootstrap: convert with plain `rtn` → serve that container with `RADIANCE_CALIB_DIR` → +push real prompts through prefill → convert again with the Hessians. The second moment is robust to +the first container's small quantisation error. A weight with no file is quantised uncalibrated and +the converter says so; `awq`, `autoround` and `paroquant` fall back to the imatrix diagonal when one +is given. + +- The accumulator must be **PERSIST**: a transient buffer may share bytes and be overwritten between + the launches it sums. +- Files are per rank; the consumer sums them. Under expert parallelism a rank's `down` input covers + only its experts' tokens; `gate_up` input is the full token set on every rank, so the sum scales + `H` by world size — GPTQ is invariant to a positive scale. +- Under tensor parallelism a column-split linear sees only its rank's columns, so a tap may write + `gram.r..part.bin`; libquant places the parts on the diagonal in rank order, leaving + cross-rank products zero. They must cover K and be in the model's own domain. +- `--imatrix` supplies the expert-popularity profile the placement planner ranks units by, the + per-column importances a quantiser uses, and the error report's weighting. + +## Reuse, drafters, merging + +```sh +# re-quantise only what changed +rad-convert $SRC -o new.rad --recipe new.recipe --reuse old.rad --changed changed.txt + +# merge a drafter shipped as its own repo (DFlash2, DSpark, MTP assistant) +rad-convert $SRC -o out.rad --draft-model /srv/models/z-lab/Qwen3.8-27B-DFlash2 \ + --recipe data/recipes/q38-27b-mxfp4-df2.recipe + +# merge a PEFT adapter at convert time +rad-convert $SRC -o tuned.rad --lora ~/run/adapter --lora-strength 1.0 \ + --recipe data/recipes/q38-27b-mxfp4-df2.recipe +``` + +- A drafter's tensors go in under `draft.`, its config under `draft.` keys. It has no embedding and + no `lm_head` of its own; it uses the target's. +- `--lora-map` exists for adapters trained on names the checkpoint does not use; a fused target + (`qkv`) takes several targets and splits the adapter's rows in order. +- `--add` + `--config` + `--tokenizer2` assemble a ComfyUI-style release of separate single files + (Anima: diffusion model, text encoder, VAE). A name in two files is refused. + +## `rad-export` — finetunes without hand-writing the command + +```sh +rad-export ~/outputs/my-run/checkpoint-400 --dry-run # what it would run +rad-export ~/outputs/my-run/checkpoint-400 # adapter -> .rad +rad-export ~/exports/merged -o ~/models/mine.rad --recipe q38-27b-fp8block-df2 +``` + +| flag | default | meaning | +|---|---|---| +| `SRC` | required | a merged 16-bit checkpoint (`config.json` + `*.safetensors`) or a PEFT adapter (`adapter_config.json` + `adapter_model.safetensors`) | +| `-o, --out FILE` | SRC's folder name + `.rad`, beside SRC or in `out_dir` | the container | +| `--recipe NAME\|FILE\|auto` | `auto` | a recipe name, a file, or the one recipe whose header names the base model, takes a bf16 checkpoint and needs no calibration | +| `--base DIR` | the adapter's `base_model_name_or_path`, from disk or the HF cache, else downloaded | the base a LoRA merges into | +| `--draft DIR\|ORG/NAME` | the config, else the Hub repo the recipe's usage line names | the drafter when the recipe merges one; a usage line with a placeholder asks for `--draft` | +| `--image IMAGE` | `$RADIANCE_IMAGE`, the config's, then `stilldeadcode/radiance` | run `rad-convert` in this image (docker, CPU-only, no network, **not pulled**) | +| `--rad-convert PATH` | — | run this binary instead of docker; recipes come from its `share/radiance` | +| `--memory M` | — | docker memory limit for the convert | +| `--dry-run` | — | resolve recipe, drafter and paths; print the `rad-convert` line; write nothing | +| `--keep-merged` | — | keep the merged checkpoint beside OUT | +| `--config FILE` | `~/.config/rad-export/config.json` | the config below | +| `--list-recipes [--json]` | — | list the recipes the image/installed tool carries | + +- Input: a merged 16-bit checkpoint (`config.json` + `*.safetensors`) or a PEFT LoRA adapter + (`adapter_config.json` + `adapter_model.safetensors`). +- An adapter is merged **exactly as PEFT's `merge_and_unload`** computes it on CPU: `scale` = + alpha/r (alpha/√r with rsLoRA), `rank_pattern`/`alpha_pattern` honoured, delta in fp32 rounded to + the adapter's dtype. It streams tensor by tensor into 1 GiB shards, so a 27B merge needs a few GiB + of RAM, not the model twice. Plain linear LoRA only: embedding LoRA, trained biases, DoRA, saved + modules, convolution LoRA, a rank other than the config's, and a missing target are refused before + anything is written. The adapter's tokeniser/template replace the base's where shipped. +- The recipe comes from the image (`/opt/radiance/share/radiance/recipes`) or the installed + `rad-convert`'s share; `auto` matches the base model's name in recipe headers and asks when several + fit. A recipe needing calibration is listed but never picked by `auto` — a finetune's Hessians are + not the stock model's. +- It runs `rad-convert` on the CPU, under Docker as the caller with no network, and writes the log to + `OUT.log`. +- `~/.config/rad-export/config.json` pins what auto cannot know: `image`, `out_dir`, + `recipes{model:recipe}`, `drafts{recipe:dir}`, `bases{repo:dir}`. +- It is Python 3 with no dependencies; merging needs `torch`/`safetensors`, fetching a missing base or + drafter needs `huggingface_hub`. The image carries no Python; extract the script with + `docker run --rm --entrypoint busybox stilldeadcode/radiance cat /opt/radiance/bin/rad-export`. + +## Scoring a container (the KL mode) + +```sh +# once, on the bf16 container +radiance --model bf16.rad --kld-record /srv/kld/q38 --kld-corpus corpus.jsonl +# each candidate, same flags otherwise +radiance --model quant.rad --kld-ref /srv/kld/q38 --kld-out report.json +``` + +- The corpus is JSONL, one `{"prompt", "score_from"?, "source"?}` a line; only `prompt` is required. + Positions `[score_from, n-1)` are scored; `source` groups the report. Keep the scoring corpus + disjoint from the calibration corpus. +- `--kld-record` writes the reference's log-probabilities (f16 over the vocabulary, the 16 most + likely tokens exactly, the token ids). DIR is never overwritten and is read only once `kld.json` + exists, which is written last. +- `--kld-ref` scores this model's f32 logits against stored rows: mean, median, 99/99.9/99.99th + percentile KL, top-1 agreement, and both models' perplexity. `--kld-out` writes the report; `FILE.rows` + is `[positions, 4]` f32 (KL, both NLLs, argmax agreement) for position-by-position comparison. +- The mode turns speculation and the prefix cache off, turns the step pipeline off, and refuses an + explicit spec depth above 0. The logits buffer holds a row per token of a step, so a large + `--max-num-batched-tokens` costs pinned host memory as well as VRAM. +- `--kld-lora NAME` scores every document through a served adapter, against a reference recorded + from the merged checkpoint; `--kld-lora-sources PREFIX` runs only matching documents through it, + the rest as the base, in the same steps — each document's rows should be byte-identical to the + all-adapter and all-base runs. + +The gate the fleet uses (mean KL ≤ 0.03, top-1 ≥ 95 %) is on [[frank-archon]] and +[[Quantization Production]]; the mode here is only the instrument. + +## Convert laws paid for in full + +- **`--plan-only` first.** A plan that resolves every op will boot; it costs seconds against an hour + of I/O on a large checkpoint. +- **Declare runs at the step and context bounds, and some graphs depend on them.** Qwen4-Exp sizes + its QSA buffers by `--max-ctx` and refuses a `--max-tok`/context pair whose score plane would pass + 2 GiB; the defaults (8192 tokens, the checkpoint's training context) fit most models, and a + generator's largest pass may override `--max-tok`. +- **Scale names follow the tensor.** `scale_name()` appends `_scale_inv` to the *tensor* name, so a + stacked `…experts.gate_up_proj` keeps its scales at `…gate_up_proj_scale_inv`. +- **File shadowing: first wins.** The reader opens index-named files sorted and emplaces every tensor + each file physically holds, first-wins. A merged PLE shard must sort first + (`00-ple.merged.safetensors`) or a raw shard wins the name. +- **Never write through a staging symlink.** A checkpoint may symlink its raw index; `open(path,"w")` + writes through it and corrupts the checkpoint. Write a temp file and `os.replace`. +- **One encoding per expert table.** A whole expert kept bf16 is the only thing legal beside a + table; a split tier (down=fp8, gate_up=w4) is unservable. +- **The descriptor pair has no fallback** (above), and **convert is card-less**: GPTQ solves on CPU + BLAS, so the GPU is irrelevant to production and the card only decides what can be decoded. + +## Related + +- [[Radiance Model Families]] — the (arch id, quant descriptor) table and the command per model. +- [[Radiance Plugins]] — quantiser and kernel-plugin contracts behind these options. +- [[Radiance Tools]] — `rad-info`, `rad-kbench`, `rad-tune`. +- [[Quantization Production]] — the fleet's calibration/KLD campaign and its gates. +- [[frank-archon]] — the deployment whose recipes and convert laws these are. diff --git a/serverAppConfigs-docs/LLM/Radiance Engine.md b/serverAppConfigs-docs/LLM/Radiance Engine.md new file mode 100644 index 0000000..75af0ac --- /dev/null +++ b/serverAppConfigs-docs/LLM/Radiance Engine.md @@ -0,0 +1,140 @@ +--- +type: "reference" +title: "Radiance Engine — the inference server behind frank-archon" +description: "The radiance inference engine: its shape, plugin model, container format, and how the reference pages fit together." +tags: + - llm + - radiance + - reference +timestamp: 2026-10-30 +--- + +# Radiance Engine — shape, plugin model, container + +Radiance is StillDeadcode's inference server for AMD GPUs, written in C++20 and HIP: language +models, plus image, video, speech and music generation and embeddings. One HTTP front end speaks +OpenAI's chat, completions, Responses, images, audio and video APIs, Anthropic's Messages API and +llama-server's native one. Nothing in the serving path is Python. + +The fleet's only radiance endpoint is [[frank-archon]] (2× R9700, TP=2, Dankserver). **This page +and its siblings own the engine reference; the live deployment shape, its Eras and its measured +laws stay on the endpoint page.** The numbers here describe upstream `main` as read 2026-10-30 +(release 1.14.0, plugin ABI 31). The fleet pin is older (1.8.1): a flag below that post-dates the pin +is a boot refusal, never a silent no-op, and the endpoint page records the CLI families that pin +itself retired relative to its own rollback target. + +Source: `https://codeberg.org/StillDeadcode/radiance` at `8da043c`. Engine docs live in `docs/` +(`GUIDE.md` for serving, `TOOLS.md` for tools, `PLUGIN.md` for plugins, `ARCHITECTURES.md` per +family, `OPS.md` for the op vocabulary, `spec.md` for the design). + +## The four moving parts + +```mermaid +graph LR + A[Checkpoint
safetensors / GGUF] -->|rad-convert + recipe| B[.rad container] + B --> C[radiance engine] + C --> D[OpenAI / Anthropic /
llama-server wire APIs] + P1[kernel library .so
libr4d, libavx, libref] --> C + P2[architecture .so
one per arch id × quant descriptor] --> C + P3[quantiser .so
libquant] -->|rad-convert| A +``` + +- **Engine** — `core/`: declare phase, placement/memory planner, scheduler, KV manager, sampler, + tokeniser, HTTP server, media preprocessing. +- **Kernel libraries** — implement ops for hardware. `libr4d` targets RDNA4 (`gfx1200`, + `gfx1201`); `libavx` serves the host backend (AVX-512/AVX2) with no GPU at all; `libref` is the + slow reference implementation every other library is checked against. +- **Architecture plugins** — declare one model family at one weight format: its weights, buffers, + KV groups and the ops it issues each step. The engine picks one by an **exact (architecture id, + quantisation descriptor) pair, with no fallback**. +- **Quantiser plugins** — `libquant`'s `rtn`, `gptq`, `awq`, `autoround`, `paroquant`, `m2xfp4`, + `ggml`, `cast`, `import`; they write a weight's numbers into an encoding's planes. +- **Tools** — `rad-info`, `rad-convert`, `rad-export`, `rad-kbench`, `rad-tune`, `rad-schemas`, + `rad-tokcheck`, plus `rad-chatparse`, `rad-avx-check`, `r4d_selftest` in the build tree. + +## Two phases, one static graph + +`rad_arch_declare` runs once per rank and names every weight, buffer, KV group and op, in order. +The core resolves every op against the kernel hierarchy per band, per domain, and completes the +whole plan before a byte of weights is read. `rad_arch_step` then only *issues* handles obtained at +declare; it may not allocate and may not synchronise. A model whose declare succeeds is fully known +statically, which is what makes `--debug-graph`, the buffer liveness plan and `rad-convert`'s +plan-only check possible. + +Consequences a plugin author must respect: + +- An op that never resolves is `RAD_NULL_HANDLE` and only fails if issued. A fusion is a *selection*: + ask for the fused op, emit the chain when `rad_op_resolved` is false. +- Declaration order is the buffer planner's liveness. A drafter must be declared after the trunk + it runs behind, or trunk and drafter buffers silently share bytes. +- Every op in the graph must have a row in a loaded kernel library. `libref` implements the + conventional vocabulary in `docs/OPS.md`; device-only ops (most fusions) exist only in `libr4d`. + +## The `.rad` container (RAD2) + +A container is one file holding everything the server needs: weights as **canonical encoding +planes**, the tokeniser (and optionally a second vocab), the chat template, metadata, the recipe it +was made with, and any draft head. It names no kernel library. + +- Planes are kernel-independent: codes, scales, zero points, codebooks — row-major, bit-packed + sub-byte where needed. A kernel rearranges them into its own layout at load (`layout`/`relayout` + hooks), losslessly, so a kernel library can change layouts without reconverting the model. +- Encodings are data, not an enum: `RadEncoding` names the scheme (`plain`, `affine`, or a + quantiser's own), the planes by role, the scale grouping, and a transform (`fwhtN`, `perm`, + `givens`). That is how one file can hold block-FP8, int4, MXFP4, NVFP4-imported, ParoQuant + rotations and llama.cpp grids. +- `rad-info` is the reader: sections (strings, metadata, directory, planes, encodings, vocab, + profile, data), `--meta`, `--vocab`, `--recipe`, `--plugins`, and `-v` for every tensor with its + encoding and what made it. + +See [[Radiance Conversion]] for producing one and [[Radiance Tools]] for inspecting it. + +## Selection key and version law + +- A checkpoint's architecture id is its `config.json` `model_type` without underscores; `rad-convert + --arch ID` overrides it. `rad_arch_quant` supplies the quantisation descriptor (empty for an + unquantised checkpoint). +- **No fallback.** A raw official FP8 checkpoint whose `quantization_config.quant_method` is `fp8` + is not the pair the `qwen4exp_fp8` plugin claims (`(qwen4exp, "")`), so both serve and convert + refuse it until the descriptor is changed with `--set` or a staged `config.json`. The deployment + lesson is on [[frank-archon]]; the mechanism is on [[Radiance Conversion]]. +- `RAD_ABI_VERSION` covers every header a plugin compiles against. A plugin reporting a different + number is refused at load, with both numbers printed. It moves on any shape change, not on a new + optional export; rebuild when it changes. Release version and ABI are independent: `/version` + reports the release, `rad-info --plugins` reports what loaded. + +## Environment + +| variable | meaning | +|---|---| +| `RADIANCE_HOME` | plugin search path: `:`-separated homes, each holding `kernels/`, `architectures/`, `quantizers/`; first wins, same-name files shadow later homes. Compiled-in default is `/share/radiance` | +| `RADIANCE_KERNELS` | default for `--kernels`: ordered kernel libraries, first wins; unnamed libraries are not loaded | +| `RADIANCE_TUNE` | pin a kernel's tuned axes for a measurement (a measurement aid, not a deployment setting) | +| `RADIANCE_CALIB_DIR` | a serve run records per-layer input Gram matrices here (the GPTQ calibration tap) | +| `RADIANCE_CALIB_TOKENS` | tokens after which the tap stops and writes (default 65536) | +| `RADIANCE_LOG`, `RADIANCE_LOG_STEPS` | log level; per-step token counts | +| `RADIANCE_DEBUG_RESID` | residual-stream probe per (layer, site) | +| `RADIANCE_DFLASH_Q_TEMP` | temperature of a DFlash2 coupled walk's candidate softmax | + +The full list of debug knobs (`RADIANCE_DEBUG_*`, `RADIANCE_DUMP_*`, `RADIANCE_OP_ORACLE`, …) is in +the source's `getenv` calls; none is a deployment setting. + +## Where the pages are + +| Page | Owns | +|---|---| +| [[Radiance Serve Options]] | every serve flag, grouped, with defaults and the law behind each knob | +| [[Radiance HTTP API]] | endpoints, request fields, streaming, tool calls, refusal rules | +| [[Radiance Conversion]] | `rad-convert`, the recipe language, quantisers, imports, reuse, `rad-export`, KL scoring | +| [[Radiance Model Families]] | which plugin claims which model, and the conversion command per family | +| [[Radiance Plugins]] | kernel and quantiser plugins: ABI, rows, hooks, encodings, build and checks | +| [[Radiance Architecture Plugins]] | declaring a model: weights, buffers, KV groups, ops, step, probe | +| [[Radiance Tools]] | `rad-info`, `rad-kbench`, `rad-tune`, `rad-schemas`, `rad-tokcheck`, harnesses | +| [[Radiance Operations]] | the container contract, host prerequisites, readiness, ledgers, restarts | +| [[frank-archon]] | the fleet's live radiance endpoint: shape, Eras, rollback, measured laws | + +## Sources + +- `README.md`, `docs/GUIDE.md` §1–§4, `docs/TOOLS.md`, `docs/PLUGIN.md`, `docs/DOCKER.md`, + `docs/ARCHITECTURES.md`, `docs/OPS.md`, `spec.md` §2–§4, `abi/*.h`, `core/config.cpp`, + `core/rad_core.h` at commit `8da043c` (codeberg.org/StillDeadcode/radiance). diff --git a/serverAppConfigs-docs/LLM/Radiance HTTP API.md b/serverAppConfigs-docs/LLM/Radiance HTTP API.md new file mode 100644 index 0000000..6f8f531 --- /dev/null +++ b/serverAppConfigs-docs/LLM/Radiance HTTP API.md @@ -0,0 +1,198 @@ +--- +type: "reference" +title: "Radiance HTTP API — endpoints, fields, refusals" +description: "The HTTP surface radiance serves: endpoints, request fields, streaming, tools, and refusal rules." +tags: + - llm + - radiance + - api + - reference +timestamp: 2026-10-30 +--- + +# Radiance HTTP API + +One server serves one model. It answers OpenAI's chat/completions/Responses/images/audio/video APIs, +Anthropic's Messages API and llama-server's native one, so their clients and SDKs work unchanged. +`model` in a request is accepted and **not enforced**. + +## Endpoints + +| route | what it serves | +|---|---| +| `POST /v1/chat/completions` | chat: tools, reasoning, streaming, grammars | +| `POST /v1/completions` | raw text completion | +| `POST /v1/embeddings` | an embedding head, else 501 | +| `POST /v1/images/generations`, `/v1/images/edits` | OpenAI images API; needs an image model, else 501 | +| `POST /v1/systemone` | LiquidAI d1 decisions: named yes/no, choice and score questions as probabilities; needs a d1 container, else 501 | +| `POST /v1/audio/transcriptions` | OpenAI speech-to-text; needs a transcribing plugin, else 501 | +| `GET /v1/realtime` (WebSocket) | streaming transcription session | +| `POST /v1/audio/speech` | speech (Breeze-TTS-2, drama-3) or a song (MiniMax-Music3), whole or streamed | +| `GET /v1/audio/speech/stream` (WebSocket) | text streamed in, each sentence's audio out | +| `POST /v1/videos`; `GET /v1/videos[/:id[/content]]`; `DELETE /v1/videos/:id` | video jobs made in the background, downloaded as MP4 | +| `POST /v1/messages`, `/v1/messages/count_tokens` | Anthropic Messages, for Claude Code and the Anthropic SDKs | +| `POST /v1/responses`, `/v1/responses/input_tokens` | OpenAI Responses, for Codex and SDK clients | +| `POST /completion`, `/completions`, `/infill` | llama-server native completion and fill-in-the-middle | +| `GET /v1/models`, `/v1/models/:id` | what is loaded, with `max_model_len`, `root`, `capabilities` | +| `GET /health`, `/v1/health`, `/ping`, `POST /ping` | liveness | +| `GET /load`, `/version`, `/props`, `/server_info`, `/diagnostics` | load; release version; llama-server props; full configuration; the startup report as JSON (`/diagnostics` is same-machine-only without an API key) | +| `POST /apply-template`, `/v1/chat/completions/input_tokens` | the rendered prompt; its token count | +| `GET /metrics` | Prometheus | +| `POST /tokenize`, `/detokenize`; `GET /get_tokenizer_info` | vLLM's shape and llama-server's at once | +| `POST /reset_prefix_cache`; `DELETE /sessions/