Skip to content

package: the payload ships the HSA runtime set it opens, and the release refuses one that resolves outside it (Refs #1411) - #1506

Merged
bong-water-water-bong merged 3 commits into
mainfrom
pkg/1411-selfcontained
Nov 8, 2026
Merged

bong-water-water-bong merged 3 commits into
mainfrom
pkg/1411-selfcontained

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

The c22 install's llama-server maps 64 files under /work-c22/hrx-toolchain/… — the ROCm core runtime set — and ldd shows none of them, because the backend opens the runtime by name at run time:

ggml-hrx-dmabuf.cpp:29:  void * lib = dlopen(path != nullptr ? path : "libhsa-runtime64.so.1", RTLD_NOW | RTLD_NOLOAD);

So pruning that toolchain kills both services, and no dependency row can see it. This is the two halves that were missing: the payload ships the set, and the release refuses a payload that resolves outside itself.

Refs #1411 — left open deliberately: no package was built, no install directory created, no drop-in touched, no service restarted. Live verification is deferred to the release gate (owner order).

1. The payload ships the whole runtime set

scripts/mk-plugin-package.sh staged what ldd resolves, which covers libhsa-runtime64.so.1, librocprofiler-register.so.0 and the 8 rocm_sysdeps entries — but not libhsa-amd-aqlprofile64.so.1, which the runtime opens by name itself. It ships beside the runtime now, and the build refuses when it is not where the runtime was found.

NOTICE-BINARY gains the set's licences, checked against upstream rather than assumed:

component licence
libhsa-runtime64 (ROCr) University of Illinois/NCSA (from rocr-runtime/LICENSE.txt)
libhsa-amd-aqlprofile64 MIT (ROCm/aqlprofile)
librocprofiler-register MIT (ROCm/rocprofiler-register)
rocm_sysdeps: bz2 / drm / elf / liblzma / numa / z / zstd bzip2-1.0.6 / MIT / GPL-3.0-or-later OR LGPL-3.0-or-later / public domain / LGPL-2.1 / Zlib / BSD-3-Clause OR GPL-2.0

2. The release check fails when anything resolves outside the payload

The #1461 rows move out of weekly.sh into scripts/pack-check.sh (so a test can source them) and gain runtime_selfcontained:

  • no packed ELF may declare an absolute RUNPATH/RPATH entry;
  • every library ldd resolves for every packed ELF must be inside the payload or a system directory;
  • the HSA runtime set must be present beside the backend: libhsa-runtime64.so.1, libhsa-amd-aqlprofile64.so.1, librocprofiler-register.so.0, and at least 8 rocm_sysdeps entries.

It runs for both packages, next to the build-path row.

3. Its unit test, in CI

tests/pack_check_test.sh — ctest pack_check, registered in CMakeLists.txt — builds synthetic trees with the C compiler and asserts each row's own message:

PASS  a clean payload passes both rows                     (the control)
PASS  #1411: a runtime set without aqlprofile fails
PASS  #1411: an absolute RUNPATH fails
PASS  #1411: a library resolved outside the payload fails   (RELATIVE rpath, escapes the tree)
PASS  #1461: a build-machine path in a packed ELF fails
pack_check: all rows PASS

The escaping arm earned its keep: it failed on the first version of the row. ldd prints the path it was given unnormalised — <tree>/bin/../../outside/libx.so — so a prefix test counted a library that had left the payload as "inside" it. The row canonicalises each resolved path now, and that arm is what proves it.

Red arm, measured on the install that is live today

runtime_selfcontained ~/.local/share/1bit-embed-c22/lemonade-onebit-rc-2026-11-03-c22 → 19 FAIL lines, rc 1, including

FAIL libhrx.so.0 declares an absolute RUNPATH/RPATH entry: /work-c22/hrx-toolchain/…/clang/24/lib/x86_64-…
FAIL libllama.so.0.0.1 declares an absolute RUNPATH/RPATH entry: …
FAIL llama-server declares an absolute RUNPATH/RPATH entry: …
FAIL libggml-hrx.so.0.18.0 …  FAIL libloomc.so.0 …  FAIL libggml.so.0.18.0 …  (19 in all)
FAIL the HSA runtime set ships without libhsa-runtime64.so.1
FAIL the HSA runtime set ships without libhsa-amd-aqlprofile64.so.1
FAIL the HSA runtime set ships without librocprofiler-register.so.0
FAIL the HSA runtime set ships 0 rocm_sysdeps entries, fewer than the 8 the runtime needs

The green arm on a fixed payload is the release gate's job, since building and installing one is exactly what was deferred.

Deferred by owner order (not done here)

No package build, no new install directory under ~/.local/share, no drop-in edit (never mind .bak), no restart of coder-llm (:8090) or 1bit-canonical (:8095), and no /work-c22-hidden independence run. Those happen at the release gate once all issues are solved; /work-c22 and the two running services are untouched.

Scope and checks

scripts/mk-plugin-package.sh, scripts/pack-check.sh (new), scripts/weekly.sh, tests/pack_check_test.sh (new), CMakeLists.txt, NOTICE-BINARY. bash -n clean on the touched scripts; shellcheck -S warning adds nothing (weekly.sh keeps its three pre-existing findings); tools/copyright.py --check rc 0; ctest pack_check 5/5 here.

…ase refuses one that resolves outside it (Refs #1411)

The c22 install's llama-server maps 64 files out of /work-c22/hrx-toolchain/... - the ROCm core runtime set -
and ldd shows none of them, because libhsa is opened BY NAME at run time (ggml-hrx-dmabuf.cpp:29,
dlopen("libhsa-runtime64.so.1")). Pruning that toolchain therefore kills both services, and no dependency row
can see it. Two things were missing, and this is both halves:

1. The payload ships the whole set. scripts/mk-plugin-package.sh staged what ldd resolves, which covers the
   runtime, rocprofiler-register and the 8 rocm_sysdeps entries - but NOT libhsa-amd-aqlprofile64.so.1,
   which the runtime opens by name itself. It is staged now, beside the runtime, and the build refuses when
   it is not where the runtime was found. NOTICE-BINARY gains the set's licences, checked against upstream
   rather than assumed: ROCr is the University of Illinois/NCSA licence, aqlprofile and rocprofiler-register
   are MIT, and the rocm_sysdeps entries keep their upstream licences (bzip2, libdrm MIT, elfutils, liblzma
   public domain, libnuma LGPL-2.1, zlib, zstd).

2. The release check fails when anything resolves outside the payload. The #1461 rows move from weekly.sh
   into scripts/pack-check.sh (so a test can source them) and gain runtime_selfcontained:
   - no packed ELF may declare an absolute RUNPATH/RPATH entry;
   - every library ldd resolves for every packed ELF must be inside the payload or a system directory;
   - the HSA runtime set has to be present beside the backend: libhsa-runtime64.so.1,
     libhsa-amd-aqlprofile64.so.1, librocprofiler-register.so.0 and at least 8 rocm_sysdeps entries.
   It runs for both packages, next to the build-path row.

tests/pack_check_test.sh (ctest pack_check, in CI) exercises both rows on synthetic trees and asserts each
row's own message: a clean tree as the control, a build-machine path, an absolute RUNPATH, a RELATIVE rpath
that resolves outside the payload, and a runtime set without aqlprofile.

That last arm earned its keep: it failed on the first version of the row, because ldd prints the path it was
given unnormalised ("<tree>/bin/../../outside/libx.so"), so a prefix test read a library that had left the
payload as "inside" it. The row canonicalises each resolved path now, and the arm is what proves it.

Measured 2026-11-08, the red arm on the install that is live today
(~/.local/share/1bit-embed-c22/lemonade-onebit-rc-2026-11-03-c22): 19 FAIL lines, rc 1, including every packed
library's /work-c22 RUNPATH (libhrx.so.0, libllama.so.0.0.1, llama-server, libggml-hrx.so.0.18.0,
libloomc.so.0, ...) and "the HSA runtime set ships without libhsa-runtime64.so.1 /
libhsa-amd-aqlprofile64.so.1 / librocprofiler-register.so.0" plus "0 rocm_sysdeps entries".

Not run, by owner order: no package build, no new install directory, no drop-in change, no service restart,
and no /work-c22 independence run. The end-to-end proof (a fixed payload serving on HRX0 with /work-c22
hidden, and the two services switched to it) is deferred to the release gate once the open issues are
solved; #1411 stays open with that noted.

checks: bash -n on the touched scripts; shellcheck -S warning adds nothing (weekly.sh keeps its three
pre-existing findings); tools/copyright.py --check rc 0; ctest pack_check runs here (5/5 arms) and in CI.
…1411)

