パイプライン並列とマイクロバッチスケジューリング
層をステージに割ると待ち時間(バブル)が生まれる理由と、GPipe/1F1Bのマイクロバッチ刻みでそれを薄める原理、メモリと通信のトレードオフ、3D並列での組み込み方が一本でつながる。
- 1.パイプライン並列は層をステージに分けて別GPUへ置く。素朴に流すと前後のステージが互いを待ちアイドルになる。この遊休がバブルで、ステージ数pに対し概ね (p-1)/(p-1+m) の割合を占める。
- 2.ミニバッチをm個のマイクロバッチに刻んで連続投入すると、mを増やすほどバブル率が下がる。GPipeは全前進→全後退、1F1B(PipeDream)は前進と後退を交互に走らせ、保持する活性化メモリをステージ数程度に抑える。
- 3.通信はステージ境界の活性化授受のみで軽く、ノード間に展開しやすい。実務ではノード内テンソル並列・ノード間パイプライン・外側データ並列の3D並列に、活性化再計算を重ねてメモリを稼ぐ。
なぜ層を割ると「待ち時間」が生まれるのか
分散学習の全体像 で触れたように、パイプライン並列はモデルの層をステージに分け、別々のGPUへ縦に並べる分割です。GPU0が第1〜8層、GPU1が第9〜16層…と担当を割り、データはステージ間をバケツリレーで流れます。テンソル並列のように1つの行列を割らないので、通信はステージ境界の活性化(中間出力)の授受だけで済み、ノード間にまたがらせやすいのが利点です。
問題は稼働率です。順伝播は先頭ステージから末尾へ一方向に進み、逆伝播は末尾から先頭へ戻ります。1つのミニバッチをそのまま流すと、ある瞬間に働けるのは1ステージだけで、残りは入力か勾配を待ってアイドルになります。この遊休がパイプラインバブルです。
ステージ↓ / 時間→ 素朴な実行(ミニバッチ1個をそのまま流す)
S0: F . . . . . . B
S1: . F . . . . B .
S2: . . F . . B . .
S3: . . . F B . . .
(F=順伝播, B=逆伝播, .=アイドル)
ステージ数を p とすると、1個のバッチが端から端まで進むのに p ステップ、戻るのに p ステップかかり、各ステージは大半の時間を空けています。台数を増やすほど1ステージが受け持つ層は減って速くなりそうですが、素朴に流すかぎりアイドル時間も比例して増えるため、スループットは伸びません。
マイクロバッチ:充填でバブルを薄める
打開策がマイクロバッチ化です。ミニバッチを m 個の小片(マイクロバッチ)に刻み、間を置かず次々に投入します。先頭マイクロバッチがS1へ渡った直後、S0は2個目を処理でき、パイプラインが充填(fill)されると全ステージが同時に稼働します。最後のマイクロバッチが流れ切る**排出(drain)**まで、定常状態では遊休が消えます。
GPipe: マイクロバッチ4個(番号)を全部前進→全部後退
S0: F1 F2 F3 F4 .. .. .. .. B4 B3 B2 B1
S1: .. F1 F2 F3 F4 .. .. B4 B3 B2 B1 ..
S2: .. .. F1 F2 F3 F4 .. B4 B3 B2 B1 .. ..
S3: .. .. .. F1 F2 F3 F4 B4 B3 B2 B1 .. .. ..
(最終ステージS3はF4の直後に同じマイクロバッチをB4で後退できる)
残るのは充填と排出にかかる立ち上がり・立ち下がりだけです。前進・後退あわせた所要を考えると、バブルが占める割合はおおよそ次式になります。
バブル率 ≈ (p - 1) / (m + p - 1)
ここで p はステージ数、m はマイクロバッチ数です。m を p に対して十分大きく(経験則で m が p の4倍以上)とればバブル率は小さくなります。たとえば p = 4、m = 16 ならバブル率は 3/19 ≒ 16%、m = 32 なら 3/35 ≒ 9% まで下がります。マイクロバッチを増やすほど効率は上がる——これがパイプライン並列の中心原理です。
m 個のマイクロバッチの勾配は加算(または平均)してから1回更新するため、数学的にはミニバッチ1個ぶんの更新と等価です。バッチ正規化のような統計をバッチ全体で取る層さえ避ければ、刻んでも学習結果は変わりません。バブルを薄める純粋なスケジューリング上の工夫です。
GPipe と 1F1B:活性化メモリで効くスケジュール差
マイクロバッチをどの順で前進・後退させるかがスケジュールで、代表が GPipe と 1F1B(PipeDream系)です。両者のバブル率はほぼ同じですが、保持する活性化メモリが決定的に違います。
GPipe は素直に「全マイクロバッチを前進させてから、全マイクロバッチを後退させる」方式です。逆伝播は順伝播の活性化を要するため、m 個ぶんの活性化を後退開始まで全部抱え続けることになります。マイクロバッチを増やすほどバブルは減りますが、活性化メモリが m に比例して膨らむジレンマを抱えます。
1F1B(one-forward-one-backward)は、充填後に1回前進したら1回後退するよう交互に走らせます。先に流したマイクロバッチを早めに後退させて活性化を解放するため、各ステージが同時に抱える活性化はマイクロバッチ数 m ではなくステージ数 p 程度で頭打ちになります。バブル率を保ったまま活性化メモリを m から p オーダーへ落とせるのが核心で、PipeDream-Flush(同期版1F1B)として大規模学習の標準になっています。
| 観点 | GPipe | 1F1B(PipeDream-Flush) |
|---|---|---|
| 前進・後退の順 | 全前進 → 全後退 | 充填後は前進と後退を1回ずつ交互 |
| バブル率 | (p-1)/(m+p-1) | 同等(ほぼ同じ) |
| 同時保持の活性化 | マイクロバッチ数 m に比例 | ステージ数 p 程度で頭打ち |
| 更新の同期性 | ステップ末で同期(厳密) | Flush版は同期。非同期版は重みのズレあり |
| 長所/短所 | 実装が単純/メモリ大 | メモリ効率が高い/スケジュールが複雑 |
オリジナルのPipeDreamはバブルを完全に消すため、後退を待たずに次のステップへ進む非同期実行を採る亜種があります。すると同じマイクロバッチでも前進時と後退時で重みのバージョンがずれ、勾配が古い重みに対するものになります(weight staleness)。これを各ステージで重みのコピーを保持して整合させる必要があり、メモリと実装が重くなります。学習の素直さを優先する現場では、定期的に全マイクロバッチを流し切って同期する **Flush 版(=1F1B同期)**が選ばれます。
メモリと通信のトレードオフ
パイプライン並列の設計は、バブル(時間の無駄)・活性化メモリ・通信の三者のやりくりです。
- バブル対策はマイクロバッチを増やすこと。ただしGPipeでは活性化メモリと正面衝突するため、1F1Bで活性化を p オーダーに抑えるのが前提になります。
- それでも活性化が支配的なら、活性化再計算(勾配チェックポイント)を重ねます。順伝播の中間活性化を保存せず捨て、逆伝播で必要になった区間だけ順伝播をやり直して作り直します。活性化メモリを大きく削れる代わりに、順伝播ぶんの計算が増える(おおよそ1.3〜1.5倍)交換です。パイプラインのステージ境界は再計算の区切りと相性がよく、両者は併用されます。
- 通信はステージ境界の活性化のみで、テンソル並列のような層ごとの高頻度AllReduceは不要です。授受するのはマイクロバッチ1個ぶんの活性化テンソルで、しかも隣接ステージ間のP2P送受信に閉じます。だからネットワークが相対的に細いノード間に展開しても破綻しにくいのです。
バブルは最も遅いステージに律速されます。層を機械的に等数で割ると、埋め込み層や語彙射影を抱えるステージだけ重くなり、他が待ちます。各ステージの順伝播+逆伝播の実時間が揃うように層を配分するのが、バブルを最小化する実務上の勘所です。
3D並列のなかでの位置づけ
実際の超大規模学習では、パイプライン並列は単独では使われず、3つの並列軸を掛け合わせる3D並列の一角を担います。割り当ての原則は「通信が高頻度なものほど内側(近い)GPUへ」です。
| 並列軸 | 割る対象 | 通信の中身と頻度 | 配置の定石 |
|---|---|---|---|
| テンソル並列 | 層内の行列 | 部分結果のAllReduce(層ごと・超高頻度) | ノード内(NVLink必須級) |
| パイプライン並列 | 層の並び(ステージ) | 境界の活性化P2P(マイクロバッチ毎) | ノード間に展開しやすい |
| データ並列(+ZeRO) | 入力バッチ | 勾配のAllReduce(イテレーション毎) | 最外周。レプリカ間で同期 |
典型構成は、ノード内の8枚をテンソル並列で密結合し、その単位を複数ノードにまたぐパイプラインステージとして並べ、さらにこのパイプライン全体をデータ並列で複製します。たとえば「テンソル並列8 × パイプライン並列4 × データ並列N」のように、各軸の積が総GPU数になります。パイプライン並列が担うのは、テンソル並列だけではモデルが載りきらず、しかもノード間で高頻度通信を避けたい中間レンジの分割です。
- パイプライン並列=層をステージに割る縦分割。通信は境界の活性化P2Pのみで軽い。
- 素朴な実行はバブルで遊休。マイクロバッチ m を増やすとバブル率 ≈ (p-1)/(m+p-1) が下がる。
- GPipeは活性化を m 個保持、1F1B(PipeDream-Flush)は p 程度に抑える。バブル率は同等。
- 活性化が支配的なら再計算で削る(計算1.3〜1.5倍の交換)。3D並列ではテンソル=内、パイプライン=中、データ=外。
まとめ:充填率という1つの問いに還元する
パイプライン並列は、突き詰めれば「各ステージをどれだけ遊ばせずに充填できるか」という1問に帰着します。バブルはステージ間の依存が生む構造的な遊休で、マイクロバッチはそれを時間方向に重ねて隠す手段、スケジュール(GPipe/1F1B)はその重ね方を活性化メモリと引き換えに最適化する選択です。
| 論点 | 実態 | そこから言えること |
|---|---|---|
| なぜ遊ぶのか | ステージ間の前進・後退依存で1ステージしか働けない | バブルは構造的。台数増だけでは解消しない |
| マイクロバッチの効能 | m個を連続投入し充填でステージを同時稼働 | mを増やすほどバブル率が下がる |
| GPipeと1F1Bの差 | 活性化をm個抱えるかp程度に抑えるか | メモリ制約下では1F1Bが標準 |
| 3D並列での役割 | テンソル(内)とデータ(外)の中間を埋める分割 | 通信頻度が配置の内外を決める |
この見立てを持つと、「ステージを増やしたのに速くならない」「マイクロバッチを増やしたらメモリが溢れた」「ノードをまたいでも意外と速い」といった現象が、充填率・活性化メモリ・通信パターンの言葉で一貫して説明できます。メモリ全体の内訳は 分散学習:データ並列・モデル並列・ZeRO と、活性化や状態の精度を落として帯域を稼ぐ理屈は 混合精度学習 と、なぜここまでして巨大化するのかは スケーリング則 と合わせて読むと、巨大モデルを学習可能にする工夫の全体像が線でつながります。
AI/機械学習 Article
パイプライン並列とマイクロバッチスケジューリングを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
パイプライン並列
比較で見る軸
難易度: advanced / カテゴリ: AI/機械学習 / タグ数: 6
導入後に効く点
ミニバッチをm個のマイクロバッチに刻んで連続投入すると、mを増やすほどバブル率が下がる。GPipeは全前進→全後退、1F1B(PipeDream)は前進と後退を交互に走らせ、保持する活性化メモリをステージ数程度に抑える。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- AI/機械学習
- タグ数
- 6
判断チェックリスト
- 自社の用途が「パイプライン並列 / マイクロバッチ」に近いか確認する。
- 強みである「パイプライン並列は層をステージに分けて別GPUへ置く。素朴に流すと前後のステージが互いを待ちアイドルになる。この遊休がバブルで、ステージ数pに対し概ね (p-1)/(p-1+m) の割合を占める。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。