VRAM 10GB の RTX 3080 で 35B の MoE を常用環境にする — FreeToken + Ornith 1.5 実測ノート
私が所持している RTX 3080 の VRAM は 10GB です。Ornith-1.5-35B-A3B の NVFP4 版は 23.5GB あります。入りません。
それでも動かせて、しかも 9B の dense モデルより省トークンで速くタスクが終わるという結果になったので、実測をまとめてみました。
結論から書くと、執筆時点(2026年9月)で私の環境では FreeToken + Ornith-1.5-35B-A3B-NVFP4 が一番しっくりくる構成になりました。
なお、比較に使った論理パズルは記事末尾の付録にそのまま載せてあります。答えが一意に決まるので自己採点できます。お手元の環境で試された方は、モデル名 / 量子化 / 推論エンジン / GPU / 正誤 / 生成トークン数 / 所要時間をコメントで教えていただけると嬉しいです。同じ問題を同じ条件で回した数字が集まると、単体のベンチマークでは見えない傾向が出てくるかもしれません。
検証環境
数字を再現したい方のために、CPU とメモリ速度まで書いておきます。
| 項目 | 内容 |
|---|---|
| ホスト | Proxmox VE 9 |
| ゲスト | Ubuntu 26.04(VM、20 vCPU、48GB RAM) |
| CPU | Core i7-12700K |
| メモリ | DDR4-3200 |
| GPU | RTX 3080 10GB(PCIe パススルー) |
| ドライバ | 610.43.02 / CUDA UMD 13.3 |
| CUDA Toolkit | 13.3 |
| モデル配置 | NVMe(lvm-thin 上のルートLV) |
GPU はパススルーなので、ホスト側と VRAM を共有していません。ゲストから見て 10240MiB がまるごと使えます。
Ornith 1.5 について
Ornith 1.5 は Qwen3.5 をベースにファインチューニングされたモデルファミリで、9B(dense)、35B-A3B(MoE)、397B(MoE)の3サイズが公開されています。terminal-bench や tool decathlon といった、エージェント/コーディング系の評価を前面に出しているのが特徴です。
今回はこの9B(dense)、35B-A3B(MoE)を比較しています。なお、qwen3.5では解けなかったという参考も記載しています。
なお公開されているベンチマークスコアはいずれもベンダー自己申告で、第三者による独立検証はまだ出ていません。ですので以下では、公開スコアではなく自分の環境で測った結果だけを書きます。
出発点:Ollama + ornith-1.5:9b はかなり優秀だった
もともとは Ollama + qwen3.5:9b で Open WebUI から Web 検索をさせる構成を使っていました。ここを ornith-1.5:9b に差し替えたところ、以前から悩まされていた挙動がいくつか改善しました。
- 検索が空振りしたときに、延々と同じクエリを投げ続ける現象が減った
- 取得に失敗したページの内容を「それらしく」埋めてしまうことがなくなった
- 検索スニペットではなく、実際に取得したページ本文を根拠にするようになった
特に効いたのは、Open WebUI の組み込み Web Search(埋め込み+RAG テンプレート方式)をやめて、Workspace > Tools に search / fetch を関数として登録し、Function Calling を Native に切り替えたことでした。これでツール定義がそのままモデルの tool calling 機構に渡るようになり、2周目でシステムプロンプトが差し替わる問題も消えました。
9B は 6.6GB で 100% GPU に載り、コンテキストも 102400 まで取れます。正直、これで割と使える構成だと考えてました。
と、そこへ、「FreeTokenは必要なエキスパートをVRAMに動的に載せ替える」というような記事を見つけてしまいました。
FreeToken を試す
FreeToken は MoE 向けの推論エンジンで、推論のたびに実際に使われるエキスパートだけを VRAM に呼び出し、LRU で入れ替えるという設計になっているそうです。
llama.cpp / Ollama がレイヤ単位でオフロードするのに対して、こちらはエキスパート単位です。MoE は総パラメータが大きくても 1 トークンで使うのは一部だけなので、この粒度の差が効いてきます。Ornith-1.5-35B-A3B は 256 エキスパート中 8 個、約 3B 分しか活性化しません。
OpenAI 互換 API(ポート 1919)を持っているので、Open WebUI からは Ollama と別の接続として並べて登録できます。
導入
# CUDA Toolkit(ドライバは入れ替えないよう cuda-toolkit-13-3 を明示指定)
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2604/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update && sudo apt install -y cuda-toolkit-13-3
echo 'export PATH=/usr/local/cuda/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc
# FreeToken(Python 3.12 を明示。3.14 では torch のホイールが揃っていません)
mkdir -p /opt/freetoken && cd /opt/freetoken
uv venv --python 3.12 && source .venv/bin/activate
uv pip install "freetoken[accel]"
# モデル(23.5GB)
hf download ornith-ai/Ornith-1.5-35B-A3B-NVFP4 --local-dir /opt/models/ornith-35b-nvfp4
起動
ft serve \
--model /opt/models/ornith-35b-nvfp4 \
--host 0.0.0.0 --port 1919 \
--moe-backend offload \
--moe-cache-size 1500 \
--max-seq-len-override 65536 \
--max-running-requests 1 \
--max-prefill-length 4096 \
--kv-reserve-tokens 65536
起動ログの要点です。
Free memory before loading model: 9.37 GiB
NVFP4 expert backend: triton
Allocating 142696 tokens for KV cache, K + V = 2.72 GiB
Free memory after initialization: 0.68 GiB
API server is ready to serve on 0.0.0.0:1919
エキスパートは 10240 個中 1500 個を GPU に常駐(約15%)、残りはホスト RAM から都度呼び出す形です。KV キャッシュは 142696 トークン分。サービスの起動から応答可能になるまでは約50秒かかります(大半はエキスパートバンクの構築です)。初回のみ、これに加えて Triton の JIT コンパイルが走るため、CUDA グラフのキャプチャに30秒ほど余計にかかります。
ただしこれはサービスを起動したときだけの話です。FreeToken は Ollama の keep_alive のようなアイドル時のアンロード機構を持たず、一度立ち上がればモデルを VRAM に保持したまま常駐します。チャットのたびにロードが走ることはありません。
つまずいた箇所
検索で辿り着く方のために、実際に出たエラーをそのまま残しておきます。
_POSIX_C_SOURCE redefined が大量に流れる
/usr/include/features.h:319:10: note: this is the location of the previous definition
319 | # define _POSIX_C_SOURCE 202405L
これはエラーではなく gcc の警告です。Triton が CUDA カーネルを JIT コンパイルするたびに出ます。放っておけば進みます(私は一度これで止めてしまいました)。
FileNotFoundError: [Errno 2] No such file or directory: 'ninja'
systemd から起動したときだけ出ました。venv 内の ninja が PATH にないためです。unit の Environment=PATH= に /opt/freetoken/.venv/bin を含めれば解決します。
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 104.00 MiB
--moe-cache-auto は空き VRAM をエキスパートで埋め尽くすため、残り 0.66GiB しかない状態で GDN の prefill が一時バッファを要求して落ちました。--moe-cache-size を手動指定して余白を作ると解決します。
なお --kv-reserve-tokens は --moe-cache-auto 併用時の下限指定です。--moe-cache-size を明示した場合には効かないようで、上記の起動オプションで --kv-reserve-tokens 65536 を渡していても、実際には残り VRAM いっぱいの 142696 トークン分が確保されています。
ft serve: error: unrecognized arguments: --max-extend-tokens
内部の変数名は max_extend_tokens ですが、CLI のフラグ名は --max-prefill-length です。
systemd unit
[Unit]
Description=FreeToken MoE inference server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=masa
Group=masa
WorkingDirectory=/opt/freetoken
Environment="PATH=/opt/freetoken/.venv/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Environment="LD_LIBRARY_PATH=/usr/local/cuda/lib64"
Environment="CUDA_HOME=/usr/local/cuda"
ExecStart=/opt/freetoken/.venv/bin/ft serve \
--model /opt/models/ornith-35b-nvfp4 \
--host 0.0.0.0 --port 1919 \
--moe-backend offload \
--moe-cache-size 1500 \
--max-seq-len-override 65536 \
--max-running-requests 1 \
--max-prefill-length 4096 \
--kv-reserve-tokens 65536
Restart=on-failure
RestartSec=10
TimeoutStartSec=600
TimeoutStopSec=60
KillSignal=SIGINT
[Install]
WantedBy=multi-user.target
TimeoutStartSec を長めに取っているのは、初回起動時に Triton の JIT コンパイルが走るためです。KillSignal=SIGINT は uvicorn が SIGINT で正常終了する作りのためです。
比較
10GB という制約のもとで、それぞれ最良と思われる形で動かして比べました。
| ornith-1.5:9b + Ollama | Ornith-1.5-35B-A3B-NVFP4 + FreeToken | |
|---|---|---|
| 量子化 | GGUF | NVFP4 |
| 配置 | 全レイヤ GPU | エキスパート 1500/10240 を GPU 常駐 |
| VRAM 使用 | 8557 MiB | 9715 MiB |
| 生成速度 | 85.9 tok/s | 45〜60 tok/s |
素の生成速度では 9B が 1.5 倍ほど速いです。
同じ問題を解かせる
生成速度そのものではなく、タスクが終わるまでの時間を見たかったので、答えが一意に決まって機械的に採点できる問題を使いました。25人の正直者・嘘つきパズル(正直者はちょうど11人)です。問題文は記事末尾の付録に載せてあります。
結果です。
| ornith-1.5:9b | 35B-A3B | |
|---|---|---|
| 生成トークン | 39,865 / 40,922(2回) | 約14,500 |
| 所要時間 | 7分59秒 / 8分04秒 | 4分37秒 |
| 生成速度 | 85.9 / 85.6 tok/s | 45〜60 tok/s |
| 正誤 | 両回とも正解 | 正解 |
速い方が 1.7 倍の時間をかけました。 9B は 2.7 倍のトークンを使っています。
推論の中身にも差がありました。35B は 23番を正直者と仮定して16番の発言と矛盾することを示す背理法から入り、24番で場合分けして片方を「正直者が13人になる」という理由で棄却し、最後に25発言すべてを検算して一意性まで示しました。
9B も同じ答えに到達しましたが、途中で3回間違えて3回とも自力で修正しています。「17番がそう言っているから23番は嘘つき」と決めつけて全部やり直したり、数え上げで1人分を落として12人になったことに気付いて式を立て直したり、という具合です。正解にはたどり着くものの、そこまでの手数が多くなっています。
参考:ベースモデル(qwen3.5:9b)
Ornith 1.5 のベースである Qwen3.5 の同サイズでも同じ問題を投げてみました(Ollama、1回のみ)。
結果は 2, 5, 6, 7, 9, 10, 13, 14, 18, 20, 25 で、不正解でした。人数が11人という条件は満たしていますが、正解とは6人分ずれています。
興味深いのは、添えられていた検証部分が自己矛盾していたことです。
- 2番を正直者としながら、その検証で「6番と5番はどちらも正直者、矛盾なし」と書いています。5番と6番が両方正直者なら、2番の発言「6番と5番の少なくとも一方は嘘つき」は偽になるので、2番は嘘つきでなければなりません
- 7番の検証で3番を正直者として数えていますが、自分が挙げた正直者リストに3番は入っていません
- 「他の発言についても同様に検証可能です」と書いて、実際の検証を打ち切っています
- 一意性を示さないまま「この組み合わせが唯一の解となります」と断定しています
答えが違うこと自体より、検証したと書いてある部分が検証になっていない点の方が実用上は問題になりそうです。ornith-1.5:9b は同じサイズで正解し、しかも途中の誤りを自力で修正していました。1回ずつの比較なので断定はできませんが、ファインチューニングの効果を自分の目で確認できた形です。
消費電力
これは予想外でした。
| Watt | GPU-Util | |
|---|---|---|
| Ollama + 9b | 338W / 340W | 91% |
| FreeToken + 35B | 201W / 340W | 100% |
GPU 使用率が高い方が電力を食っていません。FreeToken 側は PCIe 越しのエキスパート転送で待つ時間が長く、GPU-Util は「カーネルが載っている割合」なので転送待ちも 100% に計上されます。実際に SM が回っている時間は 9B より短いということかと思われます。
タスク完了までのエネルギーで見ると差はさらに開きます。
- 9B: 338W × 479秒 ≒ 162 kJ
- 35B: 201W × 277秒 ≒ 56 kJ
約3倍です。
CPU 側も穏やかでした。推論中の VM 全体の CPU 使用率は 5.4% 前後で平坦、メモリは 48GB 中 32GB 前後で安定しています。エキスパートの計算が CPU に降りてきていないことがグラフからも見て取れます。
考察
素の tok/s は 9B の勝ちで、タスク完了時間は 35B の勝ちでした。トークン単価では勝っているのに、タスク単価では負けている、ということになります。
MoE のオフロードは「大きいモデルを無理やり動かすための妥協」だと思っていたのですが、この条件では違いました。1トークンあたり 3B 分しか活性化しないので、GPU 側の計算量は 9B の dense とそこまで変わりません。増えるのは転送待ちだけで、その代わりモデルの容量が 4 倍近くになる。10GB という制約の中では、かなり割の良い交換になっています。
エキスパートの常駐率も、思っていたより効きませんでした。3059 → 1973 → 1500 と減らしても生成速度はほとんど変わっていません。256 個中 8 個しか使わない疎さに加えて、実際のアクセスに偏りがあるためではないかと思われます。「VRAM を埋めるほど速い」という直感は、少なくともこの構成では成り立ちませんでした。
そして KV キャッシュを 142696 トークン分確保できていることが、この用途では効いています。39,865 トークンの生成が必要なタスクは、コンテキストを 8192 に切り詰めた設定では最後まで到達できません。ベンチマークで速度だけを測っていると見えない領域です。
留保
- 各条件 1〜2 回の測定です。9B のみ 2 回測って再現を確認しました。参考として載せた qwen3.5:9b は 1 回のみです
- 9B は GGUF + Ollama、35B は NVFP4 + FreeToken で、エンジンも量子化形式も異なります。「10GB VRAM でそれぞれ最良の形にした結果の比較」であって、モデルサイズだけを変えた比較ではありません
- 消費電力は
nvidia-smiの瞬時値です。区間積分ではありません - 評価に使ったのは論理パズル1問です。他のタスクで同じ傾向が出るかは確認していません
- 出力の日本語には両モデルとも乱れが出ています(35B に「第一步」「至少」、9B に「ちょうど」→「恰好」など)
- 9B の 2 回目は、答えは正解でしたが説明部分に矛盾がありました。答えの正しさと説明の正しさは別に見る必要があります
- Ornith 1.5 の公開ベンチマークはベンダー自己申告で、第三者検証は出ていません
- FreeToken は起動時にモデルをロードして常駐します。Ollama と VRAM を取り合うため同時には動かせません
- NVFP4 は本来 Blackwell 世代の形式です。Ampere では Triton 経由で動いており、新しい世代と同じ効率は出ていない可能性があります
- llama.cpp 側にも
--n-cpu-moeというエキスパート単位のオフロード機能があります。今回は試していません
付録:使用した問題
同じ条件で試せるよう、問題文をそのまま載せておきます。プロンプトはこの表に「正直者は何番の人?」と付けただけで、システムプロンプトやヒントは与えていません。
この問題は、界隈では有名な「ゆっくり解説」の以下の動画で扱われていた形式を参考に、25人版として Claude Opus 5 (High) に生成してもらったものです。
問題(25人版・正直者はちょうど11人)
正直者は本当のことだけを言い、嘘つきは嘘だけを言います。
人 発言
1 18番と6番はどちらも嘘つき
2 6番と5番の少なくとも一方は嘘つき
3 11番と5番はどちらも正直者
4 13番は嘘つき
5 19番と17番はどちらも嘘つき
6 18番・10番・2番のうち正直者はちょうど2人
7 1番・3番・25番のうち正直者は2人以上
8 24番・23番・22番のうち正直者はちょうど1人
9 12番・8番・25番のうち正直者はちょうど2人
10 5番と19番はどちらも嘘つき
11 4番・10番・19番のうち正直者はちょうど2人
12 24番と4番の少なくとも一方は嘘つき
13 20番・10番・16番のうち正直者は2人以上
14 21番は嘘つき
15 2番と21番はどちらも正直者
16 17番と3番の少なくとも一方は嘘つき
17 23番は嘘つき
18 24番は嘘つき
19 23番・13番・24番のうち正直者は2人以上
20 9番・17番・8番のうち正直者は2人以上
21 23番・22番・24番のうち正直者はちょうど2人
22 3番と11番の少なくとも一方は正直者
23 16番は嘘つき
24 7番と8番の少なくとも一方は正直者
25 4番と23番の少なくとも一方は嘘つき
正直者は何番の人?
正解:2, 9, 10, 12, 13, 14, 16, 17, 18, 20, 25(嘘つきは残りの14人)
2の25乗(約3355万通り)を総当たりして、正直者ちょうど11人という条件下で解がこの1通りだけであることを確認しています。
採点しやすいように、この問題は次のような性質を持たせてあります。
- 答えが一意 — 「11人」という条件が最後の絞り込みではなく、途中の場合分けの棄却にも必要になります
- 偽の解が存在する — 各人の発言とは矛盾しないのに、正直者の人数が11人にならない組み合わせがあります。局所的な整合だけで満足すると、ここで止まります
- 単純な2彩色に落ちない — 「Xは正直者/嘘つき」だけなら発言は「同じ種類/違う種類」のリンクに還元できてしまいます。連言(どちらも〜)は片方向にしか効かず(話者が正直者なら両方確定、嘘つきなら「少なくとも一方は違う」という弱い情報しか出ない)、これを混ぜることで還元を防いでいます
部分点も取れるので、正誤だけでなく「何人分が正しかったか」でも比較できます。
まとめ
LLM 周りは日々変わっていて、この記事の内容も来月には古くなっているかもしれません。llama.cpp の --n-cpu-moe のように、同じ問題に別のアプローチで取り組む実装も出てきています。
それでも執筆時点の私の環境では、FreeToken + Ornith-1.5-35B-A3B が一番しっくりくる構成になりました。速度で選んだのではなく、タスクが終わるまでの時間と、答えに至る過程の確からしさで選んだ形です。
10GB のカードをお持ちで、9B クラスで頭打ちを感じている方には、試してみる価値があるかと思います。