Skip to content

Repository files navigation

⚡️ SwiftLM

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.

SwiftLM Mac macOS demo


SwiftBuddy iOS demo


🏁 Getting Started

Fastest: Download Pre-built Binary

Download the latest release tarball from the Releases page. The archive is self-containedmlx.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

Build from Source

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:

  1. Initialize git submodules
  2. Install cmake via Homebrew (if not already installed)
  3. Compile mlx.metallib from the Metal kernel sources
  4. Build the SwiftLM binary in release mode

Then 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.)

📊 Performance: MTP Speculative Decoding — Gemma 4-26B (MacBook Pro M5 Pro 64 GB)

Benchmarked with gemma-4-26b-a4b-it-4bit running three configurations across 512 / 40K / 100K token contexts.

Generation Speed (tok/s) — higher is better

Configuration512 tokens40K tokens100K tokensAvg TPS*
Baseline70.834.325.836.6
MTP Speculative71.5 (1.01×)38.4 (1.12×)29.1 (1.13×)40.3
MTP + TurboQuant72.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.

Time to First Token (seconds) — lower is better

Configuration512 tokens40K tokens100K tokens
Baseline0.64s22.85s63.11s
MTP Speculative0.34s20.45s55.17s
MTP + TurboQuant0.33s13.17s33.95s

GPU Memory (allocated / peak physical RAM)

Configuration512 tokens40K tokens100K tokens
Baseline20.4 GB / 15.8 GB54.8 GB / 19.3 GB54.3 GB / 23.3 GB
MTP Speculative20.4 GB / 16.0 GB54.6 GB / 20.8 GB54.3 GB / 23.1 GB
MTP + TurboQuant20.3 GB / 15.8 GB23.9 GB / 17.3 GB26.4 GB / 18.2 GB

Key takeaways:

  • 🚀 1.81× avg throughput — MTP + TurboQuant delivers 66.2 tok/s time-weighted vs 36.6 tok/s baseline
  • 🏎️ Nearly 2× faster TTFT at 100K context — 33.95s vs 63.11s baseline (46% reduction)
  • 💾 Massive memory savings at long context — GPU allocation drops from 54.8 GB → 23.9 GB at 40K tokens (TurboQuant KV compression)
  • 🔬 MTP alone is free — 1.10× time-weighted TPS and lower TTFT with zero additional memory overhead

Run python3 -u scripts/profiling/profile_runner.py --model gemma-4-26b-a4b-it-4bit --contexts "512,40000,100000" to reproduce on your device.

📊 Performance: Gemma 4-26B on Apple Silicon

Benchmark results for gemma-4-26b-a4b-it-4bit (26B MoE, 4-bit) on M5 Pro 64 GB.

Headline Numbers

Configuration512 ctx40K ctx100K ctx
Dense/Vanilla33.0 tok/s · 23.4 GB20.2 tok/s · 57.0 GB15.7 tok/s · 56.7 GB
SSD Stream10.8 tok/s · 22.2 GB10.4 tok/s · 24.2 GB9.0 tok/s · 27.6 GB
TurboQuant29.0 tok/s · 23.7 GB3.9 tok/s · 39.4 GB3.9 tok/s · 57.3 GB
SSD + TurboQuant11.4 tok/s · 22.0 GB2.5 tok/s · 22.5 GB1.6 tok/s · 22.3 GB

Values shown as generation speed · GPU memory allocated

Key takeaways:

  • 🚀 Speed Doubled: The newer MLX backend modifications have more than doubled raw SSD Stream inference speed (from 4.5 -> 10.8 tok/s) while maintaining streaming stability.
  • 📄 40K context on 24 GB MacBook Pro: SSD + TurboQuant effortlessly fits a 26B model in 22.5 GB of memory footprint.
  • 📚 100K context on 24 GB MacBook Pro: Due to hyper-efficient 3-bit KV compression paired with SSD weight streaming, you can process 100,000 tokens of context on a 24 GB machine — only utilizing 22.3 GB total. (Previously required a 64 GB Mac Studio).

Run ./run_benchmark.sh to generate these metrics on your own device. (See Benchmarks & Testing below).

Qwen3.6-35B-A3B-UD-MLX-4bit (Full-RAM) — M1 Ultra 64 GB

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.

ConfigurationShort (~126 tok)Medium (~400 tok)Long (~800 tok)
Vanilla full-GPU61.7 tok/s62.3 tok/s62.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 of temperature, which on Qwen3.6-35B-A3B + the z-lab/Qwen3.6-35B-A3B-DFlash draft 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.

DeepSeek-V4-Flash (126 GB, Q3-mixed-gs128-affine) — M5 Pro 64 GB

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.

Configuration512 ctx40K ctx
SSD Stream4.65 tok/s · 16.7 GB RAM0.32 tok/s · 12.5 GB RAM
SSD + TurboQuant4.78 tok/s · 16.8 GB RAM4.16 tok/s · 16.8 GB RAM
SSD + 16-Worker Prefetch4.43 tok/s · 16.6 GB RAM0.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:

  • 🏆 SSD + TurboQuant dominates at long context — 4.16 tok/s at 40K vs 0.32 tok/s for plain SSD Stream (13× faster). TurboQuant compresses the KV cache so far fewer layers need to stream from SSD per token.
  • At 512-token context all configurations perform similarly (~4.4–4.8 tok/s); TurboQuant's advantage is KV-cache compression at long context.
  • Peak physical RAM stays ≤ 17 GB across all configurations — the 126 GB model streams the rest from NVMe SSD.

🚀 Features

  • 🍎 100% Native Apple Silicon: Powered natively by Metal and Swift.
  • 🔌 OpenAI-compatible: Drop-in replacement for OpenAI SDKs (/v1/chat/completions, streaming, etc).
  • 🧠 Smart Model Routing: Loads HuggingFace format models directly, with native Safetensors parsing.
  • 👁️ Vision-Language Models (VLM): Native multimodal vision processing natively on Metal via the --vision flag, supporting real-time base64 image parsing (e.g., Qwen2-VL, PaliGemma).
  • 🎧 Audio-Language Models (ALM): High-performance audio ingestion via the --audio flag, decoding OpenAI-spec input_audio payloads with AVFoundation WAV extraction.
  • ⚡️ TurboQuantization Integrated: Custom low-level MLX Metal primitives that apply extremely fast quantization for KV caching out-of-the-box.
  • 💾 SSD Expert Streaming (10x): High-performance NVMe streaming that loads Mixture of Experts (MoE) layers directly from SSD to GPU — engineered by @ericjlake, achieving 10x speedup (0.58 → 5.91 tok/s) on 122B+ models with only ~10 GB resident memory. Uses cross-projection batching, concurrent pread (QD=24), asyncEval pipeline, and runtime top-k expert selection.
  • 🔮 Speculative Decoding: Load a small draft model (e.g. 9B) alongside a large main model to generate candidate tokens and verify in bulk — accelerating in-RAM inference.
  • 🎛️ Granular Memory Control: Integrated Layer Partitioning (--gpu-layers) and Wisdom Auto-Calibration for squeezing massive models into RAM.

📡 Supported Models & Methodologies

SwiftLM dynamically maps Apple MLX primitives to standard HuggingFace architectures, enabling native Metal inference across the latest frontier open-weights models.

💬 Text (LLMs)

FamilyModelsNotes
Gemma 4gemma-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 / 3ngemma-3-*, gemma-3n-*Google Gemma 3 and nano variants
Gemma / Gemma 2gemma-*, gemma-2-*Original Gemma family
Qwen 3.5Qwen3.5-7B, Qwen3.5-27B, Qwen3.5-122B-A10B, Qwen3.5-397B-A22BDense + MoE; SSD streaming at 10× for 122B/397B
Qwen 3Qwen3-* (dense + MoE)Sliding window + hybrid attention
Qwen 2.5Qwen2.5-7B, Qwen2.5-14B, Qwen2.5-72BRobust RoPE scaling
Qwen 2Qwen2-*Linear RoPE variants
Phi 4 / PhiMoEphi-4-mlx, Phi-3.5-MoEMicrosoft Phi family incl. MoE
Phi 3 / PhiPhi-3, Phi-3.5-mini128k context via chunked prefill
Mistral / MixtralMistral-7B, Mistral-4, Mixtral-*GQA + sliding window variants
Llama / Llama 3Llama-3.1-*, Llama-3.2-*, Llama-3.3-*YaRN + dynamic NTK RoPE scaling
GLM 4GLM-4-*THUDM GLM-4 dense + MoE-Lite variants
DeepSeek V3DeepSeek-V3-*MLA attention architecture
Falcon H1Falcon-H1-*Falcon hybrid SSM+attention
LFM 2LFM2-*, LFM2-MoE-*Liquid AI dense + MoE
OLMo 2 / OLMo 3 / OLMoEOLMo-2-*, OLMo-3-*AllenAI open language models
Granite / GraniteMoEGranite-*, GraniteMoE-Hybrid-*IBM Granite hybrid Mamba+attention
SmolLM 3SmolLM3-*HuggingFace compact LM
MiniCPMMiniCPM-*Lightweight efficient LM
InternLM 2InternLM2-*Shanghai AI Lab series
Cohere / Command-RCommand-R-*, c4ai-*Cohere retrieval-tuned models
JambaJamba-v0.1AI21 hybrid Mamba+attention
Exaone 4EXAONE-4.0-*LG AI Research
MiMo / MiMo V2MiMo-7B-*Xiaomi reasoning model
Ernie 4.5ERNIE-4.5-*Baidu ERNIE series
Baichuan M1Baichuan-M1-*Baichuan multimodal base
Bailing MoELing-*Bailing/Ling MoE family
NemotronHNemotron-H-*NVIDIA Nemotron hybrid
Starcoder 2starcoder2-*Code generation
OpenELMOpenELM-*Apple on-device efficient LM
Apertus / AfMoEApertus-*Sparse MoE research models
BitNetbitnet-*1-bit weight quantization
MiniMaxMiniMax-Text-*Lightning attention architecture
Olmo3Olmo3-*AllenAI Olmo3 series

👁️ Vision (VLMs)

Run with --vision flag.

FamilyModelsNotes
Gemma 4gemma-4-* (VLM mode)Native image tower via MLXVLM
Gemma 3gemma-3-* (VLM mode)PaLiGemma-style image projection
Qwen3-VL / Qwen3.5-VLQwen3-VL-*, Qwen3.5-VL-*Dynamic resolution with native RoPE
Qwen2-VL / Qwen2.5-VLQwen2-VL-2B/7B, Qwen2.5-VL-*Real-time positional bounding + Metal image scaling
LFM2-VLLFM2-VL-1.6BLiquid AI multimodal
Pixtralpixtral-12bMistral vision model
PaliGemmapaligemma-*Google vision-language
Idefics 3Idefics3-*HuggingFace multimodal
Mistral 3Mistral-Small-3.1-*Mistral vision variant
FastVLMFastVLM-*Apple on-device VLM
SmolVLM 2SmolVLM2-*HuggingFace compact VLM
GLM OCRglm-4v-*THUDM vision+OCR
QwenVLQwen-VL-*Original Qwen VL

🎧 Audio (ALMs)

Run with --audio flag. Only gemma-4-e4b variants include an audio tower.

FamilyModelsNotes
Gemma 4 Omnigemma-4-e4b-it-4bit, gemma-4-e4b-it-8bitAudio-in via vDSP STFT → Mel spectrogram (16kHz, 128 bins); text-out

📱 SwiftBuddy — iOS App

A native iPhone & iPad companion app that downloads MLX models directly from HuggingFace and runs inference on-device via MLX Swift.

Features

  • Tab UI: Chat · Models · Settings
  • Live download progress with speed indicator and circular progress ring
  • Model catalog: Qwen3, Phi-3.5, Mistral, Llama — with on-device RAM fit indicators
  • HuggingFace search — find any mlx-community model by name
  • Context-aware empty states — downloading ring, loading spinner, idle prompt
  • iOS lifecycle hardened — model unload only fires on true background (not notification banners); 30-second grace period on app-switch

📱 Running live on iPhone 13 Pro (6 GB) — no Python, no server, no GIL. Pure on-device MLX inference via Metal GPU.

Build & Run (iOS)

cd SwiftBuddy
python3 generate_xcodeproj.py # Generates SwiftBuddy.xcodeproj
open SwiftBuddy.xcodeproj

Then in Xcode:

  1. Select the SwiftBuddy target → Signing & Capabilities
  2. Set your Team (your Apple Developer account)
  3. Select your iPhone as the run destination
  4. ⌘R to build and run

Note for contributors: The .xcodeproj is git-ignored (it contains your personal Team ID). Run generate_xcodeproj.py after cloning to regenerate it locally. Your Team ID is never committed.


⚡️ TurboQuantization: KV Cache Compression

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.

By combining V2 Speed with V3 Quality:

Recent reproductions of the TurboQuant algorithm (e.g., turboquant-mlx) revealed two distinct paths:

  1. V2 (Hardware-Accelerated): Fast, but uses linear affine quantization which degrades quality at 3-bit.
  2. V3 (Paper-Correct): Excellent quality using non-linear Lloyd-Max codebooks, but painfully slow due to software dequantization.

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.

The Algorithm:

