Accelerate 文件

FSDP1 與 FSDP2 對比

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

FSDP1 與 FSDP2 對比

本指南解釋了 `FSDP1` 和 `FSDP2` 之間的主要區別,並幫助您以最小的改動將現有程式碼遷移到 `FSDP2`。

FSDP2 比 FSDP1 好在哪裡?

首先,我們需要了解 `FSDP1` 和 `FSDP2` 的內部工作原理,以便理解它們之間的差異。這也有助於我們理解 `FSDP1` 的侷限性以及 `FSDP2` 如何解決這些問題。

我們將討論一個場景:我們有一個包含 3 個 `Linear` 層的 `Layer`,並使用 `FSDP` 將其包裝以在 2 個 GPU 上進行分片。

Layer

FSDP1

首先,我們必須瞭解最初的 `FSDP1` 及其帶來的侷限性。它將每個 `FSDP` 模組表示為單個 `FlatParameter`,這是一個包含所有模組引數的一維張量,然後在不同的程序(ranks)之間進行分片。也就是說,如果用 `FSDP1` 包裝 `Layer`,會得到如下結果:

FSDP1

您可能會注意到一個問題。整個 `Layer` 被展平成一個 `FlatParameter`,然後在各個程序之間分片。但如果它是一個單獨的 `FlatParameter` 物件,我們如何儲存元資料呢?這是其侷限性之一。如果不使用一些不優雅的技巧,就無法正確儲存每個引數的元資料,例如 `dtype`、`requires_grad` 等。

FSDP2

這就是為什麼引入了 `FSDP2`。它不使用 `FlatParameter`,而是使用 `DTensor`,即“分散式張量”(Distributed Tensor)。每個 `DTensor` 基本上代表一個已在不同程序間分片的普通 `torch.Tensor`。它包含關於原始 `torch.Tensor` 的元資料以及它是如何分片的,放置型別是什麼等等。這就是為什麼它被稱為“按引數分片”(per-parameter sharding)。下圖顯示了差異:

FSDP2

原始 `Layer` 的每個引數都在第 0 維上進行分片,並在 2 個 GPU 之間分割。現在,每個 `Linear` 層都是一個單獨的 `DTensor`,按引數儲存元資料變得可能且直接。

在上圖中,為了適應螢幕顯示,張量是在第 1 維上分片的,實際上,如上所述,它們是在第 0 維上分片的。

FSDP2 提供了什麼?

`FSDP2` 是 PyTorch 完全分片資料並行訓練 API 的新改進版本。它的主要優勢是使用 `DTensor` 來表示分片引數。與 `FSDP1` 相比,它提供了:

  • 更簡單的內部實現,其中每個 `Parameter` 都是一個單獨的 `DTensor`
  • 由於上述原因,可以輕鬆實現部分引數凍結,這使得像 `LORA` 這樣的方法可以直接使用
  • 透過 `DTensor`,`FSDP2` 支援在同一模型中混合使用 `fp8` 和其他引數型別
  • 使用 `SHARDED_STATE_DICT` 和 `torch.distributed.checkpoint`,可以實現更快、更簡單的檢查點,無需跨程序進行額外通訊,這樣,每個程序只儲存自己的分片和相應的元資料
  • 對於載入,它使用分片模型的 `state_dict` 直接載入分片引數
  • 支援非同步檢查點,其中引數首先被複制到 CPU 記憶體,之後主執行緒繼續訓練,而另一個執行緒將引數儲存到磁碟
  • 記憶體效率和確定性的記憶體使用,`FSDP2` 不再使用 `recordStream`,而是使用流到流的同步(更多技術細節請參閱此論壇帖子此 issue
  • 未來,計劃透過 `torch.compile` 最佳化通訊模式,進一步提高效能和記憶體效率

API 差異

我們已經討論了內部差異,現在讓我們討論一下您作為使用者需要了解的差異。

以下是透過 `accelerate` CLI 使用 `FSDP2` 時配置選項的主要變化:

舊版 (`FSDP1`) 新版 (`FSDP2`) 變化說明
--fsdp_sharding_strategy --fsdp_reshard_after_forward 取代 `--fsdp_sharding_strategy`,改為 `true`(之前是 `FULL_SHARD`)或 `false`(之前是 `SHARD_GRAD_OP`)
--fsdp_backward_prefetch **已移除** `FSDP2` 預設使用之前的 `BACKWARD_PRE` 選項,因為只有這樣才能實現通訊和計算的重疊
--fsdp_forward_prefetch **尚未實現** 如何實現此功能正在積極討論中,目前在 `FSDP2` 中尚不支援
--fsdp_sync_module_states **已移除** 使用 `FSDP2` 後,此引數變得多餘
--fsdp_cpu_ram_efficient_loading --fsdp_cpu_ram_efficient_loading 如果為 `true`,`FSDP2` 同樣只在 rank 0 上載入模型,然後引數同步到其他 rank,這與 `FSDP1` 的行為相同,但是不再需要設定 `--fsdp_sync_module_states`
--fsdp_state_dict_type --fsdp_state_dict_type `LOCAL_STATE_DICT` 已過時,在 `FSDP2` 中 `SHARDED_STATE_DICT` 是預設選項,這導致沒有額外的通訊,並且每個 rank 儲存自己的分片;另一個可能的選項是 `FULL_STATE_DICT`,它會導致額外的通訊和記憶體使用激增,但會從 rank 0 儲存完整的模型。
--fsdp_use_orig_params **已移除** `FSDP2` 在後臺使用 `DTensor` 類,這意味著它預設*總是*使用原始引數
**新增** --fsdp_version 預設選項為 `1`,以免破壞現有程式碼,設定為 `2` 以使用 `FSDP2`

對於所有其他未更改的選項,請參閱 `FSDP` 文件

如何切換到 FSDP2

如果使用 Python 程式碼:

在建立外掛時,只需設定 `fsdp_version=2`,並根據上表替換選項即可。

from accelerate import FullyShardedDataParallelPlugin, Accelerator

fsdp_plugin = FullyShardedDataParallelPlugin(
    fsdp_version=2
    # other options...
)
accelerator = Accelerator(fsdp_plugin=fsdp_plugin)

如果使用 YAML 配置:

使用我們的轉換工具

accelerate to-fsdp2 --config_file config.yaml --output_file new_config.yaml

這將自動將所有 FSDP1 設定轉換為其 FSDP2 的等效設定。使用 `--overwrite` 來更新現有檔案,而不是建立一個新檔案。

< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.