Repository navigation
Conversation
|
Answer: it is the latest. |
|
The package tracks the project's qualified toolchain. README pins that at ROCm 7.2.3 (GCC 15.3); 7.2.4 is the latest patch of that line from AMD's repository, so the deb stays ABI-compatible with the qualified version while picking up the newest patch. ROCm 10 is not qualified for gfx1151 yet — there's an open draft PR (#228) evaluating it, and shipping production packages on an unqualified math-library toolchain would bypass the project's quality checks. The ROCm version is pinned in one place ( |
|
We are currently working on the stability of gufo (tool calling, caching, etc.). We'll take a look in the new few days, hopefully. thanks for the pr |
no stress, no hurry. just something that I needed for myself, and I wanted to give something back to your incredible feat. keep up the good work! :o) |
f46adc5 to
787b297
Compare
|
actually, on my own testing on my bosgame M5 with 128GB of ram, I was having some bad TTFT, and it seems to be related to disk trashing on the n-gram table: the Flash-Next PLE n-gram table is disk-resident by design — it's a 28.8 GB table (320M rows, IQ4_NL) read straight from the GGUF with O_DIRECT, and the row cache was a hardcoded 8 MB. Every prefill token does 16 row lookups, so a cache-miss prefill of 70–95k tokens means ~1.5M random 4KB reads (~5–6 GB) per request. My fix: made the row cache size configurable with --ngram-cache-bytes (default 512 MiB). At 1 GiB the cache holds ~11.8M rows — enough to cover every distinct row a long prefill touches — so the disk reads collapse. Measured: prefill went from 645–1189 tok/s (I/O-bound) to 1425 tok/s (compute-bound, matching the benchmark), a 70k-token prefill's TTFT dropped to ~49 s, disk reads went from 508 MB/10 s to 0, and cache-hit turns are back to 50 ms TTFT. The ngram table design (O_DIRECT, avoiding page-cache pollution) stays intact — we just sized the cache for the workload instead of the benchmark. can submit it as a new PR if you want, but it's simple enough: // ngram.cpp — the actual change |
cdc1937 to
b10f3c6
Compare
|
Adding a CI build for releases is going to make Gufo much easier for end users to adopt. Nice! |
5c1a713 to
e86db30
Compare
95055b9 to
d943188
Compare
6f6c17b to
2cdd770
Compare
…d proves it needs Units (classic + instantiated template), the launcher that reads every knob from /etc/gufo, the ROCm repo keyring and an apt pin scoped to the ROCm package names, the upgrade-path scripts (prerm marks, postinst decides), the container lifecycle test, and the gates that keep the build honest. Filesystem hardening is set from measured file descriptors, not copied from a template: ProtectSystem=full, ProtectHome=yes, PrivateTmp=yes, ReadWritePaths=/var/lib/gufo. strict, ProtectKernelTunables and NoNewPrivileges are deliberately off until each is proven with a cold start on real hardware. The apt pin is one `Package:` line of space-separated globs, not one line per glob. apt reads one `Package:` field per stanza, so the multi-line form installed cleanly, sat in /etc/apt/preferences.d looking configured, and never applied the priority it asked for -- the distro default stayed in force. The postinst now states the rule and carries a scratch-dir reproduction that does not touch /etc, and the negative control (the old shape -> no pin) is what proves the difference is real and not a reading of `apt-cache policy`. Test fixtures are named `gufo@demo.service`, and the container test picks its stub port from the kernel's ephemeral range instead of a fixed block: a shipped test should not carry a caller's instance names or port conventions.
2cdd770 to
3781707
Compare
|
@avlis We would like to keep the scaffolding for packaging/etc. outside of this repo, and collect everything in https://github.com/gufo-org/toolboxes, do you moving everything there? Sorry for the long delay, but we wanted to focus on stability at first. We are now trying to reduce the backlog of PRs/issues |
Reply to @fedeizzo — packaging scaffolding to
|
|
Thanks for the detailed proposal. Moving the packaging into Toolboxes sounds good. Please open a PR there, then close this one with a link so we retain the discussion. For that first PR, I’d like to keep the scope as small as possible: a native Debian build that packages the executable, required runtime files and licenses, with the dependencies needed to run it declared correctly. Please leave systemd services, launchers, environment-based configuration, model/cache management, deployment helpers and custom service upgrade handling for follow-up PRs. Those introduce deployment choices that we should evaluate separately. The first goal is to validate the build, installation and release pipeline; then we can iterate based on what users actually need. To answer your three questions:
Please also keep the runtime distinction explicit: the AMD apt runtime does not include our ROCr patch, so this package should not be described as equivalent to the qualified Nix build. A small first PR will be easier to review and validate. We can add service integration and other conveniences incrementally once the basic package pipeline works. |
Moves the Debian packaging for gufo out of the engine repository and into toolboxes as a deb/ lane beside nix/ and systemd/. Files in deb/common/ are assembled into <checkout>/debian/ at build time (docker-ce-packaging's shape); the version rules live in gen-deb-ver, which is handed the source directory. Native format 3.0 (native), because upstream and packager are the same project and DEP-14 excludes that case from debian/ branch and tag mangling. Every machine-specific choice is a default an environment variable overrides, and the shipped default is the portable one: CPU_FLAGS is empty by default, because a package compiled for one instruction set fails as SIGILL under load on another machine, not as a build error on this one. Contents: - common/: control, rules, maintscripts, units, gufo.env, launch and upgrade-unit logic, the ROCm keyring and ld.so registration, and tests/ (19-scenario stub matrix + the privileged container lifecycle test that takes the .deb as an argument) - build-deb, gen-deb-ver, debian-trixie.Dockerfile, tools/deploy/install-gufo-deb.sh - tests/apt-pin-scope.sh: the scoped ROCm pin at 600, with a negative control that writes the broken six-Package:-line shape and requires apt to report it unpinned in the same run - .github/workflows/build_and_publish_deb.yml: consumes the gufo-release dispatch the engine already emits; a pull request runs the gates and mints no version - .gitignore: dh staging is build output and never belongs in the lane Verified on v0.23.0 in a rebuilt container on a freshly pulled debian:trixie, with real dpkg and real systemd: 36 assertions / 0 failed, 0 unresolved libraries, ROCm resolvable through the loader cache, apt-cache policy answering 600 inside the container, gufo diagnose [supported], purge handing /var/lib/gufo to root:root while leaving a mounted model cache at 108:115, legacy empty-marker upgrade honoured from the state record with the process really replaced. Stub matrix 19/0 against the tree and against the artifact. lintian reports 1 E and 10 W, each accepted on a stated ground in the PR body. Two defects were found by running this and are fixed here rather than papered over: the container test resolves its installer at $SRC_ROOT/tools/deploy/install-gufo-deb.sh, and without that file it falls back to bare dpkg on an unmet dependency list - 11 unresolved libraries, unit stuck in activating, 17 assertions failing that test nothing; and the private-infrastructure scan matched its own pattern text, which is how a gate earns to be ignored. No apt feed, no retention policy, no engine source changes, no services enabled or started on install, and no claim that a .deb equals the qualified Nix build - the Nix build applies rocr-fixed-cwsr.patch to ROCm and a host install against AMD's published packages does not. Supersedes gufo-org/gufo#372.
|
Closing this: the packaging has moved, as asked in review.
What travels unchanged: What changes is where they are built and how they are parameterised: files are assembled into Two things from this thread are worth restating where they will be read:
This PR stays as the review history of the packaging and its branch is frozen rather than renamed — |
Summary
Adds Debian packaging producing two packages with
dpkg-buildpackage -b:gufo— the binary, runtime data, license notices, a dedicatedgufosystem user (primary group
llm-servers, plusvideo/render), a classicgufo.serviceand an instantiatedgufo@.service, the shared launcher/usr/lib/gufo/gufo-launch.sh, and admin configuration through/etc/gufo/gufo.envplus/etc/gufo/instance.d/<name>.env.gufo-rocm-repo— registers the AMD ROCm apt repository (pinned to the7.2.x line) and its signing key, because hipblaslt, rocwmma and the HIP
runtime Gufo links against are not in Debian or Ubuntu.
Plus deployment tooling (
tools/deploy/) and a README "Debian package" section.Configuration model
/etc/gufo/gufo.env— host-wide facts, a dpkg conffile: the model librarypath, the cache root.
/etc/gufo/instance.d/<name>.env— a serving configuration: model, draftsidecar, context, sessions, speculative backend, port, cache budget, log level.
Admin-owned, never touched by the package.
gufo@<name>.service+gufo-launch.sh— the launcher sources the host file,then the instance file, then builds the command line. The unit carries no
model-, port- or cache-specific value, so a configuration change never means
editing a unit and never needs a drop-in. The README's MTP/context recipe uses
this route rather than an
ExecStartoverride, which would be a second sourceof truth that silently wins on every start.
/var/lib/gufo/cache/instances/<name>,created as the service user) because the server rejects a cache directory it
does not own; weights are shared in
/var/lib/huggingface/hub, setgidllm-servers, so several servers on one machine reuse the same downloads.Both survive
apt purge.Service lifecycle
dh_installsystemd --no-enable --no-startemits neither a start nor a stop, soboth sides are explicit.
Instances cannot be enumerated the obvious way:
deb-systemd-invokepasses unitnames verbatim and does not expand
gufo@.service;systemctl is-enabled gufo@.serviceanswersindirect;systemctl list-unit-files 'gufo*'does notlist an enabled instance at all, because enabling an instance is a wants symlink
named after the instance.
prermtherefore expands the wants symlinks in/etc/systemd/systemand/run/systemd/systemforgufo.serviceandgufo@*.service, unions that withsystemctl list-units --all --type=service 'gufo*'to catch units started without being enabled, drops the bare template(systemd refuses to act on it), marks each live unit in
/run/gufo/<unit>.was-active— one marker per unit, a host may run several —stops each, and prints a note to stderr when it found nothing running.
postinstthen decides, in/usr/lib/gufo/gufo-upgrade-units.sh, called withdpkg's configured-from version (
$2: empty on first install, the previousversion on upgrade or reinstall):
configurestartis a no-opprermstopped it in this transaction; restart on an inactive unit starts itprerm/runwent with the markers; booting is the recovery the admin asked forprermgufo@.serviceWhy the boot id. Enablement is a statement about boot, not about the present moment, so
is-enabledcannot distinguish "the machine rebooted,/runwas wiped with the markers, andthe admin wants this up" from "an operator stopped it on a live host to keep the device
free" — and getting that wrong takes a device and re-enters a load balancer's path.
prermtherefore writes the boot id twice: inside each marker, and in
/var/lib/gufo/upgrade-boot.idoutside
/run, which is the only thing that survives the reboot the distinction depends on.startfires only when the recorded id differs from/proc/sys/kernel/random/boot_id, andconfigureconsumes that record, so a reboot predating an upgrade can never start anything.A marker is honoured only when it names the running kernel's boot id, and
prermclearsmarkers before writing its own: a transaction that dies between
prermandconfigureleavesno live claim on the next one.
One line of evidence is printed per unit —
restart,startorleave, each with thereason above; a failed action exits non-zero so
postinstwarns instead of failing the install silently.Why the decision is not in
prerm: dpkg runs theprermof the packagebeing upgraded from. A
prerm-only rule therefore cannot govern the upgrade thatfirst introduces it, and its
startfallback is a no-op on a unit that is stillactive — which leaves the unit executing the inode the package replaced while
dpkg -landgufo --versionreport the new one.postinstis this package's ownscript, so the rule holds from the first upgrade that carries it.
prerm's markersremain, as the record of what was running before it was stopped.
/etc/gufo/instance.d/<name>.env, thensystemctl enable --now gufo@<name>activewith a new pid, no manual step/proc/<pid>/exe→/usr/bin/gufo/run/guforemoved, user and group dropped, shared cache and home keptA restart is model-load downtime. Measured on
gfx1151at 128k context with twosessions: ~25 s with the weights in the page cache, ~96 s cold — the spread is the
page cache, not the release, so budget for the cold figure on a host that has not run
this model recently. Upgrades therefore belong in a window with a second backend in front,
and the backend health check should be readiness-based (
GET /ready, which is 503 until amodel is resident) rather than liveness-based, so a reloading host withdraws itself.
Source revision stamp
The binary advertises its revision through
-DGUFO_REVISION, computed bydebian/get-revision.sh, which writes nothing to the tree:GUFO_REVISION=…X.Y.ZX.Y.Z-N-g<sha>-dirtydebian/changelogregenerated by this packaging-dirty— that path alone is excluded.gitunknown--tagsis required because the release tags here are lightweight, and--git-dir/--work-treeare pinned to the source root so a tarball extractedinside another repository cannot inherit that repository's HEAD. Because the stamp is
read at build time,
docs/RELEASING.mdstates the order of operations: commit,then build, then verify the stamp from the artifact before deploying.
Key decisions
-devcounterparts, which pull the compiler and the GCC toolchain; every install path
uses
--no-install-recommends./opt/rocm/libwithout registering it,so
libhipblas/librocblas/libamdhip64fail to load. The package ships/etc/ld.so.conf.d/rocm.confand runsldconfiginpostinst.control: AMD's debs ship no shlibsmetadata, so
dpkg-shlibdepscannot derive them.StartLimitIntervalSec/StartLimitBurstare[Unit]keys — in
[Service]systemd ignores them silently — and a crash loop must notreload a ~100 GiB model repeatedly.
ProtectSystem=full,ProtectHome=yes,PrivateTmp=yesand one explicitReadWritePaths=/var/lib/gufoon both units. The set was derived by measuring what therunning instance holds open — file descriptors under the model cache (read-only
mmaps)and
/var/lib/gufo/cache/instances/<instance>(the disk cache the launchermkdirs) —plus the shipped env files (no
/homepath) and the launcher (no/tmp, no writes under/usr). Deliberately not set:ProtectSystem=strict,ProtectKernelTunablesandNoNewPrivileges. Some sources read/sysfor device information and the ROCmkfd/driaccess path is unproven under a read-only
/sysor a no-new-privileges domain; the failuremode of those three is a unit that will not start, which costs a full model reload — a
larger outage than the exposure they remove. Each should be widened only with a cold start
and a real completion measured under it, and the reason is written into both unit files.
Verified in a namespace, not just declared:
PrivateTmpresolves to asystemd-private-…mount,touch /usr/bin/…failsRead-only file system,/homeisempty, and the cache path keeps a read-write descriptor.
install-gufo-deb.shresolves the newest package withsort -V | tail -1,so a new release needs no script change and a plain
ls | head -1cannotre-install an older one that sorts first.
Contents
debian/— two packages,gufo.service,gufo@.service,gufo-launch.sh,maintainer scripts,
gufo-upgrade-units.sh(shipped to/usr/lib/gufo/, and thepart of the lifecycle that can be tested with a stubbed service manager),
get-revision.sh,gen-changelog.sh,rules,tests/(the upgrade-path matrix and the container lifecycle test,both executable, described under Verified) plus the release-workflow hook that generates the
changelog entry;
tools/deploy/install-gufo-deb.shandtools/deploy/sync-model-cache.sh; the README "Debian package" section;docs/RELEASING.md.Builds without a wrapper
The package is the interface.
dpkg-buildpackage -b -us -ucmust work on a machine nobodyhas ssh'd into, with no helper script, no exported flags and no vendor-specific prefix:
Build-Dependsis complete.find_package(hip)needship-dev, and.hipdevicecode needs ROCm's clang from
rocm-llvm—gcchas noamdgcntarget.python3isresolved by CMake directly. Every one of those is declared, so a clean chroot resolves
them itself rather than succeeding only on a machine that happened to have them.
hipconfig'sHIP_PATH, overridable withDEB_ROCM_PREFIXfor a staging chroot, a ROCm under$HOME, or a multi-version host./opt/rocmis only the last fallback, and a wrong value fails loudly atfind_package(hip)instead of being silently right on one maintainer's box.-march=and no pinned compiler appear anywhere indebian/: a packager's CPU flags belong to the packager, and the build must be correct onhardware its author does not own.
Rules-Requires-Root: no,no
curl/wget/aptin any maintainer script. Source tarballs without.gitarehandled by
debian/get-revision.sh.Verified
debian:trixiecontainer:ldd /usr/bin/guforesolves,apt purgestops the unit and removes the package files while keeping the shared cacheand home.
replaces
ExecStartwith a stub, so no weights are loaded and no throughput is claimed;gufo diagnosedoes run there and only enumerates the device. Serving a model inside acontainer is a manual, GPU-passthrough bring-up documented in the packaging notes
(2026-10-10), where
gufo.servicereachedactive (running)and answered/v1/modelsand a chat completion — it is not part of
debian/tests/, and CI should not be expectedto reproduce it on a host with a resident model.
gfx1151, Flash-Next Q4 + MTP at 128k context behind a load balancer:package-driven upgrades restart the instance unattended, printing
gufo: restart gufo@<name>.service …, after whichreadlink /proc/<pid>/exeis
/usr/bin/gufoand not(deleted), the marker directory is empty, andfront-end probes stay
200across the reload window on the second backend.check-docs.pypasses; no C++ or Python files are touched.debian/tests/upgrade-units: 19 scenarios over the lifecycle table above, runnable asmake testordebian/tests/upgrade-units;systemctlanddeb-systemd-invokeare stubbed,so it exercises the decision without a service manager.
debian/tests/test_deb_package_in_container.sh: the same lifecycle against real dpkg andreal systemd in
debian:trixie- prerm before unpack, postinst after, marker files as thepackage writes them. 34 assertions, 0 failed on the artifact under review, and 33 assertions,
0 failed replaying an upgrade from
0.15.1-1— a release whoseprermwrote emptymarkers, so the postinst falls back to the state record (it prints
restart gufo@legacy.service · marked by an older prerm; boot provenance from the state record). Scenario E also runs self-contained, patching the installedprermto emitempty markers, so the replay needs no historical binary if none is kept. Takes the
.deb(s) as arguments; image, port, model cache and device passthrough are flags orenvironment, and GPU assertions are skipped with a printed reason where there is no device.
ExecStartis replaced by a stub inside those containers, so this covers packaging and servicelifecycle, not inference. It is deliberately not listed in
debian/tests/control: it needsa privileged container, which autopkgtest would fail for reasons unrelated to the packaging.
Accepted lintian findings
package-modifies-ld.so-search-path(intentional, see above);maintscript-calls-ldconfig/command-with-path-in-maintainer-script;custom-library-search-path RUNPATH /usr/lib/llvm-19/lib(libomp);hardening-no-pie(the HIP toolchain emits no PIE objects, consistent with theNix build);
no-manual-page(the project ships none).Notes
Package:line of space-separated globs, not one line per glob. apt readsa single
Package:field per preferences stanza, so a stanza carrying six of them installs withoutcomplaint, sits in
/etc/apt/preferences.dlooking configured, and never applies the priority itasks for — the distribution's 500 stays in force and nothing reports it.
debian/tests/nowasserts both halves: that the installed stanza has exactly one
Package:field (no network needed)and that
apt-cache policyresolves a ROCm package to 600 (reported as a skip when the sandboxfetched no index, never as a pass it did not earn).
gufo-rocm-repo.postinst; bump per release.The package tracks the qualified toolchain line in the README and takes the
newest patch of it, so the deb stays ABI-compatible.
apt removeROCm-devpackages on hosts with xrt/dkms — AMD'sdependency graph cascades into unrelated packages.
-o Dpkg::Options::=--force-confold: aninteractive conffile prompt hits EOF and strands the package half-configured
(
iU)..debon release; the changelogtrailer format in generated entries; a CI lane that can load a small model in a
privileged container, which is the one coverage
debian/tests/still lacks (seeVerified), and readiness-based backend health checks where a balancer fronts the package.