Repository navigation
docs: install the engine on any Linux with Lemonade from its own package (PPA) - #860
bong-water-water-bong wants to merge 26 commits into
Conversation
…ackage (PPA) The engine needs no patched Lemonade: its llamacpp-hrx recipe resolves the name llama-server inside the directory named by hrx_bin, so a stock Lemonade runs our engine once hrx_bin (env LEMONADE_HRX_HRX_BIN, or the config key hrx.hrx_bin) points at the package's lemonade-bin/ directory. - scripts/mk-plugin-package.sh builds 1bit-engine as a .deb and a .tar.zst: the engine, its HRX llama.cpp build, the HSA runtime it dlopens beside it ($ORIGIN), the sealed NPU kernel sets, and a llama-server shim that sets the engine's HRX environment (the recipe hands its backend only GGML_DISABLE_VULKAN=1) and the -ngl 99 default. No kernel module, no Python. - docs/install-any-distro.md: PPA + package steps, the wiring, per-device host prerequisites measured on the Strix Halo box, verification, and the warning that the PPA's amdxdna-dkms builds a stock module into modules/updates/, which is the driver this board resets with. Measured: the deb installs in a clean ubuntu:26.04, the shim and the engine run, and a stock Lemonade (PPA 2026.44.1) flips llamacpp-hrx from installable to installed from the hrx_bin setting alone.
PR Reviewer Guide 🔍(Review updated until commit b221074)Here are some key observations to aid the review process:
|
…ade-hrx wrapper Lemonade's llamacpp-hrx build_server_environment() hands its backend exactly one variable (GGML_DISABLE_VULKAN=1), so pointing hrx_bin at the raw engine binary starts the HRX kernels without the environment 1bit serve gives them -- measured on hardware: a stock PPA lemond exec'd the shim with -m <gguf> --ctx-size 40960 --device HRX0 --port 8001 --jinja --metrics --parallel 1 and the engine refused the device (invalid device: HRX0) because the shim had only the trio and not serve's plan (HSA runtime path and the rest). So the package now installs lemonade-bin/llama-server as a symlink to the bundle's own lemonade-hrx, which is exactly that contract already: it runs 1bit serve --print-backend-env (architecture and memory checks, the backend environment) and then execs the engine on Lemonade's argv with serve's defaults. The doc's limits section is corrected with it: serve's checks DO run on this route (a model that does not fit is refused with exit 3, not silently started); what is missing is our catalog rows and any NPU recipe.
|
Persistent review updated to latest commit 459ff86 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 459ff8603 — the contract it rests on is real, and I built the package end to end
The Lemonade side, checked in the source tree on this box (~/wt/lemonade-44-1):
BackendUtils::find_external_backend_binary() reads LEMONADE_<SECTION>_<BINKEY> as an environment override
(backend_utils.cpp:374) or the config value, and when the value is a directory it resolves spec->binary
inside it (is_directory(dir) -> dir / spec->binary, top level only) — so <dir>/llama-server plus
hrx_bin=<dir> is the supported form, exactly as the page says. And hrx_server.cpp:78 returns the backend
environment as {{"GGML_DISABLE_VULKAN", "1"}} and nothing else, which is why the shim (rather than a bare
symlink) is needed at all.
The package, built here against the release payload bundle
(~/wt/rel-payload/lemonade-onebit-rc-2026-11-02/1bit):
scripts/mk-plugin-package.sh <bundle> /tmp/plug 0.0.0-prs rc 0
1bit-engine_0.0.0-prs_amd64.deb 17,370,000 B
1bit-engine-0.0.0-prs-linux-x86_64.tar.zst 17,458,722 B
dpkg -c / extraction show what the doc promises: opt/1bit/engine/1bit, the HRX build under
hrx/llama/bin/ with libggml-hrx.so, libhrx.so and libhsa-runtime64.so.1 beside it (the $ORIGIN
lookup), the sealed NPU kernel sets, lemonade-bin/llama-server (the shim), the
usr/lib/systemd/system/lemond.service.d/10-1bit-engine.conf drop-in carrying
Environment=LEMONADE_HRX_HRX_BIN=/opt/1bit/engine/lemonade-bin, and
usr/share/1bit/lemonade-config-fragment.json = {"hrx": {"hrx_bin": "/opt/1bit/engine/lemonade-bin"}} for a
lemond without systemd. Control: Package: 1bit-engine, Recommends: lemonade-server, and the description says
"userspace only: no kernel modules, no Python at runtime" — which the tree backs: no .ko, no .py in the
extracted package.
The shim's mechanism, run: 1bit serve -m <gguf> --device hrx --llama-server /bin/true --print-backend-env
prints NAME=VALUE lines that include the no-SVM trio the engine needs
(HRX_ALLOW_NO_SVM=1, HSA_USERPTR_FOR_PAGED_MEM=0, plus GGML_HRX_REPORT/STRICT), so the shim can inject what
stock Lemonade's single GGML_DISABLE_VULKAN=1 never would. The flag exists at this head (app/serve.cpp:2769)
and in the shipped rc-2026-11-02 bundle's binary.
bash -n clean, shellcheck -S warning silent on the new script, tools/copyright.py --check rc 0.
Two caveats, neither a gate:
- I built the package; I did not install it on a bare distro, so the
Depends/Recommendslist is
unexercised (the page's "per-device host prerequisites" is the part a fresh-box test would settle). - one discipline note on my side, not yours:
--print-backend-envprints the HRX plan line but loads no weights
and starts no backend; still it is GPU-adjacent, so I will wrap even that in agpu-holdclaim from now on.
…hsa cannot load)
The package installed libhsa-runtime64.so.1 as a single file. A TheRock libhsa is not
standalone: it names TheRock's own libraries in NEEDED (librocm_sysdeps_elf.so.1,
librocm_sysdeps_drm.so.2, librocm_sysdeps_drm_amdgpu.so.1, librocm_sysdeps_numa.so.1,
librocm_sysdeps_z*.so, librocm_sysdeps_liblzma.so.5, librocm_sysdeps_bz2.so,
librocprofiler-register.so.0) and carries RUNPATH=$ORIGIN, so a lone file dlopens to
"librocm_sysdeps_elf.so.1: cannot open shared object file" and HRX registers no device.
Measured on the appliance's own libhsa (sha256 f6ef689d..., read out of the shipped
cycle-13 SFS, one file in /opt/1bit/libhsa): dlopen FAIL from a flat directory, and the
same file OK once its nine siblings are beside it. The same shape is what gave the board
a GPU it could not use, so the build now takes the set:
- ldd the chosen runtime, copy every resolved private library (skipping /lib and /usr/lib,
which remain dpkg Depends) into hrx/llama/bin/ beside libhsa-runtime64.so.1;
- re-ldd from inside the staged directory and REFUSE the build if anything is unresolved,
naming what is missing, instead of shipping a package that installs but has no device.
Verified: the extracted deb's runtime loads under `env -i` (no ROCm, no LD_LIBRARY_PATH);
the shipped engine on a box with /opt/rocm-therock emptied finds it through its own
RUNPATH=$ORIGIN and answers a chat on HRX0 at 88.5 tok/s, mapping the package's copy.
|
Persistent review updated to latest commit 87c20b9 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 87c20b968 (re-posted; my earlier READY was at 459ff8603 and this head adds one commit).
The new commit's central claim reproduces exactly, and it reproduces the same numbers as #863's diagnosis. Against
this box's TheRock runtime, the staging loop the commit adds copies nine sibling libraries:
$ L=/opt/rocm-therock/lib/python3.14/site-packages/_rocm_sdk_core/lib/libhsa-runtime64.so.1
$ LD_LIBRARY_PATH=$srcdir:$srcdir/rocm_sysdeps/lib:… ldd "$L" | awk '/=> \// {print $3}' | sort -u
librocprofiler-register.so.0 librocm_sysdeps_bz2.so librocm_sysdeps_drm_amdgpu.so.1
librocm_sysdeps_drm.so.2 librocm_sysdeps_elf.so.1 librocm_sysdeps_liblzma.so.5
librocm_sysdeps_numa.so.1 librocm_sysdeps_z.so.1 librocm_sysdeps_zstd.so.1
and shipped flat alone the same file leaves exactly five unresolved
(librocm_sysdeps_elf.so.1 librocprofiler-register.so.0 librocm_sysdeps_drm.so.2 librocm_sysdeps_drm_amdgpu.so.1 librocm_sysdeps_numa.so.1) — the same five #863 measures out of the cycle-13 image. So "a lone libhsa cannot load" is
right, the sibling set is the fix, and the two PRs agree on the artifact.
The two-step shape is what makes it safe: the /lib/*//usr/lib/* skip can only ever make the build refuse (the
re-ldd from inside the staged directory catches anything the skip dropped), never ship a package that installs without a
device. bash -n clean, tools/copyright.py --check rc 0, and the refusal message names the missing libraries and what to
pass instead.
Still owner-parked (the PPA), so this is a gate on the head, not a request to merge; tprs-ready stays as accurate.
|
#860 ready for review at 1ef3f37 (branch was 26 commits behind
Re-checked at the merged head: Docs + one packaging script, no engine code. Owner merges. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 1ef3f376b — a merge of main into the head I verdicted. Both of this PR's files are blob-identical to 87c20b968 (docs/install-any-distro.md, scripts/mk-plugin-package.sh).
|
#860 ready for review at
Re-checked at the merged head: |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 0cbf402e6 — merge of main only; both files this PR owns are blob-identical to 1ef3f376b, the head I verdicted (docs/install option pages, copyright.py --check rc 0, site builds). Still owner-parked from your side; nothing of mine holds it. Label stays.
There was a problem hiding this comment.
🟡 Changes recommended
The package omits the mandatory binary license and NPU serving helpers, while unresolved runtime-selection and installation defects can prevent successful use.
4 open issues
What changed in this PR
Adds standalone packaging and installation guidance so stock Lemonade can use the 1bit engine through its existing HRX backend configuration, without a Lemonade fork.
Changes:
- Builds Debian and tarball packages containing the engine, HRX backend, HSA runtime dependencies, and sealed NPU kernels.
- Adds wrapper-based backend wiring through a systemd drop-in or configuration fragment.
- Documents installation, device prerequisites, verification, and integration limits.
| File | Description |
|---|---|
scripts/mk-plugin-package.sh |
Packages the engine payload, stages HSA dependencies, and supplies Lemonade integration files. |
docs/install-any-distro.md |
Explains Ubuntu and other-distribution installation, host requirements, and verification. |
Review effort: Balanced
…ever fall back to the distro's ROCr
Four Copilot review findings, each reproduced on a fake bundle before and after:
- the payload copied NOTICE-BINARY but not LICENSE-BINARY, which LICENSE-BINARY section 2
requires beside every redistributed copy of the HRX/NPU binaries (package-linux.sh ships
both for the same reason). Both now land in /opt/1bit/engine/.
- the payload dropped the bundle's scripts/ and tools/, which 1bit resolves BESIDE ITSELF
(app/built_path.h:44-56 -> packaged_path("scripts/repack_gguf.py") app/serve.cpp:2482,
packaged_path("tools/gguf_to_q4nx.py") app/serve.cpp:2589). Without them GGUF-on-NPU serve
cannot find its repack script. Both directories are now copied, and a bundle missing them
refuses the build instead of producing a package that fails at run time.
- LIBHSA's fallback list ended in the distro's libhsa-runtime64, which rejects the
HSA_AMD_AGENT_INFO_PM4_EMULATION probe on gfx1151, so HRX registers no device
(docs/hrx.md:932-941) and a clean ldd says nothing about it. The bundle's own runtime is now
the only default; anything else must be named with LIBHSA, and the refusal says why.
- docs/install-any-distro.md told the reader to tee a drop-in into
/etc/systemd/system/lemond.service.d, a directory a fresh install does not have; it now
creates it and says the package already ships the same drop-in under /usr/lib.
Measured: with a staged bundle the deb and the tarball carry LICENSE-BINARY, scripts/{repack_gguf,
check_repack_config}.py and tools/gguf_to_q4nx.py under /opt/1bit/engine/ (dpkg-deb -c,
tar --zstd -tf), the build prints "HSA runtime + 9 sibling libraries, ldd clean", and a bundle
with no runtime exits 1 with the new message instead of packaging /usr/lib/x86_64-linux-gnu/
libhsa-runtime64.so.1. copyright.py --check rc 0.
…not in tree, the shipped artifact name Six Copilot review findings, each checked against the sources rather than the prose: - README and install.md §6 said Secure Boot "can stay on" with "shim, grub, the kernel and its modules all signed with our MOK". The shim is Microsoft-signed (os/v3/puppy/mkimage.sh:75-80 pulls shimx64.efi.signed), and on a machine that has never trusted our key the firmware refuses the kernel and the certificates are not on the stick — so a fresh machine has to enroll first or turn Secure Boot off. Both places now say that and link to the enrollment steps. - install.md step 1 named the third menu entry as the serial shell; the recipe orders headless, desktop, install-to-a-disk, serial shell (os/v3/puppy/mkimage.sh:152-166), so the documented ordinal opened the installer. All four are now named with their flags. - the RAM-only paragraph contradicted the PUPMODE=12 save layer above it. It now separates the two measured cases: no save folder + `poweroff -f` (marker gone, the cycle-13 observation) versus a save folder + normal shutdown, where G7 measured an /etc marker surviving (os/v3/puppy/g7-probe.sh:28-33,93-99), with 05onboard-data's shutdown sync/unmount named. - install.md advertised `scripts/mk-plugin-package.sh` as an existing route; it is on PR #860, not in this tree. Marked as such. - the enrollment row imported only `MOK-Kernel.der`; kernel.md's table has a second key, `MOK.der`, for every module we build, so a machine that enrolled only the kernel key still refuses the NPU and `ec` modules. The row now imports both, and §6 states the split. - releases.md's artifact row listed `1bit-os-<tag>.efi` as the appliance file; the Puppy recipe builds a disk image (`1bit-os-puppy.img.xz` + SHA256SUMS) and there is no standalone `.efi` for it. The `.efi` and `.img.zst` names stay with the earlier BusyBox os/mini candidates, which the same page's gated-candidate table already lists. The Hugging Face privacy clause is unchanged. copyright.py --check rc 0; site builds 53 pages, one <h1> per touched page.
Copilot review answered — 4/4 threads resolved at
|
| thread | finding | fix, measured |
|---|---|---|
scripts/mk-plugin-package.sh:59 |
payload carried NOTICE-BINARY but not LICENSE-BINARY, which section 2 requires beside every redistributed copy |
both in /opt/1bit/engine/; present in the .deb (dpkg-deb -c) and the tarball (tar --zstd -tf) |
docs/install-any-distro.md:60 |
tee into a lemond.service.d that a fresh install does not have under /etc |
mkdir -p added, and the doc says the package already ships the drop-in under /usr/lib (both paths in the built .deb) |
scripts/mk-plugin-package.sh:47 |
LIBHSA fallback could silently package the distro ROCr, which rejects the gfx1151 PM4 probe |
fallback removed; the bundle runtime is the only default, anything else must be named, refusal prints the reason. No-runtime bundle now exits 1 even though /usr/lib/x86_64-linux-gnu/libhsa-runtime64.so.1 exists here (that was the old loop's second candidate); the TheRock bundle still builds with ldd clean |
scripts/mk-plugin-package.sh:58 |
payload dropped the scripts/+tools/ helpers 1bit resolves beside itself |
both directories copied beside the binary (app/built_path.h:44-56), missing ones refuse the build; scripts/{repack_gguf,check_repack_config}.py and tools/gguf_to_q4nx.py verified in the .deb |
bash -n clean, tools/copyright.py --check rc 0.
|
Persistent review updated to latest commit 2feb604 |
|
Persistent review updated to latest commit ee0cd26 |
|
Head moved to Re-verified at the new head: Nothing in this PR cites the console or the boot menu, so cycle 18's console change (#921/#927) does not touch it; #859 got the two corrections. Owner merges. |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY (content unchanged since 9379e7ac; the new head is merges only)
Re-verdicted at c2fb21b47. The PR's own two files did not move: git diff 9379e7ac c2fb21b47 -- docs/install-any-distro.md scripts/mk-plugin-package.sh is empty, and every commit since is "Merge origin/main into
docs/install-any-distro" (four of them). So the review at 9379e7ac — a docs page plus scripts/mk-plugin-package.sh
(+359), fully green CI — stands as written.
CI at this head: pr_agent red, which is expected while coder-llm is off and is not a blocker anywhere in my table.
This stays in the owner-held group in my state file: the verdict is READY, the merge is the orchestrator's/owner's call,
and nothing about the newer main changes it.
|
Persistent review updated to latest commit c2fb21b |
|
Persistent review updated to latest commit 96abb89 |
|
Persistent review updated to latest commit 8b82688 |
|
Persistent review updated to latest commit e0d6ecd |
|
Current at Audited against everything that landed in Verified: |
|
Persistent review updated to latest commit e5a46fe |
|
Persistent review updated to latest commit b768477 |
|
Current at |
|
Persistent review updated to latest commit 2612ca5 |
|
Persistent review updated to latest commit 75b3865 |
|
Handover state at |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 75b386546
Merge-only rebase: own contribution (merge-base with origin/main..head, +/- lines only) hashes to b6efdad0fc4e at this head and at e5a46fed3, the head I reviewed — 2 files, +300/−0, unchanged. The push is main moving under it (#1020, #1009, #994, #989).
So my review stands as posted. It is still owner-held, which is not a verdict question; the gate from my side is this line at this head.
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at 5d6a38671
Rebase only: own contribution hashes to b6efdad0fc4e at this head, at 75b386546 and at e5a46fed3 — 2 files, +300/−0, unchanged. Review stands; still owner-held, which is not a verdict question.
|
Persistent review updated to latest commit 5d6a386 |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at f9c7e8a
Merge-only rebase: own contribution hashes to b6efdad0fc4e at this head, at 5d6a38671 and at e5a46fed3 — 2 files, +301/−0, unchanged. My review stands; still owner-held, which is not a verdict question.
|
Persistent review updated to latest commit f9c7e8a |
|
Persistent review updated to latest commit b221074 |
|
Final hand-over state: head |
bong-water-water-bong
left a comment
There was a problem hiding this comment.
VERDICT: READY at b22107464
Rebase-only: own contribution hashes to b6efdad0fc4e at this head and at the previous three (e5a46fed3, 5d6a38671, f9c7e8a) — 2 files, +301/−0, unchanged; the merge base moved to a5c774ac8. Review stands; still owner-held, which is not a verdict question.


Item 8 of
~/briefs/TLEMONADE-PPA.md(owner: "that would be a huge win, then they can take any linux distro really. we would need to leave instructions"), plus item 6's packaging.The finding this rests on (item 5, measured)
A stock Lemonade runs our engine — no fork, no patch, nothing upstream. Its
llamacpp-hrxrecipe(recipe
llamacpp-hrx,config_section hrx,binary llama-server) resolves its backend binary throughBackendUtils::find_external_backend_binary():LEMONADE_<SECTION>_<BINKEY>in the environment, else theconfig key
<section>.<bin_key>, and a directory is resolved tobinaryinside it. Measured on the PPAdeb
2026.44.1~26.04(extracted, run isolated, no model load): backend stateinstallable→installed,both from
LEMONADE_HRX_HRX_BIN=<dir>and from{"hrx": {"hrx_bin": "<dir>"}}alone.So the plug-in contract is: ship
<dir>/llama-server, pointhrx_binat<dir>. Everything in this PR isthat, and the reason
llama-serveris a shim rather than our binary: the recipe's ownbuild_server_environment()hands the backend exactly one variable,GGML_DISABLE_VULKAN=1, so Lemonadewould otherwise spawn the engine without the no-SVM trio — the configuration engine#715 page-faults in.
What is added
scripts/mk-plugin-package.sh <1bit-bundle-dir> <out-dir> <version>— emits1bit-engine_<v>_amd64.deband
1bit-engine-<v>-linux-x86_64.tar.zst: the engine, its HRX llama.cpp build, the HSA runtime the HRXbackend dlopens (placed beside
libhrx.so, which searches$ORIGIN), the sealed NPU kernel sets, thellama-servershim, alemond.service.ddrop-in carryingLEMONADE_HRX_HRX_BIN, and the config-keyfragment for a lemond without systemd. Userspace only: no kernel module, no Python at runtime.
docs/install-any-distro.md— PPA + package + wiring steps for Ubuntu 26.04/24.04, the 1bit OS case, andthe tarball path for other distros; per-device host prerequisites; verification (one chat per device plus
1bit-engine servealone); and the two limits of this route (no1bit servepreflight, no NPU catalogrows — our rows can ride along through the config dir's
user_models.json).Measured
ubuntu:26.04container:dpkg -i→install ok installed;1bit-engine --version, the shim (version: 1 (d8bd0ff6)) and1bit serve --helpall run; 3 NPU kernel sets present; 0.kofilesllamacpp-hrxinstallable→installed, env form and config-key formsrc/cpp/server/backends/hrx/hrx_server.cppbuild_server_environment()lemonade-serverpulls no kernel bits;amdxdna-dkmsdoesapt-get -s: 11 packages, zero dkms/xrt/amdxdna; the dkms package pullsdkms+build-essential+linux-headers-*-genericand DKMS installs into/lib/modules/$(uname -r)/updates/— the driver this board resets with (scripts/mkimage.sh), so the doc says not to install itNot measured here: a chat through stock Lemonade on the engine (GPU window queued behind other lanes at
the time of writing; the wiring it depends on is the
installedresult above). I will post the number herewhen it lands, and the doc's table only lists claims that were run.
87c20b96— the package shipped a runtime that cannot load (found on the board, 2026-11-02)The 1bit OS appliance rebooted into a GPU it could not use (
invalid device: HRX0,libhsa-runtime64.so.1never opened). Two causes; the second one was this script's shape too:
the appliance named the runtime with
ONEBIT_HRX_LIBHSA=…, a CMake compile definition that nothingreads at run time — the engine's runtime knob is
IREE_HAL_AMDGPU_LIBHSA_PATH(or a scan of/opt/rocm-therock,app/serve.cpp:201). Reproduced on this box by emptying/opt/rocm-therock: theshipped plan then carries no HSA path and the backend dies with the board's exact error.
a TheRock
libhsa-runtime64.so.1is not a standalone file. It names TheRock's own libraries inNEEDED and carries
RUNPATH=$ORIGIN, so shipping it alone give a dlopen that fails:readelf -don it:RUNPATH [$ORIGIN], NEEDEDlibrocm_sysdeps_elf.so.1,librocprofiler-register.so.0,librocm_sysdeps_drm.so.2,librocm_sysdeps_drm_amdgpu.so.1,librocm_sysdeps_numa.so.1.The script now
ldds the chosen runtime, copies every resolved private library beside it (skipping/lib,/usr/lib, which stay dpkg Depends), and re-ldds from inside the staged directory, refusing thebuild if anything is unresolved:
Measured on the built deb (
1bit-engine_2026.11.02-2_amd64.deb, 18.9 MB):env -i(no ROCm, noLD_LIBRARY_PATH)FAIL -> librocm_sysdeps_elf.so.1)LIBHSA=a lone file/opt/rocm-therockemptied, trio only, no env pathok, chat on HRX0 88.5 tok/s, backend maps<prefix>/hrx/llama/bin/libhsa-runtime64.so.1through its ownRUNPATH=$ORIGINSo the package now installs a GPU that works on a machine with no ROCm at all, which is the claim this PR's
doc makes.