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?

RunPod Serverless で 27B GGUF の LLM を動かしたときのハマりどころ(llama.cpp・256K コンテキスト・コールドスタート)

0
Posted at

はじめに

OpenAI 互換の LLM API ゲートウェイ Apixo を個人開発しています(OpenRouter のように、統一 API・トークン課金・複数モデルのバックエンド)。上流 API との接続に加えて、Hugging Face のオープンウェイトモデルを自前でホストしようとして、RunPod Serverless でいくつもハマりました。同じことをやろうとしている方の参考になれば幸いです。

要件

  1. 使った分だけ課金・アイドル時はゼロ円(常時稼働の Pod は RTX PRO 6000 で約 $3.49/時間なので NG)
  2. 長いコンテキスト:256K(262144)トークン
  3. 標準の OpenAI /v1 エンドポイント:OpenCode や OpenAI SDK からそのまま使いたい

モデルは Qwen3.8-27B の GGUF(Q5 量子化)、プラットフォームは RunPod Serverless です。

ハマりどころ 1:GGUF に vLLM Worker は使えない

RunPod のおすすめは vLLM Worker ですが、最初に FP8 モデルを vLLM で動かしたところ、デプロイは成功するのにクライアントに応答が返ってきませんでした。GGUF に切り替えて気づいたのは:

  • GGUF は llama.cpp で動かすべき
  • 公式イメージ ghcr.io/ggml-org/llama.cpp:server-cuda がそのまま使える

起動コマンド例(リポジトリ名は自分のものに置き換えてください):

llama-server \
  --host 0.0.0.0 --port 8080 \
  -hf <hfユーザー名>/<リポジトリ名>:<量子化名> \
  -c 262144 \
  -ngl 99 \
  --flash-attn on \
  --cache-type-k q8_0 --cache-type-v q8_0
  • -hf は指定した量子化ファイル 1 つだけをダウンロードします(ハマりどころ 3 で重要)
  • KV キャッシュを q8_0 にすると 256K コンテキストの VRAM 使用量を大きく削減できます。V キャッシュの量子化には flash attention が必要です(オプション表記はバージョンにより異なるので llama-server --help で確認してください)
  • ポートは RunPod エンドポイントの設定と合わせる

実測では、llama.cpp でこのモデルは問題なくロードでき、Blackwell 世代の RTX PRO 6000(96GB)で 262144 コンテキスト + q8_0 KV キャッシュの確保にも成功しました。

ハマりどころ 2:Queue 型エンドポイントには OpenAI /v1 がない

種類 呼び出し方 OpenAI API としてそのまま使えるか
Queue 型 /run、/runsync にジョブ投入 ❌ 自前でラップが必要
Load Balancer 型 HTTP をコンテナにそのまま転送 ✅ /v1/chat/completions に直接アクセス可

OpenCode や OpenAI SDK から直接つなぐなら Load Balancer 型一択 です。

from openai import OpenAI

client = OpenAI(
    base_url="https://<エンドポイントID>.api.runpod.ai/v1",
    api_key="<RUNPOD_API_KEY>",
)

resp = client.chat.completions.create(
    model="local",
    messages=[{"role": "user", "content": "こんにちは"}],
)
print(resp.choices[0].message.content)

ハマりどころ 3:「Cached model」に複数量子化の GGUF リポジトリを入れると永遠に initializing

RunPod には Hugging Face モデルを事前キャッシュする「Cached model」欄があります。ここに GGUF リポジトリを指定したところ:

  • Worker がずっと initializing のまま
  • コンテナログがまったく出ない
  • ゲートウェイは 403 / 500 を返す

原因は、GGUF リポジトリには十数種類の量子化ファイルが入っており、RunPod が リポジトリ全体をキャッシュしようとする こと(私の場合は約 180GB)。終わるはずがありません。

対策:Cached model 欄を空にして、llama.cpp の -hf で必要な量子化だけを実行時に取得する。空にした瞬間にエンドポイントが Ready になりました。

診断のコツ
Worker が initializing のまま、ログがゼロ、課金も $0.00 → コンテナがそもそも起動していない(起動前に詰まっている)。
クラッシュループなら必ずログが出て、課金時間も発生します。

ハマりどころ 4(根本原因):ヘルスチェックの仕様 × 2 分を超えるコールドスタート

ここまで直しても timed out waiting for worker が出続けました。原因は 2 つの仕様の組み合わせです。

1. RunPod Load Balancer のヘルスチェック

/health のステータス RunPod の解釈
200 正常、トラフィックを受ける
204 初期化中
それ以外(503 など) 異常、ルーティングから外す

llama-server はモデルのロード中、/health に 503 を返します。つまりロード中ずっと「壊れた Worker」と見なされます。

2. 待ち時間の上限は約 2 分、コールドスタートは約 5 分

  • そのうち約 97 秒 は 256K コンテキスト用の KV キャッシュ確保で、キャッシュでは短縮できない
  • イメージのエントリポイントが llama-server バイナリそのものなので、204 を返すサイドカーも置けない

結論:コールドスタートを 2 分未満にするか、「ウォームアップしてから使う」しかない。

打ち手:

  1. コンテキスト長を下げる(最大のレバー)。全タスクに 256K は不要
  2. ウォームアップスクリプト:200 が返るまでダミーリクエストを投げ続け、アイドルタイムアウト(私は 300 秒)内に作業する
  3. Network Volume でのモデルキャッシュ:私の DC(US-PA-1)では使えず、RTX PRO 6000 がある対応リージョンも在庫が少ないため断念
import time, requests

URL = "https://<エンドポイントID>.api.runpod.ai/v1/chat/completions"
HEADERS = {"Authorization": "Bearer <RUNPOD_API_KEY>"}
PAYLOAD = {"model": "local", "messages": [{"role": "user", "content": "ping"}], "max_tokens": 1}

start = time.time()
while True:
    try:
        r = requests.post(URL, json=PAYLOAD, headers=HEADERS, timeout=150)
        if r.status_code == 200:
            print(f"ウォームアップ完了:{time.time() - start:.0f} 秒")
            break
        print("ステータス", r.status_code, "待機中…")
    except requests.RequestException as e:
        print("リクエスト例外:", e)
    time.sleep(10)

最終構成とコスト

  • エンドポイント種別:Load Balancer
  • イメージ:ghcr.io/ggml-org/llama.cpp:server-cuda
  • GPU 優先順位:96GB PRO(RTX PRO 6000)→ 80GB PRO(H100)
  • Worker:常駐 0 / 最大 1、アイドルタイムアウト 300 秒
  • Network Volume なし、Cached model なし

初日のデバッグで約 $1.10 使いましたが、すべて「起動したのに 1 リクエストも処理できなかった」Worker の分でした。

まとめ

  1. GGUF → llama.cpp。vLLM Worker は使わない
  2. OpenAI API が欲しいなら Load Balancer 型エンドポイント
  3. 複数量子化の GGUF リポジトリを Cached model に入れない
  4. ログゼロ + 課金ゼロ = コンテナ未起動
  5. llama-server はロード中 /health が 503。コールドスタートは 2 分未満にするか、ウォームアップ前提で
  6. コールドスタート時間の最大要因は長いコンテキスト。必要な分だけ設定する

この知見は、OpenAI 互換の LLM API ゲートウェイ Apixo を作る中での自前ホストの試行から得たものです。同じように Serverless で LLM を動かしている方、ぜひコメントで情報交換しましょう。

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?