CI's build job compiles with gcc-16 and ships no bare `cc`, so the first version of the test failed there
with "no C compiler (cc)" - the row was right and the harness assumed a name. It probes $CC, cc, gcc,
gcc-16, gcc-15, clang now, and only refuses when the box has none at all (which cannot build this repository
either, so that is a failure and not a skip).

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY at 449b3538f555f7596e8747466ab2920e4a3437c0

The right two halves for a runtime the backend dlopens by name, where ldd can't see it:

  • the payload now ships libhsa-amd-aqlprofile64.so.1 beside the runtime, and the build refuses when it isn't found;
  • the release refuses a payload that resolves anything outside itself.

Checked at this head (a831785 + 449b353, base on main):

  • The move is complete. build_path_strings (#1475's row) now lives in scripts/pack-check.sh, which weekly.sh sources (:59-60), and both rows run for both packages: :429-430 for the engine tarball and :479-480 for the Lemonade package. Nothing from #1461 was dropped in the move.
  • The test passes: tests/pack_check_test.sh (bash, as add_test at CMakeLists.txt:256 runs it) → pack_check: all rows PASS. That covers the clean control, a runtime set without aqlprofile, an absolute RUNPATH, a relative rpath that escapes the tree, and a build-machine path. The escaping case is the one that needed the canonicalisation (ldd prints the unnormalised bin/../../outside/…), and it's pinned.
  • The allowlist is tight: /lib/*, /lib64/*, /usr/lib/*, /usr/lib64/* and the payload's own canonical path. So /opt/rocm-therock, /usr/local/lib or a leftover /work-*/hrx-toolchain all fail, which is exactly the c22 shape.
  • bash -n passes on pack-check.sh, weekly.sh and mk-plugin-package.sh; tools/copyright.py --check is rc 0.

Leaving #1411 open for the live install and service check, per the owner's order, is the right scope.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

CI at 449b3538: build pass (6m19s), and the new test runs in it —

21/130 Test  #21: pack_check .......................   Passed    1.48 sec
100% tests passed, 0 tests failed out of 130

It failed on the first push, and the reason is worth recording: the CI build job compiles with gcc-16 and ships no bare cc, so the harness reported "no C compiler (cc) to build the tree with". The row was fine; the test assumed a name. It probes $CC, cc, gcc, gcc-16, gcc-15, clang now, and refuses only when the box has none at all — which cannot build this repository either, so that stays a failure rather than a skip.

…payloads are self-contained (Refs #1411)

The row this PR adds runs for both packages, and the second one is the payload the services actually run
from: the Lemonade + onebit package carries the engine tarball as 1bit/. That tarball shipped no HSA runtime
at all - measured: 0 files in c33's lemonade-onebit payload - so the new row would have failed every roll on
it, correctly but uselessly, until the set arrived where both packages inherit it. mk-plugin-package.sh had
been overlaying the set for the engine package only.

So the packer stages it, from the toolchain THIS build used (read out of the build's CMakeCache, so a build
cannot be packed against a toolchain it did not use), and refuses if the toolchain, the core lib directory or
any of the three roots is missing. The set is the transitive NEEDED closure of libhsa-runtime64,
libhsa-amd-aqlprofile64 and librocprofiler-register - not every file in rocm_sysdeps/lib, which also holds
libraries nothing here opens: my first version globbed them all and shipped 36 files, taking the payload from
17.5 to 30.5 MB for nothing. Closure only, flat beside the backend, unmodified (NOTICE-BINARY promises the
set as built, so it is staged after pack_localize/strip, the way the licences are).

Measured 2026-11-08, packing the existing c33 build tree (no rebuild):

  hrx/llama/bin: + the HSA runtime set (12 files: the runtime, its aqlprofile agent, the profiling shim and
                 rocm_sysdeps)                       payload 19.9 MB, was 17.5
  the 12 are exactly the owner's list - libhsa-runtime64.so.1, libhsa-amd-aqlprofile64.so.1,
  librocprofiler-register.so.0 and the 8 rocm_sysdeps the runtime links (bz2, drm, drm_amdgpu, elf,
  liblzma, numa, z, zstd) - and libhsa/libhsa-amd-aqlprofile64 are byte-identical to the toolchain's
  (sha256), so what ships is what TheRock built
  runtime_selfcontained <unpacked tarball>  ->  ok  ... and the HSA runtime set it dlopens ships beside it
                                                rc 0
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

One more commit, ba60557f, because the row this PR adds pointed at my own fix being half a fix.

The row runs for both packages, and the second one is what the services actually run. lemonade-onebit-… carries the engine tarball as 1bit/, and that tarball shipped no HSA runtime at all — measured: 0 files in c33's lemonade payload. So the new row would have failed every roll on it: correctly, but uselessly, because mk-plugin-package.sh was overlaying the set for the engine package only.

scripts/package-linux.sh stages it now, so both packages inherit it:

  • from the toolchain this build used, read out of the build's CMakeCache.txt (ONEBIT_HRX_TOOLCHAIN), so a build cannot be packed against a toolchain it did not use;
  • refusing if the toolchain, the core lib directory, or any of the three roots is missing;
  • the closure, not the directory: the first version globbed rocm_sysdeps/lib/* and shipped 36 files, taking the payload 17.5 → 30.5 MB for libraries nothing here opens. It is the transitive NEEDED closure of the three roots now (same rule mk-plugin-package.sh applies), staged flat and unmodified after pack_localize/strip — NOTICE-BINARY promises these as built.

Measured by packing the existing c33 build tree, no rebuild:

hrx/llama/bin: + the HSA runtime set (12 files)          payload 19.9 MB, was 17.5
the 12 = exactly the owner's list: libhsa-runtime64.so.1, libhsa-amd-aqlprofile64.so.1,
         librocprofiler-register.so.0, and the 8 rocm_sysdeps the runtime links
libhsa-runtime64.so.1 / libhsa-amd-aqlprofile64.so.1 byte-identical to the toolchain (sha256)

runtime_selfcontained <unpacked tarball>
  ok   every packed ELF resolves inside the payload or the system loader, declares no absolute RUNPATH,
       and the HSA runtime set it dlopens ships beside it          rc 0

That is the green arm on a real packed tree; the red arm is the c22 install in the body (19 FAIL lines). Still nothing installed, nothing restarted, /work-c22 untouched — the services stay on c22 until the release gate.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY at ba60557f8dce54c5afa1a661c11dfd532d90a26d

The premise is exactly right, and I measured all three parts of it on this box:

1. libhsa opens the agent by NAME, so ldd cannot see it
   strings libhsa-runtime64.so.1 | grep aqlprofile   -> libhsa-amd-aqlprofile64.so        (a bare soname)
   readelf -d libhsa-runtime64.so.1 | grep -c aqlprofile -> 0                             (not in the dynamic section)

2. the agent exists beside the runtime in the toolchain
   /opt/rocm-therock/…/_rocm_sdk_core/lib/libhsa-amd-aqlprofile64.so.1                   present

3. and the shipped payload does NOT carry it
   find <c32 payload> -name '*aqlprofile*'                                                  -> nothing

So the sentence in the comment is not a hypothesis — "a payload built from ldd alone ships a runtime that reaches for it wherever the machine happens to offer it — which is how engine #1411's c22 install ended up mapping 64 files out of a builder's toolchain" is the direct consequence of those three facts: the runtime is present, the dependency is invisible to ldd, and the loader therefore resolves the agent from whatever path is on the machine, including a builder's tree baked into LD_LIBRARY_PATH.

And the harness that catches it runs green here:

$ bash tests/pack_check_test.sh
PASS  #1411: a runtime set without aqlprofile fails
PASS  #1411: an absolute RUNPATH fails
      RUNPATH  Library runpath: [$ORIGIN/../../../../outside]         (real readelf output in the failing fixture)
PASS  #1411: a library resolved outside the payload fails
PASS  #1461: a build-machine path in a packed ELF fails
pack_check: all rows PASS

Four pack_check failures that are all the same disease — the payload is not self-contained — each proven on a synthetic payload rather than asserted. The "a library resolved outside the payload fails" row in particular is what would have caught c22 directly: it does not care why a library came from outside.

The build refusing when the agent is not beside the runtime ([ -n "$aq" ] || { echo "no $pat beside $libhsa: …") is the right failure: adding the file silently would ship an empty promise, and a payload that cannot satisfy a dlopen must fail at build time rather than on a user's machine.

All four scripts parse (mk-plugin-package.sh, package-linux.sh, weekly.sh, tests/pack_check_test.sh). The weekly.sh diff moves the logic into package-linux.sh (−46/+38), which is where a payload's closure belongs.

Not run: a full c34 roll (the release lane's next cut). What I can take — the dlopen premise, the missing file in the shipped payload, and the harness's four arms — is measured.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant