0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

12GBのGPU2枚で27BのLLMを24人同時に使う ― Ternary-Bonsai-2-27B を vLLM で動かす手順(WSL2)

0
Last updated at Posted at 2026-11-13

はじめに: こんなことができます

12GB の GPU 2枚(RTX 5070 × 2)を挿した Windows PC で、27B(270億パラメータ)の LLM を、社内の何人かで同時に使っています。

できること 数字(手元の実測)
1人で使ったときの生成速度 約90 トークン/秒(日本語で1秒に約100文字)
同時に使える人数 24本まで同時に生成(合計 約600 トークン/秒)
読める長さ 13万トークン(文庫本1冊くらい)
画像入力 できる(写真・スクリーンショットの説明、文字の書き起こし)
ツール呼び出し・思考 できる(OpenAI 互換 API)
混んでいるとき 対話の人を優先して、待ち時間 1秒以下

モデルは Prism ML の Ternary-Bonsai-2-27B です。重みが {-1, 0, +1} の3値(2bit)で、27B なのに重みが約10GB しかありません。

この記事は、同じ構成をまねするための手順と、なぜ速く・たくさん入るのかの解説です。改造は全部 GitHub に置きました。コマンドを3つ実行すれば動きます。

数字は全部、自分の環境での実測です。測っていないことは「未確認」と書きました。


1. 必要なもの

項目 内容
GPU VRAM 12GB 以上 × 2枚(私は RTX 5070。RTX 50 シリーズ以外は未確認)
OS Windows 11 + WSL2(Ubuntu)。ネイティブの Linux でも動くはずです(未確認)
メモリ 32GB 以上(私は 64GB を WSL に割り当て)
ディスク 空き 40GB 以上(SSD 推奨)
ドライバ Windows 側に、CUDA 13.0 対応の NVIDIA ドライバ
ツール git、patch、uv(Python の環境を作るツール)

WSL2 の Ubuntu で、足りないツールを入れておきます。

sudo apt install -y git patch
curl -LsSf https://astral.sh/uv/install.sh | sh

nvidia-smi を実行して、GPU が2枚見えていれば準備完了です。


2. 手順(3コマンド)

git clone https://github.com/Nakanokensetsu/bonsai-27b-wsl-tp2.git
cd bonsai-27b-wsl-tp2

./setup.sh                  # ① vLLM とパッチ、プラグインを入れる(15〜30分)
./model/prepare_model.sh    # ② モデルの重みを用意する(約10.5GB のダウンロード)
./serve.sh                  # ③ サーバーを起動する

それぞれの中身は次のとおりです。

  1. setup.sh: venv/ に vLLM 0.32.0 を入れて、私のパッチを当てます。続けて、2bit の重みを計算するプラグイン(私のフォーク)を入れます。
  2. prepare_model.sh: Hugging Face から vLLM 用の重みを落とします。画像を読むための部品(ビジョンタワー)と、日本語用の先読みヘッド(6章)を足して、単語の埋め込みを SSD 用に書き出します。
  3. serve.sh: 起動します。初回は GPU 用のプログラムのコンパイルがあるので、3〜5分かかります。

ログに次の行が出たら起動完了です。

INFO:     Application startup complete.

動いたか確かめる

別のターミナルで聞いてみます。

curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"bonsai-27b","messages":[{"role":"user","content":"17×23は?"}]}' \
  | python3 -m json.tool

391 が返ってくれば成功です。思考の中身は reasoning_content、答えは content に入ります。

チャット画面から使う

OpenAI 互換なので、Open WebUI などのチャット画面から、そのまま使えます。接続先の URL に http://<このPCのアドレス>:8000/v1 を、モデル名に bonsai-27b を入れてください。

他の PC から使うときは、HOST=0.0.0.0 ./serve.sh で起動します。WSL2 では、Windows 側のファイアウォールとポート転送の設定も必要です。

うまく動かないとき

症状 対処
起動中に out of memory / KV が足りない MEM=0.65 ./serve.sh のように下げる。画面表示に使っている GPU は、他のアプリ(ゲーム、3D ソフト)も VRAM を使います
遅い(1本で 60 tok/s を切る) 画面表示側の GPU で重いアプリが動いていないか確認。私の環境では、3D アプリが 1.5GB 使うだけで約30%遅くなりました
数学など難しい問題で間違える 要求に "reasoning_effort": "xhigh" を付けると、よく考えるようになります(時間は約2倍)

3. 混んでいるときに、対話の人を先にする

何人かで使うと、長く考える要求(エージェントやまとめ処理)が枠を埋めて、後から来た短い質問が待たされます。私の環境では 30〜58秒 待たされていました。

要求の JSON に priority を入れると、小さい数字の要求が先になります。

{"model": "bonsai-27b", "priority": 0, "messages": [...]}

素の vLLM では、すでに動いている要求は追い出されないので、枠が埋まっていると待つだけです。私のパッチでは、待っている要求のほうが優先度が高ければ、動いている一番低い要求を1本ずつ追い出します。

条件 対話(priority 0)の待ち時間
追い出しなし(16本が動いている) 30〜58 秒
追い出しあり 0.2〜0.3 秒

追い出された要求は、後で続きから再開します。背景の20本も、全部最後まで正常に終わりました。

私は手前に小さなプロキシを置いて、送信元ごとに priority を付けています(チャット画面 0、エージェント 5、測定 10)。


4. 解説: なぜ 12GB × 2 で 24本も入るのか

ここからは、前回の記事(2026-10-29)から2週間でやった改善の解説です。

4.1 そもそも何が足りないのか

LLM が文章を作るときは、**それまでの文章を覚えておくメモ(KV キャッシュ)**を GPU に置きます。長い文章や同時に何本も処理するほど、このメモが大きくなります。

12GB × 2 では、重みを置いた残りが KV キャッシュの場所です。ここに何トークン分入るか(KV プール)で、同時に何本・どれだけ長く扱えるかが決まります。

時期 KV プール(同時16本の設定)
前回(10/29) 282,089 トークン
今回 424,423 トークン(1.50倍)

増やした方法は2つです。

4.2 KV キャッシュを小さく書く(OSCAR)

KV キャッシュは、ふつうは16bit の数字で持ちます。これを K は3bit、V は2bit まで縮めました。そのまま縮めると精度が落ちるので、次の工夫をしています。

  • 縮める前に、ベクトルを回転して値の偏りをならす(外れ値が減って、少ないビットでも表しやすくなる)
  • 直近のトークンだけは16bit のまま持つ(直近の文脈ほど大事なため)

考え方は vLLM の未マージ PR #46774(OSCAR)のものです。この PR は CUDA グラフ・投機デコード・このモデルの形(head_dim=256、GDN との混成)に対応していなかったので、そこを作り足しました。

形式 1トークン・1ヘッドのバイト数 KV プール 数学(AIME)の正答率
TurboQuant 4bit(前回) 262 282,089 82.5%
OSCAR K2V2 136 458,752 76%
OSCAR K3V2(採用) 168 378,652 83.3%

最初はいちばん小さい K2V2 を使っていました。ふつうの質問や長文の検索テスト(ニードル)では差が見えなかったのですが、数学の問題で初めて差が出ました。K のビット数が効くことが分かったので、K3V2 にしています。

教訓: 量子化の比較は、ふつうの質問だけでなく、数学のような厳しいテストでもやる。

4.3 単語の埋め込みを SSD に置く

モデルの重みの内訳を調べたら、単語の埋め込み表(単語 → ベクトルの対応表)だけで 2.37GiB ありました。本体の層(5.66GiB)の4割にもなります。

埋め込み表は「引くだけの表」で、計算には使いません。しかも、実際の利用ログ(5,456万トークン)を数えると、よく使う上位 32,000 語で 99.86% をカバーしていました(語彙は全部で約25万語)。

そこで、次のようにしました。

  • 全部の表は SSD 上のファイルに置く(OS のキャッシュ越しに読む)
  • GPU には上位 32,000 語の分だけ置く(2.37 → 0.31GiB)
  • 表に無い語が来たら、CPU がファイルから読んで差し込む
項目 前 後
重み(GPU 1枚あたり) 5.24 GiB 4.36 GiB
KV プール 458,752 627,633(1.37倍)
生成速度(中央値) 79.3 tok/s 87.7 tok/s

速度は落ちていません。本体の計算は正確なので、出力も変わりません。

先読み(MTP)の部分だけは GPU の表しか見ませんが、先読みの結果は本体が必ず確かめるので、出力には影響しません。

増えた KV プールは、同時処理の本数(16 → 24)に回しました。


5. 解説: 長文を読む速度(prefill)

int8 AllReduce を Triton で書き直して採用

