AI・機械学習
Llama.cppに匹敵するRust製推論エンジンの構築
Building a Rust Inference Engine That Matches Llama.cpp (fratepietro.com)
要約
この記事では、純粋なRustで書かれた新しいローカルLLM推論エンジン「Ferrox」を紹介しています。Ferroxは、llama.cppやggmlに依存せず、GGUF形式のモデルをCPU、Apple Metal、CUDA上で実行できます。作者は、推論の仕組みを深く理解し、パフォーマンスを実証するためにゼロから開発しました。ベンチマークでは、特にApple Metal上で一部のモデルにおいてllama.cppと同等かそれ以上の性能を示しています。
全文翻訳
過去数日間、Ferroxという、オープンなLLMをローカルで実行するための純粋Rust製推論エンジンを構築してきました。これは、CPU、Apple Metal、またはCUDA上で、密なモデルとMixture-of-Experts(MoE)の両方を実行できます。llama.cppやggmlへのバインディングはなく、既存のランタイムをラップしているわけでもありません。すべてのカーネル、すべてのローダー、すべてのスケジューリングの決定はゼロから書き直されました。
当然の疑問は「llama.cppが既に存在し、優れているのに、なぜか?」でしょう。正直な答えは、私は「バイナリを実行する」以上のレベルで推論を理解したかったのです。そして、パフォーマンスに関するあらゆる主張が、主張されるのではなく、実際のよく知られたベースラインに対して獲得されなければならないプロジェクトが欲しかったのです。
Ferroxとは何か
その核心において、Ferroxはllama.cppが使用するのと同じ量子化モデル形式であるGGUFファイルをロードし、それに対して推論を実行します。
使用方法は2通りあります。
1. CLIツール「ferrox」: llama.cpp互換のフラグを備えています。モデルを指定すれば、完了が得られます。
2. サーバー「ferrox-server」: OpenAIのchat-completions APIを話します。ChatGPTのAPIに対して構築されたUI、エージェントフレームワーク、テストハーネスなどは、localhostを指すだけで、変更なしで動作します。
内部では、モデルの重みはディスクから直接メモリマップされ、RAMに完全に展開されることはありません。デ量子化は、重みが実際に必要とされる瞬間に、ドット積に融合されて行われます。これはllama.cppが使用するのと同じトリックであり、両方のエンジンが30GBではなく数GBのメモリを搭載したラップトップで8Bパラメータモデルを実行できる大きな理由の一つです。
試してみてください: ビルドし、GGUFをダウンロードし、Ferroxを実行します。
Ferroxは重みを配布しません。llama.cppと同じ方法でローカルの.ggufをダウンロードします。Hugging Face CLI(pip install -U huggingface_hub)をお勧めします。
```bash
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 スモークテスト
hf download TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF \
tinyllama-1.1b-chat-v1.0.Q8_0.gguf --local-dir models
# オプション: 小さなインストラクトチャットモデル (~0.8 GB)
hf download bartowski/Llama-3.2-1B-Instruct-GGUF \
Llama-3.2-1B-Instruct-Q4_K_M.gguf --local-dir models
```
READMEからの便利な開始点:
| Model | Hugging Face repo | File |
| ---------------------- | ---------------------------------------------------- | -------------------------------------------------- |
| TinyLlama 1.1B Chat Q8_0 | TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF | tinyllama-1.1b-chat-v1.0.Q8_0.gguf |
| Llama 3.2 1B Instruct Q4_K_M | bartowski/Llama-3.2-1B-Instruct-GGUF | Llama-3.2-1B-Instruct-Q4_K_M.gguf |
| Llama 3.1 8B Instruct Q4_K_M | bartowski/Meta-Llama-3.1-8B-Instruct-GGUF | Meta-Llama-3.1-8B-Instruct-Q4_K_M.gguf |
| SmolLM2 135M Instruct Q8_0 | bartowski/SmolLM2-135M-Instruct-GGUF | SmolLM2-135M-Instruct-Q8_0.gguf |
日常的な使用にはQ4_K_Mを、非常に小さなスモークテストにはQ8_0をお勧めします。
その他のGGUF: Hugging Face上のllama.cpp互換モデル。
Ferroxが現在検証しているもの: docs/MODELS.md。
その後:
```bash
# ワンショット完了
./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
# Metal上でのチャット(GGUFがチャットテンプレートをシップする場合のデフォルト)
./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互換サーバー
./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}'
```
フラグはllama.cpp(-m, -p, -n, -t, --temp, -ngl, …)をミラーリングしています — 完全なリファレンスはdocs/CLI.mdにあります。
知っておく価値のあるFerrox固有のノブは1つだけです: --ctk q8_0 (またはFERROX_CTK=q8_0) は、Metal上でKVキャッシュをデフォルトのf16の代わりにQ8_0として保存します。これは、精度をわずかに犠牲にして、長いコンテキストでのKVフットプリントを小さくします。
パフォーマンスが主張ではなく証明可能でなければならなかった理由
ローカル推論プロジェクトはすべて高速だと主張します。ほとんどがその方法論を示していません。私はその山にノイズを加えたいと思いませんでした。そのため、Ferroxのベンチマーク設定全体は、主張を偽造可能にするように構築されています。同じマシン、同じGGUFファイル、同じバックエンドで、両方のエンジンに対して、バックトゥバックで実行します。ウォームラン、グリーディデコーディング、複数回実行、中央値が報告されます。すべてのヘッドライン番号は、リポジトリ内のJSONレシートにピン留めされています — それを再生成すると、数字は保持されるか、されません。
その規律は、Apple M2 Pro(ホストB)で実際の成果をもたらしました。ヘッドライン番号は、benchmarks/RESULTS.mdのfair-chatスイートから予測されたtok/sです — Gapはllama / ferrox(1.0未満はFerroxが速いことを意味します。ほぼ同等は〜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× (同等 / Ferrox) |
| Llama-3.2-1B Q4_K_M | Metal | 140.8 tok/s | 140.8 tok/s | 1.00× (同等) |
| TinyLlama-1.1B Q8_0 | Metal | 117.9 tok/s | 113.7 tok/s | ~0.96× (同等) |
| 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× (ほぼ同等) |
| Phi-4-mini Q4_K_M | Metal | 50.0 tok/s | 53.1 tok/s | ~1.06× (ほぼ同等) |
私にとって最も重要な北極星のピンは、Llama-3.1-8B on Metalで、現在〜0.97×です — Ferroxは同じホストとGGUF(28.27 vs 27.55 pred)でllama.cppをわずかに上回っています。CLIのワンショットパスでは、正確に1.00×のタイ(28.85 vs 28.64)です。その瞬間、エンジンは学術的な演習のように感じられなくなりました。
最初のピンからのもう一つの大きな動きは、OLMoE on Metalです。初期のレシートはllama.cpp(〜10 tok/s)より約15倍遅れていました。MetalのExpert-placement作業後、88.4 vs 156.8(〜1.77×)に落ち着きました — まだ遅れていますが、全く異なるリーグです。Gemma-3 Metalは、遅れから先行に転じました。
完全な方法論とすべての生のピンは、RESULTSファイルにライブであります。
スイートの再実行
自分でレシートを再生成したい場合(同じホスト、同じGGUF、両方のエンジン):
```bash
python3 benchmarks/run_suite.py --skip-missing --fit-host
# オプションのCLIモードピン:
python3 benchmarks/run_suite.py --skip-missing --fit-host --mode cli
```
これは、benchmarks/receipts/pins/の下のピンを上書きし、render_results.pyを介してRESULTS.mdを再生成します。発明された数字はありません — ピンが欠落している場合、テーブルにその旨が表示されます。
--fit-hostは、マシンに適合しないモデルをスキップします(およびdarwinでのCUDAをスキップします)。
勝利がどこから来たか
最も多くの作業を行ったのは、2つのアーキテクチャ上の選択でした。
1. デ量子化を行列乗算に融合する。
量子化された重み(4ビット、8ビット)は、フルプレシジョンのバッファに展開されることはありません。デ量子化の数学は、ドット積の一部としてインラインで発生するため、キャッシュで一度だけコストを支払い、個別のメモリバウンドパスとして支払う必要はありません。
2. アーキテクチャ固有のGPUパス。
モデルは構造的に同一ではありません — QwenはヘッドごとのQK正規化を持ち、Gemma-3はスライディングウィンドウアテンションとGeGLUを使用し、Phi-3はQKVとFFNの射影を融合します。Ferroxは、これらのそれぞれに対して専用のMetalカーネルを実装しており、すべてのモデルを1つの汎用アテンションパスに強制するのではなく、それを行います。これが、小さなQwenおよびSmolLM2モデルがMetal上でllama.cppを大幅に上回る正確な理由です — カーネルは、必要としない汎用性のためのオーバーヘッドを支払うのではなく、実際の計算形状に一致します。同じ考え方がOLMoE Metalジャンプを推進しました: GPU上でのExpert-placementであり、MoEコスチュームを着た汎用密結合パスではありません。
ユーティリティ: なぜこれが単なるベンチマーク演習以上のものなのか
数字を超えて、Ferroxはモデルをローカルで実行するための本当に実用的な方法です。
* 単一の静的バイナリ。
Python環境、CUDAツールキットのバージョンのルーレット、pip installの依存関係解決はありません。バイナリをコピーし、.ggufファイルにポイントして実行します。
* 既存のツールへのドロップイン。
OpenAI互換サーバーは、既にChatGPTのAPIに接続されているあらゆるアプリケーション — LangChainスクリプト、カスタムチャットフロントエンド、評価ハーネス — が、ベースURLを1行変更するだけで、完全にローカルなモデルに対して動作することを意味します。
* MoEサポート、密結合モデルだけではありません。
OLMoE-1B-7Bは、検証済みのピンでCPUとMetal上で実行されます。Metal MoEは依然としてllama.cppに遅れをとっていますが、ギャップは縮まっています。これは、Mixture-of-Expertsが多くの最先端モデルの効率性の向上をもたらす場所であるため重要です。