Gradient Accumulation(グラディエント アキュムレーション)
複数のミニバッチにわたって勾配を蓄積し、蓄積完了後にまとめてパラメータを更新する学習手法。GPU メモリが不足して大きなバッチサイズを確保できない場合に、論理的に大きなバッチサイズを実現する標準的なテクニック。
Gradient Accumulation とは
Gradient Accumulation(勾配累積)は、GPU メモリの制約でバッチサイズを大きくできない場合に、複数のステップにわたって勾配を足し合わせてからパラメータを更新する手法です。物理バッチサイズ × 累積ステップ数 = 実効バッチサイズとなり、メモリ消費を増やさずに大きなバッチ効果を得られます。
なぜバッチサイズが重要なのか
LLM の学習では、バッチサイズが小さすぎると以下の問題が発生します。
| バッチサイズ | 勾配の推定精度 | 学習の安定性 | GPU利用効率 |
|---|---|---|---|
| 非常に小さい(1-4) | 低い(ノイズ大) | 不安定(振動) | 低い |
| 中程度(16-64) | 中程度 | 安定 | 中程度 |
| 大きい(256-2048) | 高い(滑らか) | 非常に安定 | 高い |
GPT-3 の論文では有効バッチサイズ 3.2M トークンが使用されており、これを物理バッチで実現するには膨大な GPU メモリが必要です。Gradient Accumulation はこのギャップを埋めます。
動作の仕組み
通常の学習ループでは、ミニバッチごとに「順伝播→逆伝播→パラメータ更新」を行います。Gradient Accumulation では「順伝播→逆伝播→勾配蓄積」を N 回繰り返し、N 回目の後にまとめて「パラメータ更新→勾配ゼロクリア」を実行します。
計算例:
- GPU メモリの制約で物理バッチサイズ = 4 が限界
- 目標の有効バッチサイズ = 32
- 累積ステップ数 = 32 / 4 = 8
- 8 ステップの勾配を蓄積 → 1 回のパラメータ更新
注意点と実装上のポイント
勾配のスケーリング: 蓄積した勾配は累積ステップ数で割って平均化する必要があります。PyTorch では loss = loss / accumulation_steps としてから loss.backward() を呼ぶか、更新時に学習率を調整します。
学習率スケジューラとの整合: ステップ数ベースのスケジューラ(CosineAnnealingLR 等)は、パラメータ更新回数(= 全ステップ / 累積ステップ数)に合わせて調整が必要です。Hugging Face Trainer では gradient_accumulation_steps パラメータで自動調整されます。
BatchNorm との相互作用: BatchNorm 層の統計量は各ミニバッチごとに計算されるため、累積ステップ数が大きいと実効バッチサイズと BatchNorm のバッチサイズが乖離します。LLM では LayerNorm が標準なのでこの問題はほとんど発生しません。
データ並列との関係: 複数 GPU でのデータ並列学習時、有効バッチサイズ = 物理バッチ × GPU 数 × 累積ステップ数になります。4 GPU × バッチ 4 × 累積 4 = 有効バッチ 64 です。
分散学習フレームワークでの設定
主要フレームワークでの設定方法を示します。
Hugging Face Trainer:
TrainingArguments に per_device_train_batch_size=4 と gradient_accumulation_steps=8 を指定するだけで有効バッチ 32 × GPU 数を実現。
DeepSpeed:
train_micro_batch_size_per_gpu と gradient_accumulation_steps で設定。ZeRO Stage 2/3 と自動的に連携。
PyTorch FSDP:
no_sync() コンテキストマネージャで累積ステップ中の All-Reduce を抑制し、通信オーバーヘッドを削減。
速度への影響
Gradient Accumulation 自体は計算量を増やしませんが、パラメータ更新の頻度が減少するため、1 エポックあたりの更新回数が減ります。ただし有効バッチサイズが大きくなることで収束に必要なエポック数が減少し、全体の学習時間は同等かむしろ短縮されることが一般的です。
通信コストの面では、累積中は GPU 間の All-Reduce が不要(no_sync 使用時)なため、マルチ GPU 環境では通信オーバーヘッドの削減効果があります。
FAQ
Q1: Gradient Accumulation を使うと学習結果は変わりますか?
A1: 理論的には、同じ有効バッチサイズ・同じ学習率であれば結果は同一です。ただし浮動小数点の丸め誤差により、ごくわずかな差異が生じる場合があります。BatchNorm を使用するモデルでは注意が必要ですが、LLM では LayerNorm のため問題ありません。
Q2: 累積ステップ数が大きすぎるとどうなりますか?
A2: 累積ステップ数が大きいと 1 更新あたりの計算時間が長くなり、学習の反応が遅くなります。また有効バッチサイズが過大になると勾配のノイズが減りすぎて汎化性能が低下する場合があります。一般的に有効バッチサイズ 256〜2048 トークンバッチが推奨範囲です。
Q3: 個人環境の RTX 4090 でも Gradient Accumulation は有効ですか?
A3: 非常に有効です。RTX 4090(24GB)で 7B モデルの LoRA ファインチューニングを行う場合、物理バッチ 2 × 累積 16 = 有効バッチ 32 が一般的な設定です。メモリ不足エラーが出たらまず物理バッチを下げて累積を増やすのがセオリーです。