โก nuvec
MLX for Rust, written in Rust. Lazy arrays, composable transforms and Metal kernels for Apple Silicon, with no bindings to MLX or any C/C++ library.
Documentation ยท Getting started ยท Benchmarks
nuvec brings MLX's programming model to Rust: arrays that are evaluated lazily on unified memory, grad/vjp/jvp/vmap/compile that compose freely, and the Metal kernels that make MLX fast, ported and tested against MLX itself. On top sits an LLM runtime that loads mlx-community checkpoints and generates as fast as mlx-lm.
๐ง Pre-alpha. The API can still change between releases. Everything listed below works today and is covered by tests.
โจ Highlights
- ๐ฆ Pure Rust. No C++ build, no FFI to MLX. Metal is reached through Apple's system frameworks (
objc2-metal);unsafestays in the Metal runtime, nuvec-core's three kernel-dispatch calls and one CPU matrix-product call, and the other crates contain none. - ๐ค Lazy arrays on unified memory. Operations build a graph; nothing runs until you read a result. The CPU and GPU share memory, so there are no copies between them.
- ๐ Composable transforms.
grad,value_and_grad,vjp,jvp,vmap,custom_function,checkpointandcompilenest in any order, through complex numbers too, over tuples, maps and your own structs (gradients come back with the same shape). - ๐ง Models as plain structs.
#[derive(Module)]gives MLX's parameter names; optimizers, freezing, LoRA,save_weightsand a compiled training step (compile_mut) work on the model's own type. - ๐ฅ MLX's Metal kernels. GEMM and GEMV, quantized products (1, 2, 3, 4, 5, 6 and 8 bits), fused attention, RMSNorm, RoPE, sort, scan, mixture-of-experts gather kernels, plus your own kernels from source (
fast::MetalKernel). - ๐งช Tested against MLX. Nearly a thousand cases replay results recorded from real MLX, with random layouts, gradients and every dtype.
- ๐ค LLM runtime. Llama, Mistral and Qwen 2/3/3.5, dense, hybrid (Gated DeltaNet) or mixture-of-experts, straight from mlx-community or Hugging Face checkpoints; speculative decoding and batched serving included.
- ๐พ Memory-mapped weights. Checkpoints whose tensors are aligned (files nuvec writes are) are mapped and handed to the GPU without a copy; mlx-community checkpoints have unpadded headers and are read once at load, in parallel, using no more memory than MLX.
- โ๏ธ An engine for servers. Attention plans and score-memory caps, one peak-memory metric comparable with MLX's, a memory limit that refuses instead of swapping, checkpoint footprints before loading, row-independent few-row products and documented determinism (used, for example, by the mrun inference server).
๐ Performance
nuvec-lm against mlx-lm 0.32.2 with the same token ids, measured on 2026-10-23 with nuvec 0.4.0 on an Apple M4 Pro (48 GB). Decoding is greedy, and each cell is the median of four runs. Ratios above 1 favour nuvec.
| Model | Kind | Decode (25 / 961 / 3,841-token prompt) | Prefill (961 tokens) | Time to first token (2,048 tokens) |
|---|---|---|---|---|
| Qwen3-0.6B 4-bit | dense | 1.10ร / 1.09ร / 1.06ร | 1.04ร | 1.03ร |
| Llama-3.2-3B-Instruct 4-bit | dense | 1.00ร / 1.00ร / 1.00ร | 1.01ร | 1.00ร |
| Llama-3.1-8B-Instruct 4-bit | dense | 1.01ร / 1.01ร / 1.00ร | 1.01ร | 1.00ร |
| Qwen3-8B 4-bit | dense | 1.00ร / 1.01ร / 1.01ร | 1.01ร | 1.00ร |
| Qwen3.5-9B 4-bit | hybrid | 0.99ร / 0.99ร / 0.99ร | 1.00ร | 1.00ร |
| Qwen3.8-27B 4-bit | hybrid | 1.02ร / 1.01ร / 1.02ร | 1.01ร | 1.01ร |
| Qwen3-30B-A3B 4-bit | MoE | 1.03ร / 1.03ร / 1.03ร | 0.99ร | 0.99ร |
| Qwen3.6-35B-A3B 4-bit | hybrid MoE | 1.01ร / 1.01ร / 1.01ร | 0.98ร | 1.00ร |
The geometric mean is 1.02ร for decode and 1.00ร for prefill (the 25-token rows are not counted). For Qwen3.6-35B-A3B the time to first token is 0.98ร at 32K and 64K tokens. At 261,952 tokens nuvec prefills in 20.5 minutes, and mlx-lm runs out of GPU memory on the same 48 GB machine.
Beyond single requests:
- Warm load to the first token: 1.13โ2.77ร faster (geometric mean 1.63ร).
- Peak memory: 1.02ร (slightly below mlx-lm's).
- Serving several requests at once: 1.02ร / 0.99ร / 0.99ร aggregate throughput at 1 / 4 / 8 requests. The lowest cells are 0.97ร.
The full record (JSON) and the method are on the benchmarks page. Speculative decoding with prompt-lookup drafts more than doubles decode speed on text that repeats its input.
๐ Quick start
Arrays are lazy, and gradients are arrays too:
use *;
use grad;
Create, index and chain; errors surface where you read results:
use *;
use ops as mx;
Operations are methods that chain and return arrays, never Result. An invalid call gives a failed array that remembers the operation, where you called it and why; everything computed from it fails the same way, and the error surfaces at a boundary: check()? (no evaluation), eval()?, item()?, to_vec()?, I/O and transforms. Arrays come from array![...], From (Rust numbers, arrays, Vecs and iterators) and Array::from_slice(&data, shape); MLX's free functions (zeros, arange, concatenate, โฆ) are in nuvec::ops.
Train a model: a struct with #[derive(Module)], a compiled step over (model, optimizer):
use ;
use ;
use ;
// parameters: hidden.weight, hidden.bias, โฆ
Generate text with an mlx-community checkpoint:
use ;
Or from the command line (the model must be in the local Hugging Face cache, e.g. hf download mlx-community/Qwen3-8B-4bit):
๐ฆ Crates
| Crate | What it does |
|---|---|
nuvec |
The facade: arrays, transforms, fast kernels, random, I/O and the GPU runtime in one import |
nuvec-core |
Lazy graph, primitives, dtypes, evaluation, derivative and batching rules |
nuvec-compile |
compile and compile_mut: tracing, simplification, element-wise kernel fusion |
nuvec-macros |
#[derive(Tree)], #[derive(Module)], #[derive(Static)] (re-exported by nuvec and nuvec-nn) |
nuvec-gpu |
Metal runtime: pipelines, buffers, hazard-tracked command encoding, allocator, wired memory, profiler |
nuvec-kernels |
Metal kernel sources and the precompiled nuvec.metallib |
nuvec-cpu |
Pure-Rust CPU kernels |
nuvec-io |
safetensors, npy and IDX; memory-mapped loading |
nuvec-nn |
Modules (#[derive(Module)]), layers (Linear, quantized, LoRA, norms, RoPE, dropout), losses, optimizers (SGD, Adam, AdamW, Lion, Muon, parameter groups) |
nuvec-lm |
LLM runtime: models, tokenizer, KV cache, sampling, speculative decoding, LoRA adapters |
๐ฅ๏ธ Platforms
| ๐ macOS 15+ on Apple Silicon | The primary target, with the full Metal backend |
| ๐ง Linux | The core and CPU crates build and run; the GPU crates compile as stubs |
๐ ๏ธ Building from source
| Tool | Install |
|---|---|
Rust (pinned in rust-toolchain.toml) |
rustup installs it automatically |
| Metal toolchain | xcodebuild -downloadComponent MetalToolchain |
| just | brew install just |
| cargo-deny, Miri, jq | cargo install cargo-deny --locked, rustup +nightly component add miri, brew install jq |
&&
Without the Metal toolchain the build still succeeds: the shader sources are embedded and compiled at runtime (NUVEC_METALLIB=off selects this mode explicitly).
๐งช Against MLX
Python is not needed to build or test: the fixtures recorded from MLX are checked in. With uv installed you can re-record them and compare speed on your own machine:
| Command | Compares |
|---|---|
just bench-parity |
nuvec-lm vs mlx-lm over a roster of cached models: decode, time to first token, peak memory, load, concurrency (a JSON record; the table above) |
just bench-gemm / bench-quant / bench-fast |
matrix products, quantized products, fused kernels |
just bench-compile, just bench-load, just mnist |
compiled functions, checkpoint loading, MNIST training |
just fixtures, lm-fixtures, nn-fixtures, io-fixtures |
re-record the reference results |
NUVEC_PROFILE=1 prints the GPU time per kernel of any run (see profiling).
๐งญ Stability
nuvec is 0.x: a minor release may break the API, and the changelog lists every break with a migration table. Semver covers every item reachable through nuvec:: paths and nuvec-lm; the pinned items are listed in crates/nuvec/tests/api_surface.rs. The sub-crates (nuvec-core, nuvec-gpu, โฆ) make no promise when depended on directly. The minimum supported Rust version is 1.99 (rust-version in Cargo.toml); raising it is a minor release.
๐ License
Licensed under either of Apache License, Version 2.0 or MIT license, at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Code ported from MLX and mlx-lm keeps its MIT attribution (THIRD_PARTY_NOTICES). nuvec is not affiliated with Apple.