构建一个媲美 Llama.cpp 的 Rust 推理引擎
Building a Rust Inference Engine That Matches Llama.cpp

原始链接: https://www.fratepietro.com/2026/ferrox-rust-gguf-inference-engine/

Ferrox 是一个新的纯 Rust 推理引擎,旨在通过 CPU、Apple Metal 或 CUDA 在本地运行大语言模型(稠密模型和混合专家模型)。该项目从零构建,未绑定 llama.cpp,优先考虑透明度,旨在提供可验证、可复现的性能指标以及实用的工具集。 该引擎支持 GGUF 模型格式,并提供两种主要模式:模拟常见 llama.cpp 参数的命令行界面(CLI),以及兼容 OpenAI 的服务器。Ferrox 通过将反量化直接融合到矩阵乘法运算中,并利用针对特定架构的 GPU 内核而非通用路径,实现了极具竞争力的性能。 主要特性包括: * **性能透明:** 每一项性能声明均有仓库中可复现、固定的基准测试作为支撑。 * **易于使用:** 以单一静态二进制文件形式提供,无需外部依赖或 Python 环境。 * **硬件灵活性:** 同一套代码支持 CPU、Metal 和 CUDA 后端。 * **高效率:** 通过内存映射权重并进行即时反量化,最大限度地减少内存占用。 尽管目前仍处于积极开发阶段——当前的重点是优化 Metal 性能并扩展混合专家模型(MoE)支持,但 Ferrox 为在本地运行开放权重模型提供了一个强大且无依赖的替代方案。它是开源的(Apache 2.0 协议),欢迎社区贡献。

“Ferrox”是一款全新的纯 Rust 本地大语言模型推理引擎,其开发者近期在 Hacker News 上分享了该项目。该引擎旨在跨 CPU、Metal 和 CUDA 平台运行稠密模型与混合专家模型(MoE),其独特之处在于完全摒弃了 `llama.cpp` 或 `ggml` 绑定,所有内核和加载器均为从零编写。作者表示,该项目的初衷是为了掌握推理机制,并在既定基准测试中验证性能。 然而,社区对此反响极为冷淡且充满怀疑。评论者质疑该项目的真实性,许多人指责作者使用人工智能生成代码及帖文内容,这种做法被戏称为“氛围编程”(vibe coding)。反对者对充斥着低质量、人工智能辅助项目的现状表示沮丧,并指出“基于 Rust 编写”这一标签已成为一种重复的营销套路。尽管作者坚持该项目是真实的学习练习,但这番讨论凸显了开发者对人工智能生成内容以及缺乏真实人类投入的项目日益增长的疲劳感。
相关文章

原文

I’ve spent the last few days building Ferrox, a pure-Rust inference engine for running open LLMs locally — dense models and Mixture-of-Experts, on CPU, Apple Metal, or CUDA. No bindings to llama.cpp or ggml, no wrapping an existing runtime. Every kernel, every loader, every scheduling decision written from scratch.

The obvious question is “why, when llama.cpp already exists and is excellent.” The honest answer: I wanted to understand inference at a level deeper than “run the binary,” and I wanted a project where every performance claim had to be earned against a real, well-known baseline rather than asserted.

What Ferrox actually is

At its core, Ferrox loads a GGUF file — the same quantized model format llama.cpp uses — and runs inference on it. Two ways to use it:

  • A CLI, ferrox, with llama.cpp-compatible flags. Point it at a model, get a completion.
  • A server, ferrox-server, that speaks the OpenAI chat-completions API. Anything built against ChatGPT’s API — a chat UI, an agent framework, a test harness — works against it unchanged, just pointed at localhost.

Under the hood, model weights are memory-mapped straight off disk and never fully decompressed into RAM — dequantization happens fused into the dot product, at the moment the weight is actually needed. That’s the same trick llama.cpp uses, and it’s a big part of why both engines can run an 8B-parameter model on a laptop with a few gigabytes of memory instead of thirty.

Try it: build, download a GGUF, run

Ferrox does not ship weights. You download a local .gguf the same way you would for llama.cpp — I recommend the Hugging Face CLI (pip install -U huggingface_hub):

git clone https://github.com/antonellof/ferrox.git
cd ferrox
cargo build --release -p ferrox-cli -p ferrox-server --features metal

