Zenn
Closed6

LLM高速推論ライブラリ「vLLM」を試す

kun432kun432

GitHubレポジトリ

https://github.com/vllm-project/vllm

概要

vLLMは、LLMの推論およびサービングのための、高速で使いやすいライブラリです。

vLLMは、もともとUC BerkeleyのSky Computing Labで開発され、その後、学術機関と産業界の貢献によって、コミュニティ主導のプロジェクトへと発展しました。

vLLMは以下の点で高速です:

  • 最先端のサービングスループット
  • PagedAttentionを活用した効率的なアテンションキー・バリューメモリ管理
  • 連続的なバッチ処理
  • CUDA/HIPグラフによる高速なモデル実行
  • 量子化技術のサポート:GPTQAWQ、INT4、INT8、FP8
  • FlashAttentionおよびFlashInferと統合された最適化CUDAカーネル
  • 投機的デコーディング(Speculative Decoding)
  • チャンク化されたプリフィル

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

こちらのブログ記事の最後に、vLLMのパフォーマンスベンチマークを掲載しています。このベンチマークでは、vLLMと他のLLMサービングエンジン(TensorRT-LLM, SGLang, LMDeploy)の比較を行っています。

実装はnightly-benchmarksフォルダにあり、こちらのワンクリックスクリプトを使って再現できます。


vLLMは、柔軟で使いやすい設計になっています:

  • Hugging Faceの人気モデルとシームレスに統合
  • 並列サンプリング、ビームサーチなどの多様なデコーディングアルゴリズムによる高スループットサービング
  • 分散推論のためのテンソル並列処理およびパイプライン並列処理のサポート
  • ストリーミング出力対応
  • OpenAI互換のAPIサーバー
  • NVIDIA GPU、AMD CPU/GPU、Intel CPU/GPU、PowerPC CPU、TPU、AWS Neuronのサポート
  • プレフィックスキャッシングのサポート
  • Multi-LoRAのサポート

vLLMは、Hugging Face上の主要なオープンソースモデルを幅広くサポートしています:

  • Transformer系LLM(例:Llama)
  • Mixture-of-Expert LLM(例:Mixtral、Deepseek-V2、Deepseek-V3)
  • 埋め込みモデル(例:E5-Mistral)
  • マルチモーダルLLM(例:LLaVA)

対応モデルの全リストはこちらで確認できます。

公式ドキュメントはこちら

https://docs.vllm.ai/en/stable/

kun432kun432

インストール

https://docs.vllm.ai/en/stable/getting_started/installation/index.html

以下の環境で試す

  • Ubuntu-22.04
  • RTX4090(24GB)
  • Python-3.12.9
  • CUDA-12.6

ということでGPUの場合の手順を参考に。

https://docs.vllm.ai/en/stable/getting_started/installation/gpu/index.html?device=cuda#

作業ディレクトリ作成

mkdir vllm-work && cd vllm-work

Python仮想環境作成。最近はuvを使っている。

uv venv --python 3.12

パッケージインストール

uv pip install vllm
出力
Installed 133 packages in 1.05s
(snip)
 + vllm==0.7.1
(snip)
kun432kun432

Quickstart

https://docs.vllm.ai/en/stable/getting_started/quickstart.html

vLLMを使った推論は以下の2つの方法がある

  • オフラインバッチ推論
  • OpenAI互換サーバを使用したオンライン推論

オフラインバッチ推論

サンプルコードが以下に用意されている。

https://github.com/vllm-project/vllm/blob/main/examples/offline_inference/basic.py

以下のモデルを使用した。

https://huggingface.co/google/gemma-2-2b-it

offline_inference.py
from vllm import LLM, SamplingParams

# サンプルプロンプト
prompts = [
    "こんにちは!私の名前は、",
    "日本の総理大臣は、",
    "日本の首都は、",
    "AIの未来は、",
]

# サンプリングパラメータを定義
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)

# LLMを定義
llm = LLM(model="google/gemma-2-2b-it")

# プロンプトからテキストを生成します。出力は、プロンプト、生成されたテキスト、
# その他の情報を含むRequestOutputオブジェクトのリスト。
outputs = llm.generate(prompts, sampling_params)

# 出力
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"プロンプト: {prompt!r}, 生成されたテキスト: {generated_text!r}")

実行

uv run offline_inference.py

結果

出力
プロンプト: 'こんにちは!私の名前は、', 生成されたテキスト: ' 〇〇です。\n今日は、〇〇についてお話します。\n\nこの文は'
プロンプト: '日本の総理大臣は、', 生成されたテキスト: '誰が就任しましたか?\n\n**答え:** Fumio Kishida\n\n**'
プロンプト: '日本の首都は、', 生成されたテキスト: '\na) 上海\nb) 東京\nc) 北京\nd) 香港'
プロンプト: 'AIの未来は、', 生成されたテキスト: '私たちの想像を超える可能性を秘めている。その可能性を実現するために、私たちはAI'

基本は以下のような流れみたい

  • LLMクラスでLLMを定義
  • SamplingParamsでサンプリングパラメータを定義
  • LLM.generateにプロンプト、サンプリングパラメータを渡して、生成
  • 結果がRequestOutputのリストで返される

