feat: select and apply MLX backend per shard (#958)#2200
Open
oliver0006 wants to merge 1 commit into
Open
Conversation
Placement now records which backend each runner should use (ShardMetadata.backend, chosen per node from its supported backends in INSTANCE_META_BACKENDS preference order), and the runner bootstrap applies it with mx.set_default_device() before the engine is built. Unassigned shards keep the engine default, so existing behavior is unchanged when no backend is recorded. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Motivation
Addresses #958 ("Support all MLX backends").
MLX supports several computational backends (Metal, CUDA, CPU), and the placement layer already knows which backends every node supports (
state.node_backends,INSTANCE_META_BACKENDS) — but that decision was never recorded on the instance, and runners never calledmx.set_default_device(). Backend selection only worked implicitly, by whatever default the local MLX build picked. Recording the backend per shard and applying it early in the runner lifecycle unlocks explicit support for heterogeneous clusters (e.g. Macs + Linux CUDA/CPU nodes in one ring) without introducing a new runner, as suggested in the issue.Changes
ShardMetadata.backend: Backend | None(defaultNone) — records the compute backend a shard should run on;Nonepreserves today's behavior end to end.assign_shard_backends()inplacement_utils.py— pure function that picks, for every runner, the first backend of the instance-meta preference order (Metal > Cuda > Cpu, intersected with the model card's backends) that its node supports.place_instance()calls it right afterget_shard_assignments(), so all instance types get per-runner backends.apply_shard_backend()in the runnerbootstrap.py— maps the assigned backend to an MLX device and callsmx.set_default_device()before the engine is built (MlxMetal/MlxCuda→mx.gpu,MlxCpu→mx.cpu, anything else → no-op).Why It Works
The master is the only place that knows both the node capabilities and the model requirements, so the backend decision is made there and travels to the runner inside the shard metadata it already receives. Setting the default device at the very top of the runner entrypoint (before
MlxBuilder/MfluxBuilderconstruction) means every subsequent MLX allocation lands on the assigned device. The field defaults toNoneand unassigned shards change nothing, so old serialized states and any path that doesn't set a backend behave exactly as before.Test Plan
Manual Testing
Hardware: Fedora Linux, AMD Ryzen (16 threads) + Radeon 780M iGPU — deliberately a non-Apple box, since that's what this issue is about. Installed the
mlx-cpuwheel (matching themlx-cpuextra) and verifiedapply_shard_backend(Backend.MlxCpu)flipsmx.default_device()to CPU on a Linux host.Automated Testing
test_assign_shard_backends_picks_first_preferred_backend_per_node— pure-function behavior incl. preference order and nodes with no matching backend.test_placement_assigns_a_backend_to_every_shard— end-to-endplace_instance()on a heterogeneous 3-node ring (Metal/CUDA/CPU nodes) asserts each runner gets its node's backend.test_apply_shard_backend_*— MLX default device is set forMlxCpuand untouched forNone/Vllm(device restored after each test).test_masterexpectation updated: the created instance's shard now carriesbackend=MlxMetal, which asserts the full master→instance propagation.uv run basedpyright(0 errors),uv run ruff check,ruff format --checkall pass;uv run pytest src→ 344 passed, 5 skipped (the 13 collection errors are pre-existing on Linux:mfluxandmlx_lm.models.deepseek_v4aren't importable in this environment and are untouched by this change).nix fmtwasn't runnable on this machine (no nix without root) —ruff formatwas used instead.Disclosure: co-written by a human (@oliver0006) directing the work and Claude AI (Fable 5) writing code, following AGENTS.md and .clauderules.
🤖 Generated with Claude Code