Repository navigation
os/v3/puppy: the console names the engine of the model that is answering, not the first one loaded - #1050
Conversation
…ing, not the first one loaded
`engine_of` picked its model with `loaded.first()`, and `loaded` is every entry of
/v1/health's `all_models_loaded[]` in load order. Lemonade keeps one model per type
in that array (max_models: llm 1, embedding 1, ...), so an embedding loaded before
the chat model made the console print the embedding's recipe/device: measured
`llamacpp (cpu)` where the box was answering `flm (npu)`.
Prefer health's `model_loaded` ("the most recently accessed model"), then the first
loaded entry the catalogue knows, so a stale `model_loaded` cannot blank the row.
Closes #1049
|
#1050 ready for review at For the reviewer, the two things worth a look: (1) the resolution order in The negative control is in the body: the new test run against the old |
PR Reviewer Guide 🔍(Review updated until commit 4f061e9)Here are some key observations to aid the review process:
|
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 1301f42f — the bug is real, the fix is the right resolution order, and the negative control reproduces exactly as described.
Ran it here (cargo test --offline, thermal-run):
test result: ok. 52 passed; 0 failed; 0 ignored (51 before, +the new case)
cargo fmt --check rc 0
cargo clippy --offline --all-targets no warnings, no errors
Negative control, reproduced independently. I put the old engine_of body back inside my own review worktree (keeping the new serving parameter, unused) and ran only the new test:
thread 'facts::tests::the_engine_is_the_model_answering_not_the_first_loaded' panicked:
assertion `left == right` failed
left: Some("llamacpp (cpu)") <- the embedding, loaded first
right: Some("flm (npu)") <- what model_loaded names
test result: FAILED. 0 passed; 1 failed; 51 filtered out (exit 101)
So the test fails on the old body with precisely the pair in the body, and passes at this head. Restored afterwards; git status clean.
The frame does not move, checked rather than assumed:
$ 1bit-console-tui --snapshot 80x25 --fixture | diff -q - tests/snapshots/80x25.txt -> identical
$ 1bit-console-tui --snapshot 240x67 --fixture | diff -q - tests/snapshots/240x67.txt -> identical
LEMONADE up 8882 engine: llamacpp (gpu) | GPU HRX0 43C | NPU idle | MODELS 2
One detail worth calling out as done right: the device lookup moved to the resolved model (n == &e.model) rather than staying on the old name. Without that, engine_of would have picked the NPU model and then reported its device from the embedding's entry — the test would have caught it, but it is the half of the change that is easy to miss.
Residual, not blocking: when model_loaded is absent, null, stale or names a model the catalogue does not know, the fallback is still "first catalogue-known loaded entry", which in a two-type box can be the embedding again. all_models_loaded[] carries type (llm / embedding), so preferring llm there would close the last case — worth a line in the comment if you touch this again, not worth another push tonight.
|
Persistent review updated to latest commit 4f061e9 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 4f061e97 (rebase-only; my review stands).
4f061e97 is a rebase of the same change onto a main that has since taken #1045, #1046 and #1047, so a raw diff between the two heads shows those PRs, not this one. The PR's own lines are unchanged:
head 1301f42f own-diff hash 311fb6b6835aaed812a2abfae2cc2d3845c8ab909592a2c00daa56e3235ee2c3
head 4f061e97 own-diff hash 311fb6b6835aaed812a2abfae2cc2d3845c8ab909592a2c00daa56e3235ee2c3
Everything in my previous verdict therefore still holds at this head: cargo test --offline 52/52, the negative control failing on the old engine_of body with Some("llamacpp (cpu)") vs Some("flm (npu)"), both snapshots byte-identical, fmt/clippy clean.
I re-ran the suite at this head rather than assuming the rebase was inert:
$ cargo test --offline -> 52 passed; 0 failed
…_loaded` moves to an embedding `model_loaded` is "the most recently accessed model", so a request to an embedding model loaded beside the llm moves it and the row would name the embedding again. Lemonade types every `all_models_loaded[]` entry and keeps one llm per box (max_models.llm = 1), so take that entry and fall back to `model_loaded` only when the type is not reported. Closes #1049
|
Stronger selector added at Reviewing Lemonade's schema further showed my first rule was still a proxy: New rule: take the Negative control for this arm (same test, Re-verified on the rebased branch: I also measured the shape the box has not shipped yet, since it can change the 80x25 layout: with a hybrid |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at eff0bb4f — the selector change is the right fix and I re-ran everything at this head.
cargo test --offline -> 52 passed; 0 failed (the third assertion lives inside the existing test, so the count is unchanged)
--snapshot 80x25 --fixture byte-identical to tests/snapshots/80x25.txt
--snapshot 240x67 --fixture byte-identical to tests/snapshots/240x67.txt
Negative control, done the right way round. Reverting the call site to json_str(&body, "model_loaded") does not fail the test — the test drives llm_model_of() directly, so the wiring is not what it exercises. The control that does bite is making llm_model_of() itself ignore the type:
(make llm_model_of skip the "type": "llm" pass)
thread '…the_engine_is_the_model_answering_not_the_first_loaded' panicked:
left: Some("llamacpp (cpu)") right: Some("flm (npu)")
So the new assertion genuinely covers the "an embedding request moved model_loaded" case, and the llm-typed entry wins as intended.
One gap worth naming, and it is the same shape as the bug being fixed: nothing tests the call site. Facts::refresh could go back to json_str(&body, "model_loaded") and all 52 tests would still pass, because every assertion starts from llm_model_of(...) rather than from a health body through refresh. A test that drives the facts fetch with the embed_last body (the way the snapshot fixtures already drive the frame) would close it; it is a five-line test and not worth another push on its own.
Two small things I checked while reading llm_model_of(): the checkpoint fallback is a good belt-and-braces for a health entry without model_name, and returning model_loaded when no entry is typed llm keeps the previous behaviour for any Lemonade that does not report types — so an older box degrades to exactly what it did before this PR.
…ames, and --status is pinned to carry it (#1059) - README: the engine paragraph now states the selection rule (#1050) and cites the test; the Lemonade source row lists the health fields; 51 -> 52 tests. - README: records the measured hybrid shape (ryzenai-llm (gpu npu), 83 columns) re-wrapping inside the facts block at 80x25 with the log pane intact. - test: the_console_says_which_engine_is_serving also asserts --status carries "engine: llamacpp (gpu)", since a booted-image gate row reads --status rather than a terminal. No behaviour change: frames byte-identical, release binary differs only by the crate hash. Co-authored-by: bong-water-water-bong <bong-water-water-bong@users.noreply.github.com>
…nges and stops saying 52 tests (#1149) The crate's README describes the frame heal (#1093) but not the two changes that arrived with it, so a reader of the shipped crate cannot see why a control byte from Lemonade's log is harmless, or why an interrupted paint does not end the screen: * #1118 - facts::screen_text() cleans text from outside (CSI and OSC removed whole, TAB to a space, other C0/DEL dropped) before it becomes a cell. Measured against the recorded take's own log: the shipped c26 candidate (5b21a0a8, heal and no sanitiser) leaves 4 escape bytes in one rendered frame and on a 240x67 pty cuts its progress row at the first ESC[K; this build is 0 bytes. On the appliance's own 160x50 VT (booted c26 candidate, QEMU) the same wrapped progress line came back whole on BOTH binaries - the once-a-second full paint rewrites the cells the erase cleared - so the README calls this defence in depth rather than a visible repair, which is what the measurement says. Test: a_log_line_or_a_model_name_cannot_carry_an_escape_into_the_screen * #1102 - a paint that fails with ErrorKind::Interrupted repaints instead of ending the console (crossterm's SIGWINCH handler makes an ssh resize the realistic trigger); every other kind still exits to the dialog menu. Test: an_interrupted_paint_is_repainted_but_a_dead_terminal_ends_the_console Also corrects the test count: cargo test is 55, not the 52 the README has said since #1050/#1059. Docs only - no code, no frame, no gate string touched. cargo test 55/55, tools/copyright.py --check rc 0. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Closes #1049.
Root cause.
engine_of(os/v3/puppy/console-tui/src/facts.rs) resolved theengine: <recipe> (<backend>)row fromloaded.first(), whereloadedis every entry of/api/v1/health'sall_models_loaded[]in array order. Lemonade keeps one model per type in that array - its/v1/healthdocs show anembeddingentry beside thellmundermax_models: {llm:1, embedding:1, reranking:1, …}- and names the answering one separately inmodel_loaded("model name of the most recently accessed model"). So an embedding model loaded first made the row describe the embedding, which is the opposite of what the row is for.Measured before/after (same catalogue + health body as the docs,
nomic-embedonllamacpp/cpu loaded first,qwen3-0.6b-1bitonflm/npu loaded second,model_loaded: qwen3-0.6b-1bit), onmain1cd4e4bc:engine_ofanswerSome("llamacpp (cpu)")- the embeddingSome("flm (npu)")- what is answeringThe change is one resolution order:
model_loadedwhen the catalogue knows it, else the first loaded entry the catalogue knows (which also means a stalemodel_loadedno longer blanks the row), else nothing as before. Signature gains theservingargument; the one call site parsesmodel_loadedfrom the same health body it already reads. 6 existing assertions updated for the new argument, one new testthe_engine_is_the_model_answering_not_the_first_loadedcovering both the embedding-beside-llm case and the stale-model_loadedcase.Verification (all on this branch, built and tested through
~/lb/thermal-run.sh):cargo test --offline52/52 (was 51),cargo fmt --checkandcargo clippy --all-targetsclean.engine_ofbody with exactly the measured pair above, passes after.build-static.sh->fd8e80d9e32cb993, 1,581,392 B, still static (ldd= "statically linked", noPT_INTERP).--snapshot 80x25/240x67 --fixtureare byte-identical to the committedtests/snapshots/*.txt, so this PR does not move the frame at all (the fixtures load one model; os/v3/puppy: the console names the engine that is serving, and stops printing a URL before it has one #1041's engine row is unchanged) - no baseline churn for the next cut.vm-check.sh/puppy-check.shstring touched;tools/copyright.py --checkclean; no new files.Found by
pr_agenton #1041.