Repository navigation
serve: --device hybrid (external prefill provider + HRX decode) - #530
Conversation
PR Reviewer Guide 🔍(Review updated until commit 882a77b)Here are some key observations to aid the review process:
|
The prompt's KV cache comes from a prefill provider named by --prefill-provider or ONEBIT_HYBRID_PREFILL; llama-server restores it into its one slot and decodes on HRX (the CPU in a build without HRX). app/serve_hybrid.h holds the handoff: template and tokenize on the backend, the provider's state file, the slot restore, the request pinned to the slot with cache_prompt on, and the fallback to a plain request when any step fails. docs/hybrid.md states the contract. tests/hybrid_route.sh runs the path with the fake backend and a fake provider; tests/hybrid_e2e.sh runs it on a real model with the decoding server making the state file, and checks the greedy reply equals the plain run's and that the backend prefilled one token. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CodeQL flagged the restore path as user-controlled. The fake backend now accepts only a bare name inside --slot-save-path, as llama-server's fs_validate_filename does, and answers 400 otherwise. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
99c4563 to
fe391df
Compare
|
Persistent review updated to latest commit fe391df |
The restore path is taken from os.listdir of --slot-save-path, keyed by the requested bare name, so no request string reaches a path expression. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Persistent review updated to latest commit fe391df |
|
Persistent review updated to latest commit 999d885 |
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Persistent review updated to latest commit 102cf06 |
|
Persistent review updated to latest commit 882a77b |
1bit serve --device hybrid: the prompt's KV cache is computed by an external prefill provider (the NPU engine), llama-server restores it into its one slot and decodes on HRX (the CPU in a build without HRX, so the route runs anywhere). Public plumbing only: the provider is a program with a stated contract, the way-DONEBIT_NPU_PRIVATEkeeps the NPU kernels out of this tree. Stacked on #529 (no ONNX left).Per request (app/serve_hybrid.h, docs/hybrid.md): template + tokenize on the backend (
/apply-template,/tokenize, so the provider sees the backend's own ids) -> provider writes a llama.cpp sequence-state file into the backend's--slot-save-path->POST /slots/0?action=restore, checked to hold all the prompt's tokens -> the request forwarded withid_slot0 andcache_prompttrue; llama-server re-evaluates the last token and generates. One line per request on stderr: prompt tokens, prefill ms, restore ms, first byte ms. One slot (-np 1); handoffs serialized until the backend has started on the slot. Any failure (no provider, provider exit/timeout/no file, restore short) is said with its reason and the request is forwarded as it came: the device is never unusable because the provider is. Refused with hybrid:--mtp,--dflash,--mmproj,--moe-slots,--parallel> 1, roles,--pm; the GGUF's own MTP head is off.--print-backendexits 5 (needs serve).Provider contract:
<provider> --tokens <ids file, one decimal id per line> --out <state file, bare name in the slot dir> --model <path> [--elfs DIR]; stdout one JSON line{"n_tokens": N, "argmax": id, "prefill_ms": x}; exit 0. Named by--prefill-providerorONEBIT_HYBRID_PREFILL;ONEBIT_HYBRID_ELFSadds--elfs; the provider also getsONEBIT_HYBRID_BACKEND_URL(the decoding server's address). State file = whatllama_state_seq_save_filewrites (ids, then seq 0's cells: per layer K type/row size/rows, then V;v_trans0 with flash attention; the backend's cache type, f16 by default), matching the pinned llama.cpp's version.Tests
tests/hybrid_route.sh(CTesthybrid_route, no model): fake backend + fake provider (FAKE_PREFILL_COPY=1); checksid_slot0 /cache_promptreach the backend, the stderr lines, the slot directory lifecycle, the no-provider and failing-provider fallbacks, the refused flags,--print-backendexit 5. 21 checks pass.tests/hybrid_e2e.sh(CTesthybrid_e2ewithONEBIT_SERVE_TEST_GGUF, skips without a model or llama-server): Qwen3-0.6B-Q4_K_M on the CPU llama-server built from the pinned fork, on ryzen. The fake provider makes a real state file by having the decoding server prefill the ids into slot 0, save, and erase the slot, so the file is the only path for the KV. Result: the hybrid greedy reply equals the plain--device cpureply ("Three colours of the rainbow are red, orange, and yellow."), the backend'sprompt_nis 1 (the re-evaluated last token) against 22 for the plain run, no fallback line, streaming works, a second request answers the same.Not in this PR: the NPU prefill provider itself (private; the A1 proof on strixhalo writes the state file from the NPU engine's KV).
🤖 Generated with Claude Code