Connect with us

人工知能

LLMデプロイの最適化: vLLM PagedAttentionと効率的なAIサービングの未来

mm
Deploy the vLLM Inference Engine to Run Large Language Models

大規模言語モデル(LLM)を実世界のアプリケーションにデプロイすることは、特に計算リソース、待ち時間、コスト効率の面で独自の課題を提起します。この包括的なガイドでは、LLMサービングのランドスケープを探索し、特にvLLM(ベクトル言語モデル)に焦点を当てます。vLLMは、LLMのデプロイとインタラクションの方法を再定義するソリューションです。

大規模言語モデルのサービングの課題

具体的なソリューションに取り組む前に、LLMサービングを複雑にする主要な課題を検討しましょう:

計算リソース

LLMは、数十億から数百億のパラメータを持つことで知られています。例えば、GPT-3には175億のパラメータがあり、より最近のモデルであるGPT-4はさらに多くのパラメータを持つと推定されています。このような規模は、推論に重大な計算リソースを必要とします。

例:
比較的慎ましいLLM、13億パラメータのLLaMA-13Bを考えてみましょう。このモデルでは:

– 16ビット精度を仮定して、モデルパラメータを保存するだけで約26 GBのメモリが必要です
– アクティベーション、注意メカニズム、 intermediate 計算用の追加メモリ
– 実時間推論用の大量のGPU計算能力

待ち時間

チャットボットやリアルタイムコンテンツ生成などの多くのアプリケーションでは、低待ち時間は良好なユーザーエクスペリエンスに不可欠です。ただし、LLMの複雑さは、特に長いシーケンスの場合に、重大な処理時間につながることがあります。

例:
LLMを搭載したカスタマーサービスチャットボットを想像してみましょう。各応答が数秒かかる場合、会話はユーザーにとって不自然で苛立たしいものになります。

コスト

LLMを大規模に実行するために必要なハードウェアは非常に高価です。高性能GPUまたはTPUが必要であり、これらのシステムのエネルギー消費も大きいです。

例:
NVIDIA A100 GPU(LLM推論に頻繁に使用される)のクラスタを実行することは、クラウドコンピューティング料金で1日あたり数千ドルかかる可能性があります。

LLMサービングの伝統的なアプローチ

より高度なソリューションを探求する前に、LLMサービングの伝統的なアプローチを簡単にレビューしましょう:

Hugging Face Transformersを使用したシンプルなデプロイ

Hugging Face Transformersライブラリは、LLMをデプロイするための明確な方法を提供しますが、高スループットのサービングに最適化されていません。

例コード:

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

モデル名 = "meta-llama/Llama-2-13b-hf"
モデル = AutoModelForCausalLM.from_pretrained(モデル名, device_map="auto")
トークナイザー = AutoTokenizer.from_pretrained(モデル名)

def generate_text(プロンプト, max_length=100):
入力 = トークナイザー(プロンプト, return_tensors="pt").to(モデル.device)
出力 = モデル.generate(**入力, max_length=max_length)
return トークナイザー.decode(出力[0], skip_special_tokens=True)

print(generate_text("AIの未来は"))

このアプローチは機能しますが、リソースの非効率的な使用とサービングの最適化の欠如により、高トラフィックアプリケーションには適していません。

TorchServeまたは同様のフレームワークを使用する

TorchServeなどのフレームワークは、ロードバランシングやモデルバージョニングなどのより堅牢なサービング機能を提供します。ただし、LLMサービングの特定の課題、たとえば大規模モデル用の効率的なメモリ管理に対処していません。

LLMサービングにおけるメモリ管理の理解

LLMをサービングする際には、メモリ管理が非常に重要です。メモリ管理のさまざまな側面を示す画像を以下に示します。

セグメント化されたメモリとページ化されたメモリ

