TL

勾配蓄積と実効バッチサイズの拡大

GPUメモリが足りなくても大バッチ学習は諦めなくていい。勾配蓄積でマイクロバッチを積み上げ、実効バッチを自在に拡大する原理と、BatchNorm・分散同期で踏む落とし穴を押さえます。

応用勾配蓄積バッチサイズメモリ最適化分散学習ディープラーニング最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.勾配蓄積はマイクロバッチの勾配を複数ステップ足し込み、まとめて1回だけ更新する技法。メモリ消費は1マイクロバッチ分のまま、実効バッチ = マイクロバッチ × 蓄積回数 を任意に拡大できる。
  • 2.数学的に正しく大バッチと一致させるには損失の平均化が鍵。合計損失をそのまま足すと勾配が蓄積回数倍になるため、各マイクロバッチの損失を蓄積回数で割ってから逆伝播する。
  • 3.BatchNorm は統計をマイクロバッチ単位で計算するため実効バッチと等価にならない。分散学習の勾配同期とも更新頻度がずれるので、no_sync で通信を蓄積中だけ抑制するのが定石。

なぜ勾配蓄積が要るのか:バッチサイズはメモリで頭打ちになる

大規模モデルの学習では「大きなバッチで学習したい」という要求と「GPU メモリに載らない」という制約が正面衝突します。バッチサイズを上げると勾配推定のノイズが減り、学習率を上げられて収束が安定する場面が多い(この関係は バッチサイズと学習率の相互作用 で詳述)。一方、順伝播・逆伝播では活性(activation)をバッチ内の全サンプル分メモリに保持する必要があり、メモリ消費はバッチサイズにほぼ比例して増えます。

ここで効くのが 勾配蓄積(gradient accumulation) です。発想は単純で、載るだけの小さなマイクロバッチで勾配を計算し、それを複数回ぶんメモリ上で足し込んでから、まとめて1回だけ重みを更新する。こうすると、ピークメモリは1マイクロバッチ分のままで、更新に使う勾配は実質的に「マイクロバッチをすべて連結した大バッチ」のものと一致します。時間をメモリと交換する技法だと言えます。

原理:勾配は和をとれる、だから分割して足せる

勾配蓄積が成立する数学的な根拠は、ミニバッチ勾配がサンプル損失の和(または平均)の勾配であるという線形性です。バッチ全体の損失を平均で書くと、勾配は次のように各サンプルの勾配の平均になります。

L_batch = (1/N) * Σ_i loss(x_i)         # N サンプルの平均損失
∇L_batch = (1/N) * Σ_i ∇loss(x_i)       # 勾配も平均(微分は線形)

つまり N サンプルを K 個のマイクロバッチ(各サイズ N/K)に割っても、各マイクロバッチの勾配を足し合わせれば全体の勾配が復元できます。これがメモリ上で逐次的に実行できるのは、勾配バッファ(param.grad)への足し込みがインプレースで累積されるからです。逆伝播を呼ぶたびに勾配が上書きではなく加算されるフレームワークの挙動を、そのまま利用しています。

optimizer.zero_grad()                    # 勾配バッファを明示的にゼロ化
for k in range(K):                       # K = 蓄積回数(accumulation steps)
    micro = next_microbatch()            # 1マイクロバッチ(メモリに載るサイズ)
    loss = compute_loss(model, micro)
    loss = loss / K                      # ★ ここで蓄積回数で割るのが核心
    loss.backward()                      # param.grad に加算(累積される)
optimizer.step()                         # K回ぶん積んだ勾配で1回だけ更新
optimizer.zero_grad()                    # 次の実効バッチに備えてクリア

実効バッチサイズ(effective batch size)は マイクロバッチサイズ × K × データ並列数 で決まります。K を増やすだけでメモリを増やさずに実効バッチを2倍にも8倍にもでき、ハードウェアを変えずに大バッチ学習を再現できるのが最大の利点です。

平均化の 1/K を忘れると勾配が K 倍になる

コードの loss = loss / K を落とすと、K 個のマイクロバッチ損失の合計の勾配が積まれ、本来の平均勾配の K 倍になります。これは実質的に学習率を K 倍したのと同じで、発散や不安定化を招きます。各マイクロバッチで損失を K で割れば、足し込み後の勾配は大バッチの平均勾配と一致します。なお損失関数が reduction="sum" の場合は割る定数が変わるので、最終的に「全サンプル平均の勾配」になっているかを基準に確認するのが安全です。

等価になる部分・ならない部分

勾配蓄積は「大バッチと完全に同じ」になるわけではありません。何が一致し、何がずれるかを切り分けることが実務では決定的です。

要素通常の大バッチ勾配蓄積(K回)等価か
重み更新に使う勾配全Nサンプルの平均勾配1/K平均で足した同じ勾配等価(数学的に一致)
ピークメモリNサンプル分の活性N/Kサンプル分の活性蓄積側が大幅に少ない
BatchNormの統計全Nサンプルで計算N/Kサンプルごとに計算等価でない
更新1回あたりの計算時間1回の前後伝播K回の前後伝播蓄積側がK倍遅い
ドロップアウト等の乱数全サンプル同一文脈マイクロバッチごとに独立実質ほぼ無影響

更新に使う勾配そのものは数学的に一致します。一方でピークメモリは大きく下がる代わりに、更新1回に K 回の前後伝播が要るので実時間は約 Kかかります。これが「時間とメモリの交換」の意味です。問題になるのは、バッチ全体を見て初めて正しく計算される演算——その代表が BatchNorm です。

BatchNorm との相互作用:ここが最大の落とし穴

BatchNorm はミニバッチ内の平均と分散を使って活性を正規化します(詳細は BatchNorm の理論)。勾配蓄積では正規化は各マイクロバッチ単位で実行されるため、統計は N/K サンプルからしか計算されません。つまり BatchNorm から見える「バッチ」はマイクロバッチであって、実効バッチではない

この結果、勾配蓄積は BatchNorm 入りモデルでは大バッチと等価になりません。マイクロバッチが小さいほど統計推定のノイズが大きくなり、正規化の挙動が変わります。さらに実行時の移動平均(running mean/var)も K 倍の頻度で更新されるため、推論時に使う統計の性質もずれます。

正規化層を選べば等価性が保てる

LayerNorm・RMSNorm・GroupNorm は各サンプル内(特徴次元)で正規化し、バッチ次元の統計に依存しません(正規化層の比較)。したがって勾配蓄積を使ってもマイクロバッチサイズに影響されず、大バッチと等価な結果が得られます。Transformer 系が LayerNorm を採用していることもあり、勾配蓄積が大規模 LLM 学習で問題なく機能するのはこの相性の良さによります。BatchNorm を使う CNN で大実効バッチが必要なら、SyncBatchNorm(後述)や GroupNorm への置換を検討します。

分散学習の勾配同期との関係:通信を「蓄積中は止める」

データ並列の分散学習(分散学習の並列化戦略)では、各 GPU が自分の勾配を計算し、backward() の直後に All-Reduce で全 GPU の勾配を平均同期します。ここに勾配蓄積を素朴に組み合わせると、致命的な無駄が生じます。

naive: 蓄積K回 × backward → 毎回 All-Reduce が走る(K回の通信)
       しかし step は1回。途中K-1回の同期は捨てられる

蓄積中の中間勾配はまだ更新に使わないのに、毎 backward() で重い通信が走ってしまう。正しくは、蓄積している K-1 回は同期を抑制し、最後の1回でだけ All-Reduce する。PyTorch DDP の no_sync() コンテキストはまさにこのための仕組みで、蓄積中は勾配を各 GPU でローカルに積むだけにし、最終マイクロバッチで一度だけ同期します。

for k in range(K):
    micro = next_microbatch()
    if k < K - 1:
        with model.no_sync():            # 同期を抑制してローカル蓄積のみ
            (compute_loss(model, micro) / K).backward()
    else:
        (compute_loss(model, micro) / K).backward()  # 最後だけAll-Reduce
optimizer.step()
optimizer.zero_grad()

これで通信回数が K 回から1回に減り、蓄積による計算時間増にさらに通信オーバーヘッドが乗るのを防げます。なお最終的な実効バッチは マイクロバッチ × K × GPU数 であり、勾配蓄積とデータ並列は乗算的に実効バッチを拡大します。

試験・面接で問われる定番
  • 勾配蓄積の目的:メモリを増やさず実効バッチを拡大。ピークメモリは1マイクロバッチ分のまま、勾配を K 回足し込んで1回更新する。
  • 1/K 平均化が必要な理由:合計勾配は平均勾配の K 倍。割らないと実質学習率が Kになり不安定化する。
  • BatchNorm と等価でない理由:統計がマイクロバッチ単位で計算され、実効バッチを見ないから。LayerNorm/RMSNorm はバッチ統計非依存なので等価。
  • 分散学習との組み合わせ:蓄積中は no_sync で All-Reduce を抑制し、最終マイクロバッチでのみ同期して通信回数を K 回から1回に削減する。

まとめ:時間でメモリを買い、等価性の境界を見極める

勾配蓄積は、勾配が和(平均)に分解できるという線形性を使い、マイクロバッチの勾配を K 回足し込んでから1回更新することで、ピークメモリを据え置いたまま実効バッチを自在に拡大する技法でした。骨格は「各損失を K で割って逆伝播し、K 回累積してから step」。更新に使う勾配は大バッチと数学的に一致しますが、BatchNorm のようにバッチ全体の統計に依存する演算は等価にならない——ここが等価性の境界です。さらに分散環境では no_sync で蓄積中の通信を止めるのが定石でした。実効バッチを大きくしたら学習率もそれに合わせて調整する必要があり、その指針は バッチサイズと学習率 と、メモリ削減の別アプローチである 混合精度学習 と合わせて読むと、大規模学習のメモリ・速度・安定性のトレードオフが立体的に見えてきます。

AI/機械学習 Article

勾配蓄積と実効バッチサイズの拡大を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

勾配蓄積

比較で見る軸

難易度: advanced / カテゴリ: AI/機械学習 / タグ数: 5

導入後に効く点

数学的に正しく大バッチと一致させるには損失の平均化が鍵。合計損失をそのまま足すと勾配が蓄積回数倍になるため、各マイクロバッチの損失を蓄積回数で割ってから逆伝播する。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
AI/機械学習
タグ数
5

判断チェックリスト

  • 自社の用途が「勾配蓄積 / バッチサイズ」に近いか確認する。
  • 強みである「勾配蓄積はマイクロバッチの勾配を複数ステップ足し込み、まとめて1回だけ更新する技法。メモリ消費は1マイクロバッチ分のまま、実効バッチ = マイクロバッチ × 蓄積回数 を任意に拡大できる。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

勾配蓄積バッチサイズメモリ最適化分散学習ディープラーニング勾配蓄積バッチサイズメモリ最適化