โก nuvec
MLX for Rust, written in Rust. Lazy n-dimensional arrays (tensors), 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 change between releases.
โจ Highlights
- ๐ฆ Pure Rust. No C++ build, no FFI to MLX. Metal goes through
objc2-metal.unsafeis confined to the GPU runtime, the CPU matrix product, kernel dispatches and buffer views in nuvec-core, and system calls in nuvec-distributed (sockets, RDMA). - ๐ค Lazy arrays on unified memory. Operations build a graph that runs when you read a result. CPU and GPU share memory, with no copies between them.
- ๐ Composable transforms.
grad,value_and_grad,vjp,jvp,vmap,custom_function,checkpointandcompile(shaped or shapeless) nest in any order, over complex numbers, tuples, maps and your own structs. - ๐ง 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 (affine 1, 2, 3, 4, 5, 6 and 8 bits; MXFP4, MXFP8, NVFP4), fused attention, RMSNorm, RoPE, sort, scan, mixture-of-experts gather kernels, and your own kernels from source (
fast::MetalKernel). Metal 4 TensorOps kernels for M5 Neural Accelerators are opt-in (NUVEC_NAX=on). - ๐ Several Macs. MLX's collectives (
all_sum,all_gather,sum_scatter,send/recv, โฆ) as lazy, differentiable array operations over MLX's TCP ring or, experimentally, JACCL over Thunderbolt 5 RDMA, with pipeline-parallel models (a model larger than one Mac, one stage per Mac) and tensor-parallel models (every layer split across Macs). - ๐งช Tested against MLX. Over a thousand cases replay results recorded from MLX, with random layouts, gradients and every dtype.
- ๐ค LLM runtime. Llama, Mistral, Qwen 2/3/3.5, gpt-oss and Gemma 4 (dense, hybrid Gated DeltaNet, mixture-of-experts) from mlx-community or Hugging Face checkpoints. Speculative decoding (MTP heads, prompt lookup, SuffixDecoding) and batched serving.
- ๐พ Memory-mapped weights. Aligned checkpoints (such as those nuvec writes) go to the GPU without a copy. Unaligned ones (mlx-community's) are read once, in parallel, with no more memory than MLX.
- โ๏ธ Built for servers. Attention plans and score-memory caps, a memory limit that refuses instead of swapping, checkpoint footprints before loading, batch-invariant serving and documented determinism. Used by the mrun inference server.
๐ Performance
nuvec-lm against mlx-lm 0.32.2 with the same token ids on an Apple M4 Pro (48 GB), measured on 2026-10-31 with nuvec 0.5.0 (just bench-parity --tier full). Decoding is greedy; ratios above 1 favour nuvec.
| Model | Kind | Decode (25 / 961 / 3,841-token prompt) | Time to first token (2K / 32K tokens) | 4 requests |
|---|---|---|---|---|
| Qwen3-0.6B 4-bit | dense | 1.10ร / 1.10ร / 1.06ร | 1.01ร / 1.01ร | 0.98ร |
| Llama 3.2 3B 4-bit | dense | 0.99ร / 1.00ร / 1.00ร | 1.00ร / 1.01ร | 1.00ร |
| Llama 3.1 8B 4-bit | dense | 1.01ร / 1.00ร / 1.00ร | 1.00ร / 1.00ร | 1.01ร |
| Qwen3-8B 4-bit | dense | 1.00ร / 1.01ร / 1.01ร | 1.00ร / 1.01ร | 1.01ร |
| Qwen3.5-9B 4-bit | hybrid | 1.00ร / 1.00ร / 1.00ร | 1.00ร / 0.99ร | 1.00ร |
| Qwen3.8-27B 4-bit | hybrid | 1.00ร / 1.01ร / 1.05ร | 1.01ร / 1.00ร | 1.00ร |
| Qwen3-30B-A3B 4-bit | MoE | 1.00ร / 1.00ร / 1.00ร | 0.99ร / 1.00ร | 0.98ร |
| Qwen3.6-35B-A3B 4-bit | hybrid MoE | 0.99ร / 1.00ร / 1.00ร | 1.01ร / 0.98ร | 0.98ร |
| gpt-oss-20b MXFP4 | MoE, sliding window | 0.99ร / 0.99ร / 0.98ร | 1.04ร / 1.03ร | 1.00ร |
| gpt-oss-20b 8-bit | MoE, sliding window | 1.01ร / 1.01ร / 1.00ร | 1.02ร / 1.02ร | 1.08ร |
| Gemma 4 E4B 4-bit | dense, sliding window | 0.98ร / 1.00ร / 1.00ร | 1.07ร / 1.07ร | 1.02ร |
| Gemma 4 26B-A4B 4-bit | MoE, sliding window | 1.01ร / 1.01ร / 1.01ร | 1.06ร / 1.08ร | 0.99ร |
- Geometric means: decode 1.01ร, time to first token 1.02ร at 2K and 32K tokens, peak memory 1.02ร (nuvec uses slightly less), warm load to the first token 1.80ร, 1 / 4 / 8 concurrent requests 1.01ร / 1.00ร / 1.00ร.
- Longest contexts: Qwen3.6-35B-A3B's first token at 64K tokens is 0.98ร. At 261,952 tokens nuvec prefills it in 20.4 minutes; mlx-lm runs out of GPU memory on the same machine.
- Speculative decoding with the model's multi-token-prediction head: Qwen3.8-27B 1.66ร and Qwen3.5-9B 1.36ร faster than plain decoding on natural text, with identical output.
The full record (JSON) and method are on the benchmarks page.
๐ Quick start
Build dependencies optimised even in debug builds; otherwise CPU kernels and tests that run them are tens of times slower:
[]
= 3
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 return arrays, never Result. An invalid call gives a failed array that records the operation, call site and reason; anything computed from it fails too. The error surfaces at check()?, eval()?, item()?, to_vec()?, I/O and transforms.
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 ;
From the command line (after 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-distributed |
MLX's TCP ring and JACCL (Thunderbolt 5 RDMA, experimental) for nuvec::distributed |
nuvec-nn |
Modules (#[derive(Module)]), layers (Linear, quantized, LoRA, norms, RoPE, dropout, tensor-parallel sharded linear), 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 (nuvec-gpu's cuda feature adds an experimental CUDA runtime that arrays do not use) |
๐ ๏ธ 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, kernels are compiled from embedded sources at runtime (NUVEC_METALLIB=off forces this).
๐งช Against MLX
Building and testing need no Python: the MLX fixtures are checked in. With uv you can re-record them and benchmark against MLX:
| Command | Compares |
|---|---|
just bench-parity |
nuvec-lm vs mlx-lm and the previous release, one model per architecture (about 45 minutes); --tier full for all models and long contexts (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; the changelog lists every break. Semver covers items reachable through nuvec:: paths and nuvec-lm (pinned in crates/nuvec/tests/api_surface.rs); the sub-crates (nuvec-core, nuvec-gpu, โฆ) make no promise when used directly. MSRV is Rust 1.99; 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.