CATHODE COASTaccess to tools
8 min read

Ollamaが3分待たせる理由は、EBSの転送速度だった

GPUインスタンスで27Bモデルを叩くと毎回3分。生成時間は18秒で、残りは全部モデルのロードでした。gp3の125MiB/sという制約と、OLLAMA_KEEP_ALIVEの話。

shareB!

はじめに

EC2のGPUインスタンス(NVIDIA L4)に27BクラスのLLMを載せて、簡単な設問を6問投げるスクリプトを書きました。

内容は「17×23を計算して」「赤・青・黄を英語で」といった軽いものです。出力は全部合わせても300トークン足らず。数秒で終わるはずでした。

かかった時間は 約3分です。

「プロンプトが重いのだろうか」と考えて、もっと軽い設問を探そうとしました。しかしメタデータを分解したところ、生成そのものは18.6秒しか使っていませんでした。

残りの2分半は何だったのか、を追いかけた記録です。

執筆時点(2026年7月)、g6.xlarge / Ollama v0.32.5 / qwen3.6:27b での測定です。


先にまとめ

  1. 時間の94%はモデルのロードだった。 17GBをEBSから読み出してVRAMに載せる工程です。
  2. ロード時間はEBSの転送速度で決まる。 gp3の既定は125MiB/s。17,000MiB ÷ 125MiB/s = 約136秒で、実測とほぼ一致します。
  3. OLLAMA_KEEP_ALIVE=-1 で解決する。 時間課金のクラウドでは、モデルをVRAMに置いたままにするコストはゼロです。
sudo tee -a /etc/systemd/system/ollama.service.d/override.conf > /dev/null << 'EOF'
Environment="OLLAMA_KEEP_ALIVE=-1"
EOF
sudo systemctl daemon-reload && sudo systemctl restart ollama

症状: 軽い設問でも3分かかる

問題の切り分けのために、短い出力で終わる設問を6問並べたスクリプトを流しました。再現性のため temperature: 0seed: 42 を固定しています。

### qwen3.6:27b
--- A_形式 (tok=6/stop) ---
red, blue, yellow
--- B_算術 (tok=4/stop) ---
391
--- C_知識 (tok=19/stop) ---
北海道, 岩手県, 宮城県, 福島県, 長野県
--- D_敬語 (tok=17/stop) ---
尊敬語:資料をご覧になった
謙譲語:資料をお見せしました
--- E_JSON (tok=27/stop) ---
(JSON出力)
--- F_拒否 (tok=200/length) ---
(包丁の研ぎ方の解説)

出力トークンの合計は 273。このモデルの生成速度は別途 14.7トークン/秒と測定済みなので、

273 ÷ 14.7 = 18.6秒

実際の生成は18.6秒。 しかし体感では3分かかっています。


診断: total_duration から引き算する

Ollamaはレスポンスに複数の時間情報を返します。ここを見れば内訳が分かります。

フィールド意味
total_duration全体の所要時間
load_durationモデルのロード時間
prompt_eval_duration入力の処理時間
eval_duration出力の生成時間

単位はすべてナノ秒です。以前に別のベンチマークで取っていたデータを見ると、原因が明確でした。

num_ctxtok/秒出力tok総時間(秒)生成のみ(秒)
409614.7117130.18.0122.1
819214.7114143.37.8135.5
1638414.7115131.37.8123.5
3276814.7111132.17.6124.5
6553614.7101130.96.9124.0

毎行、約120〜135秒が生成以外に使われています。 そして値がほぼ一定です。

このベンチマークスクリプトは、条件を揃えるため各ループの先頭で ollama stop を実行していました。つまり5回とも律儀にモデルを読み直していたわけです。

for CTX in 4096 8192 16384 32768 65536; do
  ollama stop "$MODEL" 2>/dev/null; sleep 3   # ← これ
  ...
done

「条件を揃えるため」に入れた1行が、測定時間の94%を占めていました。


なぜ2分もかかるのか

17GBのファイルを読むだけで2分。SSDならもっと速いはずです。ここでEBSの仕様に行き当たりました。

gp3ボリュームの既定スループットは125MiB/sです。IOPSは3,000、スループットは125MiB/sがベースラインとして提供され、それ以上は追加課金で引き上げる形になっています。

