はじめに
事の発端は、X(旧Twitter)で見かけたこちらのポストでした。
https://x.com/millionbiz_/status/1930298478347071975
AnthropicのIDE拡張である Claude Code は非常に便利ですが、当然出費がかさみます。
そんな中、Claude Codeのリクエストを他のLLMプロバイダーに中継できるという夢のようなライブラリ、claude-bridge を発見。早速GPUサーバー上にローカルLLM環境を構築し始めました。
-
claude-bridgeGitHubリポジトリ: https://github.com/badlogic/lemmy/tree/main/apps/claude-bridge
本記事では、その claude-bridge を使うという目的のために、まず高性能な推論エンジン vLLM のセットアップに挑戦し、数々のエラーと格闘した末、最終的に Ollama にたどり着くまでの試行錯誤の記録をまとめます。相棒であるgemini 2.5 proと二人三脚で構築しましたが、案外時間がかかってしまいました。
claude-bridge とローカルLLMの連携でハマっている方や、これからGPUサーバーで環境構築をしようとしている方の助けになれば幸いです。
本記事の登場人物:
- vLLM: 高速なLLM推論とサービングのためのライブラリ
- Ollama: ローカルでLLMを簡単に実行するためのプラットフォーム
- uv: Rust製の超高速なPythonパッケージインストーラ
- Docker: コンテナ型仮想化ツール
- claude-bridge: 手元のマシンからLLMを利用するためのCLIツール
フェーズ1: vLLM + uv + Docker 環境の構築
最初の目標は、高速なvLLMを、モダンなuvを使い、Dockerコンテナ内で動かすことでした。サーバー上にuvをインストールしたくなかったのでDocker on uvの構成にしました。
初めてuvを使ったのですが、確かにインストールが速かった...かも
Dockerfileの作成とビルド
まずは以下のようなDockerfileを作成しました。
# ベースイメージを選択
FROM nvidia/cuda:12.1.1-cudnn8-devel-ubuntu22.04
# 環境変数
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
ENV PYTHON_VERSION=3.10
ENV PATH="/root/.local/bin:/opt/venv/bin:${PATH}" # uv と venv のパス
ENV VENV_PATH="/opt/venv"
# 必要なパッケージのインストール
RUN apt-get update && \
apt-get install -y --no-install-recommends \
python${PYTHON_VERSION} \
python${PYTHON_VERSION}-dev \ # 最終的に必要になった開発用ヘッダ
python${PYTHON_VERSION}-venv \
python3-pip \
curl \
git \
build-essential \ # 念のため
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Pythonのデフォルトを更新
RUN update-alternatives --install /usr/bin/python python /usr/bin/python${PYTHON_VERSION} 1 && \
update-alternatives --set python /usr/bin/python${PYTHON_VERSION}
# uv のインストール
RUN curl -LsSf [https://astral.sh/uv/install.sh](https://astral.sh/uv/install.sh) | sh
# 仮想環境の作成
RUN uv venv ${VENV_PATH} --python $(which python)
# PyTorch と vLLM のインストール
RUN export VIRTUAL_ENV=${VENV_PATH} && \
uv pip install --no-cache-dir torch torchvision torchaudio --index-url [https://download.pytorch.org/whl/cu121](https://download.pytorch.org/whl/cu121) && \
uv pip install --no-cache-dir vllm
# 作業ディレクトリの設定とポート開放
WORKDIR /app
EXPOSE 8000
# デフォルトコマンド
CMD ["python", "-m", "vllm.entrypoints.openai.api_server", \
"--model", "mistralai/Mistral-7B-Instruct-v0.1", \
"--host", "0.0.0.0", \
"--port", "8000" \
]
しかし、このDockerfileが完成するまでには、数々のエラーとの戦いがありました。
エラー1: uv: not found
ビルドの途中で、uv コマンドが見つからないというエラーに遭遇。
-
原因:
uvのインストール後、そのパスが後続のRUN命令の実行コンテキストに引き継がれていなかった。 -
解決策:
uvのインストール先である/root/.local/bin/を明示的に指定することで解決。最終的には、# RUN uv venv ... を以下のように修正 RUN /root/.local/bin/uv venv ${VENV_PATH} --python $(which python)ENV PATH="/root/.local/bin:${PATH}"の設定でこの問題は解消されました。
エラー2: /opt/venv/bin/uv: not found
仮想環境作成後、仮想環境内の uv を使ってパッケージをインストールしようとして失敗。
-
原因:
uv venvで仮想環境を作成しても、uvコマンド自体は仮想環境内にはコピーされない。グローバルにインストールされたuvを使う必要があった。 -
解決策:
VIRTUAL_ENV環境変数を設定し、グローバルなuv(/root/.local/bin/uv) を使うことで、指定した仮想環境にパッケージをインストールするように修正。RUN export VIRTUAL_ENV=${VENV_PATH} && \ /root/.local/bin/uv pip install ...
エラー3: fatal error: Python.h: No such file or directory
70Bモデルのロード中に、vLLM (内部的にTriton) がC拡張をコンパイルしようとして失敗。
-
原因: C拡張のコンパイルに必要なPythonの開発用ヘッダーファイル (
Python.h) がコンテナ内に存在しなかった。 -
解決策: Dockerfileの
apt-get installにpython3.10-devを追加。RUN apt-get update && \ apt-get install -y --no-install-recommends \ python${PYTHON_VERSION} \ python${PYTHON_VERSION}-dev \ # <-- これを追加 ...
エラー4: RuntimeError: NCCL error: unhandled system error
マルチGPU (tensor_parallel_size=4 など) で起動しようとすると、GPU間の通信を担当するNCCLがエラーで停止。
- 原因: Dockerコンテナのプロセス間通信(IPC)や共有メモリの設定が、マルチGPUの高度な通信には不十分だった。
-
解決策:
docker runコマンドに--ipc=hostと--shm-size=16g(またはそれ以上) を追加。docker run \ --gpus all \ --ipc=host \ --shm-size=16g \ ...
エラー5: モデルダウンロードのタイムアウト (IncompleteRead, Read timed out)
巨大な70Bモデルのダウンロード中に、ネットワークがタイムアウトしてコンテナが起動に失敗。
- 原因: コンテナ内からのHugging Face Hubへのネットワーク接続が不安定、または時間がかかりすぎた。
-
解決策: モデルの事前ダウンロード戦略を採用。
- ホストマシンに
huggingface-hubライブラリをインストール (pip install huggingface-hub)。 - 以下のPythonスクリプトをホストで実行し、モデルファイル一式をDockerボリュームとしてマウントするディレクトリ (
~/vllm/huggingface_cache) にダウンロード。from huggingface_hub import snapshot_download import os model_id = "tokyotech-llm/Llama-3.1-Swallow-70B-Instruct-v0.1" cache_root_dir = os.path.expanduser("~/vllm/huggingface_cache") os.environ['HF_HOME'] = cache_root_dir print(f"Downloading model {model_id} into: {cache_root_dir}") snapshot_download( repo_id=model_id, local_dir_use_symlinks=False, resume_download=True, max_workers=8, etag_timeout=120, # タイムアウトを延長 ) print("Download complete!") - このキャッシュディレクトリをマウントして
docker runを実行。これにより、コンテナ起動時のダウンロード処理が不要になった。
- ホストマシンに
フェーズ2: claude-bridge との連携
vLLMサーバーの起動に成功したところで、次に手元のマシンから claude-bridge を使って接続を試みました。
SSHポートフォワーディング
手元のマシンからGPUサーバーへの接続にはSSHローカルポートフォワーディングを利用。手元のポート 8001 をサーバーのポート 8000 に転送します。
~/.ssh/config に追記:
Host gpu**
HostName gpu***
User sample
LocalForward 8001 localhost:8000
その後 ssh gpu** で接続。
claude-bridge でのハマりポイント
-
問題1:
API key not found->--apiKey "dummykey"で解決。 -
問題2:
claude-bridgeが--baseURL http://localhost:8001/v1を無視してAnthropic APIに接続してしまう。-
--debugオプションでログ (.claude-bridge/requests-*.jsonl) を確認したところ、リクエスト先がhttps://api.anthropic.comになっていることが判明。
-
-
原因切り分け: モデル名に
/(スラッシュ) を含む場合 (tokyotech-llm/Llama-3.1-Swallow-70B-Instruct-v0.1) にこの問題が発生し、スラッシュなしのモデル名 (Llama-3.1-Swallow-70B-Instruct-v0.1) にすると--baseURLは機能するが、今度はvLLM側が「そのモデルは存在しない」と404エラーを返すジレンマに。
vLLM側での解決策
このジレンマを解決するため、vLLMの起動オプション --served-model-name を利用しました。
docker run \
...
/opt/venv/bin/python -m vllm.entrypoints.openai.api_server \
--model tokyotech-llm/Llama-3.1-Swallow-70B-Instruct-v0.1 \ # 実際のロードはこのID
--served-model-name Llama-3.1-Swallow-70B-Instruct-v0.1 \ # APIで公開する名前 (スラッシュなし)
...
これで、claude-bridge からはスラッシュなしのモデル名でアクセスでき、vLLM側もリクエストを正しく受け取れるようになりました。
しかし、それでも claude-bridge 側で 400 Bad Request が発生。vLLMのログを見ると、/v1/chat/completions へのリクエスト形式に問題があるようでした。色々調査しましたが、原因がわからずvLLMの使用はここで一旦断念することにしました。
フェーズ3: Ollamaへの移行 - シンプルさは正義
vLLMのセットアップとクライアントとの連携の複雑さから、より手軽に利用できる Ollama に切り替えることにしました。
Ollamaの導入手順 (Docker)
-
データ用ディレクトリ作成:
mkdir -p ~/ollama_data -
Ollamaコンテナ起動:
docker run -d \ --gpus all \ -v ~/ollama_data:/root/.ollama \ -p 11434:11434 \ --name ollama_server \ ollama/ollamavLLMであれほど苦労したマルチGPU設定や共有メモリの問題が、このコマンド一発であっさりと解決しました。
-
モデルのプル:
コンテナ起動後、使いたいモデルをプルします。# 70Bモデルをプル docker exec -it ollama_server ollama pull llama3:70b -
動作確認:
curlで簡単に動作確認ができます。curl http://localhost:11434/api/generate -d '{ "model": "llama3:70b", "prompt": "日本の首都はどこですか?", "stream": false }' -
Claude-bridge で使ってみる。
vLLMと同じようにSSHポートフォワーディングを設定した後に以下を実行しました。claude-bridge openai llama3.3:70b --baseURL http://localhost:11434/v1ログを残し忘れましたが、これでOllamaでホストされたローカルLLMをClaude Codeで使用することができました。
まとめ
- vLLM は間違いなく高性能ですが、その能力を最大限に引き出すには、Docker、CUDA、NCCL、Pythonのビルド環境など、低レイヤーの知識と細かなチューニングが求められます。
- Ollama は「とりあえず動かす」までの手軽さが圧倒的で、ローカルLLMの入門や、複雑な設定を避けたい場合に最適の選択肢です。
-
--ipc=hostや--shm-sizeは、DockerでマルチGPUを利用する際の「おまじない」として覚えておくと良さそうです。 -
--served-model-nameのようなオプションは、クライアントとの互換性を保つ上で非常に有用な機能だと知りました。
最終的に claude-bridge との連携は断念しましたが、vLLMとOllama、両方のセットアップを経験できたのは大きな収穫でした。これからローカルでLLM環境を構築する方にとって、この奮闘記が少しでも参考になれば嬉しいです。
参考文献