「Amazon SageMakerを使用して、ファルコンモデルのパフォーマンスを向上させる」
「Amazon SageMakerを使って、ファルコンモデルのパフォーマンスを更に向上させる方法」
大規模言語モデル(LLM)をテキスト生成ジェネレーティブAIアプリケーションにホスティングするための最適なフレームワークと設定は何でしょうか? LLMを提供するためのさまざまなオプションがあるにもかかわらず、これはモデルのサイズ、異なるモデルアーキテクチャ、アプリケーションのパフォーマンス要件など、多くの要素により難しい問題です。 アマゾン・セイジメーカーのLarge Model Inference(LMI)コンテナは、さまざまなフレームワークとLLMの展開を最適化するためのさまざまな技術を組み合わせ、LLMを提供することを容易にします。 LMIコンテナには、基礎となるLLMに無関係な強力なサービングスタックであるDJLサービングがあります。 与えられたLLMのホスティングインフラストラクチャの最高のパフォーマンスを抽出するために調整できるシステムレベルの設定パラメータを提供します。 また、連続バッチ化とも呼ばれる最近の最適化にも対応しており、スループットを大幅に向上させます。
以前の記事で、セイジメーカー上でFalconファミリーのモデルを展開するためにLMIコンテナを使用する方法を説明しました。 この記事では、連続バッチ化などの技術を使用して、Falcon-40Bのスループットとレイテンシを向上させる方法を示します。 また、セイジメーカーLMIコンテナが提供する設定パラメータの直感的な理解も提供し、実際のアプリケーションに最適な設定を見つけるのに役立ちます。
LLMのテキスト生成推論の基礎
まず、テキスト生成のためのLLMの推論について基本的な考え方を見てみましょう。
フォワードパス、活性化、およびKVキャッシュ
入力トークンのシーケンスが与えられた場合、それらはLLM(Falconのような)のすべてのレイヤを通って次のトークンを生成するためにフォワードパス
で実行されます。 フォワードパス
は、入力データがニューラルネットワークを通じて出力を生成するプロセスを指します。 テキスト生成の場合、フォワードパスには、初期シードまたは文脈が言語モデルに供給され、シーケンスの次の文字またはトークンが生成されます。 テキストのシーケンスを生成するには、プロセスは通常反復的に行われ、出力シーケンスの各ステップまたは位置で繰り返されます。 各イテレーションでは、モデルは次の文字またはトークンを生成し、それが生成されたテキストの一部となり、このプロセスは必要なテキストの長さが生成されるまで続きます。
- 機械学習における公平性(パート1)
- 「2/10から8/10までの週のトップの重要なコンピュータビジョン論文」
- 新しいAmazon KendraのWebクローラーを使用して、ウェブにクロールされたコンテンツをインデックス化します
FalconやGPTのような言語モデルを使用したテキスト生成は自己回帰的
です。 これは、モデルがトークンを一度に1つ生成し、以前に生成されたトークンに基づいて条件付けられることを意味します。 つまり、各イテレーションで、モデルは以前に生成されたテキストを入力として受け取り、その文脈に基づいて次のトークンを予測します。 vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttentionで述べられているように、この自己回帰的なデコードプロセスでは、LLMへのすべての入力トークンはアテンションキーと値のテンソルを生成し、これらのテンソルは次のトークンを生成するためにGPUメモリに保持されます。 これらのキャッシュされたキーと値のテンソルは通常、KVキャッシュ
と呼ばれます。
プリフィルとデコードのフェーズ
Falconなどの言語モデルを使用したテキスト生成においては、通常、次の2つの主要なフェーズがあります: プリフィル
フェーズとデコード
フェーズ。 これらのフェーズは、一貫性のある文書と文脈に即したテキストの生成に重要です。
プリフィルフェーズでは、次のようなことが含まれます:
- 初期コンテキスト – プリフィルフェーズは、ユーザーが提供した初期コンテキストまたはシードテキストから始まります。 この初期コンテキストは文、フレーズ、または単語である場合があります。 テキスト生成の起点を設定し、次に何が来るかの文脈を提供します。
- モデルの条件付け – 提供されたコンテキストを使用して、言語モデルを条件付けます。 モデルはこのコンテキストを入力とし、文脈を理解してシーケンス内の次のトークン(単語または文字)を生成します。
- トークンの生成 – モデルは1回につき1つのトークンを生成し、テキスト内に次に来るべきものを予測します。 このトークンはコンテキストに追加され、効果的に拡張されます。
- 反復プロセス – トークンの生成プロセスは反復的に繰り返されます。 各ステップでは、モデルは更新されたコンテキストを考慮しながらトークンを生成します。 更新されたコンテキストには、以前のステップで生成されたトークンが含まれます。
プリフィルフェーズは、事前に定められた停止条件が満たされるまで続きます。この条件は、生成テキストの最大長、テキストの終わりを示す特定のトークン、またはユーザーまたはアプリケーションによって設定された他の基準など、さまざまなものがあります。
デコードフェーズには以下の要素が含まれます:
- 補完 – プリフィルフェーズの後、部分的に生成されたテキストがあるため、不完全であるか途中で切れている場合があります。デコードフェーズは、テキストを補完して、それを一貫性のある文法的に正しいテキストにします。
- 最後のトークンから継続 – デコードフェーズでは、モデルはプリフィルフェーズで生成された最後のトークンからスタートします。このトークンを初期コンテキストとして使用し、次のトークンを生成してテキストを続けます。
- 反復的な補完 – プリフィルフェーズ同様、トークンの生成プロセスは再び反復的です。モデルは一度に1つのトークンを生成し、シーケンス内の前のトークンに基づいて条件付けを行います。
- 停止条件 – デコードフェーズにも停止条件があります。最大長に達したり、テキストの終了トークンに遭遇したりすると、生成プロセスが停止します。
プリフィルフェーズとデコードフェーズの組み合わせにより、自己回帰モデルは初期コンテキストに基づいてテキストを生成し、一貫性があり、文脈に即したテキストシーケンスを生成することができます。
詳細な説明については、Transformer-Based Generative Modelsの分散サービングシステムを参照してください。
ダイナミックバッチングを使用したスループットの最適化
これまでは単一の入力について話をしました。実際には、アプリケーションクライアントからランダムに複数のリクエストが同時にまたは交互に受け取られることを想定しています。従来の方法では、基本的なバッチ処理を使用してGPUのスループットと計算リソースの利用率を向上させることができます。バッチ処理は、数個以上のリクエストの数値表現をバッチとして結合し、自己回帰の順方向パスの並列実行を行うことです。このインテリジェントなバッチ処理は、サーバー側で行われます。SageMaker LMIのDJLServingサーバーは、以下のパラメータをserving.propertiesに設定することで、複数のリクエストをまとめてバッチ処理して並列処理するように構成することができます:
- max_batch_delay = 100 – バッチ処理の最大遅延時間(ミリ秒)。デフォルト値は100ミリ秒です。
- batch_size = 32 – ダイナミックなバッチサイズ。デフォルトは1です。
これにより、DJLServingは100ミリ秒ごとにリクエストをキューに待機させるか、指定したbatch_sizeまでのリクエストがキューイングされた場合、バッチをバックエンドに対してインファレンスのためにスケジュールします。これをダイナミックバッチング
と呼びます。バッチサイズは、その時間間隔で追加されたリクエストの数に応じてバッチごとに変更されるため、動的です。ただし、リクエストは異なる特性を持つ可能性があります(例えば、入力が20トークンで出力が500トークンの場合、他のリクエストは逆で、入力が500トークンで出力が20トークンの場合など)。同じバッチ内のすべてのリクエストのデコードステージが完了するのを待っている間に、GPUの利用率が低下する可能性があります。これにより、キュー内で処理待ちの追加のリクエストがあっても、バッチ内のすべてのリクエストが完了するまで、GPUの利用率が低下する可能性があります。次の図は、このプロセスを示しています。
ダイナミックなバッチングビジュアル – リクエスト2と3の最後のアイドルウィンドウに注意してください
連続バッチングを使用したスループットの最適化
連続バッチング
、または反復的
またはローリング
バッチングとも呼ばれる連続バッチングは、プリフィルとデコードのステージの違いを利用します。連続バッチングをアクティブにするために、DJServingではserving.propertiesに以下の追加設定を提供しています。
- engine=MPI – 連続バッチングにはMPIエンジンを使用することをお勧めします。
- option.rolling_batch=autoまたはlmi-dist – 将来的な最適化とともに、最適なローリングバッチアルゴリズムを自動的に選択するために、autoを使用することをお勧めします。
- option.max_rolling_batch_size=32 – この設定は同時実行リクエストの数を制限します。デフォルトは32です。
連続バッチングでは、サービングスタック(DJLServing)はバッチ内のすべての進行中のリクエストがデコードステージを完了するのを待ちません。代わりに、ロジカルブレーク(デコードステージの1つのイテレーションの終わり)で、まだ処理中の現在のバッチとは別のリクエストをキューで待っている追加リクエストを取り込みます(したがって、ローリングバッチと呼ばれます)。このチェックは、デコードステージの各イテレーションの終わりに保留中のリクエストに対して行われます。1つのリクエストごとに、プリフィルステージとシーケンシャルなデコードステージを実行する必要があることを忘れないでください。リクエストの最初のプロンプトのトークンを並行して処理することができるため、新しいリクエストが引き込まれると、バッチ内の進行中のリクエストのデコードステージを一時停止し、そのKVキャッシュとアクティベーションを一時的にメモリに保存し、新しいリクエストのプリフィルステージを実行します。
このキャッシュのサイズは、以下のオプションで設定できます:
- option.max_rolling_batch_prefill_tokens=1024 – ローリングバッチのキャッシュに保存される同時プリフィルトークンの数を制限します(デコードとプリフィルステージの間)。
プリフィルが完了したら、新しいリクエストと一時停止した古いリクエストを新しいローリングバッチで組み合わせ、それらはデコードステージを並列で進めることができます。古い一時停止したリクエストは、中断した場所からデコードステージを再開しますし、新しいリクエストは最初の新しいトークンから開始します。
連続または反復的なバッチングビジュアル – アイドル時間は後続のリクエストに置き換えられていることに注意してください。
連続バッチングは、私たちが日常生活でタスクを自然に並列化する方法とほとんど同じアプローチであることに気付いたかもしれません。私たちはメッセージ、メール、電話の通知(潜在的な新しいリクエスト)がランダムなタイミングで届く(GPUに対してランダムな時間差で複数のリクエストが届くのに類似)ことを持っています。これは私たちが進行中のタスク(メールの作成、コーディング、会議への参加など)を行っている間に起こっています(GPUで現在処理中のタスクに類似)。ロジカルブレークでは、進行中のタスクを一時停止し、通知をチェックして自分の行動が必要かどうかを決定し、必要な場合は進行中のタスク(現実のローリングバッチ)に追加するか、ToDoリスト(キュー)に追加します。
全体を理解する:GPUのメモリ利用率を考える方法
ビジネスユースケースにおいて最もコスト効率の良い構成を見つけるために、モデルをロードテストすることを推奨します。理解を深めるために、モデルのロード時および連続的なリクエスト処理時におけるGPUのメモリフットプリントを視覚化しましょう。この記事では、NVIDIA A10G GPUが搭載されたG5インスタンスタイプの一つにFalcon-40Bモデルをロードしていると仮定しましょう。各GPUのメモリは24GBです。同様の理解は、V100、A100、およびH100 GPUシリーズが搭載されているp3、p4、およびp5インスタンスタイプにも適用されます。
以下は、Falcon-40Bをサーブするために必要な総メモリのおおまかな値です:
- モデルサイズ = モデルパラメータの数(Falcon-40Bでは400億)× パラメータごとのバイト数(FP32の場合は4バイト)= 160GB
- 推論のためにFalcon-40Bをロードするために必要なおおまかな総メモリ = モデルサイズ(160GB) + KVキャッシュ(アテンションキャッシュ)(*20GB) + MLフレームワークによる追加のメモリオーバーヘッド(おおよそ2GB)
メモリビジュアル – ロードされたFalcon-40Bモデルのメモリフットプリントの理解
Falcon-40Bの場合、モデルをバイトフロート16(2バイト)のデータ型に量子化してモデルを圧縮すると、モデルのサイズは約80GBになります。ご覧の通り、これはまだ1つのアクセラレータデバイスがサポートしているメモリよりも大きいため、モデルパーティショニング(シャーディング)技術を採用する必要があります。特別なテンソルパラレリズム(TP)アプローチを使用して、モデルを複数のアクセラレータデバイスに分割します。4つのA10G GPUデバイスを搭載したg5.24xlargeを選択したと仮定しましょう。以下のようにDJLServing(serving.properties)を設定すると、80GBのモデルウェイトはすべての4つのGPUに均等に分割されます:
- option.tensor_parallel_degree%0Aoption.-,tensor_parallel_degree,-%3D2%0A%23%20specify%20per) = 4または8、またはmax(インスタンスで検出された最大のGPU数)を使用する
tensor_parallel_degree
を4に設定すると、24GBのGPUメモリの約20GB(およそ84%)がすでに利用されています。残りの16%のGPUは着信リクエストのKVキャッシュに使用されます。ビジネスシナリオとそのレイテンシとスループットの要件によっては、残りのメモリの2〜3GBが十分である場合があります。そうでない場合は、g5.48xlargeにインスタンスサイズを増やすことができます。このインスタンスは8つのGPUを搭載し、tensor_parallel_degreeを8に設定します。この場合、各GPUの利用可能な24GBメモリのおおよそ10GBがモデルウェイトに使用され、残りの60%がアクティベーションとKVキャッシュに使用されます。直感的には、この構成によってスループットを向上させることができるとわかります。また、バッファーが大きくなったため、max_rolling_batch_prefill_tokensおよびmax_rolling_batch_sizeパラメータを増やすことで、スループットをさらに最適化することができます。これらの2つのパラメータは、モデルのアクティベーションプリフィルやKVキャッシュの事前割り当てを制御します。これらのパラメータを大きくすると、KVキャッシュに十分なバッファーがある場合、スループットが向上する傾向があります。
PagedAttentionによる連続バッチ処理
PagedAttentionは、連続バッチ処理を改善するUC Berkeleyが開発した新しい最適化アルゴリズムです。注意キャッシュ(KVキャッシュ)を連続でなくするために、メモリを固定サイズのページやブロックに割り当てることができます。これは、オペレーティングシステムが使用する仮想メモリとページングの概念に触発されています。
vLLM の論文によれば、各トークンのシーケンスの注意キャッシュはブロックに分割され、ブロックテーブルを介して物理ブロックにマッピングされます。注意の計算中、PagedAttentionカーネルはブロックテーブルを使用して物理メモリからブロックを効率的に取得することができます。これにより、メモリの無駄が大幅に削減され、より大きなバッチサイズ、増加したGPU利用率、およびより高いスループットが可能となります。
パフォーマンス比較
展開設定の効果的な負荷テストを行うためには、ビジネスシナリオを考慮し、LLMベースのアプリケーションの入力および出力の特性を明確に定義することがおすすめです。たとえば、コールセンターの要約ユースケースでは、入力は顧客サービス担当者と顧客の間の500トークンのチャットトランスクリプトなどの大きなテキストで構成される場合がありますが、出力は比較的小さい100トークン程度で、トランスクリプトの要約を表します。一方、コード生成シナリオでは、入力は「ページネーションを含むすべてのEC2リソースを記述する効率的なPythonの実装を作成してください」というような15トークン以下の短いテキストである場合もありますが、出力は500トークンに及ぶ場合もあります。また、特定のシナリオにおいて低いレイテンシを実現するか、スループットを最大化するかを考慮することも重要です。
ビジネスシナリオを包括的に理解した後、ホスティング環境の最適な設定を分析し、決定することができます。このコンテキストでは、ホスティング環境には<のインスタンスタイプや
engine=Python
option.model _id=tiiuae/falcon-40b
option.ten sor_parallel_degree=8
option.dtype=fp16
batch_size=4
max_batch_delay=100
opt ion.trust_remote_code = true
engine = MPI
option.model_id = {{s3_url}}
opt ion.trust_remote_code = true
option.t ensor_parallel_degree = 8
option.m ax_rolling_batch_size = 32
option.rolling_batch = auto
option.dtype = fp16
option.max_rolling _batch_prefill_tokens = 1024
o ption.paged_attention = False
engine = MPI
option.model_id = {{s3_url}}
opt ion.trust_remote_code = true
option.t ensor_parallel_degree = 8
option.m ax_rolling_batch_size = 32
option.rolling_batch = auto
option.dtype = fp16
option.max_rolling _batch_prefill_tokens = 1024
o ption.paged_attention = True
2つの構成は、実世界のアプリケーションを表すいくつかの異なるシナリオで、ml.g5.48xlargeに展開されたFP16データタイプを使用してFalcon-40Bのベンチマークテストが行われました。
- 生成されるトークンの数が多い少数の入力トークン – このシナリオでは、入力トークンの数を32に固定し、128個の新しいトークンが生成されました
バッチ処理戦略 | スループット(トークン/秒) | レイテンシp90(秒) |
ダイナミックバッチ処理 | 5.53 | 58.34 |
連続バッチ処理 | 56.04 | 4.74 |
連続バッチ処理(PagedAttentionあり) | 59.18 | 4.76 |
- 生成されるトークンの数が少ない大きな入力 – ここでは、入力トークンの数を256に固定し、LLMに対して32のトークンで要約するように指示します
バッチ処理戦略 | スループット(トークン/秒) | レイテンシp90(秒) |
ダイナミックバッチ処理 | 19.96 | 59.31 |
連続バッチ処理 | 46.69 | 3.88 |
連続バッチ処理(PagedAttentionあり) | 44.75 | 2.67 |
連続バッチ処理(PagedAttentionあり)は、シナリオ1で10倍、シナリオ2で2.3倍のスループットの向上を提供することが分かります。これは、SageMaker上でLMIコンテナを使用する際の動的バッチ処理と比較しての結果です。
結論
この記事では、LLMがメモリを使用する方法と、SageMaker上でLMIコンテナを使用する際に連続バッチ処理がスループットを改善する方法について説明しました。ベンチマーク結果を示すことで、Falcon-40Bにおける連続バッチ処理の利点をSageMaker上でLMIコンテナを使用してデモンストレーションしました。コードはGitHubリポジトリで入手できます。
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles