Repository navigation
fix(mlx): wait for the in-flight decode step before a prompt eval (fixes rare tensor-parallel deadlock) - #2393
Open
AlexCheema wants to merge 1 commit into
Open
AlexCheema wants to merge 1 commit into
AlexCheema wants to merge 1 commit into
Conversation
mlx-lm's BatchGenerator async-evaluates the batch's next decode step and then processes new prompts, and exo prefills a new request while the batch's last step is still running. For a tensor-parallel model with MLX_METAL_FAST_SYNCH, both evals queue their collectives on the one communication stream, while their GPU work spins waiting for it. When the prompt eval's spinning GPU work held the GPU while the step's last kernels still had to run (more likely with another model's runner on the same Mac), neither could finish: one rank's communication stream waited in Fence::wait for a fence update that its GPU never ran, and the other rank waited in all_reduce. Synchronize mlx-lm's generation stream before exo's prefill and before the batch processes new prompt tokens, so that no prompt eval overlaps a decode step. A request now waits for at most one decode step before its prefill; steady decoding is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
4 tasks done
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.
Problem
A tensor-parallel model over RDMA (
MlxJaccl, fast synch on) still deadlocks now and then under concurrent load, even with #2391. It happened about once an hour when another model's runner shared one of its Macs, and within minutes in chaos testing, where the cluster moves models around. The stuck-runner watchdog then kills the runner after 90 s and every request in flight fails.What the stuck rank looks like (stack samples, lldb on the live process, a Metal System Trace):
mlx::core::Fence::wait, waiting for its GPU to signal fence value 128. The update to 128 has been encoded (the fence's update count is 128), but the buffer holds 127.eval_impl, having committed everything.jaccl::MeshImpl::all_reduce.Cause
Two evals of the tensor-parallel model overlap:
BatchGenerator._nextasync-evaluates the batch's next decode step, then processes new prompts straight away;Each eval queues its collectives (128 for this 64-layer model) on the one communication stream, and its GPU work spins waiting for them (fast synch). The prompt eval's committed command buffers start spinning while the decode step's last kernels still have to run. MLX's buffers are untracked, so nothing orders those kernels against the spinning ones.
The two waits form a cycle:
With the GPU to itself this rarely bites. With another process's work on the same GPU it happens about once an hour.
Fix
Don't let a prompt eval overlap a decode step. Before exo's prefill, and before the batch processes new prompt tokens, synchronize mlx-lm's generation stream. A new request waits for at most one decode step before its prefill starts; steady decoding is unchanged.
Evidence
Reproduction without exo (mlx-lm only:
sharded_load+BatchGeneratorover JACCL on two Mac minis, M4 Pro 64 GB, Thunderbolt 5 RDMA, MLX main 0e3ff364, fast synch on, a second mlx-lm process generating on the same Macs):exo (Qwen3.8-27B tensor parallel over RDMA on 2 Mac minis, Llama-3.2-1B on one of them, 6 + 4 concurrent clients):
No cost measured elsewhere.
Single requests: 731-token prompt, 256 tokens out, medians of 20, two alternating rounds.
Sharing a Mac with Llama, the Qwen instance completed 132 requests per 10 min, against 138 in the earlier runs that didn't stall (within run-to-run variation).
Test plan
uv run basedpyright(0 errors),uv run ruff check,ruff formatuv run pytest src -m "not slow"(CI's command): 471 passed, 3 skipped🤖 Generated with Claude Code