Repository navigation
Merge upstream ml-explore/mlx main afc03c511 (2026-10-23) - #38
Merged
Merged
Conversation
…4266) Co-authored-by: Cheng <git@zcbenz.com>
…lore#4284) Co-authored-by: Cheng <git@zcbenz.com>
Co-authored-by: Daniel Hiltgen <daniel.hiltgen@ollama.com> Co-authored-by: Cheng <git@zcbenz.com>
…e#4356) Co-authored-by: Cheng <git@zcbenz.com>
Co-authored-by: Cheng <git@zcbenz.com>
Co-authored-by: Cheng <git@zcbenz.com>
Co-authored-by: katlun-lgtm <katlun@windyviews.com> Co-authored-by: Cheng <zcbenz@gmail.com>
Co-authored-by: Cheng <git@zcbenz.com>
Signed-off-by: Pradyot Ranjan <99216956+pradyotRanjan@users.noreply.github.com> Co-authored-by: Pradyot Ranjan <99216956+pradyotRanjan@users.noreply.github.com> Co-authored-by: Cheng <git@zcbenz.com>
Co-authored-by: Cheng <git@zcbenz.com>
…re#4738) Co-authored-by: Cheng <git@zcbenz.com>
Split test_fast_sdpa and test_quantized into named tests and rest entries in the CPU shards, so that no CPU shard has an entry longer than test_gated_delta_sequential. Put the entries in longest-first order. Save the ccache files and the Metal kernel cache only when the build had misses. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The full --version text of clang and metal names the install folder and the OS, and it changed between three macos-26 runner images, so every job started from an empty cache. Key ccache on the clang major.minor version and fall back to a cache of another clang version; ccache keeps compiler_check=content. Key the Metal kernel cache on the metal compiler version and the kernels hash, and fall back only to a cache of the same metal compiler. Each Metal entry key holds the first line of the metal --version text and the SDK build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream 5dbe999 (ml-explore#4634) makes is_donatable() also require that MLX owns the buffer, so the allocator never reuses a wrapped external buffer. The fork's mlx-c buffer-info API reported `unique` from is_donatable(), and mlx-swift-lm uses that as a "no other reference" proof for paged KV segments, checkpoint memory accounting and MTP state, including wrapped host pages, which now always read as not unique. Add is_uniquely_referenced(): the same reference-count test without the ownership test. is_donatable() and donation behavior do not change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
test_eval.test_async_eval_error_in_synchronize (upstream ml-explore#4338) failed on some runs of the fork's CPU test pass: mx.async_eval raised the LU factorization error that the test expects only from mx.synchronize. Cause: StreamThread::enqueue throws the stream's stored error when it runs on the main thread (ml-explore#3742). async_eval enqueues several tasks. When the stream thread finishes the failing task before async_eval enqueues the next one, that enqueue throws. The order depends on thread timing, so the failure is intermittent. The fork does not change this path. async_eval now holds a scheduler::DeferStreamErrors guard. While it lives, enqueue on that thread does not throw the stored error, and synchronize throws it later. eval does not change. Add a C++ test that stores the error before async_eval, so it fails on every run without the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
davidtai
added a commit
to Layr-Labs/mlx-c
that referenced
this pull request
Oct 23, 2026
Layr-Labs/mlx#38 moved from 699649d3 to 36f043ec: async_eval no longer throws an error that an earlier CPU stream task stored. It fixes an upstream ordering race between #3742 and #4338, and synchronize still raises the error. No mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hosted macOS runners are a shared pool. Pack the GPU pass into 2 shards and the CPU pass into 3 shards, balanced by the durations that run 39902948153 measured. A run now uses 8 macOS jobs, and at most 6 run at the same time (before: 10 jobs, 8 at the same time). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The preprocessed source of the NAX kernels names the Metal toolchain mount folder, which has a random suffix on each runner. The 10 NAX kernels missed the Metal cache in every warm build (41 of 51 hits). Remove the suffix from the text that the key hashes, and keep the toolchain version. CPU shard 0 was the slowest shard in the cold and warm runs of b72420d (651 s and 470 s of tests). Move test_quantized:rest to CPU shard 1 (221 s). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ci: build once and run the Metal and CPU test passes in parallel
Merge the 9 upstream commits after c6abf0d: ml-explore#4729, ml-explore#4730, ml-explore#4731, ml-explore#4728, ml-explore#4736, ml-explore#4737, ml-explore#4724, ml-explore#4738 and ml-explore#4744. The merge has no conflict. Every merged file gets exactly upstream's change. ml-explore#4728 widens the affine qmv_wide input-vector tile from 5 to 8. The fork has no qmv_wide change in this repository, and the GPT-OSS MXFP4 decode tail is an fp gather_qmv kernel that ml-explore#4728 does not touch. Each input vector still accumulates in its own registers, so the results do not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set base.hash to the new merge-base after the merge of the 9 upstream commits. No fork section changes: the merge has no conflict, and each merged file gets exactly upstream's change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
davidtai
added a commit
to Layr-Labs/mlx-c
that referenced
this pull request
Oct 23, 2026
Layr-Labs/mlx#38 merged 9 more upstream ml-explore/mlx commits, through afc03c511 (2026-10-23): large-offset gather/scatter and pad fixes, a ring send/recv data race fix, mx.take negative indices, normal loc/scale shapes, padding jvp/vjp, and wider affine qmv_wide row tiles. No mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
7 of 8 tasks
Gajesh2007
pushed a commit
to Layr-Labs/mlx-c
that referenced
this pull request
Oct 23, 2026
* Support MLX v0.32.2 (ml-explore#123) * bump to MLX v0.32.0 * fix generator (parser not supporting thread_local) * overloads for mlx_astype * regenerate * fix const char* return assignement * regenerate * bump to MLX v0.32.1 * fix overloads to maintain backward-compatibility * add support for mlx_compile_cache (std::weak_ptr<mlx::core::detail::CompileCache> aka CompileCacheWeakPtr) * regenerate C files * bump to MLX v0.32.2 * regenerate * add missing mlx_compile_cache new/free functions * fix mlx_string return type (again) * add support for some missing overloads * fix flip overloads * Bind new_thread_unsafe_stream (ml-explore#122) Streams are thread affine as of mlx 0.31.2: a stream's GPU command encoder is registered in a thread_local map, so evaluating on a stream from a thread other than the one that created it raises "There is no Stream(gpu, N) in current thread." mlx added `new_thread_unsafe_stream` for language bindings that do their own locking (ml-explore/mlx#3578), but mlx-c does not expose it, so those bindings have no way to reach it. mlx-swift currently carries a local C shim for this. Requires mlx 0.32.0, so the pin moves from v0.31.2. The existing bindings build and the examples pass unchanged against it. * example-ops (ml-explore#121) * Add examples for ops added in 0.32.0 * fix examples --------- Co-authored-by: Ronan Collobert <collobert@apple.com> * fix custom io reader/writer API (ml-explore#130) * Implement missing stream functions (ml-explore#132) * prefer forward-declaration to avoid circular references * support for missing stream functions * cosmetics * version 0.7.0 (ml-explore#135) * bump to v0.32.4 (ml-explore#140) * Pin the MLX core to the Layr-Labs/mlx upstream sync merge Move the FetchContent GIT_TAG from ad5966879ec5 to 3c3750f4ed40. That commit merges upstream ml-explore/mlx main c6abf0dbb into Layr-Labs/mlx main. Its parent on Layr-Labs/mlx main has the same tree as ad5966879ec5, so no fork core change is lost. The new revision adds the core functions that the upstream bindings call, for example new_thread_local_stream and get_array_buffer_size. GIT_REPOSITORY stays on Layr-Labs/mlx. fork.yaml describes the new pin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the MLX core to the repaired Layr-Labs/mlx sync head Move the FetchContent GIT_TAG from 3c3750f4ed40 to e5b6550d5898. e5b6550d5898 is 3c3750f4ed40 plus two commits on the Layr-Labs/mlx sync branch. They repair the merge in the quantized Metal kernels (qmm_t_impl template parameters and the fp_gather_qmv_fast_tail adjust_matrix_offsets call). At 3c3750f4ed40 quantized.metal and fp_quantized.metal did not compile. The pin stays provisional until the Layr-Labs/mlx sync PR is green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the MLX core to the Layr-Labs/mlx sync PR head Move the FetchContent GIT_TAG from e5b6550d5898 to 780b57ea90cc, the head of Layr-Labs/mlx#38. 780b57ea90cc adds one commit over e5b6550d5898. It changes only tests/allocator_tests.cpp and tests/gptoss_mxfp4_tests.cpp, so the core library sources are the same. The pin stays provisional until Layr-Labs/mlx#38 CI is green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the MLX core to Layr-Labs/mlx#38 head dbf463fc93ca Move the FetchContent GIT_TAG from 780b57ea90cc to dbf463fc93ca, the new head of Layr-Labs/mlx#38. dbf463fc93ca adds one commit over 780b57ea90cc. It changes the CPU SIMD headers base_simd.h and accelerate_simd.h (guard INT_MIN / -1 in floor_divide) and fork.yaml. The pin stays provisional until Layr-Labs/mlx#38 CI is green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Pin the MLX core to Layr-Labs/mlx#38 head 089acfe1 Layr-Labs/mlx#38 moved from dbf463fc to 089acfe1. The new commit computes the JACCL ring wire offsets in 64 bits (CodeQL review fix). It changes only mlx/distributed/jaccl/lib/jaccl/ring.cpp, ring_impl.h and fork.yaml, so no mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Pin the MLX core to Layr-Labs/mlx#38 head 4588237c Layr-Labs/mlx#38 moved from 089acfe1 to 4588237c. The new commit computes the vector SDPA query layout check in 64 bits (CodeQL alert 29). It changes only mlx/backend/metal/scaled_dot_product_attention.cpp and fork.yaml, so no mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Pin the MLX core to Layr-Labs/mlx#38 head 1f7e9063 Layr-Labs/mlx#38 moved from 4588237c through aaec1b63 (tests and CI only) to 1f7e9063. The code change is in mlx/ops.cpp: qqmm raises "Global scale is only supported on the GPU" on a CPU stream, because the CPU kernel has no global-scale input and returned wrong results. No mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Pin the MLX core to Layr-Labs/mlx#38 head cb73247a Layr-Labs/mlx#38 moved from 1f7e9063 to cb73247a. The code change: every CPU BLAS gemm call with beta 0 clears its output region first, because Accelerate can read C even when beta is 0 and a reused buffer can hold NaN (latent upstream bug). No mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Report buffer uniqueness from reference counts, not donatability mlx_array_get_buffer_info set `unique` from is_donatable(). Since upstream mlx 5dbe9991 (#4634), is_donatable() also requires that MLX owns the buffer, so a wrapped external buffer always read as not unique. The field means "no other array references this array or its data", and mlx-swift-lm relies on that for paged KV segments, checkpoint memory accounting and MTP state. Use array::is_uniquely_referenced() from Layr-Labs/mlx 699649d3 and pin the core there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Pin the MLX core to Layr-Labs/mlx#38 head 36f043ec Layr-Labs/mlx#38 moved from 699649d3 to 36f043ec: async_eval no longer throws an error that an earlier CPU stream task stored. It fixes an upstream ordering race between #3742 and #4338, and synchronize still raises the error. No mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Pin the MLX core to Layr-Labs/mlx#38 head 83dbdb04 Layr-Labs/mlx#38 merged 9 more upstream ml-explore/mlx commits, through afc03c511 (2026-10-23): large-offset gather/scatter and pad fixes, a ring send/recv data race fix, mx.take negative indices, normal loc/scale shapes, padding jvp/vjp, and wider affine qmv_wide row tiles. No mlx-c source changes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Ronan Collobert <collobert@apple.com> Co-authored-by: Charlie Le <charlie_le@apple.com> Co-authored-by: Eric Saxby <77073+sax@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
davidtai
added a commit
that referenced
this pull request
Oct 23, 2026
Main's tree equals 83dbdb0, the base of this branch, so the merge keeps this branch's tree. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
davidtai
added a commit
that referenced
this pull request
Oct 24, 2026
Restore the upstream sync history squashed by #38
jonathan308
added a commit
that referenced
this pull request
Oct 26, 2026
Brings in main at f78f0fa (2026-10-24): the upstream ml-explore/mlx sync to afc03c5 (#38, with its history restored by #42) and the fork fixes #40 and #43-#46. Conflicts and how they were resolved: - mlx/distributed/jaccl/lib/jaccl/mesh.cpp: main's per-wire queue setup (upstream ml-explore#4607, a second cable for the mesh) with this branch's COMPLETION_QUEUE_DEPTH for every completion queue. - mlx/distributed/jaccl/lib/jaccl/mesh_impl.h: main's stride parameters for all_gather and sum_scatter, with this branch's progress-guard calls and stage_send in every send path. - mlx/distributed/jaccl/lib/jaccl/ring_impl.h: this branch keeps its guarded member dispatch_wires (pool-wire errors are reported, the group closes after all wires return). Main moved an unguarded copy into threadpool.h and called it as dispatch_wires(pool_, ...); the ring call sites call the member again. The all_gather slice code is main's (identical to this branch's backport of upstream ml-explore#4443). - quantized_nax.h, fp_quantized_nax.h: main's kernels. Main already keeps the revert of the upstream ml-explore#4352 simdgroup skip (no sg_active) and adds the ragged-K tail bound of upstream ml-explore#4009. - fork.yaml: main's file, plus the JACCL description and section. The upstream backports this branch carried (ml-explore#4443, ml-explore#4557, ml-explore#4558, the scoped_env helper of ml-explore#4474) are now upstream ancestry, so their "not fork work" section and its references are gone. The JACCL section now says that a mesh with more than one wire has one guard per wire. Also: - python/tests/ring_test_distributed.py: main's file. The auto-merge had added test_strict_init_after_fallback twice. - tests/jaccl: the mesh all_gather and sum_scatter calls pass main's new stride argument (equal to the chunk size, as main's own all_reduce_scatter_gather does). Tests: tests/jaccl (simulated verbs, ASan and UBSan): 10 of 10 passed. check_forkdiff.py --upstream-ref upstream/main: 111 changed files, all described, base afc03c5 is the merge-base. clang-format 21: no changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jonathan308
added a commit
that referenced
this pull request
Oct 26, 2026
get_steel_attention_nax_kernel passed bv to both attention_nax and attention_nax_dsplit. attention_nax gained a BV parameter in the upstream merge (#38); attention_nax_dsplit has none (its value head dim is BD). With bv in the list, every later argument shifts by one, an integer lands in the MaskType slot, and the library fails to compile: explicit instantiation of 'attention_nax_dsplit' does not refer to a function template ... JIT builds (mlx-swift) compile this kernel at runtime for full attention with head dim 256 or 512 and at least 1,024 query tokens on NAX GPUs, so the compile error surfaced as "Unable to build metal library from source" and the caller's process exited. Builds that use the precompiled metallib were unaffected, because steel_attention_nax.metal instantiates the split kernel without bv. Pass the split kernel the same arguments as its metallib instantiation. The non-split kernel is unchanged.
Gajesh2007
pushed a commit
that referenced
this pull request
Oct 26, 2026
…48) get_steel_attention_nax_kernel passed bv to both attention_nax and attention_nax_dsplit. attention_nax gained a BV parameter in the upstream merge (#38); attention_nax_dsplit has none (its value head dim is BD). With bv in the list, every later argument shifts by one, an integer lands in the MaskType slot, and the library fails to compile: explicit instantiation of 'attention_nax_dsplit' does not refer to a function template ... JIT builds (mlx-swift) compile this kernel at runtime for full attention with head dim 256 or 512 and at least 1,024 query tokens on NAX GPUs, so the compile error surfaced as "Unable to build metal library from source" and the caller's process exited. Builds that use the precompiled metallib were unaffected, because steel_attention_nax.metal instantiates the split kernel without bv. Pass the split kernel the same arguments as its metallib instantiation. The non-split kernel is unchanged.
Gajesh2007
pushed a commit
to Layr-Labs/d-inference
that referenced
this pull request
Oct 26, 2026
#1618) * Provider 0.10.3: fix the M5 crash in head-dim split attention Provider 0.10.2 exits with "Trace/BPT trap: 5" on Macs with an M5 or newer GPU when one attention call covers at least 1,024 query tokens with head dim 256 or 512: Gemma 4 26B, Qwen3.5-9B, Qwen3.5-35B, Qwen3.6-35B and Qwen3.8-27B. MLX compiles attention_nax_dsplit at runtime on these GPUs, and the compile fails with "Unable to build metal library from source". Root cause: the upstream MLX merge in 0.10.2 (Layr-Labs/mlx#38) made jit_kernels.cpp pass a bv template argument to the head-dim split kernel, which takes none. Every later argument shifts by one, so the instantiation never compiles and MLX's error stops the provider. Fix: pass the split kernel the arguments of its metallib instantiation. Pins: - libs/mlx-swift eedca444 -> 4926d3d9 (Layr-Labs/mlx-swift branch hotfix/0.10.2-jit-nax-dsplit): eedca444 plus the nested mlx pin. - libs/mlx and the nested mlx ff4bb35b -> 31f550af (Layr-Labs/mlx branch hotfix/0.10.2-jit-nax-dsplit): ff4bb35b plus the jit_kernels.cpp fix. - libs/mlx-swift-lm stays at aee6ebb6; nothing else changes from 0.10.2. Validation on an M5 Ultra: before the fix, attention crashed with the production error; after it, 6/6 attention shapes match a float32 reference. Release preparation: ProviderCore.version is 0.10.3; the m5-head-dim-split-attention-crash fragment is rendered into CHANGELOG.md as the v0.10.3 candidate and removed; v0.10.2 is recorded as published on 2026-10-26; docs/operations/provider-release.md describes 0.10.3, its pre-tag pin checks, the deployed darkbloom-platform#464 floors and the v0.10.2 publication. * Document runtime Metal compile failures; keep the mlx hotfix branch docs/architecture/inference.md gains a failure-mode row: a provider that exits with Trace/BPT trap: 5 after "Unable to build metal library from source" hit a failed runtime Metal compile. mlx-swift builds MLX's JIT path and the release metallib holds no NAX attention kernels, so fused attention compiles on the Mac; MLX throws on a failed compile and the process stops. MLX pin changes that touch jit_kernels.cpp or the NAX kernels need an M5 run with a long Gemma 4 or Qwen prompt. The release runbook now names a signed 0.10.3 build for the Super restart-warmth step, and says to keep both hotfix branches: mlx#48 reached mlx main as a squash commit, so the mlx branch is the only ref that holds 31f550af. * Changelog: the 0.10.2 crash loop can trip the KV-backend guard; 0.10.3 clears it A provider that crash-looped on 0.10.2 may show 'KV-backend guard: ACTIVE' and run the contiguous backend, refusing packed balanced, k8v4 and k8v8 KV. The guard binds only the version that tripped it (KVBackendGuardStore.clearIfStale), so 0.10.3 clears it at startup. Clearing it on 0.10.2 does not help: the crash is in MLX's attention kernel compile, not the KV backend. --------- Co-authored-by: Jonathan Spangler <5055076+jonathan308@users.noreply.github.com>
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.
Proposed changes
This PR merges upstream
ml-explore/mlxmainatc6abf0dbb(2026-10-22) into the fork. It uses a merge commit, not a rebase. David ordered the merge on 2026-10-22.What upstream brings
77a0c1e8(2026-08-14) toc6abf0dbb.v0.32.2,v0.32.3andv0.32.4. The new base is 15 commits pastv0.32.4.mlx/version.his upstream's.qmv_mma/qmv_naxkernels, stagedaffine_qmm_t_naxwith split-K, the fp4-to-half loader infp_qmm_t, global scale forqmmandgather_qmm, NAX for non-transposedgather_qmm_rhs, 1-bit affine quantization, gated delta net kernels and VJP, SDPA VJP, SDPA D96/V64, D256 and D512 memory work, the M1/M2tk1SDPA kernels, fusedfast.cross_entropy, the fence deadlock fixes ([BUG][Metal] Deadlock in fence ml-explore/mlx#4552, [BUG] Metal fence deadlock ml-explore/mlx#4689), conv bounds fixes, a residency refresh interval.FloorDivideprimitive (Fix floor_divide for integers ml-explore/mlx#4515, Add FloorDivide primitive for integer floor division ml-explore/mlx#4642),stft/istft, compile fixes (Fix input/output scalar merging in compile ml-explore/mlx#4658, Fix cross-process race in CPU compile cache ml-explore/mlx#4638), complex VJP fixes, reduction grid overflow fix.gather_mm/gather_qmm([Metal] Gather mm improvement ml-explore/mlx#4567, [Metal] gather_qmm improvement ml-explore/mlx#4572), which the fork carried as a backport in PR [Metal] gather_mm and gather_qmm row-tile scheduling (backport of ml-explore/mlx #4567, #4572) #27.Fork changes that stay
Every
fork.yamlsection that describes fork work stays:fp_gather_qmm_rhs_naxandaffine_gather_qmm_rhs_nax(PR Single-Mac GLM-5.3-Flash native fixes without JACCL #37)Where the fork now takes upstream
matmul.cpp,matmul.h,steel_gemm_gather*.h,gather_mm_offsets.metal, the benchmarks and.gitignoreare upstream's files.DividefunctorsFloorDivideprimitiveDividetruncates, as in upstream. The PR #32 tests stay. The fork adds one guard to upstream's CPUfloor_divideformin // -1.fork.yaml.Conflicts and how I resolved them
git mergestopped on 69 files. 39 of them were files that only PR #10 changed, and PR #10 equalsv0.32.2there. For those 39 files I took upstream's file. They are listed at the end of this section.PR #10 applied upstream content as a fork commit, so
gitused the old base77a0c1e8and saw that content as fork work. For the files that the fork changed afterv0.32.2, I merged a second time withv0.32.2as the base. That merge matches the backport lines in PR #27 to upstream's lines. I compared the two merges and checked each difference. A function-level audit then compared each definition in the result with the fork, upstream andv0.32.2..github/actions/{build,build-wheel,test-linux,test-wheel,test-windows}/action.yml,.github/workflows/release.yml.github/workflows/update_bypass_list.yml,.github/scripts/{find-eligible-contributors,update-bypass-list}.js.github/workflows/build_and_test.yml.gitignore,benchmarks/python/swiglu_bench.py,benchmarks/python/swiglu_qmm_bench.pymlx/backend/cpu/binary_ops.handmlx/backend/metal/kernels/binary_ops.hFloorDivide. Removed the PR #32Dividefunctors.mlx/backend/cpu/simd/base_simd.h,mlx/backend/cpu/simd/accelerate_simd.hfloor_dividedividesminby -1. That overflows and traps on x86: the PR #32 test crashed with SIGFPE in the Fedora x86_64 job. Added the PR #32 guard: divide by 1 there, so the result wraps tomin.tests/CMakeLists.txtMETAL_TEST_SOURCEStoGPU_TEST_SOURCES. Keptgptoss_mxfp4_tests.cppunder the new name.python/tests/test_conv.pytest_conv2d_winograd_batch_tiling(PR #30). Added the same skip to upstream's newtest_conv2d_winograd_disable, because the fork's CPU run cannot tell the two paths apart.python/tests/test_fft.py,tests/ops_tests.cpppython/tests/test_ops.py,python/tests/test_quantized.py,python/tests/test_fast_sdpa.pytest_fast_sdpa.pyimports numpy again, because upstream removed the import and the fork tests use it.mlx/backend/metal/device.h,mlx/fast.hmlx/memory.hmlx/compile.cpp,mlx/compile_impl.hcompile_erase(fun_id)/compile_clear_cache(). The rest is upstream.mlx/backend/metal/kernels/sdpa_vector.hgitmerged it without a conflict, but the result had two copies ofsdpa_vector_2pass_1_gqa. I rebuilt it from upstream and applied the FP32 partials again (PR #16).mlx/backend/metal/kernels/scaled_dot_product_attention.metal,mlx/backend/metal/scaled_dot_product_attention.cpptk1work. Renamed every two-pass kernel name, including the newgqa_<g>and_tk1names, tosdpa_vector_2pass_fp32partials_*. Kept the float32 intermediate.mlx/backend/metal/kernels/quantized.hload_safe. The merge put upstream's newtypename U = Ttemplate parameter on the fork'sqmm_t_expert_impl. I moved it toqmm_t_impl.mlx/backend/metal/kernels/quantized_nax.hget_pack_factorin upstream's new staged and split-K code. Kept the revert of the ml-explore#4352 simdgroup skip inaffine_gather_qmm_rhs_nax.mlx/backend/metal/kernels/fp_quantized.hFp4HalfLoader. Kept the fork names and column bounds.fp_gather_qmv_fast_tailnow follows upstream'sfp_gather_qmv_fast: the newpartial_rowstemplate parameter and the global scale inadjust_matrix_offsets.mlx/backend/metal/kernels/fp_quantized_nax.hfp_qmv_nax. Kept the fork names, the out-of-bounds scale guard and the revert of the ml-explore#4352 simdgroup skip infp_gather_qmm_rhs_nax.mlx/backend/metal/quantized.cppbool gather_qmm_rhswith the retract, the Gemma 4 expert-tile route, thetakes_sorted_rhs_routegate, the GPT-OSS prefill tile and the decode fast tail.mlx/backend/metal/primitives.cpp,mlx/fence.h,CLAUDE.mdgitmerged them without a conflict, but PR #10 content that upstream later removed came back: duplicateCrossEntropyMetal stubs (ml-explore#4520), theFence::castconst overload (ml-explore#4552) and theCLAUDE.mdsymlink. Took upstream.scripts/check_forkdiff.pymlx/backend/cpu/gemm.h,gemms/cblas.cpp,masked_mm.cpp,conv.cpptest_fp_gather_qmm_non_multiple_of_32(K=512, N=1040, sorted, float32). The NaN was only in the referencex @ w_sel(CPU matmul, M=1). Its 266240-byte output reused the buffer of the previous subtest's packed fp4 weights, and Accelerate read C with beta = 0 (NaN * 0). One helper,clear_gemm_outputingemm.h, now fills the exact C region with zeros before every BLAS gemm with beta = 0: the float32/float64/complex64matmulwrappers (Matmul,AddMM,SegmentedMM),BlockMaskedMMandGatherMMinmasked_mm.cpp, and the two explicit-gemm convolutions inconv.cpp. No other BLAS gemm or gemv call exists in the CPU backend. MLX's own SIMD gemm already skips C when beta is 0.mlx/scheduler.{h,cpp},mlx/transforms.cpp,tests/scheduler_tests.cppStreamThread::enqueuethrows the stream's stored task error on the main thread (ml-explore#3742).async_evalenqueues several tasks, so when the stream thread finishes a failing task first,async_evalthrows. ml-explore#4338 expects the error only fromsynchronize.test_async_eval_error_in_synchronizefailed on 2 of 6 heads in the fork CPU pass.async_evalnow holdsscheduler::DeferStreamErrors, sosynchronizeraises the error. A deterministic C++ test covers it.mlx/ops.cpp(qqmm)QQMatmulhas no global-scale input and ignored the scales: wrong results with no error (upstream TODO attest_quantized.py:335). The fork CPU test pass found it.qqmmnow raises "Global scale is only supported on the GPU" on a non-GPU stream, asgather_qmmdoes.python/tests/test_fast_sdpa.py,python/tests/test_quantized.py(GPU-path tests)test_sdpa_vector_2pass_threadgroup_limitwithforce_fused=True, the global-scaleqqmmandgather_qmmtests) now check, when the default device is the CPU, that the documented error is raised. The GPU pass runs them as before.python/tests/test_reduce.py(test_init_reduce_large_output)max_buffer_lengthis less than 4 GiB, on the CPU whenmemory_sizeis less than 16 GiB. The hostedmacos-26runner caps Metal buffers at 3.5 GiB. The test cannot be smaller: it is the regression test for ml-explore#4437 and needs 2^32 output elements (more than one 32-bit grid dimension), and bool outputs of one byte each give 4 GiB..github/workflows/fork-tests.ymltests/allocator_tests.cpp,tests/gptoss_mxfp4_tests.cppallocator::mallocnow returns an owningallocator::Data, andallocator::freeis gone. The fork tests useallocator::allocator().malloc/free, as upstream's tests do. Upstreamgather_qmmhas a newglobal_scaleargument; the GPT-OSS tests passstd::nullopt.Files that only PR #10 changed, now upstream's files (39):
.pre-commit-config.yaml,CMakeLists.txt,CONTRIBUTING.md,setup.py,mlx/version.h,mlx/ops.cpp,mlx/primitives.cpp,mlx/fft.cpp,mlx/backend/metal/{conv.cpp,kernels.h,nojit_kernels.cpp},mlx/backend/metal/kernels/fp_quantized_nax.metal,mlx/backend/metal/kernels/steel/attn/kernels/{steel_attention.metal,steel_attention_nax.h,steel_attention_nax.metal},mlx/backend/cuda/{CMakeLists.txt,event.cu,fence.cpp},mlx/backend/no_gpu/{fence.cpp,primitives.cpp},python/src/{CMakeLists.txt,fast.cpp,indexing.cpp,ops.cpp,stream.cpp},python/mlx/nn/losses.py,python/mlx/optimizers/optimizers.py,python/tests/{run.py,test_array.py,test_compile.py,test_eval.py,test_fast.py,test_load.py,test_reduce.py,test_vmap.py},tests/load_tests.cpp,benchmarks/python/sdpa_bench.py,examples/cmake_extension/{pyproject.toml,requirements.txt}.fork.yamlandFORKDIFF.mdbase.hashis nowc6abf0dbb4fc141fad739b32ae486c06728e49e5, the merge-base with upstreammain.mlx/ops.cpp) and "CPU BLAS gemm clears C when beta is 0" (mlx/backend/cpu/{gemm.h,gemms/cblas.cpp,masked_mm.cpp,conv.cpp}). The coordinator added "64-bit products in the JACCL ring and the vector SDPA layout check" for two CodeQL findings.FORKDIFF.mdhas a new sync policy. The fork merges upstream when David directs it. Each merge movesbase.hashto the new merge-base and checks every section again. Between merges, an upstream fix that the fork needs is ported as its own commit.python3 scripts/check_forkdiff.py --upstream-ref refs/remotes/upstream/mainpasses: 85 changed files, 85 described.Downstream pins
Layr-Labs/mlx-candLayr-Labs/mlx-swiftpoint at the head SHA of this PR. Merge this PR with a merge commit, not a squash. A squash makes a new SHA, and the dependents must then pin again.Test plan
I did not build or run tests on the laptop. The checks are:
Fork Tests / metal-build-and-test: Metal build, C++ and Python tests, the second run on the CPU, the coverage report.Build and Test / Check Lint: pre-commit (clang-format, cmake-format, ruff).Build and Test / Linux Sanitizer Tests (ASAN)and(UBSAN).Build and Test / Linux Fedora (x86_64)and(aarch64).Fork Diff Check.-DMLX_BUILD_TESTS=ON -DMLX_METAL_JIT=OFF) found compile errors in the merged kernels and in two fork test files. The fixes are in this PR. The box build ofdbf463fc(library,mlx.metallib, C++ tests) has 0 errors. On the box I ran only the CPU test "test floor_divide integer edge cases on the CPU": it passes. The box GPU needs a lock protocol, so CI runs the GPU tests.Observed CI flake
On head
1f7e9063the ring distributed test in the GPU pass failed: the first failure is "[METAL] Command buffer execution failed: Caused GPU Timeout Error" intest_all_reduce_extra, and four more failures followed it. The same ring tests passed on4588237candaaec1b63. The only code change betweenaaec1b63and1f7e9063is theqqmmguard, which the ring test does not use. I read it as a GPU timeout on the hosted runner. I did not change the test. Upstream'stest-macosaction does not fail the step on these ring failures; it checks only for[WARN].Checklist
pre-commit run --all-filesto format my code / installed pre-commit prior to committing changes (I ran the same hook versions on the changed files: clang-format 21.1.8, ruff 0.16.10, cmake-format 0.6.13.)fork.yamlandFORKDIFF.mdare updated. No other docs change.)🤖 Generated with Claude Code
Reference uniqueness without buffer ownership (commit
699649d38164)Upstream
5dbe9991(ml-explore#4634) makesis_donatable()require that MLX owns the buffer. That is right for donation. But the fork's mlx-c buffer-info API usedis_donatable()as a "no other reference" proof, which mlx-swift-lm relies on for paged KV segments, checkpoint memory accounting and MTP state, including wrapped host pages. The fork addsarray::is_uniquely_referenced(), the same reference-count test without the ownership test.is_donatable()and donation do not change. Pushed by the orchestrator; fork.yaml section "Reference uniqueness without buffer ownership".