「FP8を用いたPyTorchトレーニング作業の高速化」

『PyTorchトレーニング作業の高速化にFP8を活用』

モダンなGPUを最大限に活用する方法

Deva Darshanによる写真、出典:Unsplash

過去数年間、AIの分野で画期的な進展が見られました。特にChatGPTなどのLLMベースのアプリケーションの人気と普及において最もよく示されています。これらのブレークスルーは、AIモデルの訓練に使用される機械の同様に興味深い進展によって支えられています。革新的なアーキテクチャ、洗練されたテンソル処理コア、専用のHWアクセラレータは、徐々に大きくなるAIモデルを高速かつ急速に収束させることを可能にしました。この記事では、AI専用のHWの特定の進展に焦点を当てます — それが専用の8ビット浮動小数点(FP8)テンソル処理コアの組み込みです。最新のAI HWアーキテクチャ(例:Nvidia HopperNvidia Ada LovelaceHabana Gaudi2)に登場するFP8テンソルコアにより、秒あたりの浮動小数点演算回数(FLOPS)が大幅に増加し、AIのトレーニングおよび推論のワークロードのメモリ最適化とエネルギー節約の機会が提供されます。

HWレベルのFP8機能を活用するためには、AIのトレーニングおよび推論アプリケーションを構築するために使用するSWスタックおよび開発フレームワークを適切にサポートする必要があります。この記事では、PyTorchのトレーニングスクリプトを変更して、Nvidia H100 GPUのFP8データ型の組み込みサポートを活用する方法について説明します。まず、FP8データ型の使用の動機を提供します。次に、Transformer Engineライブラリが公開しているFP8固有のPyTorch APIサポートを見直し、それらを簡単なトレーニングスクリプトに統合する方法を示します。AIトレーニング用のFP8の使用に関する理論には触れませんが、その使用に関連する潜在的な課題についても指摘します。最後に、FP8データ型の重要な最適化の機会を示します。

免責事項

当方がSWコンポーネント、方法論、またはサービスを言及したことは、その使用を推奨するものではありません。ML開発の最適な設計は、具体的なAIワークロードの詳細に大きく依存する場合があります。また、当該記事を読む時点で、言及する一部のSWパッケージとコンポーネントのAPIおよび動作は変更される可能性があることに留意してください。最新のHWおよびSWに基づいて潜在的な設計の決定を評価することを強くお勧めします。

動機

AIモデルがますます洗練されるにつれて、それを訓練するために必要な機械もますます洗練されます。この記事執筆時点では、”前例のないパフォーマンスとスケーラビリティをサポート”するとされるNvidia H100 GPUは、Nvidiaの最新かつ最強のAIアクセラレータです。AIのハイプにより、これらのGPUの需要は非常に高くなりました(例:こちら参照)。そのため、これらのGPUのコストも非常に高くなりました — おそらく多くの読者にとってさえ手の届かない価格です。幸いにも、AWS、GCP、Microsoft Azureなどのクラウドサービスプロバイダーは、H100を搭載したマシンへの「利用時課金」(時間ごと/秒ごとの)アクセスを提供しており、より広範なAI開発コミュニティに利用の機会を開放しています。

AWSでは、H100 GPUは最近発表されたAWS EC2 p5インスタンスファミリーの一部として提供されています。これらのインスタンスは、”前世代のGPUベースのEC2インスタンスと比較して、解決までの時間を最大4倍加速し、MLモデルの学習コストを最大40%削減する”と主張されています。

最適なMLトレーニングインスタンスの選択には、いくつかの考慮事項が必要であることを最近の投稿で議論しました。プロジェクトによっては、最適なインスタンスタイプは非常に依存します。特に、MLトレーニングインスタンスの場合、”より大きい方が常に良いわけではない”ということが真実です。これは特にp5インスタンスファミリーに当てはまります。確かに、p5は他のインスタンスタイプよりも優れたパフォーマンスを示すでしょう。なぜなら、H100は確固たるパフォーマンスを持つからです。しかし、p5のコスト(この記事執筆時点での8-GPU p5.48xlargeインスタンスの1時間あたりの料金が98.32ドル)を考慮すると、他のインスタンスタイプの方がより適している場合もあるかもしれません。

次のセクションでは、比較的大規模なコンピュータビジョンモデルをp5.48xlargeでトレーニングし、そのパフォーマンスを8つのNvidia A100 GPUsを搭載したp4d.24xlargeと比較します。

トイモデル

以下のコードブロックでは、ビジョンTransformer(ViT)をバックエンドとする分類モデル(人気のあるtimm Pythonパッケージのバージョン0.9.10を使用)とランダムに生成されたデータセットを定義しています。ViTバックボーンは多様な形状とサイズがあります。ここでは、ViT-Huge構成と呼ばれるものを選択しました。これは6億3200万のパラメータを持つ構成であり、H100の大規模モデルの能力をより良く利用するために選ばれています。

import torch, timeimport torch.optimimport torch.utils.dataimport torch.distributed as distfrom torch.nn.parallel.distributed import DistributedDataParallel as DDPimport torch.multiprocessing as mp#  GPUメモリに応じてバッチサイズを変更するbatch_size = 64from timm.models.vision_transformer import VisionTransformerfrom torch.utils.data import Dataset# ランダムデータを使用class FakeDataset(Dataset):    def __len__(self):        return 1000000    def __getitem__(self, index):        rand_image = torch.randn([3, 224, 224], dtype=torch.float32)        label = torch.tensor(data=[index % 1000], dtype=torch.int64)        return rand_image, labeldef mp_fn(local_rank, *args):    # プロセスの設定    dist.init_process_group("nccl",                            rank=local_rank,                            world_size=torch.cuda.device_count())    torch.cuda.set_device(local_rank)    device = torch.cuda.current_device()        # データセットとデータローダーを作成    train_set = FakeDataset()    train_loader = torch.utils.data.DataLoader(        train_set, batch_size=batch_size,        num_workers=12, pin_memory=True)    # ViT-Hugeモデルを定義    model = VisionTransformer(            embed_dim=1280,            depth=32,            num_heads=16,        ).cuda(device)    model = DDP(model, device_ids=[local_rank])    # 損失関数とオプティマイザを定義    criterion = torch.nn.CrossEntropyLoss()    optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)    model.train()    t0 = time.perf_counter()    summ = 0    count = 0    for step, data in enumerate(train_loader):        # データをGPUにコピーします        inputs = data[0].to(device=device, non_blocking=True)        label = data[1].squeeze(-1).to(device=device, non_blocking=True)          # bfloat16のサポートを活用するために混合精度を使用        with torch.autocast(device_type='cuda', dtype=torch.bfloat16):            outputs = model(inputs)            loss = criterion(outputs, label)        optimizer.zero_grad(set_to_none=True)        loss.backward()        optimizer.step()                # ステップ時間を計測        batch_time = time.perf_counter() - t0        if step > 10:  # 最初のステップはスキップ            summ += batch_time            count += 1        t0 = time.perf_counter()        if step > 50:            break    print(f'average step time: {summ/count}')if __name__ == '__main__':    mp.spawn(mp_fn,             args=(),             nprocs=torch.cuda.device_count(),             join=True)

このモデルは、p5.48xlargeおよびp4d.24xlargeインスタンスタイプで訓練しました。専用のPyTorch 2.1 AWSディープラーニングコンテナ (763104351884.dkr.ecr.us-east-1.amazonaws.com/pytorch-training:2.1.0-gpu-py310-cu121-ubuntu20.04-ec2)を使用しました。

予想通り、p5のステップごとのパフォーマンスはp4dのパフォーマンスを圧倒しています- 1ステップ当たり0.199秒対0.41秒- 2倍以上の速さです!これは大きなMLモデルを訓練する時間を半分にすることを意味します。しかし、コストの違いを考慮すると(p4dの場合は1時間あたり$32.77対p5の場合は$98.32-この執筆時点での情報)、完全に違った話が展開されます。p5の価格パフォーマンスはp4dよりも約30%悪いです!これは<p5の発表で示された40%の改善から非常に遠いです。</p5の発表

この時点で、2つの可能な結論を導くことができます。1つ目の可能性は、諸々の宣伝にもかかわらず、p5が単にあなたにとって適したマシンではないということです。2つ目は、p5は依然として有効な可能性があるが、その潜在能力を十分に活用するためにモデルの適応が必要であるということです。次のセクションでは、2番目のアプローチを取り、FP8データ型(p5インスタンスタイプ固有のデータ型)の使用方法が比較的価格パフォーマンスの結果を完全に変えることを示します。

Transformer EngineとFP8の統合

最初に強調すべきことは、PyTorch(バージョン2.1)にはネイティブの8ビット浮動小数点データ型が含まれていないということです。FP8を使用するためにスクリプトをプログラムするために、NVIDIA GPU上のTransformerモデルを加速するための専用ライブラリであるTransformer Engine(TE)を使用します。TE(バージョン0.12)はAWS PyTorch 2.1 DLコンテナに事前インストールされています。

トレーニングにFP8を使用する理論はこの記事の範囲を超えていますが(詳しくはこちらを参照)、FP8の使用方法は16ビットの代替(float16およびbfloat16)よりもはるかに複雑です。幸い、TEの実装はユーザーからすべての複雑な詳細を隠します。TEのAPIの使用方法についての指示については、公式のドキュメントおよびこの簡単なを参照してください。裏側で何が起こっているかをもっと知りたい場合は、次の2つのビデオチュートリアルも参照してください。

