はじめに
メモリ1GBのVPSで0.5Bの軽量LLMを動かしたのが前回。今回はその続きで、もう少しまともなサイズのモデルをGPUで動かすことにしました。
やりたいことは単純です。EC2にGPUインスタンスを立てて、Ollamaを入れて、27BクラスのモデルをVRAMに載せる。それだけのはずでした。
結果から言うと、Ollamaを起動するところに辿り着くまでに5回転びました。しかもそのうち3つは、GPUともLLMとも関係のない場所です。
執筆時点(2026年7月)、Ubuntu 24.04 LTS / Ollama v0.32.5 / NVIDIA Driver 580.173.02 での記録です。
先にまとめ
- クォータの承認は「空きがある」保証ではない。
InsufficientInstanceCapacityはこちらの設定ミスではありません。AZ単位で在庫が違うので、サブネットを指定して総当たりするのが定石でした。 nvidia-utilsを入れてもGPUは動かない。 aptが親切に提案してくるパッケージは、ドライバ本体を含んでいません。- 一番大きいディスクは、停止すると消える。 232GBのインスタンスストアにモデルを置くと、次回起動時に消滅します。
最終的に動いた構成は以下です。
| 項目 | 値 |
|---|---|
| インスタンス | g6.xlarge(NVIDIA L4 / VRAM 23,034MiB) |
| リージョン / AZ | ap-northeast-1 / 1c |
| 単価 | $1.1672/時(東京・オンデマンド) |
| OS | Ubuntu 24.04 LTS |
| ドライバ | nvidia-driver-580-server |
| ストレージ | ルート30GB(EBS) + データ100GB(EBS) + swap 232GB(インスタンスストア) |
罠1: クォータが通っていても起動できない
GPUインスタンスを使うには、まずService Quotasで上限緩和の申請が要ります。新規アカウントは G系インスタンスのvCPUクォータが0 なので、申請しないと1台も起動できません。
申請は数日で通り、確認したところ64まで引き上げられていました。
$ aws service-quotas get-service-quota \
--service-code ec2 --quota-code L-DB2E81BA \
--region ap-northeast-1 --query 'Quota.Value'
64.0g5.xlargeは4 vCPUなので、16台ぶんの余裕があります。安心して起動コマンドを叩きました。
An error occurred (InsufficientInstanceCapacity) when calling the RunInstances
operation (reached max retries: 2): Insufficient capacity.最初は自分の設定ミスを疑いましたが、違いました。これはAWS側にGPUの空きが無いという意味です。
ここで理解したことが2つあります。
クォータと在庫は別物です。 Service Quotasの承認は「あなたはここまで使ってよい」という許可であって、「マシンが空いている」という保証ではありません。GPUは特に逼迫しやすく、承認が下りていても起動できないことがあります。
在庫はAZ単位です。 run-instances でサブネットを指定しないと、デフォルトVPCの特定のAZ1箇所だけを試して諦めます。他のAZには空きがあるかもしれません。
対処: AZを総当たりする
まず、そのインスタンスタイプがどのAZで提供されているかを確認します。
aws ec2 describe-instance-type-offerings \
--location-type availability-zone \
--filters "Name=instance-type,Values=g5.xlarge" \
--region ap-northeast-1 \
--query 'InstanceTypeOfferings[].Location' --output textそのうえで、サブネットを順に指定してループさせます。
for SUBNET in $(aws ec2 describe-subnets --region $AWS_REGION \
--query 'Subnets[].SubnetId' --output text); do
AZ=$(aws ec2 describe-subnets --subnet-ids $SUBNET --region $AWS_REGION \
--query 'Subnets[0].AvailabilityZone' --output text)
echo "--- 試行: $AZ ---"
if aws ec2 run-instances \
--region $AWS_REGION \
--image-id $AMI_ID \
--instance-type g6.xlarge \
--key-name my-key \
--security-group-ids $SG_ID \
--subnet-id $SUBNET \
--associate-public-ip-address \
--block-device-mappings '[
{"DeviceName":"/dev/sda1","Ebs":{"VolumeSize":30,"VolumeType":"gp3","DeleteOnTermination":true}},
{"DeviceName":"/dev/sdf","Ebs":{"VolumeSize":100,"VolumeType":"gp3","DeleteOnTermination":false}}
]' \
--query 'Instances[0].InstanceId' --output text 2>/dev/null; then
echo "✅ 成功: $AZ"; break
fi
echo "❌ 容量なし"
doneサブネットを明示する場合は --associate-public-ip-address も必要です。設定によってはパブリックIPが付きません。
結果: g5を諦めてg6にしたら安くなった
g5.xlargeは全AZで容量不足でした。そこで**g6.xlarge(NVIDIA L4)**に変更したところ、ap-northeast-1c であっさり通りました。
--- 試行: ap-northeast-1c (subnet-xxxxxxxx) ---
i-0xxxxxxxxxxxxxxxx
✅ 成功: ap-northeast-1c意外だったのは価格です。
| インスタンス | GPU | VRAM | 東京・オンデマンド |
|---|---|---|---|
| g5.xlarge | A10G | 22GB | $1.4590/時 |
| g6.xlarge | L4 | 24GB | $1.1672/時 |
新しい世代のほうが20%安く、VRAMも多い。 容量不足で弾かれた結果、条件の良いほうに流れ着きました。
料金はPricing APIで確認できます。エンドポイントはus-east-1固定なので、--regionは変えずにフィルタで対象リージョンを指定します。
aws pricing get-products \
--region us-east-1 --service-code AmazonEC2 \
--filters \
"Type=TERM_MATCH,Field=instanceType,Value=g6.xlarge" \
"Type=TERM_MATCH,Field=location,Value=Asia Pacific (Tokyo)" \
"Type=TERM_MATCH,Field=operatingSystem,Value=Linux" \
"Type=TERM_MATCH,Field=preInstalledSw,Value=NA" \
"Type=TERM_MATCH,Field=tenancy,Value=Shared" \
"Type=TERM_MATCH,Field=capacitystatus,Value=Used" \
--query 'PriceList[0]' --output text | python3 -c "
import json,sys
d = json.load(sys.stdin)
for t in d['terms']['OnDemand'].values():
for p in t['priceDimensions'].values():
print(p['pricePerUnit']['USD'], '/', p['unit'])
"コスト感
$1.1672/時が何を意味するか、月額と並べると分かりやすいです。
| 期間 | USD | 円(@155円) |
|---|---|---|
| 1時間 | $1.17 | 約181円 |
| 24時間 | $28.01 | 約4,340円 |
| 30日連続 | $840.38 | 約130,300円 |
前回のさくらVPSが月1,000円弱だったので、放置すると130倍です。GPUインスタンスは「必要なときだけ起こす」以外の運用が成立しません。
罠2: nvidia-utils を入れてもGPUは使えない
インスタンスにSSHして nvidia-smi を叩くと、Ubuntuが親切に候補を並べてくれます。
$ nvidia-smi
Command 'nvidia-smi' not found, but can be installed with:
sudo apt install nvidia-utils-470
sudo apt install nvidia-utils-535
sudo apt install nvidia-utils-580
...この提案に従ってはいけません。
nvidia-utils-XXX は nvidia-smi などのユーティリティ群だけで、カーネルドライバを含みません。インストールするとコマンドは存在するようになりますが、実行すると NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver で落ちます。「コマンドが無い」から「コマンドが動かない」に症状が変わるだけです。
必要なのは nvidia-driver-XXX-server のほうでした。
sudo apt update
sudo apt install -y nvidia-driver-580-server
sudo reboot-server サフィックスはヘッドレス向けで、デスクトップ用のパッケージ群が入りません。推論サーバー用途ならこちらです。
再起動後、無事に認識されました。
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 580.173.02 Driver Version: 580.173.02 CUDA Version: 13.0 |
+-----------------------------------------+------------------------+----------------------+
| 0 NVIDIA L4 Off | 00000000:31:00.0 Off | 0 |
| N/A 43C P0 29W / 72W | 0MiB / 23034MiB | 4% Default |
+-----------------------------------------+------------------------+----------------------+VRAMは23,034MiB。 「24GB」と言われますが、実際に使えるのは約22.5GiBです。この差は後でモデルを載せるときに効いてきます。
そして TDPが72W。GPUとしては驚くほど低い数字ですが、これが後々の性能に関わってきます(別記事に書きます)。
罠3: 一番大きいディスクは、停止すると消える
ボリュームをマウントしようとして lsblk を叩いたら、3つ出てきました。
$ lsblk -o NAME,SIZE,MODEL
NAME SIZE MODEL
nvme0n1 30G Amazon Elastic Block Store
├─nvme0n1p1 29G
nvme1n1 100G Amazon Elastic Block Store
nvme2n1 232.8G Amazon EC2 NVMe Instance Storage作ったのはルート30GBとデータ100GBの2本だけです。232.8GBの nvme2n1 は覚えがありません。
これはインスタンスストアでした。g6.xlargeに標準で付属する物理NVMe SSDです。EBSとは性質が根本的に違います。
| デバイス | 種別 | インスタンス停止時 |
|---|---|---|
| nvme0n1 (30G) | EBS ルート | 保持される |
| nvme1n1 (100G) | EBS データ | 保持される |
| nvme2n1 (232.8G) | インスタンスストア | 完全に消える |
一番大きくて一番速いディスクが、一番消えやすい。 ここに17GBのモデルを置いたら、停止するたびに再ダウンロードです。
MODEL 列を見れば区別できます。Amazon Elastic Block Store がEBS、Amazon EC2 NVMe Instance Storage がインスタンスストアです。lsblk を単体で叩くと表示されないので、-o NAME,SIZE,MODEL を付ける習慣をつけたほうがよさそうです。
使い道はある
消えて困らないデータなら、物理NVMeなので高速です。今回はswapに割り当てました。
sudo mkswap /dev/nvme2n1
sudo swapon /dev/nvme2n1$ free -h
total used free shared buff/cache available
Mem: 15Gi 869Mi 13Gi 2.9Mi 511Mi 14Gi
Swap: 232Gi 0B 232Giメモリ15GiBに対してswap 232GiB。VRAMから溢れても落ちない構成になりました。
/etc/fstabには書かないこと。 インスタンスストアは停止・起動でデバイスが初期化されるため、fstabに書くと次回起動時にマウントに失敗します。必要なら起動スクリプトで毎回mkswapします。
一方、EBSのデータボリュームは永続化します。
sudo mkfs -t ext4 /dev/nvme1n1
sudo mkdir -p /mnt/models
sudo mount /dev/nvme1n1 /mnt/models
UUID=$(sudo blkid -s UUID -o value /dev/nvme1n1)
echo "UUID=$UUID /mnt/models ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstabnofail は必ず付けてください。 ボリュームが無い状態で起動したとき、これが無いと起動に失敗してSSHもできなくなります。
罠4: systemctl edit が余計な文字を残した
Ollamaを入れて、モデルの保存先をデータボリュームに変更します。デフォルトのままだとルートの29GBに17GBのモデルが落ちて容量不足になるためです。
sudo systemctl edit ollamaエディタで以下を記述しました。
[Service]
Environment="OLLAMA_MODELS=/mnt/models"反映を確認します。
$ systemctl show ollama -p Environment | grep OLLAMA_MODELS
Environment=PATH=/usr/local/sbin:... OLLAMA_MODELS=/mnt/models:末尾にコロンが付いています。
OLLAMA_MODELS=/mnt/models:
↑ これ案の定、Ollamaは起動していませんでした。
$ curl http://localhost:11434/
curl: (7) Failed to connect to localhost port 11434 after 0 ms/mnt/models: という存在しないパスとして解釈されていたわけです。編集時に余計な文字が混入したのか、コメント行との境界を踏んだのかは特定できていませんが、systemctl edit の対話エディタは編集位置を間違えやすいという教訓になりました。
対処: ドロップインを直接置く
対話エディタを使わず、ファイルとして書き込むほうが確実でした。
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"
EOF
sudo systemctl daemon-reload
sudo systemctl restart ollamaこの方法ならファイルをリポジトリで管理できるので、再構築時にも同じものが置けます。対話エディタで手打ちするより、あらゆる面で優れていました。
確認するときは、変数を1行ずつ表示させると異常に気づきやすくなります。
$ systemctl show ollama -p Environment | tr ' ' '\n' | grep OLLAMA
OLLAMA_MODELS=/mnt/modelstr ' ' '\n' を挟むだけです。grep だけだと1行に全変数が並ぶので、末尾のコロンのような小さな異常を見落とします。
罠5: 起動しても、まだ入れない
これは小さな話ですが、地味に時間を無駄にしました。
run-instances が成功してインスタンスIDが返っても、すぐにSSHはできません。running 状態はハイパーバイザ上で起動したという意味で、OSの初期化は終わっていません。
# これを待つ
aws ec2 wait instance-status-ok --instance-ids $INSTANCE_ID --region $AWS_REGIONinstance-status-ok はステータスチェックが2つとも通るまで待ちます。数分かかりますが、その間も課金されています。
ハマりどころまとめ
| 症状 | 原因 | 対処 |
|---|---|---|
InsufficientInstanceCapacity | AZにGPUの在庫が無い | サブネットを指定してAZを総当たり。別ファミリー(g6等)も試す |
VcpuLimitExceeded | クォータ未承認 | Service Quotasで申請。リージョンごとに独立 |
nvidia-smi が動かない | nvidia-utils はドライバを含まない | nvidia-driver-XXX-server を入れる |
| 停止したらモデルが消えた | インスタンスストアに置いた | lsblk -o NAME,SIZE,MODEL でEBSか確認 |
| 再起動でマウントされない | /etc/fstab の記述 | UUID指定+nofail |
| Ollamaが起動しない | 環境変数に余計な文字 | ドロップインを直接書く。tr ' ' '\n' で確認 |
| SSHが繋がらない | OS初期化が未完了 | aws ec2 wait instance-status-ok |
やってみての所感
詰まった場所がGPUと無関係だった
5つの罠のうち、GPU固有と言えるのは1つ目(容量不足)と2つ目(ドライバ)だけです。残りはディスク、systemd、待ち時間。普通のLinuxサーバー構築の話でした。
前回のVPS構築でも、詰まったのはファイアウォールという枯れた領域でした。今回も同じで、新しい技術を触るときほど、足を引っ張るのは古い部分なのかもしれません。
エラーメッセージを正確に読む
InsufficientInstanceCapacity と VcpuLimitExceeded は、症状が「起動できない」で同じですが、原因も対処もまったく違います。前者はAWS側の在庫、後者は自分の申請不足です。
前回の記事でも Timeout during connect と Connection refused の違いが切り分けの鍵になりました。エラーメッセージは正確に書かれているので、雰囲気で読まずに文字どおり受け取るのが結局は近道でした。
CLIで組んでよかった
最初はコンソールで作ろうとしていましたが、AZ総当たりが必要になった時点でCLIに切り替えました。結果的にこれが正解で、インスタンスタイプを変えて再試行する作業が一行の書き換えで済みました。
構築手順がそのままシェルスクリプトとして残るので、次に別リージョンで作り直すときにも使えます。GPUインスタンスは作っては壊す運用になりがちなので、この形にしておく価値は高そうです。
まとめ
GPUインスタンスを立ててOllamaが起動するところまで、実作業は1時間ほど。ただし詰まった時間を含めると2倍以上かかりました。
一番の学びは、Service Quotasの承認を「使える権利」だと思い込んでいたことです。承認されているのに起動できない、という状況を想定していませんでした。クラウドは無限にリソースがあるように見えて、GPUのような希少な資源では在庫の概念が顔を出します。
そして結果的に、容量不足で弾かれたおかげでより安くVRAMの多いインスタンスに落ち着きました。第一希望が通らなかったことが、そのまま最適解になった形です。
次はこのGPUで実際にモデルを動かし、速度を測ります。メモリ1GBのVPSで動かした0.5Bと、ほぼ同じ速度が出るという予想外の結果になったのですが、それはまた別の話です。