メインコンテンツへジャンプ

LLM推論のパフォーマンスエンジニアリング:ベストプラクティス


この投稿を共有する
LLM Inference Performance Engineering: Best Practices

このブログ記事では、MosaicML のエンジニアリングチームが、一般的なオープンソースの大規模言語モデル (LLM) を本番環境で使用するためのベストプラクティスを紹介します。また、これらのモデルを中心に構築された推論サービスをデプロイするためのガイドラインも提供し、モデルの選択とデプロイハードウェアの選択を支援します。私たちは、本番環境で複数の PyTorch ベースのバックエンドを使用してきました。これらのガイドラインは、FasterTransformers、vLLM、NVIDIA が間もなくリリースするTensorRT-LLMなどでの経験から得られたものです。

LLM テキスト生成について

大規模言語モデル (LLM) は、テキストを 2 段階のプロセスで生成します。1 つは、入力プロンプト内のトークンが並行して処理される「プリフィル」、もう 1 つは、テキストが自己回帰的に一度に 1 つの「トークン」ずつ生成される「デコード」です。生成された各トークンは入力に追加され、次のトークンを生成するためにモデルにフィードバックされます。生成は、LLM が特別なストップトークンを出力するか、ユーザー定義の条件が満たされた場合 (たとえば、トークンの最大数が生成された場合) に停止します。LLM がデコーダーブロックをどのように使用するかについて詳しくは、こちらのブログ記事をご覧ください。

トークンは単語またはサブワードにすることができます。テキストをトークンに分割する正確なルールは、モデルによって異なります。たとえば、Llama モデルがテキストをトークン化する方法と、OpenAI モデルがテキストをトークン化する方法を比較できます。LLM 推論プロバイダーは、トークンベースのメトリクス (トークン/秒など) でパフォーマンスについて語ることがよくありますが、これらの数値は、これらのバリエーションを考慮すると、モデルタイプ間で常に比較できるとは限りません。具体的な例として、Anyscale のチームは、Llama 2 のトークン化は ChatGPT のトークン化よりも 19% 長いことを発見しました (ただし、全体的なコストははるかに低くなっています)。また、HuggingFace の研究者は、Llama 2 は GPT-4 と同じ量のテキストをトレーニングするために、約 20% 多くのトークンを必要としたことも発見しました。

LLM サービス提供の重要な指標

では、推論速度についてどのように考えればよいでしょうか。

私たちのチームは、LLM サービス提供に 4 つの主要な指標を使用しています。

  1. Time To First Token (TTFT): ユーザーがクエリを入力した後、モデルの出力が表示され始めるまでの時間。応答までの待ち時間が短いことは、リアルタイムのインタラクションでは不可欠ですが、オフラインのワークロードではそれほど重要ではありません。この指標は、プロンプトの処理に必要な時間と、最初の出力トークンの生成に必要な時間によって左右されます。
  2. Time Per Output Token (TPOT): システムにクエリを実行しているユーザーの出力トークンを生成する時間。この指標は、各ユーザーがモデルの「速度」をどのように認識するかと対応しています。たとえば、TPOT が 100 ミリ秒/tok の場合、ユーザー 1 人あたり 1 秒あたり 10 トークン、または 1 分あたり約 450 語になり、これは一般的な人が読める速度よりも速くなります。
  3. レイテンシ: モデルがユーザーの完全な応答を生成するのにかかる全体的な時間。全体的な応答レイテンシは、前の 2 つの指標を使用して計算できます。レイテンシ = (TTFT) + (TPOT) * (生成されるトークンの数)。
  4. スループット: 推論サーバーがすべてのユーザーとリクエストに対して生成できる 1 秒あたりの出力トークンの数。

私たちの目標は何でしょうか。最初のトークンまでの最短時間、最大のスループット、および出力トークンあたりの最短時間です。言い換えれば、モデルができるだけ多くのユーザーをサポートできるように、できるだけ速くテキストを生成したいと考えています。

特に、スループットと出力トークンあたりの時間の間にはトレードオフがあります。16 個のユーザーのクエリを同時に処理する場合、クエリを順番に実行するよりもスループットは高くなりますが、各ユーザーの出力トークンを生成するのにより長い時間がかかります。

