Skip to content

Single-Mac GLM-5.3-Flash native fixes without JACCL - #37

Merged
Gajesh2007 merged 13 commits into
mainfrom
work/glm53-flash-release
Oct 21, 2026
Merged

Gajesh2007 merged 13 commits into
mainfrom
work/glm53-flash-release

Conversation

@jonathan308

@jonathan308 jonathan308 commented Oct 21, 2026 •

Copy link
Copy Markdown

Update 2026-10-21: merged main

The head is now ad596687: main merged in (mlx#36, thread-scoped command-buffer commit limits, the Nemotron 3.5 Super native prerequisite). No conflicts. scripts/check_forkdiff.py passes (190 changed files, all described). The Metal library built from this tree is unchanged (156f2624…); #36 changes host code only.

Update: production footprint charges restored

The head is now 6ee3062c. 4a355746 reverts 99dc5f42 (the one-page minimum in get_allocation_footprint_policy()), so the Metal footprint policy is again {page, page, 0, 0, page}, the same as the core the current provider release ships. 6ee3062c keeps docs/src/usage/unified_memory.rst in fork.yaml, because the capped no-copy paragraph still differs from upstream.

Why: with the page minimum, the SDK's allocation estimates for MTP drafters that declare no buffer layout (Qwen4 inline MTP, NemotronH35 MTP) grow from about 4.5 KB to 37–671 MB per token, so those requests can never be admitted, and Qwen3.5 MTP's partitioned buffers cost about 32 KB more per token. This release is for GLM-5.3-Flash and must not change how other models are charged. Nothing in the provider or the SDK installs the allocation cap whose page-backed fallback the minimum covered.

Effect on GLM: the serving MTP charge is 4.05 GB per request (4.89 GB with the minimum). It was rechecked on one M5 Ultra with the rebuilt chain (d-inference ml-explore#1516 15d7a833d, darkbloom 94a83e27…, metallib 156f2624… unchanged): admission flood, concurrency, 125K context, SSD prefix restore across a restart and a 20-minute soak. See ml-explore#1516.

The source otherwise matches 2bba342d: git diff 2bba342d 6ee3062c touches only mlx/backend/metal/allocator.cpp (one line), its docs paragraph, the standalone cap test cases that asserted the minimum, and fork.yaml.


Summary

The single-Mac native core for GLM-5.3-Flash. It holds the ten general commits from darkbloom/sealed-all-sum (#35) that one-Mac serving needs, replayed onto main, with none of the two-Mac code.

Until now, the single-Mac release (d-inference #1516, mlx-swift-lm #314) pinned core 3a179119. That commit exists only on the cluster stack (#33 → #35), so the single-Mac release could not merge before the JACCL and sealed all-sum work. This branch removes that dependency.

Commit Cherry-picked from Change
b7434f7f ccf517a0 Revert the NAX gather_qmm simdgroup skip (ml-explore#4352) on this tree
0077571e eb576eb2 Bound sorted quantized NAX tail loads
6beeabd8 65eb14fa Test contiguous sorted quantized activation tiles
e3799f23 f7bbf7d7 Give floating-point NAX loaders a distinct Metal symbol
d02ac777 1fb86d3f Separate incompatible quantized Metal loader implementations
9266fda0 adbc349b Fix legacy quantized partial-tile bounds
2a0c9bbb 9e22e199 Optional Metal backing caps; FP32 CPU quantized sums
99dc5f42 4d913204 Cover page-backed small buffers in detached Metal footprints
23164b8b 54975cd0 (Metal half only) Prefault capped no-copy backing; validate custom launch dimensions
2bba342d 3a179119 Retry 512-thread kernels with explicit compiler capacity

54975cd0 mixed two things: the JACCL startup-wait budget and Metal fixes. Only its Metal half is here. The startup-wait budget and its group API stay on darkbloom/sealed-all-sum, together with the JACCL send-frame/progress-guard/GID fixes, the upstream JACCL backports and the sealed all-sum.

Before

flowchart LR
  R["Single-Mac GLM release<br/>#1516 / #314"] --> S["mlx-swift bfaa8a90"]
  S --> C["core 3a179119<br/>(cluster branch only)"]
  C --> J["JACCL fixes + sealed all-sum<br/>#33 → #35"]
  J --> M["main"]
  classDef block fill:#fee2e2,stroke:#b91c1c,color:#450a0a
  class C,J block
Loading

After

flowchart LR
  R["Single-Mac GLM release<br/>#1516 / #314"] --> S["mlx-swift work/glm53-flash-release"]
  S --> C["core work/glm53-flash-release<br/>(this PR)"]
  C --> M["main"]
  T["Cluster stack #33 → #35"] -.->|"same general patches, rebased later"| M
  classDef safe fill:#dbeafe,stroke:#2563eb,color:#172554
  class R,S,C safe
Loading

Evidence

  • Same tree as the cluster pin, minus the cluster code. git diff 3a179119 2bba342d -- . ':!mlx/distributed' ':!tests/jaccl' ':!fork.yaml' lists only six cluster-only files: the distributed and environment-variable docs, mlx/backend/cpu/distributed.cpp, and three distributed Python test files including the scoped_env helper. mlx/distributed/ and tests/jaccl/ are identical to main. Every Metal kernel source matches 3a179119 byte for byte.
  • Fork-diff gate: scripts/check_forkdiff.py --upstream-ref upstream/main passes, with 189 changed files and all 189 described. fork.yaml is the 3a179119 version with three sections removed: JACCL, two-rank sealed all-sum, and the upstream JACCL fixes.
  • Metallib: scripts/fetch-metallib.sh in the d-inference [BUG] vmap over function returning constant value has incorrect output shape ml-explore/mlx#1516 worktree builds mlx.metallib from this branch with sha256 156f2624f9f1b1413dfabd8b6e16423121b0e5b18e2573c23c4e023b606a462d. That is byte-identical to the metallib built from 3a179119 on the same Mac and toolchain (Xcode 27.1).
  • Downstream build: d-inference [BUG] vmap over function returning constant value has incorrect output shape ml-explore/mlx#1516 (provider, -c release, Xcode 27.1 / Swift 6.4) builds and links on the full single-Mac chain (mlx 2bba342d, mlx-c 2f1bcab9, mlx-swift cf921c05, SDK 3ab6dd68). darkbloom sha256 is e863e668…. No model was loaded with this build; end-to-end single-Mac qualification is the next gate.

Relationship to the cluster stack

#35 keeps its commits unchanged. When this PR merges, the cluster stack has to merge main. Its general commits carry the same patches as the ones here, so the code conflicts should be trivial; fork.yaml will conflict and needs resolving by hand. The cluster pair (tp/pair.sh) must be re-verified bitwise after that merge.

Merge danger

Door: two-way. This is a cherry-pick of commits already reviewed on #35, with no new source.

Blast radius: quantized Metal kernels (tail bounds and loader symbols), the optional allocation cap (off unless installed), footprint quotes and the custom-kernel launch retry. Kernel sources are the ones ml-explore#1516 already pinned through 3a179119.

🤖 Generated with Claude Code

Upstream ml-explore/mlx c7ff35d ("Skip unnecessary simdgroup
computations for quantised MOE matmuls on NAX", ml-explore#4352) reached this fork
through the v0.32.2 squash (734241b), not by ancestry, in the form that
ml-explore#4572 later gave it: `const bool sg_active = sgp_sm > 0;` and two
`if (sg_active)` blocks around the MMA loop in each of
fp_gather_qmm_rhs_nax (fp_quantized_nax.h) and affine_gather_qmm_rhs_nax
(quantized_nax.h). This removes the flag and runs the MMA in every
simdgroup again, as before ml-explore#4352. It is the same change as the earlier
revert of ml-explore#4352 on 0.32.2 (e9debbe) and its re-application on the
0.32.3 kernels, hand-ported because the surrounding lines differ here.

Why: these kernels run only on GPUs with NAX. On a two-Mac JACCL pair
with one NAX GPU, mixture-of-experts serving with the skip present lost
all-reduce completions, and 92 to 96 GiB stayed wired on the host until
it was rebooted. The builds that served that pair afterwards carried the
revert. It is therefore kept out of every build that runs distributed on
mixed hardware.

Output is unchanged: stores stay bounded by sgp_sm, and the rows past
the tile that the MMA now reads are either inside M (rows_in_bounds) or
loaded as zeros by load_safe with sgp_sm rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

(cherry picked from commit ccf517a)
Use the remaining reduction width for the final activation tile. Check
row and packed-column bounds before reading affine or floating-quantized
weights and scales, then zero padding lanes.

Add exact-value partial-tile and NaN-neighbor regression coverage for
affine, MXFP4, MXFP8 and NVFP4. The companion Swift regression reproduced
48 failing geometries and passes all 272 cases with this repair. The
original MiMo and Qwen fallback failures also pass on M5 Max.

AI assistance: Codex diagnosed, implemented and tested this patch under
the repository owner's explicit direction.

(cherry picked from commit eb576eb)
Keep the allocation bound stable before and after process cap installation.
Add real ordinary/capped boundary coverage without changing allocation,
ownership, process-cap enforcement or the policy ABI. Document the bound.

Implemented and validated with OpenAI Codex.

(cherry picked from commit 4d91320)
…mensions

Prefault capped no-copy backing before handing it to Metal. Add a one-shot JACCL startup wait budget with C-facing group operations and failure cleanup tests. Validate custom Metal launch dimensions and bound the 1024-thread pipeline retry to the declared geometry.

CPU policy, bridge, allocation-ledger and evaluator regressions pass. Native GPU and end-to-end qualification are tracked separately.

Single-Mac release split: this pick keeps only the Metal half (CPU prefault of
capped no-copy backing, validated custom-kernel launch dimensions and the bounded
pipeline retry). The JACCL startup wait budget and its group API stay on
darkbloom/sealed-all-sum with the rest of the two-Mac code.

(cherry picked from commit 54975cd)
The file still differs from upstream (capped no-copy backing paragraph),
so its glob stays in the allocator accounting section.
@jonathan308
jonathan308 marked this pull request as ready for review October 21, 2026 17:16
@Gajesh2007
Gajesh2007 merged commit ad4745a into main Oct 21, 2026
10 of 13 checks passed
jonathan308 added a commit that referenced this pull request Oct 26, 2026
…ectives branch

Brings in merge/33 at 187b6c6, which is main f78f0fa (the upstream
ml-explore/mlx sync to afc03c5 with its restored history, and the fork
fixes #40, #43-#46) merged into the JACCL send-frame and progress-guard
branch. This branch had merged main up to ad4745a; everything main added
since comes in here.

Conflicts and how they were resolved:
- mlx/fence.h, mlx/backend/{metal,cuda,no_gpu}/fence.cpp, mlx/transforms.cpp:
  main's value-based fence API (upstream ml-explore#4552: update returns a value,
  wait takes it) with this branch's fenced inputs and eager commit.
  Fence::wait_in_consumer now takes the value of the update it waits for,
  and FencedInput carries that value, so a fenced input never waits for a
  later update than its own. The Metal fence keeps the two-word buffer of
  the fused coherent update, now owned through main's allocator::Data.
- Metal fast fence wait: this branch orders the spin with
  wait_for_all_pending (the pass waits for every pass in flight). Upstream
  ml-explore#4689 gets the same order by putting the spin in a pass of its own
  (end_encoding() before it, end_encoding(true) after it). The merge keeps
  the spin in the current pass and adds a buffer barrier after
  wait_for_all_pending, so it also follows the earlier kernels of the same
  pass, as upstream's separate pass does (the same line that the next
  branch, fix/cluster-metal-fence-wait, adds). A wait adds no encoder
  boundary; tensor-parallel decode waits for a CPU-stream all-sum about
  twice a layer. end_encoding(bool wait_in_flight) stays and shares one
  loop with wait_for_all_pending.
- docs/src/usage/environment_variables.rst: both sides' entries
  (MLX_METAL_FUSED_FENCE_UPDATE, MLX_METAL_FENCED_INPUTS, MLX_CONV_WINOGRAD).
- fork.yaml: the merged JACCL branch's file plus this branch's sections
  (startup readiness, two-rank sealed all-sum, fused fence update, fenced
  inputs, cheaper custom-kernel launches). The fenced-input section now
  explains the fence-wait order against upstream ml-explore#4689 and the value-based
  waits. The "not fork work" backport section is gone (upstream ancestry).

Also adapted:
- mlx/distributed/jaccl/lib/jaccl/mesh.h: main's mesh has one MeshImpl per
  wire (upstream ml-explore#4607). The startup wait budget goes to every wire; the
  two-rank exchange of the streamed sealed sum runs on the first wire. The
  two-Mac cluster's device matrix has one device per peer, so it has one
  wire and nothing changes for it.

Unchanged by the merge: main's quantized, matmul and SDPA kernels and their
dispatch (this branch had no change there beyond what main already carried
since #37), main's revert of the upstream ml-explore#4352 simdgroup skip, and this
branch's revert of 4d91320 (main does not carry 4d91320; the footprint
policy keeps {page, page, 0, 0, page}).

Tests (Ultra A, M5 Ultra; Debug build with Metal, 8 jobs):
- tests/tests on the GPU: 310 of 316 cases passed. The 6 failures (linalg
  QR, inverse, Cholesky, pseudo-inverse, eigh, LU) fail the same way on
  main f78f0fa on this machine.
- tests/tests with DEVICE=cpu: 316 of 316 passed.
- ctest process_allocation_cap: 15 of 15 passed.
- check_forkdiff.py --upstream-ref upstream/main: 137 changed files, all
  described, base afc03c5 is the merge-base.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants