CATHODE COASTaccess to tools
9 min read

メモリ1GBのVPSとGPUインスタンスで、生成速度が同じだった話

27BのLLMをNVIDIA L4で動かしたら14.7トークン/秒。CPUで動かした0.5Bとほぼ同じでした。LLMの生成速度がメモリ帯域で決まることを実測で確認した記録。

shareB!

はじめに

前回、メモリ1GBのさくらVPSで qwen2.5:0.5b を動かして 約14トークン/秒 という結果を得ました。CPU推論としては十分実用的で、ストリーミングなら快適に使えます。

今回はその続きで、EC2のGPUインスタンス(NVIDIA L4)に 27Bクラスのモデルを載せました。パラメータ数にして54倍。当然、桁違いに速くなるものと思っていました。

結果は14.7トークン/秒でした。

パラメータ54倍、GPU導入、それで速度はほぼ同じ。この数字の意味を追いかけたのが今回の記事です。

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


先にまとめ

  1. LLMの生成速度はメモリ帯域でほぼ決まる。 演算性能でもGPU世代でもありません。実測値は理論上限の83%まで達していました。
  2. 新しいGPUのほうが遅いことがある。 L4(2023年)はA10G(2021年)に対して世代は新しいものの、メモリ帯域は約半分です。
  3. カタログのコンテキスト長は使い切れない。 262,144トークン対応と書かれていますが、24GBのVRAMで実際に使えるのは約9万トークンでした。

測定結果

num_ctxtok/秒VRAM(MiB)出力tok総時間(秒)
409614.717,091117130.1
819214.717,351114143.3
1638414.717,871115131.3
3276814.718,911111132.1
6553614.720,991101130.9

測定環境

項目
インスタンスg6.xlarge(東京・オンデマンド $1.1672/時)
GPUNVIDIA L4 / VRAM 23,034MiB / TDP 72W
ドライバ580.173.02 / CUDA 13.0
CPU / RAM4 vCPU / 15GiB
モデルqwen3.6:27b(Q4量子化 / 約17GB)
Ollamav0.32.5

比較対象の前回環境は、さくらVPS(仮想2コア / メモリ1GB / GPUなし)で qwen2.5:0.5b(約398MB)です。


実際にやったこと

ベンチマークを自動化する

GPUインスタンスは1時間$1.17かかります。手作業で何度も叩くより、一気に流したほうが安上がりです。

cat > ~/bench.sh << 'EOF'
#!/bin/bash
MODEL="${1:?usage: bench.sh <model>}"
PROMPT="桜の季節を舞台にした恋愛小説の冒頭を200文字程度で書いてください。"
 
echo "| num_ctx | tok/秒 | VRAM(MiB) | 出力tok | 総時間(秒) |"
echo "|---|---|---|---|---|"
 
for CTX in 4096 8192 16384 32768 65536; do
  ollama stop "$MODEL" 2>/dev/null; sleep 3
 
  R=$(curl -s http://localhost:11434/api/chat -d "{
    \"model\": \"$MODEL\",
    \"messages\": [{\"role\":\"user\",\"content\":\"$PROMPT\"}],
    \"stream\": false,
    \"think\": false,
    \"options\": {\"num_predict\": 300, \"num_ctx\": $CTX}
  }")
 
  VRAM=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits)
 
  echo "$R" | python3 -c "
import json,sys
d=json.load(sys.stdin)
if 'error' in d: print('| $CTX | ERROR: '+d['error'][:40]+' | | | |'); sys.exit()
e=d.get('eval_count',0); ed=d.get('eval_duration',1); t=d.get('total_duration',0)
print(f'| $CTX | {e/(ed/1e9):.1f} | $VRAM | {e} | {t/1e9:.1f} |')
"
done
EOF
chmod +x ~/bench.sh

Markdownの表がそのまま出るので、記録にそのまま貼れます。

Ollamaは応答のメタデータに eval_count(生成トークン数)と eval_duration(生成時間・ナノ秒)を含めてくれるので、割るだけで正確な生成速度が出ます。time コマンドで測るとモデルのロード時間が混ざるので、こちらのほうが正確です。

結果: 全行が14.7

| num_ctx | tok/秒 | VRAM(MiB) | 出力tok | 総時間(秒) |
|---|---|---|---|---|
| 4096 | 14.7 | 17091 | 117 | 130.1 |
| 8192 | 14.7 | 17351 | 114 | 143.3 |
| 16384 | 14.7 | 17871 | 115 | 131.3 |
| 32768 | 14.7 | 18911 | 111 | 132.1 |
| 65536 | 14.7 | 20991 | 101 | 130.9 |

コンテキスト長を16倍にしても、生成速度は1桁目まで一致。 ここまで安定するとは思っていませんでした。

そして前回の記録と並べます。

ハードウェアモデルパラメータ速度
さくらVPS仮想2コア・GPUなしqwen2.5:0.5b0.5B13.9 tok/秒
EC2 g6.xlargeNVIDIA L4qwen3.6:27b27B14.7 tok/秒

54倍のモデルを、GPUで動かして、6%速い。


なぜこうなるのか

生成の律速はメモリ帯域

LLMがトークンを1つ生成するとき、モデルの重み全体を一度読み出します。27Bを4bit量子化した約17GBなら、1トークンごとに17GBをVRAMから読む計算です。

つまり理論上の上限はこうなります。

生成速度の上限 ≒ メモリ帯域 ÷ モデルサイズ

L4のメモリ帯域は公称約300GB/s。モデルは約16.8GB。

300 ÷ 16.8 ≒ 17.9 トークン/秒

実測は14.7。理論値の82%です。

推論エンジンとしてはかなり優秀な数字で、逆に言えばこれ以上の高速化余地はほとんどありません。演算を最適化しても、帯域という物理的な壁が先にあります。

実効帯域に直すと以下になります。

14.7 tok/秒 × 16.8GB = 約247 GB/s(公称300GB/sの82%)

VPSのほうも計算が合う

同じ式を前回のVPSに当てはめます。qwen2.5:0.5b は約398MB、DDR4のメモリ帯域を仮に20GB/s程度とすると、

20 ÷ 0.4 = 50 トークン/秒

実測は13.9でした。GPUの82%に比べると効率は悪いのですが、CPUではメモリコントローラの効率が落ちるうえ、演算そのものもボトルネックになります。それでも桁としては説明がつく範囲です。

重要なのは、GPUかCPUかではなく「帯域÷モデルサイズ」で大枠が決まるということです。小さいモデルなら遅い環境でも速く、大きいモデルなら速い環境でも遅い。


新しいGPUのほうが遅い

今回L4を使ったのは積極的な選択ではなく、A10G(g5.xlarge)が容量不足で起動できなかったためです。結果的にこれが興味深い比較材料になりました。

A10G (g5.xlarge)L4 (g6.xlarge)
発売2021年2023年
アーキテクチャAmpereAda Lovelace(新しい)
VRAM22GB24GB
メモリ帯域約600 GB/s約300 GB/s
TDP300W72W
東京・オンデマンド$1.4590/時$1.1672/時

世代は新しく、VRAMは多く、価格は安い。しかし帯域は半分です。

L4は推論の効率とデータセンターの電力密度を重視した設計で、TDP 72Wという数字がそれを物語っています。A10Gの300Wに対して4分の1以下。性能を削って電力効率を取ったわけです。

上の式に当てはめると、A10Gなら理論上35トークン/秒前後、実効82%として約29トークン/秒が期待できたことになります。同じモデルで倍近い差です。

教訓

GPUを選ぶとき、つい「世代」「VRAM容量」「価格」を見てしまいます。しかしLLM推論では、カタログの目立つ位置に書かれていないメモリ帯域が最も効きます

「新しい世代だから速い」は成り立ちません。用途によって最適化の方向が違うためです。

ここに挙げた帯域値は一般的な公称値です。厳密にはNVIDIAの製品ページで確認してください。


VRAMとコンテキスト長の関係

測定表のVRAM列を見ると、きれいな規則性があります。

num_ctxVRAM(MiB)前行からの増分
4,09617,091
8,19217,351+260
16,38417,871+520
32,76818,911+1,040
65,53620,991+2,080

コンテキストを倍にするたび、増分もちょうど倍。1トークンあたり0.0635MiB(約65KiB) で、誤差なく線形です。

これはKVキャッシュのサイズで、モデルの構造から決まる定数です。ここから逆算できます。

モデル本体のみ(ctx=0)  ≒ 16,831 MiB
実VRAM                     23,034 MiB
────────────────────────────────────
KVキャッシュに使える上限    ≒  6,200 MiB
→ 最大コンテキスト長       ≒ 97,000 トークン

実際には推論中の一時バッファも要るので、安全に使えるのは9万トークン程度でしょう。

カタログとの差

qwen3.6の公称コンテキスト長は 262,144トークンです。

使える割合 = 90,000 ÷ 262,144 ≒ 34%

カタログスペックの3分の1しか使えません。 24GBのVRAMでは、モデル本体を載せた時点で残りが6GBしかないためです。

「256K対応」を額面どおり受け取ると、実運用で足をすくわれます。モデルサイズとVRAMから、KVキャッシュに回せる容量を先に計算しておくべきでした。

なお、コンテキストを長くしても生成速度は落ちませんでした(全行14.7)。VRAMに収まっている限りは影響しないようです。溢れた瞬間に急落するはずですが、今回は65,536でも収まったため、その境界は未測定です。


見落としていたモデルのロード時間

