はじめに
MacのApple Silicon(M1/M2/M3/M4)では、PyTorchのMPS(Metal Performance Shaders)バックエンドを使用することで、GPU推論を大幅に高速化できます。本記事では、uv環境でのセットアップから実際のベンチマーク結果まで、簡潔に解説します。
環境
- MacBook Pro M4
- PyTorch 2.9.1
- Python 3.10.19(Homebrew)
- uvパッケージマネージャー
1. 環境構築
uv仮想環境の作成
# 仮想環境を作成
uv venv
# 仮想環境を有効化
source .venv/bin/activate
PyTorchのインストール
# PyTorchとtorchvisionをインストール
uv pip install torch torchvision
2. MPSの有効化方法
PyTorchでMPSデバイスを使用するのは非常に簡単です。
import torch
# MPS利用可能性の確認
if torch.backends.mps.is_available():
device = torch.device("mps")
print("MPSデバイスが利用可能です")
else:
device = torch.device("cpu")
print("CPUを使用します")
# モデルとデータをMPSデバイスに転送
model = model.to(device)
input_data = input_data.to(device)
3. 推論速度比較
ResNet18を使った画像分類タスクで、CPUとMPSの推論速度を比較しました。
ベンチマーク条件
- モデル: ResNet18(事前学習済み)
- 入力: バッチサイズ32、224×224のRGB画像
- 計測回数: 100回(ウォームアップ10回)
実装コード
import torch
import torchvision.models as models
import time
# モデル準備
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
model.eval()
# デバイス選択
device = torch.device("mps") # または "cpu"
model = model.to(device)
# ダミーデータ
dummy_input = torch.randn(32, 3, 224, 224, device=device)
# 推論実行
start = time.time()
with torch.no_grad():
output = model(dummy_input)
# MPS使用時は同期が必要
if device.type == "mps":
torch.mps.synchronize()
elapsed = time.time() - start
結果
| デバイス | 平均推論時間 | スループット |
|---|---|---|
| CPU | 454.29 ms | 2.20 batch/sec |
| MPS (GPU) | 58.29 ms | 17.15 batch/sec |
高速化率: 7.79倍
まとめ
- uvを使ったPyTorch環境構築は簡単で高速
- MPSバックエンドは
torch.device("mps")で簡単に利用可能 - M4 MacではCPUと比較して約8倍の高速化を実現
- 推論処理が大量にある場合、MPSの活用で大幅な時間短縮が可能
サンプルコード
完全なベンチマークコードは以下で公開しています。
"""
PyTorch MPS (Metal Performance Shaders) vs CPU 推論速度比較
"""
import torch
import torchvision.models as models
import time
import numpy as np
def check_mps_availability():
"""MPS利用可能性を確認"""
return torch.backends.mps.is_available() and torch.backends.mps.is_built()
def prepare_model_and_data(device):
"""モデルとデータを準備"""
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
model.eval()
model = model.to(device)
dummy_input = torch.randn(32, 3, 224, 224, device=device)
return model, dummy_input
def benchmark_inference(model, input_data, num_iterations=100):
"""推論速度をベンチマーク"""
inference_times = []
# ウォームアップ
for _ in range(10):
with torch.no_grad():
_ = model(input_data)
# 計測
for _ in range(num_iterations):
start_time = time.time()
with torch.no_grad():
_ = model(input_data)
if 'mps' in str(input_data.device):
torch.mps.synchronize()
end_time = time.time()
inference_times.append(end_time - start_time)
return np.mean(inference_times) * 1000 # ミリ秒
def main():
# CPU
cpu_device = torch.device("cpu")
cpu_model, cpu_input = prepare_model_and_data(cpu_device)
cpu_time = benchmark_inference(cpu_model, cpu_input)
# MPS
if check_mps_availability():
mps_device = torch.device("mps")
mps_model, mps_input = prepare_model_and_data(mps_device)
mps_time = benchmark_inference(mps_model, mps_input)
print(f"CPU: {cpu_time:.2f} ms")
print(f"MPS: {mps_time:.2f} ms")
print(f"高速化率: {cpu_time/mps_time:.2f}x")
if __name__ == "__main__":
main()