K-Cache (3-bit PolarQuant + 1-bit QJL) = 4.25 bits/dim

  1. Extract L2 norm and normalize: x̂ = x / ‖x‖
  2. Apply Fast Walsh-Hadamard Transform (WHT) rotation to distribute outliers evenly.
  3. Quantize each coordinate using 3-bit non-linear Lloyd-Max centroids.
  4. Compute the residual error between the original vector and the quantized approximation.
  5. Project the residual via a random Johnson-Lindenstrauss (QJL) matrix and store the 1-bit signs. (Why QJL? QJL acts as an additional regularizer that prevents centroid resolution loss from degrading the attention dot-product.)

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


💾 SSD Expert Streaming: 10x MoE Speedup

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.

Benchmark Results (M1 Ultra 64GB, Qwen3.5-122B-A10B-4bit)

Configurationtok/svs. OriginalNotes
Original --stream-experts0.58baselineSequential pread, 1 NVMe queue
This PR (top-k=8, full quality)4.958.5×All 8 experts evaluated
This PR (top-k=6, default)5.209.0×Recommended default
This PR (top-k=4, speed mode)5.9110.2×Best quality/speed tradeoff
This PR (top-k=2, turbo mode)6.5211.2×Still coherent output

Memory stable at ~10.6 GB resident, no swap activity. Tested over 200-token generation runs.

The Approach: Small Model Helps Large Model

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-experts with --draft-model requires 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 4 this 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-tokens to 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-experts alone (no draft model).

Optimization Techniques

  1. Cross-Projection Batching: Collapses ~1,400 per-expert eval() calls down to ~48 per token by orchestrating gate/up/down projections together in SwitchGLU.
  2. Concurrent NVMe pread (QD=24): Replaces sequential pread with DispatchQueue.concurrentPerform, saturating the NVMe controller's queue depth (8 experts × 3 projections = 24 parallel reads).
  3. AsyncEval Pipeline with Speculative Pread: Overlaps GPU compute with SSD I/O — uses previous-token routing to speculatively pre-load experts for the next token during the GPU async window (~70% hit rate). Only missed experts (~30%) require on-demand pread after routing sync.
  4. Persistent Metal Buffers: Expert weight buffers are allocated once per SwitchGLU layer and reused across tokens, eliminating per-token allocation overhead.
  5. Runtime Top-K Expert Selection: The 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.

Key Engineering Findings

FindingDetail
GPU compute is the bottleneckAt 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 memoryAn 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 rollbackUnlike 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).

Usage

# 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

🔮 Speculative Decoding & Multi-Token Prediction (MTP)

SwiftLM supports two forms of Speculative Decoding to accelerate in-RAM inference:

1. Traditional Dual-Model Speculative Decoding

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.

2. Multi-Token Prediction (MTP) Native Decoding

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.

⚠️ Hardware Limitations & SSD Streaming (Help Wanted!)

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!)


🔀 Why We Forked Apple MLX

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/mlx and SharpAI/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.mm and fence.air). Official ml-explore repositories 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


💻 Benchmarks & Testing

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:

Test 1: Automated Context & Memory Profile (TPS & RAM matrix)

Tests generation speed (TPS) and granular Apple Metal GPU memory allocation across extreme context lengths (e.g., 512, 40000, 100000 tokens).

  • Iterates over 4 configurations: Vanilla, SSD Streaming, TurboQuant, and SSD + TurboQuant.
  • Generates a rich ANSI console visualization with bar charts and a configuration scoreboard.
  • Saves the complete results matrix to docs/profiling/profiling_results_<hostname>.md.

Test 2: Prompt Cache & Sliding Window Regression Test

Verifies the stability of the engine's KV prompt cache when interleaving long contexts with sliding window attention bounds.

  • Automatically spins up an isolated background inference server instance.
  • Generates a 5,000+ token mock JSON payload.
  • Fires an extreme alternating sequence of 4 concurrent requests (5537t18t5537tBig Full Cache Hit).
  • Confirms the memory bounds remain stable without throwing $O(N^2)$ OS memory warnings, $OOM$ exceptions, or SIGTRAP errors.

Throughput & Inference Memory Profile

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:

ModelTime To First Token (s)Generation Speed (tok/s)Peak GPU Memory (GB)
gemma-4-e2b-it-4bit0.08s116.27 tok/s1.37 GB
gemma-4-e4b-it-8bit0.33s48.21 tok/s7.64 GB
gemma-4-26b-a4b-it-4bit0.14s85.49 tok/s13.46 GB
gemma-4-31b-it-4bit0.55s14.82 tok/s16.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 to llama.cpp's --batch-size parameter and mirrors the mlx-lm Python 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.


📡 API Endpoints

EndpointMethodDescription
/healthGETServer health + loaded model capabilities
/v1/modelsGETList available models
/v1/chat/completionsPOSTChat completions (LLM and VLM support, multi-turn, system prompts)

💻 Usage Examples

Chat Completion (Streaming)

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."} ] }'

Vision-Language Models (VLM)

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..."}} ] } ] }'

⚙️ CLI Options

OptionDefaultDescription
--model(required)HuggingFace model ID or local path
--port5413Port to listen on
--host127.0.0.1Host to bind
--visionfalseEnable VLM (vision-language model) mode for image inputs
--audiofalseEnable ALM (audio-language model) mode for audio inputs
--max-tokens2048Max tokens limit per generation
--prefill-size512Prompt prefill chunk size (micro-batching for long contexts)
--top-p1.0Default top-p nucleus sampling (overridable per-request)
--top-k50Default top-k sampling (0 disables, overridable per-request)
--min-p0.0Default min-p sampling threshold relative to the highest probability token (0 disables)
--gpu-layersmodel_defaultRestrict the amount of layers allocated to GPU hardware
--stream-expertsfalseEnable SSD expert streaming for MoE models (10x speedup)
--turbo-kvfalseEnable 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-tokens4Tokens per speculation round. Auto-capped to 1 when combined with --stream-experts.
--dflashfalseEnable 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

🔧 Per-Request API Parameters

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:

FieldTypeDescription
kv_bitsint (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_thinkingboolForce-enable or disable chain-of-thought thinking blocks for Gemma-4 / Qwen3.
kv_group_sizeintGroup size for kv_bits quantization (default: 64).
top_kintPer-request top-k sampling override (0 = disabled).
min_pfloatPer-request min-p sampling threshold (0 = disabled).
repetition_penaltyfloatToken repetition penalty (e.g. 1.15).

kv_bits vs --turbo-kv — What's the difference?

kv_bits (per-request)--turbo-kv (server flag)
ScopePer-request, sent in JSON bodyServer-wide, set at startup
AlgorithmMLX-native group quantization (4-bit / 8-bit)Custom 3-bit PolarQuant + QJL Walsh-Hadamard
ActivationFrom token 0After 2048 tokens
Memory savings~2–4× vs FP16~3.5× vs FP16
Use caseTargeted memory reduction per conversationExtreme long-context (100K+) compression

Example: Enable 4-bit KV cache per request

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."} ] }'

📦 Requirements

  • macOS 14.0+
  • Apple Silicon (M1/M2/M3/M4/M5)
  • Xcode Command Line Tools
  • Metal Toolchain (xcodebuild -downloadComponent MetalToolchain)

📖 The "Aha!" Moment

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.

🙏 Acknowledgments & Credits

Awesome MLX

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:

Contributors

  • Eric Lake — Engineered the SSD Expert Streaming 10x rewrite (PR #26), achieving 10× generation speedup on 122B+ MoE models via cross-projection batching, concurrent NVMe pread (QD=24), asyncEval pipeline with speculative pread, and runtime top-k expert selection. Also implemented the speculative decoding infrastructure with DraftModelRef, dual-model loading, and MambaCache checkpoint/restore for hybrid Attention+Mamba architectures.

Projects & References

  • mlx-swift — The core Apple MLX wrapper bringing Metal-accelerated operations into the Swift ecosystem.
  • mlx-lm — The official Python language models implementation, serving as the core inspiration for our chunked-prefill architecture and attention manipulation logic.
  • flash-moe — Inspired the memory-mapped out-of-core SSD Expert Streaming mechanics that we implemented natively in SwiftLM.
  • Hummingbird — The incredible event-driven Swift HTTP engine powering the OpenAI-compatible REST API.
  • TurboQuant Paper"TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate" (Zandieh et al., AISTATS 2026). Provided the initial algorithmic framework for the dual-stage PolarQuant + QJL engine.
  • TheTom/llama-cpp-turboquant — Served as an invaluable reference architecture for the C and GPU quantization tables, guiding the development of our native turbo-wht Walsh-Hadamard kernels and custom Metal wrapper layers.
  • TheTom/turboquant_plus — Essential Python validation logic used to certify the correctness of our manually constructed Lloyd-Max codebook generation math.
  • amirzandieh/QJL — The original 1-bit residual correction engine backing the paper, which informed our QJL error recovery in dot-product regimes.

License: MIT

About

⚡ 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.

Topics

Resources

Stars

735 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages