vLLM で社内に LLM 推論サーバーを立てる:OpenAI 互換 API・量子化・複数 GPU

#vLLM#ローカルLLM#GPU#推論サーバー#OpenAI互換API

データセンターに並ぶサーバーラック
写真:Pexels

社内に言語モデルを置くなら、推論サーバーには vLLM を使い、OpenAI 互換の API として社内のアプリから呼ぶ形が、2026年10月時点で一番手間が少ない構成です。アプリ側は接続先の URL を変えるだけで、クラウドの API と社内のモデルを切り替えられます。この記事では、日本語を強化したオープンモデルを 1〜2 枚の GPU で動かし、外から守るところまでを組みます。どの大きさのモデルにどれだけのメモリが要るかは ローカルLLMは製造現場に置けるか に書いたので、ここでは手順に絞ります。

構成

社内アプリ(OpenAI SDK)
   │  HTTPS(社内 LAN)
   ▼
nginx(リバースプロキシ)  … 使うパスだけ通す・認証・流量制限・記録
   │  http://127.0.0.1:8000
   ▼
vLLM(vllm serve)         … OpenAI 互換 API、GPU 2 枚に分割
   │
   ▼
GPU × 2(例:24GB × 2)

vLLM は外に直接出しません。理由は最後の「守り方」で説明します。

1. 導入

2026年10月時点の最新は vLLM 0.31.0(10月5日公開)です。動くのは Linux だけで、Windows では WSL を使います。Python は 3.10〜3.13 に対応しています。

uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install vllm --torch-backend=auto

0.31.0 から、PyPI の配布物と Docker イメージの既定が CUDA 13.0 になりました。GPU のドライバが古くて CUDA 13 に対応していない場合は、CUDA 12.9 版の Docker イメージ(vllm/vllm-openai:v0.31.0-cu129)を使います。導入の文書にはまだ「CUDA 12.9 でビルド」と書かれている箇所があるので、リリースノートの表を正としてください。

Docker で動かす場合は、公式文書の例にならって次のようにします。モデルの保存先をホストと共有し、--ipc=host を付けます(複数 GPU に分割するときに共有メモリが要るため)。

docker run --runtime nvidia --gpus all \
    -v ~/.cache/huggingface:/root/.cache/huggingface \
    --env "HF_TOKEN=$HF_TOKEN" \
    -p 127.0.0.1:8000:8000 \
    --ipc=host \
    vllm/vllm-openai:v0.31.0 \
    --model tokyotech-llm/Qwen3-Swallow-32B-RL-v0.2-AWQ-INT4

文書の例では -p 8000:8000 と latest タグですが、ここでは外から直接つながらないよう 127.0.0.1 に限り、版も固定しています。

2. モデルを選んで起動する

例には、東京科学大学と産業技術総合研究所の Qwen3 Swallow の 32B を 4 ビット(AWQ)に量子化したものを使います。Apache 2.0 で、モデルカードに vLLM での起動方法が書かれています。

vllm serve tokyotech-llm/Qwen3-Swallow-32B-RL-v0.2-AWQ-INT4 \
    --reasoning-parser qwen3 \
    --tensor-parallel-size 2 \
    --max-model-len 32768 \
    --served-model-name swallow-32b \
    --host 127.0.0.1 --port 8000 \
    --api-key "$VLLM_API_KEY"

それぞれの意味は次のとおりです。

指定意味
--reasoning-parser qwen3思考型モデルの「考えた過程」を、回答の本文と分けて返す。モデルカードの指定どおり
--tensor-parallel-size 2モデルを 2 枚の GPU に分けて載せる。既定は 1
--max-model-len 327681 回に扱える長さ(入力と出力の合計)。モデルカードは 32,768 以下を勧めている
--served-model-nameAPI で指定するモデル名。省略すると Hugging Face のリポジトリ名になる
--api-key/v1 などの下のパスに鍵を求める(後述のとおり、これだけでは守れない)

このモデルのファイルは約 19GB(2026年10月時点の Hugging Face の表示)なので、重みだけなら 24GB の GPU 1 枚にも載りますが、長い文脈と同時処理の分のメモリ(KV キャッシュ)を考えると、24GB × 2 枚か 48GB 以上の 1 枚が現実的です。これはファイルの大きさからの見積もりで、モデルカードに書かれている数字ではありません。

GPU 同士が NVLink でつながっていない機材(例:L40S)では、vLLM の文書はテンソル並列ではなくパイプライン並列(--pipeline-parallel-size)を使うよう勧めています。

モデルを小さくしたい場合は、同じ Swallow の 8B(tokyotech-llm/Qwen3-Swallow-8B-RL-v0.2)があり、モデルカードは vLLM で主に検証したと書いています。

3. 社内アプリから呼ぶ

vLLM は OpenAI 互換の API(/v1/chat/completions、/v1/responses、/v1/models など)を出すので、OpenAI の Python SDK の接続先を変えるだけで呼べます。

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://llm.example.local/v1",  # nginx の URL
    api_key=os.environ["VLLM_API_KEY"],
)

