Hugging Face Accelerate: PyTorchで大規模モデルを実行する

Hugging Face Accelerate は、コンシューマーハードウェアの RAM や GPU メモリを超える非常に大きな言語モデル(LLM)をロードして実行できるようにします。特定の PyTorch 機能と改良されたロードパイプラインを利用することで、Accelerate はモデルの重みを複数の GPU、CPU RAM、ディスクストレージといった利用可能なハードウェアリソースに分散させます。

モデルロード時のメモリ制約の克服

従来の PyTorch のモデルロードは線形プロセスに従います:モデルを作成し、重みをメモリ上の state_dict にロードし、その重みをモデルに読み込み、モデルをデバイスへ移動します。非常に大きなモデルの場合、このプロセスは RAM の観点で実質的に不可能です。例えば、float32 精度で 67 億パラメータを持つモデルは、初期モデル作成だけで約 26.8 GB の RAM が必要で、state_dict をロードするためにさらに 26.8 GB が必要となり、GPU に到達する前に合計で 53 GB 超が必要になります。

この問題を解決するために、Accelerate はよりメモリ効率の高いパイプラインを実装しています:

  1. 重みなしの空のモデルを作成する(weights なし)。
  2. 各層が配置される場所を決めるデバイスマップを決定する。
  3. 重みを小さなパーツ(シャード)でロードする。
  4. それらの重みを空のモデルにロードする。
  5. 推論用に重みを指定されたデバイスへ移動する。
  6. 残りのすべての重みについてこのプロセスを繰り返す。

Meta デバイスによる効率的なモデル初期化

Accelerate は PyTorch の "meta" デバイス(PyTorch 1.9 で導入)を利用し、実際のデータメモリを割り当てずにモデルをインスタンス化します。meta デバイス上のテンソルは形状とデータ型情報のみを保持し、CPU や GPU の RAM を消費せずに任意に大きなモデルを作成できます。

Transformers ライブラリ内のすべてのモデルを書き換えて device キーワードをサポートさせるのは非現実的なため、Hugging Face は init_empty_weights() コンテキストマネージャを開発しました。これにより、任意のモデルを meta デバイス上の「シェル」としてインスタンス化でき、実際の重みをロードせずにメモリ要件を計算するために必要な構造情報を提供します。

自動デバイスマッピングとリソース割り当て

Accelerate は infer_auto_device_map 関数を使用して、利用可能なハードウェア全体にモデルの重みを自動的に分配します。システムはリソースを次の順序で優先します:GPU → CPU RAM → ディスクオフロード。

デバイスマップの構成

使用ケースに応じて、ユーザーはさまざまなマッピング戦略を選択できます:

  • "auto" または "balanced":利用可能なすべての GPU に重みを均等に分配します。
  • "balanced_low_0":GPU 全体に均等に分配しつつ、最初の GPU(GPU 0)の負荷を最小化します。これは、最初の GPU がモデル出力(例:テキスト生成)に必要な場合に有用です。
  • "sequential":GPU を順番に埋めていき、後続の GPU が未使用になる可能性があります。

単一の層が複数デバイスに跨って分割されること(残差接続が壊れる)を防ぐため、Accelerate は no_split_module_classes(例:["OPTDecoderLayer"])で分割禁止クラスを指定できるようにしています。

RAM負荷軽減のためのシャーディングチェックポイント

単一の巨大な state_dict ファイルをロードすることはほとんどのハードウェアで現実的ではありません。例えば、BLOOM モデル(176 B パラメータ)は bfloat16 で重みをロードするだけで 352 GB の RAM が必要です。この問題を緩和するために、Hugging Face は sharded checkpoints を使用します。

シャーディングチェックポイントでは:

  • pytorch_model.bin.index.json ファイルが各パラメータ名を特定のシャードファイルにマッピングします。
  • 重みは複数の標準 PyTorch state dict ファイル(例:BLOOM では 72 ファイル)に分割されます。

このアプローチにより、システムはモデル全体ではなく最大単一シャード(例:BLOOM の 7.19 GB)を保持できるだけの RAM で済みます。GPU と CPU の RAM が不足している場合、Accelerate は指定された offload_folder に重みをディスクへオフロードできます。offload_state_dict=True オプションを使用すると、他のシャードを処理中に CPU に常駐しているモデル部分を一時的にオフロードし、RAM 使用量をさらに削減できます。

動的フックによる実行

複数デバイスに分割されたモデルを実行するために、Accelerate は PyTorch フックにヒントを得たメカニズムを使用します。dispatch_model 関数は、各モジュールとサブモジュールにフックを付与し、各フォワードパスの前後で実行されます。これらのフックは次の操作を行います:

  • すべてのモジュール入力が重みと同じデバイス上にあることを保証する。
  • フォワードパス直前に CPU から GPU 0 へ重みを移動し、直後に CPU に戻す。
  • フォワードパス直前にディスクから RAM、そして GPU 0 へ重みをロードし、直後にメモリを解放する。

この手法は複雑なパイプライン並列化を用いずに GPU を順次使用しますが、はるかに小さなハードウェア構成でも巨大モデルの実行を可能にします。

Sources