From-scratch LLM inference engine in Rust + Metal for Apple Silicon
No Python. No PyTorch. No llama.cpp dependency. Pure Rust + Metal shaders.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π― 100% token-match with llama.cpp on Qwen2.5-0.5B Q8_0 β
β β‘ 233 tok/sec (Q8_0, 3.2Γ speedup) β matches llama.cpp β
β π§ Qwen2 + Qwen3 + LLaMA + Mistral + OLMoE architectures β
β π¦ Pure Rust β zero Python runtime β
β π§ 15 Metal shaders, 85+ GPU kernels, fused Q4/Q8 dequant β
β π¦ GGUF native β Q4_0, Q8_0, Q4K, Q5K, Q6K, F16, BF16 β
β π OpenAI-compatible HTTP server β
β π¬ f32 precision pipeline for research-grade accuracy β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Every test produces identical output to llama.cpp (greedy, temp=0):
| Prompt | llama.cpp | RunIT Engine | Match |
|---|---|---|---|
| "What is 2+2?" | 2 + 2 equals 4. | 2 + 2 equals 4. | β |
| "Capital of France?" | The capital of France is Paris. | The capital of France is Paris. | β |
| "Capital of Japan?" | The capital of Japan is Tokyo. | The capital of Japan is Tokyo. | β |
| "What is machine learning?" | Machine learning is a subset of AI... (92 tok) | Machine learning is a subset of AI... (92 tok) | β |
| "Translate to French" | Bonjour, comment Γ§a va ? | Bonjour, comment Γ§a va ? | β |
| "Write a poem about the moon" | The moon, a celestial sight... | The moon, a celestial sight... | β |
π Full per-position logit comparison: docs/COMPARISON.md
| Metric | v0.0.1 | v0.2.0 | v0.3.0 | v0.7.0 (Q8_0) | llama.cpp |
|---|---|---|---|---|---|
| Decode tok/sec | 73 | 161 | 224 | 233 β | 230 |
| Avg latency | 13.94 ms | 6.28 ms | 4.73 ms | 4.29 ms | ~4.35 ms |
| Quantization | Q8_0 | Q8_0 | Q8_0 | Q8_0 | Q8_0 |
| Output quality | β Perfect | β Perfect | β Perfect | β Perfect | β |
Measured on Apple M4 Pro Β· Qwen2.5-0.5B Q8_0 Β· greedy decode Β· 50 tokens
The engine also supports Q4_0 fused GEMV (258 tok/sec) for larger models (3B+) where 4-bit quantization produces good output. On 0.5B, Q4_0 loses too much precision.
| Version | Optimization | tok/sec | Speedup | Key Technique |
|---|---|---|---|---|
| v0.0.1 | Kahan GEMV + f32 weights | 73 | 1.0Γ | Correctness-first |
| v0.2.0 | simd_sum + half4 + f16 dequant | 161 | 2.2Γ | Hardware SIMD reduction |
| v0.3.0 | Fused Q8_0 GEMV | 224 | 3.1Γ | On-the-fly dequant (1.06 B/elem) |
| v0.6.0 | Kernel fusion (QKV+bias, FFN, RoPE) | 228 | 3.1Γ | 242 dispatches (was 387) |
| v0.7.0 | + Fused Q4_0 GEMV | 233 (Q8) | 3.2Γ | Q4_0 ready for larger models |
| Optimization | Impact | Files |
|---|---|---|
| Fused Q8_0 GEMV | 1.06 bytes/elem β 47% less BW than f16 (primary path) | gemv_q8_0.metal |
| Fused Q4_0 GEMV | 0.56 bytes/elem β for larger models (3B+) at Q4_0 | gemv_q4_0.metal |
| Fused QKV+bias | 6 dispatches β 1 per layer (Qwen path) | fused_qkv.metal |
| Fused gate+up+silu | 3 dispatches β 1 per layer | fused_ffn.metal |
| simd_sum() reduction | 1-cycle hardware sum replaces 124-step Kahan | all GEMV shaders |
| Vectorized half4/float4 | 4Γ fewer loads, perfect 256-byte coalescing | gemv.metal |
| Multi-row GEMV | 4 rows/TG (128 threads) for better occupancy | gemv_multirow.metal |
π Full roadmap: docs/implementation-plan.md
- macOS 14+ with Apple Silicon (M1/M2/M3/M4)
- Rust 1.78+ (
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh) - Xcode Command Line Tools (
xcode-select --install)
# Qwen2.5-0.5B Q8_0 (recommended for testing β 675 MB)
huggingface-cli download Qwen/Qwen2.5-0.5B-Instruct-GGUF \
qwen2.5-0.5b-instruct-q8_0.gguf --local-dir ~/models/
# Qwen2.5-Coder-7B Q4_K_M (for coding tasks β 4.7 GB)
huggingface-cli download Qwen/Qwen2.5-Coder-7B-Instruct-GGUF \
qwen2.5-coder-7b-instruct-q4_k_m.gguf --local-dir ~/models/
# Download tokenizer
huggingface-cli download Qwen/Qwen2.5-0.5B-Instruct \
tokenizer.json --local-dir ~/models/# Build
cargo build --release -p bare-metal-engine
# Generate text
./target/release/generate ~/models/qwen2.5-0.5b-instruct-q8_0.gguf \
--tokenizer ~/models/tokenizer.json \
--prompt '<|im_start|>user
What is 2+2?<|im_end|>
<|im_start|>assistant
' \
--tokens 100
# Start OpenAI-compatible server
./target/release/serve ~/models/qwen2.5-0.5b-instruct-q8_0.gguf \
~/models/tokenizer.json --port 8080
# Query the server
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Hello!"}],"max_tokens":64}'generate <model.gguf> [OPTIONS]
Options:
--tokenizer <path> Path to tokenizer.json
--prompt <text> Input prompt
--tokens N Max tokens to generate (default: 30)
--temperature T 0.0 = greedy (default: 0.0)
--top-p P Nucleus sampling (default: 0.9)
--top-k K Top-K sampling (default: 50)
--rep-penalty F Repetition penalty (default: 1.1)
--seed S RNG seed (default: 42)
--tq Enable TurboQuant KV cache compression
| Crate | Description |
|---|---|
bare-metal-gguf |
GGUF parser β mmap, tensor metadata, zero-copy views |
bare-metal-tokenizer |
HuggingFace tokenizer wrapper |
bare-metal-kernels |
Metal shaders + Rust dispatch layer |
bare-metal-engine |
Model config, forward pass, KV cache, server |
| Kernel | File | Description |
|---|---|---|
gemv_q8_0_f32in_f32out |
gemv_q8_0.metal |
Fused Q8_0 GEMV β 47% less bandwidth (primary) |
gemv_q4_0_f32in_f32out |
gemv_q4_0.metal |
Fused Q4_0 GEMV β for larger models at 4-bit |
gemv_f16w_f32in_f32out |
gemv.metal |
GEMV with simd_sum + vectorized half4 loads |
gemv_q4k_f16 |
gemv_q4k.metal |
Fused Q4_K dequant + GEMV |
decode_attention_f32 |
attention.metal |
Decode attention with online softmax |
flash_attention_f16 |
attention.metal |
Tiled FlashAttention-2 for prefill |
rms_norm_f32_f32_f32g |
norm.metal |
Full f32 RMSNorm |
rope_inplace_f32 |
rope.metal |
Non-interleaved RoPE (Qwen2/LLaMA) |
silu_mul_f32 |
activation.metal |
SwiGLU activation (fused) |
dequant_q4k_f16 |
dequant.metal |
Q4_K_M GPU dequantization |
fused_qkv_bias_q8_0_f32 |
fused_qkv.metal |
Q+K+V+bias in ONE dispatch |
fused_ffn_q4_0_f32 |
fused_ffn.metal |
gate+up+silu fused |
| + 60 more | various | See docs/KERNELS.md for full reference |
RunIT uses an f32 residual stream with fused quantized weight kernels for maximum speed without quality loss. Weights are read in their native packed format (Q4_0/Q8_0) and dequantized on-the-fly during the GEMV dot product β no intermediate buffer needed:
Token Embedding (f32 lookup)
β
βΌ
RMSNorm (f32 in β f32 out, f32 gamma)
β
βΌ
Q/K/V Projections (fused QKV+bias, Q8_0/Q4_0 on-the-fly dequant)
β βββ simd_sum + vectorized half4 loads
βΌ
QK-Norm (optional, Qwen3: per-head RMSNorm on Q and K)
β
βΌ
RoPE (f32, non-interleaved pairing)
β
βΌ
KV Cache (f32, flat layout)
β
βΌ
Decode Attention (f32 QΒ·K, f32 softmax, f32 output)
β
βΌ
FFN: gate/up β SiLU β down (f16 weights, f32 accumulation)
β
βΌ
lm_head (f16 weights Γ f32 input β f32 logits)
Weights dequantized from Q8_0/Q5K/Q6K to f16 (2 bytes/element) instead of f32 (4 bytes). Quantized formats have β€8 significant bits β f16's 10-bit mantissa loses nothing.
| Architecture | Status | Models Tested | tok/sec |
|---|---|---|---|
| Qwen2 | β Full support | Qwen2.5-0.5B (Q8_0) | 233 |
| Qwen3 | β Full support | Qwen3-0.6B (Q8_0) | 176 |
| LLaMA | β Full support | LLaMA-2, LLaMA-3 | β |
| Mistral | β Full support | Mistral-7B | β |
| OLMoE | β MoE support | OLMoE-1B-7B (Q4K_M) | β |
| Quantization | Status | Notes |
|---|---|---|
| Q8_0 | β Fused GEMV | On-the-fly dequant, 1.06 B/elem β primary path |
| Q4_0 | β Fused GEMV | On-the-fly dequant, 0.56 B/elem β fastest (3B+ models) |
| Q4_K_M | β Fused Q4K | GPU dequant for aligned K, f16 fallback |
| Q6_K | β Supported | CPU dequant to f16 |
| Q5_K | β Supported | CPU dequant to f16 |
| Q5_0 | β Supported | CPU dequant to f16 |
| Q8_K | β Supported | CPU dequant to f16 |
| F16 | β Native | Zero-copy if page-aligned |
| BF16 | β Converted | BF16 β f16 at load time |
| Phase | Feature | Status |
|---|---|---|
| 0 | GGUF parser (mmap, tensors, metadata) | β Done |
| 1 | Metal GPU kernels (GEMV, RoPE, RMSNorm, Attention) | β Done |
| 2 | Model loading + config | β Done |
| 3 | Transformer forward pass + KV cache | β Done |
| 4 | Q4_K_M GPU dequantization | β Done |
| 5 | TurboQuant KV-cache compression (3-4 bit) | β Done |
| 6 | Text I/O β tokenizer, sampling | β Done |
| 7 | OpenAI-compatible HTTP server | β Done |
| 8 | Prefill batching (GEMM kernel) | β Done |
| 9 | f32 precision pipeline | β Done |
| 10 | RoPE fix + llama.cpp parity | β Done |
| 11 | GEMV optimization (simd_sum, vectorize, f16 dequant) | β Done β 2.2Γ |
| 12 | Fused Q8_0 GEMV (on-the-fly dequant, 47% less BW) | β Done β 3.1Γ |
| 13 | Kernel fusion (QKV+bias, FFN, RoPE) + multi-row | β Done |
| 14 | Fused Q4_0 GEMV (for larger models at 4-bit) | β Done |
| 15 | Profiling + speculative decoding infra | β Done |
| 16 | Qwen3 support (QK-norm + head_dim detection) | β Done |
| 17 | 7B model support + benchmarks | π Next |
| 18 | PagedAttention KV cache + continuous batching | π Planned |
The engine had a critical bug where the Rotary Position Embedding used interleaved pairing (0,1), (2,3)... instead of the correct non-interleaved pairing (i, i+d/2) used by Qwen2/LLaMA/Mistral.
Why it was hidden: At position 0, RoPE is the identity rotation (cos(0)=1, sin(0)=0) regardless of pairing β so single-token tests always passed. The bug only manifested at position β₯ 1, scrambling the positional encoding and corrupting multi-token attention.
Before (wrong): pairs (0,1) (2,3) (4,5) ... β scrambled positions at posβ₯1
After (correct): pairs (0,32) (1,33) (2,34) ... β perfect llama.cpp match
π Full investigation: docs/COMPARISON.md Β· docs/STATUS_REPORT.md
RunIT/
βββ crates/
β βββ gguf/ # GGUF file parser (mmap, zero-copy)
β βββ tokenizer/ # HuggingFace tokenizer wrapper
β βββ kernels/ # Metal shaders + Rust dispatch
β β βββ shaders/ # .metal source β .metallib at build time
β β β βββ gemv.metal # 14 GEMV variants (simd_sum + half4)
β β β βββ gemv_q4_0.metal # 7 fused Q4_0 GEMV (0.56 B/elem) π
β β β βββ gemv_q8_0.metal # 7 fused Q8_0 GEMV (1.06 B/elem)
β β β βββ gemv_q4k.metal # 6 fused Q4K GEMV
β β β βββ fused_qkv.metal # Q+K+V+bias in 1 dispatch
β β β βββ fused_ffn.metal # gate+up+silu in 1 dispatch
β β β βββ fused_rope.metal # QK RoPE in 1 dispatch
β β β βββ gemv_multirow.metal # Multi-row GEMV (4 rows/TG)
β β β βββ attention.metal # FlashAttention-2 + decode
β β β βββ gemm.metal # simdgroup_matrix GEMM + tiled
β β β βββ norm.metal # RMSNorm (4 variants)
β β β βββ rope.metal # RoPE (f16/f32/batch)
β β β βββ activation.metal # SwiGLU, add, argmax, KV scatter
β β β βββ dequant.metal # Q4K GPU dequantization
β β β βββ turboquant.metal # TurboQuant KV compression
β β βββ src/ # MetalContext, dispatch, error types
β βββ engine/ # Model loader, forward pass, server
β βββ src/
β βββ forward.rs # Executor + 8 quantization decoders
β βββ kv_cache.rs # f32 flat KV cache
β βββ sampler.rs # temp/top-k/top-p/rep-penalty
β βββ server/ # OpenAI-compatible HTTP (axum, SSE)
β βββ bin/
β βββ generate.rs # CLI text generation + benchmark
β βββ serve.rs # HTTP server binary
βββ docs/
β βββ assets/ # Logo, charts, diagrams
β βββ ARCHITECTURE.md # System design and data-flow
β βββ COMPARISON.md # llama.cpp comparison + test results
β βββ KERNELS.md # Metal kernel reference
β βββ STATUS_REPORT.md # Bug fixes + precision improvements
βββ README.md
# Check all crates
cargo check --workspace
# Run tests
cargo test --workspace
# Build release
cargo build --release -p bare-metal-engine
# Debug: per-position logits during prompt processing
DEBUG_LOGITS=1 ./target/release/generate <model.gguf> \
--tokenizer <tokenizer.json> --tokens 10 \
--prompt '<|im_start|>user\nHello<|im_end|>\n<|im_start|>assistant\n'
# Debug: per-layer hidden state dump
DEBUG_LAYERS=1 ./target/release/generate <model.gguf> \
--tokenizer <tokenizer.json> --tokens 1 --prompt 'Hello'- Awesome LLM Inference β Curated collection of LLM inference papers, frameworks, and optimization techniques. Covers attention mechanisms, quantization, speculative decoding, KV cache management, and hardware-specific optimizations that inform RunITβs design.
- FlashAttention-2 β Tiled attention with online softmax (implemented in
attention.metal) - TurboQuant β KV-cache compression to 3β4 bits (implemented in
turboquant.metal) - GGUF Format β Model file format specification
- llama.cpp β Reference C++ implementation used for accuracy validation
- Metal Shading Language Spec β Appleβs GPU programming language
MIT
Built with π¦ Rust and β‘ Metal
For Apple Silicon, from the ground up