全体的な推論レイテンシの目標がある場合は、モデルを評価するための便利なヒューリスティックを次に示します。

  • 出力長が全体的な応答レイテンシを支配する: 平均レイテンシの場合、通常は予想/最大出力トークン長を取得し、モデルの出力トークンあたりの全体的な平均時間を掛けるだけで済みます。
  • 入力長はパフォーマンスには重要ではありませんが、ハードウェア要件には重要です: 512 個の入力トークンを追加すると、MPT モデルで 8 個の追加の出力トークンを生成するよりもレイテンシが小さくなります。ただし、長い入力をサポートする必要があるため、モデルのサービス提供が難しくなる可能性があります。たとえば、MPT-7B をその最大コンテキスト長である 2048 トークンでサービス提供するには、A100-80GB (またはそれ以降) を使用することをお勧めします。
  • 全体的なレイテンシは、モデルサイズに応じて準線形的にスケーリングされます: 同じハードウェアでは、モデルが大きいほど遅くなりますが、速度比は必ずしもパラメーター数比と一致するとは限りません。MPT-30B のレイテンシは、MPT-7B のレイテンシの約 2.5 倍です。Llama2-70B のレイテンシは、Llama2-13B のレイテンシの約 2 倍です。

見込み客のお客様から、平均推論レイテンシを提供するように求められることがよくあります。「トークンあたり 20 ミリ秒未満にする必要がある」など、特定のレイテンシ目標に固執する前に、予想される入力長と必要な出力長を特徴付ける時間を費やすことをお勧めします。

LLM 推論の課題

LLM 推論の最適化は、次のような一般的な手法の恩恵を受けます。

  • 演算子の融合: 異なる隣接する演算子を組み合わせると、多くの場合、レイテンシが向上します。
  • 量子化: アクティベーションと重みが圧縮され、より少ないビット数を使用します。
  • 圧縮: スパース性または蒸留。
  • 並列化: 複数のデバイスにわたるテンソル並列処理、またはより大きなモデルのパイプライン並列処理。

これらの方法に加えて、多くの重要な Transformer 固有の最適化があります。この主な例は、KV (キーと値) キャッシュです。デコーダーのみの Transformer ベースのモデルの注意メカニズムは、計算効率が低くなります。各トークンは以前に表示されたすべてのトークンに注意を払い、新しいトークンが生成されるたびに同じ値の多くを再計算します。たとえば、N 番目のトークンを生成している間、(N-1) 番目のトークンは (N-2) 番目、(N-3) 番目… 1 番目のトークンに注意を払います。同様に、(N+1) 番目のトークンを生成している間、N 番目のトークンの注意は、(N-1) 番目、(N-2) 番目、(N-3) 番目… 1 番目のトークンを再度参照する必要があります。KV キャッシュ、つまり注意レイヤーの中間キー/値を保存することは、それらの結果を後で再利用するために保持するために使用され、繰り返しの計算を回避します。

メモリ帯域幅が重要

LLM の計算は、主にマトリックス-マトリックス乗算演算によって支配されます。小さな次元を持つこれらの演算は、通常、ほとんどのハードウェアでメモリ帯域幅に制限されます。自己回帰的にトークンを生成する場合、アクティベーションマトリックスの次元の 1 つ (バッチサイズとシーケンス内のトークン数によって定義される) は、小さなバッチサイズでは小さくなります。したがって、速度は、ロードされたデータでどれだけ速く計算できるかではなく、GPU メモリからローカルキャッシュ/レジスタにモデルパラメーターをどれだけ速くロードできるかに依存します。推論ハードウェアで使用可能で達成されたメモリ帯域幅は、ピーク時の計算パフォーマンスよりもトークン生成の速度をより適切に予測できます。

推論ハードウェアの使用率は、サービス提供コストの点で非常に重要です。GPU は高価であり、できるだけ多くの作業を行う必要があります。共有推論サービスは、多くのユーザーからのワークロードを組み合わせ、個々のギャップを埋め、重複するリクエストをまとめてバッチ処理することで、コストを低く抑えることを約束します。Llama2-70B のような大規模モデルの場合、大規模なバッチサイズでのみ優れたコスト/パフォーマンスを実現できます。大規模なバッチサイズで動作できる推論サービス提供システムを持つことは、コスト効率にとって非常に重要です。ただし、バッチが大きいほど KV キャッシュサイズが大きくなり、その結果、モデルのサービス提供に必要な GPU の数が増加します。ここには綱引きがあり、共有サービスオペレーターはいくつかのコストのトレードオフを行い、システムの最適化を実装する必要があります。

モデル帯域幅使用率 (MBU)

LLM 推論サーバーはどの程度最適化されていますか。

前に簡単に説明したように、LLM の推論は、特にデコード時には、小さなバッチサイズでは、デバイスメモリから計算ユニットにモデルパラメーターをどれだけ速くロードできるかによってボトルネックになります。メモリ帯域幅は、データ移動がどれだけ速く行われるかを決定します。基盤となるハードウェアの使用率を測定するために、モデル帯域幅使用率 (MBU) と呼ばれる新しい指標を導入します。MBU は (達成されたメモリ帯域幅) / (ピークメモリ帯域幅) として定義され、達成されたメモリ帯域幅は ((モデルパラメーターの合計サイズ + KV キャッシュサイズ) / TPOT) です。

たとえば、16 ビット精度で実行されている 7B パラメーターの TPOT が 14 ミリ秒の場合、14 ミリ秒で 14 GB のパラメーターを移動し、1 TB/秒の帯域幅使用量に変換されます。マシンのピーク帯域幅が 2 TB/秒の場合、MBU は 50% で実行されています。簡単にするために、この例では KV キャッシュサイズを無視しています。これは、バッチサイズが小さく、シーケンス長が短い場合は小さくなります。MBU 値が 100% に近い場合は、推論システムが使用可能なメモリ帯域幅を効果的に利用していることを意味します。MBU は、正規化された方法で異なる推論システム (ハードウェア + ソフトウェア) を比較するのにも役立ちます。MBU は、計算に制限のある設定で重要なモデル Flops 使用率 (MFU; PaLM ペーパーで紹介) メトリックを補完します。

図 1 は、ルーフラインプロットと同様のプロットで MBU を図で表したものです。オレンジ色の網掛け領域の傾斜のある実線は、メモリ帯域幅が 100% で完全に飽和している場合に可能な最大スループットを示しています。ただし、実際には、バッチサイズが小さい場合 (白い点)、観測されるパフォーマンスは最大値よりも低くなります。どれだけ低いかは、MBU の尺度です。バッチサイズが大きい場合 (黄色の領域)、システムは計算に制限があり、ピーク時の可能なスループットに対する達成されたスループットの割合は、モデル Flops 使用率 (MFU) として測定されます。

モデル帯域幅使用率
図 1: MBU (モデル帯域幅使用率) と MFU (モデル Flops 使用率) を示しています。MBU と MFU は、それぞれメモリ制限領域と計算制限領域で達成されたピークの割合です。

MBU と MFU は、特定のハードウェア設定で推論速度をさらに向上させるために利用できる余地がどれだけあるかを判断します。図 2 は、TensorRT-LLM ベースの推論サーバーを使用した、さまざまな程度のテンソル並列処理で測定された MBU を示しています。ピーク時のメモリ帯域幅使用率は、大きな連続したメモリチャンクを転送するときに達成されます。MPT-7B のような小さなモデルが複数の GPU に分散されている場合、各 GPU でより小さなメモリチャンクを移動するため、MBU が低くなります。

経験的に観測された MBU
図 2: A100-40G GPU での TensorRT-LLM を使用した、さまざまな程度のテンソル並列処理で経験的に観測された MBU。リクエスト: バッチサイズ 1 の 512 個の入力トークンのシーケンス。

図 3 は、NVIDIA H100 GPU でのさまざまな程度のテンソル並列処理とバッチサイズで経験的に観測された MBU を示しています。MBU はバッチサイズが増加するにつれて減少します。ただし、GPU をスケールすると、MBU の相対的な減少はそれほど大きくありません。また、メモリ帯域幅が大きいハードウェアを選択すると、GPU の数を減らしてパフォーマンスを向上させることができることにも注意してください。バッチサイズ 1 では、4xA100-40GB GPU の 55% と比較して、2xH100-80GB で 60% の高い MBU を達成できます (図 2)。

