Local LLM · Build Log

Tesla V100 ×2 · llama.cpp

NVLink なしのV100 2 枚で
180B MoE を常用する

NVLink も P2P もない Tesla V100 ×2 のサーバで、Qwen3.8-Flash-Next(180B MoE)を 256K コンテキストで普段使いしている。llama.cpp に手を入れて、prefill を約 585 t/s から 2,361 t/s まで上げた。そのときの構成、数字、うまくいかなかったことのメモ。

数値はすべて自宅環境での実測 改造パッチ(GitHub)
Prefill · 256K · 170 W現行 V6
2,361.3t/s

改造前の約 585 t/s から 約 4.0 倍
boost 250 W:最大 2,591.5 t/s(depth 128K)

4.0×
prefill(改造前比・概算)
約 585 → 2,361.3 t/s
262,144
常用のコンテキスト長
256K 設定
95.7t/s
実使用の decode
中央値・254 リクエスト・普段の電力設定
0回
落ちた回数
V6 切替後の約 12 時間
Setup

GPU 間は PCIe だけで、片側は x4。層分割+パイプライン並列しか選べないので、2 枚の処理の重なりを切らさないことが速度をほぼ決めた。

Result

MoE をテンソルコアで回す自作カーネルと、同期・メモリ予約まわりの修正を重ねて、prefill は改造前の約 4 倍。

Open

実際の作業での decode 100 t/s には届いていない。MTP の受理率と、decode 側のカーネルが次の課題。

01System

構成

GPU は Tesla V100-PCIE-32GB と Tesla PG500-216(中身は V100 PCIe 32GB)の 2 枚。NVLink はなく、GGML_CUDA_P2P を有効にすると出力が壊れたので P2P も使っていない。さらに V100 はチップセット側のスロットで、電気的には x4 になっている。

80 GiB のモデルは tensor-split 26,24 で層ごとに 2 枚へ分け、パイプライン並列で動かしている。層の受け渡しは毎回ホスト経由になる。

ハードウェアと接続太線 = PCIe Gen3 x16 / 細線 = PCIe Gen3 x4 / 赤破線 = 使えない直結経路
x16 · CPU 直結 x4 · チップセット経由 MAIN RAM DDR4 30 GiB PLE 26.8 GiB(mmap) CPU Ryzen 7 5700X3D 8C / 16T CHIPSET X570 V100 はこちら側 CUDA0 · split 26 Tesla PG500-216 32 GB · Volta CUDA1 · split 24 Tesla V100-PCIE-32GB 32 GB · Volta NVLink なし · P2P なし。層の受け渡しはホスト経由

PLE(per_layer_token_embd)は約 27 GiB あり、VRAM には載せずホスト RAM に mmap している。中身は 2-gram・3-gram のハッシュ表 16 本(各約 2,000 万行)で、1 トークンごとに散らばった 16 行を読む。RAM 30 GiB に対してこれなので、メモリにはほとんど余裕がない(07 参照)。

GPUTesla V100-PCIE-32GB + Tesla PG500-216(各 32 GB)
接続PG500:PCIe Gen3 x16(CPU 直結)/ V100:PCIe Gen3 x4(チップセット経由)
CPU・RAMRyzen 7 5700X3D / DDR4-3000 30 GiB
ストレージモデル用に NVMe 500 GB を追加。ロードが 144–156 s から 41–45 s に
モデルQwen3.8-Flash-Next GGUF 80.0 GiB(MTP 込み)experts UD-IQ3_XXS / dense Q8_0 / KV q8_0
ソフトllama.cpp 0.5.0 + 有志の Qwen3.8 対応パッチ + 自前の修正(qwen38-v100-patches)CUDA 12.9.86・sm_70・Ubuntu 26.04.1
サーバ設定192K(196,608)/ 256K(262,144・常用)/ 320K(はみ出し用・YaRN 1.25・MTP なし)前段のルーターがリクエスト長で振り分け
電力常用は PL 170 W(prefill 中 170 W・それ以外 150 W に自動で切り替え)。数字は特に断りがなければ 170 W、03 章と V6 boost だけ 250 W
02Progress

prefill の推移

