Repository navigation
Merge upstream ml-explore/mlx main afc03c511 (2026-10-23) - #38
Merged
Merged
Conversation
…4266) Co-authored-by: Cheng <[email protected]>
…lore#4284) Co-authored-by: Cheng <[email protected]>
Co-authored-by: Daniel Hiltgen <[email protected]> Co-authored-by: Cheng <[email protected]>
…e#4356) Co-authored-by: Cheng <[email protected]>
Co-authored-by: Cheng <[email protected]>
Co-authored-by: Cheng <[email protected]>
Co-authored-by: katlun-lgtm <[email protected]> Co-authored-by: Cheng <[email protected]>
Co-authored-by: Cheng <[email protected]>
Signed-off-by: Pradyot Ranjan <[email protected]> Co-authored-by: Pradyot Ranjan <[email protected]> Co-authored-by: Cheng <[email protected]>
Co-authored-by: Cheng <[email protected]>
Co-authored-by: Cheng <[email protected]>
test_fp_gather_qmm_non_multiple_of_32 (K=512, N=1040, sorted, float32) gave NaN on the CPU. The diagnostic job found the NaN only in the reference y_hat = x @ w_sel, a batched matmul with M = 1. Its inputs had no NaN. The test failed alone too, but only after the earlier subtests of the same test: y_hat is 266240 bytes, the same size as the packed fp4 weights of the previous subtest, so the output reused that buffer from the allocator cache. Accelerate read C even with beta = 0 and computed NaN * 0. Fill C with zeros before cblas_sgemm, cblas_dgemm and cblas_cgemm when beta is 0. MLX's own SIMD gemm already skips C when beta is 0. Remove the temporary diagnostic job. Co-Authored-By: Claude Opus 5.5 <[email protected]>
The previous commit cleared C only in the gemms/cblas.cpp wrappers. Four more call sites call cblas_sgemm directly with beta 0: BlockMaskedMM and GatherMM (the CPU MoE gathered matmul) in masked_mm.cpp, and the two explicit-gemm convolutions in conv.cpp. They can give the same NaN * 0 from a reused output buffer. Add one helper, clear_gemm_output in gemm.h, that fills the exact C region of a call (rows by columns, with its ldc) with zeros. Use it at all five call sites, the wrappers included. No other BLAS gemm or gemv call exists in the CPU backend. Co-Authored-By: Claude Opus 5.5 <[email protected]>
davidtai
added a commit
to Layr-Labs/mlx-c
that referenced
this pull request
Oct 23, 2026
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) <[email protected]>
davidtai
added a commit
to Layr-Labs/mlx-swift
that referenced
this pull request
Oct 23, 2026
Layr-Labs/mlx#38 moved to cb73247a: 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; affects CPU matmul, gather_mm, block-masked matmul and convolution). Layr-Labs/mlx-c#17 moved to 44763458, which only moves its CMake GIT_TAG to the same mlx commit. The changed files are C++ sources that Cmlx compiles directly, so no generated sources change. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Split the fork-tests job into parallel jobs. build-wheel and build-cpp build in parallel. build-cpp also runs the C++ tests on the GPU and on the CPU. python-gpu (3 shards) and python-cpu (4 shards) run the Python tests from the uploaded wheel. metal-build-and-test merges the coverage profiles of all jobs, writes the same coverage report, and fails when another job failed. python_shards.py checks the shard map: each python/tests/test*.py file must be in each pass exactly one time. Split files must partition their test IDs. The last job fails when a test ran in two shards. fork-build-cache restores and saves ccache on pull requests and on main, and keeps a cache of the compiled Metal kernels through an xcrun wrapper keyed on the preprocessed source, the arguments and the toolchain. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…re#4738) Co-authored-by: Cheng <[email protected]>
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) <[email protected]>
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) <[email protected]>
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) <[email protected]>
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 <[email protected]>
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) <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
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) <[email protected]>
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) <[email protected]>
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 <[email protected]>
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 <[email protected]>
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) <[email protected]>
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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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 <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> * 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) <[email protected]> --------- Co-authored-by: Ronan Collobert <[email protected]> Co-authored-by: Charlie Le <[email protected]> Co-authored-by: Eric Saxby <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
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) <[email protected]>
davidtai
added a commit
that referenced
this pull request
Oct 24, 2026
Restore the upstream sync history squashed by #38
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".