Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.1.4

- `superfluid serve` takes `--pull-file <name>` for a baseRT model: one `.base` from a repo that holds several, such as one whose files are named by size (`Foo-Medium.base`) rather than by quant. It is passed to `basert pull --file`, and the model is served from the install `basert list --json` records that file in. It needs basert 0.4.1 or later.
- `superfluid runtime install basert`, and a `serve` that installs it first, take the newest baseRT release this build drives, as 0.1.3 said they did: it installed the newest pinned one (0.3.0), and a newer release only with `--sha256`. The engine's digest is the one GitHub lists for it; the pins, now with 0.4.0, answer offline.

## 0.1.3

Expand Down
18 changes: 17 additions & 1 deletion crates/superfluid-adapters/superfluid-adapter-basert/recipe.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
{
"about": "baseRT engines this adapter installs without being told a SHA-256, as basecompute publishes them: each engine with the size and SHA-256 its release lists (the digest GitHub shows and the release's own <name>.sha256 agree). A version not here installs only with --sha256. Add a release here, newest first, after checking both digests.",
"about": "baseRT engines this adapter installs with no release listing, as basecompute publishes them: each engine with the size and SHA-256 its release lists (the digest GitHub shows and the release's own <name>.sha256 agree). With the listing, the newest release whose engine ABI this build drives is installed, by the digest GitHub lists for its engine; these pins answer offline and for `runtime versions`. Add a release here, newest first, after checking both digests.",
"release": "https://github.com/basecompute/baseRT/releases/download/v{version}/{name}",
"releases": "https://api.github.com/repos/basecompute/baseRT/releases?per_page=50",
"pinned": [
{
"version": "0.4.0",
"abi": 1,
"assets": [
{
"name": "basert-engine-linux-arm64-cuda-0.4.0.tar.gz",
"size": 36349756,
"sha256": "8b06deb5a51e03d6327f1cd0149abf6b19f3aefa74758afceba59eb289bdb5c2"
},
{
"name": "basert-engine-macos-arm64-0.4.0.tar.gz",
"size": 20249930,
"sha256": "c9b49ea1906fa871c646df3f62fb8237b53a817f786356323ff04bda1e6e79f2"
}
]
},
{
"version": "0.3.0",
"assets": [
Expand Down
94 changes: 82 additions & 12 deletions crates/superfluid-adapters/superfluid-adapter-basert/src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,19 @@ pub fn version_of(name: &str) -> Option<String> {

pub fn plan(req: &Request, host: &Host) -> Result<Vec<Plan>, String> {
let pins = pinned();
// With no version named the newest release is wanted, which only the listing knows; offline the pins answer.
if req.version.is_none() && req.from.is_none() {
return match fetch_json(recipe()["releases"].as_str().unwrap_or_default()) {
Ok(releases) => plan_with(req, host, &pins, &releases),
Err(listing) => {
let mut plans = plan_with(req, host, &pins, &Value::Null).map_err(|why| format!("{why} ({listing})"))?;
Comment on lines +89 to +92

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.

Codex Review — [P2] Bound the listing lookup before putting it ahead of the pins

Every unversioned install (including a fresh serve) now waits here before using a pinned release. fetch_json() in adapter-kit invokes curl without a connect timeout, overall timeout, or low-speed deadline, so a connection that succeeds but stops sending the listing can block forever; the Err branch that is supposed to provide the pinned fallback is never reached. I reproduced this using a local TLS fixture that sends HTTP 200 and then stalls its JSON body: the old planner returned its pin in ~0.38s, while the new one remained blocked until I terminated it after 8s. This affects users whose API connection stalls even when release assets are reachable. Give release discovery a bounded deadline and fall back to the pins on expiry before making it unconditional.

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.

Accepted, fixed in e22a3ac. The fix is in the kit, since fetch_json is also what llama.cpp's release lookup and the speculator catalog call, and neither should block forever either. Every lookup now ends within 20 s, retries included (--connect-timeout, --max-time, --retry-max-time), and fetch_json_within(url, deadline) takes another deadline. When the listing times out, plan falls back to the pins as designed.

Failing first: a_json_lookup_that_stalls_gives_up_by_its_deadline points a lookup with a 2 s deadline at a socket that accepts and never answers. Without the flags it gave no answer within the test's 15 s; with them it returns an error in about 2 s. I also checked your case, HTTP 200 headers followed by a body that never comes, directly with curl and the same flags: it exits 28 at the deadline, with no retry past it.

for p in &mut plans {
p.why = format!("{} (the newest pinned: {listing})", p.why);
}
Ok(plans)
}
};
}
plan_with(req, host, &pins, &Value::Null).or_else(|why| {
if req.from.is_some() {
return Err(why);
Expand Down Expand Up @@ -185,6 +198,13 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) ->
extra: Value::Null,
}]);
}
let prerelease: Vec<String> = releases
.as_array()
.into_iter()
.flatten()
.filter(|r| r["prerelease"].as_bool().unwrap_or(false))
.filter_map(|r| Some(r["tag_name"].as_str()?.trim_start_matches('v').to_string()))
.collect();
let mut published: Vec<(String, Option<u32>, Asset)> = releases
.as_array()
.map(|rs| {
Expand All @@ -194,7 +214,9 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) ->
let version = r["tag_name"].as_str()?.trim_start_matches('v').to_string();
let want = engine(&version);
let a = r["assets"].as_array()?.iter().find(|a| a["name"] == want.as_str())?;
let asset = Asset { name: want, url: a["browser_download_url"].as_str()?.to_string(), sha256: None, size: a["size"].as_u64() };
// GitHub's own digest of the file as uploaded, the one the release's `.sha256` states too.
let sha256 = a["digest"].as_str().and_then(|d| d.strip_prefix("sha256:")).map(str::to_string);
let asset = Asset { name: want, url: a["browser_download_url"].as_str()?.to_string(), sha256, size: a["size"].as_u64() };
Some((version, abi_marked(r), asset))
})
.collect()
Expand All @@ -207,11 +229,11 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) ->
let sha256 = req.sha256.clone().or(pinned.sha256.clone());
return Ok((version, Asset { sha256, ..pinned }));
}
match &req.sha256 {
Some(sha) => Ok((version, Asset { sha256: Some(sha.clone()), ..asset })),
match req.sha256.clone().or(asset.sha256.clone()) {
Some(sha) => Ok((version, Asset { sha256: Some(sha), ..asset })),
None => Err(format!(
"basert {version} has no SHA-256 pinned in this superfluid, so it is not installed on what the release page alone \
says: check the engine's SHA-256 (baseRT publishes it as {}.sha256) and pass it with --version {version} --sha256 <digest>",
"basert {version} has no SHA-256 pinned in this superfluid, and GitHub lists none for its engine: check the engine's \
SHA-256 (baseRT publishes it as {}.sha256) and pass it with --version {version} --sha256 <digest>",
asset.name
)),
}
Expand All @@ -229,7 +251,12 @@ pub fn plan_with(req: &Request, host: &Host, pins: &[Pin], releases: &Value) ->
(version, found.1, asset)
}
None => {
let found = pinned_here.into_iter().find(|(v, abi, _)| drives(v, *abi)).or_else(|| published.into_iter().find(|(v, abi, _)| drives(v, *abi)));
// The newest this build drives, pinned or not; a pin stays ahead of the listed copy of its own version, and a
// pre-release is installed only when named.
let listed = published.into_iter().filter(|(v, _, _)| !prerelease.contains(v));
let mut known: Vec<(String, Option<u32>, Asset)> = pinned_here.into_iter().chain(listed).collect();
known.sort_by_key(|(v, _, _)| std::cmp::Reverse(version_key(v)));
let found = known.into_iter().find(|(v, abi, _)| drives(v, *abi));
let found = found.ok_or_else(|| {
format!(
"baseRT publishes no engine this superfluid drives ({}) for {part} yet (the newest is {newest}; this superfluid was built \
Expand Down Expand Up @@ -334,18 +361,61 @@ mod tests {
}

#[test]
fn a_release_with_no_pinned_digest_needs_one_named() {
fn the_newest_release_this_superfluid_drives_is_installed_pinned_or_not() {
let (same, other) = (abi(), abi() + 1);
let pins = [pin("0.3.9", None, "cd"), pin("0.4.5", Some(same), "ef")];
let mut releases = json!([release("0.3.9", None), release("0.4.5", Some(same)), release("0.4.6", Some(same)), release("0.5.0", Some(other))]);
let p = &plan_with(&Request::default(), &mac(), &pins, &releases).unwrap()[0];
assert_eq!((p.version.as_str(), p.tested, p.assets[0].sha256.as_deref()), ("0.4.6", true, Some("ab")), "the digest GitHub lists");
assert!(p.assets[0].url.starts_with("https://github.com/basecompute/baseRT/releases/download/v0.4.6/"));
assert_eq!(plan_with(&Request::default(), &mac(), &pins, &Value::Null).unwrap()[0].version, "0.4.5", "offline, the newest pin");
releases.as_array_mut().unwrap().remove(2);
let p = &plan_with(&Request::default(), &mac(), &pins, &releases).unwrap()[0];
assert_eq!((p.version.as_str(), p.assets[0].sha256.as_deref()), ("0.4.5", Some("ef")), "a listed release that is pinned keeps its pin");
assert_eq!(p.assets[0].url, "https://pinned/basert-engine-macos-arm64-0.4.5.tar.gz");
let mut pre = release("0.4.8", Some(same));
pre["prerelease"] = json!(true);
let p = &plan_with(&Request::default(), &mac(), &pins, &json!([pre, release("0.4.7", Some(same))])).unwrap()[0];
assert_eq!(p.version, "0.4.7", "a pre-release is never the newest");
}

#[test]
fn a_pre_release_is_installed_when_named_and_never_as_the_newest() {
let same = abi();
let mut pre = release("0.4.8", Some(same));
pre["prerelease"] = json!(true);
let releases = json!([pre, release("0.4.7", Some(same))]);
assert_eq!(plan_with(&Request::default(), &mac(), &[], &releases).unwrap()[0].version, "0.4.7");
let named = Request { version: Some("0.4.8".into()), ..Request::default() };
let p = &plan_with(&named, &mac(), &[], &releases).unwrap()[0];
assert_eq!((p.version.as_str(), p.tested, p.assets[0].sha256.as_deref()), ("0.4.8", true, Some("ab")));
// One of another ABI, as before: with --untested and a digest of one's own.
let mut other = release("0.5.1", Some(same + 1));
other["prerelease"] = json!(true);
let req = Request { version: Some("0.5.1".into()), untested: true, sha256: Some("12".into()), ..Request::default() };
let p = &plan_with(&req, &mac(), &[], &json!([other])).unwrap()[0];
assert_eq!((p.version.as_str(), p.tested, p.assets[0].sha256.as_deref()), ("0.5.1", false, Some("12")));
}

#[test]
fn a_release_with_no_digest_pinned_or_listed_needs_one_named() {
let same = "0.4.9".to_string();
let releases = json!([release(&same, Some(abi()))]);
let p = &plan_with(&Request::default(), &mac(), &[], &json!([release(&same, Some(abi()))])).unwrap()[0];
assert_eq!((p.version.as_str(), p.assets[0].sha256.as_deref()), (same.as_str(), Some("ab")));
let mut unlisted = release(&same, Some(abi()));
unlisted["assets"][0].as_object_mut().unwrap().remove("digest");
let releases = json!([unlisted]);
let e = plan_with(&Request::default(), &mac(), &[], &releases).unwrap_err();
assert!(e.contains(&format!("basert {same} has no SHA-256 pinned")) && e.contains("--sha256"), "{e}");
assert!(e.contains(&format!("basert {same} has no SHA-256 pinned")) && e.contains("GitHub lists none") && e.contains("--sha256"), "{e}");
assert!(e.contains(&format!("basert-engine-macos-arm64-{same}.tar.gz.sha256")), "{e}");
let e = plan_with(&Request { version: Some(same.clone()), untested: true, ..Request::default() }, &mac(), &[], &releases).unwrap_err();
assert!(e.contains("has no SHA-256 pinned"), "--untested is about the version, not the digest: {e}");
let named = Request { version: Some(same.clone()), sha256: Some("12".into()), ..Request::default() };
let p = &plan_with(&named, &mac(), &[], &releases).unwrap()[0];
assert_eq!((p.assets[0].sha256.as_deref(), p.assets[0].size), (Some("12"), Some(10)));
assert!(p.assets[0].url.starts_with("https://github.com/basecompute/baseRT/releases/download/"));
for releases in [releases, json!([release(&same, Some(abi()))])] {
let p = &plan_with(&named, &mac(), &[], &releases).unwrap()[0];
assert_eq!((p.assets[0].sha256.as_deref(), p.assets[0].size), (Some("12"), Some(10)), "the digest named wins");
assert!(p.assets[0].url.starts_with("https://github.com/basecompute/baseRT/releases/download/"));
}
}

#[test]
Expand Down
20 changes: 20 additions & 0 deletions crates/superfluid-adapters/superfluid-adapter-kit/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::Duration;

use serde_json::{json, Value};

Expand Down Expand Up @@ -402,11 +403,18 @@ pub fn fetch(asset: &Asset, dir: &Path) -> Result<(PathBuf, String), String> {
}

pub fn fetch_json(url: &str) -> Result<Value, String> {
fetch_json_within(url, Duration::from_secs(20))
}

/// A lookup a caller falls back from must end: an answer that stalls fails once `deadline` has passed, retries included.
pub fn fetch_json_within(url: &str, deadline: Duration) -> Result<Value, String> {
if !url.starts_with("https://") {
return Err(format!("{url}: only https:// URLs are fetched"));
}
let secs = deadline.as_secs().max(1).to_string();
let mut curl = Command::new("curl");
curl.args(["-fsSL", "--proto", "=https", "--tlsv1.2", "--retry", "2", "-H", "Accept: application/json"]);
curl.args(["--connect-timeout", &secs, "--max-time", &secs, "--retry-max-time", &secs]);
// GitHub's API allows sixty unauthenticated requests an hour per address, which hosted
// runners share; a token in the environment lifts that, for the API alone.
if url.starts_with("https://api.github.com/") {
Expand Down Expand Up @@ -573,6 +581,18 @@ pub fn own_install() -> Option<PathBuf> {
mod tests {
use super::*;

#[test]
fn a_json_lookup_that_stalls_gives_up_by_its_deadline() {
// A server that takes the connection and never answers, as a stalled API does.
let server = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let url = format!("https://127.0.0.1:{}/releases", server.local_addr().unwrap().port());
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || tx.send(fetch_json_within(&url, Duration::from_secs(2))));
let got = rx.recv_timeout(Duration::from_secs(15)).expect("the lookup gives up by its deadline");
assert!(got.is_err(), "{got:?}");
drop(server);
}

#[test]
fn the_driver_version_is_read_from_the_kernel_module_line() {
let line = "NVRM version: NVIDIA UNIX Open Kernel Module for aarch64 580.95.05 Release Build (dvs-builder@U16-I1-N08-12-3) Thu Sep 18 2025\nGCC version: gcc 13";
Expand Down
2 changes: 1 addition & 1 deletion docs/models/runtimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ SYCL and OpenVINO builds are installed only with `--backend`.

**MLX.** Apple silicon only. Fetches CPython 3.12.14 (python-build-standalone) and, from PyPI, `mlx`, `mlx-metal`, `mlx-lm` and their dependencies, every wheel pinned by hash (tested: mlx 0.32.2, mlx-lm 0.31.3). Installs with `pip --require-hashes --no-deps --only-binary=:all:`; your own Python and `PYTHON*`/`PIP_*` settings are not used (pip's network settings excepted).

**basert.** Engine bundles from baseRT's releases for `macos-arm64` (Metal) and `linux-arm64-cuda`; other machines are refused. A release is tested when its major.minor matches the engine headers this daemon was built with; another needs `--untested` and may not load. Each engine's SHA-256 is pinned in the adapter; a release without a pin installs only with `--version V --sha256 H` (baseRT publishes each engine's digest beside it as `<engine>.sha256`), never on the release page's word alone. Instead of installing, you can name an existing library with `--basert-lib` or `BASERT_LIB`.
**basert.** Engine bundles from baseRT's releases for `macos-arm64` (Metal) and `linux-arm64-cuda`; other machines are refused. A release is tested when the engine ABI number it publishes beside its bundles (`basert-engine-abi-<N>.txt`) is the one this daemon was built for, or, for a 0.3.x release, which states none, by its line; another needs `--untested` and may not load. With no `--version`, the newest tested release on baseRT's release page is installed, checked against the SHA-256 GitHub lists for its engine (the digest baseRT also publishes beside it as `<engine>.sha256`); the adapter also pins releases with their digests, which install the same way offline, when the release page cannot be read. A release GitHub lists no digest for, and that is not pinned, installs only with `--version V --sha256 H`. Instead of installing, you can name an existing library with `--basert-lib` or `BASERT_LIB`.

## How a worker is found

Expand Down
7 changes: 7 additions & 0 deletions docs/release-notes/0.1.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,12 @@ superfluid serve basecompute/Qwen3.8-27B-BaseQuant-V1 --pull-file Qwen3.8-27B-Ba
`.base` extension is optional, and a name several directories share is given
with its path. It needs basert 0.4.1 or later.

## Fixed

- `superfluid runtime install basert`, and a `serve` that installs the baseRT
engine first, take the newest baseRT release this build serves. They took
0.3.0, and a newer release only with `--sha256`, so `--pull-file` failed on
a fresh install.

See [supported models](https://github.com/basecompute/superfluid/blob/v0.1.4/docs/models/supported_models.md)
for every runtime's pull options.
Loading