これら2つの図は、オペレーティングシステム(OS)で一般的に使用されるセグメント化されたメモリ管理とページ化されたメモリ管理技術を比較しています。

  • セグメント化されたメモリ: この技術は、メモリをプログラムまたはプロセスごとに異なるセグメントに分割します。LLMサービングのコンテキストでは、異なるセグメントは、トークナイゼーション、埋め込み、注意メカニズムなどのモデルのさまざまなコンポーネントに割り当てられます。各セグメントは独立して拡大または縮小でき、柔軟性を提供しますが、セグメントが適切に管理されていない場合に断片化につながる可能性があります。
  • ページ化されたメモリ: ここでは、メモリは固定サイズのページに分割され、物理メモリにマップされます。ページは必要に応じてスワップインまたはスワップアウトできますが、メモリリソースの効率的な使用を可能にします。LLMサービングでは、これはモデル重みと中間計算を保存するために必要な大量のメモリを管理する上で非常に重要です。

OSとvLLMのメモリ管理

この画像は、従来のOSメモリ管理とvLLMで使用されるメモリ管理アプローチを対比しています。

  • OSメモリ管理: 従来のオペレーティングシステムでは、プロセス(例:プロセスAとプロセスB)は、物理メモリ(ページ0、ページ1など)にページのメモリが割り当てられます。この割り当ては、プロセスがメモリを要求または解放するにつれて時間の経過とともに断片化につながる可能性があります。
  • vLLMメモリ管理: vLLMフレームワークは、Key-Value(KV)キャッシュを使用してメモリをより効率的に管理します。要求(例:要求Aと要求B)は、KVキャッシュ(KVブロック0、KVブロック1など)のブロックに割り当てられます。このアプローチは、断片化を最小限に抑え、メモリ使用を最適化するのに役立ち、より迅速で効率的なモデルサービングを可能にします。

LLMの注意メカニズム

LLMの注意メカニズム

LLMの注意メカニズム

注意メカニズムは、LLMで一般的に使用されるトランスフォーマーモデルの中核となるコンポーネントです。この図は、注意式とそのコンポーネントを示しています:

  • クエリ(Q): デコーダー段階の新しいトークンまたは、モデルが最後に見たトークン。
  • キー(K): モデルが注目するべき前のコンテキスト。
  • 値(V): 前のコンテキストの加重合計。

式は、クエリとキーのドット積を計算し、キーの次元の平方根でスケーリングし、ソフトマックス関数を適用し、最後に値とのドット積を計算することで、注意スコアを計算します。このプロセスにより、モデルは各トークンを生成するときに入力シーケンスの関連する部分に注目できます。

サービングスループットの比較

vLLM: PagedAttentionを使用した簡単、高速、低コストのLLMサービング

vLLM: PagedAttentionを使用した簡単、高速、低コストのLLMサービング

この画像は、さまざまなフレームワーク(HF、TGI、vLLM)を使用して、さまざまなハードウェア設定でLLaMAモデルを使用したサービングスループットの比較を示しています。

  • LLaMA-13B、A100-40GB: vLLMは、Hugging Face Transformers(HF)よりも14倍から24倍、Hugging Face Text Generation Inference(TGI)よりも2.2倍から2.5倍高いスループットを達成します。
  • LLaMA-7B、A10G: 同様の傾向が観察され、vLLMはHFとTGIを大幅に上回ります。

vLLM: 新しいLLMサービングアーキテクチャ

カリフォルニア大学バークレー校の研究者によって開発されたvLLMは、LLMサービング技術における重要な進歩を表します。vLLMの主な特徴と革新を探ってみましょう:

PagedAttention

vLLMの核となるのは、オペレーティングシステムの仮想メモリ管理からインスピレーションを得た新しい注意アルゴリズムであるPagedAttentionです。ここでは、そのしくみを説明します:

Key-Value(KV)キャッシュのパーティショニング: KVキャッシュ全体をメモリに連続して保存するのではなく、PagedAttentionはそれを固定サイズのブロックに分割します。
非連続的なストレージ: これらのブロックはメモリに非連続して保存できますが、より柔軟なメモリ管理を可能にします。
オンデマンド割り当て: ブロックは必要なときにのみ割り当てられ、メモリの浪費を削減します。
効率的な共有: 複数のシーケンスがブロックを共有できるため、並列サンプリングやビームサーチなどのテクニックを最適化できます。

イラスト:

“`
従来のKVキャッシュ:
[トークン1のKV][トークン2のKV][トークン3のKV]…[トークンNのKV]
(連続したメモリ割り当て)

PagedAttentionのKVキャッシュ:
[ブロック1] -> 物理アドレスA
[ブロック2] -> 物理アドレスC
[ブロック3] -> 物理アドレスB

(非連続したメモリ割り当て)
“`

このアプローチにより、メモリの断片化が大幅に削減され、GPUメモリの使用が大幅に効率化されます。

連続バッチ処理

vLLMは、リクエストが到着するとそれらを動的に処理する連続バッチ処理を実装します。固定サイズのバッチを形成するのを待たなくても、待ち時間を低減し、スループットを向上させます。

例:
リクエストのストリームを想像してみましょう:

“`
0ミリ秒: リクエストAが到着
10ミリ秒: リクエストAの処理を開始
15ミリ秒: リクエストBが到着
20ミリ秒: リクエストBの処理を開始(Aと並行して)
25ミリ秒: リクエストCが到着

“`

連続バッチ処理を使用すると、vLLMは各リクエストをすぐに処理し始めることができ、事前に定義されたバッチにグループ化するのを待たなくても済みます。

効率的な並列サンプリング

複数の出力サンプルを生成する必要があるアプリケーション(例:創造的書き込みアシスタント)では、vLLMのメモリ共有機能が際立つようになります。共通のプレフィックスのKVキャッシュを再利用しながら、複数の出力を生成できます。

vLLMを使用した例コード:


from vllm import LLM, SamplingParams

llm = LLM(model="meta-llama/Llama-2-13b-hf")
prompts = ["AIの未来は"]

# プロンプトごとに3つのサンプルを生成
sampling_params = SamplingParams(n=3, temperature=0.8, max_tokens=100)
outputs = llm.generate(prompts, sampling_params)

for output in outputs:
print(f"プロンプト: {output.prompt}")
for i, out in enumerate(output.outputs):
print(f"サンプル {i + 1}: {out.text}")

このコードは、指定されたプロンプトに対して、vLLMの最適化を活用しながら効率的に複数のサンプルを生成します。

vLLMのパフォーマンスのベンチマーク

vLLMの影響を真正に理解するには、パフォーマンス比較をいくつか見てみましょう:

スループットの比較

提供された情報に基づいて、vLLMは他のサービングソリューションを大幅に上回ります:

– Hugging Face Transformersよりも最大24倍高いスループット
– Hugging Face Text Generation Inference(TGI)よりも2.2倍から3.5倍高いスループット

イラスト:

“`
トークン/秒のスループット
|
| ****
| ****
| ****
| **** ****
| **** **** ****
| **** **** ****
|————————
HF TGI vLLM
“`

メモリ効率

vLLMのPagedAttentionはほぼ最適なメモリ使用を実現します:

– 伝統的なシステムでは60〜80%のメモリ浪費に対して、約4%のメモリ浪費
– この効率により、同じハードウェアでより大きなモデルをサービングしたり、より多くの同時リクエストを処理したりできます

vLLMの開始

vLLMの利点を探ったので、セットアップとプロジェクトでの使用方法について説明しましょう。

6.1 インストール

vLLMをインストールするのは、pipを使用して簡単です:


!pip install vllm

6.2 オフライン推論の基本的な使用

vLLMを使用したオフラインテキスト生成の簡単な例を以下に示します:

from vllm import LLM, SamplingParams

# モデルの初期化
llm = LLM(model="meta-llama/Llama-2-13b-hf")

# プロンプトの準備
prompts = [
"人工知能についての短い詩を書いて:",
"量子コンピューティングを単純な用語で説明して:"
]

# サンプリングパラメータの設定
sampling_params = SamplingParams(temperature=0.8, max_tokens=100)

# 応答の生成
outputs = llm.generate(prompts, sampling_params)

# 結果の印刷
for output in outputs:
print(f"プロンプト: {output.prompt}")
print(f"生成されたテキスト: {output.outputs[0].text}\n")

このスクリプトは、モデルをロードし、サンプリングパラメータを設定し、複数のプロンプトに対してテキストを生成する方法を示しています。

