「ResourceExhaustedError: OOM when allocating tensor…」について解説します。
このエラー、通称「OOM(Out Of Memory)」は、TensorFlowという巨大なフレームワークを扱う上で避けては通れない通過儀礼のようなものです。しかし、単に「バッチサイズを下げればいい」という短絡的な理解で済ませてはいませんか? 実は、このエラーの裏側には、NVIDIA GPUのハードウェアアーキテクチャ、CUDAのメモリマネジメント、そしてTensorFlow独自の「BFC Allocator」という極めて高度で複雑な仕組みが隠されています。
この「ResourceExhaustedError」を単なるエラーメッセージとしてではなく、コンピュータサイエンスの深淵から解き明かしていきます。この記事を読み終える頃、あなたはGPUメモリの状態を脳内で視覚化し、最適化の極意をマスターしているはずです。
1. なぜ「メモリが足りない」だけでは不十分なのか?
まず、エラーの表面的な意味を整理しましょう。ResourceExhaustedErrorは、TensorFlowが計算グラフ(Graph)を実行する過程で、新しいテンソル(Tensor)を保持するためのメモリ領域を確保しようとした際、デバイス(主にGPU)に十分な空き容量がない場合にスローされます。
しかし、不思議に思ったことはありませんか? nvidia-smiで確認すると、数GBの空きがあるように見えるのに、わずか数百MBのテンソルを確保しようとしてOOMが発生することがあります。ここに、現代のディープラーニング・インフラストラクチャが抱える「メモリ断片化(Fragmentation)」の真実があります。
GPUメモリとCPUメモリの決定的な違い
通常のCPUメモリ(RAM)であれば、OSの仮想メモリ機構が強力に働き、物理的に離れたメモリ領域をページングによって論理的な連続領域として見せかけることができます。しかし、GPUの世界はもっと過酷です。高いスループットを実現するために、GPUメモリ(VRAM / HBM)は極めて高速なバス帯域を持っていますが、その管理単位はCPUほど柔軟ではありません。
特にTensorFlowのようなフレームワークでは、カーネル(GPU上で動作する関数)を高速に実行するために、メモリの「連続性」を強く要求します。つまり、全体で1GB空いていたとしても、512MBの連続したブロックがなければ、512MBのテンソルは確保できないのです。これが、私たちが直面するOOMの正体です。
2. TensorFlowのメモリ戦略:強欲なアロケータの正体
TensorFlowは、起動時に利用可能なGPUメモリを「ほぼすべて(デフォルトで約95%以上)」確保しようとします。この挙動は、初心者にとっては「バグ」に見えるかもしれませんが、これには歴史的・技術的な背景があります。
BFC Allocator (Best-Fit with Coalescing)
TensorFlowの内部では、「BFC Allocator」と呼ばれるアルゴリズムが採用されています。これは、Doug Leaのmallocの実装をベースにしたもので、以下の特徴を持ちます。
- Best-Fit: 要求されたサイズに最も近い、空いているメモリブロックを探す。
- Coalescing: 隣り合う空きブロックを結合して、より大きなブロックを作る。
TensorFlowがメモリを最初に全確保するのは、「メモリアロケーションのオーバーヘッドを最小化するため」です。学習中、毎秒何百回と繰り返されるテンソルの生成と破棄のたびに、CUDAのAPI(cudaMalloc)を呼び出していたのでは、パフォーマンスが著しく低下します。そのため、TensorFlowは最初に巨大なプールを確保し、その内部を独自のアロケータで高速に切り盛りするのです。
3. 実践! OOMをねじ伏せるための5つの戦略
理屈がわかったところで、現場で即座に役立つ解決策を、深掘りした技術解説とともに提示します。
戦略1:メモリの「成長」を許可する
デフォルトの「全確保」戦略は、単一のプロセスでGPUを独占する場合には有利ですが、マルチプロセスで動かす場合や、Jupyter Notebookを複数立ち上げる場合には致命的です。以下のコードは、必要に応じてメモリを確保するように指示します。
import tensorflow as tf
gpus = tf.config.experimental.list_physical_devices('GPU')
if gpus:
try:
for gpu in gpus:
# メモリの使用量を必要最小限に抑え、徐々に増やす
tf.config.experimental.set_memory_growth(gpu, True)
except RuntimeError as e:
print(e)技術的解説: これにより、TensorFlowは最初から全メモリをロックせず、徐々にBFC Allocatorのプールを拡張します。ただし、これを行うとメモリの断片化が発生しやすくなるという副作用もあります。断片化が極限に達すると、再びOOMが訪れることになります。
戦略2:バッチサイズと「仮想的なバッチサイズ」
もっとも単純な解決策はバッチサイズを下げることですが、それでは学習の安定性や収束速度に悪影響が出ることがあります。そこで登場するのが「勾配蓄積(Gradient Accumulation)」です。
物理的なバッチサイズが8でも、4ステップ分の勾配を溜めてから1回更新すれば、実質的なバッチサイズは32となります。これにより、VRAMを節約しながら、大規模バッチの恩恵を受けることができます。
# 疑似コードによる解説
accumulation_steps = 4
for step, (x, y) in enumerate(dataset):
with tf.GradientTape() as tape:
prediction = model(x)
loss = loss_fn(y, prediction) / accumulation_steps
# 勾配を計算
gradients = tape.gradient(loss, model.trainable_variables)
# 勾配を蓄積(自作の蓄積用変数に足し合わせる)
# ... (省略) ...
if (step + 1) % accumulation_steps == 0:
# ここで初めて重みを更新
optimizer.apply_gradients(zip(accumulated_gradients, model.trainable_variables))
# 蓄積をリセット戦略3:混合精度訓練(Mixed Precision)
現代のGPU(NVIDIA Voltaアーキテクチャ以降)には、Tensor Coreという強力な武器が搭載されています。これを利用しない手はありません。通常、テンソルは32bit浮動小数点(float32)で計算されますが、これを16bit(float16)に落とすことで、メモリ消費量を理論上半分に抑え、計算速度を劇的に向上させます。
from tensorflow.keras import mixed_precision
# 混合精度ポリシーの設定
policy = mixed_precision.Policy('mixed_float16')
mixed_precision.set_global_policy(policy)
# モデル構築時にDTypeを自動調整
model = build_model()技術的解説: ただし、単純に精度を落とすと「アンダーフロー(勾配が小さすぎて消える)」が発生します。TensorFlowのMixed Precision APIは、これを防ぐために「Loss Scaling」という仕組みを自動で導入します。これにより、数値安定性を保ちつつVRAMを節約できるのです。
4. 究極の技術解説:XLAコンパイラとメモリの融合
より高度な最適化として、XLA (Accelerated Linear Algebra) の存在を忘れてはいけません。TensorFlowの標準的な実行では、オペレーション(Add, MatMul, Reluなど)ごとに個別のカーネルが呼び出され、そのたびに中間結果がVRAMに書き込まれます。これがメモリ消費を加速させます。
XLAは、これらのオペレーションを解析し、複数の演算を「融合(Fusion)」した一つのカスタムカーネルを生成します。これにより、中間結果をVRAMに書き出す必要がなくなり、レジスタ内での計算が可能になります。結果として、メモリ帯域の節約だけでなく、VRAM使用量そのものの削減にも寄与します。
@tf.function(jit_compile=True)
def train_step(data):
# XLAによって最適化される計算
...5. 空間と時間のトレードオフ:Gradient Checkpointing
もし、どうしても巨大なモデルを(例えば解像度の高い画像入力で)動かしたい場合、究極の手段があります。それが「勾配チェックポインティング」です。
通常、逆伝播(Backpropagation)のために、順伝播(Forward pass)ですべての中間層の出力をメモリに保持しておく必要があります。チェックポインティングでは、一部の中間結果を破棄し、逆伝播の際に「その都度再計算」します。
「計算時間(時間)」を犠牲にして「メモリ(空間)」を稼ぐという、まさにコンピュータサイエンスの真髄とも言えるアプローチです。TensorFlowでは tf.recompute_grad を活用することで、この機能を実現できます。
まとめ:OOMは「対話」である
ResourceExhaustedError: OOM は、単なるエラーではなく、ハードウェアのリソースを限界まで引き出そうとするエンジニアに対する、GPUからの「対話」です。メモリの断片化、BFCアロケータの挙動、混合精度によるビットの節約、そしてXLAによるコンパイル最適化。これらを理解したとき、あなたは単なる「コードの書き手」から、ハードウェアの性能を極限まで絞り出す「パフォーマンスエンジニア」へと昇華します。
次にこのエラーに出会ったときは、忌々しく思うのではなく、ぜひニヤリと笑って nvidia-smi を眺めてください。そして、どの戦略を適用してこのパズルを解くか、楽しんでみてください。
さらに深く学びたい方へ
TensorFlowを単なるライブラリとしてではなく、実際のビジネスや分析の現場で「勝つための武器」として使いこなしたいなら、理論だけでなく「実践的な実装」を身体に叩き込む必要があります。特にメモリ制約の厳しい環境下で、いかに効率的なディープラーニングモデルを構築するか。そのヒントは、意外にも金融データのような「ノイズが多く、計算資源の最適化が求められる分野」の知見に詰まっています。
こちらの書籍は、TensorFlowの基礎から、実データへの適用、さらには高度なネットワーク構築までを網羅しており、OOMに悩まされるあなたの技術力をもう一段階引き上げる良き相棒となるでしょう。


コメント