Repository navigation
Two-Mac cluster serving: real JACCL worker, guided link setup, coordinator and provider fixes - #1407
Draft
jonathan308 wants to merge 581 commits into
Draft
jonathan308 wants to merge 581 commits into
jonathan308 wants to merge 581 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
jonathan308
requested a deployment
to
benchmarks
October 9, 2026 03:38 — with
GitHub Actions
Waiting
jonathan308
requested a deployment
to
benchmarks
October 9, 2026 04:08 — with
GitHub Actions
Waiting
jonathan308
requested a deployment
to
benchmarks
October 9, 2026 05:20 — with
GitHub Actions
Waiting
jonathan308
requested a deployment
to
benchmarks
October 9, 2026 06:18 — with
GitHub Actions
Waiting
…ther model over its saved setup Hardware (M3 Ultra + M5 Ultra): after the 9B was saved, step 4 on the leader never planned the 27B: `r` re-plans only where the wizard may plan, which needed an SSH pair description in the record. A leader that paired over the link (S17) now plans again on `r` too, asking the other Mac over the paired link. Checked: provider build passes (darkbloom a34667b456b31992); console runner PASS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hree record-module files The pairing merge added ClusterPairingCeremony, ClusterPairingProtocol and ClusterPairingStore to DarkbloomClusterSecurity; the runner's source closure guard still expected 14 files and refused to run. With 17 the closure compiles and 23 codec/adapter/data-plane groups PASS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shown on hardware tonight: the 27B planned over the paired link and the saved 9B setup with r, accepted on the other Mac, served. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ns, not when it parses; pairing tests use a real temp path The developer gate on `darkbloom cluster --pair` (DARKBLOOM_CLUSTER_DEVELOPER_SSH=1) moves from option validation to the wizard's start, so the parser keeps its own contract (its test failed on the gate) and the gate has its own test (refused without the flag or with another value, allowed with 1, nothing to refuse without a pair). The pairing provider tests' temporary stores use the real path of the temporary directory: the installed-file checks refuse a path through /var, so aStartAfterUnpairingNamesTheUnpairing (from the pairing merge) failed on every Mac. Checked: build passes (darkbloom 559aee86707c639a); ClusterConsoleCommandTests + ClusterPairingProviderTests 14/14 PASS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… at the index cuts: faster than the M5 Ultra alone for four rows; GPT-OSS cut 4 fails Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Ultra alone; MiMo on the new pair Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ofile with its speed index too The guided flow behind `darkbloom cluster plan` and `start --distributed` now asks the installed plan tool for `device --json --index`, as the setup wizard does, and its --peer-profile hint names the same command, so both Macs' indices reach the estimator. Placement setup 23 + flow 12 checks, console 74,372. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ink setup v2) (S18) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ites and permissions; boot wait covers the port's GID (S18) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir (9B, 27B) Cable results: every row exact or a near tie against Mac A references with step records; throughput at concurrency 1/2/4/8; pool ceilings and peak footprint per Mac; one-request paged decode is 6-12 % slower than contiguous. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ngine refusals name their reason (S18) On M3 Ultra x M5 Ultra the canary was refused by the pair engine (DistributedEngineError.unsupportedRequest, shown only as "error 1") on every formation, so each pair was retired 4 s after commit. An omitted knob takes the model's generation_config default, which the pair engine refuses as non-greedy; the submit error now names the engine's reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…as the pair's scheduler does (S18)
h3 on M3 Ultra x M5 Ultra named the refusal: the pair's execution owner
refuses a request carrying a prefix-cache receipt ("worker supports only
greedy text; stop strings stay in the provider"). The canary used the
bridge's chat submit with the cache on, which mints a receipt when a
prefix cache tier is present; it now passes cacheEnabled false. (The
explicit greedy knobs of c849f7d still guard models whose
generation_config sets top_p/top_k; Qwen3.5-9B has none.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ather check A paged host's row snapshot gathers every attention layer's pages; building it after every decode token cost the one-request worker 6-12 % of its decode rate. One-token steps on a paged host still check each row's frontier and binding; the attendable views are checked at adoption, after prefill frames and after speculative windows. build-native-worker.sh: keep only existing regular files when locating the paged-attention source (nullglob kept a missing literal path). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9B and 27B, eight requests each on the M3 Ultra + M5 Ultra pair: tokens equal the earlier paged run exactly; decode +0.9 % (9B) / +3.2 % (27B); paged stays 10 % (9B) / 3 % (27B) behind contiguous in the one-request worker. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…8 link session, S17 pairing, S09 pair serving slot, S16 data plane) into integration round 3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… member pair-failure diagnostics; member stop/cancel receipts) into integration round 3 Conflict: ProviderLoop+ClusterPairServing.swift pair-readiness catch. Kept the sanitized follower refusal (DistributedInstalledStartRefusal via startFailure) and added public-e2e's readiness/owner diagnostics for the other failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on parity-shaped qualifications) into integration round 3 Conflicts (5): the grant file takes D4's qualification structure (ClusterChipClass, per-order/cut/mode qualifications) and keeps the reconcile branch's M5 Ultra rows (9aea390: M3 Ultra rank 0 + M5 Ultra rank 1 for the 9B at cut 16 and the 27B at cut 32, phase split with MTP) as qualifications; the member preparation decides the grant inside prepare() (D4) and keeps public-e2e's DEBUG roster-unpaired switch; StartCommand keeps the loop-and-preparation split; the placement check runner compiles both sides' sources; the eligibility tests expect the M5 Ultra as a decoding class of the 9B and the 27B only. The link session's grant check follows the rename (chipClass). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in the rank's reservation; serving context; MTP acceptance on cache hits) into integration round 3 Conflicts (8), all additive: the worker ready message, codec, validation, pair readiness and resident runtime carry both the S16 data plane and the serving context; the resident request log keeps the prefix charge fields and the S16 status line; the pair check takes both the data-plane flags/checks and the serving-context check and log prefixes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x reasons, streamed tool calls, queue + 429) into integration round 3 Conflicts (9): the pipe owner and the CBv2 admission take S19's profile refusals and sampling (greedy-only guards removed) and keep round 2's serving-context refusal up front; the pair check reserves with the request's sampling and keeps the refused-request handling; the installed serving table and its checks keep D4's grant-based rows (052a501's "open the 27B" is the same change as D4's 9ec79d7); the fake worker and protocol/contract checks carry both sides (serving context and sampling groups). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t budget/backoff, link-ready wait, re-formation) into integration round 3 Conflict: EngineV2Bridge+Translation admission message keeps round 2's retryable capacity wording for refusedForCapacity and supervision's named engine-error message for every other DistributedEngineError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… status mirror, telemetry) into integration round 3 Conflicts (8), additive: worker runtime protocol carries the serving-context refresh / reservation give-back and the request telemetry; the engine factory and local server pass the model directory/type and the pair telemetry; the link listener starts the follower status monitor and binds the interface that reaches the leader address; `darkbloom cluster` lists member-identity, pair and incidents; the check runners compile both sides' sources. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dex, conformance/golden plans for every family, derived per-token request charge) into integration round 3 Conflicts (7): the planner charges a requested context beyond the planned request with round 2's policy charge and otherwise universal's per-token layout charge at the request's context (plus its prefill transient), and keeps round 2's headroom preference within ties; the dense family keeps both the per-token charge and round 2's largest-context charge (now through QwenPlacementCharge); the stage load policy and the design note keep public-e2e's wording of the same speculative-pages fix (both branches made it); the pair check keeps every runtime log prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ault, continuous-batching lanes, one-token paged decode fix) into integration round 3 Conflicts (13), additive: the resident runtime carries round 2's serving context / prefix budget and the batch settings / KV kind (both in the load agreement with the S16 data-plane fields); the worker runtime protocol has give-back, serving-context refresh, telemetry and the lane calls; the pair takes the serving-context refusal and the lane count; the CBv2 admission keeps S19's profile refusal before the concurrent-requests guard; the installed session forms its pair with the plan's lanes and names a follower's refusal; the pair check keeps every flag (serving context, data plane, batching). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…frame is queued instead of 5 ms polling) into integration round 3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the condition it failed The installed-input check refuses a file with more than one link (another name could change it after verification), but said only "Installed input is unsafe, empty, oversized or not executable". On the M5 Ultra a model folder hard-linked into another tool's cache refused every pair start at rank 1 with no way to tell which file or why. The refusal now reads e.g. "Installed input model-00001-of-00002.safetensors is unsafe: has 2 hard links (copy or clone it instead of linking it)". The conditions are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The lane session (paged batching) accepts the keep-warm answer's serving context (round 2) within the profile, and one request telemetry event per request (observability), as the one-request session does. - The pair check's last lease is optional (a refused last request leaves none); the batch phase retires it only when there is one. - The worker's give-back of an unstarted reservation names the one active request through the lane-aware accessor. - The admission message lists the capacity refusal in the engine error switch; its retryable wording follows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er refuses names the file and the condition) into integration round 3 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r the round 3 merges - The owner wrote a worker's abnormal-exit stderr twice: public-e2e's forwarder (c1347ed) and S24's echo (observability). One record is kept, S24's `darkbloom-worker-exit-v1` / `darkbloom-worker-tail-v1` lines, which the incident log reads; it now removes control characters as the forwarder did. The late-diagnostic drain check (SSHChecks) passes again. - The owner-writer-wake round trip matches the keep-warm answer's serving context (round 2). - The console runner compiled two member sources twice (merge of both lists). - The placement check's mirrored-order case follows D4: the 9B fixture's grant lets only the M3 Ultra lead, so asking the M5 Max to lead is refused at plan time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The D4 grant test's normal-order 27B now lists the M5 Ultra as a decoding class as well (the 9aea390 rows kept as qualifications). - The non-streamed first-token-budget test waits for the release that follows the acknowledged retirement: on the integrated engine the HTTP error can reach the client before the lease is released (failed once in the full suite at 962247e with releaseCount 0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch is waiting to be deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Experimental two-Mac ("cluster") serving: one model split across two Macs as a two-stage layer pipeline over MLX's JACCL backend (RDMA over Thunderbolt 5). This PR carries the provider and runtime side of the foundation extracted from the research drafts (#1226, #1227), completes the parts that extraction left out, and adds what was needed to run it on real hardware. Ordinary single-Mac serving is unchanged; everything here is opt-in (
darkbloom start --local --distributed,darkbloom cluster …).Status: draft. As of 2026-10-12 the pair serves through the product path and through a locally built coordinator, including a public request with billing, on an M3 Ultra + M5 Ultra over RDMA, with SSH-free guided setup. See the round 3 update below and "Not shown yet".
Update 2026-10-12: integration round 3 (SSH-free guided setup, public serving, paged KV and MTP by default, supervision, observability)
Head
d88ba3ee6, 227 signed commits onfa41f769d, provider and runtime only. Merged in order: the SSH-free guided setup (S07/S08 link session, S17 pairing, S09 pair serving slot, S16 encrypted data plane), the public end-to-end path (registration v3, stop/cancel receipts, and a memory-gate fix that counts speculative file pages, without which a 256 GB M5 Ultra refused every stage), per-model pair-serving grants (D4), the prefix-cache memory fix (F1), the request envelope (S19), always-on supervision (S18), cluster observability (S24), universal placement with the planner crediting MTP, paged KV in cluster stages by default, and the owner writer waking on enqueue. Three integration fixes. The optimization experiments (tensor parallel, SSD prefix tier, hand-off speedups, N-Mac, exact rows, decode engine) are not in this round.Hardware: new test pair, M3 Ultra (256 GB) + M5 Ultra (256 GB) over Thunderbolt 5 RDMA, both cable ports isolated by link setup v2 (one macOS admin approval per Mac). All on
bin/integration3unless noted:darkbloom start --local --distributed+darkbloom cluster link-follower, no SSH in setups): 9B and 27B with phase split + MTP; prefix reuse (repeat first token 0.17 s 9B, 0.39 s 27B); client hang-up; clean stop with memory back on both Macs.bin/universal9): all pass the near-tie rule against both Macs' own single-Mac references at the cuts a fresh install picks, except GPT-OSS at cut 4 (diverges at its second token; under investigation, see below).bin/paged2): tokens identical to the earlier paged runs on all 16 requests; 27B paged decode within 3 % of contiguous, 9B within 10 %.Tests at the head: cluster library 246 (only the known file-cache environment test and one memory-interference test); worker 58; all 32 check runners including StartupChecks; provider native suite 4,635 tests with only the 7 baseline failures. Private-detail scan of every added line: clean.
Not shown yet / known gaps (tracked):
DARKBLOOM_CLUSTER_KV_BACKEND=contiguousis the interim switch.darkbloom-cluster-worker/darkbloom-cluster-planinto Darkbloom.app; members do not start themselves after a reboot until the S18 login agent ships in a Developer-ID-signed build (which is also what makes the Local Network approval one-time).darkbloom clusteron one Mac, then on the other; 2 commands and 4 keys on this pair) is proven on a separate branch and is not in this round.Update 2026-10-11: integration round 2 (no fixed lifetime, 32K context, MTP and prefix reuse on by default, SSH-free link channel, setup wizard, MLX guard v2)
Ten first-parent commits on
1cb482bf8(six merges, the MLX pin move, three fixes; provider and runtime only):phase_split_mtp_v1, drafter and verifier on the decoding Mac) for models that ship a head.cached_tokensin usage.darkbloom cluster.fdae3174(JACCL guard v2: 30 s limit, teardown exit with memory release; revert of the NAX quantised-MoE simdgroup skip) and mlx-swift64070c4(mlx-c489e965).Tests: cluster library 211/212 (one environment-dependent test); worker package 71; 28 check runners + worker startup checks; provider native suite 4,557 tests with only master's five environment failures.
Hardware (M3 Ultra rank 0 + M5 Max rank 1 over Thunderbolt 5 RDMA, indicative figures): all nine families pass on this head (tokens equal to the single-Mac reference or first difference at an exact tie). MTP default: 9B decode 115-124 tok/s (acceptance 0.73-0.79), 27B 44.7-50 tok/s (pipeline without MTP 26). Prefix reuse with MTP: repeat first token 9B 2.35 → 0.093 s, 27B 8.60 → 0.166 s (5,120 tokens), 9B 20,480 tokens 10.5 → 0.070 s. 9B 20,480-token request with MTP passes. Faults: a rank killed mid-decode releases memory on both Macs with no SIGKILL; guard v2 dead peer exits 75 in ~30 s.
Known limits: the 27B 20,480-token request was refused by the M5 Max's per-request memory gate with ~43 GB of desktop applications resident there (the rank had admitted a 32K context at load); a refused request currently ends the pair session (being fixed); on a prefix-cache hit MTP acceptance drops; not yet run on hardware: provider-served pair sessions and coordinator-driven serving.
Update 2026-10-10: every model family across both Macs, placement, clean hang-up, link setup v2, no orphaned memory
This branch now carries ten merged work branches (128 signed commits on
807ac8ea9, 345 files, provider and runtime only; no coordinator code):libs/mlx-swift-lmto9e901a0, the stage seam in draft Layr-Labs/mlx-swift-lm 301), MiMo V2.6 Flash (one resident catalog), Gemma 4 26B — in addition to the registered 9B and 27B.darkbloom cluster plan.Tests: cluster library 196/197 (one environment-dependent sampler test); worker package 66/66; 27 check runners; provider native suite 4,558 tests with only master's five environment failures.
Hardware smoke on this head's products (M3 Ultra rank 0 + M5 Max rank 1 over Thunderbolt 5 RDMA; one cold request each, indicative; pass = exact, tokens equal with logits differing, or first difference at a near tie):
Every run: both ranks exited cleanly, no worker process left, wired memory back on both Macs. Fault on this build (9B, rank 1 SIGTERM mid-decode): the signalled rank released and exited in 0.07 s, the survivor ended itself through the release path, wired memory back on both Macs within 1.1 s, no SIGKILL.
Not in this head yet: long prompts to 32K (proven separately on the 9B and 27B at 5,120 to 30,720 tokens; to be merged), the MLX pin move to the guarded JACCL head with the NAX MoE simdgroup-skip revert (draft Layr-Labs/mlx 33
fdae3174, mlx-swift 5464070c4), MTP on the cluster path, coordinator-driven serving.Note: draft d-inference PR 1426 is stacked on this branch; it needs a plain merge of the new head.
Linked issue
None.
Before and after
Before — the extracted branch could not start a collective, could not finish a distributed start, and gave the operator no way to see why.
flowchart LR classDef bad fill:#fde2e1,stroke:#b42318,color:#3b0d0a classDef ok fill:#e3f1e5,stroke:#1e7a34,color:#0b2e14 classDef neutral fill:#eef1f5,stroke:#5b6676,color:#1a2230 OP["Operator"]:::neutral --> START["start --local --distributed"]:::neutral START --> REG["Register as cluster member"]:::ok REG --> ACK["Coordinator never acknowledges"]:::bad ACK --> KILL["Leader tears down after 10 s"]:::bad START --> WRK["Launch rank worker"]:::neutral WRK --> NOSRC["Worker has no source in the tree"]:::bad WRK --> STUB["macOS 14 target compiles the JACCL stub"]:::bad OP --> LINK["Thunderbolt port without its own IPv4 address"]:::neutral LINK --> SILENT["Nothing reports it; JACCL refuses at init"]:::badAfter — a 26.2 worker package builds real JACCL, the coordinator (its side is in darkbloom-platform) acknowledges and guards members, and the link is inspected before anything is launched.
flowchart LR classDef ok fill:#e3f1e5,stroke:#1e7a34,color:#0b2e14 classDef neutral fill:#eef1f5,stroke:#5b6676,color:#1a2230 classDef pending fill:#fff3d6,stroke:#9a6700,color:#3d2a00 OP["Operator"]:::neutral --> CL["darkbloom cluster link"]:::ok CL --> VERDICT["Per-port verdict and one sentence of guidance"]:::ok OP --> STAGE["darkbloom-cluster-stage-check"]:::ok STAGE --> LOADED["Each Mac loads and releases its stage of the real model"]:::ok OP --> START["start --local --distributed"]:::neutral START --> REG["Register as cluster member"]:::ok REG --> ACK["cluster_member_accepted"]:::ok START --> WRK["darkbloom-cluster-worker, macOS 26.2 build"]:::ok WRK --> JACCL["Real JACCL group"]:::ok JACCL --> XPORT["Collectives over RDMA: exact on two Macs"]:::ok XPORT --> RUN["Two-rank generation of the real model"]:::ok RUN --> SERVE["Served by start --distributed and the coordinator"]:::pending OP --> SETUP["darkbloom cluster: detect, one approval, ready"]:::okGreen is exercised by a test or a run recorded below; amber is built but not yet run on two Macs.
What changed
Native worker package (
libs/darkbloom-cluster-worker, new). The provider and the shared cluster modules build for macOS 14, where the pinnedmlx-swiftcompiles the JACCL stub. A sibling package with a macOS 26.2 minimum builds the real backend with no submodule pin change.build-native-worker.shrefuses a binary that holds the stub or whose Mach-O minimum is not exactly 26.2. Products:darkbloom-cluster-worker— the rank worker the installed plan launches (from Preserve shared distributed runtime and provider integration #1226).darkbloom-cluster-collective-check— both ranks run it; verifies reductions, point-to-point transfers and ordering byte for byte. No model.darkbloom-cluster-stage-check— one Mac, one rank, the real artifact, no collective: verified stage load, release, and memory before and after.darkbloom-cluster-reference,darkbloom-cluster-pair-check— a single-Mac staged reference, a two-Mac driver (rank 0 local, rank 1 over SSH, never signals a worker) and a comparator with a closed set of verdicts (exact,tokensEqualLogitsDiffer,divergedAtNearTie,diverged,incomparable).A pair could not decode before this PR: rank 0 handed over its residual while Metal completion handlers still held the buffer, and the sender refuses storage it does not own alone. One GPU stream synchronization on rank 0's path fixes it (
fix(cluster): drain the GPU stream before rank 0 hands over its residual). That change is on the serving path and wants a careful read.A rank's last resort against a native call that never returns is now a deadline thread (
ProcessDeadline), not onlyalarm: on real hardware a rank spinning in the RDMA completion poll ran more than nine minutes past its alarm.Coordinator. Moved to
Layr-Labs/darkbloom-platform(draft pull request 6 there): the member acknowledgement, the pair lifecycle and quarantine release, pair formation, routing an owner's requests to its pair's leader, and the owner views. Nothing undercoordinator/differs from master here.Provider
darkbloom cluster(no subcommand): guided link setup. It checks RDMA, waits for a Thunderbolt connection, and if the connected port has no IPv4 address of its own it gives it a link-local one through one macOS approval prompt and installs a small root launchd job (one file under/Library/LaunchDaemons, two system tools, no installed program) that re-adds the address whenever the port has none; then verifies. A one-time address proved not durable on a Mac whose port is a bridge member: it was gone about two hours later with no restart or replug.--temporarykeeps the address-only path,--dry-runprints the exact commands and changes nothing, andcluster link --removeremoves the job, its file and the address.cluster link --fix,--removeand--watchare the pieces. Darkbloom never calls sudo. The root job has not run behind a real approval yet.--cluster-memberstart mode; 21 research test files ported.cluster_membershipin the member registration, the pair approval saved bycluster configure, the member control installed before connecting and attached at acceptance. This build declines every pair preparation on purpose: a coordinator-formed pair needs an owner mode, a worker bootstrap and a rank-1 request channel that are not in the tree, so a member answers 503 instead of half-serving.darkbloom cluster console(and baredarkbloom clusteron a terminal that can draw): an interactive screen over the operations above. Readiness comes from the link probe and doctor, pairing and trust from the saved setup with an explicit approval that counts only once the whole question has been on screen for 500 ms, the admitted models from the runtime's own list; it can fix the link, start and stop the local session, recover a stranded journal and write a redacted diagnostic export.handoff/TUI-wiring.mdlists 53 screen items: 31 wired to a real operation, 10 whose operation exists but is not connected, 12 with no operation yet, and a check fails if the screen shows anything outside the wired set.--plainand--jsonprint the same state without a terminal.darkbloom cluster link [--json], also run bycluster doctor: read-only inspection of RDMA state, port state, interface address, bridge membership and the IPv4-mapped GID, with a closed set of verdicts. No address, MAC or GID is kept from tool output.provider.tomlowner-only and atomically; a file first created by an ordinary save was 0644 and the cluster readers refused it.start --local --distributed):cluster statusandcluster doctorsay that nothing vouches for the peer that answers.darkbloom cluster recoverclears a journal whose owner died, and refuses while any process still holds the device or carries the recorded epoch.cluster doctorrefuse a worker binary that does not carry the progress guard.Runtime
A second registered model. The resident adapter selects its model from a closed catalog (
QwenResidentModelDefinition), and admission, byte ceilings, capability and the worker's model and cut validation are per registered model. The registered Qwen3.8 27B is admitted with every structural cut; the 9B's receipts, capability bytes and tests are unchanged.Phase split, a second generation mode beside the pipeline, for both registered models: the pair prefills with lookahead, rank 0 hands its committed request state to rank 1 after the first token (one checked transfer per state tensor, verified by rank 1 before it adopts, a 20 s limit bound into the agreement), and rank 1, which holds the whole model, decodes alone and relays tokens to rank 0 in batches of 1, 2, 4, 8 then 16: 41 transfers after the first token for 128 outputs against 1,397 in the pipeline. The worker takes
--generation-modeand advertises the modes it supports; the pipeline stays the default and its agreement bytes are unchanged and pinned by a test. A correctness-only local-socket transport lets two ranks run on one Mac for tests; it and the fault-injection switch are refused unless a worker is started with--qualification-switches yes.Benchmark tooling under
scripts/benchmarks/cluster/(a streamed load generator with a self-test, an isolated serve harness, artifact verification against manifests) and the night's report,docs/reports/2026-10-09-two-mac-baseline-and-model-matrix.md: the single-Mac "before" through the product's serving path on both Macs, and the state of every catalog model on each.Six of twelve slices of delivering a follower's stage over the link (design and measurements in
handoff/DESIGN-stage-transfer.md): a pinned per-tensor content inventory with a generator and an independent Python cross-check, the pin for the registered 9B, the transfer plan and state machines, a loader seam that separates stage metadata from where the bytes come from (all 14 existing load receipts unchanged), stage metadata derived from the pin with no weight file, and the MLX-side intake with astage-check transfermode. In one process on the M3 Ultra a follower's stage (3.98 GB) is received and verified in 1.65 s against 4.72 s for a local load of the same stage, and corrupted, swapped and truncated inputs are refused; the follower is ready about when a local load would be, so the transfer buys a follower with no weights on disk, not time. Nothing has crossed the link or run between two processes, and the load path does not use it yet.Host gate: swap left from earlier is admitted while memory pressure is normal and refused under warning pressure. The previous rule refused any Mac with swap in use. Free-page requirements are unchanged.
Resident admission tests: a real positive path on both ranks from the registered metadata, and one property per refusal. The previous suite could not fail for the reason each case named.
Test plan
Run on an M3 Ultra (256 GB, macOS 27.2) and an M5 Max (128 GB, macOS 27.0), Xcode 27.0, one Thunderbolt 5 cable.
ClusterLinkCheckswith 1,353 expectations)swift test --package-path libs/darkbloom-cluster— 22 tests in 5 suites;DarkbloomClusterWorkerTestsbuilt for 26.2 — 9 testsmake provider-testafter merging master — the same 75 tests fail as on unmodified master on this machine (same 148 issues, same 56 XCTest errors; mostly a missingpagedattention.metaltest resource and MiMo fixtures). The branch adds 142 tests to those targets and none of them faildarkbloom-cluster-stage-checkon the registered Qwen3.5 9B artifact: every supported cut and both ranks on the M3 Ultra, cut 4 and both ranks on the M5 Max. Each loads in about 3 seconds, releases to a few KB active and 0 cached, and both Macs report the same storage commitmentdarkbloom clusteron both Macs: one reported ready; on the other the approval prompt was accepted, the address added and verifieddarkbloom-cluster-collective-checkacross the two Macs: 5.36 GiB of reductions and transfers, 0 mismatches on both ranks, IP byte counters at 0.0003% of the payload63fd73706(after the 27B and stage-transfer commits): cluster library 37 tests in 8 suites; worker package 48 XCTest cases; all 18 check runners (11 library, 3 worker package includingStageTransferChecks, 4 provider; the link runner has 2,072 expectations in 32 groups); the provider buildsdarkbloom cluster --dry-runon both Macs with the new build: the M3 Ultra needs no prompt; the M5 Max prints the fourteen commands the approval would run and changes nothing. The job definition, built by the same command chain, was loaded as a harmless stand-in in a user launchd domain: runs at load and every 10 s, survives six immediate reinstalls, unloads cleanly4e5f70793, built with--build-system nativeas the release tooling builds it: 4,539 tests in 621 suites, 5 failing, the same five that fail on unmodified master on this machine (three MiMo tests that need CI's fixtures, two that depend on the home directory). With the toolchain's default build system 75 fail on master too, because nested resource bundles are not found657a22d8f(phase split and console): library tests 72 in 10 suites; worker package tests (16 and 41); 19 self-contained check runners, the console's with 11,689 expectations in 23 groups on pseudo-terminals; the full provider suite, native build system, 4,544 tests in 622 suites, failing only the five environment tests that fail on masterq, Ctrl-C, SIGTERM and SIGHUP restore the terminal; a second instance is refused; fifteen existing read-only commands print the same output as before895aece69and the test-only12250136a(memory gate second version, stage-transfer slices 3, 4 and 6, installed-path mode selection): library tests 102 in 14 suites; worker package tests; 20 check runners (the worker startup checks 15 of 15 against a built worker); the full provider suite, native build system, 4,548 tests in 623 suites, failing only the five environment tests that fail on masterdarkbloom start --local --distributedon the two Macs, then the lifecycle faultsmake docs-checkreports one broken link, indocs/reports/2026-10-03-all-model-prefix-qualification.md; it is on master (from #1326) and unchanged here.make docs-impact-checkpasses.Components touched
Layr-Labs/darkbloom-platformProtocol / interface changes
No protocol/interface changes
Yes — described above and matching side updated
The provider registers
cluster_membershipin the member role and expectscluster_member_accepted; the coordinator side of both is in darkbloom-platform draft 6. A coordinator without it ignores the fields and never sends the acknowledgement, and the provider's negotiation timer then ends a distributed start.New CLI:
darkbloom cluster(guided setup),darkbloom cluster link,darkbloom cluster recover.cluster doctornow also fails when the local link is not ready or the worker binary has no progress guard.cluster statusreports the bootstrap selection and the progress limit.New optional worker argument
--startup-deadline-uptime-nanoseconds; exit status 123 when it expires. New optional worker arguments--generation-modeand--qualification-switches;supportedGenerationModesin the capability record, omitted for a pipeline-only runtime so existing records keep their bytes. An owner and a worker must come from one tree: an owner without this change refuses a new worker's capability record.New CLI:
darkbloom cluster console [--json|--plain] [--dry-run] [--temporary].New decision-receipt policy identifier
registered_dense_selected_stage_load_resources_v2.New runtime model ID
registered_qwen38_27b; the worker validates--model-idand--stage-cutagainst the closed catalog. New optional fieldcontentInventorySHA256on a registered specification, in no existing fingerprint.darkbloom clustergains--dry-runand--temporary;cluster linkreports gainaddressKept, andguidancecan be non-null while the state isready.No release artifact or version change.
Documentation impact
docs/reference/cluster-control-protocol.md,protocol-messages.md,configuration.md,docs/provider/installation.md(uninstall removes the link job), and the two package READMEs. Coordinator-only pages are back at master's text; citations of coordinator files that moved are marked as paths in the platform repository.make docs-impact-checkpasses;make docs-checkreports only the broken link that is on master.Not shown by this PR
handoff/DESIGN-pair-prefill-engine-decode.md; by its arithmetic the gain for this model is a few percent at the one measured pair configuration. A layer pipeline helps prefill and models that do not fit one Mac; tensor parallel is not in this runtime.libs/mlxorlibs/mlx-swift. Until it does, the guarded JACCL exists only in builds that check out the mlx branch by hand, and the installed path refuses to serve without it.handoff/DESIGN-resource-gate-v3.md). The review's concern about the first version was confirmed on hardware: with the cache held mapped and read by another process, a load on the M5 Max compressed 18.4 GiB while the pressure level still read normal; that is what the growth check is for. Run on both Macs for both models: the after-download state, cache made of the model being loaded, cache held by another process, two loads started together, and held loads that must still be refused. Not done: each of its commits built and tested on its own, and the comparison of the final binaries with the ones the hardware runs used. Small Macs are still refused from the after-download state (a 32 GB Mac never admits a 10 GiB stage). This is a resource-admission policy change and wants a decision before it is relied on anywhere else.Notes for reviewers
Refactor pass. The link work had its own refactor pass and a second read-only review. The coordinator half had both before it moved. The library changes for the 27B and the stage-transfer slices have had correctness reviews by their authors' reviewers but not yet the dedicated refactor pass this repository requires; it will run over the library once the branches still in flight (phase split, memory gate) have landed, and this pull request stays a draft until then.
A wait that could hang. Two provider unit tests in the suite "Owned native member invocation" failed intermittently in CI (12 of 30 isolated runs locally). The cause was a product defect, not the tests:
Process.waitUntilExit()for a process started from a dispatch thread returned late and was sometimes never reported, so a member never learned its owner had exited, stayed quarantined and never released its pair.ClusterProcessExitnow takes the exit from the termination handler and latches it, at the remote endpoint, the worker process pump and the capability probe; no bound was loosened and no signal added or moved. A check runs 300 sessions from a dispatch thread and fails at about session 47 on the old files. After: 0 of 30 isolated suite runs. Worth a reviewer's eye: the one SIGKILL that can reach a worker is in the worker process pump, after the worker's own hard deadline, SIGTERM and a further margin (the documented exception inClusterWorkerSignalPolicy); and the endpoint can SIGKILL an owner past its ceiling, which would leave a still-live worker with a closed command stream and nobody to clear the journal.AGENT-HANDOFF.mdandhandoff/are working documents (status by gate, gap map, evidence ledger, runbook). They are here so the state is reviewable; drop them before merge if they do not belong in the tree.The swap-gate change is a resource-admission policy change. The previous rule came from the research measurements; I found no measurement behind it, but it wants a second opinion.
🤖 Generated with Claude Code