res = client.chat.completions.create(
    model="swallow-32b",  # --served-model-name で付けた名前
    messages=[
        {"role": "system", "content": "あなたは製造現場の品質担当を手伝うアシスタントです。"},
        {"role": "user", "content": "外観検査で見つかったクラックの、よくある原因を3つ挙げてください。"},
    ],
)
print(res.choices[0].message.content)

思考型モデルを --reasoning-parser 付きで動かしていると、考えた過程は本文とは別の項目(モデルカードの例では message.reasoning)に入ります。画面に出すのは content だけにします。

考える過程そのものが不要なら、Qwen3 系のモデルでは、サーバー側でまとめて切ることもできます(文書の例)。

vllm serve Qwen/Qwen3-8B \
    --reasoning-parser qwen3 \
    --default-chat-template-kwargs '{"enable_thinking": false}'

なお、vLLM は既定で、モデルのリポジトリにある generation_config.json の生成設定(温度など)を使います。自分で決めたい場合は --generation-config vllm を付けます。

4. メモリの設定

うまく起動しない、途中で落ちる、というときに見る設定です。

設定既定(0.31.0)調整の考え方
--gpu-memory-utilization0.92vLLM がその GPU のメモリをどこまで使うか。同じ GPU で他のプロセスも動かすなら下げる
--max-model-lenモデルの設定から自動長いほど KV キャッシュが要る。業務で使う長さに絞るとメモリが空く
--max-num-seqs70GiB 未満の GPU では 256同時に処理する数の上限。下げるとメモリが空く
--enable-prefix-caching有効同じ前置き(システムプロンプトなど)の計算を使い回す
--enforce-eager無効CUDA グラフを使わない。メモリは空くが遅くなる

--gpu-memory-utilization の既定は、よく引用される 0.9 ではなく 0.92 です(0.31.0 のソースコードで確認)。

量子化の方式は、GPU の世代で使えるものが違います。vLLM の対応表では、AWQ は Turing から Hopper まで使えますが、8 ビット浮動小数点(FP8)の量子化済みモデルは Ada と Hopper の世代だけです。また 0.31.0 で、読み込み時に FP8 に量子化する指定が --quantization fp8 から --quantization fp8_per_tensor に変わりました。古い手順書をそのまま使うと動かないので注意してください。

5. 守り方:--api-key だけに頼らない

vLLM の文書は、--api-key が守るのは /v1、/v2、/inference、/cohere の下のパスだけで、同じサーバーの他の多くのパス(たとえば /invocations は /v1 と同じ推論を行う)は認証なしで開いている、と明記しています。そのうえで、--api-key だけに頼らず、使うパスだけを通すリバースプロキシの後ろに置くことを勧めています。

最小の nginx の設定は次のようになります(証明書や認証の方式は社内の決まりに合わせてください)。

limit_req_zone $binary_remote_addr zone=llm:10m rate=5r/s;

server {
    listen 443 ssl;
    server_name llm.example.local;
    ssl_certificate     /etc/nginx/certs/llm.crt;
    ssl_certificate_key /etc/nginx/certs/llm.key;

    location = /v1/chat/completions {
        limit_req zone=llm burst=10;
        proxy_pass http://127.0.0.1:8000;
        proxy_buffering off;        # ストリーミングの応答をためずに流す
        proxy_read_timeout 300s;    # 長い生成で切れないように
    }
    location = /v1/models {
        proxy_pass http://127.0.0.1:8000;
    }
    location / {
        return 404;                 # それ以外のパスは外に出さない
    }
}

ほかに、文書は次のことも挙げています。

  • 1 回のリクエストで生成する数(n)の上限を、環境変数 VLLM_MAX_N_SEQUENCES で小さくする(既定は 16384。文書の例は 64 や 128)
  • 本番で VLLM_SERVER_DEV_MODE=1 を設定しない
  • 複数台に分けて動かす場合、サーバー間の通信は既定で暗号化されないので、専用のネットワークに閉じる

社内に置くとデータは外に出ませんが、社内の誰でも何にでも使える状態にすると、機密の図面を誰が読ませたかが分からなくなります。nginx の記録と、どのアプリにどの鍵を渡したかの管理は、最初から用意しておくのが安全です。

出典

#vLLM#ローカルLLM#GPU#推論サーバー#OpenAI互換API

執筆weaveAI 編集部(株式会社サオス)

株式会社サオスで weaveAI を開発しているチームです。製造業の業務システムと AI について、一次情報と実際の検証をもとに解説しています。

運営者情報・編集方針 →

weaveAI FactorySuite

御社の工場で、動かしてみませんか。

マスタ・在庫・製造実績・品質・保全をすぐに使え、足りない画面は AI との対話で作れます。いまお使いの帳票(紙・Excel)があれば、どの画面に置き換わるかを一緒に確認します。