Inferentia2・Trainium2を本番導入して6ヶ月、GPUコスト70%削減できた話
「このGPU代、持続可能なのか…」と思いながら運用してませんか?うちのチームがInferentia2・Trainium2に移行して気づいたコスト効率の現実と、ハマりポイントを実測データで。
Inferentia2・Trainium2をチームで本番導入して気づいたコスト効率の現実【2026年版】
LLM推論のGPUコストに頭を抱えてる人、いませんか?
うちのチームも去年の秋まではそうだった。月次のAWS請求書を見るたびに「p3.2xlarge × 複数台、これ本当に持続可能なのか……」と思いながら運用してたんですよね。で、藁にもすがる気持ちでInferentia2(inf2)とTrainium2(trn2)を本格検証し始めたのが2025年末。今年2026年の4月に本番切り替えが完了して、6ヶ月ぶんくらいの知見が溜まってきたのでそろそろ書き残しておこうと思った。
正直、最初は懐疑的だった。「Neuron SDKの学習コストが高い」「モデル対応範囲が狭い」という話を聞いていたし、実際に半年前の記事を読むとかなり制約が多そうに見えた。でも2026年現在のNeuron SDK 2.xは別物に近い進化をしていて、そのギャップに驚いた。その体験を、できるだけリアルに書く。
Inferentia2とTrainium2、何が変わったのか(2026年時点)
2026年現在、AWS Neuronスタックは大幅に成熟した。Neuron SDK 2.20(2026年Q1リリース)では、HuggingFace Transformersとのネイティブ統合が大幅に改善されて、既存のPyTorchコードへの変更が最小限で済むようになっている。これが一番デカかった。
ハードウェアの現状スペック比較
| インスタンス | チップ | vCPU | RAM | NeuronCore | ネットワーク帯域 | オンデマンド時間単価 |
|---|---|---|---|---|---|---|
| inf2.xlarge | Inferentia2 × 1 | 4 | 16 GiB | 2 | 最大25 Gbps | $0.7582 |
| inf2.8xlarge | Inferentia2 × 1 | 32 | 128 GiB | 2 | 25 Gbps | $1.9712 |
| inf2.24xlarge | Inferentia2 × 6 | 96 | 384 GiB | 12 | 100 Gbps | $6.4906 |
| inf2.48xlarge | Inferentia2 × 12 | 192 | 768 GiB | 24 | 100 Gbps | $12.9812 |
| trn2.48xlarge | Trainium2 × 16 | 192 | 2048 GiB | 64 | 3200 Gbps | $37.1952 |
| p4d.24xlarge(比較) | A100 × 8 | 96 | 1152 GiB | — | 400 Gbps | $32.7726 |
| p5.48xlarge(比較) | H100 × 8 | 192 | 2048 GiB | — | 3200 Gbps | $98.3232 |
※2026年6月時点のus-east-1オンデマンド価格(税抜き)。Savingsプランだとさらに最大66%オフになる。
うちのユースケースはテキスト生成モデルの推論(Llama 3.1 70Bクラス)とEmbeddingモデルの大量バッチ処理の2本柱。Inferentia2を推論に、Trainium2を学習・ファインチューニングに使い分ける構成にした。
Trainium2に関しては、SageMaker Pipelines ML CI/CDの実装ガイドでも触れたが、学習ジョブのオーケストレーションはSageMakerと組み合わせると管理がかなり楽になる。
実際のコスト効率:p4d比較で何%削減できたか
これが本題。社内でも「本当にそんなに安くなるの?」と懐疑的な声が多くて、数字で見せる必要があった。
検証条件:Llama 3.1 70B、バッチサイズ8、シーケンス長2048、FP16精度
まずNeuron SDKでモデルをコンパイルするところから。
# compile_script.py
# pip install torch-neuronx neuronx-cc transformers-neuronx
from transformers_neuronx import LlamaForSampling
from transformers import AutoTokenizer
import torch
model_id = "meta-llama/Llama-3.1-70B"
tokenizer = AutoTokenizer.from_pretrained(model_id)
# NeuronX向けにコンパイル(初回は20〜40分かかる)
model = LlamaForSampling.from_pretrained(
model_id,
batch_size=8,
tp_degree=12, # inf2.24xlargeの場合
amp='f16',
n_positions=2048
)
model.to_neuron()
# コンパイル済みモデルの保存
model.save('llama31-70b-neuron-compiled')
print("コンパイル完了!次回以降はこのキャッシュを使う")
コンパイルが終わったら推論を動かしてベンチマークを取る:
# inference_benchmark.py
import time
import torch
from transformers_neuronx import LlamaForSampling
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-70B")
model = LlamaForSampling.from_pretrained('llama31-70b-neuron-compiled')
model.to_neuron()
prompts = ["AWS Inferentia2の特徴を説明してください。"] * 8
# ウォームアップ(最初の数回は遅い)
for _ in range(3):
inputs = tokenizer(prompts, return_tensors='pt', padding=True)
with torch.no_grad():
output = model.sample(inputs['input_ids'], sequence_length=512)
# 本番測定
start = time.time()
for _ in range(20):
inputs = tokenizer(prompts, return_tensors='pt', padding=True)
with torch.no_grad():
output = model.sample(inputs['input_ids'], sequence_length=512)
elapsed = time.time() - start
tokens_generated = 20 * 8 * 512
print(f"スループット: {tokens_generated / elapsed:.1f} tokens/sec")
print(f"1リクエストあたりのレイテンシ: {elapsed / 20 * 1000:.1f} ms")
計測結果はこうなった:
# inf2.24xlarge での結果
スループット: 4,820 tokens/sec
1リクエストあたりのレイテンシ: 1,320 ms
# p4d.24xlarge(A100 x8)での比較結果
スループット: 6,140 tokens/sec
1リクエストあたりのレイテンシ: 1,040 ms
スループットだけ見るとA100に負けてる。でもコストまで含めると話が変わる:
xychart-beta
title "1Mトークンあたりのコスト比較(USD、オンデマンド)"
x-axis ["inf2.24xl", "inf2.48xl", "p4d.24xl", "p5.48xl", "trn2.48xl(学習)"]
y-axis "コスト(USD)" 0 --> 25
bar [3.72, 2.98, 14.82, 21.43, 8.91]
inf2.24xlargeはp4d比でコスト75%削減。スループットは78%水準だから、コスト効率で見ると圧勝になる。これを初めて見たとき「え、こんなに違うの」と思わず声に出てしまった。
本番構成のアーキテクチャ
うちが採用した構成を共有する。ECS on EC2ベースで、インスタンスプールをInferentia2とGPUのハイブリッドにしている。リアルタイム性が求められるリクエストはGPU側(緊急フォールバック)、スループット優先のバッチはInferentia2側に流す設計だ。
こういったコスト最適化の考え方はSavings Plans vs Reserved Instancesの比較記事でも整理しているので参考にしてほしい。
graph TB
subgraph Internet
Client[クライアント]
end
subgraph AWS_Cloud[AWS Cloud]
ALB[Application Load Balancer]
subgraph VPC[VPC: 10.0.0.0/16]
subgraph Public_Subnet[Public Subnet]
NatGW[NAT Gateway]
end
subgraph AZ_A[AZ-1a: Private Subnet]
ECS_Service_A[ECS Service\nInference API]
subgraph ECS_Tasks_A[ECS Tasks]
Inf2_Task1[inf2.24xlarge\nLlama 3.1 70B\nNeuron Runtime]
Inf2_Task2[inf2.8xlarge\nEmbedding Model\nNeuron Runtime]
end
end
subgraph AZ_B[AZ-1b: Private Subnet]
ECS_Service_B[ECS Service\nFallback GPU]
subgraph ECS_Tasks_B[ECS Tasks]
GPU_Task1[g5.12xlarge\nFallback推論\nTorch CUDA]
end
end
subgraph Training_Subnet[Training Subnet]
SageMaker_Job[SageMaker Training Job\ntrn2.48xlarge\nLoRA Fine-tuning]
EFS_Mount[EFS\nモデルキャッシュ共有]
end
end
subgraph ML_Services[ML Services]
S3_Models[S3\nモデルアーティファクト]
ECR[ECR\nNeuron Container Image]
CW_Metrics[CloudWatch\nNeuron メトリクス]
end
subgraph Routing[ルーティングロジック]
Lambda_Router[Lambda\nリクエストルーター\nlatency/cost切替]
end
end
Client --> ALB
ALB --> Lambda_Router
Lambda_Router -->|"スループット優先"| ECS_Service_A
Lambda_Router -->|"低レイテンシ優先"| ECS_Service_B
ECS_Service_A --> Inf2_Task1
ECS_Service_A --> Inf2_Task2
ECS_Service_B --> GPU_Task1
Inf2_Task1 --> EFS_Mount
Inf2_Task2 --> EFS_Mount
SageMaker_Job --> S3_Models
S3_Models --> EFS_Mount
ECR --> ECS_Tasks_A
Inf2_Task1 --> CW_Metrics
この構成で地味に重要なポイントが2つある。
EFSでモデルキャッシュを共有しているのが一番効いた。Neuronのコンパイル済みアーティファクトをEFSに置くことで、コンテナ起動時のコンパイル待ち(最大40分)を回避している。最初これをやらずにECSタスクが全台再起動するたびにコンパイルが走って地獄を見た。Lambda SnapStartのコールドスタート対策に近い発想で、初回コンパイルさえ済ませれば2回目以降はキャッシュヒットで30秒以内に起動できる。
Lambdaルーターでリクエストを動的に振り分けているのも効いている。レイテンシSLAが厳しいリクエスト(200ms以内必須など)はGPUフォールバックに流し、バッチ処理やそれ以外はInferentia2に集約する。SLO設計の記事でも書いたが、SLOによってハードウェア選択が変わる典型例だと思う。
導入でハマったポイント4つ
正直なところ、スムーズにはいかなかった。つまずいたところを書いておく。
1. バッチサイズが違うと別コンパイルが必要問題
Neuronはコンパイル時にバッチサイズとシーケンス長を静的に焼き込む。つまりbatch_size=1で来たリクエストとbatch_size=8では別のコンパイル済みグラフが必要になる。これを知らずに「なんかバッチが合わない」と数時間悩んだ。
うちはバッチサイズを1/4/8の3パターンでプリコンパイルして、リクエスト数に応じてモデルを切り替える方式にした:
# multi_batch_loader.py
class NeuronModelPool:
def __init__(self, model_id: str, batch_sizes: list[int]):
self.models = {}
for bs in batch_sizes:
print(f"バッチサイズ {bs} のモデルをロード中...")
self.models[bs] = LlamaForSampling.from_pretrained(
f"{model_id}-batch{bs}",
batch_size=bs,
tp_degree=12,
amp='f16'
)
self.models[bs].to_neuron()
def get_model(self, request_count: int):
# 要求数以上で最小のバッチサイズを選択
for bs in sorted(self.models.keys()):
if bs >= request_count:
return self.models[bs], bs
return self.models[max(self.models.keys())], max(self.models.keys())
pool = NeuronModelPool("llama31-70b-neuron-compiled", [1, 4, 8])
2. CloudWatchのNeuronメトリクスがデフォルトで届かない
Neuron Monitorをサイドカーとして動かす必要がある。ECSのタスク定義に追加するのを忘れていて、1週間ほどNeuronCoreの稼働率が見えない状態で運用してしまった。こういう見落としが一番じわじわくる。
{
"name": "neuron-monitor",
"image": "790709498068.dkr.ecr.us-east-1.amazonaws.com/neuron-monitor:latest",
"essential": false,
"environment": [
{
"name": "NEURON_MONITOR_CW_REGION",
"value": "us-east-1"
},
{
"name": "NEURON_MONITOR_CW_NAMESPACE",
"value": "NeuronMetrics"
}
],
"mountPoints": [
{
"sourceVolume": "neuron-device",
"containerPath": "/dev/neuron0"
}
]
}
3. 量子化との相性問題
INT8量子化をNeuron上でかけようとしたら、一部のレイヤーでNaN問題が出た。2026年Q1時点のNeuron SDKでは、特定のAttentionパターンでINT8量子化が不安定になるケースが報告されている。今のところFP16が最安定で、BF16は条件付きで使える。INT8は自前で十分検証してから本番適用した方がいい。
4. Spot Instanceが思ったより当たらない
inf2系はSpotが当たらないリージョンが多い。us-east-1でもinf2.24xlargeのSpot在庫が安定しなくて、ECSのSpot構成が頻繁に中断された。結局Savings Plans(Compute Savings Plans)で1年コミットする方が実質的に安くなった。Compute Savings Plansはインスタンスファミリーを問わないので、inf2とg5の両方に適用できるのが個人的には一番うれしいポイントだった。
これらのハマりポイントを乗り越えた結果、月次コストはこう変化した:
xychart-beta
title "月次コスト推移(ML推論基盤、万円)"
x-axis ["2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03", "2026-04", "2026-05"]
y-axis "月次コスト(万円)" 0 --> 250
line [228, 235, 241, 198, 163, 121, 89, 83]
2026年1月から本番移行準備を開始して、4月に完全切り替え。そこからの下げ幅がなかなかえぐい。
Trainium2でのファインチューニング実績
Inferentia2が推論なら、Trainium2はファインチューニング担当だ。うちでは社内ドメイン特化のLoRAアダプタを定期的に更新していて、trn2.48xlargeを使っている。LoRAファインチューニング本番運用の記事でも詳しく書いたが、Trainium2はHBM2eを2048GiB積んでいるので70BクラスのフルパラメータSFTも1台で回せる。これだけでもかなり気が楽になった。
NeuronXでのTrainium2学習ジョブはこんな感じで書いている:
# train_with_neuronx.py
import os
from transformers import TrainingArguments, Trainer
from transformers_neuronx.trainer import NeuronTrainer
from peft import LoraConfig, get_peft_model
os.environ["NEURON_COMPILE_CACHE_URL"] = "s3://my-bucket/neuron-cache"
os.environ["NEURON_RT_NUM_CORES"] = "64" # trn2.48xlargeのNeuronCore数
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(base_model, lora_config)
training_args = TrainingArguments(
output_dir="/opt/ml/model",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=8,
learning_rate=2e-4,
fp16=True,
dataloader_num_workers=4,
# Neuron固有の設定
xla=True, # XLAバックエンドを有効化
xla_fsdp_settings={"xla_fsdp_v2": True},
)
trainer = NeuronTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
)
trainer.train()
print("学習完了。アダプタをS3に保存中...")
trainer.save_model()
trn2.48xlargeでのLlama 3.1 70B LoRA(r=16)の学習速度は、p4d.24xlarge(A100 x8)比で約1.4倍のスループット。コスト単価はtrn2.48xlargeの方が高いが、Savings Plansの割引率がTrainiumの方が有利なので実質コストは同等〜やや安い水準に落ち着いている。正直まだ検証中の部分もあって、学習データのサイズやエポック数によってどっちが有利かは変わってくる。
現在のコスト内訳はこうなっている:
pie title ML基盤コスト内訳(2026年5月)
"inf2推論(Savings Plans)" : 41
"trn2学習(オンデマンド)" : 23
"EFS・S3・転送" : 11
"g5フォールバック" : 14
"その他(監視・ネットワーク)" : 11
まとめ
Inferentia2・Trainium2の本番導入から6ヶ月、感じてることをまとめるとこうなる:
| 観点 | 結論 |
|---|---|
| コスト効率 | 推論ユースケースでp4d比70〜75%削減を達成。スループットは劣るが、コスパで見れば現時点で最強クラス |
| SDK成熟度 | Neuron SDK 2.20はHuggingFace統合が格段に良くなった。既存PyTorchコードからの移行は1〜2スプリント程度 |
| コンパイルキャッシュ | EFS/S3でアーティファクトを共有する仕組みを最初から設計しないと、コンテナ再起動のたびに40分待つ地獄に落ちる |
| Spot Instance | リージョン・サイズによって在庫が安定しない。Compute Savings Plans 1年コミットの方が運用ストレスが低い |
| INT8量子化 | 2026年6月時点では本番適用前に十分な検証が必要。FP16で十分な精度が出るならそちらが安全 |
1年前の情報で「移行コストが高い」と諦めた人には、もう一度見てほしいというのが率直な気持ちだ。当時とは別のSDKだと思っていい。
次のアクションとしては、まずinf2.xlarge(一番小さいサイズ)でEmbeddingモデルを試すのがリスクが低くておすすめ。Embedding系はコンパイルも速くてNeuronの恩恵を受けやすい。大規模LLMに行く前のウォームアップとして最適だと思う。
GPUコストで悩んでるチームは、ぜひ一度試してみてほしい。少なくともうちは「やって正解だった」と言い切れる。