The app builds its Metal kernels from a fork of mlx-core pinned at v0.31.1 (July 2026). Upstream is at v0.32.2. The move was attempted on 2026-07-27 and parked: mlx 0.31.2 made a stream's GPU command encoder thread-local, and our Swift-concurrency runtime evaluates arrays from whatever thread a task resumes on, so every test that hopped threads died with There is no Stream(gpu, 0) in current thread. The proper fix, new_thread_unsafe_stream, was not reachable from Swift because mlx-c had no binding for it. docs/mlx-core-fork.md has the full account and the re-attempt checklist.
What changed: the binding landed. ml-explore/mlx-c#122 (merged 2026-08-27) exposes it as mlx_stream_new_thread_unsafe, and upstream mlx-swift main already carries the regenerated header. mlx-swift does not yet create its default streams through it, so step 1 of the checklist is now "adopt the binding in mlx-swift" rather than "wait for mlx-c".
What the move involves (from the parked branch pin-tesseract-2026-07-27 in both forks, which built green against mlx main at the time):
- Rebase the parked branch onto mlx v0.32.2 / current mlx-swift
main in spokvulcan/mlx and spokvulcan/mlx-swift; re-run tools/update-mlx.sh and check the hand-maintained JIT kernel list for kernels added since (a missing one is a link error, not a build error).
- Make mlx-swift's default streams thread-unsafe streams via the new binding, shaped as an upstream mlx-swift PR.
- Re-port the carries that do not rebase: C4, C5, C7 (they patch the command-buffer struct upstream merged into
CommandEncoder) and re-check C6 against the regex removal in mlx#3869. Re-measure each per the experiments ledger's A/B protocol before re-accepting. The DFlash2 loop's kernel carries (qmv_wide backport, mma8 QMM tiles, SDPA variants, dynamicSlice, fast-math custom kernels, profiler probes) rebase onto the new base and need the fixture identity check (scripts/dflash2-bench.sh --bench-check on travel/code/math).
- Keep the two
GPU.setCommitLimits call sites in LLMActor working: their Swift surface rides C4+C7, so they go dark on the new base until those are re-ported.
- Move the three lockstep
Package.swift pins and update docs/mlx-core-fork.md.
Why now: the 2026-09-15 vendor re-pin (mlx-swift-lm to upstream main, swift-transformers to 1.3.4, swift-jinja to 2.5.1) left mlx-core as the only fork level still on a July base, and the qmv_wide backport it carries is already upstream in v0.32.0, so the carry list shrinks on the move.
Not in scope of the re-pin PR: this is a multi-day port with re-measurement, tracked here separately.
The app builds its Metal kernels from a fork of mlx-core pinned at v0.31.1 (July 2026). Upstream is at v0.32.2. The move was attempted on 2026-07-27 and parked: mlx 0.31.2 made a stream's GPU command encoder thread-local, and our Swift-concurrency runtime evaluates arrays from whatever thread a task resumes on, so every test that hopped threads died with
There is no Stream(gpu, 0) in current thread. The proper fix,new_thread_unsafe_stream, was not reachable from Swift because mlx-c had no binding for it.docs/mlx-core-fork.mdhas the full account and the re-attempt checklist.What changed: the binding landed. ml-explore/mlx-c#122 (merged 2026-08-27) exposes it as
mlx_stream_new_thread_unsafe, and upstream mlx-swiftmainalready carries the regenerated header. mlx-swift does not yet create its default streams through it, so step 1 of the checklist is now "adopt the binding in mlx-swift" rather than "wait for mlx-c".What the move involves (from the parked branch
pin-tesseract-2026-07-27in both forks, which built green against mlx main at the time):maininspokvulcan/mlxandspokvulcan/mlx-swift; re-runtools/update-mlx.shand check the hand-maintained JIT kernel list for kernels added since (a missing one is a link error, not a build error).CommandEncoder) and re-check C6 against the regex removal in mlx#3869. Re-measure each per the experiments ledger's A/B protocol before re-accepting. The DFlash2 loop's kernel carries (qmv_wide backport, mma8 QMM tiles, SDPA variants,dynamicSlice, fast-math custom kernels, profiler probes) rebase onto the new base and need the fixture identity check (scripts/dflash2-bench.sh --bench-checkon travel/code/math).GPU.setCommitLimitscall sites inLLMActorworking: their Swift surface rides C4+C7, so they go dark on the new base until those are re-ported.Package.swiftpins and updatedocs/mlx-core-fork.md.Why now: the 2026-09-15 vendor re-pin (mlx-swift-lm to upstream main, swift-transformers to 1.3.4, swift-jinja to 2.5.1) left mlx-core as the only fork level still on a July base, and the qmv_wide backport it carries is already upstream in v0.32.0, so the carry list shrinks on the move.
Not in scope of the re-pin PR: this is a multi-day port with re-measurement, tracked here separately.