Skip to content

Vendored mlx predates the June crash fix (a025496c): Metal command-buffer errors still hard-abort host apps #458

Description

Summary

The mlx submodule vendored by mlx-swift (Source/Cmlx/mlx) is pinned to
ce45c52 (March 12, 2026) on both main (3a24fa3, Aug 14, 2026) and the
latest release 0.31.6 — I checked every 0.31.x tag, they all pin the same
commit.

That pin predates ml-explore/mlx PR #3523 (a025496c, "Catch error in
CommandBuffer and poison the events", merged June 13, 2026), which fixed the
uncatchable-crash class tracked in ml-explore/mlx#3224 and ml-explore/mlx#3390:
gpu::check_error throwing std::runtime_error from inside
MTL::CommandBuffer::addCompletedHandler callbacks
(mlx/backend/metal/eval.cpp, lines 63/70/80 at ce45c52). Those callbacks
run on Metal's com.Metal.CompletionQueueDispatch libdispatch queue, where no
C++ catch frame exists, so any command-buffer error terminates the process with
SIGABRT. Swift host apps cannot catch it — no withError, do/catch, or
signal-level handling helps.

As a result, every mlx-swift app today (including anything built on
mlx-swift-lm) still hard-crashes on transient Metal failures: GPU watchdog
kills, memory-pressure kernel terminations, device loss.

Real-world impact

Production macOS/Catalyst app (on-device legal document analysis, models up to
70B). Under memory pressure, the OS killed an in-flight Metal kernel; the
completion handler then aborted the whole app:

Exception Type:  EXC_CRASH (SIGABRT)
Termination Reason: Namespace SIGNAL, Code 6 Abort trap: 6
abort() called
Crashed thread — Dispatch queue: com.Metal.CompletionQueueDispatch
  libsystem_c.dylib        abort
  libc++abi.dylib          demangling_terminate_handler()
  libc++abi.dylib          std::__terminate(void (*)())
  libc++abi.dylib          __cxxabiv1::failed_throw(...)
  libc++abi.dylib          __cxa_throw
  LexPenalAI               (statically linked Cmlx frame)
  LexPenalAI               (statically linked Cmlx frame)
  Metal                    MTLDispatchListApply
  Metal                    -[_MTLCommandBuffer didCompleteWithStartTime:endTime:error:]

(The two unsymbolicated app frames sit exactly between __cxa_throw and
MTLDispatchListApply; with mlx statically linked they correspond to
gpu::check_error and the completion-handler block in
mlx/backend/metal/eval.cpp — the only throw reachable from a command-buffer
completion callback at pin ce45c52.)

We can only prevent the OS-initiated kill (holding NSProcessInfo activity
assertions during generation); we cannot survive the abort if it happens.
a025496c fixes this properly: handlers no longer throw; the error poisons the
stream's events and surfaces on the calling thread, where Swift can catch it
via the existing error path.

Ask

Bump Source/Cmlx/mlx to a commit ≥ a025496c (June 13, 2026) and cut a
release. Current mlx main also includes follow-ups (693ed59
new_thread_unsafe_stream, 09ebe730 residency-set split) that may need Cmlx
build-glue updates — if a full bump to head is too churny in one step, a bump
to any commit that includes a025496c already removes the crash class.

Happy to test a branch on our workload (sustained 8B–70B generation on M-class
hardware with deliberate memory-pressure kills) — this crash is reliably
reproducible for us.

Environment

  • mlx-swift 0bb916c (July 1, 2026) / also verified against main = 3a24fa3
  • vendored mlx ce45c52 (March 12, 2026)
  • macOS 27.0 beta (Darwin 27), Xcode 27 beta 4, Apple Silicon

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions