本番運用におけるPyTorch+CUDAサーバーでの「Unknown Error」問題とその対策

こんにちは!Qualitegプロダクト開発部です。
今日は、GPUをつかった商用サービスにて悩ましい、テストは全部通るけど、長時間運用をしていると急に起こる「CUDA error: unknown error」についての内容です。
これ、出会うと残念な気持ちになりますが、けっこうGPU商用サービス界隈では「あるある」なんです。
原因を真面目に探るには CUDAバージョン、PyTorchバージョンの調合具合、実際のアプリケーションコードまですべてソースまで追う必要があるのですが、多くの場合、運用でカバーします。
なぜなら仮に1つ原因をみつけて対処できたとしても、CUDAバージョンはしょっちゅうあがりますし、PyTorchもそれに追従して頻繁に更新されます。さらにやっかいなことに、1日、2日、いや1週間くらいは安定的に動作しているようにみえて、数週間後にとつぜんエラーが出るといった具合なので、修正確認の難易度が高いんです。
そこで本日は「開発環境や実験環境」ではなく「本番環境」で発生しがちなこのCUDA Unknown Error について問題の原因と実践的な対策について解説します。
問題の具体例
典型的なエラーは次のようなスタックトレースとして現れます:
RuntimeError: CUDA error: unknown error
CUDA kernel errors might be asynchronously reported at some other API call, so the stacktrace below might be incorrect.
For debugging consider passing CUDA_LAUNCH_BLOCKING=1
Compile with `TORCH_USE_CUDA_DSA` to enable device-side assertions.
特に多く見られるのは、NumPy配列からPyTorchテンソルへの変換時に発生するエラーです:
img = torch.from_numpy(img.copy()).to(device, dtype=torch.float32)
なぜこの問題が発生するのか
この問題が発生する主な理由は以下のようなものがかんがえられます
1. CUDA・PyTorchのバージョン互換性の問題
CUDAとPyTorchのバージョンをアップグレードした後に、このようなエラーが突然発生するケースが多く見られます。
特にマイナーアップデートでさえ、内部実装の変更によって未知の互換性問題が引き起こされることがあります。例えば、CUDA 12.4から12.8へのアップグレード後、それに追従したPyTorchのアップグレード、また、そのために Pythonのバージョンをアップグレードしたときに付随して問題が発生することがよくあります。
2. GPUメモリの管理とリーク
PyTorchはGPUメモリを効率的に使用するためにキャッシュ機構を採用していますが、このキャッシュは完璧ではありません。
長時間の実行中に、小さなメモリリークが蓄積したり、メモリの断片化が進行したりします。
さらに、PyTorchのメモリ管理はアプリケーション層から詳細に制御することができません。
ざっくりとした解放命令やGC要求はできますが、それ以上の細かい制御はできずメモリの解放、予約領域の解放など簡単にはアプリ層からは触れず、あるいいみPyTorchまかせです。
2. CUDA非同期処理の特性
CUDAの多くの操作は非同期で行われるため、エラーが実際に発生した場所と報告される場所が異なることがあります。
エラーメッセージにも記載されている通り、実際のエラーは別のAPIコールでしれっと非同期的に報告されることがあります。
これによりデバッグがさらに困難になります。
3. 長時間実行の影響
商用サーバーアプリケーションは通常、数日から数週間(あるいはそれ以上)にわたって実行され続けます。
この間、小さな問題が徐々に蓄積し、最終的にはクリティカルなエラーとなって現れることがあります。
4. モデルの複雑性と負荷
当社サービスでも頻繁に実行される顔検出や画像認識などの複雑なモデルでは、大量のGPUリソースが必要とされ、長時間の高負荷状態がこの問題を引き起こす可能性が高まります。
実用的な対策
この問題に対処するための実践的なアプローチをいくつか紹介します
0.バージョンアップグレード前の長時間テスト
CUDAやPyTorchのバージョンをアップグレードする前に、必ず本番環境と同じ条件で長時間のテストを実施することが重要です。
特に注意すべき点は以下の通りです
- 実際の負荷条件での検証 実際の運用時と同様の負荷をかけたテストを行います
- 最低24時間の継続実行 メモリリークなどは時間経過とともに発生するため、短時間のテストでは発見できません
- 繰り返しの処理 同じ処理を数千回繰り返すことで、安定性を確認します
# 最低でも24時間は同じ負荷で継続的に実行し、安定性を確認
for i in {1..10000}; do
python your_processing_script.py --batch-size 32
sleep 5
done
- リソース使用量の監視 テスト中はGPUメモリやCPU使用率を継続的に監視し、徐々に増加する傾向がないか確認します
このとき PyTorchやPythonの組み込み関数だけでなく、NVML(NVIDIA Management Library)を使用すて定期的に実行して監視するのがおすすめです。プロセス横断での使用メモリ量をより性格に取得することができます。 - エラーログの監視 わずかな警告メッセージでも見逃さないようにログを注意深く監視します
互換性マトリックスの確認
PyTorchとCUDAのバージョン互換性を事前に確認します。PyTorch公式サイトには互換性マトリックスが公開されていますが、実際のアプリケーションでの互換性は環境によって異なることがあります。

1. 運用面での対策
定期的な再起動の自動化
最も単純かつ効果的な対策は、サービスを定期的に再起動するスケジュールを設定することです。
# crontabの例: 毎日午前3時にサービスを再起動
0 3 * * * systemctl restart your_service
ヘルスチェックと自動再起動
定期的な再起動に加え、アプリケーションの状態を監視し、エラーが発生したら自動的に再起動するようにします
try:
# メイン処理
process_images()
except RuntimeError as e:
if "CUDA error" in str(e):
# ログ記録
logging.error("CUDA error detected, restarting service")
# プロセス再起動コード
os.execv(sys.executable, ['python'] + sys.argv)
GPUリソースのモニタリング
NVML をつかってGPU使用率やメモリ消費を定期的に監視し、問題の予兆を検知します
import pynvml
def monitor_gpu():
pynvml.nvmlInit()
handle = pynvml.nvmlDeviceGetHandleByIndex(0)
info = pynvml.nvmlDeviceGetMemoryInfo(handle)
used_percent = info.used / info.total * 100
# 使用率が90%を超えたら警告
if used_percent > 90:
logging.warning(f"GPU memory usage high: {used_percent:.2f}%")
# 対策を実施 (キャッシュクリア、プロセス再起動など)
2. コード面での対策
コード面での一般的な対策も記載しておきましょう。
これは今回の根深い問題の処方箋ではありませんが、一般論としてご紹介いたします
CUDAキャッシュのクリア
定期的にPyTorchのCUDAキャッシュをクリアすることで、メモリリークの影響を減らすことができます
def clear_gpu_memory():
torch.cuda.empty_cache()
# 一定回数の処理ごとにキャッシュクリア
for i, batch in enumerate(data_loader):
process_batch(batch)
if i % 100 == 0:
clear_gpu_memory()
バッチサイズの最適化
こちらも一般論ですが、大きなバッチサイズはGPUメモリを圧迫します。より小さなバッチサイズを使用することで、メモリ問題を軽減できることがあります
# より小さなバッチサイズを設定
data_loader = DataLoader(dataset, batch_size=8, shuffle=True)
CPUフォールバックの実装
GPU処理に失敗した場合に自動的にCPUにフォールバックするロジックを実装すると、サービスの継続性が向上します
def process_with_fallback(image):
try:
# GPUで処理
return process_on_gpu(image)
except RuntimeError as e:
if "CUDA error" in str(e):
logging.warning("Falling back to CPU processing")
# CPUで処理
return process_on_cpu(image)
else:
raise
デバッグフラグの使用
エラーメッセージで推奨されているように、CUDA_LAUNCH_BLOCKING=1
フラグを設定することで、非同期エラーの正確な位置を特定しやすくなります
CUDA_LAUNCH_BLOCKING=1 python your_script.py
3. アーキテクチャ面での対策
マイクロサービス化と水平スケーリング
モノリシックなアプリケーションを小さなマイクロサービスに分割することで、一部に問題が発生しても全体のサービスが継続できるようになります。
また、個別のサービスごとに再起動戦略を実装できます。
当社サービスも小さな単位でマイクロサービス化されており、同一の機能を提供するマイクロサービスが水平展開されています。マイクロサービスの定期的な再起動タイミングが重ならないようにスケジューリングすることでサービスの継続性を向上させることができます。
つまり複数のサーバーインスタンスを使用してロードバランシングすることで、一部のインスタンスが失敗しても、他のインスタンスがリクエストを処理できるようになります。
Kubernetes等のコンテナオーケストレーションツールを使用すると、こうした構成を効率的に管理できます。
まとめ
「CUDA error: unknown error」は、PyTorchとCUDAを使用した長時間実行サーバーでよく見られる問題です。
特にそれまでは安定していたのに、バージョンアップグレード後に突然発生することが多いため、事前の十分な検証と長時間テストが重要です。
この問題は完全に防ぐことは難しいものの、適切な運用戦略と予防策を組み合わせることで、その影響を最小限に抑えることができます。
バージョン互換性の慎重な検証、定期的な再起動の自動化、リソースモニタリング、コード最適化、適切なアーキテクチャ設計など、複数のアプローチを組み合わせて、信頼性の高いGPU対応サービスを構築することが重要です。
特に本番環境へのデプロイ前には、必ず数日間の安定性テストを行い、長時間実行時の問題を事前に発見することが、予期せぬダウンタイムを防ぐ鍵となります。
このような対策を導入することで、ディープラーニングやコンピュータビジョンのサーバーアプリケーションを、より安定して運用することができるでしょう。
また、当社ではGPU商用サービスをご検討中または、商用サービス運用中でお悩みをお持ちの事業者様へのGPUテクニカルコンサルティング・アドバイザリーのご提供も行っておりますので、お気軽にご相談くださいませ。