テンソル並列処理モード
図 3: H100-80G GPU でのさまざまなバッチサイズとテンソル並列処理モードで経験的に観測された MBU。リクエスト: 512 個の入力トークンのシーケンス

ベンチマーク結果

レイテンシ

MPT-7B モデルと Llama2-70B モデルのさまざまな程度のテンソル並列処理で、最初のトークンまでの時間 (TTFT) と出力トークンあたりの時間 (TPOT) を測定しました。入力プロンプトが長くなるにつれて、最初のトークンを生成する時間が合計レイテンシのかなりの部分を占めるようになります。複数の GPU にわたってテンソルを並列化すると、このレイテンシを短縮できます。

モデルトレーニングとは異なり、より多くの GPU にスケールすると、推論レイテンシに対する収穫逓減が大幅に発生します。たとえば、Llama2-70B の場合、4x から 8x GPU に移行しても、小さなバッチサイズではレイテンシが 0.7 倍しか減少しません。この理由の 1 つは、並列処理が高いほど MBU が低くなるためです (前述のとおり)。もう 1 つの理由は、テンソル並列処理によって GPU ノード間の通信オーバーヘッドが発生するためです。

  最初のトークンまでの時間 (ミリ秒)
モデル 1xA100-40GB 2xA100-40GB 4xA100-40GB 8xA100-40GB
MPT-7B 46 (1x) 34 (0.73x) 26 (0.56x) -
Llama2-70B 適合しません 154 (1x) 114 (0.74x)

表 1: 入力リクエストがバッチサイズ 1 の 512 トークン長の場合の、最初のトークンまでの時間。Llama2 70B のような大規模モデルは、メモリに収まるように少なくとも 4xA100-40B GPU が必要です

バッチサイズが大きいほど、テンソル並列処理が高いほど、トークンレイテンシの相対的な減少が大きくなります。図 4 は、MPT-7B の出力トークンあたりの時間がどのように変化するかを示しています。バッチサイズ 1 では、2x から 4x に移行しても、トークンレイテンシが約 12% しか減少しません。バッチサイズ 16 では、4x のレイテンシは 2x よりも 33% 低くなります。これは、バッチサイズ 1 と比較して、バッチサイズ 16 のテンソル並列処理の程度が高いほど、MBU の相対的な減少が小さくなるという以前の観測と一致しています。

GPU の数を増やす
図 4: A100-40GB GPU 全体で MPT-7B をスケールする場合の、ユーザーごとの出力トークンあたりの時間。レイテンシは、GPU の数が増加するにつれて線形にスケールしません。リクエスト: 128 個の入力トークンと 64 個の出力トークンのシーケンス

図 5 は、Llama2-70B の同様の結果を示していますが、4x と 8x の間の相対的な改善はそれほど顕著ではありません。また、2 つの異なるハードウェア間での GPU スケーリングも比較します。H100-80GB は A100-40GB と比較して 2.15 倍の GPU メモリ帯域幅を備えているため、4x システムの場合、バッチサイズ 1 ではレイテンシが 36% 低く、バッチサイズ 16 では 52% 低くなっています。

複数の GPU
図 5: 複数の GPU 全体で Llama-v2-70B をスケールする場合の、ユーザーごとの出力トークンあたりの時間 (入力リクエスト: 512 トークン長)。Llama-v2-70B (float16) はこれらのシステムに適合しないため、1x40GB GPU、2x40GB、および 1x80GB GPU の数値はここにありません。

スループット

リクエストをまとめてバッチ処理することで、スループットとトークンあたりの時間をトレードオフできます。GPU 評価中にクエリをグループ化すると、クエリを順番に処理するよりもスループットが向上しますが、各クエリの完了にはより長い時間がかかります (キューイング効果は無視します)。

