Skip to content

Upgrade MLX core 0.31.1 → 0.32.0 (M5 Neural-Accelerator nax fixes) + keep resource-count trim - #3

Merged
Gajesh2007 merged 196 commits into
darkbloom-basefrom
darkbloom/mlx-0.32.0-nax
Jun 25, 2026
Merged

Gajesh2007 merged 196 commits into
darkbloom-basefrom
darkbloom/mlx-0.32.0-nax

Conversation

@Gajesh2007

Copy link
Copy Markdown
Member

Why

Our M5 Max providers can only get the GPU Neural-Accelerator speedup (Apple's _nax / Metal Performance Primitives path; ~2.5–4× compute-bound prefill/TTFT) if the MLX C++ core actually contains the nax kernels and their fixes. Our submodule was pinned to MLX 0.31.1 (d5213411), which predates the nax MoE work — so on M5 the MoE expert matmul silently fell off the nax path and we captured ~0× of the win.

This branch is upstream MLX 0.32.0 (b410f6c) + our resource-count trim re-applied (aa480bd8), i.e. it brings in all the nax fixes while preserving the Darkbloom allocator patch our provider depends on.

What this includes (was missing on 0.31.1)

What's preserved

The "Bound Metal buffer COUNT, not just bytes, in MetalAllocator" patch (orig d5213411, re-applied as aa480bd8). Our provider relies on the public getters it adds — MLX.Memory.numResources / resourceLimit are read by EngineCore.swift and CheckpointCapturePipeline.swift to drive count-aware capacity + checkpoint-pressure decisions. Upstream 0.32.0 independently added the num_resources_/resource_limit_ members and a clear_cache()-path reclaim, so the merge keeps upstream's base count logic and adds our public getters + high-water clear() in malloc + MLX_RESOURCE_LIMIT override with no duplicated members.

git diff origin/main..HEAD is exactly the trim (5 files, +105/−1) — nothing else diverges from upstream 0.32.0.

Conflict resolutions

  • mlx/backend/metal/allocator.cpp — includes (kept <algorithm>/<cassert>/<cerrno>); malloc reclaim region (kept the autorelease pool + count-aware comment for the high-water clear() we add).
  • mlx/backend/no_gpu/allocator.cpp — kept upstream's real set_cache_limit (delegates to common_allocator) and added the trim's get_num_resources/get_resource_limit stubs.

⚠️ Build note (required to actually get nax)

ml-explore#3622 makes the nax kernels compile only when MLX_METAL_VERSION ≥ 400 AND MACOS_SDK_VERSION ≥ 26.2 AND CMAKE_OSX_DEPLOYMENT_TARGET ≥ 26.2. So a nax-enabled mlx.metallib has a minimum OS of macOS 26.2 and will not launch on older macOS. The M5 provider artifact must be built with the macOS 26.2 SDK + deployment target 26.2 (and avoid the Xcode 26.5 NAX-shader miscompile — use 26.4 / a validated toolchain). M4 / pre-26.2 providers keep building the standard (non-nax) artifact, unchanged.

Verification

  • git grep for conflict markers: none.
  • mlx/version.h = 0.32.0; gather_qmm_nax bk = 64; steel_gemm_segmented_nax.metal present; getters present in memory.h + allocator.h.
  • ☐ CI build on macOS 26.2 SDK (nax variant) — required before merge.
  • ☐ Runtime verify on an M5 Max: is_nax_available()==true, nax kernels in metallib, gather_qmm_nax dispatches (tracked in DAR-351).

Before / After

flowchart LR
  subgraph Before["Before — submodule @ 0.31.1 + trim"]
    A1[M5 request: prefill] --> B1[gather_qmm bk=32]
    B1 --> C1[NAX kernel-name lookup FAILS]
    C1 --> D1[fallback simdgroup_matrix path]
    D1 --> E1[~0x of nax speedup]
  end
  subgraph After["After — 0.32.0 + trim, built w/ 26.2 SDK"]
    A2[M5 request: prefill] --> B2[gather_qmm bk=64 + segmented nax]
    B2 --> C2[NAX matmul2d / cooperative tensors]
    C2 --> D2[Neural Accelerator]
    D2 --> E2[~2.5-4x prefill/TTFT]
  end
Loading

Pairs with the mlx-swift submodule-bump PR (points Source/Cmlx/mlx at this branch's tip) and DAR-351 (nax telemetry).

angeloskath and others added 30 commits March 11, 2026 23:57
Co-authored-by: mm65x <mm65x@users.noreply.github.com>
…lore#3088)

Signed-off-by: Ihar Hrachyshka <ihar.hrachyshka@gmail.com>
Co-authored-by: Angelos Katharopoulos <a_katharopoulos@apple.com>
Co-authored-by: Robert Johansson <robert@Mac-Mini-KI.lan>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Angelos Katharopoulos <katharas@gmail.com>
Co-authored-by: Angelos Katharopoulos <a_katharopoulos@apple.com>
qflen and others added 25 commits June 17, 2026 15:05
Signed-off-by: Bvicii <yizhanhuang2002@gmail.com>
Co-authored-by: katlun-lgtm <264247399+katlun-lgtm@users.noreply.github.com>
Co-authored-by: Cheng <git@zcbenz.com>
…ore#3633)

Co-authored-by: Angelos Katharopoulos <a_katharopoulos@apple.com>
…pose (ml-explore#3729)

Co-authored-by: katlun-lgtm <264247399+katlun-lgtm@users.noreply.github.com>
Co-authored-by: Cheng <git@zcbenz.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Angelos Katharopoulos <a_katharopoulos@apple.com>
Co-authored-by: Cheng <git@zcbenz.com>
The Metal allocator throws `[metal::malloc] Resource limit (N) exceeded`
when num_resources_ (the live+cached Metal buffer COUNT) reaches
resource_limit_ (the iogpu.rsrc_limit sysctl, default ~499000). Freed
buffers are recycled into a size-keyed cache whose only trim is by BYTES
(release_cached_buffers takes a bytes-to-free target, max_pool_size_ ~=
physical RAM). Under churn with many distinct buffer shapes (varied prompt
lengths, growing KV caches, multiple co-resident models) the cache fills
with entries never reused at that exact size, so the COUNT climbs to the
limit while byte usage stays modest and the byte trim never fires — the
process crashes mid-inference on a machine with most of its RAM free.

malloc() now also reclaims by count: when num_resources_ crosses a 90%
high-water mark of resource_limit_, it clears the (pure-reuse) buffer
cache so the count drops back to the live working set. Clearing the cache
only costs re-allocation, never correctness, so the count limit becomes
unreachable by any request mix or batching method while the existing byte
limits keep total memory bounded.

Adds get_num_resources()/get_resource_limit() to the public memory API
(metal + no_gpu + cuda backends) so the count and its ceiling are
observable from callers. Adds an MLX_RESOURCE_LIMIT env override that can
only LOWER the ceiling (clamped to the OS limit, strictly validated) to
exercise the trim deterministically and as an operator safety valve.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.