Amazon Web Services ブログ
Amazon SageMaker JumpStartによるエンドポイントデプロイのベンチマークと最適化
大規模言語モデル (LLM) をデプロイする場合、機械学習 (ML) の担当者は通常、モデルサービングのパフォーマンスの 2 つの測定値に注目します。1 つ目は 1 トークンの生成にかかる時間で定義されるレイテンシー、二つ目は 1 秒あたりに生成されるトークンの数によって定義されるスループットです。デプロイされたエンドポイントへの単一のリクエストは、モデルレイテンシーの逆数とほぼ同じスループットを示しますが、複数の同時リクエストがエンドポイントに同時に送信される場合は必ずしもそうではありません。クライアント側で同時リクエストを連続的なバッチとして処理するなどのモデルサービング手法により、レイテンシーとスループットの複雑な関係は、モデルアーキテクチャ、サービス構成、インスタンスタイプによるハードウェア、同時リクエスト数、入力トークンや出力トークンの数などの入力ペイロードのバリエーションによって大きく異なります。
この投稿では、Amazon SageMaker JumpStart で利用可能な LLM (Llama 2、Falcon、Mistral の各バリアントを含む) の包括的なベンチマークを通じて、これらの関係を探ります。SageMaker JumpStart を使用すると、ML の担当者は広く公開されているさまざまな基盤モデルから選択して、ネットワークから隔離された環境内の専用の Amazon SageMakerインスタンスにデプロイできます。それらを活用して、アクセラレータ仕様が LLM ベンチマークにどのように影響するかについての理論的な原則について述べます。また、1 つのエンドポイントの背後に複数のインスタンスをデプロイすることによる影響についても説明します。最後に、レイテンシー、スループット、コスト、および利用可能なインスタンスタイプの制約に関する要件に合わせて SageMaker JumpStart のデプロイプロセスを調整するための実践的な推奨事項を示します。ベンチマークの結果と推奨事項はすべて、ユースケースに合わせて調整できる汎用性の高いノートブックに基づいています。
デプロイされたエンドポイントのベンチマーク
次の図は、さまざまなモデルタイプとインスタンスタイプにわたるデプロイ設定の最小レイテンシー(左)と最高スループット(右)を示しています。このベンチマークでは、デプロイ時にSageMaker JumpStartの提供するデフォルト設定を活用しています。これにより、モデルのデプロイに際して、各モデルにとって最適なモデルIDとインスタンスタイプが自動的に設定されます。
これらのレイテンシーとスループットの値は、256 個の入力トークンと 256 個の出力トークンのペイロードに対応します。レイテンシーが最も低い構成では、処理できるモデルは同時リクエストが 1 つに制限され、スループットが最も高い構成では、同時リクエストの数が最大化されます。(訳注:つまりこの二つのグラフは同一のリクエスト設定のもとで測定されたものではないことに注意してください)ベンチマークでわかるように、同時リクエストを増やすとスループットが単調に向上しますが、大量の同時リクエストの改善は弱まります。さらに、サポート対象のインスタンスではモデルが完全にシャーディングされます。たとえば、ml.g5.48xlarge インスタンスには 8 つの GPU があるため、このインスタンスを使用するすべての SageMaker JumpStart モデルは、使用可能な 8 つのアクセラレータすべてでテンソル並列処理を使用してシャーディングされます。
この図からいくつかのポイントがわかります。まず、すべてのモデルがすべてのインスタンスでサポートされているわけではありません。Falcon 7B などの一部の小規模モデルはモデルシャーディングをサポートしていませんが、大規模モデルではコンピューティングリソース要件が高くなります。2 つ目は、シャーディングが増えるほどパフォーマンスは一般的に向上しますが、小規模なモデルでは必ずしも向上するとは限りません。これは、7B や 13B のような小さなモデルでは、あまりにも多くのアクセラレータでシャードすると、通信オーバーヘッドが大きくなるためです。これについては後で詳しく説明します。最後に、A10G GPU よりも A100 GPUの方がメモリ帯域が改善されているため、ml.p4d.24xlarge インスタンスのほうがスループットが大幅に向上する傾向があります。後で説明するように、特定のインスタンスタイプを使用するかどうかは、レイテンシー、スループット、コスト制約などのデプロイ要件によって異なります。
これらの最小のレイテンシーと最高のスループットの設定値を得るにはどうすればよいでしょうか?まず、次の曲線に示すように、256 個の入力トークンと 256 個の出力トークンを含むペイロードの ml.g5.12xlarge インスタンス上の Llama 2 7B エンドポイントのレイテンシーとスループットをプロットすることから始めましょう。デプロイされた他のすべての LLM エンドポイントでも同様のプロットが可能です。
同時実行が増えると、スループットとレイテンシーも単調に増加します。したがって、同時リクエスト値が 1 のときにレイテンシーが最も低く抑えることができ、逆に同時リクエストを増やすことでシステムスループットをコスト効率よく向上させることができます。この曲線には明確な変化点 (“knee”)があり、この変化点の左側では、レイテンシーの増加を影響を受けずに同時実行数の増加によるスループットの向上が期待できます。(訳者注:この変化点を超えると曲線の立ち上がりが顕著になるため、同時実行数の増加がレイテンシーの増加に大きく影響します)。この変化点の場所をどこと捉えるかはユースケースに依存します。事前に指定されたレイテンシー要件(たとえば、100 ミリ秒/トークン)を超えた時点で変化点を定義するケースもあれば、half-latency ruleなどの負荷テストのベンチマークやキューイング理論の手法を適用するケースもあり、さらには理論上のアクセラレータ仕様を使用するケースもあります。
また、同時リクエストの最大数には制限があることにも注意してください。上の図では、プロットは 192 件の同時リクエストで終了しています。この制限の原因は、SageMaker エンドポイントが60 秒後に呼び出し応答をタイムアウトすることです。この設定はアカウント固有で、個々のエンドポイントでは設定できません。LLM では、大量の出力トークンを生成するのに数秒から数分かかることがあります。そのため、入力または出力のペイロードが大きいと、呼び出しリクエストが失敗する可能性があります。さらに、同時リクエストの数が非常に多いと、多くのリクエストでキュー時間が長くなり、この 60 秒のタイムアウト制限が発生します。この調査では、タイムアウト制限下で可能なリクエストにおいて最大スループットを定義しました。重要なのは、SageMaker エンドポイントが呼び出し応答のタイムアウトを観察せずに多数の同時リクエストを処理する場合もあるかもしれないが、レイテンシー-スループット曲線の変化点を基準にして最大同時リクエスト数を定義したい場合があるということです。この時点で、水平スケーリングを検討し始める可能性があります。水平スケーリングとは、単一のエンドポイントが複数のインスタンスにモデルレプリカをプロビジョニングし、受信リクエストをレプリカ間で負荷分散して、より多くの同時リクエストをサポートすることです。
さらに一歩進んで、次の表には、入出力トークンの数、インスタンスタイプ、同時リクエスト数など、Llama 2 7Bモデルのさまざまな構成のベンチマーク結果が含まれています。上の図では、この表の 1 行だけがプロットされていることに注意してください。
Throughput (tokens/sec) | Latency (ms/token) | |||||||||||||||||||
Concurrent Requests | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128 | 256 | 512 |
Number of total tokens: 512, Number of output tokens: 256 | ||||||||||||||||||||
ml.g5.2xlarge | 30 | 54 | 115 | 208 | 343 | 475 | 486 | — | — | — | 33 | 33 | 35 | 39 | 48 | 97 | 159 | — | — | — |
ml.g5.12xlarge | 59 | 117 | 223 | 406 | 616 | 866 | 1098 | 1214 | — | — | 17 | 17 | 18 | 20 | 27 | 38 | 60 | 112 | — | — |
ml.g5.48xlarge | 56 | 108 | 202 | 366 | 522 | 660 | 707 | 804 | — | — | 18 | 18 | 19 | 22 | 32 | 50 | 101 | 171 | — | — |
ml.p4d.24xlarge | 49 | 85 | 178 | 353 | 654 | 1079 | 1544 | 2312 | 2905 | 2944 | 21 | 23 | 22 | 23 | 26 | 31 | 44 | 58 | 92 | 165 |
Number of total tokens: 4096, Number of output tokens: 256 | ||||||||||||||||||||
ml.g5.2xlarge | 20 | 36 | 48 | 49 | — | — | — | — | — | — | 48 | 57 | 104 | 170 | — | — | — | — | — | — |
ml.g5.12xlarge | 33 | 58 | 90 | 123 | 142 | — | — | — | — | — | 31 | 34 | 48 | 73 | 132 | — | — | — | — | — |
ml.g5.48xlarge | 31 | 48 | 66 | 82 | — | — | — | — | — | — | 31 | 43 | 68 | 120 | — | — | — | — | — | — |
ml.p4d.24xlarge | 39 | 73 | 124 | 202 | 278 | 290 | — | — | — | — | 26 | 27 | 33 | 43 | 66 | 107 | — | — | — | — |
このデータには、他にもいくつかのパターンが見られます。コンテキストのサイズが大きくなると、レイテンシーが増加し、スループットが低下します。たとえば、同時実行数が 1 の ml.g5.2xlarge では、合計トークン数が 512 の場合、スループットは 30 トークン/秒ですが、トークンの総数が 4,096 の場合は 20 トークン/秒です。これは、入力が大きいほど処理に時間がかかるためです。また、GPU の能力とシャーディングを増やすと、最大スループットとサポートされる同時リクエストの最大数に影響することもわかります。この表から、Llama 2 7B はインスタンスタイプごとに最大スループット値が著しく異なり、これらの最大スループット値は同時リクエストの値が異なる場合に発生することがわかります。このような特徴から、ML 実践者は、あるインスタンスのコストを別のインスタンスよりも正当化したいと思うでしょう。たとえば、低レイテンシー要件の場合、実践者は ml.g5.2xlarge インスタンス(1 つの A10G GPU)よりも ml.g5.12xlarge インスタンス(4 つの A10G GPU)を選択するかもしれません。高いスループット要件がある場合、フルシャーディング機能を備えた ml.p4d.24xlarge インスタンス (8 個の A100 GPU) の使用は、同時実行性が高い場合にのみ正当化されます。ただし、7B モデルの複数の推論コンポーネントを 1 つの ml.p4d.24xlarge インスタンスにロードすることが、しばしば有益であることに注意してください。このようなマルチモデルのサポートについては、この記事の後半で説明します。
前述の観察は、Llama2 7Bモデルについて行われました。ただし、他のモデルにも同様のパターンが当てはまります。主なポイントは、レイテンシーとスループットのパフォーマンスの数値はペイロード、インスタンスタイプ、同時リクエストの数に依存するため、特定のアプリケーションに最適な構成を見つける必要があるということです。ユースケースに合わせて前述の数値を生成するには、リンクされたノートブックを実行して、モデル、インスタンスタイプ、およびペイロードに合わせてこの負荷テスト分析を設定できます。
アクセラレータの仕様を理解する
LLM 推論に適したハードウェアの選択は、特定のユースケース、ユーザーエクスペリエンスの目標、選択した LLM に大きく依存します。この節では、アクセラレータ仕様に基づく大まかな原則を基準として、レイテンシーとスループット曲線の変化点を理解することを目指しています。これらの原則だけで意思決定をおこなうには不十分であり、実際のベンチマークが必要です。ここでは、デバイスという用語はすべての ML ハードウェアアクセラレータを示しています。我々は、レイテンシースループット曲線は、次の 2 つの要因のうちの 1 つによって決まると断言します。
- アクセラレータが KV (訳註: TransformerアーキテクチャのKeyとValue) マトリックスをキャッシュするためにメモリを使い果たしたため、以降のリクエストはキューに入れられます。(この場合はメモリが計算のボトルネックになりmemory バウンドと呼ばれます)
- アクセラレータには KV キャッシュ用の空きメモリがまだありますが、十分なバッチサイズを使用しているため、処理時間はメモリ帯域幅ではなく計算処理のレイテンシーによって決まります。(訳注:この場合はcompute バウンドと呼ばれます)
2番目の要素はアクセラレータのリソースが飽和状態になっているため、一般的には 2 番目の要素による制限が好まれます。基本的に、支払ったリソースを最大限に活用していることになるからです。さらに詳しく見ていきましょう。
KV キャッシュとデバイスメモリ
標準的な transformer の attention メカニズムは、新しいトークンごとに以前のすべてのトークンに対して attention を計算します。最新の ML サーバー(訳者注: LLM の推論機能を提供しているフレームワーク)のほとんどは、attention の key と value をデバイスメモリ (DRAM) にキャッシュして、各ステップで再計算されないようにします。これは KV キャッシュと呼ばれ、バッチサイズやシーケンスの長さとともに大きくなります。これにより、並行して処理できるユーザリクエストの数が定義されます。使用可能な DRAM が利用可能であることから前述の 2 番目のシナリオの compute バウンドレジームがまだ満たされていない場合には、レイテンシー・スループット曲線のどの程度が優先されるかが決まります。次の式は、KV キャッシュの最大サイズの大まかな概算です。
この式では、B はバッチサイズ、N はアクセラレータの数です。たとえば、A10G GPU (24 GB DRAM) 上で動作する FP16 の Llama 2 7B モデル (2 バイト/パラメーター) は、約 14 GB を消費しますが、残りの 10 GB は KV キャッシュ用です。モデルの全コンテキスト長 (N = 4096) と残りのパラメーター (n_layers=32、n_kv_attention_heads=32、d_attention_head=128) を差し込むと、この式は DRAM の制約により、同時に処理できるバッチサイズが 4 ユーザーに制限されていることがわかります。前の表の対応するベンチマークを観察すると、このレイテンシ・スループット曲線で観測された変化点の近似値が適切であることがわかります。グループ化されたクエリアテンション (GQA) などの方法では KV キャッシュサイズを減らすことができます。GQA の場合は、KV ヘッドの数を減らすのと同じ係数で KV キャッシュサイズを減らすことができます。
算術強度とデバイスメモリ帯域幅
ML アクセラレータの計算能力の増大は、メモリ帯域幅を上回っています。そのため、データの 1 バイトにアクセスするのにかかる時間に対して、はるかに多くの計算をおこなうことができます。
演算の算術強度、つまり計算処理とメモリアクセスの比率によって、選択したハードウェアのメモリ帯域幅または計算能力のどちらによって制限されるかが決まります。たとえば、FP16 で 70 TFLOPS、メモリ帯域幅が 600 GB/秒の A10G GPU (g5 インスタンスタイプファミリー) では、約 116 オペレーション/バイトを計算できます。A100 GPU (p4d インスタンスタイプファミリー) は、約 208 オペレーション/バイトを計算できます。transformer モデルの算術強度がその値を下回る場合はメモリに制限され、それを上回ると計算量に制限されます。Llama 2 7B のアテンションメカニズムは、バッチサイズ 1 でバイトあたり 62 オペレーションを必要とします (説明については、LLM 推論とパフォーマンスのガイドを参照してください)。つまり、メモリに制約があるということです。アテンションメカニズムがメモリーに制約されている場合、高価な FLOPS は未使用のままになります。
アクセラレータをより有効に活用して算術強度を上げるには、操作に必要なメモリアクセスを減らす方法(FlashAttenttionが注目している点)とバッチサイズを増やす方法の2つがあります。ただし、DRAM が小さすぎて対応する KV キャッシュを保持できない場合、バッチサイズを十分に増やすことができないため、compute バウンド領域に達することができない場合があります。標準的な GPT デコーダーによる推論のcompute バウンド領域とmemory バウンド領域を区別するクリティカルバッチサイズ B* の大まかな概算は、次の式で表されます。ここで、A_mb はアクセラレータのメモリ帯域幅、A_f はアクセラレータ FLOPS、N はアクセラレータの数です。この重要なバッチサイズは、メモリアクセス時間が計算時間と等しい場所を見つけることで導き出すことができます。このブログ記事を参照して、式 2 とその前提条件をより詳しく理解してください。
これは、以前にA10Gで計算したのと同じオペレーション/バイト比なので、この GPU のクリティカルバッチサイズは 116 です。この理論上重要なバッチサイズにアプローチする方法の 1 つは、モデルのシャーディングを増やし、キャッシュを N 個のアクセラレータに分割することです。これにより、KV キャッシュ容量だけでなく、メモリー制限のあるバッチサイズも効果的に増加します。
モデルシャーディングのもう 1 つの利点は、モデルパラメーターとデータの読み込み作業を N 個のアクセラレーターに分割できることです。このタイプのシャーディングは、テンソル並列処理とも呼ばれるモデル並列処理の一種です。単純に、メモリ帯域幅と計算能力は合計で N 倍になります。どのような種類のオーバーヘッド (通信、ソフトウェアなど) もないと仮定すると、メモリに制約がある場合はトークンあたりのデコード待ち時間が 1/N に減ります。これは、この領域におけるトークンのデコード待ち時間は、モデルの重みとキャッシュを読み込むのにかかる時間によって制限されるためです。しかし実際には、シャーディングの度合いを上げると、デバイス間の通信が増加し、すべてのモデルレイヤーで中間アクティベーションを共有するようになります。この通信速度は、デバイス相互接続の帯域幅によって制限されます。その影響を正確に推定することは困難ですが (詳細はモデルの並列処理を参照)、最終的にはメリットが得られなくなったり、パフォーマンスが低下したりする可能性があります。これは、データ転送が小さいほど転送速度が遅くなるため、特に小規模なモデルに当てはまります。
ML アクセラレータを仕様に基づいて比較するには、以下をお勧めします。まず、式 2 に従って各アクセラレータタイプのおおよそのクリティカルバッチサイズを計算し、式 1 に従ってクリティカルバッチサイズの KV キャッシュサイズを計算します。その後、アクセラレータで使用可能な DRAM を使用して、KV キャッシュとモデルパラメータに適合するのに必要なアクセラレータの最小数を計算できます。複数のアクセラレータを選択する場合は、メモリ帯域幅 1 GB/秒あたりのコストが最も低い順にアクセラレータを優先します。最後に、これらの構成をベンチマークし、希望するレイテンシーの上限に対して最適なコスト/トークンを検証します。
エンドポイントのデプロイ設定を選択する
SageMaker JumpStart によって配布されている多くの LLM は、モデルのサービングに text-generation-inference (TGI) の SageMaker コンテナを使用しています。次の表は、レイテンシー・スループット曲線に影響するモデル・サービングに対応するように、またはエンドポイントに過負荷をかけるリクエストからエンドポイントを保護するために、さまざまなモデル・サービング・パラメーターを調整する方法を示しています。これらは、ユースケースに合わせてエンドポイントのデプロイメントを設定するために使用できる主なパラメーターです。特に指定がない限り、デフォルトの text generation payload パラメータと TGI 環境変数を使用します。
Environment Variable | Description | SageMaker JumpStart Default Value |
Model serving configurations | ||
MAX_BATCH_PREFILL_TOKENS |
事前入力 (prefill) 操作のトークンの数を制限します。この操作は、新しい入力プロンプトシーケンスの KV キャッシュを生成します。これはメモリを大量に消費し、計算量も限られるため、この値によって 1 回の事前入力操作で許可されるトークンの数が制限されます。プレフィルの実行中は、他のクエリのデコードステップが一時停止します。 | 4096 (TGI デフォルト) またはモデル固有のサポートされるコンテキストの最大長 (SageMaker JumpStart 提供) のいずれか大きい方。 |
MAX_BATCH_TOTAL_TOKENS |
デコード中にバッチに含めるトークンの最大数、またはモデルを推論する回数を制御します。理想的には、使用可能なすべてのハードウェアの使用率を最大化するように設定してください。 | 指定なし (TGI デフォルト)。TGI は、モデルのウォームアップ中に CUDA メモリの残量を基準にしてこの値を設定します。 |
SM_NUM_GPUS |
使用するシャードの数。つまり、テンソル並列処理を使用してモデルを実行するために使用される GPU の数です。 | インスタンスによって異なります (SageMaker JumpStart 提供)。特定のモデルでサポートされている各インスタンスについて、SageMaker JumpStart はテンソル並列処理に最適な設定を提供します。 |
Configurations to guard your endpoint (set these for your use case) | ||
MAX_TOTAL_TOKENS |
これにより、入力シーケンスのトークン数と出力シーケンスのトークンの数 (max_new_tokens ペイロードパラメーター) の合計が制限され、1 つのクライアントリクエストのメモリバジェットが制限されます。 |
モデル固有のサポートされるコンテキストの最大長。たとえば、Llama 2の場合は4096です。 |
MAX_INPUT_LENGTH |
1 つのクライアントリクエストの入力シーケンスで許可されるトークンの最大数を識別します。この値を増やす際に考慮すべき点としては、入力シーケンスが長くなるとメモリが必要になり、連続バッチ処理に影響します。また、多くのモデルがサポートしているコンテキストの長さを超えないようにしてください。 | モデル固有のサポートされるコンテキストの最大長。たとえば、Llama 2 の場合は 4095 です。 |
MAX_CONCURRENT_REQUESTS |
The maximum number of concurrent requests allowed by the deployed endpoint. New requests beyond this limit will immediately raise a model overloaded error to prevent poor latency for the current processing requests. | 128 (TGI デフォルト)。この設定により、さまざまなユースケースで高いスループットを得ることができますが、SageMaker 呼び出しのタイムアウトエラーを軽減するために適切なピン設定を行う必要があります。 |
TGI サーバーは連続バッチ処理を使用します。連続バッチ処理では、同時に発生するリクエストを動的にバッチ処理して 1 つのモデル推論フォワードパスを共有します。フォワードパスには、prefillとdecodeの 2 種類があります。新しいリクエストごとに、prefillフォワードパスを 1 回実行して、入力シーケンストークンの KV キャッシュに入力する必要があります。KV キャッシュにデータが入力されると、decode フォワードパスはバッチ処理されたすべてのリクエストに対して次のトークンの予測を 1 回実行し、これを繰り返し実行して出力シーケンスを生成します。新しいリクエストがサーバーに送信されると、新しいリクエストに対してprefill ステップが実行されるまで、次のdecode ステップを待つ必要があります。この処理は、新しいリクエストが連続的にバッチ処理される後続のdecode ステップに含まれる前に行われなければなりません。ハードウェアの制約により、decode に使用される連続バッチ処理にはすべてのリクエストが含まれない場合があります。この時点で、リクエストは処理キューに入り、スループットの向上はわずかですが、推論レイテンシーは大幅に増加し始めます。
LLM のレイテンシーベンチマーク分析を、prefill レイテンシー、decode レイテンシー、queue レイテンシーに分けることができます。これらの各コンポーネントで消費される時間は本質的に異なります。prefill は 1 回限りの計算で、decode は出力シーケンスのトークンごとに 1 回行われ、queue にはサーバーのバッチ処理が含まれます。複数の同時リクエストが処理されている場合、特定のクライアントリクエストで発生するレイテンシーには、新しい同時リクエストを prefill する必要があることによる queue レイテンシーと、リクエストを batch デコードプロセスに含めることによる queue レイテンシーが含まれるため、これらの各コンポーネントのレイテンシーをそれぞれ算出することが困難になります。そのため、この記事ではエンドツーエンドの処理レイテンシーに焦点を当てています。レイテンシー – スループット曲線で問題となるのは、キューの待ち時間が大幅に増加し始める飽和点です。この現象はどのモデル推論サーバーでも発生し、アクセラレータの仕様によるものです。
デプロイ時の一般的な要件には、最低限必要なスループット、最大許容レイテンシー、1 時間あたりの最大コスト、および 100 万トークンを生成するための最大コストなどが含まれます。これらの要件は、エンドユーザーのリクエストを表すペイロードに条件を付ける必要があります。これらの要件を満たす設計では、特定のモデルアーキテクチャ、モデルのサイズ、インスタンスタイプ、インスタンス数 (水平スケーリング) など、多くの要素を考慮する必要があります。以下のセクションでは、レイテンシーを最小限に抑え、スループットを最大化し、コストを最小限に抑えるためのエンドポイントのデプロイに焦点を当てます。この分析では、合計 512 個のトークンと 256 個の出力トークンを仮定しています。
レイテンシーの最小化
レイテンシーは、多くのリアルタイムユースケースにおいて重要な要件です。次の表では、各モデルと各インスタンスタイプの最小レイテンシーを示しています。MAX_CONCURRENT_REQUESTS = 1
に設定すると、レイテンシーを最小限に抑えることができます。
Minimum Latency (ms/token) | |||||
Model ID | ml.g5.2xlarge | ml.g5.12xlarge | ml.g5.48xlarge | ml.p4d.24xlarge | ml.p4de.24xlarge |
Llama 2 7B | 33 | 17 | 18 | 20 | — |
Llama 2 7B Chat | 33 | 17 | 18 | 20 | — |
Llama 2 13B | — | 22 | 23 | 23 | — |
Llama 2 13B Chat | — | 23 | 23 | 23 | — |
Llama 2 70B | — | — | 57 | 43 | — |
Llama 2 70B Chat | — | — | 57 | 45 | — |
Mistral 7B | 35 | — | — | — | — |
Mistral 7B Instruct | 35 | — | — | — | — |
Mixtral 8x7B | — | — | 33 | 27 | — |
Falcon 7B | 33 | — | — | — | — |
Falcon 7B Instruct | 33 | — | — | — | — |
Falcon 40B | — | 53 | 33 | 27 | — |
Falcon 40B Instruct | — | 53 | 33 | 28 | — |
Falcon 180B | — | — | — | — | 42 |
Falcon 180B Chat | — | — | — | — | 42 |
モデルのレイテンシーを最小限に抑えるには、目的のモデル ID とインスタンスタイプを指定して次のコードを使用できす。
from sagemaker.jumpstart.model import JumpStartModel
model = JumpStartModel(
model_id="meta-textgeneration-llama-2-7b",
model_version="3.*",
instance_type="ml.g5.12xlarge",
env={
"MAX_CONCURRENT_REQUESTS": "1",
"MAX_INPUT_TOKENS": "256",
"MAX_TOTAL_TOKENS": "512",
},
)
predictor = model.deploy(accept_eula=False) # Change EULA acceptance to True
レイテンシーの数値は、入力トークンと出力トークンの数によって変化することに注意してください。ただし、環境変数 MAX_INPUT_TOKENS
と MAX_TOTAL_TOKENS
以外のデプロイプロセスは同じままです。ここでは、入力シーケンスが大きくなるとレイテンシー要件に違反する可能性があるため、これらの環境変数はエンドポイントのレイテンシー要件を保証するために設定されています。SageMaker JumpStart では、インスタンスタイプを選択する際に他の最適な環境変数がすでに用意されていることに注意してください。たとえば、ml.g5.12xlarge を使用すると、モデル環境で SM_NUM_GPUS
が 4 に設定されます。
スループットの最大化
このセクションでは、1 秒あたりに生成されるトークンの数を最大化します。これは通常、モデルとインスタンスタイプに対する有効な同時リクエストの最大数に達したときに達成されます。次の表では、任意のリクエストで SageMaker 呼び出しのタイムアウトが発生する前に達成された最大同時リクエスト値で達成されたスループットを報告しています。
Maximum Throughput (tokens/sec), Concurrent Requests | |||||
Model ID | ml.g5.2xlarge | ml.g5.12xlarge | ml.g5.48xlarge | ml.p4d.24xlarge | ml.p4de.24xlarge |
Llama 2 7B | 486 (64) | 1214 (128) | 804 (128) | 2945 (512) | — |
Llama 2 7B Chat | 493 (64) | 1207 (128) | 932 (128) | 3012 (512) | — |
Llama 2 13B | — | 87 (128) | 496 (64) | 3245 (512) | — |
Llama 2 13B Chat | — | 782 (128) | 505 (64) | 3310 (512) | — |
Llama 2 70B | — | — | 124 (16) | 1585 (256) | — |
Llama 2 70B Chat | — | — | 114 (16) | 1546 (256) | — |
Mistral 7B | 947 (64) | — | — | — | — |
Mistral 7B Instruct | 986 (128) | — | — | — | — |
Mixtral 8x7B | — | — | 701 (128) | 3196 (512) | — |
Falcon 7B | 1340 (128) | — | — | — | — |
Falcon 7B Instruct | 1313 (128) | — | — | — | — |
Falcon 40B | — | 244 (32) | 382 (64) | 2699 (512) | — |
Falcon 40B Instruct | — | 245 (32) | 415 (64) | 2675 (512) | — |
Falcon 180B | — | — | — | — | 1100 (128) |
Falcon 180B Chat | — | — | — | — | 1081 (128) |
モデルのスループットを最大化するには、次のコードを使用できます。
from sagemaker.jumpstart.model import JumpStartModel
model = JumpStartModel(
model_id="meta-textgeneration-llama-2-7b",
model_version="3.*",
instance_type="ml.g5.12xlarge",
env={
"MAX_CONCURRENT_REQUESTS": "128", # For your application, identify it from the benchmarking table with the maximum feasible concurrent requests.
"MAX_INPUT_TOKENS": "256",
"MAX_TOTAL_TOKENS": "512",
},
)
predictor = model.deploy(accept_eula=False) # Change EULA acceptance to True
同時リクエストの最大数は、モデルタイプ、インスタンスタイプ、入力トークンの最大数、および出力トークンの最大数によって異なることに注意してください。そのため、MAX_CONCURRENT_REQUESTS
を設定する前にこれらのパラメーターを設定する必要があります。
また、レイテンシの最小化に関心のあるユーザーは、スループットの最大化に関心のあるユーザーと対立することが多いことにも注意してください。前者はリアルタイム応答に関心があり、後者はエンドポイントのキューが常に飽和状態になって処理のダウンタイムを最小限に抑えるようなバッチ処理に関心があります。レイテンシー要件を条件としてスループットを最大化したいユーザーは、レイテンシー – スループット曲線の変化点に立って処理することに関心がある場合がよくあります。
コストの最小化
コストを最小限に抑える最初の選択肢は、1 時間あたりのコストを最小限に抑えることです。これにより、選択したモデルを 1 時間あたりのコストが最も低い SageMaker インスタンスにデプロイできます。SageMaker インスタンスのリアルタイム価格については、Amazon SageMaker 料金表を参照してください。一般的に、SageMaker JumpStart LLM のデフォルトのインスタンスタイプは最も低コストのデプロイオプションです。
コストを最小限に抑えるための2つ目の選択肢は、100万トークンを生成するためのコストを最小限に抑えることです。これは、先に説明した表を単純に変換してスループットを最大化する方法です。まず、100 万トークンの生成にかかる時間 (1e6/スループット/3600) を時間単位で計算します。その後、この時間を指定した SageMaker インスタンスの 1 時間あたりの価格にかけて、 100 万トークンを生成のにかかるコストを算出できます。
1 時間あたりのコストが最も低いインスタンスは、100 万トークンを生成するためのコストが最も低いインスタンスと同じではないことに注意してください。たとえば、呼び出しリクエストが散発的である場合は、1 時間あたりのコストが最も低いインスタンスが最適である一方、スロットリングシナリオでは、100 万トークンを生成するコストが最も低いインスタンスの方が適切な場合があります。
テンソル並列 vs 複数モデルのトレードオフ
これまでのすべての分析では、デプロイインスタンスタイプの GPU の数に等しいテンソル並列度を持つ単一のモデルレプリカをデプロイすることを検討しました。これが SageMaker JumpStart のデフォルト動作です。ただし、前述のように、モデルをシャーディングすることは、ある特定の限界までモデルのレイテンシーとスループットを改善することができますが、その限界を超えると、デバイス間通信の要求が計算時間を支配するようになります。これは、高いテンソル並列度を持つ単一モデルをデプロイするよりも、低いテンソル並列度を持つ複数のモデルを単一インスタンスにデプロイする方が、しばしば有益であることを意味しています。
ここでは、テンソル並列 (TP) 次数が 1、2、4、8 の ml.p4d.24xlarge インスタンスに Llama 2 の 7B および 13B エンドポイントをデプロイします。モデルの動作をわかりやすくするために、これらのエンドポイントはそれぞれ 1 つのモデルのみをロードします。
以前の分析では、ml.p4d.24xlarge インスタンスではスループットが大幅に向上していることがすでに示されています。これは、同時リクエストの負荷が高い条件下で g5 インスタンスファミリーよりも 100 万トークンを生成するコスト面でのパフォーマンスの向上につながることを示しています。この分析は、単一インスタンス内でのモデルシャーディングとモデル複製のトレードオフを考慮する必要があることを明確に示しています。つまり、完全にシャーディングされたモデルは、通常、7B および 13B モデルファミリーの ml.p4d.24xlarge コンピューティングリソースの最適な使用法ではありません。実際、7B モデルファミリーでは、テンソルの並列次数が 8 ではなく 4 の単一モデルレプリカが最高のスループットが得られています。
ここから、7B モデルの最高スループット構成は 8 つのモデルレプリカによるテンソル並列次数 1 で、13B モデルの最高スループット構成は 4 つのモデルレプリカによるテンソル並列次数 2 である可能性が高いと推定できます。これを実現する方法の詳細については、「推論コンポーネントベースのエンドポイントを使用したAmazon SageMaker の最新機能を使用してモデルのデプロイコストを平均 50% 削減する」を参照してください。負荷分散の技術、サーバールーティング、および CPU リソースの共有により、レプリカ数とレプリカあたりのスループットを掛けた値とまったく同じスループットの向上を完全には達成できない場合があります。
水平スケーリング
前に説明したように、各エンドポイントのデプロイでは、入力トークンと出力トークンの数、およびインスタンスタイプに応じて、同時リクエストの数に制限があります。これがスループットまたは同時リクエストの要件を満たさない場合は、デプロイされたエンドポイントの背後で複数のインスタンスを利用するようにスケールアップできます。SageMaker はインスタンス間のクエリの負荷分散を自動的に実行します。例えば、次のコードは 3 つのインスタンスがサポートするエンドポイントをデプロイします。
model = JumpStartModel(
model_id="meta-textgeneration-llama-2-7b",
model_version="3.*",
instance_type="ml.g5.2xlarge",
)
predictor = model.deploy(
accept_eula=False, # Change EULA acceptance to True
initial_instance_count = 3,
)
次の表は、Llama 2 7B モデルのインスタンス数の係数としてのスループットゲインを示しています。
特に、インスタンス数が多いほど、マルチインスタンスエンドポイント内で処理できる同時リクエストの数が増えるため、レイテンシースループット曲線の変化点が右にシフトしています。この表では、同時リクエストの値はエンドポイント全体のもので、個々のインスタンスが受け取る同時リクエストの数ではありません。
また、自動スケーリング機能を使用してワークロードを監視し、容量を動的に調整して、可能な限り低いコストで安定した予測可能なパフォーマンスを維持することもできます。これはこの記事の範囲外です。自動スケーリングの詳細については、「Amazon SageMaker での自動スケーリング推論エンドポイントの設定」を参照してください。
同時リクエストでのエンドポイントの呼び出し
たとえば、スループットの高い条件下においてデプロイされたモデルからレスポンスを生成するために使用するクエリのバッチが大量にあるとします。たとえば、次のコードブロックでは、1,000 個のペイロードのリストを作成し、各ペイロードで 100 個のトークンの生成を要求しています。合計で 100,000 個のトークンの生成をリクエストしています。
payload = {
"inputs": "I believe the meaning of life is to ",
"parameters": {"max_new_tokens": 100, "details": True},
}
total_requests = 1000
payloads = [payload,] * total_requests
SageMaker ランタイム API に大量のリクエストを送信すると、スロットリングエラーが発生することがあります。これを軽減するために、再試行回数を増やすカスタム SageMaker ランタイムクライアントを作成できます。既にデプロイされているエンドポイントに新しいpredictor をアタッチしたい場合には、作成された SageMaker セッションオブジェクトを JumpStartModel
コンストラクターまたは sagemaker.predictor.retrieve_default
に渡すことができます。次のコードでは、Llama 2 モデルをデフォルトの SageMaker JumpStart 構成でデプロイするときにこのセッションオブジェクトを使用します。
import boto3
from botocore.config import Config
from sagemaker.session import Session
from sagemaker.jumpstart.model import JumpStartModel
sagemaker_session = Session(
sagemaker_runtime_client=boto3.client(
"sagemaker-runtime",
config=Config(connect_timeout=10, retries={"mode": "standard", "total_max_attempts": 20}),
)
)
model = JumpStartModel(
model_id="meta-textgeneration-llama-2-7b",
model_version="3.*",
sagemaker_session=sagemaker_session
)
predictor = model.deploy(accept_eula=False) # Change EULA acceptance to True
このデプロイされたエンドポイントは、デフォルトで MAX_CONCURRENT_REQUESTS = 128 になっています。次のブロックでは、concurrent futures ライブラリを使用して、128 個のワーカースレッドを含むすべてのペイロードのエンドポイントを繰り返し呼び出します。エンドポイントは最大で 128 件の同時リクエストを処理し、リクエストが応答を返すたびに、エグゼキューターはすぐに新しいリクエストをエンドポイントに送信します。
import time
from concurrent import futures
concurrent_requests = 128
time_start = time.time()
with futures.ThreadPoolExecutor(max_workers=concurrent_requests) as executor:
responses = list(executor.map(predictor.predict, payloads))
total_tokens = sum([response[0]["details"]["generated_tokens"] for response in responses])
token_throughput = total_tokens / (time.time() - time_start)
この結果、1 つの ml.g5.2xlarge インスタンスで合計 100,000 個のトークンが生成され、スループットは 1255 トークン/秒になります。この処理には約 80 秒かかります。
このスループット値は、この記事の前の表にある ml.g5.2xlarge の Llama 2 7B の最大スループット (64 件の同時リクエストで 486 トークン/秒) とは著しく異なることに注意してください。これは、入力ペイロードが 256 の代わりに 8 個のトークンを使用し、出力トークン数が 256 個ではなく 100 個になり、トークン数が少ないほど 128 個の同時リクエストが可能になるためです。最後に、レイテンシーとスループットの数値はすべてペイロードに依存していることを思い出させてくれます。ペイロードトークンの数を変更すると、モデル提供中のバッチ処理に影響が及び、ひいてはアプリケーションの事前入力、デコード、キューにかかる時間にも影響します。
結論
この投稿では、Llama 2、Mistral、Falconなどを含む SageMaker JumpStart LLM のベンチマークについて説明しました。また、エンドポイントのデプロイ構成のレイテンシー、スループット、コストを最適化するためのガイドも紹介しました。まずは、関連するノートブックを実行してユースケースのベンチマークを行ってください。
このブログのオリジナルは、Benchmark and optimize endpoint deployment in Amazon SageMaker JumpStart で、機械学習ソリューションアーキテクトの卜部が翻訳しました。