⚡ Native MLX Swift LLM inference server for Apple Silicon. OpenAI-compatible API, SSD streaming for 100B+ MoE models, TurboQuant KV cache compression, MACOS + iOS iPhone app.
Swift
772
617 commits
updated Sep 23, 2026
A blazingly fast, native Swift inference server that serves MLX models with a strict OpenAI-compatible API.
No Python runtime, no Global Interpreter Lock (GIL), no unnecessary memory copies. Just bare-metal Apple Silicon performance compiled to a single binary.
Download the latest release tarball from the Releases page.
The archive is self-contained — mlx.metallib is bundled alongside the binary.
tar -xzf SwiftLM-<version>-macos-arm64.tar.gz
./SwiftLM --model mlx-community/Qwen2.5-3B-Instruct-4bit --port 5413
The build script handles everything: submodules, cmake, Metal kernel compilation, and the Swift build.
git clone --recursive https://github.com/SharpAI/SwiftLM
cd SwiftLM
./build.sh
This will:
cmake via Homebrew (if not already installed)mlx.metallib from the Metal kernel sourcesSwiftLM binary in release modeThen start the server (models download automatically if not cached):
.build/release/SwiftLM \
--model mlx-community/gemma-4-26b-a4b-it-4bit \
--port 5413
(Add --stream-experts when running oversized MoE models to bypass macOS virtual memory swapping and stream expert layers directly from NVMe SSD.)
Benchmarked with gemma-4-26b-a4b-it-4bit running three configurations across 512 / 40K / 100K token contexts.
| Configuration | 512 tokens | 40K tokens | 100K tokens | Avg TPS* |
|---|---|---|---|---|
| Baseline | 70.8 | 34.3 | 25.8 | 36.6 |
| MTP Speculative | 71.5 (1.01×) | 38.4 (1.12×) | 29.1 (1.13×) | 40.3 |
| MTP + TurboQuant ⭐ | 72.1 (1.02×) | 65.2 (1.90×) | 62.1 (2.41×) | 66.2 |
* Time-weighted average: total_tokens / sum(60/TPS) — correct wall-clock representation vs arithmetic mean.
| Configuration | 512 tokens | 40K tokens | 100K tokens |
|---|---|---|---|
| Baseline | 0.64s | 22.85s | 63.11s |
| MTP Speculative | 0.34s | 20.45s | 55.17s |
| MTP + TurboQuant ⭐ | 0.33s | 13.17s | 33.95s |
| Configuration | 512 tokens | 40K tokens | 100K tokens |
|---|---|---|---|
| Baseline | 20.4 GB / 15.8 GB | 54.8 GB / 19.3 GB | 54.3 GB / 23.3 GB |
| MTP Speculative | 20.4 GB / 16.0 GB | 54.6 GB / 20.8 GB | 54.3 GB / 23.1 GB |
| MTP + TurboQuant ⭐ | 20.3 GB / 15.8 GB | 23.9 GB / 17.3 GB | 26.4 GB / 18.2 GB |
Key takeaways:
Run
python3 -u scripts/profiling/profile_runner.py --model gemma-4-26b-a4b-it-4bit --contexts "512,40000,100000"to reproduce on your device.
Benchmark results for gemma-4-26b-a4b-it-4bit (26B MoE, ~4B active params/token, 4-bit) on M5 Pro 64 GB.
⚠️ This is a Mixture-of-Experts (MoE) model, not a dense model. Each token activates ~4B of the 26B parameters. "Vanilla" = all experts loaded into unified RAM (no SSD streaming).
gemma-4-26b-a4b-it-4bit (4-bit MoE)Benchmarked on M5 Pro 64 GB ·
gemma-4-26b-a4b-it-4bit·./run_benchmark.shOption 13 Values shown asgeneration TPS · OS RAM used(TTFT excluded from speed measurement)
| Configuration | 512 ctx | 40K ctx | 100K ctx |
|---|---|---|---|
| Vanilla (full-RAM MoE) | 77.5 tok/s · 14.6 GB | 44.3 tok/s · 48.7 GB | 27.5 tok/s · 48.5 GB |
| Vanilla + MTP | 72.7 tok/s · 16.6 GB | 44.9 tok/s · 49.4 GB | 37.5 tok/s · 49.3 GB |
| Vanilla + TurboQuant | 77.3 tok/s · 14.7 GB | 70.1 tok/s · 18.2 GB | 66.9 tok/s · 20.7 GB |
| Vanilla + MTP + TurboQuant | 73.5 tok/s · 16.6 GB | 53.8 tok/s · 19.7 GB | 32.8 tok/s · 22.0 GB |
| SSD Stream | 10.8 tok/s · 22.2 GB | 10.4 tok/s · 24.2 GB | 9.0 tok/s · 27.6 GB |
| SSD + TurboQuant | 11.4 tok/s · 22.0 GB | 2.5 tok/s · 22.5 GB | 1.6 tok/s · 22.3 GB |
GPU Peak physical RAM (from
ioreg): Vanilla 100K peaks at 21.8 GB in-use · TurboQuant 100K stays at 17.1 GB in-use
Key takeaways (4-bit):
Vanilla + TurboQuant delivers 66.9 tok/s vs 27.5 tok/s Vanilla — a 2.43× speedup.gemma-4-26b-a4b-it-8bit (8-bit, bandwidth-bound)Benchmarked on M5 Pro 64 GB ·
gemma-4-26b-a4b-it-8bit·./run_benchmark.shOption 13
| Configuration | 512 ctx | 40K ctx | 100K ctx |
|---|---|---|---|
| Vanilla | 53.7 tok/s · 26.1 GB | 32.4 tok/s · 49.4 GB | 14.9 tok/s · 49.3 GB |
| Vanilla + MTP ⭐ | 47.1 tok/s · 28.0 GB | 38.8 tok/s (+20%) · 49.6 GB | 22.5 tok/s (+51%) · 49.6 GB |
| Vanilla + TurboQuant | 53.5 tok/s · 26.1 GB | 50.1 tok/s · 29.6 GB | 48.3 tok/s · 32.0 GB |
| Vanilla + MTP + TurboQuant | 47.4 tok/s · 28.0 GB | 31.0 tok/s · 31.1 GB | 23.3 tok/s · 33.3 GB |
Key takeaways (8-bit):
--mtp for 8-bit models at 40K+ contexts without TurboQuant. Use --turbo-kv (without MTP) when maximum throughput or memory efficiency is the priority.Run
./run_benchmark.sh→ Option 13 to reproduce these metrics on your own device.
Benchmark results for full-RAM (no SSD streaming) MoE inference on M1 Ultra. The 3.4× vanilla improvement vs. earlier builds comes from the needsMoeFlush gate in mlx-swift-lm (see SwiftLM #84) — the per-layer GPU sync barrier required for SSD streaming was firing unconditionally on the full-RAM path and flushing MLX's kernel-batching pipeline.
| Configuration | Short (~126 tok) | Medium (~400 tok) | Long (~800 tok) |
|---|---|---|---|
| Vanilla full-GPU | 61.7 tok/s | 62.3 tok/s | 62.1 tok/s |
Hardware: Apple M1 Ultra, 64 GB unified memory, macOS 26.x. Model ~20 GB on disk, ~21.6 GB resident weight + ~2.1 GB KV at runtime. Flags:
--repeat-penalty 1.1 --max-tokens 2000,temperature: 0.6, single-stream/v1/chat/completions. Vanilla baseline beforeneedsMoeFlushgate (for reference): 19.2 / 18.1 / 18.3 tok/s — see #84.
⚠️ DFlash on this model is currently unsuitable for production. DFlash uses pure greedy (
argMax) decoding regardless oftemperature, which on Qwen3.6-35B-A3B + thez-lab/Qwen3.6-35B-A3B-DFlashdraft locks into low-entropy attractors ("and and and...","**UMA** **UMA**..."). Earlier 70 tok/s DFlash numbers were degenerate output that scored high acceptance because draft and target both committed to the same locked-in token. Repetition-penalty mitigation works on some prompts but tanks acceptance on others — the proper fix is stochastic posterior sampling with rejection-based accept (Leviathan/Chen formulation), which is a DFlash architecture change tracked at z-lab/dflash#91.
Model: Thump604/DeepSeek-V4-Flash-MLX-Q3-mixed-gs128-affine
Dense/Vanilla and TurboQuant (non-SSD) configurations are skipped automatically — the 126 GB model exceeds physical RAM.
| Configuration | 512 ctx | 40K ctx |
|---|---|---|
| SSD Stream | 4.65 tok/s · 16.7 GB RAM | 0.32 tok/s · 12.5 GB RAM |
| SSD + TurboQuant | 4.78 tok/s · 16.8 GB RAM | 4.16 tok/s · 16.8 GB RAM |
| SSD + 16-Worker Prefetch | 4.43 tok/s · 16.6 GB RAM | 0.32 tok/s · 13.6 GB RAM |
Values shown as
generation speed · peak physical RAM used(sampled every 0.5s during prefill + generation). The 126 GB model streams the rest from NVMe SSD.
Key takeaways:
/v1/chat/completions, streaming, etc).--vision flag, supporting real-time base64 image parsing (e.g., Qwen2-VL, PaliGemma).--audio flag, decoding OpenAI-spec input_audio payloads with AVFoundation WAV extraction.--gpu-layers) and Wisdom Auto-Calibration for squeezing massive models into RAM.SwiftLM dynamically maps Apple MLX primitives to standard HuggingFace architectures, enabling native Metal inference across the latest frontier open-weights models.
| Family | Models | Notes |
|---|---|---|
| Gemma 4 | gemma-4-e2b, gemma-4-e4b (dense) · gemma-4-26b-a4b, gemma-4-31b (MoE) | Interleaved local + global attention; KV sharing; native quantized KV cache (issue #71 fix) |
| Gemma 3 / 3n | gemma-3-*, gemma-3n-* | Google Gemma 3 and nano variants |
| Gemma / Gemma 2 | gemma-*, gemma-2-* | Original Gemma family |
| Qwen 3.5 | Qwen3.5-7B, Qwen3.5-27B, Qwen3.5-122B-A10B, Qwen3.5-397B-A22B | Dense + MoE; SSD streaming at 10× for 122B/397B |
| Qwen 3 | Qwen3-* (dense + MoE) | Sliding window + hybrid attention |
| Qwen 2.5 | Qwen2.5-7B, Qwen2.5-14B, Qwen2.5-72B | Robust RoPE scaling |
| Qwen 2 | Qwen2-* | Linear RoPE variants |
| Phi 4 / PhiMoE | phi-4-mlx, Phi-3.5-MoE | Microsoft Phi family incl. MoE |
| Phi 3 / Phi | Phi-3, Phi-3.5-mini | 128k context via chunked prefill |
| Mistral / Mixtral | Mistral-7B, Mistral-4, Mixtral-* | GQA + sliding window variants |
| Llama / Llama 3 | Llama-3.1-*, Llama-3.2-*, Llama-3.3-* | YaRN + dynamic NTK RoPE scaling |
| GLM 4 | GLM-4-* | THUDM GLM-4 dense + MoE-Lite variants |
| DeepSeek V3 | DeepSeek-V3-* | MLA attention architecture |
| Falcon H1 | Falcon-H1-* | Falcon hybrid SSM+attention |
| LFM 2 | LFM2-*, LFM2-MoE-* | Liquid AI dense + MoE |
| OLMo 2 / OLMo 3 / OLMoE | OLMo-2-*, OLMo-3-* | AllenAI open language models |
| Granite / GraniteMoE | Granite-*, GraniteMoE-Hybrid-* | IBM Granite hybrid Mamba+attention |
| SmolLM 3 | SmolLM3-* | HuggingFace compact LM |
| MiniCPM | MiniCPM-* | Lightweight efficient LM |
| InternLM 2 | InternLM2-* | Shanghai AI Lab series |
| Cohere / Command-R | Command-R-*, c4ai-* | Cohere retrieval-tuned models |
| Jamba | Jamba-v0.1 | AI21 hybrid Mamba+attention |
| Exaone 4 | EXAONE-4.0-* | LG AI Research |
| MiMo / MiMo V2 | MiMo-7B-* | Xiaomi reasoning model |
| Ernie 4.5 | ERNIE-4.5-* | Baidu ERNIE series |
| Baichuan M1 | Baichuan-M1-* | Baichuan multimodal base |
| Bailing MoE | Ling-* | Bailing/Ling MoE family |
| NemotronH | Nemotron-H-* | NVIDIA Nemotron hybrid |
| Starcoder 2 | starcoder2-* | Code generation |
| OpenELM | OpenELM-* | Apple on-device efficient LM |
| Apertus / AfMoE | Apertus-* | Sparse MoE research models |
| BitNet | bitnet-* | 1-bit weight quantization |
| MiniMax | MiniMax-Text-* | Lightning attention architecture |
| Olmo3 | Olmo3-* | AllenAI Olmo3 series |
Run with --vision flag.
| Family | Models | Notes |
|---|---|---|
| Gemma 4 | gemma-4-* (VLM mode) | Native image tower via MLXVLM |
| Gemma 3 | gemma-3-* (VLM mode) | PaLiGemma-style image projection |
| Qwen3-VL / Qwen3.5-VL | Qwen3-VL-*, Qwen3.5-VL-* | Dynamic resolution with native RoPE |
| Qwen2-VL / Qwen2.5-VL | Qwen2-VL-2B/7B, Qwen2.5-VL-* | Real-time positional bounding + Metal image scaling |
| LFM2-VL | LFM2-VL-1.6B | Liquid AI multimodal |
| Pixtral | pixtral-12b | Mistral vision model |
| PaliGemma | paligemma-* | Google vision-language |
| Idefics 3 | Idefics3-* | HuggingFace multimodal |
| Mistral 3 | Mistral-Small-3.1-* | Mistral vision variant |
| FastVLM | FastVLM-* | Apple on-device VLM |
| SmolVLM 2 | SmolVLM2-* | HuggingFace compact VLM |
| GLM OCR | glm-4v-* | THUDM vision+OCR |
| QwenVL | Qwen-VL-* | Original Qwen VL |
Run with --audio flag. Only gemma-4-e4b variants include an audio tower.
| Family | Models | Notes |
|---|---|---|
| Gemma 4 Omni | gemma-4-e4b-it-4bit, gemma-4-e4b-it-8bit | Audio-in via vDSP STFT → Mel spectrogram (16kHz, 128 bins); text-out |
A native iPhone & iPad companion app that downloads MLX models directly from HuggingFace and runs inference on-device via MLX Swift.
mlx-community model by name📱 Running live on iPhone 13 Pro (6 GB) — no Python, no server, no GIL. Pure on-device MLX inference via Metal GPU.
cd SwiftBuddy
python3 generate_xcodeproj.py # Generates SwiftBuddy.xcodeproj
open SwiftBuddy.xcodeproj
Then in Xcode:
Note for contributors: The
.xcodeprojis git-ignored (it contains your personal Team ID). Rungenerate_xcodeproj.pyafter cloning to regenerate it locally. Your Team ID is never committed.
SwiftLM implements a hybrid V2+V3 TurboQuant architecture for on-the-fly KV cache compression. At roughly ~3.6 bits per coordinate overall, the KV cache is compressed ~3.5× vs FP16 with near-zero accuracy loss.
Recent reproductions of the TurboQuant algorithm (e.g., turboquant-mlx) revealed two distinct paths:
We built the "Holy Grail" hybrid: We ported the V3 non-linear Lloyd-Max codebooks directly into the native C++ encoding path, and process the dequantization natively in fused Metal (bggml-metal) shaders. This achieves V3 quality at V2 speeds, completely detached from Python overhead.
K-Cache (3-bit PolarQuant + 1-bit QJL) = 4.25 bits/dim
x̂ = x / ‖x‖V-Cache (3-bit PolarQuant) = 3.125 bits/dim Because the V-cache matrix is not used for inner-product attention scoring, the QJL error correction provides no benefit. We cleanly disable QJL for the V-cache, extracting an additional 25% memory savings without sacrificing quality.
Reference implementations: turboquant-mlx | turboquant_plus | Paper: TurboQuant, Google 2504.19874
SwiftLM implements a rewritten SSD expert streaming pipeline (engineered by Eric Lake) that achieves 10x generation speedup for massive Mixture of Experts (MoE) models running on memory-constrained Apple Silicon. This enables running models like Qwen3.5-122B (69.6 GB) and Qwen3.5-397B (209 GB) on a 64 GB Mac by streaming expert weights from NVMe SSD.
| Configuration | tok/s | vs. Original | Notes |
|---|---|---|---|
Original --stream-experts | 0.58 | baseline | Sequential pread, 1 NVMe queue |
| This PR (top-k=8, full quality) | 4.95 | 8.5× | All 8 experts evaluated |
| This PR (top-k=6, default) | 5.20 | 9.0× | Recommended default |
| This PR (top-k=4, speed mode) | 5.91 | 10.2× | Best quality/speed tradeoff |
| This PR (top-k=2, turbo mode) | 6.52 | 11.2× | Still coherent output |
Memory stable at ~10.6 GB resident, no swap activity. Tested over 200-token generation runs.
A novel aspect of this architecture is the dual-model speculative decoding pattern: a small draft model (e.g. Qwen3.5-9B at 73 tok/s) runs entirely in RAM while the large MoE model (e.g. 122B) streams experts from SSD. The draft model generates candidate tokens at high speed, and the main model verifies them in bulk — dramatically reducing the number of SSD-bound generation rounds needed.
Performance note: Combining
--stream-expertswith--draft-modelrequires care. The verify pass sends N+1 tokens simultaneously, each routing to different experts — SSD I/O scales with the union of all positions' expert selections. At the default--num-draft-tokens 4this creates a 5× I/O fan-out that regresses throughput below solo SSD streaming.Auto-cap strategy (Issue #72 fix): SwiftLM automatically caps
--num-draft-tokensto 1 when both flags are active. With 1 draft token the verify pass covers only 2 positions (2× fan-out). If the draft model's acceptance rate is ≥ 50% — typical for same-family models — the net throughput is still positive despite the 2× I/O overhead. A startup advisory is printed when the cap fires.For maximum throughput: use
--stream-expertsalone (no draft model).
eval() calls down to ~48 per token by orchestrating gate/up/down projections together in SwitchGLU.DispatchQueue.concurrentPerform, saturating the NVMe controller's queue depth (8 experts × 3 projections = 24 parallel reads).SwitchGLU layer and reused across tokens, eliminating per-token allocation overhead.SWIFTLM_TOP_K environment variable reduces the number of active experts per token at runtime without model recompilation — trading marginal quality for significant speed gains.| Finding | Detail |
|---|---|
| GPU compute is the bottleneck | At steady state, GPU compute is ~190ms of ~200ms per-token time. The OS page cache serves ~90% of expert reads from RAM. |
| Don't cache experts in application memory | An LRU expert cache stole from the OS page cache and regressed performance (4.84 → 4.01 tok/s). Let the kernel manage it. |
| MambaCache requires checkpoint rollback | Unlike attention KV caches (trim = decrement offset), Mamba's recurrent state integrates all history and cannot be partially undone. We implemented checkpoint()/restore() for speculative decoding on hybrid Attention+Mamba architectures (Qwen3.5). |
# Standard SSD streaming (recommended, top-k=6):
SWIFTLM_TOP_K=6 SwiftLM --port 8002 \
--model <path>/Qwen3.5-122B-A10B-4bit --stream-experts
# Speed mode (top-k=4):
SWIFTLM_TOP_K=4 SwiftLM --port 8002 \
--model <path>/Qwen3.5-122B-A10B-4bit --stream-experts
# With speculative decoding (in-RAM models only — both models fit in RAM):
SwiftLM --port 8002 \
--model <path>/Qwen3.5-27B-4bit \
--draft-model <path>/Qwen3.5-9B-4bit \
--num-draft-tokens 4
# With SSD streaming + draft model (auto-cap mode):
# SwiftLM automatically caps --num-draft-tokens to 1 to minimise the
# verify-pass I/O fan-out. Net positive if draft acceptance rate ≥ 50%.
SwiftLM --port 8002 \
--model <path>/Qwen3.5-122B-A10B-4bit \
--stream-experts \
--draft-model <path>/Qwen3.5-9B-4bit
# ↑ num-draft-tokens is auto-capped to 1 at startup
SwiftLM supports two forms of Speculative Decoding to accelerate in-RAM inference:
Load a small draft model alongside a large main model. The draft model generates candidate tokens at high speed, and the main model verifies them in bulk.
Requires passing both --model and --draft-model.
For models trained with native MTP heads (e.g., the Qwen3 family), SwiftLM automatically leverages the hidden MTP layers to draft future tokens within a single forward pass, completely eliminating the need to load a separate draft model.
Algorithmic Parity (Leviathan et al.)
SwiftLM implements mathematically rigorous probabilistic rejection sampling (as defined by Leviathan et al.) in its MTPTokenIterator. This ensures exact mathematical output parity with the target model's true distribution, even at non-zero temperatures, properly evaluating $P_{target} / P_{draft}$ and resampling the corrected distribution upon rejection.
MTP is strictly a Compute-Bound optimization.
We successfully verified algorithmic parity and a 15%+ TPS speedup on the dense Qwen/Qwen3.6-27B model, which fits completely in 64GB VRAM.
However, running MTP on massive MoE models (like the Qwen3.6-35B-A3B) on a 64GB Mac requires --stream-experts to fetch MoE weights from the NVMe SSD. Because MTP evaluates multiple draft tokens in parallel, the verify pass forces a massive I/O fan-out, attempting to fetch up to 3x as many unique experts from the SSD simultaneously.
This saturates the NVMe bandwidth, causing the GPU to stall and completely neutralizing the MTP speedup. If you are running a 64GB Mac, MTP on 35B+ MoE models will be slower than the baseline.
(Community Help Wanted: We are actively looking for optimizations to batch expert pre-fetching during MTP verification to make this viable on 64GB Unified Memory limits!)
To achieve the extreme memory efficiency and speeds seen in SSD Expert Streaming and Speculative Decoding, SwiftLM relies on custom C++ primitives that bypass standard unified memory limits.
[!NOTE] We maintain custom forks (
SharpAI/mlxandSharpAI/mlx-c) to support out-of-core memory-mapped execution, streaming tensor blocks directly from the SSD (NVMe) to the GPU via custom Metal kernels (ssd_streamer.mmandfence.air). Officialml-explorerepositories do not yet support this out-of-the-box.
For a detailed breakdown on repository architecture, upstream synchronization, our specific custom patches, and the specific indications for when we can safely revert to Apple's native upstream, read the full documentation: 👉 Upstream MLX Synchronization & SSD Streaming Maintenance
Run our automated benchmark suites via the interactive script:
./run_benchmark.sh
The script provides an interactive menu to select any model and run one of two automated testing suites:
Tests generation speed (TPS) and granular Apple Metal GPU memory allocation across extreme context lengths (e.g., 512, 40000, 100000 tokens).
docs/profiling/profiling_results_<hostname>.md.Verifies the stability of the engine's KV prompt cache when interleaving long contexts with sliding window attention bounds.
5537t → 18t → 5537t → Big Full Cache Hit).SIGTRAP errors.Tested by rendering exactly 20 tokens under standard conversational evaluation (--prefill-size 512) to capture precise Token Generation (TPS) and Apple Metal memory footprint limits:
| Model | Time To First Token (s) | Generation Speed (tok/s) | Peak GPU Memory (GB) |
|---|---|---|---|
gemma-4-e2b-it-4bit | 0.08s | 116.27 tok/s | 1.37 GB |
gemma-4-e4b-it-8bit | 0.33s | 48.21 tok/s | 7.64 GB |
gemma-4-26b-a4b-it-4bit | 0.14s | 85.49 tok/s | 13.46 GB |
gemma-4-31b-it-4bit | 0.55s | 14.82 tok/s | 16.83 GB |
To run the automated suite on your machine for these models, execute:
python3 tests/run_4models_benchmark.py
🧠 How it works: SwiftLM implements Chunked Prefill (controlled via
--prefill-size, defaulting to 512). This is functionally equivalent tollama.cpp's--batch-sizeparameter and mirrors themlx-lmPython library's reference implementation approach to preventing $O(N^2)$ Unified Memory over-allocation during massive sequence parsing.
⚠️ Quantization Disclaimer: While heavier quantization shrinks the required memory footprint, 4-bit quantization remains the strict production standard for MoE models. Our metrics indicated that aggressive 2-bit quantization heavily destabilizes JSON grammars—routinely producing broken keys like
\name\instead of"name"—which systematically breaks OpenAI-compatible tool calling.
| Endpoint | Method | Description |
|---|---|---|
/health | GET | Server health + loaded model capabilities |
/v1/models | GET | List available models |
/v1/chat/completions | POST | Chat completions (LLM and VLM support, multi-turn, system prompts) |
Drop-in compatible with standard OpenAI HTTP consumers:
curl http://localhost:5413/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-4-26b-a4b-it-4bit",
"stream": true,
"messages": [
{"role": "system", "content": "You are Aegis-AI, a local home security agent. Output strictly in JSON format."},
{"role": "user", "content": "Clip 1: Delivery person drops package at 14:02. Clip 2: Delivery person walks away down driveway at 14:03. Do these clips represent the same security event? Output a JSON object with a `duplicate` boolean and a `reason` string."}
]
}'
To run a vision model (e.g., mlx-community/Qwen2-VL-2B-Instruct-4bit), launch SwiftLM with the --vision flag:
./.build/release/SwiftLM --model mlx-community/Qwen2-VL-2B-Instruct-4bit --vision
You can then pass standard OpenAI base64 encoded images directly. SwiftLM handles hardware spatial-mapping natively via Metal:
curl http://localhost:5413/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2-vl",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "Describe the contents of this image."},
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."}}
]
}
]
}'
| Option | Default | Description |
|---|---|---|
--model | (required) | HuggingFace model ID or local path |
--port | 5413 | Port to listen on |
--host | 127.0.0.1 | Host to bind |
--vision | false | Enable VLM (vision-language model) mode for image inputs |
--audio | false | Enable ALM (audio-language model) mode for audio inputs |
--max-tokens | 2048 | Max tokens limit per generation |
--prefill-size | 512 | Prompt prefill chunk size (micro-batching for long contexts) |
--top-p | 1.0 | Default top-p nucleus sampling (overridable per-request) |
--top-k | 50 | Default top-k sampling (0 disables, overridable per-request) |
--min-p | 0.0 | Default min-p sampling threshold relative to the highest probability token (0 disables) |
--gpu-layers | model_default | Restrict the amount of layers allocated to GPU hardware |
--stream-experts | false | Enable SSD expert streaming for MoE models (10x speedup) |
--turbo-kv | false | Enable TurboQuant 3-bit KV cache compression (activates after 2048 tokens, server-wide) |
--draft-model | (none) | Draft model path/ID for speculative decoding. When used with --stream-experts, --num-draft-tokens is auto-capped to 1 to minimise SSD I/O fan-out (see performance note above). |
--num-draft-tokens | 4 | Tokens per speculation round. Auto-capped to 1 when combined with --stream-experts. |
--dflash | false | Enable DFlash block-diffusion speculative decoding. Requires a compatible DFlash draft model |
--dflash-block-size | (auto) | Number of tokens per DFlash draft block. Defaults to draft model config |
In addition to the standard OpenAI fields (temperature, top_p, max_tokens, etc.), SwiftLM accepts the following SwiftLM-specific fields on POST /v1/chat/completions:
| Field | Type | Description |
|---|---|---|
kv_bits | int (4 or 8) | Enable MLX-native quantized KV cache for this request. Uses QuantizedKVCache (standard group quantization) instead of KVCacheSimple. Separate from --turbo-kv. Reduces KV memory ~2–4× at mild quality cost. |
enable_thinking | bool | Force-enable or disable chain-of-thought thinking blocks for Gemma-4 / Qwen3. |
kv_group_size | int | Group size for kv_bits quantization (default: 64). |
top_k | int | Per-request top-k sampling override (0 = disabled). |
min_p | float | Per-request min-p sampling threshold (0 = disabled). |
repetition_penalty | float | Token repetition penalty (e.g. 1.15). |
kv_bits vs --turbo-kv — What's the difference?kv_bits (per-request) | --turbo-kv (server flag) | |
|---|---|---|
| Scope | Per-request, sent in JSON body | Server-wide, set at startup |
| Algorithm | MLX-native group quantization (4-bit / 8-bit) | Custom 3-bit PolarQuant + QJL Walsh-Hadamard |
| Activation | From token 0 | After 2048 tokens |
| Memory savings | ~2–4× vs FP16 | ~3.5× vs FP16 |
| Use case | Targeted memory reduction per conversation | Extreme long-context (100K+) compression |
curl http://localhost:5413/v1/chat/completions \\
-H "Content-Type: application/json" \\
-d '{
"model": "gemma-4-26b-a4b-it-4bit",
"kv_bits": 4,
"messages": [
{"role": "user", "content": "Summarize the history of computing in 3 sentences."}
]
}'
xcodebuild -downloadComponent MetalToolchain)The "2+2=4" Aha Moment: During development, we encountered a severe "silent failure" where the model would successfully load and evaluate all 32 layers at high speed, but generate nothing but infinite whitespace. The model logits showed the correct shape but the wrong magnitudes.
The breakthrough arrived when we realized the embedding scale was missing. The Gemma architecture requires scaling embedding outputs by sqrt(hidden_size). For a hidden size of 2816, missing this meant every activation in the network was ~53x too small! By adding one single math operation:
h = h * MLXArray(Float(config.hiddenSize).squareRoot())
The model instantly woke up from "whispering" whitespace and successfully responded to "What is 2+2?" with a perfect "2 + 2 equals 4." — proving that the entire massive structural pipeline from Swift to Metal was working.
SwiftLM leverages the powerful foundation of the Apple MLX community and relies heavily on the open-source ecosystem. While the custom C++ implementations, Metal optimizations, and high-performance pipeline architecture were engineered natively for this engine, we owe massive thanks to the following projects and contributors for their indispensable reference materials and underlying protocols:
DraftModelRef, dual-model loading, and MambaCache checkpoint/restore for hybrid Attention+Mamba architectures.turbo-wht Walsh-Hadamard kernels and custom Metal wrapper layers.License: MIT
Swift
77.4%
Shell
12.9%
Python
9.0%
⚡ Native MLX Swift LLM inference server for Apple Silicon. OpenAI-compatible API, SSD streaming for 100B+ MoE models, TurboQuant KV cache compression, MACOS + iOS iPhone app.
Swift
772
617 commits
updated Sep 23, 2026
A blazingly fast, native Swift inference server that serves MLX models with a strict OpenAI-compatible API.
No Python runtime, no Global Interpreter Lock (GIL), no unnecessary memory copies. Just bare-metal Apple Silicon performance compiled to a single binary.
Download the latest release tarball from the Releases page.
The archive is self-contained — mlx.metallib is bundled alongside the binary.
tar -xzf SwiftLM-<version>-macos-arm64.tar.gz
./SwiftLM --model mlx-community/Qwen2.5-3B-Instruct-4bit --port 5413
The build script handles everything: submodules, cmake, Metal kernel compilation, and the Swift build.
git clone --recursive https://github.com/SharpAI/SwiftLM
cd SwiftLM
./build.sh
This will:
cmake via Homebrew (if not already installed)mlx.metallib from the Metal kernel sourcesSwiftLM binary in release modeThen start the server (models download automatically if not cached):
.build/release/SwiftLM \
--model mlx-community/gemma-4-26b-a4b-it-4bit \
--port 5413
(Add --stream-experts when running oversized MoE models to bypass macOS virtual memory swapping and stream expert layers directly from NVMe SSD.)
Benchmarked with gemma-4-26b-a4b-it-4bit running three configurations across 512 / 40K / 100K token contexts.
| Configuration | 512 tokens | 40K tokens | 100K tokens | Avg TPS* |
|---|---|---|---|---|
| Baseline | 70.8 | 34.3 | 25.8 | 36.6 |
| MTP Speculative | 71.5 (1.01×) | 38.4 (1.12×) | 29.1 (1.13×) | 40.3 |
| MTP + TurboQuant ⭐ | 72.1 (1.02×) | 65.2 (1.90×) | 62.1 (2.41×) | 66.2 |
* Time-weighted average: total_tokens / sum(60/TPS) — correct wall-clock representation vs arithmetic mean.
| Configuration | 512 tokens | 40K tokens | 100K tokens |
|---|---|---|---|
| Baseline | 0.64s | 22.85s | 63.11s |
| MTP Speculative | 0.34s | 20.45s | 55.17s |
| MTP + TurboQuant ⭐ | 0.33s | 13.17s | 33.95s |
| Configuration | 512 tokens | 40K tokens | 100K tokens |
|---|---|---|---|
| Baseline | 20.4 GB / 15.8 GB | 54.8 GB / 19.3 GB | 54.3 GB / 23.3 GB |
| MTP Speculative | 20.4 GB / 16.0 GB | 54.6 GB / 20.8 GB | 54.3 GB / 23.1 GB |
| MTP + TurboQuant ⭐ | 20.3 GB / 15.8 GB | 23.9 GB / 17.3 GB | 26.4 GB / 18.2 GB |
Key takeaways:
Run
python3 -u scripts/profiling/profile_runner.py --model gemma-4-26b-a4b-it-4bit --contexts "512,40000,100000"to reproduce on your device.
Benchmark results for gemma-4-26b-a4b-it-4bit (26B MoE, ~4B active params/token, 4-bit) on M5 Pro 64 GB.
⚠️ This is a Mixture-of-Experts (MoE) model, not a dense model. Each token activates ~4B of the 26B parameters. "Vanilla" = all experts loaded into unified RAM (no SSD streaming).
gemma-4-26b-a4b-it-4bit (4-bit MoE)Benchmarked on M5 Pro 64 GB ·
gemma-4-26b-a4b-it-4bit·./run_benchmark.shOption 13 Values shown asgeneration TPS · OS RAM used(TTFT excluded from speed measurement)
| Configuration | 512 ctx | 40K ctx | 100K ctx |
|---|---|---|---|
| Vanilla (full-RAM MoE) | 77.5 tok/s · 14.6 GB | 44.3 tok/s · 48.7 GB | 27.5 tok/s · 48.5 GB |
| Vanilla + MTP | 72.7 tok/s · 16.6 GB | 44.9 tok/s · 49.4 GB | 37.5 tok/s · 49.3 GB |
| Vanilla + TurboQuant | 77.3 tok/s · 14.7 GB | 70.1 tok/s · 18.2 GB | 66.9 tok/s · 20.7 GB |
| Vanilla + MTP + TurboQuant | 73.5 tok/s · 16.6 GB | 53.8 tok/s · 19.7 GB | 32.8 tok/s · 22.0 GB |
| SSD Stream | 10.8 tok/s · 22.2 GB | 10.4 tok/s · 24.2 GB | 9.0 tok/s · 27.6 GB |
| SSD + TurboQuant | 11.4 tok/s · 22.0 GB | 2.5 tok/s · 22.5 GB | 1.6 tok/s · 22.3 GB |
GPU Peak physical RAM (from
ioreg): Vanilla 100K peaks at 21.8 GB in-use · TurboQuant 100K stays at 17.1 GB in-use
Key takeaways (4-bit):
Vanilla + TurboQuant delivers 66.9 tok/s vs 27.5 tok/s Vanilla — a 2.43× speedup.gemma-4-26b-a4b-it-8bit (8-bit, bandwidth-bound)Benchmarked on M5 Pro 64 GB ·
gemma-4-26b-a4b-it-8bit·./run_benchmark.shOption 13
| Configuration | 512 ctx | 40K ctx | 100K ctx |
|---|---|---|---|
| Vanilla | 53.7 tok/s · 26.1 GB | 32.4 tok/s · 49.4 GB | 14.9 tok/s · 49.3 GB |
| Vanilla + MTP ⭐ | 47.1 tok/s · 28.0 GB | 38.8 tok/s (+20%) · 49.6 GB | 22.5 tok/s (+51%) · 49.6 GB |
| Vanilla + TurboQuant | 53.5 tok/s · 26.1 GB | 50.1 tok/s · 29.6 GB | 48.3 tok/s · 32.0 GB |
| Vanilla + MTP + TurboQuant | 47.4 tok/s · 28.0 GB | 31.0 tok/s · 31.1 GB | 23.3 tok/s · 33.3 GB |
Key takeaways (8-bit):
--mtp for 8-bit models at 40K+ contexts without TurboQuant. Use --turbo-kv (without MTP) when maximum throughput or memory efficiency is the priority.Run
./run_benchmark.sh→ Option 13 to reproduce these metrics on your own device.
Benchmark results for full-RAM (no SSD streaming) MoE inference on M1 Ultra. The 3.4× vanilla improvement vs. earlier builds comes from the needsMoeFlush gate in mlx-swift-lm (see SwiftLM #84) — the per-layer GPU sync barrier required for SSD streaming was firing unconditionally on the full-RAM path and flushing MLX's kernel-batching pipeline.
| Configuration | Short (~126 tok) | Medium (~400 tok) | Long (~800 tok) |
|---|---|---|---|
| Vanilla full-GPU | 61.7 tok/s | 62.3 tok/s | 62.1 tok/s |
Hardware: Apple M1 Ultra, 64 GB unified memory, macOS 26.x. Model ~20 GB on disk, ~21.6 GB resident weight + ~2.1 GB KV at runtime. Flags:
--repeat-penalty 1.1 --max-tokens 2000,temperature: 0.6, single-stream/v1/chat/completions. Vanilla baseline beforeneedsMoeFlushgate (for reference): 19.2 / 18.1 / 18.3 tok/s — see #84.
⚠️ DFlash on this model is currently unsuitable for production. DFlash uses pure greedy (
argMax) decoding regardless oftemperature, which on Qwen3.6-35B-A3B + thez-lab/Qwen3.6-35B-A3B-DFlashdraft locks into low-entropy attractors ("and and and...","**UMA** **UMA**..."). Earlier 70 tok/s DFlash numbers were degenerate output that scored high acceptance because draft and target both committed to the same locked-in token. Repetition-penalty mitigation works on some prompts but tanks acceptance on others — the proper fix is stochastic posterior sampling with rejection-based accept (Leviathan/Chen formulation), which is a DFlash architecture change tracked at z-lab/dflash#91.
Model: Thump604/DeepSeek-V4-Flash-MLX-Q3-mixed-gs128-affine
Dense/Vanilla and TurboQuant (non-SSD) configurations are skipped automatically — the 126 GB model exceeds physical RAM.
| Configuration | 512 ctx | 40K ctx |
|---|---|---|
| SSD Stream | 4.65 tok/s · 16.7 GB RAM | 0.32 tok/s · 12.5 GB RAM |
| SSD + TurboQuant | 4.78 tok/s · 16.8 GB RAM | 4.16 tok/s · 16.8 GB RAM |
| SSD + 16-Worker Prefetch | 4.43 tok/s · 16.6 GB RAM | 0.32 tok/s · 13.6 GB RAM |
Values shown as
generation speed · peak physical RAM used(sampled every 0.5s during prefill + generation). The 126 GB model streams the rest from NVMe SSD.
Key takeaways:
/v1/chat/completions, streaming, etc).--vision flag, supporting real-time base64 image parsing (e.g., Qwen2-VL, PaliGemma).--audio flag, decoding OpenAI-spec input_audio payloads with AVFoundation WAV extraction.--gpu-layers) and Wisdom Auto-Calibration for squeezing massive models into RAM.SwiftLM dynamically maps Apple MLX primitives to standard HuggingFace architectures, enabling native Metal inference across the latest frontier open-weights models.
| Family | Models | Notes |
|---|---|---|
| Gemma 4 | gemma-4-e2b, gemma-4-e4b (dense) · gemma-4-26b-a4b, gemma-4-31b (MoE) | Interleaved local + global attention; KV sharing; native quantized KV cache (issue #71 fix) |
| Gemma 3 / 3n | gemma-3-*, gemma-3n-* | Google Gemma 3 and nano variants |
| Gemma / Gemma 2 | gemma-*, gemma-2-* | Original Gemma family |
| Qwen 3.5 | Qwen3.5-7B, Qwen3.5-27B, Qwen3.5-122B-A10B, Qwen3.5-397B-A22B | Dense + MoE; SSD streaming at 10× for 122B/397B |
| Qwen 3 | Qwen3-* (dense + MoE) | Sliding window + hybrid attention |
| Qwen 2.5 | Qwen2.5-7B, Qwen2.5-14B, Qwen2.5-72B | Robust RoPE scaling |
| Qwen 2 | Qwen2-* | Linear RoPE variants |
| Phi 4 / PhiMoE | phi-4-mlx, Phi-3.5-MoE | Microsoft Phi family incl. MoE |
| Phi 3 / Phi | Phi-3, Phi-3.5-mini | 128k context via chunked prefill |
| Mistral / Mixtral | Mistral-7B, Mistral-4, Mixtral-* | GQA + sliding window variants |
| Llama / Llama 3 | Llama-3.1-*, Llama-3.2-*, Llama-3.3-* | YaRN + dynamic NTK RoPE scaling |
| GLM 4 | GLM-4-* | THUDM GLM-4 dense + MoE-Lite variants |
| DeepSeek V3 | DeepSeek-V3-* | MLA attention architecture |
| Falcon H1 | Falcon-H1-* | Falcon hybrid SSM+attention |
| LFM 2 | LFM2-*, LFM2-MoE-* | Liquid AI dense + MoE |
| OLMo 2 / OLMo 3 / OLMoE | OLMo-2-*, OLMo-3-* | AllenAI open language models |
| Granite / GraniteMoE | Granite-*, GraniteMoE-Hybrid-* | IBM Granite hybrid Mamba+attention |
| SmolLM 3 | SmolLM3-* | HuggingFace compact LM |
| MiniCPM | MiniCPM-* | Lightweight efficient LM |
| InternLM 2 | InternLM2-* | Shanghai AI Lab series |
| Cohere / Command-R | Command-R-*, c4ai-* | Cohere retrieval-tuned models |
| Jamba | Jamba-v0.1 | AI21 hybrid Mamba+attention |
| Exaone 4 | EXAONE-4.0-* | LG AI Research |
| MiMo / MiMo V2 | MiMo-7B-* | Xiaomi reasoning model |
| Ernie 4.5 | ERNIE-4.5-* | Baidu ERNIE series |
| Baichuan M1 | Baichuan-M1-* | Baichuan multimodal base |
| Bailing MoE | Ling-* | Bailing/Ling MoE family |
| NemotronH | Nemotron-H-* | NVIDIA Nemotron hybrid |
| Starcoder 2 | starcoder2-* | Code generation |
| OpenELM | OpenELM-* | Apple on-device efficient LM |
| Apertus / AfMoE | Apertus-* | Sparse MoE research models |
| BitNet | bitnet-* | 1-bit weight quantization |
| MiniMax | MiniMax-Text-* | Lightning attention architecture |
| Olmo3 | Olmo3-* | AllenAI Olmo3 series |
Run with --vision flag.
| Family | Models | Notes |
|---|---|---|
| Gemma 4 | gemma-4-* (VLM mode) | Native image tower via MLXVLM |
| Gemma 3 | gemma-3-* (VLM mode) | PaLiGemma-style image projection |
| Qwen3-VL / Qwen3.5-VL | Qwen3-VL-*, Qwen3.5-VL-* | Dynamic resolution with native RoPE |
| Qwen2-VL / Qwen2.5-VL | Qwen2-VL-2B/7B, Qwen2.5-VL-* | Real-time positional bounding + Metal image scaling |
| LFM2-VL | LFM2-VL-1.6B | Liquid AI multimodal |
| Pixtral | pixtral-12b | Mistral vision model |
| PaliGemma | paligemma-* | Google vision-language |
| Idefics 3 | Idefics3-* | HuggingFace multimodal |
| Mistral 3 | Mistral-Small-3.1-* | Mistral vision variant |
| FastVLM | FastVLM-* | Apple on-device VLM |
| SmolVLM 2 | SmolVLM2-* | HuggingFace compact VLM |
| GLM OCR | glm-4v-* | THUDM vision+OCR |
| QwenVL | Qwen-VL-* | Original Qwen VL |
Run with --audio flag. Only gemma-4-e4b variants include an audio tower.
| Family | Models | Notes |
|---|---|---|
| Gemma 4 Omni | gemma-4-e4b-it-4bit, gemma-4-e4b-it-8bit | Audio-in via vDSP STFT → Mel spectrogram (16kHz, 128 bins); text-out |
A native iPhone & iPad companion app that downloads MLX models directly from HuggingFace and runs inference on-device via MLX Swift.
mlx-community model by name📱 Running live on iPhone 13 Pro (6 GB) — no Python, no server, no GIL. Pure on-device MLX inference via Metal GPU.
cd SwiftBuddy
python3 generate_xcodeproj.py # Generates SwiftBuddy.xcodeproj
open SwiftBuddy.xcodeproj
Then in Xcode:
Note for contributors: The
.xcodeprojis git-ignored (it contains your personal Team ID). Rungenerate_xcodeproj.pyafter cloning to regenerate it locally. Your Team ID is never committed.
SwiftLM implements a hybrid V2+V3 TurboQuant architecture for on-the-fly KV cache compression. At roughly ~3.6 bits per coordinate overall, the KV cache is compressed ~3.5× vs FP16 with near-zero accuracy loss.
Recent reproductions of the TurboQuant algorithm (e.g., turboquant-mlx) revealed two distinct paths:
We built the "Holy Grail" hybrid: We ported the V3 non-linear Lloyd-Max codebooks directly into the native C++ encoding path, and process the dequantization natively in fused Metal (bggml-metal) shaders. This achieves V3 quality at V2 speeds, completely detached from Python overhead.
K-Cache (3-bit PolarQuant + 1-bit QJL) = 4.25 bits/dim
x̂ = x / ‖x‖V-Cache (3-bit PolarQuant) = 3.125 bits/dim Because the V-cache matrix is not used for inner-product attention scoring, the QJL error correction provides no benefit. We cleanly disable QJL for the V-cache, extracting an additional 25% memory savings without sacrificing quality.
Reference implementations: turboquant-mlx | turboquant_plus | Paper: TurboQuant, Google 2504.19874
SwiftLM implements a rewritten SSD expert streaming pipeline (engineered by Eric Lake) that achieves 10x generation speedup for massive Mixture of Experts (MoE) models running on memory-constrained Apple Silicon. This enables running models like Qwen3.5-122B (69.6 GB) and Qwen3.5-397B (209 GB) on a 64 GB Mac by streaming expert weights from NVMe SSD.
| Configuration | tok/s | vs. Original | Notes |
|---|---|---|---|
Original --stream-experts | 0.58 | baseline | Sequential pread, 1 NVMe queue |
| This PR (top-k=8, full quality) | 4.95 | 8.5× | All 8 experts evaluated |
| This PR (top-k=6, default) | 5.20 | 9.0× | Recommended default |
| This PR (top-k=4, speed mode) | 5.91 | 10.2× | Best quality/speed tradeoff |
| This PR (top-k=2, turbo mode) | 6.52 | 11.2× | Still coherent output |
Memory stable at ~10.6 GB resident, no swap activity. Tested over 200-token generation runs.
A novel aspect of this architecture is the dual-model speculative decoding pattern: a small draft model (e.g. Qwen3.5-9B at 73 tok/s) runs entirely in RAM while the large MoE model (e.g. 122B) streams experts from SSD. The draft model generates candidate tokens at high speed, and the main model verifies them in bulk — dramatically reducing the number of SSD-bound generation rounds needed.
Performance note: Combining
--stream-expertswith--draft-modelrequires care. The verify pass sends N+1 tokens simultaneously, each routing to different experts — SSD I/O scales with the union of all positions' expert selections. At the default--num-draft-tokens 4this creates a 5× I/O fan-out that regresses throughput below solo SSD streaming.Auto-cap strategy (Issue #72 fix): SwiftLM automatically caps
--num-draft-tokensto 1 when both flags are active. With 1 draft token the verify pass covers only 2 positions (2× fan-out). If the draft model's acceptance rate is ≥ 50% — typical for same-family models — the net throughput is still positive despite the 2× I/O overhead. A startup advisory is printed when the cap fires.For maximum throughput: use
--stream-expertsalone (no draft model).
eval() calls down to ~48 per token by orchestrating gate/up/down projections together in SwitchGLU.DispatchQueue.concurrentPerform, saturating the NVMe controller's queue depth (8 experts × 3 projections = 24 parallel reads).SwitchGLU layer and reused across tokens, eliminating per-token allocation overhead.SWIFTLM_TOP_K environment variable reduces the number of active experts per token at runtime without model recompilation — trading marginal quality for significant speed gains.| Finding | Detail |
|---|---|
| GPU compute is the bottleneck | At steady state, GPU compute is ~190ms of ~200ms per-token time. The OS page cache serves ~90% of expert reads from RAM. |
| Don't cache experts in application memory | An LRU expert cache stole from the OS page cache and regressed performance (4.84 → 4.01 tok/s). Let the kernel manage it. |
| MambaCache requires checkpoint rollback | Unlike attention KV caches (trim = decrement offset), Mamba's recurrent state integrates all history and cannot be partially undone. We implemented checkpoint()/restore() for speculative decoding on hybrid Attention+Mamba architectures (Qwen3.5). |
# Standard SSD streaming (recommended, top-k=6):
SWIFTLM_TOP_K=6 SwiftLM --port 8002 \
--model <path>/Qwen3.5-122B-A10B-4bit --stream-experts
# Speed mode (top-k=4):
SWIFTLM_TOP_K=4 SwiftLM --port 8002 \
--model <path>/Qwen3.5-122B-A10B-4bit --stream-experts
# With speculative decoding (in-RAM models only — both models fit in RAM):
SwiftLM --port 8002 \
--model <path>/Qwen3.5-27B-4bit \
--draft-model <path>/Qwen3.5-9B-4bit \
--num-draft-tokens 4
# With SSD streaming + draft model (auto-cap mode):
# SwiftLM automatically caps --num-draft-tokens to 1 to minimise the
# verify-pass I/O fan-out. Net positive if draft acceptance rate ≥ 50%.
SwiftLM --port 8002 \
--model <path>/Qwen3.5-122B-A10B-4bit \
--stream-experts \
--draft-model <path>/Qwen3.5-9B-4bit
# ↑ num-draft-tokens is auto-capped to 1 at startup
SwiftLM supports two forms of Speculative Decoding to accelerate in-RAM inference:
Load a small draft model alongside a large main model. The draft model generates candidate tokens at high speed, and the main model verifies them in bulk.
Requires passing both --model and --draft-model.
For models trained with native MTP heads (e.g., the Qwen3 family), SwiftLM automatically leverages the hidden MTP layers to draft future tokens within a single forward pass, completely eliminating the need to load a separate draft model.
Algorithmic Parity (Leviathan et al.)
SwiftLM implements mathematically rigorous probabilistic rejection sampling (as defined by Leviathan et al.) in its MTPTokenIterator. This ensures exact mathematical output parity with the target model's true distribution, even at non-zero temperatures, properly evaluating $P_{target} / P_{draft}$ and resampling the corrected distribution upon rejection.
MTP is strictly a Compute-Bound optimization.
We successfully verified algorithmic parity and a 15%+ TPS speedup on the dense Qwen/Qwen3.6-27B model, which fits completely in 64GB VRAM.
However, running MTP on massive MoE models (like the Qwen3.6-35B-A3B) on a 64GB Mac requires --stream-experts to fetch MoE weights from the NVMe SSD. Because MTP evaluates multiple draft tokens in parallel, the verify pass forces a massive I/O fan-out, attempting to fetch up to 3x as many unique experts from the SSD simultaneously.
This saturates the NVMe bandwidth, causing the GPU to stall and completely neutralizing the MTP speedup. If you are running a 64GB Mac, MTP on 35B+ MoE models will be slower than the baseline.
(Community Help Wanted: We are actively looking for optimizations to batch expert pre-fetching during MTP verification to make this viable on 64GB Unified Memory limits!)
To achieve the extreme memory efficiency and speeds seen in SSD Expert Streaming and Speculative Decoding, SwiftLM relies on custom C++ primitives that bypass standard unified memory limits.
[!NOTE] We maintain custom forks (
SharpAI/mlxandSharpAI/mlx-c) to support out-of-core memory-mapped execution, streaming tensor blocks directly from the SSD (NVMe) to the GPU via custom Metal kernels (ssd_streamer.mmandfence.air). Officialml-explorerepositories do not yet support this out-of-the-box.
For a detailed breakdown on repository architecture, upstream synchronization, our specific custom patches, and the specific indications for when we can safely revert to Apple's native upstream, read the full documentation: 👉 Upstream MLX Synchronization & SSD Streaming Maintenance
Run our automated benchmark suites via the interactive script:
./run_benchmark.sh
The script provides an interactive menu to select any model and run one of two automated testing suites:
Tests generation speed (TPS) and granular Apple Metal GPU memory allocation across extreme context lengths (e.g., 512, 40000, 100000 tokens).
docs/profiling/profiling_results_<hostname>.md.Verifies the stability of the engine's KV prompt cache when interleaving long contexts with sliding window attention bounds.
5537t → 18t → 5537t → Big Full Cache Hit).SIGTRAP errors.Tested by rendering exactly 20 tokens under standard conversational evaluation (--prefill-size 512) to capture precise Token Generation (TPS) and Apple Metal memory footprint limits:
| Model | Time To First Token (s) | Generation Speed (tok/s) | Peak GPU Memory (GB) |
|---|---|---|---|
gemma-4-e2b-it-4bit | 0.08s | 116.27 tok/s | 1.37 GB |
gemma-4-e4b-it-8bit | 0.33s | 48.21 tok/s | 7.64 GB |
gemma-4-26b-a4b-it-4bit | 0.14s | 85.49 tok/s | 13.46 GB |
gemma-4-31b-it-4bit | 0.55s | 14.82 tok/s | 16.83 GB |
To run the automated suite on your machine for these models, execute:
python3 tests/run_4models_benchmark.py
🧠 How it works: SwiftLM implements Chunked Prefill (controlled via
--prefill-size, defaulting to 512). This is functionally equivalent tollama.cpp's--batch-sizeparameter and mirrors themlx-lmPython library's reference implementation approach to preventing $O(N^2)$ Unified Memory over-allocation during massive sequence parsing.
⚠️ Quantization Disclaimer: While heavier quantization shrinks the required memory footprint, 4-bit quantization remains the strict production standard for MoE models. Our metrics indicated that aggressive 2-bit quantization heavily destabilizes JSON grammars—routinely producing broken keys like
\name\instead of"name"—which systematically breaks OpenAI-compatible tool calling.
| Endpoint | Method | Description |
|---|---|---|
/health | GET | Server health + loaded model capabilities |
/v1/models | GET | List available models |
/v1/chat/completions | POST | Chat completions (LLM and VLM support, multi-turn, system prompts) |
Drop-in compatible with standard OpenAI HTTP consumers:
curl http://localhost:5413/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-4-26b-a4b-it-4bit",
"stream": true,
"messages": [
{"role": "system", "content": "You are Aegis-AI, a local home security agent. Output strictly in JSON format."},
{"role": "user", "content": "Clip 1: Delivery person drops package at 14:02. Clip 2: Delivery person walks away down driveway at 14:03. Do these clips represent the same security event? Output a JSON object with a `duplicate` boolean and a `reason` string."}
]
}'
To run a vision model (e.g., mlx-community/Qwen2-VL-2B-Instruct-4bit), launch SwiftLM with the --vision flag:
./.build/release/SwiftLM --model mlx-community/Qwen2-VL-2B-Instruct-4bit --vision
You can then pass standard OpenAI base64 encoded images directly. SwiftLM handles hardware spatial-mapping natively via Metal:
curl http://localhost:5413/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "qwen2-vl",
"messages": [
{
"role": "user",
"content": [
{"type": "text", "text": "Describe the contents of this image."},
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."}}
]
}
]
}'
| Option | Default | Description |
|---|---|---|
--model | (required) | HuggingFace model ID or local path |
--port | 5413 | Port to listen on |
--host | 127.0.0.1 | Host to bind |
--vision | false | Enable VLM (vision-language model) mode for image inputs |
--audio | false | Enable ALM (audio-language model) mode for audio inputs |
--max-tokens | 2048 | Max tokens limit per generation |
--prefill-size | 512 | Prompt prefill chunk size (micro-batching for long contexts) |
--top-p | 1.0 | Default top-p nucleus sampling (overridable per-request) |
--top-k | 50 | Default top-k sampling (0 disables, overridable per-request) |
--min-p | 0.0 | Default min-p sampling threshold relative to the highest probability token (0 disables) |
--gpu-layers | model_default | Restrict the amount of layers allocated to GPU hardware |
--stream-experts | false | Enable SSD expert streaming for MoE models (10x speedup) |
--turbo-kv | false | Enable TurboQuant 3-bit KV cache compression (activates after 2048 tokens, server-wide) |
--draft-model | (none) | Draft model path/ID for speculative decoding. When used with --stream-experts, --num-draft-tokens is auto-capped to 1 to minimise SSD I/O fan-out (see performance note above). |
--num-draft-tokens | 4 | Tokens per speculation round. Auto-capped to 1 when combined with --stream-experts. |
--dflash | false | Enable DFlash block-diffusion speculative decoding. Requires a compatible DFlash draft model |
--dflash-block-size | (auto) | Number of tokens per DFlash draft block. Defaults to draft model config |
In addition to the standard OpenAI fields (temperature, top_p, max_tokens, etc.), SwiftLM accepts the following SwiftLM-specific fields on POST /v1/chat/completions:
| Field | Type | Description |
|---|---|---|
kv_bits | int (4 or 8) | Enable MLX-native quantized KV cache for this request. Uses QuantizedKVCache (standard group quantization) instead of KVCacheSimple. Separate from --turbo-kv. Reduces KV memory ~2–4× at mild quality cost. |
enable_thinking | bool | Force-enable or disable chain-of-thought thinking blocks for Gemma-4 / Qwen3. |
kv_group_size | int | Group size for kv_bits quantization (default: 64). |
top_k | int | Per-request top-k sampling override (0 = disabled). |
min_p | float | Per-request min-p sampling threshold (0 = disabled). |
repetition_penalty | float | Token repetition penalty (e.g. 1.15). |
kv_bits vs --turbo-kv — What's the difference?kv_bits (per-request) | --turbo-kv (server flag) | |
|---|---|---|
| Scope | Per-request, sent in JSON body | Server-wide, set at startup |
| Algorithm | MLX-native group quantization (4-bit / 8-bit) | Custom 3-bit PolarQuant + QJL Walsh-Hadamard |
| Activation | From token 0 | After 2048 tokens |
| Memory savings | ~2–4× vs FP16 | ~3.5× vs FP16 |
| Use case | Targeted memory reduction per conversation | Extreme long-context (100K+) compression |
curl http://localhost:5413/v1/chat/completions \\
-H "Content-Type: application/json" \\
-d '{
"model": "gemma-4-26b-a4b-it-4bit",
"kv_bits": 4,
"messages": [
{"role": "user", "content": "Summarize the history of computing in 3 sentences."}
]
}'
xcodebuild -downloadComponent MetalToolchain)The "2+2=4" Aha Moment: During development, we encountered a severe "silent failure" where the model would successfully load and evaluate all 32 layers at high speed, but generate nothing but infinite whitespace. The model logits showed the correct shape but the wrong magnitudes.
The breakthrough arrived when we realized the embedding scale was missing. The Gemma architecture requires scaling embedding outputs by sqrt(hidden_size). For a hidden size of 2816, missing this meant every activation in the network was ~53x too small! By adding one single math operation:
h = h * MLXArray(Float(config.hiddenSize).squareRoot())
The model instantly woke up from "whispering" whitespace and successfully responded to "What is 2+2?" with a perfect "2 + 2 equals 4." — proving that the entire massive structural pipeline from Swift to Metal was working.
SwiftLM leverages the powerful foundation of the Apple MLX community and relies heavily on the open-source ecosystem. While the custom C++ implementations, Metal optimizations, and high-performance pipeline architecture were engineered natively for this engine, we owe massive thanks to the following projects and contributors for their indispensable reference materials and underlying protocols:
DraftModelRef, dual-model loading, and MambaCache checkpoint/restore for hybrid Attention+Mamba architectures.turbo-wht Walsh-Hadamard kernels and custom Metal wrapper layers.License: MIT
Swift
77.4%
Shell
12.9%
Python
9.0%