mkdir -p models

# ~1.2 GB smoke test
hf download TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF \
  tinyllama-1.1b-chat-v1.0.Q8_0.gguf --local-dir models

# Optional: small instruct chat model (~0.8 GB)
hf download bartowski/Llama-3.2-1B-Instruct-GGUF \
  Llama-3.2-1B-Instruct-Q4_K_M.gguf --local-dir models

Handy starting points from the README:

Prefer Q4_K_M for everyday use, Q8_0 for tiny smoke tests. More GGUFs: llama.cpp-compatible models on Hugging Face. What Ferrox verifies today: docs/MODELS.md.

Then:

# One-shot completion
./target/release/ferrox -m models/tinyllama-1.1b-chat-v1.0.Q8_0.gguf \
  -p "The capital of France is" -n 32 --temp 0 --no-cnv

# Chat on Metal (default when the GGUF ships a chat template)
./target/release/ferrox -m models/Llama-3.2-1B-Instruct-Q4_K_M.gguf \
  -p "What is 2+2?" -n 64 --temp 0 -dev metal -ngl all

# OpenAI-compatible server
./target/release/ferrox-server \
  -m models/tinyllama-1.1b-chat-v1.0.Q8_0.gguf \
  --host 127.0.0.1 --port 8383 -dev metal -ngl all

curl -s -X POST http://127.0.0.1:8383/v1/chat/completions \
  -H 'content-type: application/json' \
  -d '{"model":"m","messages":[{"role":"user","content":"Hi"}],"max_tokens":32,"temperature":0}'

Flags mirror llama.cpp (-m, -p, -n, -t, --temp, -ngl, …) — full reference in docs/CLI.md. One Ferrox-specific knob worth knowing: --ctk q8_0 (or FERROX_CTK=q8_0) stores the KV cache as Q8_0 on Metal instead of the default f16, which trades a bit of precision for a smaller KV footprint on longer contexts.

Why performance had to be provable, not claimed

Every local-inference project claims to be fast. Almost none show the methodology. I didn’t want to add noise to that pile, so the whole benchmarking setup in Ferrox is built to make claims falsifiable:

  • Same machine, same GGUF file, same backend, for both engines, back to back.
  • Warm runs, greedy decoding, multiple reps, median reported.
  • Every headline number is pinned to a JSON receipt in the repo — regenerate it and the numbers either hold or they don’t.

That discipline turned up real results on an Apple M2 Pro (Host B). Headline numbers below are predicted tok/s from the fair-chat suite in benchmarks/RESULTS.md — Gap is llama / ferrox (under 1.0 means Ferrox is faster; near-parity is within ~5%):

Model Backend Ferrox llama.cpp Gap
Llama-3.1-8B Q4_K_M Metal 28.3 tok/s 27.6 tok/s ~0.97× (parity / Ferrox)
Llama-3.2-1B Q4_K_M Metal 140.8 tok/s 140.8 tok/s 1.00× (parity)
TinyLlama-1.1B Q8_0 Metal 117.9 tok/s 113.7 tok/s ~0.96× (parity)
Qwen2.5-0.5B Q8_0 Metal 196.1 tok/s 132.8 tok/s ~0.68× (Ferrox)
SmolLM2-135M Q8_0 Metal 290.2 tok/s 241.2 tok/s ~0.83× (Ferrox)
Gemma-3-1B Q8_0 Metal 94.1 tok/s 81.7 tok/s ~0.87× (Ferrox)
OLMoE-1B-7B Q4_0 Metal 88.4 tok/s 156.8 tok/s ~1.77× (llama)
Mistral-7B Q4_K_M Metal 31.4 tok/s 33.3 tok/s ~1.06× (near parity)
Phi-4-mini Q4_K_M Metal 50.0 tok/s 53.1 tok/s ~1.06× (near parity)

The north-star pin is the one that matters most to me: Llama-3.1-8B on Metal is now ~0.97× — Ferrox slightly ahead of llama.cpp on the same host and GGUF (28.27 vs 27.55 pred). On the CLI one-shot path it is an exact 1.00× tie (28.85 vs 28.64). That is the moment the engine stopped feeling like an academic exercise.