推論リクエストをバッチ処理するための一般的な手法がいくつかあります。

  • 静的バッチ処理: クライアントは複数のプロンプトをリクエストにパックし、バッチ内のすべてのシーケンスが完了した後に応答が返されます。当社の推論サーバーはこれをサポートしていますが、必須ではありません。
  • 動的バッチ処理: プロンプトはサーバー内でその場でまとめてバッチ処理されます。通常、この方法は静的バッチ処理よりもパフォーマンスが低下しますが、応答が短いか、長さが均一な場合は、最適に近づくことができます。リクエストに異なるパラメーターがある場合は、うまく機能しません。
  • 継続的バッチ処理: リクエストが到着したときにまとめてバッチ処理するというアイデアは、この優れたペーパーで紹介され、現在 SOTA の方法です。バッチ内のすべてのシーケンスが完了するのを待つ代わりに、反復レベルでシーケンスをグループ化します。動的バッチ処理よりも 10 倍から 20 倍優れたスループットを実現できます。

LLM サービス提供
図 6: LLM サービス提供でのさまざまな種類のバッチ処理。バッチ処理は、推論の効率を向上させる効果的な方法です。

継続的バッチ処理は通常、共有サービスに最適なアプローチですが、他の 2 つの方が優れている状況もあります。低 QPS 環境では、動的バッチ処理は継続的バッチ処理よりも優れたパフォーマンスを発揮できます。より単純なバッチ処理フレームワークでは、低レベルの GPU 最適化を実装する方が簡単な場合があります。オフラインのバッチ推論ワークロードの場合、静的バッチ処理は大幅なオーバーヘッドを回避し、より優れたスループットを実現できます。

バッチサイズ

バッチ処理がどれだけうまく機能するかは、リクエストストリームに大きく依存します。ただし、均一なリクエストで静的バッチ処理をベンチマークすることにより、そのパフォーマンスの上限を取得できます。

  バッチサイズ
ハードウェア 1 4 8 16 32 64 128
1 x A10 0.4 (1x) 1.4 (3.5x) 2.3 (6x) 3.5 (9x) OOM (メモリ不足) エラー
2 x A10 0.8 2.5 4.0 7.0 8.0  
1 x A100 0.9 (1x) 3.2 (3.5x) 5.3 (6x) 8.0 (9x) 10.5 (12x) 12.5 (14x)  
2 x A100 1.3 3.0 5.5 9.5 14.5 17.0 22.0
4 x A100 1.7 6.2 11.5 18.0 25.0 33.0 36.5

表 2: 静的バッチ処理と FasterTransformers ベースのバックエンドによる、ピーク時の MPT-7B スループット (req/秒)。リクエスト: 512 個の入力トークンと 64 個の出力トークン。入力が大きいほど、OOM 境界はバッチサイズが小さくなります。

レイテンシのトレードオフ

リクエストレイテンシは、バッチサイズとともに増加します。たとえば、1 つの NVIDIA A100 GPU では、バッチサイズ 64 でスループットを最大化すると、スループットが 14 倍に増加する一方で、レイテンシが 4 倍に増加します。共有推論サービスは通常、バランスの取れたバッチサイズを選択します。独自のモデルをホストするユーザーは、アプリケーションに適したレイテンシ/スループットのトレードオフを決定する必要があります。チャットボットのような一部のアプリケーションでは、高速応答のための低レイテンシが最優先事項です。構造化されていない PDF のバッチ処理のような他のアプリケーションでは、個々のドキュメントを処理するためのレイテンシを犠牲にして、すべてのドキュメントを並行して高速に処理したい場合があります。

図 7 は、7B モデルのスループット対レイテンシ曲線を示しています。この曲線上の各線は、バッチサイズを 1 から 256 に増やすことで得られます。これは、さまざまなレイテンシ制約の下で、バッチサイズをどれだけ大きくできるかを判断するのに役立ちます。上記のルーフラインプロットを思い出すと、これらの測定値は予想されるものと一致していることがわかります。特定のバッチサイズの後、つまり、計算制限体制に移行すると、バッチサイズを 2 倍にするたびに、スループットを増加させることなくレイテンシが増加するだけです。

レイテンシ曲線
図 7: MPT-7