【後編】HPE ProLiant DL385 Gen10 × TITAN RTX でローカル LLM ─ 推論エンジン 4 種と 17 モデルの比較

ローカルLLMGPU,llama.cpp,Ollama,SGLang,TITAN RTX,vLLM,ローカルLLM

はじめに

前編では、HPE ProLiant DL385 Gen10 に TITAN RTX を積み、電力上限を 240W に決めるまでを書きました。

→ 【前編】補助電源と電力上限の見極め

後編は、この GPU でどの推論エンジンとモデルを使うかを決めた話です。用途は、ツールを呼びながら調査を進めるエージェントです。エージェントは毎回、長いツール定義(数千〜数万トークン)を入力として読むので、入力の処理速度(prefill)が効きます。

先に結論です。

  • TITAN RTX(Turing、Compute Capability 7.5)では、llama.cpp 系(Ollama / llama-server)が prefill で vLLM の 3〜5 倍速い。生成速度はほぼ同じ
  • SGLang は生成が 7〜13 tok/s しか出ず、この GPU では実用になりませんでした
  • モデルは MoE 系(gemma4:26b-a4b、gpt-oss:20b、qwen3-coder:30b など)が速く、24GB の VRAM にも収まります
  • Ollama の OpenAI 互換 API(/v1)は思考(reasoning)が既定で有効です。ここに気づかないと、ツール呼び出しの精度を見誤ります

構成

Ollama は OS に直接入れた

本番で使う推論ランタイムは Ollama にし、Docker ではなく OS に直接入れて systemd で常駐させています。常駐させるものが 1 つだけなら、コンテナにしても層が増えるだけだからです。Docker の公開ポートは iptables を直接書き換えるので、ufw の制御を素通りしてしまう点も避けたかった理由です。

比較用のエンジン(llama-server / vLLM / SGLang)は Docker で動かしました。/etc/docker/daemon.json に "ip": "127.0.0.1" を書き、コンテナのポートを既定でループバックにだけ公開するようにしています。

Ollama の設定

# /etc/systemd/system/ollama.service.d/override.conf(抜粋)
Environment=OLLAMA_CONTEXT_LENGTH=65536
Environment=OLLAMA_NUM_PARALLEL=1
Environment=OLLAMA_KV_CACHE_TYPE=q8_0
Environment=OLLAMA_MAX_LOADED_MODELS=2
Environment=OLLAMA_KEEP_ALIVE=30m
Environment=OLLAMA_NO_CLOUD=1

最初はコンテキスト 32k × 並列 2 にしていました。ところが、エージェントの会話が 43.9k トークンに達したところで、Ollama が入力を黙って切り詰めていました。

Ollama は内部で llama-server を -c(コンテキスト長 × 並列数) で起動し、それを並列数で分け合います。32k × 2 は「64k を 2 人で分ける」設定で、1 リクエストあたりは 32k までです。エージェント用途では 64k × 1 に変えました。KV キャッシュは q8_0 に量子化して VRAM を節約しています。

Turing では FlashAttention 2 が使えない

エンジンの比較に入る前に、前提を 1 つ書いておきます。

TITAN RTX は Turing 世代で、Compute Capability は 7.5 です。FlashAttention 2 は Compute Capability 8.0 以上(Ampere 以降)が必要なので、この GPU では使えません。

  • vLLM は FlashAttention 2 を使えないため、Triton で書かれた代替のバックエンド(TRITON_ATTN)で動きます
  • SGLang は既定の FlashInfer が sm75 にも対応していますが、後で書くとおり生成(decode)が極端に遅くなりました
  • llama.cpp 系は独自の CUDA カーネルを持っているので、この制約の影響をあまり受けません

推論エンジンの比較

同じ Qwen3-14B を 4 つのエンジンで動かしました(電力上限 200W のとき)。

エンジン(バックエンド)形式prefill(tok/s)生成(tok/s)VRAMツール正答
Ollama(/v1、思考なし)GGUF Q4_K_M1,00450.614.4GB30/30
llama-server(Docker)GGUF Q4_K_M1,06351.114.4GB27/30
vLLM(TRITON_ATTN)AWQ 4bit35553.420.7GB30/30
vLLM(FLEX_ATTENTION)AWQ 4bit35253.020.8GB30/30
SGLang(FlashInfer)AWQ 4bit1,9337.620.4GB30/30
SGLang(triton)AWQ 4bit857.519.9GB30/30
  • 量子化の形式が違う点には注意してください(llama.cpp 系は GGUF Q4_K_M、vLLM / SGLang は AWQ 4bit)
  • OpenAI 互換 API では、prefill を最初のトークンが返るまでの時間(TTFT)から計算しています
  • ツール正答は、13 個のツール定義を渡して 10 問 × 3 回、正しいツール名を選べたかの割合です
  • vLLM はバックエンドを変えても差がありませんでした。起動にも約 3 分かかります
  • SGLang は prefill こそ速いものの、生成が 7.6 tok/s しか出ません。バックエンドを triton にすると prefill まで崩れました