The other big movement since the first pins: OLMoE on Metal. Early receipts were roughly ~15× behind llama.cpp (~10 tok/s). After Metal expert-placement work it sits at 88.4 vs 156.8 (~1.77×) — still trailing, but in a completely different league. Gemma-3 Metal flipped from trailing to ahead. Full methodology and every raw pin live in the RESULTS file.

Re-running the suite

If you want to regenerate the receipts yourself (same host, same GGUFs, both engines):

python3 benchmarks/run_suite.py --skip-missing --fit-host
# optional CLI mode pins:
python3 benchmarks/run_suite.py --skip-missing --fit-host --mode cli

That overwrites pins under benchmarks/receipts/pins/ and regenerates RESULTS.md via render_results.py. No invented numbers — if a pin is missing, the table says so. --fit-host skips models that do not fit the machine (and skips CUDA on darwin).

Where the wins came from

Two architectural choices did most of the work:

Fusing dequantization into the matmul. Quantized weights (4-bit, 8-bit) never get expanded into a full-precision buffer. The dequant math happens inline as part of the dot product, so you pay for it once, in cache, rather than as a separate memory-bound pass.

Architecture-specific GPU paths. Models aren’t structurally identical — Qwen has per-head QK-normalization, Gemma-3 uses sliding-window attention and GeGLU, Phi-3 fuses its QKV and FFN projections. Ferrox implements dedicated Metal kernels for each of these instead of forcing every model through one generic attention path. That’s precisely why small Qwen and SmolLM2 models beat llama.cpp on Metal by a wide margin — the kernel matches the actual computation shape instead of paying overhead for generality it doesn’t need. The same idea drove the OLMoE Metal jump: expert placement on GPU, not a generic dense path wearing a MoE costume.

Utility: why this is more than a benchmark exercise

Beyond the numbers, Ferrox is a genuinely practical way to run models locally:

  1. Single static binary. No Python environment, no CUDA-toolkit version roulette, no pip install dependency resolution. Copy the binary, point it at a .gguf file, run.
  2. Drop-in for existing tooling. The OpenAI-compatible server means any app already wired for ChatGPT’s API — LangChain scripts, custom chat frontends, eval harnesses — works against a fully local model with a one-line base-URL change.
  3. MoE support, not just dense models. OLMoE-1B-7B runs on CPU and Metal with verified pins. Metal MoE still trails llama.cpp, but the gap is closing. That matters because Mixture-of-Experts is where a lot of frontier-model efficiency gains are coming from — an inference engine that only handles dense transformers is increasingly incomplete.
  4. Backend flexibility for the hardware you actually own. CPU-only laptop, Apple Silicon, Nvidia GPU — one codebase covers all three, rather than three separate tools. Quantized KV (--ctk q8_0 on Metal) helps when context length starts eating unified memory.

What isn’t done yet

I’d rather undersell this than oversell it:

  • CUDA performance work is paused. The suite supports --backend cuda, but there is no current CUDA pin — needs a GPU host.
  • Metal prefill still needs watching against llama.cpp on larger models — decode is where the parity numbers live; prompt_per_second has more room.
  • MoE on Metal improved a lot, but OLMoE is still ~1.8× behind llama.cpp. Qwen2-MoE / Mixtral pins are missing on Host B (GGUF / RAM).
  • Gemma-4-E2B is explicitly refused today (needs a dedicated engine path) — both Ferrox and Homebrew llama.cpp reject it.
  • Frontier-scale MoE / MLA — Kimi, GLM, DeepSeek — have primitives and synthetic stacks, but no real multi-hundred-billion-parameter checkpoint has been run end-to-end. That is a hardware problem as much as a software one.

Conclusion

Ferrox started as a way to actually understand inference internals instead of treating them as a black box behind a pip install. It turned into something I’d genuinely reach for: a single binary that loads a GGUF file and either chats in the terminal or serves an OpenAI-compatible API, at speeds that hold up against the reference implementation on real hardware, with the receipts to prove it.

If you’re curious how quantized inference works under the hood, want a dependency-free way to run open models locally, or just want to poke holes in the benchmark methodology, the repo is Apache-2.0 and open for issues and PRs.

Further resources:


Building Ferrox from August 2026, on why performance claims should come with receipts.

联系我们 contact @ memedata.com