Skip to content

CI: a nightly job tests each runtime's newest upstream release and records the result - #13

Open
IsuraManchanayake wants to merge 12 commits into
basecompute:rc-0.1.3from
IsuraManchanayake:upstream/nightly
Open

IsuraManchanayake wants to merge 12 commits into
basecompute:rc-0.1.3from
IsuraManchanayake:upstream/nightly

Conversation

@IsuraManchanayake

@IsuraManchanayake IsuraManchanayake commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

A scheduled workflow installs the newest upstream llama.cpp build and MLX version untested, through each adapter's own plan --untested and install, and runs the adapter's tests on it: llama.cpp on macos-14 and ubuntu-24.04 with a small GGUF, MLX on macos-14 in the install's own environment and against its own Python. A release that passes arrives as a PR adding it to the adapter's recipe (scripts/nightly_runtimes.py <runtime> append): for llama.cpp with the size and SHA-256 GitHub lists for each asset, for MLX with the environment pip installed for the tests, every wheel with the size and SHA-256 PyPI lists for it, read from a real pip install --report. One that fails opens an issue with the run attached. workflow_dispatch tests a named release.

$ python3 scripts/nightly_runtimes.py llamacpp pick
version=b11630
$ python3 scripts/nightly_runtimes.py llamacpp append b11630
note: b11630 lacks llama-b11630-bin-ubuntu-openvino-x64.tar.gz; left out
recorded b11630 (2026-10-14) with 15 assets in crates/superfluid-adapters/superfluid-adapter-llamacpp/recipe.json

A schedule fires only from the default branch, so the job needs merging to main and the Actions setting that lets workflows open pull requests.

The llama.cpp macOS leg runs on a self-hosted Apple silicon runner ([self-hosted, macOS, ARM64]), not the hosted macos-14. On a self-hosted runner the job keeps the machine's toolchain (rustup's bin directory on the job's PATH, the toolchain action skipped), runs no rust-cache and uses no model cache, since the machine keeps its work directory and the model between jobs.

Why

The tested lists in the recipes are what superfluid compat declares and what a release installs on its own; without a job they fall behind upstream, and a human types digests. See the new section "How an upstream release becomes a tested one" in docs/models/runtimes.md.

The hosted macos-14 runner cannot pass the adapter's real-model tests on any build: its virtualized GPU takes llama.cpp's fallback Metal kernels, where a batched round differs from solo decode past the tests' bound and a padded batch answers a zero logit row, and its 7 GB leaves the adapter one shared pool where the tests expect a buffer per lane. The failures were bit-identical across runs (https://github.com/IsuraManchanayake/superfluid/actions/runs/38725440780, https://github.com/IsuraManchanayake/superfluid/actions/runs/38739018175); the same suite passes on real Apple silicon, which is what a tested build has to mean for a Mac. rust-cache stays on hosted runners because its save step deletes every regular file in ~/.cargo/bin that cargo did not install during the job, rustup included on a developer's Mac (https://github.com/IsuraManchanayake/superfluid/actions/runs/38744781241/job/116457149024); the model cache too, since its key is the workflow file's hash and every edit re-uploaded 386 MB from the runner.

How it was tested

Reviewed in the fork as IsuraManchanayake#4, #5 and #14.

  • cargo clippy --all-targets -- -D warnings
  • cargo test
  • Changes a wire protocol, the WAL format or the tick ABI (says so above, version bumped)

…cords the result

Each adapter installs on its own only the upstream versions it was built
and tested against, so those lists go stale as llama.cpp and MLX release.
The job picks the newest upstream release the recipe does not list
(scripts/nightly_runtimes.py), installs it untested, and runs the adapter's
tests on it: llama.cpp on macos-14 and ubuntu-24.04 with a small GGUF for
the model tests, MLX on macos-14 with the worker built as a release ships
it. A llama.cpp build that passes arrives as a PR adding it to the recipe
with the size and SHA-256 GitHub lists for each of its assets, the same
asset names the previous tested build carries; one that fails opens an
issue with the run attached. An MLX version is pinned by hand, wheel by
wheel, so its result is an issue either way. workflow_dispatch tests a
named release instead of the newest.
…Python

recipe::install leaves two site-packages: the bundled interpreter's under
python/ and the environment's under env/, where mlx and mlx-lm went. The
job took the first find found, which can be the bare interpreter's, and
built its test executables against whatever Python PyO3 picked on the
runner. Now SUPERFLUID_MLX_VENV names env/, checked by importing mlx and
mlx-lm from it, and PYO3_PYTHON names the install's Python 3.12.
An MLX version that passes the adapter's tests arrives the same way a
llama.cpp build does: as a PR adding it to the adapter's recipe, built
from pip's report of the environment it resolved on the runner, every
wheel with the size and SHA-256 PyPI lists for it. The issue stays for
a version that fails, which includes one needing a newer Python than
the adapter ships.
A dry-run resolution made after the install could name other versions or
wheels than the environment installed moments before, and the recipe would
pin those as tested. The report now comes from a real install into the
test environment, which the tests then run on.
- A listing that fails (issues disabled, no permission) was read as an
  open issue and the run recorded nothing: every listing check now
  stops with a clear error.
- The MLX test executable, built against the install's Python, links
  @rpath/libpython with no rpath into the install: the install's lib
  directory is named for dyld.
- Hosted runners share GitHub's unauthenticated API rate limit, and the
  worker's plan of an untested build hit it: the kit sends GITHUB_TOKEN
  for api.github.com when set, and the plan step has the job's token.
- A runtime has one open issue; a later build that fails too is noted
  on it instead of opening one a day.

Seen on the fork: https://github.com/IsuraManchanayake/superfluid/actions/runs/38715098729
…e adapter runs on

pip on a runner or machine newer than macOS 14 resolves the mlx and
mlx-metal wheels built for its own macOS, which an older Mac cannot
install. The recording script now pins, for a wheel with a macOS tag, the
same wheel built for the oldest macOS at or above 14 that PyPI offers,
with the size and digest PyPI lists for it.
The hosted macos-14 runner's GPU is virtualized: llama.cpp takes its
fallback Metal kernels there, a 40-token prompt costs seconds, a padded
batch answers a zero logit row, and a batched round differs from solo
decode past the tests' bound, whatever the build; and with 2 GB free the
adapter keeps one shared pool where the tests expect a buffer per lane.
The leg now asks for a self-hosted Apple silicon runner and keeps the
toolchain that runner has.
The runner's PATH is its owner's shell's at start, which need not name
~/.cargo/bin; the first run on a Mac died in fifteen seconds on
'cargo: command not found'.
…stup

Swatinem/rust-cache's save step removes every regular file in ~/.cargo/bin
that cargo did not install during the job: on a hosted runner that is
nothing of value, on a developer's Mac it was rustup itself. A self-hosted
runner keeps its work directory between jobs and needs no cache.
…e file

Its key is the workflow file's hash, so every edit to the workflow
missed the cache and the post step uploaded the model from the runner:
on a laptop, minutes at a megabyte a second.

This branch has not been deployed

No deployments
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