モデルのサイズを変えても構図は同じ

モデルのサイズで差が変わるかを見るため、Qwen3 の 4B / 8B / 14B でも測りました(電力上限 240W、思考なし)。

サイズOllamallama-servervLLM
4B2,938 / 130.7─600 / 136.0
8B2,047 / 86.1─565 / 88.2
14B1,102 / 52.61,144 / 53.3364 / 55.4

数字は「prefill / 生成(tok/s)」です。SGLang は 14B で生成が成り立たなかったため、8B だけ再確認しました(200W で 3,292 / 13.4)。結果は同じでした。

どのサイズでも、prefill は Ollama が vLLM の 3〜5 倍、生成は vLLM が数 % 速い、という構図は変わりません。エージェントは長い入力を毎回読むので、prefill の差のほうが体感に効きます。本番のエンジンは Ollama にしました。

この評価は Turing だからこそです。FlashAttention 2 が使える Ampere 以降の GPU なら、vLLM の評価は変わるはずです。

モデルの比較

Ollama で 17 のモデルを測りました(電力上限 200W)。

モデル提供元種別VRAM / GPU 率prefill生成ツール日本語
gpt-oss:20bOpenAIMoE13.0GB / 100%2,76595.690%26
gemma4:26b-a4b-it-qatGoogleMoE15.3GB / 100%2,76293.690%43
glm-4.7-flashZ.aiMoE21.0GB / 100%1,26496.690%38
qwen3-coder:30bAlibabaMoE22.1GB / 100%1,830105.990%39
gemma4:31b-it-qatGoogledense19.8GB / 100%60025.180%42
qwen3.6:27bAlibabadense17.4GB / 100%60434.9100%39
qwen3.8:27bAlibabadense17.4GB / 100%60430.790%34
devstral-small-2:24bMistraldense20.1GB / 100%82832.5100%30
mistral-small3.2:24bMistraldense20.1GB / 100%82733.250%31
granite4.2:30bIBMdense23.7GB / 86%47716.530%19
nemotron-3-nano:4bNVIDIA小型4.0GB / 100%4,052143.680%26
gemma4:e4b-it-qatGoogle小型3.8GB / 100%3,56390.10%36
granite4.2:8bIBM小型11.2GB / 100%1,97378.735%17
Llama-3-ELYZA-JP-8BELYZA国産6.0GB / 100%2,42588.3非対応21
llm-jp-4.1-8b-thinkingNII国産10.2GB / 100%2,388─動作せず動作せず
deepseek-r1:14bDeepSeek蒸留15.7GB / 100%1,11650.8非対応19
deepseek-r1:32bDeepSeek蒸留23.7GB / 80%40312.7非対応26

測り方は次のとおりです。

  • prefill は約 7〜10k トークンの入力、生成は 500 トークンで、どちらも 3 回の中央値(tok/s)
  • ツールは、13 個のツール定義 × 10 問 × 3 回(思考なし。思考を切れない gpt-oss は low)で、正しいツール名を選べた割合
  • 日本語は、ネットワークの技術的な質問 3 問 × 3 軸(正確さ・自然さ・指示どおりか)× 5 点の 45 点満点。AI による仮の採点なので参考程度に見てください
  • 「非対応」は、チャットテンプレートがツール定義をプロンプトに載せないもの

分かったこと

  • MoE は速い。 gpt-oss:20b、gemma4:26b-a4b、qwen3-coder:30b、glm-4.7-flash は、prefill 1,200〜2,800 tok/s、生成 93〜106 tok/s でした
  • dense の 27〜31B は遅い。 prefill 600 前後、生成 25〜35 tok/s で、エージェントの応答としては待たされます
  • 24GB に収まらないモデルがある。 granite4.2:30b と deepseek-r1:32b は一部が CPU にはみ出し、大きく遅くなりました
  • 国産モデルは使いどころが限られた。 llm-jp-4.1 は Ollama では動きません(llama.cpp のチャット形式の解析が未対応で、公式の README にも明記されています)。ELYZA は速いものの、ツール呼び出しに対応していません
  • ツール定義が多くても VRAM に収まる。 90 個のツール定義(約 34k トークン)でも、コンテキスト 64k で gemma4:26b-a4b が 15GB、glm-4.7-flash が 22GB と、どちらも GPU に収まりました。正答率は 90% です。Ollama のプレフィックスキャッシュが効くので、2 回目以降は 0.1 秒で読み終わります