6.3 vLLMサーバーの設定

オンラインサービングの場合、vLLMはOpenAIと互換性のあるAPIサーバーを提供します。設定方法は以下のとおりです:

1. サーバーを開始します:

python -m vllm.entrypoints.openai.api_server --model meta-llama/Llama-2-13b-hf

2. サーバーにcurlを使用してクエリを送信します:

curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-2-13b-hf",
"prompt": "人工知能の利点には:",
"max_tokens": 100,
"temperature": 0.7
}'

このセットアップにより、OpenAI APIと互換性のあるインターフェイスでLLMをサービングでき、既存のアプリケーションへの統合が容易になります。

高度なトピック: vLLM

vLLMはLLMサービングに大きな改善をもたらしますが、さらに考慮すべき点と高度なトピックがあります:

7.1 モデル量子化

特にメモリが限られているハードウェアでのサービングをさらに効率化するために、量子化テクニックを使用できます。vLLM自体は現在量子化をサポートしていませんが、量子化モデルと共に使用できます:

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch

# 量子化モデルをロード
model_name = "meta-llama/Llama-2-13b-hf"
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto", load_in_8bit=True)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# 量子化モデルをvLLMと共に使用
from vllm import LLM

llm = LLM(model=model, tokenizer=tokenizer)

7.2 分散推論

非常に大きなモデルまたは高トラフィックアプリケーションの場合、複数のGPUまたはマシンにわたる分散推論が必要になる場合があります。vLLMはこれをネイティブにサポートしていませんが、Rayなどのフレームワークを使用して分散システムに統合できます:

import ray
from vllm import LLM

@ray.remote(num_gpus=1)
class DistributedLLM:
def __init__(self, model_name):
self.llm = LLM(model=model_name)

def generate(self, prompt, params):
return self.llm.generate(prompt, params)

# 分散LLMの初期化
llm1 = DistributedLLM.remote("meta-llama/Llama-2-13b-hf")
llm2 = DistributedLLM.remote("meta-llama/Llama-2-13b-hf")

# それらを並行して使用
result1 = llm1.generate.remote("プロンプト1", sampling_params)
result2 = llm2.generate.remote("プロンプト2", sampling_params)

# 結果の取得
print(ray.get([result1, result2]))

7.3 モニタリングと観察可能性

LLMをプロダクションでサービングする場合、モニタリングは非常に重要です。vLLMには組み込みのモニタリング機能はありませんが、PrometheusやGrafanaなどのツールと統合できます:

from prometheus_client import start_http_server, Summary
from vllm import LLM

# メトリクスを定義
REQUEST_TIME = Summary('request_processing_seconds', 'リクエストの処理に費やされた時間')

# vLLMの初期化
llm = LLM(model="meta-llama/Llama-2-13b-hf")

# メトリクスを公開
start_http_server(8000)

# モニタリングを使用したモデル
@REQUEST_TIME.time()
def process_request(prompt):
return llm.generate(prompt)

# サービングループここに

このセットアップにより、リクエスト処理時間などのメトリクスを追跡でき、Grafanaのダッシュボードで視覚化できます。

結論

LLMを効率的にサービングすることは、AIの時代に重要な課題です。vLLMは、革新的なPagedAttentionアルゴリズムと最適化された実装を備え、LLMのデプロイをよりアクセスしやすくコスト効率の良いものにします。

スループットの向上、メモリ浪費の削減、サービングオプションの柔軟化により、vLLMは、チャットボット、コンテンツ生成システム、またはその他のNLP駆動型アプリケーションを構築する場合に、LLMを統合するための新しい可能性を開きます。vLLMのようなツールを理解して活用することは、これらのアプリケーションの成功の鍵となります。

私は過去5年間、機械学習とディープラーニングの魅力的世界に没頭してきました。私の情熱と専門知識は、AI/MLに特に焦点を当てた50以上の多様なソフトウェアエンジニアリングプロジェクトに貢献することにつながりました。私の継続的な好奇心は、私がさらに探究したい分野である自然言語処理にも私を引き寄せました。