npu-forge / README.md
LJTSG's picture
v1.0: PROVEN end-to-end — forge tune produces coherent, in-band NPU models @ 41.9 tok/s. Q4_K_M fix (snags 9/10), 10-wall ledger.
89efab4 verified
|
Raw
History Blame Contribute Delete
6.47 kB
---
license: mit
tags:
- amd
- ryzen-ai
- npu
- xdna2
- fastflowlm
- strix-halo
- lora
- fine-tuning
- tools
pipeline_tag: text-generation
---
# NPU-Forge 🔥 — fine-tune a model and put it on your AMD Ryzen AI NPU in ~3 minutes
**Measured, on a Strix Halo (Ryzen AI MAX+ 395), June 2026:** a LoRA fine-tune of
Llama-3.2-1B on 300+ real chat exchanges — trained, merged, behavior-verified,
converted to GGUF, re-quantized to FastFlowLM's Q4NX, NPU-ready — in
**183 seconds of cloud time** (≈ $0.10 on a rented T4):
```
forge tune my-chats.jsonl --name grandma # proven: coherent + IN-BAND on NPU
├─ LoRA fine-tune (cloud GPU) 122 s
├─ merge 3 s
├─ voice proof (model speaks first!) 5 s
├─ HF -> f16 -> Q4_K_M GGUF 20 s
└─ GGUF -> Q4NX (NPU format) 24 s
forge register (one UAC click)
flm run grandma-forge:1b
```
The "voice proof" stage generates a sample from the merged model *inside the
training job*, before any conversion — so you know the tune actually took.
Ours came back with the persona's exact ritual phrases after 2 minutes of
training. That's the bar.
## What's in this repo
- **`forge.js` / `forge.bat`** — the CLI: `tune`, `convert`, `register`,
`list`, `doctor`, `serve`
- **`modal/tune_npu.py`** — the whole tune→NPU pipeline as one
[Modal](https://modal.com) job (bring your own Modal account; T4 is plenty)
- **`modal/convert_q4nx.py`** — just the GGUF→Q4NX stage (65 s for a 1B)
- **`bin/assemble.js`** — downloads results and stages the FLM model folder
- **`bin/register.js` + `register-admin.bat`** — the permanent custom-model
registry that survives FLM updates (see below)
- **`registry.example.json`** — entry template
Chat data format: one JSON per line, `{"messages":[{"role":"user","content":...},{"role":"assistant","content":...}]}`.
## The registry problem (why `forge register` exists)
FLM's `model_list.json` lives in `C:\Program Files\flm\` and **every FLM update
resets it**, silently de-registering all your custom models. Your model files
survive (they're in `Documents\flm\models\`) but they vanish from `flm list`.
Forge keeps its own user-space `registry.json` forever and re-merges with one
click. `forge doctor` tells you when an update has eaten your registrations.
## NEW in v0.3 — a voice-verifier "ear" that runs on the NPU
Train a ~111KB classification head over EmbeddingGemma-300m embeddings
(`modal/train_ear_head.py`, bring your own labeled texts), then run it locally
with `bin/ear.js` against FLM's `/v1/embeddings` (`flm serve <model> --embed 1`).
The embeddings come off the NPU; the head is plain JS. In our tests the
111KB head **matched a fine-tuned 268MB DistilBERT on real-voice accuracy
(95.9%) and beat it on the hard boundary cases**, live on a Strix Halo NPU.
Also measured: llama3.2:1b chat on the NPU = **47.8 tokens/s** including
prefill (FLM, performance pmode).
Snag #8: FLM's embeddings endpoint closes the TCP connection per request —
retry once on ECONNRESET (ear.js does).
`start.bat` gives you a menu: doctor / list / register / serve / tune guide.
## The snag ledger — ten walls we hit so you don't
1. **The Q4NX converter's `convert.py` CLI is broken at HEAD** (uncommented
debug `sys.argv` override hijacks every invocation). Call the module API:
`from q4nx import create_converter; create_converter(gguf, "").convert(q4nx_path=out, weights_type="language")`
2. Converter needs `einops` and `tqdm` beyond its README list, and **must run
with cwd = its repo root** (relative `configs/<arch>.json` loads).
3. **Llama-3.2 tokenizers need `transformers>=4.46`** — the error
`untagged enum ModelWrapper` is that wall exactly.
4. **`transformers 4.46` needs `accelerate>=1.0`** — the error
`'AdamW' object has no attribute 'train'` at step 0 is that skew.
5. **T4 + Llama-3.2's 128k vocab OOMs at batch 4** (loss-logits blowup).
Floor: batch 1 × grad-accum 8 + gradient checkpointing.
6. **NPU driver minimum for current FLM: `32.0.203.304`** (`.311`
recommended). `flm validate` will tell you; so will `forge doctor`.
7. **EmbeddingGemma needs `transformers>=4.5x` + `sentence-transformers 5.x`**
and the official weights are license-gated (use the `unsloth/` mirror, or
accept the Gemma license on your HF account + pass an `HF_TOKEN` secret).
8. **FLM's `/v1/embeddings` closes the TCP connection per request** — retry
once on `ECONNRESET` (the ear runtime does).
9. **For a FINE-TUNED model, exporting GGUF as `q8_0` produces repetition
garbage on the NPU** even though the merged model is perfect — the q8_0
then Q4NX re-quant is a lossy double-quantization. **Use `Q4_K_M`.**
10. **The Q4NX converter's llama path rejects `f16`** (`not enough values to
unpack` — it expects pre-quantized blocks). So the GGUF must be quantized
*before* Q4NX, and `Q4_K_M` is the format proven to produce a coherent,
in-voice NPU model. Pipeline: HF → f16 → `llama-quantize Q4_K_M` → Q4NX.
**Frozen known-good stack** (the whole point — never debug this again):
`torch 2.4.1 · transformers 4.46.3 · trl 0.9.6 · peft 0.12.0 ·
accelerate 1.1.1 · datasets 2.21.0 · gguf · amd-quark · einops · tqdm ·
protobuf` + a compiled `llama-quantize` (the Modal job builds it).
## Proven, measured (Strix Halo, June 2026)
A LoRA fine-tune of Llama-3.2-1B on 300 real chat exchanges, run through the
whole pipeline and served on the NPU:
- **Coherent and in-voice** — the persona's rituals and endearments intact.
- **41.9 tokens/s** on the NPU (FLM, performance pmode).
- **In-band against the source voiceprint** — mean 0.845 vs the original's
own held-out band of 0.83 ± 0.07 (3 prompts). A separate stylometric scorer
certified the NPU model speaks like the source it was tuned on.
That is the bar: not "it converts," but "it talks like itself, on the NPU."
## Requirements
- AMD Ryzen AI machine with XDNA2 NPU (Strix, Strix Halo, Kraken…) +
[FastFlowLM](https://github.com/FastFlowLM/FastFlowLM)
- Node.js (the CLI), Python + a [Modal](https://modal.com) account (the cloud legs)
- NPU driver ≥ 32.0.203.304
Part of an ongoing project to make local NPUs a first-class home for personal
AI — voices you own, on silicon you own.