After studying this material, you should be able to:
MLX is an open-source array framework built specifically for Apple Silicon. Think of it like PyTorch or NumPy, but optimized for Mac hardware.
Apple Silicon uses a Unified Memory Architecture (UMA):
Traditional Hardware: Apple Silicon:
┌─────────┐ ┌─────────┐ ┌──────────────────────┐
│ CPU │ │ GPU │ │ Unified Memory │
│ RAM │ │ VRAM │ │ CPU + GPU share RAM │
└─────────┘ └─────────┘ └──────────────────────┘
Data must copy between them No copying needed = faster
| Chip | Memory Bandwidth | Max RAM |
|---|---|---|
| M3 Ultra | 819 GB/s | 512 GB |
| M5 Ultra | 1.2 TB/s | 512 GB |
Why this matters: More bandwidth = faster token generation
Before benchmarking makes sense, you need to understand what is being measured.
TTFT (Prefill Phase):
Input tokens → [Matrix Multiplications] → First output token
↑
Needs fast COMPUTE
→ M5 Neural Accelerators help here (up to 4.6× faster)
Decode Phase:
Each new token → [Load weights from RAM] → Next token
↑
Needs fast MEMORY BANDWIDTH
→ M5's 1.2 TB/s helps here (1.6× faster)
Dense Model (128B):
Token → [ALL 128B parameters process it] → Output
MoE Model (816B total, 42B active):
Token → [Router] → [Expert 1] [Expert 7] [Expert 23]... → Output
Only ~42B parameters used per token
| Format | Bits per Parameter | Use Case |
|---|---|---|
| BF16 | 16 | Standard training/inference |
| FP8 | 8 | Efficient inference |
| NVFP4 | 4 | Aggressive compression |
Key insight: Muse Spark Open needs ~1.6TB in BF16, but only ~493GB in NVFP4 — making it possible to run on Mac hardware
Qwen 3.8 (27B): ████░░░░░░░░░░░░ ~68GB ✓ Fits easily
Mistral (128B): ████████░░░░░░░░ ~158GB ✓ Fits
Muse Spark (816B): ████████████████ ~493GB+ ✗ Does NOT fit
mlx_lm.benchmark --model Qwen/Qwen3.8-27B -p 8192 -g 128 -b 1
Breaking down the flags:
--model → which model to load-p 8192 → 8,192 tokens of input context-g 128 → generate 128 output tokens-b 1 → batch size of 1| Model | M3 Ultra TTFT | M5 Ultra TTFT | Speedup |
|---|---|---|---|
| Qwen 3.8 | 18.2s | 4.1s | 4.4× |
| Mistral 128B | 95.9s | 24.6s | 3.9× |
When a model is too large for one machine, you must distribute it.
Mac Studio 1 ←──Thunderbolt 5──→ Mac Studio 2
(RDMA / jaccl)
Low latency, high bandwidth
Split the model by depth (layers):
Model Layers: [1][2][3][4][5][6][7][8]
↓
Mac 1: [1][2][3][4] → sends output → Mac 2: [5][6][7][8]
For large contexts, MLX breaks input into chunks and overlaps computation:
Time →
Mac 1: [Chunk 1, layers 1-4] [Chunk 2, layers 1-4] ...
Mac 2: [Chunk 1, layers 5-8] [Chunk 2, layers 5-8]
↑ Overlap! Both machines work simultaneously
mlx.launch --hostfile hostfile.json --backend jaccl \
--env MLX_METAL_FAST_SYNCH=1 -- \
mlx_lm.benchmark --model mistralai/Mistral-Medium-3.5-128B \
-p 8192 -g 128 -b 1 --prefill-step-size 8192 --pipeline
Key flags:
--backend jaccl → use RDMA ThunderboltMLX_METAL_FAST_SYNCH=1 → faster CPU-GPU sync--pipeline → enable pipeline parallelization--prefill-step-size 8192 → chunk size for overlapping| Metric | Result |
|---|---|
| Decode speed | Similar to single machine |
| TTFT at large context | Up to 1.8× faster (due to overlap) |
Limitation: Layers still run sequentially, so decode doesn't improve much
Split the model by width (each layer distributed across machines):
Pipeline (depth split): Tensor (width split):
Mac 1: [Layer 1-4] Mac 1: [Half of EVERY layer]
Mac 2: [Layer 5-8] Mac 2: [Half of EVERY layer]
Sequential Parallel per layer
Pipeline Parallelization:
Mac1 → [L1→L2→L3→L4] → Mac2 → [L5→L6→L7→L8] → Output
↑ waits for Mac1
Tensor Parallelization:
Mac1 → [L1a→L2a→L3a...] ↘
→ Combine → Output (each layer)
Mac2 → [L1b→L2b→L3b...] ↗
Both work simultaneously per layer!
mlx.launch --hostfile hostfile.json --backend jaccl \
--env MLX_METAL_FAST_SYNCH=1 -- \
mlx_lm.benchmark --model meta-models/Muse-Spark-Open-816B-A42B-NVFP4 \
-p 8192 -g 128 -b 1
Note: No
--pipelineflag needed — tensor parallelization is the default when launching across multiple nodes
| Model | 1 Machine | 2 Machines | 4 Machines |
|---|---|---|---|
| Muse Spark Open decode | baseline | 24.99 t/s | 36.73 t/s |
| Mistral 128B decode | 7.70 t/s | 13.69 t/s | 23.37 t/s |
| Qwen 3.8 decode | 19.69 t/s | 33.27 t/s | 50.89 t/s |
Is the model too large for one machine?
│
├── NO → Use single machine
│
└── YES → How many machines do you have?
│
├── 2 machines, large context → Pipeline may be better for TTFT
│
└── 4+ machines → Tensor parallelization wins
| Strategy | Complexity | Decode Speed | TTFT | Communication |
|---|---|---|---|---|
| Single Machine | Low | Baseline | Baseline | None |
| Pipeline | Low | ≈ Same | Better at large context | Low (only between layer groups) |
| Tensor | High | Much better | Better (scales with machines) | High (every layer, every token) |
Key insight: Tensor parallelization requires very low latency communication — this is why RDMA over Thunderbolt is critical
mlx.launch --hostfile hostfile.json --backend jaccl \
--env MLX_METAL_FAST_SYNCH=1 -- \
mlx_lm.server --model meta-models/Muse-Spark-Open-816B-A42B-NVFP4 \
--port 8080
{
"model": "meta-models/Muse-Spark-Open-816B-A42B-NVFP4",
"provider": {
"mlx": {
"options": {
"baseURL": "http://127.0.0.1:8080/v1"
}
}
}
}
The server exposes an OpenAI-compatible API, so any tool that supports OpenAI can use your local model.
Hardware Layer:
Mac Studio M5 Ultra
├── 512GB Unified Memory (CPU + GPU share)
├── 1.2 TB/s Memory Bandwidth
├── Neural Accelerators (fast matrix multiply)
└── Thunderbolt 5 (RDMA to other Macs)
Software Layer (MLX):
├── mlx_lm.benchmark → measure performance
├── mlx_lm.server → serve models via API
├── mlx.launch → distribute across machines
└── jaccl backend → RDMA communication
Parallelization:
├── Pipeline → split by layers (simple, good for very large models)
└── Tensor → split by width (complex, better decode scaling)
| Achievement | Value |
|---|---|
| Muse Spark Open on 4× M5 Ultra | 36+ tokens/second |
| TTFT speedup M5 vs M3 | Up to 4.6× |
| Decode speedup M5 vs M3 | 1.6× |
| Peak power consumption | ~300W (M5 Ultra) |
| Largest model run locally | 816B parameters |
Test yourself with these questions:
Why is TTFT compute-bound but decode is memory-bandwidth-bound?
TTFT processes all input tokens at once (heavy computation). Decode generates one token at a time, requiring model weights to be loaded from memory repeatedly.
Why does pipeline parallelization not improve decode speed much?
Layers still run sequentially — one machine must wait for the other's output before proceeding.
Why does tensor parallelization require low-latency communication?
Every layer, for every token, requires broadcasting inputs and aggregating outputs across machines. High latency would negate the compute gains.
What makes MoE models like Muse Spark Open memory-efficient at inference?
Only a fraction of experts (and thus parameters) are active per token, reducing compute even though total parameter count is huge.
What is the role of NVFP4 precision for Muse Spark Open?
It reduces memory from ~1.6TB (BF16) to ~493GB, making it possible to fit across a cluster of Mac Studios.