OpenAI互換サーバを使用したオンライン推論

vLLMはOpenAI互換APIサーバも提供している。vllmコマンドを使う。

Usageをまず見てみる。

uv run vllm --help
出力
usage: vllm [-h] [-v] {serve,complete,chat} ...

vLLM CLI

positional arguments:
  {serve,complete,chat}
    serve               Start the vLLM OpenAI Compatible API server
    complete            Generate text completions based on the given prompt via the running API server
    chat                Generate chat completions via the running API server

options:
  -h, --help            show this help message and exit
  -v, --version         show program's version number and exit

serveサブコマンドでOpenAI互換APIサーバを起動することができる。余談だが、コマンドラインクライアント的にも使えるように見える。

serveのUsageもみてみるが、めちゃめちゃ長いので一部だけ。

出力
usage: vllm serve <model_tag> [options]

positional arguments:
  model_tag             The model tag to serve

options:
(snip)
  --host HOST           Host name.
(snip)
  --port PORT           Port number.
(snip)

デフォルトではhttp://localhost:8000で待ち受けるようなので、必要ならばオプションを指定する。

では起動。自分はLAN内のリモートサーバなので--host--portオプションを指定した。

uv run vllm serve google/gemma-2-2b-it \
    --host 0.0.0.0 \
    --port 8888

起動した。以下のようなエンドポイントがある様子。

出力
(snip)
INFO 02-06 22:58:49 launcher.py:19] Available routes are:
INFO 02-06 22:58:49 launcher.py:27] Route: /openapi.json, Methods: GET, HEAD
INFO 02-06 22:58:49 launcher.py:27] Route: /docs, Methods: GET, HEAD
INFO 02-06 22:58:49 launcher.py:27] Route: /docs/oauth2-redirect, Methods: GET, HEAD
INFO 02-06 22:58:49 launcher.py:27] Route: /redoc, Methods: GET, HEAD
INFO 02-06 22:58:49 launcher.py:27] Route: /health, Methods: GET
INFO 02-06 22:58:49 launcher.py:27] Route: /ping, Methods: GET, POST
INFO 02-06 22:58:49 launcher.py:27] Route: /tokenize, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /detokenize, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /v1/models, Methods: GET
INFO 02-06 22:58:49 launcher.py:27] Route: /version, Methods: GET
INFO 02-06 22:58:49 launcher.py:27] Route: /v1/chat/completions, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /v1/completions, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /v1/embeddings, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /pooling, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /score, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /v1/score, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /rerank, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /v1/rerank, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /v2/rerank, Methods: POST
INFO 02-06 22:58:49 launcher.py:27] Route: /invocations, Methods: POST
INFO:     Started server process [1937112]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8888 (Press CTRL+C to quit)

では別のマシンからcurlで叩いてみる。

curl http://[サーバのIP]:8888/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "google/gemma-2-2b-it",
        "prompt": "競馬の魅力を5つリストアップするならば、",
        "max_tokens": 1024,
        "temperature": 0
    }' | jq -r .