測定表をよく見ると、おかしな点があります。

| 8192 | 14.7 tok/秒 | 114トークン | 総時間 143.3秒 |

114トークンを14.7トークン/秒で生成すれば 7.8秒のはずです。しかし総時間は143秒。

差の約135秒はモデルのロード時間でした。17GBをEBSから読み出してVRAMに転送しています。ベンチスクリプトで毎回 ollama stop していたため、5回ぶん律儀に読み直していました。

これは実運用に効く

Ollamaは最後に使ってから5分でモデルをメモリから降ろします。 つまり利用者が久しぶりにアクセスするたび、2分以上待たされることになります。

対処は簡単で、常駐させるだけです。

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

-1 で無期限に保持します。インスタンスを起動している間は課金されているので、VRAMを空けておく理由がありません。

「使っていない時間のメモリを節約する」というOllamaの既定は、ローカルPCでは合理的ですが、時間課金のクラウドでは逆効果でした。


コストに換算する

$1.1672/時で14.7トークン/秒。100%稼働させた場合の単価を出してみます。

14.7 tok/秒 × 3,600秒 = 52,920 トークン/時
$1.1672 ÷ 52,920 × 1,000,000 = 約 $22.1 / 1Mトークン

出力100万トークンあたり$22。 これは生成し続けた場合の理論値で、実際にはアイドル時間があるぶん実効単価はさらに悪化します。1日2時間起動して実際の生成が10分なら、単価は12倍になる計算です。

「自前でホストすれば安い」という直感は、使用密度が十分に高い場合にのみ成立するようです。個人や家庭の利用頻度では、API課金のほうが安く済む可能性が高いと感じました。


ハマりどころまとめ

症状原因対処
GPUにしたのに速くならない生成はメモリ帯域律速帯域÷モデルサイズで上限を見積もる
新世代GPUのほうが遅いL4は電力効率重視で帯域が低いカタログの世代でなく帯域を見る
256K対応なのに長文が入らないVRAMの残りがKVキャッシュ上限(VRAM − モデルサイズ) ÷ 65KiB で計算
生成時間より総時間が極端に長いモデルのロードOLLAMA_KEEP_ALIVE=-1 で常駐
time で測ると数字がぶれるロード時間が混入eval_count ÷ eval_duration を使う

やってみての所感

数字が理論と合うのは気持ちがいい

「300 ÷ 16.8 = 17.9」という単純な割り算が、実測14.7という結果を82%の精度で説明してしまいました。LLM推論という複雑そうな処理が、帯域という一つの数字でほぼ決まるというのは、直感に反しつつも納得感があります。

VRAMの増分が1トークンあたり65KiBぴったりだったのも同様です。実測データがここまで綺麗に線形になると、逆算した「最大9万トークン」という予測にも自信が持てます。

「速いハードを買えば速くなる」は限定的

今回いちばんの学びは、投資の方向を間違えやすいということでした。

GPUを導入し、54倍のモデルを載せても、トークン生成の速度は変わりませんでした。速くしたいなら、より高帯域なGPUを選ぶか、モデルを小さくするかのどちらかです。VRAM容量を増やしても「より大きなモデルが載る」だけで、速くはなりません。

一方で、同じ速度でも1トークンあたりの価値はまるで違います。0.5Bは「100文字で」という指示を無視して652トークン書き続けましたが、27Bは113トークンで的確に終わりました。速度が同じでも、必要なトークン数が6分の1なら実質6倍速いことになります。

「トークン/秒」だけを見ていると、この差を見落とします。

向く人・向かない人

自前ホストが向くのは、生成量が多く、稼働率を高く保てるケースです。バッチ処理や常時稼働のサービスなら、時間課金は有利に働きます。

向かないのは、今回のような個人・家庭利用です。1日数回の会話のために時間課金のGPUを起動するのは、単価で見ると割高です。学習目的なら別ですが、実用性だけを見るならAPIを使うほうが合理的でしょう。

その「合理的でない」ことを自分の数字で確認できたのが、今回いちばんの収穫かもしれません。


まとめ

27BのモデルをNVIDIA L4で動かした結果は14.7トークン/秒。メモリ1GBのVPSでCPU推論した0.5Bと、ほぼ同じでした。

理由はメモリ帯域律速という一点に尽きます。生成のたびにモデル全体を読み出す以上、帯域とモデルサイズの比が上限を決める。GPUの世代でも演算性能でもありません。

そして容量不足でやむなく選んだL4が、A10Gの半分の帯域だったことも今回わかりました。新しくてVRAMが多くて安い、しかし遅い。 GPUのカタログを読む視点が一つ増えました。

次はこのモデルで、生成された文章の中身を見ていきます。実は今回の測定に至るまでに、curlが何も返さないという別の問題を解いていたのですが、それはまた別の話です。


参考リンク