電力上限を 240W にすると、上位 6 本の prefill は最大で 1 割ほど伸びました(例: gemma4:26b-a4b は 2,987 / 96.9、gpt-oss:20b は 2,884 / 97.4)。gpt-oss:20b のツール正答は 240W の試験で 30/30 でした。

Mac との比較

参考として、以前 Mac(M5・24GB)で動かしたときの gpt-oss:20b と比べます。条件は揃っていないので、あくまで目安です。

prefill(tok/s)生成(tok/s)
Mac(M5・24GB)35014
TITAN RTX(200W)2,76595.6

用途ごとの選び方

用途モデル理由
エージェント(日本語)gemma4:26b-a4b-it-qat日本語の点が最も高く、速い。画像入力にも対応
エージェント(英語中心・最軽量)gpt-oss:20b速く、VRAM も 13GB で済む。日本語の技術説明には誤りがあった
ツール精度を最優先qwen3.6:27b正答 100%。ただし dense なので遅い
コーディングqwen3-coder:30b生成が最も速い MoE
小型の補助(タイトル付けなど)nemotron-3-nano:4b4GB で、大きなモデルと同時に載せられる

ハマったところ

1. Ollama の /v1 は思考が既定で有効

最初の比較では、Ollama のツール正答率が 80%、vLLM が 100% と差が出ました。原因はエンジンではなく、Ollama の OpenAI 互換 API(/v1)では思考が既定で有効になることでした。思考に生成上限を使い切って、ツール呼び出しまでたどり着かなかった問が出ていたのです。

思考を切って同じ条件にそろえると、Ollama も 100% になりました。

  • gemma4 / Qwen3 系: リクエストに "reasoning_effort": "none" を付ける
  • gpt-oss: none が効かないので "low" にする

思考を切るかどうかでモデルごとに指定が違うため、クライアント側ではエンドポイントを 2 本に分けています。

2. llama-server では gpt-oss のツール呼び出しが解釈されない

素の llama-server で gpt-oss:20b を動かすと、ツール正答が 0/30 でした。gpt-oss はツール呼び出しを独自の harmony 形式で出力しますが、--jinja を付けてもこれを解釈しません。Ollama は harmony 形式のパーサを持っているので、同じモデルが Ollama では動きます。

3. 同梱テンプレートが enum の候補を出さない

Ollama に同梱されている gpt-oss のテンプレートは、ツール定義の enum(選択肢)と、省略できる引数の印を出力しません。モデルは選択肢を知らないまま引数を作るので、スキーマに合わない値を返していました。テンプレートを直した派生モデルを Modelfile で作って解決しました。

4. 引数の多いツールで JSON が途中で切れる

引数が 26 個あるツール定義を渡すと、モデルが全部の引数を null で埋めようとして、出力の JSON が途中で切れ、Ollama が HTTP 500 を返しました。ツール側で、必要な引数だけに絞ったスキーマを渡すことで防げます。

5. 前のモデルが残っていると、次のモデルが CPU にはみ出す

連続で計測していたら、nemotron-3-nano:4b だけ極端に遅い回がありました(prefill 572 / 生成 41.6、GPU 率 66%)。直前の qwen3.6:27b(17.4GB)が keep_alive で VRAM に残っていたため、収まりきらずに CPU にはみ出していました。単独で測り直すと 3,862 / 140.5(GPU 率 100%)です。計測の前には ollama stop で VRAM を空にします。

6. CPU が空回りしている

Ollama は既定で CPU のスレッドを大量に使い、GPU で推論していても CPU 使用率が 1,600% 前後になっていました。num_thread=8 にしても生成速度は 95.1 tok/s で変わらず、CPU の無駄な消費だけを抑えられます。

まとめ

  • Turing(Compute Capability 7.5)では FlashAttention 2 が使えないため、llama.cpp 系(Ollama)が prefill で vLLM の 3〜5 倍速い。エージェントのように長い入力を読む用途では、この差が効きます
  • SGLang は、この GPU では生成が成り立ちませんでした
  • 24GB の VRAM では、MoE の 20〜30B クラスが速さと精度のバランスがよい選択です
  • Ollama の /v1 は思考が既定で有効です。ツール呼び出しの精度を比べるときは、思考の有無をそろえてから比べます

TITAN RTX は 2018 年の GPU ですが、24GB の VRAM があれば、今の MoE モデルでも十分に実用的な速度が出ました。