はじめに
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 での測定です。
先にまとめ
- 時間の94%はモデルのロードだった。 17GBをEBSから読み出してVRAMに載せる工程です。
- ロード時間はEBSの転送速度で決まる。 gp3の既定は125MiB/s。17,000MiB ÷ 125MiB/s = 約136秒で、実測とほぼ一致します。
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: 0 と seed: 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_ctx | tok/秒 | 出力tok | 総時間(秒) | 生成のみ(秒) | 差 |
|---|---|---|---|---|---|
| 4096 | 14.7 | 117 | 130.1 | 8.0 | 122.1 |
| 8192 | 14.7 | 114 | 143.3 | 7.8 | 135.5 |
| 16384 | 14.7 | 115 | 131.3 | 7.8 | 123.5 |
| 32768 | 14.7 | 111 | 132.1 | 7.6 | 124.5 |
| 65536 | 14.7 | 101 | 130.9 | 6.9 | 124.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=-1tr ' ' '\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_reason が load になり、これで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_duration と eval_duration の差を見る |
| ロードに2分かかる | gp3の既定スループット125MiB/s | KEEP_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のアクセスログにしておく必要があります。