Repository navigation
docs: laya-first — the first request on a cold Lemonade is Laya's, and ours to answer - #942
Conversation
Lemonade starts with no model loaded, and the first request must be decided by
Laya and answered by one of our own models on the hub, on our kernels. Our fork
already routes every request through a collection.router policy engine, so the
gap is four things: a Laya-backed classifier condition (25 ms on HRX instead of
loading an LLM to decide), a shipped table naming our 1bit-MONSTER repos and
their device, the name 'auto' resolving to it, and the scorer warm at lemond
start.
Measured on the fork build 2026.46.0~1778.8888dd42 today: a deterministic
policy routes three prompts in 3-9 ms through /v1/routing/validate, while
'model': 'auto' answers model_not_found and a {"type":"laya"} classifier
answers 'unknown classifier type: laya' -- the two exact gaps this doc closes.
Sections: request flow with the fork's own symbols, the routing table format
with our real repos/files/devices/ctx, cold-start behaviour (preload, 627 ms
daemon, pull-on-miss with a budget and a marked degraded fallback), the override
rule, the laya-auto release-check row, the work order, and four owner decisions.
PR Reviewer Guide 🔍(Review updated until commit 45a5797)Here are some key observations to aid the review process:
|
Ran the installed 1bit route --classify (CPU scorer, pinned checkpoints) on the three prompts the note uses: code 0.44 in 361 ms, short 0.38 in 448 ms, and a 1,751-char document long_doc 1.00 in 0 ms via the size gate. Each invocation also pays 3.1-4.3 s of scorer load, which is the measured reason the design asks for a resident decision service instead of a process per request.
|
Persistent review updated to latest commit 8421ada |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 8421adaed. I ran the doc's "what exists today" section against the binary it names, and checked
every Hub claim; all of it holds.
The three probes, reproduced (~/wt/lemonade-46/fork/build/lemond, --version = 2026.46.0~1778.8888dd42,
exactly the build the doc cites; started on 127.0.0.1:13499 with an empty cache and config, killed afterwards — the
owner's :8088 embed server and the system lemond were not touched):
POST /v1/routing/validate, deterministic policy (keywords_any -> ZAYA1-8B, min_total_chars 2000 -> 27B, default 0.6B)
"def f(x): return x" 200 0.6 ms decision.route_to=1bit.ZAYA1-8B matched_rule="code" default_used=false
2170-char document 200 0.6 ms decision.route_to=1bit.Qwen3.8-27B matched_rule="long" default_used=false
one-line question 200 0.6 ms decision.route_to=1bit.Qwen3-0.6B matched_rule="" default_used=true
(trace shows each condition: keywords_any true / false, then min_total_chars true / false)
POST /v1/chat/completions {"model":"auto"} 404 {"error":{"code":"model_not_found", …}}
POST /v1/routing/validate, {"type":"laya"} 400 {"error":"Invalid routing policy: unknown classifier type: laya"}
So the three claims that matter for the design are exactly right: the validator and decision format work today, auto
does not exist, and laya is not a classifier type. My timings are 0.6 ms rather than the doc's 9/4/3 ms — same
order, faster box; the argument ("25 ms, not a model load") is unaffected.
The Hub table, checked against the API. HF has 68 models under 1bit-MONSTER (the doc's count), and every
row's repo exists with the file it names:
1bit-MONSTER/Qwen3-0.6B-GGUF Qwen3-0.6B-Q8_0.gguf OK
1bit-MONSTER/ZAYA1-8B-GGUF zaya1-8b-Q4_K_M.gguf OK
1bit-MONSTER/Qwen3-Coder-30B-A3B-Instruct-GGUF Qwen3-Coder-30B-A3B-Instruct-Q4_K_M.gguf OK
1bit-MONSTER/Qwen3.8-27B-UD-Q4_K_XL-MTP Qwen3.8-27B-UD-Q4_K_XL.gguf + MTP/mtp-…Q4_0.gguf OK / OK
1bit-MONSTER/Qwen3.6-35B-A3B-NPU model.q4nx OK (7 files, dir-mode)
The cited symbols exist where the doc says (offsets of ±1 line move with the fork's commits, which is expected):
Router::resolve_model_name router.cpp:161, try_get_model_info :165, Router::load_model :850,
parse_classifier_configs with the model-type validation block at routing_policy_parser.cpp:417-440,
OnebitServer::load onebit_server.cpp:220 and the serve -m … --device … --alias … argv at :247-251,
struct ClassifierServices routing_policy.h:162.
Docs only, no policy schema or behaviour change; tools/copyright.py --check rc 0 and the new file carries the Apache
notice; docs/laya.md only gains a pointer to it.
Two notes, neither a gate:
- The policy example's
scorer.hf_repois"mys/laya-typed-decisions-GGUF"— a placeholder for a scorer that is not
published yet. Worth saying so in one word, since every other repo in that block is real and checked. - The doc says the classifier type is "unknown"; once this work exists, an outdated fork that rejects
layawill
look identical to a current one that rejects a typo. The version string in the error, or aGET /v1/routing/types,
would separate "not implemented" from "misspelled" — a note for the implementation PR, not this one.
…ion as the only reason to take the NPU
Measured on strixhalo 2026-11-04 (llama-bench, same file, -r 2): a dense 27B-class model, Ternary-Bonsai-2-27B,
runs on HRX0 at 124-152 tok/s prefill / 15.3 tok/s decode against 13.9 / 3.05 on our own NPU route
(bonsai2-27b-t2s2, 916e90d) -- HRX wins both halves by 10.7-13.2x and 5.0x, and a post-reboot re-run with four
other lanes live (138.8/151.8/131.1 prefill, 13.9 decode) does not move it.
So the heavy classes' device is hrx by default and the NPU is a contention fallback, not a faster second half:
an HRX reply pins the shared iGPU for its whole length (512 tokens = 33 s), the NPU has its own device. The table
gains an optional per-class on_busy {device, model, queue_depth, tctl_c, wait_s}:
- signals are the ones the box already has: live tickets in the fleet's fair GPU queue (gpu-hold's FIFO) or a
foreign holder, and Tctl from k10temp temp1_input (the file gpu-hold's start gate and thermal guard read);
- decided once at load time, never per request, and the decision header must say why (device=npu, why=gpu_busy|gpu_hot)
because the decode penalty is 5x;
- off for the interactive classes, which would rather wait in the queue than answer 5x slower;
- thresholds are H0's to fill (power/heat of a 512-token HRX decode and what a 33 s iGPU pin costs other lanes),
so on_busy is absent from the shipped table until then, and section 7 gains the busy case as the fifth assertion
when it ships.
Doc-only; no code, no table shipped, no threshold invented.
|
New commit Input (measured on this box by the Bonsai hybrid lane, What the note now specifies — an optional per-class
No code, no shipped table, no threshold invented in this commit. |
|
Persistent review updated to latest commit d1fa3d5 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at d1fa3d5d6 (re-verdict: the two commits after 8421ada are content, not a main merge — 1f8852d + 8421ada are the doc, d1fa3d5 adds the "HRX by default, NPU only when the GPU is busy or hot" section, +46 doc lines).
Read: the measurement table (HRX0 124-152 prefill / 15.3 decode vs the NPU route 13.9 / 3.05 on the same file, -r 2, and the reboot re-run 138.8/151.8/131.1 with four lanes loading) is the kind of arithmetic that kills a design rather than promoting one, and the conclusion follows: device: hrx for the heavy class, the NPU as a contention fallback, not a speed one. The three properties are the ones that matter (decided once at load and said in the decision header, off for the interactive classes, thresholds left to H0 rather than guessed), and on_busy absent from the shipped table until H0 lands is the honest state.
One nit worth one sentence before merge: grep finds no code anywhere in the tree that reads laya-first.json or --laya-first — the whole format is still design (§Engine at the file's end lists it as work). So the new JSON example is an example of a file nothing parses yet, on_busy included. The section does say "absent from the shipped table", but a reader who lands on the field table first will not know that; a single line above the table ("nothing reads this file yet; the format below is the one the router will take") costs nothing and prevents the same trap #967 had to fix later.
|
Persistent review updated to latest commit e0589e7 |
|
Persistent review updated to latest commit 45a5797 |
Design note for the owner ask of 2026-11-03: Lemonade starts with no model loaded, and the first request must be decided by Laya and answered by one of our own models on the hub, on our kernels ("we need to own that space completely hip kernels and our npu engine").
What this doc says
Our fork already routes every request through a policy engine (
collection.router,docs/dev/router-policy.md): ordered rules over conditions pick a candidate model and the decision is reported inx-lemonade-route/x_lemonade_route. So the cold first request needs four things, none of which is a new router:1bit-MONSTERrepos, file, device (hrx/npu/hybrid) and ctx per class;autoresolving to that table;The measured before, on the fork build
2026.46.0~1778.8888dd42(2026-11-03, 127.0.0.1:13499, empty cache)POST /v1/routing/validate, deterministic policy, three promptscode→ ZAYA1-8B in 9 ms, a 2,170-char document → Qwen3.8-27B in 4 ms, a one-liner → default in 3 ms,matched_rule/default_usedcorrectPOST /v1/chat/completionswith"model": "auto"{"error":{"code":"model_not_found", …}}{"type":"laya"}classifier{"error":"Invalid routing policy: unknown classifier type: laya"}The routing engine, decision format and validator work today with zero new code; the two gaps are exactly Laya-as-a-condition and
auto-as-a-name.Contents
Request flow with the fork's own symbols (
Router::resolve_model_namerouter.cpp:160,try_get_model_info:164, routing enginerouting_policy.cpp/routing_policy_parser.cpp:417-440,Router::load_model:849,OnebitServer::loadonebit_server.cpp:220and the1bit serveargv at:242-250, theClassifierServicesseam atrouting_policy.h:162); the routing-table JSON with our real Hub repos and file names (verified against the Hub API: 681bit-MONSTERrepos, all four classes have a first-party repo with the file named); cold-start behaviour including preload, the 25 ms/627 ms/2.7 s decision costs, pull-on-miss with a budget and a marked degraded fallback; the override rule (an explicit model always wins, Laya failure isScore.ok=false→match_false→default_model); thelaya-autorelease-check row; the work order; and four owner decisions (the small NPU row is a FastFlowLM checkpoint, not ours; catalogue vs table repos;autoas a reserved name vs a collection; whether the firstcoderequest may pull 18.6 GB).Docs only — no code, no policy schema, no behaviour change.
tools/copyright.py --checkpasses; the new file carries the Apache notice.Refs: engine #186/#187 (routing RFC), #206 (GGUF scorer), #208 (HRX scorer),
docs/laya.md,docs/hybrid.md.