出発点の V0 は、配布元のパッチに上流でマージ済みの PLE 先読み(#29599)を移植し、192K 設定・ubatch 512 にした状態で約 585 t/s。パッチを当てただけの 256K 設定では 362〜419 t/s で、配布元のモデルカード(DGX-1 の V100-SXM2 ×2・層分割で 710 t/s)の 5〜6 割だった。

V1 以降は構成を切り替えるたびに、256K 設定で合言葉入りの長い入力(05 の needle 検査)を流して prefill を測っている。V2 以降は 74,627 トークン、V1 だけ 50,139 トークン。電力は常用の PL 170 W。

構成ごとの prefill256K 設定・切替直後の検査で計測(t/s)。V2〜V6 は 74,627 トークン、V1 は 50,139 トークン
V0改造前
約 585別条件
V1
1,081.5+約 85%
V2
1,102.9+約 2%
V3
1,797.8+63.0%
V4
1,948.7+8.4%
V5
2,089.6+7.2%
V6
NOW
2,361.3+11.0%*
V6boost
2,591.5250 W・別条件

V0 は参考値で、192K 設定・ubatch 512・入力 32K / 64K・PL 170 W・MTP ありで測った 578〜590 t/s。V6 boost は PL 250 W で depth 別に測ったときの最大値(depth 128K・3 回平均・03 参照)。増減は一つ前の構成との比較で、検査の条件が違う V0→V1・V1→V2 は概算。* V6 だけは、モデルを NVMe に移したあと同日に V5 を測り直した値(2,127.2 t/s)との比較。

V0
自前の改造前。配布元の Qwen3.8 対応パッチ + 上流の PLE 先読みの移植、192K 設定・ubatch 512・層分割・P2P なし
V1
全層を GPU に載せてパイプライン並列を有効にし、スケジューラの入力コピー数を 2 に減らした構成
V2
MTP の追いつきを KV の書き込みだけに + QSA の chunk 固定
V3
MoE 用テンソルコアカーネル(第 2 版)+ 同期・メモリ予約の修正 + ubatch 512 → 1,536
V4
カーネル第 3 版
V5
MTP の追いつき処理の非同期化 + カーネル第 4 版 + GDN のチャンク計算・QSA の dp4a 化
V6
層の境目で GPU 間に送るデータを減らす(RMSNorm を受け側の GPU へ移す)+ MoE のタイル調整・融合 + QSA の改良。現在の常用構成

V3 の +63% はカーネル単体の効果ではない

V3 ではカーネルの差し替えと同時に、同期まわりの修正と ubatch の拡大も入れている。カーネル以外の条件を揃えてモデル全体の prefill で比べると、第 2 版は既存の MMQ 比で +6% 前後(192K 設定・250 W。256K では +2%)、第 3 版は第 2 版比で +9% 前後だった。

03Depth

depth と速度

V6 を depth ごとに測った結果(256K 設定・各 3 回の平均)。この章と 02 の V6 boost だけ PL 250 W(boost)で、常用の 170 W で測った他の章の数字とは直接比べられない。

prefill は depth 32K〜190K で 2,309〜2,592 t/s、ピークは 128K。depth 250K でも 2,181 t/s 出る。decode は depth にあまり影響されず 121〜128 t/s。電力・温度による減速は一度もなかった。

depth 別 prefill(t/s)V6・256K 設定・PL 250 W(boost)・3 回平均。横軸は実スケール、縦軸は 1,200 から
1,2001,6002,0002,4002,800 064K128K192K256K depth 2,592(128K) 2,181(250K) 1,760

depth 0 は 1,978 トークンの新規プロンプト。32K 以降は、前の段に 3.2 万〜6.7 万トークンを足したときの prefill。

depth 別 decode(t/s)V6・256K 設定・PL 250 W(boost)・3 回平均・合成テキスト・MTP あり
depth032K64K128K190K250K
decode123.1122.2128.0125.8124.3121.2

合成テキストと実際の作業で、decode が大きく違う

設計・レビュー・質問回答の 3 種のプロンプト(temperature 0.7・top_p 0.9)では、それぞれ 99.1 / 78.5 / 81.3 t/s。MTP の受理率が合成テキストの 94〜98% から 45〜66% に落ちるのが主な理由で、decode を伸ばすならここになる。

04Work

やったこと

4.1設定

起動オプションと環境変数で変えられるものは一通り試した。

ubatch / batch 512–61,440tensor-split 26,24--fit offcache-ramMTP n2–n4MTP の追いつき幅CUDA graphPL 150 / 170 Wmin_p 0

nsys で見ると、ubatch をある程度以上大きくしても CPU 側のカーネル起動が詰まるだけで速くならなかった。設定だけで詰められる範囲はわりと早く尽きた。

4.2llama.cpp への修正

修正はすべて環境変数で切り替えられる。

MoE をテンソルコアで計算する

64 トークン以上の MoE の行列積について、低ビットの重みを共有メモリ上で FP16 に展開し、Volta のテンソルコアで計算するカーネルを書いた。第 1 版は展開のコストが重く、既存の MMQ より遅かった。モデル全体の prefill で、第 2 版は MMQ 比 +6% 前後(192K 設定・250 W)、第 3 版は第 2 版比 +9% 前後。精度は CPU の計算結果と突き合わせて確認している。

第 1 版 遅い→第 2 版 +6%→第 3 版 +9%→第 4 版

パイプラインの重なりを切らさない

計算バッファの取り直し、グラフの作り直し、入力転送のどれかで 2 枚が同期すると、重なりが途切れる。メモリ予約を先に済ませる、QSA の chunk サイズを固定する、予約用グラフを小さくする、KV の参照範囲の丸め方を変える、入力転送の同期を外す、といった修正で同期点を減らした。計算の内容は変えていない。

MTP を軽くする

prefill 中の MTP の追いつきは KV の書き込みだけにして、次の ubatch の後ろへ回した。追いつき幅は 128 トークン。ドラフト側の出力語彙は頻出 65,536 語に絞っている(検証は全語彙なので出力は変わらない)。

Volta 向けの書き換え

新しい世代の GPU 前提で書かれている処理を、Volta で動くように置き換えたもの。

GDN のチャンク処理QSA の dp4a 化MoE タイルの詰め込みMoE 処理の融合GPU 上での top-kパイプラインの空き解消

4.3試してやめたもの

案結果判定
CPU 経由で 2 枚同時に decode実測で効果なし不採用
ubatch 2048 / 40962048 は 1536 比 −3.6〜−10.7%。4096 は出力が壊れた不採用
batch 61,440短い入力は +4.1%、64K で −4.1%、128K で −2.8%不採用
QSA ブロック 256/512・4 件まとめ単体では +44%、モデル全体では −5〜−7%不採用
Q8 経路の mmvq 高速化出力は完全一致したが、目標の平均 +2% に届かず不採用
MTP ドラフトの余分な前処理を削る1 ステップあたり 0.4% しか変わらず、再ビルドで起動が 8 分延びる不採用
--moe-expert-cacheQ4 版での試験で decode が 30 → 21 → 19 t/s。CPU に回る expert が増え、キャッシュヒットで取り返せない不採用

4.4Strata との比較

別の推論エンジンの Strata も試している。

Strata v0.1.40.4 と V6 へ同じプローブを当てて測った。条件は PL 170 W、思考なし、greedy。

同じプローブでの比較Strata v0.1.40.4 と V6(llama.cpp)・PL 170 W・思考なし

prefill(t/s)ロード直後の 1 回目・目盛 0–2,400

26.7K
Strata1,558.9
V61,780.5+14.2%
52.0K
Strata2,041.5
V62,247.3+10.1%
103.9K
Strata2,118.5
V62,364.7+11.6%

decode(t/s)greedy・2 回平均・目盛 0–125・破線 = 100 t/s

英語
Strata94.0
V698.4+4.6%
日本語
Strata103.9
V6108.9+4.9%
コード
Strata118.0
V6119.7+1.4%

prefill は 1 回ずつ。V6 の 2 回目は 2,161.0 / 2,296.9 / 2,370.7 t/s で、26.7K だけ 1 回目が低い。decode は 2 回の平均どうしの差。

同じ測り方なら、V6 が prefill で 10〜14%、decode で 1〜5% 速い。Strata も 0.1.39 から 0.1.40.4 で decode が平均 6.6% 伸びている(prefill はほぼ同じ)。

  • 揃っていない条件 GGUF が別物(V6 は IQ3E-Q8D-MTP、Strata は IQ3_XXS とその pack)。ドラフトは V6 が MTP n-max 3・probabilistic、Strata は spec 6。KV は q8_0 と int8。こちらのエンジンの方が若干不利な条件となっている。

思考なし・greedy の短い質問なら V6 は日本語 108.9、コード 119.7 t/s で 100 を超える(英語は 98.4)。ただ、実際の作業に近いプロンプトでは 78〜99 t/s のまま(03・250 W)。エンジンを替えても同じ測り方で V6 を上回るものはなかった。

05Workflow

検証の回し方

実装と測定には AI コーディングエージェントを使ってほぼ無人で回した。

  1. 計画試す構成と測定を書く
  2. 投入形式チェック・ハッシュ照合をしてキューへ
  3. 実行キューから順に起動
  4. 測定コールド → ウォーム(短文・連続・長文・画像)
  5. needle 検査正解しなければ結果は捨てる
  6. 記録採用・不採用を記録

needle 検査

2,600 行のログ風テキストの途中 1 行に合言葉を埋め込み、全体を 74,627 トークンにして答えさせる。

……(ログ 2,600 行)…… アーカイブの合言葉: VIOLET-HARBOR-7342 ……(続く)……

長文の中の 1 点を拾えるかの確認と、毎回同じ入力での速度計測を兼ねている。02 のグラフの V2〜V6 はこの検査での値(V1 は 50,139 トークン版)。

  • 優先順位 効果が大きそうな順にキューを並べ替える。新しく積む前に、既存の測定で答えが出ていないか確認する。
06In Use

常用での実績

V6 に切り替えてからの約 12 時間、Qwen Code 0.25.0(VS Code 拡張)から普段どおり使ったときのログを集計した。

266
完了したリクエスト
36
クライアント側でキャンセル
0
再起動・fatal・OOM
1,968.8t/s
prefill 中央値(入力 4,000 トークン以上の 59 件)
95.7t/s
decode 中央値(出力 64 トークン以上の 254 件・最小 64.6)
175,212
最長のコンテキスト(トークン)

VRAM は 256K 設定で PG500 側が 31,400 MiB まで埋まる(残り約 1.3 GiB)。

6.1前に使っていた 27B との比較

それまで使っていた Qwen3.8-27B(dense・Q6)と、同じ課題セットで比べた。

一般課題140 問の正答数
Flash-Next138
27B136
実コードの修正18 件の解決数
Flash-Next18
27B18
1 件あたりの時間短いほど良い
Flash-Next52 秒
27B347 秒

正答数に意味のある差は出なかった(一般課題 p=0.69)。課題が易しすぎて両方とも上限近くに張り付いている。一方、修正 1 件にかかる時間は 6.7 倍違ったので、普段使いは Flash-Next に切り替えた。

07Trouble

トラブルと対策

無人で回していると、同じ失敗に気づかないまま何度も繰り返しやすい。起きたものは、運用ルールを足すよりスクリプト側で防ぐようにした。

#起きたこと対策
1改行コードで計画が誤作動計画ファイルに CRLF が混じり、チェックを通っていない設定で走ったキューへの投入をスクリプト経由に限定(形式チェック・ハッシュ照合・アトミックな配置)
2空の応答のまま 25 分空回り測定結果が妥当かを確認せずに受け取っていたマルチターンの模擬測定に、応答の中身を確かめるターンを追加。おかしければ無効として扱う
3エージェントが作った検査で 5 回停止レビューせずにそのまま実行した受け入れ前の確認手順を決め、実機での dry-run を必須にした
4死活監視がモデルをアンロードするルーターが叩く /slots でモデルがメモリから退避されていたサーバが空くのを待ってから操作するようにした
5PLE と RAM の取り合いbatch 16,384 で direct reclaim が 21 倍に増えた当時は batch 8,192 に戻した(V3 以降は ubatch 1,536 / batch 15,360)
6早期 EOS を疑った件短い応答で止まる退行を疑い、302 回ずつ再実行して比較(p=0.088)V6 の退行ではなく temperature 1 のサンプリングの性質と判断。min_p 0 を採用
08Notes

まとめ

  1. NVLink なしの 2 枚構成では、重なりを切らさない修正の積み重ねが効く

    MoE の計算は GPU 時間の 30〜40% 程度。残りの多くは同期とメモリ予約、カーネルの積み方の整理で縮んだ。

  2. Volta でもテンソルコアで MoE を回す価値はある

    ただし第 1 版は既存の MMQ に負けた。第 2 版で上回り、第 3 版で差を広げた。

  3. batch の境目ごとに 1 秒を超える直列待ちがあった

    MTP の追いつきなどが原因で、ここを数字で押さえられたことが V5 の修正につながった。

  4. 無人検証で一番多かった失敗は、測定側の不備

    空の応答を成功扱いにしない、レビューしていない検査を走らせない。モデルよりも測る側で事故が起きた。

AConditions

測定条件

構成ごとの prefill256K 設定・合言葉入りの長文(V2〜V6 は 74,627、V1 は 50,139 トークン)・常用の PL 170 W。切替直後の検査での値
depth 別V6・256K 設定・PL 250 W・各 3 回平均・2 GPU の層分割で計測
Strata 比較PL 170 W・思考なし・greedy。prefill は 1 回、decode は 2 回平均
実使用V6・普段の電力設定・Qwen Code からのリクエストをサーバログで集計
解析nsys / ncu

llama.cpp への修正は GitHub の qwen38-v100-patches で公開している。配布元の v3 パッチの上に当てる差分で、V6 は版 pcie-20261006。