AI infrastructure
The inference plane is the most engineered corner of the platform: two consumer mini PCs with laptop-class iGPUs serve frontier-scale quantized models locally, fronted by an mTLS gateway that can fall back to remote providers. This page follows one request from silicon to tokens — and records why each layer exists.
Hardware floor
| Fact | Value |
|---|---|
| Nodes | kushira, sashina — Minisforum MS-S1 Max |
| APU | AMD Strix Halo, Radeon 8060S iGPU (gfx1151) |
| Memory | 128 GiB LPDDR5X unified, no VRAM split |
| GPU memory model | GTT, the Graphics Translation Table — GPU shares host RAM |
| Allocatable to k8s | ~116 GiB per node of a 125 GiB capacity |
| Scheduling label | node.kubernetes.io/instance-type: minisforum-ms-s1 |
The single design fact everything else derives from: unified memory. The 8060S and the CPU share one 128 GiB GTT pool, so "VRAM" is a policy decision, not a hardware boundary. A model that fits RAM can be served. Anything else must be quantized or routed elsewhere.
Serving path — live shape, Oct 2026
Memory dataflow, layer by layer
- Weights arrive via an init prefetch into a local-path PVC
(
cache-huggingface, 128–256 GiB) —qwen38-flash-next-w4b.hgnis 115.5 GiB including the multi-token-prediction MTP sidecar and tokenizer. The engine container itself makes no outbound connections. No runtime downloads happen. The cache claim is mounted at the default HF path. - Weights lock ~68 GiB of the node's unified memory from the w4b
checkpoint plus the KV pool reservation. The k8s memory request is 80 GiB
so the scheduler reserves practically the whole node. VPA was scoped
to CPU only in #2711 — memory is pinned, not autoscaled. One engine
per node:
podAntiAffinityonkubernetes.io/hostnameacross the 2-replica StatefulSet. - Decode runs on the iGPU over ROCm (
/dev/kfd,/dev/dridevice mounts). Unified memory means zero host→device copies: the GPU dereferences host RAM directly. - KV cache shares what weights leave free — context length and
concurrency trade directly against model size inside one pool.
Sessions are pinned to a pod via
x-sks-session-idheader affinity (BackendTrafficPolicy halogen-flash-affinity) so a conversation's KV stays warm on the node that built it. - Requests enter through the tailnet-only Gateway VIP, present a
client certificate — bare probes die at TLS with
000, by design — plus a gateway-issuedsk-inferkey, and carryx-ai-eg-modelfor route selection.
History: why this shape
- Ollama era, 2024 — single-model serving on the cluster. PVs moved around by hand. First appearance of "GPU node" scheduling.
- llama.cpp single node, 2025 to 2026 —
llama-serverper model, GGUFs prefetched by init containers into emptyDir.models-preset.inimaps gateway model names to local files. - RPC pair experiment, Sep 2026 — two llama.cpp processes bridged
over RPC as
rpc-head+rpc-worker, with macvlanbr1attach and chunked prefill tuning, to pool both Halos for one model. Deep effort: health probes on pod IPs, cache on local-path, RPC-peer alerts. Verdict: workable but prefill-bound — long prompts starved decode. - halogen-flash cutover, Sep 30 in #2703 — peonist
halogen-flash-serveradopted from a live evaluation in #2701: 10–20× prefill, 3× decode, 13× lower TTFT — time-to-first-token — at 32K depth vs the RPC baseline, on a single node. The RPC pair was removed the same day. Final removal landed Oct 6 in #2774. The route forqwen/qwen3.8-flashpoints at halogen with nous/openrouter as fallback tiers. - llama-cpp residual role, Oct 12 in #2875 — scaled to one
replica, embeddings only (
qwen/qwen3-embedding-8b, Q6_K,n-gpu-layers 999= fully offloaded to the iGPU). LLM serving is halogen's job now.
The lesson the platform took: on unified-memory APUs the bottleneck is prefill bandwidth, not weight capacity — an engine built for the memory model beats clustering general-purpose servers.
Gateway plane (apps/inference)
Gateway/inference(nsshikanime): client auth is API-key based —SecurityPolicy/inference-apikeychecks gateway-issued keys fromSecret/inference-key, one key per client identity. Upstream provider keys are NOT client credentials — they 401. TLS client-cert validation is configured on the EnvoyProxy listener plane.AIGatewayRoute default:x-ai-eg-modelheader → backend chain, each withmodelNameOverride:qwen/qwen3.8-flash→ halogen at priority p0 → nous → openrouter;z-ai/glm-5.3-flash→ z-ai-openai → nous;qwen/qwen3-embedding-8b→ llama-cpp → nous → openrouter. A catch-all.*route and further model routes for mistral and glm-5.3 exist — readaigatewayroute.yamlfor the live table.Backend/AIServiceBackendCRs resolve each tier: halogen via headless svc :8731. Remote providers use their own key-injectingBackendSecurityPolicys.- Timeouts:
AIGatewayRouterules setrequest: 0s, disabled — generation length is unbounded at the route. The llama-cpp HTTPRoutes carry the 600 s cap instead. - Known edges:
/mcp/z-ai500s from an ai-gateway v1.1.0 MCP header gap, tracked in manifests#1894. Anthropic-schema backends are only reachable via/anthropic/v1/messages. There is no OpenAI-to-Anthropic translation.
Observability
VMServiceScrape on halogen /metrics (60 s), HalogenFlashDown
VMRule, llama-cpp dashboard re-pinned, Envoy dataplane SLI rules with
gateway-name relabeling — the inference plane is fully on the fleet
dashboard stack (see Observability).