GPU 2枚で1つのモデルを動かすと、層ごとに GPU 同士で計算結果を足し合わせる通信(AllReduce)が入ります。WSL2 では GPU 同士の直接通信が使えないので、これが遅いのが前回からの課題でした。

前回は、この通信を int8 に縮めて半分にする案を試しましたが、縮める・戻す処理(PyTorch で書いた)が遅くて効果が出ず、見送りました。

今回は、縮める・戻す処理を Triton(GPU 用のプログラムを Python で書ける仕組み)の1カーネルずつに書き直しました。

処理 PyTorch 版 Triton 版
縮める(1584行) 0.47 ms 0.018 ms
戻して足す 0.57 ms 0.018 ms
AllReduce 全体(16bit 比) 1.12〜1.28 倍速 1.61〜1.70 倍速
長文を読み終わるまでの時間 前 後
8,000 トークン 5.3 秒 3.9 秒
32,000 トークン 22.4 秒 16.9 秒
100,000 トークン 81.3 秒 64.8 秒

長文を読むとき(prefill)だけに使うので、文章を書く速度(decode)と品質は変わりませんでした。


6. 解説: 先読み(MTP)を日本語に合わせる

このモデルは、次の1トークンを先に予想する小さな頭(MTP ヘッド)を持っています。予想が当たれば、1回の計算で2トークン進めます。

元の MTP ヘッドは英語中心のデータで作られています。私の使い方はほとんど日本語なので、このモデル自身が書いた日本語の文章で、MTP ヘッドだけを学習し直しました(GPU 2枚で約10分)。

項目 元のヘッド 学習し直したヘッド
予想が当たる率 65.9% 70.0%
生成速度(平均) 90.8 tok/s 93.6 tok/s

学習し直したヘッドは Hugging Face に置きました。prepare_model.sh が自動で落として差し替えます(JA_MTP=0 で元のヘッドのまま)。

罠: 学習データを作るとき、vLLM の extract_hidden_states の層 ID は「その層への入力」です。最終層の出力が欲しければ、63 ではなく 64 を指定します。


7. 試したが採用しなかったこと

案 結果
vLLM 0.32 の NVFP4 KV KV プールが20%減る
OSCAR K4V2(K を4bit に) 数学 80.0%、プールも小さい(K3V2 に劣る)
別の人が配布している MTP ヘッド 当たる率が上がらず
mamba 状態を32bit に 品質の差が見えず、プールが16.5%減る
vLLM 0.33 への更新 効く変更が少なく、パッチ移植の手間に見合わない(未実施)

8. つまずきやすい点(私が実際にはまったこと)

  • 画面表示と兼用の GPU: 他のアプリが VRAM を使うと、速度が約30%落ちたり、起動に失敗したりします。WSL2 では VRAM が足りなくても、数字にほとんど出ません。
  • xxhash の入れ忘れ: 起動に失敗して、再起動を繰り返しました(setup.sh では入れています)。
  • 優先度の追い出しが連鎖: 追い出した要求が空いた枠をすぐ取り直して、追い出しが300回連鎖しました。パッチでは修正済みです。
  • 数学の正答率が低く見える: チャットテンプレートの「どれだけ考えるか」の既定が medium だと、モデルカードの値より低く出ます。"reasoning_effort": "xhigh" で 93.3% でした。

まとめ

  • 12GB × 2 の GPU で、27B の LLM を 24本同時・13万トークン・画像入力付きで動かせました。
  • KV キャッシュの縮小(OSCAR K3V2)と埋め込みの SSD 化で、KV プールは 1.5倍になりました。
  • int8 AllReduce で、長文の読み込みが 20〜25% 速くなりました。
  • 優先度による追い出しで、混雑時の対話の待ち時間が数十秒から1秒以下になりました。
  • 手順と改造は全部 GitHub にあります。
付録: serve.sh の主な設定(本番と同じ)
環境変数 既定 内容
KV turboquant_k3v2_oscar KV キャッシュの形式
MEM 0.72 GPU メモリのうち vLLM が使う割合
SEQS 24 同時に処理する本数
PRISM_AR_INT8 1 prefill の AllReduce を int8 で
VLLM_PRIO_PREEMPT 1 優先度による追い出し
VLLM_SSD_EMBED_DIR ./embed_ssd 埋め込みを SSD に(空で無効)
KV_OFFLOAD 1 追い出された KV を RAM → SSD に退避
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?