出力
{
  "id": "cmpl-7dd42f61d4f74b76b2397e7b60ce0287",
  "object": "text_completion",
  "created": 1738850462,
  "model": "google/gemma-2-2b-it",
  "choices": [
    {
      "index": 0,
      "text": "\n\n1. **競争の激しさ**:  馬と騎手の連携、そしてレース展開の予測性。\n2. **予想の面白さ**:  馬の能力、騎手の戦略、そして運の要素。\n3. **歴史と伝統**:  長い歴史の中で培われた文化、そして伝統的なレース。\n4. **人とのつながり**:  競馬場での交流、そしてレースを通して生まれる友情。\n5. **興奮と感動**:  レースの展開、そして勝利の喜び。\n\nこれらの要素が組み合わさって、競馬の魅力を最大限に引き出す。\n\n**補足**: \n\n* 競馬は、単に馬と競争するスポーツではなく、戦略、分析、そして運の要素が複雑に絡み合っています。\n* 競馬は、人々の心を揺さぶるスポーツであり、その魅力は人それぞれに感じられるでしょう。 \n\n\n",
      "logprobs": null,
      "finish_reason": "stop",
      "stop_reason": 107,
      "prompt_logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 11,
    "total_tokens": 203,
    "completion_tokens": 192,
    "prompt_tokens_details": null
  }
}

OpenAI SDKでも試してみる。こちらはChat Completions APIで。

from openai import OpenAI

client = OpenAI(
    api_key="dummy",
    base_url="http://[サーバのIP]:8888/v1"
)

stream = client.chat.completions.create(
    model="google/gemma-2-2b-it",
    messages=[
        {
            "role": "user",
            "content": "競馬の魅力を5つリストアップして。",
        }
    ],
    stream=True,
)

for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")
出力
 競馬の魅力を5つリストアップします。

1. **予測不可能で、スリリングな展開:**  競馬はまさに「誰が勝つのか」という自然の物語です。予想通りの展開ではなく、時には予想外のレース展開が魅力的で興奮を誘います。
2. **賭け方次第で、経済的な利益を得る可能性:** 競馬は賭博の一種です。しかし、知識・分析、そして戦略的な賭け方次第で、成功の可能性もあります。
3. **歴史と伝統:** 競馬は長い歴史と伝統を誇り、その伝統的な格式感はただレースだけを楽しむだけでなく、それを楽しむ文化を形作っています。
4. **憧憬と夢:** 競馬では、「夢を叶える」という願望や「成功と栄光」を追求する人々が集い、その熱狂的な雰囲気は競馬場の雰囲気を盛り上げる大きな要素です。
5. **静かな夜と、興奮の瞬間:** 競馬場では、静かな夜を過ごす人もいれば、興奮した瞬間を味わう人もいます。競馬場には、様々な人の集いがあり、それぞれの魅力が楽しめます。



 競馬は、単なるスポーツだけではなく、歴史、文化、経済、そして人の夢や感情と深く結びついています。
その全てが、競馬の魅力を生み出しています。

なお、1サーバでロードできるのは1モデルだけみたい。

kun432kun432

量子化

https://docs.vllm.ai/en/stable/features/quantization/index.html

vLLMは量子化もサポートしている。ドキュメントに記載があるのは以下。

  • AutoAWQ
  • BitsAndBytes
  • GGUF
  • INT4 W4A16
  • INT8 W8A8
  • FP8 W8A8
  • Quantized KV Cache

BitsAndBytesを使って4ビット量子化してみる。

パッケージインストール

uv pip install bitsandbytes>=0.45.0
出力
Installed 1 package in 107ms
 + bitsandbytes==0.45.1

上のオフライン推論のスクリプトを修正してみる。

from vllm import LLM, SamplingParams
import torch

(snip)

llm = LLM(
    model="google/gemma-2-2b-it",
    dtype=torch.bfloat16,
    quantization="bitsandbytes",
    load_format="bitsandbytes",
)

(snip)

実行してみると以下のように出力される。

出力
(snip)
INFO 02-06 23:21:34 loader.py:1078] Loading weights with BitsAndBytes quantization.  May take a while ...
(snip)
INFO 02-06 23:21:37 worker.py:266] model weights take 2.09GiB; non_torch_memory takes 0.08GiB; PyTorch activation peak memory takes 2.36GiB; the rest of the memory reserved for KV Cache is 16.63GiB.
(snip)

ちなみに量子化なしの場合は以下のように表示されていたので、一応効いていると考えてもいいのかな。

出力
INFO 02-06 22:42:27 worker.py:266] model weights take 4.90GiB; non_torch_memory takes 0.08GiB; PyTorch activation peak memory takes 2.36GiB; the rest of the memory reserved for KV Cache is 13.82GiB.

OpenAI互換APIサーバの場合。以下のようにオプションを追加する。

uv run vllm serve google/gemma-2-2b-it \
    --host 0.0.0.0 \
    --port 8888 \
    --quantization bitsandbytes \
    --load-format bitsandbytes

量子化してもVRAMはあるだけ使う(KVキャッシュのため)ってことなのかな?オプションあり/なし、それぞれのnvidia-smiを見ても違いはほとんどないように見える。

出力
Thu Feb  6 23:32:17 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 560.35.03              Driver Version: 560.35.03      CUDA Version: 12.6     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |           Memory-Usage | GPU-Util  Compute M. |
|                                         |                        |               MIG M. |
|=========================================+========================+======================|
|   0  NVIDIA GeForce RTX 4090        Off |   00000000:01:00.0 Off |                  Off |
|  0%   46C    P8             17W /  450W |   20495MiB /  24564MiB |      0%      Default |
|                                         |                        |                  N/A |
+-----------------------------------------+------------------------+----------------------+

+-----------------------------------------------------------------------------------------+
| Processes:                                                                              |
|  GPU   GI   CI        PID   Type   Process name                              GPU Memory |
|        ID   ID                                                               Usage      |
|=========================================================================================|
|    0   N/A  N/A   1644271      G   /usr/lib/xorg/Xorg                            167MiB |
|    0   N/A  N/A   1644372      G   /usr/bin/gnome-shell                           15MiB |
|    0   N/A  N/A   1942547      C   ...ository/vllm-work/.venv/bin/python3      20276MiB |
+-----------------------------------------------------------------------------------------+
kun432kun432

まとめ

とりあえずサラッと触りだけやってみた。高速なのかどうか?はこれぐらいじゃわからないけれども、以下を見る限りは高速みたい。

https://qiita.com/xxyc/items/fa6188b89fa9533dc674

ドキュメントはめちゃめちゃ豊富、というかめちゃめちゃ多いので、一通り機能的なところをカバーしてみよう、みたいなのは諦めた。具体的にこういうことがやりたい、とか、こういうときはどうすれば?みたいな感じなら、だいたい書いてありそうな雰囲気を感じた。

というわけでこっちを眺めてみようかと思う。

https://github.com/vllm-project/production-stack

このスクラップは2ヶ月前にクローズされました
ログインするとコメントできます