Transformer Engineを使用したFP8トレーニング | NVIDIAオンデマンド

このセッションでは、FP8と混合精度の紹介、Transformer Engineの機能の概要、そして…

www.nvidia.com

深層学習のためのFP8 | NVIDIAオンデマンド

FP8は深層学習(DL)トレーニングを進めるための16ビットフォーマット以外の自然な進化です…

www.nvidia.com

モデルをTEを使うように変更するために、TEの特化したTransformer Layerをtimmのblock layer signatureに準拠するカスタムのtransformer block classでラップします。

import transformer_engine.pytorch as te
from transformer_engine.common import recipe

class TE_Block(te.transformer.TransformerLayer):
    def __init__(
        self,
        dim,
        num_heads,
        mlp_ratio=4.,
        qkv_bias=False,
        qk_norm=False,
        proj_drop=0.,
        attn_drop=0.,
        init_values=None,
        drop_path=0.,
        act_layer=None,
        norm_layer=None,
        mlp_layer=None
    ):
        super().__init__(
            hidden_size=dim,
            ffn_hidden_size=int(dim * mlp_ratio),
            num_attention_heads=num_heads,
            hidden_dropout=proj_drop,
            attention_dropout=attn_drop
        )

# 次に、VisionTransformerの初期化を修正して、カスタムのブロックレイヤーを使用するようにします:
model = VisionTransformer(
    embed_dim=1280,
    depth=32,
    num_heads=16,
    block_fn=TE_Block
).cuda(device)

# これまで、私たちは特にH100に特化した変更は行っていません - 同じコードはA100で駆動されるp4dインスタンスタイプでも実行できます。最後の変更は、モデルの順方向パスをte.fp8_autocastコンテキストマネージャでラップすることです。この変更には、FP8をサポートするGPUが必要です:
with torch.autocast(device_type='cuda', dtype=torch.bfloat16):
    with te.fp8_autocast(enabled=True):
        outputs = model(inputs)
    loss = criterion(outputs, label)

# FP8の使用に関する注意事項
8ビット浮動小数点表現(16ビットまたは32ビット表現と比較して)は、より低い精度と低い動的範囲を意味します。これらは、モデルの収束可能性および/または速度に有意な影響を与える可能性があります。TE FP8の実装はこの課題に対応するように設計されていますが、あなたのモデルでうまく動作する保証はありません。基礎となるFP8メカニクス(TEのrecipe APIを使用するなど)を調整する必要があり、いくつかのハイパーパラメータを調整し、またはFP8の適用をモデルの一部に制限する必要があるかもしれません。あなたのすべての試みにもかかわらず、モデルが単にFP8と互換性がないことがわかるかもしれません。

結果
以下の表では、p4d.24xlargeおよびp5.48xlarge EC2インスタンスタイプの実験結果をTEライブラリの有無でまとめています。p5.48xlargeの実験では、バッチサイズを2倍にして、80GBのGPUメモリの使用率を高めました。FP8の使用により、GPUメモリの消費量が減少し、バッチサイズをさらに増やすことが可能になります。

![実験結果(著者による)](https://ai.miximages.com/miro.medium.com/v2/resize:fit:640/format:webp/1*gLWVXaA_MRlYQ4WrcfgMmA.png)

TEトランスフォーマーブロックの使用により、p4d(〜19%)およびp5(〜32%)インスタンスタイプのパフォーマンスが向上したことがわかります。FP8の使用により、p5のパフォーマンスがさらに20%向上します。 TEおよびFP8の最適化に続いて、H100ベースのp5.48largeの価格パフォーマンスがA100ベースのp4d.24largeの価格パフォーマンスを上回りました-ただし、非常にわずかな差です(〜2%)。トレーニング速度の3倍の増加を考慮に入れると、最適化されたモデルのトレーニングにはp5がより優れたインスタンスタイプであると私たちは安全に結論付けることができます。

比較的小さな価格パフォーマンスの向上(p5のアナウンスメントで言及された40%よりもはるかに低い)は、追加のH100固有の最適化を望むものにしてくれます...しかし、それらは別の投稿まで待たなければなりません :)

サマリー

この記事では、PyTorchトレーニングスクリプトを8ビット浮動小数点型を使用するようにプログラムする方法を示しました。さらに、FP8の使用はNvidia H100などのモダンなGPUから最高のパフォーマンスを引き出すための重要な要素であることを示しました。重要なのは、FP8の実行可能性およびトレーニングパフォーマンスへの影響は、モデルの詳細に基づいて非常に異なることです。

この投稿は、機械学習のワークロードの最適化に関する一連の長い記事の続きです。この重要なトピックについての他の投稿もぜひご覧ください。

We will continue to update VoAGI; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more