計算してみます。

モデルサイズ  約17,000 MiB
gp3の既定     125 MiB/s
─────────────────────────
17,000 ÷ 125 = 136秒

実測120〜135秒。ほぼ一致します。

つまりロード時間はディスクの帯域で決まっており、CPUもGPUも関係ありません。17GBを125MiB/sで読むには、物理的に2分以上かかるという話でした。

対処は2つある

A. モデルを常駐させる(無料)

Environment="OLLAMA_KEEP_ALIVE=-1"

一度読んだら降ろさない。追加費用ゼロ。

B. gp3のスループットを上げる(有料)

gp3は最大1,000MiB/sまで引き上げられます。125→1,000にすれば、ロードは136秒から17秒程度に短縮されます。

ただし追加のスループットには課金されます。そしてAで解決するなら、Bは不要です。インスタンスを頻繁に停止・起動する運用で、起動直後の初回だけを速くしたい場合には検討の価値があります。


Ollamaの既定値は、クラウドでは逆効果

Ollamaは最後に使ってから5分でモデルをメモリから降ろします。 これが既定の挙動です。

ローカルPCなら合理的です。使っていないのにVRAMを16GB占有し続けたら、ゲームも動画編集もできません。「使わないなら返す」のが礼儀にかなっています。

しかし時間課金のGPUインスタンスでは、これが完全に裏目に出ます。

ローカルPC時間課金のクラウド
VRAMを空けると他の用途に使える何も得られない
モデルを載せ直すと数秒〜数十秒2分以上(EBS帯域)
GPUの費用買い切り載せていなくても発生

インスタンスが起動している間、GPUの料金は発生し続けています。VRAMを空けても1円も安くなりません。 それなのに次回のアクセスで2分待たされる。得るものが何もない取引です。

実運用への影響

これは検証だけの話ではありません。

家族が使うチャットアプリを想定すると、5分以上間隔が空いた最初のメッセージだけ2分待たされることになります。日常利用では、ほぼ毎回それに当たります。

「なぜか最初だけ異常に遅い」という症状は、利用者からすると原因が想像できません。設定1行で消せる問題です。


設定と確認

Ollamaはsystemdで動いているので、ドロップインで環境変数を追加します。

sudo mkdir -p /etc/systemd/system/ollama.service.d
 
sudo tee /etc/systemd/system/ollama.service.d/override.conf > /dev/null << 'EOF'
[Service]
Environment="OLLAMA_MODELS=/mnt/models"
Environment="OLLAMA_KEEP_ALIVE=-1"
EOF
 
sudo systemctl daemon-reload
sudo systemctl restart ollama

反映を確認します。

$ systemctl show ollama -p Environment | tr ' ' '\n' | grep OLLAMA
OLLAMA_MODELS=/mnt/models
OLLAMA_KEEP_ALIVE=-1

tr ' ' '\n' を挟んで1行ずつ表示させるのがコツです。grep だけだと全変数が1行に並ぶので、値に余計な文字が混ざっていても気づきません。

実際に一度、OLLAMA_MODELS=/mnt/models: と末尾にコロンが混入していて、Ollamaが起動しなくなったことがありました。1行ずつ表示させていれば即座に分かった話です。

事前にロードしておく

インスタンスを起動した直後は、当然まだモデルが載っていません。空のリクエストを投げるとロードだけ実行できます。

time curl -s http://localhost:11434/api/generate -d '{"model":"qwen3.6:27b"}' > /dev/null

レスポンスの done_reasonload になり、これでVRAMに載ります。以降は KEEP_ALIVE=-1 で降りません。

起動スクリプトの最後にこれを入れておけば、利用者が最初のメッセージを送る時点でモデルが準備済みになります。


副作用: 自動シャットダウンとの相互作用

一点、注意が必要でした。

コスト対策として「アイドルが続いたら自動停止」の仕組みを入れていたのですが、KEEP_ALIVE=-1 にするとモデルがVRAMに載り続けます。

もし「GPUメモリが使われているか」でアイドル判定をしていたら、永遠に停止しなくなります

# これで判定してはいけない
nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits

正しくはAPIへのリクエスト履歴を見ます。Ollamaは内部でGINを使っているので、アクセスログがjournalに流れます。

$ journalctl -u ollama --since "-5 min" -q | grep "/api/" | tail -1
Jul 28 01:04:18 ip-172-31-x-x ollama[651]: [GIN] 2026/07/28 - 01:04:18 | 200 |     229.445µs |       127.0.0.1 | GET      "/api/tags"

「モデルが載っている」と「使われている」は別の状態です。KEEP_ALIVEを有効にすると、この2つが恒久的に乖離します。


効果

設定後、同じ6問のスクリプトを再実行しました。

KEEP_ALIVE 既定KEEP_ALIVE=-1
6問の所要時間約3分20〜30秒
うちロード時間約2分0秒(事前ロード済み)
生成時間18.6秒18.6秒

生成時間は当然変わりません。 消えたのは待ち時間だけです。

そして再現性の確認もできました。temperature: 0 / seed: 42 を固定した状態で、A〜Eの5問が前回とバイト単位で完全に一致しました。トークン数も6/4/19/17/27と同じです。

ロード時間という大きなノイズが消えたことで、モデル間の比較に使える測定環境になりました。


ハマりどころまとめ

症状原因対処
軽い設問でも数分かかるモデルのロード時間total_durationeval_duration の差を見る
ロードに2分かかるgp3の既定スループット125MiB/sKEEP_ALIVE=-1。または gp3 のスループット引き上げ
たまに最初だけ遅い5分アイドルでモデルが降りるOLLAMA_KEEP_ALIVE=-1
ベンチの数字が実態と合わないollama stop がループ内にある測定目的に応じて外す
環境変数が反映されない値に余計な文字が混入tr ' ' '\n' で1行ずつ確認
自動停止が発火しないGPUメモリでアイドル判定しているAPIのアクセスログで判定する

やってみての所感

「重い処理」を疑う前に分解する

最初に考えたのは「プロンプトが重いのだろうか、もっと軽い設問にしよう」でした。完全に見当違いでした。 出力4トークンの算数の問題でも、同じだけ待たされていたはずです。

以前、生成に61秒かかったのを「遅い」と解釈して、実際は「モデルが指示を無視して書きすぎていた」だけだったことがあります。今回も同じで、体感の遅さと原因を短絡させる癖がなかなか抜けません。

Ollamaは load_duration / prompt_eval_duration / eval_duration を分けて返してくれます。最初にこれを見れば5分で解決した話でした。time コマンドだけで判断すると、内訳が見えないぶん遠回りになります。

既定値は「どこで動くか」を前提にしている

OLLAMA_KEEP_ALIVE の既定5分は、ローカルPCという文脈では正しい設計です。VRAMは希少な共有資源で、使わないなら返すべきものです。

しかしクラウドのGPUインスタンスでは、その前提が崩れます。GPUはこのプロセスの専有物で、空けても誰も得をしない。 同じソフトウェアの同じ設定が、動く場所によって最適から最悪に変わります。

移植したソフトウェアの既定値を疑う、という視点は持っておくべきでした。

ボトルネックは意外なところにある

ロード時間の正体がEBSの転送速度だったのは予想外でした。GPUインスタンスの性能を考えるとき、GPUとVRAMばかり見ていて、モデルがどこから来るかを考えていませんでした。

17GBを125MiB/sで読む。この計算をしていれば、136秒という数字は最初から見えていたはずです。生成速度がメモリ帯域で決まるのと同じで、結局はどこかの帯域が上限を決めているという話でした。


まとめ

軽い設問でも3分かかる原因は、モデルのロードでした。生成は18.6秒で、残りはEBSから17GBを読み出す時間です。gp3の既定スループット125MiB/sで割ると136秒、実測とほぼ一致しました。

対処は OLLAMA_KEEP_ALIVE=-1 の1行です。時間課金のクラウドでは、モデルをVRAMに置いたままにするコストがゼロなので、降ろす理由がありません。3分が20〜30秒になりました。

ただしこれを有効にすると、「モデルが載っている」と「使われている」が恒久的に乖離します。自動シャットダウンなどの仕組みを併用しているなら、判定材料をGPUメモリではなくAPIのアクセスログにしておく必要があります。


参考リンク