CPU環境でスケーラブルなLLM Webサービスをデプロイする方法
この記事について
こんにちは、東京大学鈴村研究室で、インフラエンジニアとしてお手伝いさせていただいています、福田と申します。
これまで、クラウド基盤mdxの上でKubernetes環境を構築し、サーバレスWebアプリケーションを開発するための手順や、分散学習を行うための手順について説明してきました。
今回のこの記事では、mdxのGPU nodeではなくCPU nodeを使って、Kubernetes上にLLM Webサービスをデプロイする方法について説明してきます。
mdxはGPUリソースよりもCPUリソースの方が潤沢にあるので、CPUノードを使ったLLMサービスを構築できるのは、私個人としては非常に価値が高いと考えています。
なお、LLMのモデルについては、実用性に耐えうることが重要ですので、サイバーエージェント社の公開している、DeepSeek-R1 14Bの日本語対応モデルを量子化させたものを使用しています。
量子化はしていますが、DeepSeek自体が高精度な回答を返すため、私個人の感覚としては、十分に実用性に耐えうるものだと感じました。
CPUノードを使ったKubernetesクラスタの構築
始めにCPUノードを使ったKubernetesクラスタの構築を行います。
手順については、過去記事を参照ください。
手順はGPUノードを前提に書かれていますが、そのままCPUに置き換えてもらえれば大丈夫です。
なお、CPUのコア数ですが、1つのLLM Podを立ち上げるために、32コア使用するため、その他のプロスでも使うことを想定し、40コア程度あるのが理想です。
もし、1CPUノードあたり、4つのPodを立ち上げたい場合は、32 * 4 + 8 = 136コアくらいあれば良いと思います。
CPUノードへのLLMサービスのデプロイ
llama.cppについて
CPUノードに対して、LLM Webサービスをデプロイするため、以下のllama.cppというライブラリを使用します。
llama.cpp は、Metaが開発した大規模言語モデル「LLaMA(Large Language Model Meta AI)」シリーズなどのオープンソースモデルを、軽量かつ高速にローカル環境で動かすことができるようにしたオープンソースの推論エンジンです。名前にある「.cpp」が示す通り、主に C++ 言語で実装されており、Mac、Windows、Linux、さらにはスマートフォン(iOSやAndroid)といった幅広い環境で動作します。
これまで、大規模言語モデルを動かすには数十GB以上のGPUメモリや専用インフラが必要でした。しかし llama.cpp の登場により、個人のラップトップや小型サーバーでも LLM を手軽に動かせるようになりました。
「ChatGPT のような言語モデルを、GPUを必要としない自前のオンプレミス環境で実行できる」それがllama.cpp のもたらす最大の価値となっています。
DeepSeek-R1-Distill-Qwen-14B-Japaneseモデルのダウンロード
今回は、DeepSeek-R1-Distill-Qwen-14B-Japaneseをllama.cppでも動くように量子化した、以下のモデルを使用します。
この量子化モデルを、Kubernetesの全worker nodeにダウンロードする必要があります。
この作業を1台1台行うのは大変なので、自動で行えるようansibleのスクリプトを作成しました。
このansibleスクリプトを実行すると、全worker nodeに自動的に重みファイルがダウンロードされます。
まず、以下のコマンドで踏み台サーバにログインします。
eval `ssh-agent`
ssh-add
ssh -A (踏み台サーバのIPアドレス)
ansibleの実行のため、k8s-configsのディレクトリに移動します。
cd k8s-configs
以下のコマンドを実行すると、重みファイルのダウンロードと、全worker nodeへの配信が行われます。
uv run ansible-playbook -i inventory.ini site.yaml --tags llm_settings --ask-vault-pass
llama.cppを使っての、LLMサービスのデプロイ
次に、llama.cppを、Kubernetes環境に対してデプロイするため、以下のようなyamlファイルを定義します。
LLM webサービスのPod1つ当たり、CPU32コアとRAM16GBのメモリを必要としています。
今回は、CPU136コアを持つmdx仮想マシンを2台立ち上げているため、合計8つのLLMサービスが起動できることになります。
よって、初期状態でのPodの起動数である、minScaleとmaxScaleの値をそれぞれ8に設定し、8個が起動されるように固定しておきます。
minScaleを8に設定しているのは、LLM Webサービス Podの起動には時間が掛かるので、ホットスタンバイにしておきたいという意図です。
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: deepseek-14b-q4
namespace: default
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "8" # 最初から8pod立ち上げておく
autoscaling.knative.dev/maxScale: "8" # 8pod以上は立ち上げない
spec:
containerConcurrency: 1 # LLMの処理は時間が掛かるため、1つのPodにおいて同時に扱えるリクエストは1つに限定する。
timeoutSeconds: 1800
containers:
- image: ghcr.io/ggml-org/llama.cpp:server
args:
- "--model"
- "/models/DeepSeek-R1-Distill-Qwen-14B-Japanese-Q4_K_M.gguf"
- "--host"
- "0.0.0.0"
- "--port"
- "8000"
- "--api-key"
- "xxxxxxxxxxxxxxxxxxxxx" # 認証のためのパスワードを設定する
- "--n_gpu_layers"
- "0" # GPUは使わないのでゼロに設定
- "--threads"
- "32" # resources.requests.cpuで定義したCPUコア数に合わせる
ports:
- containerPort: 8000
resources:
requests:
cpu: "32" # 最低32コア必要として定義
memory: "16Gi" # 最低16GBのRAMが必要として定義
limits:
cpu: "32"
memory: "16Gi"
volumeMounts:
- mountPath: /models
name: pvc-large-volume
volumes:
- name: pvc-large-volume
persistentVolumeClaim:
claimName: hostpath-pvc-large
上記のyamlファイルを使って、以下のコマンドでデプロイを行います
kubectl apply -f (yamlファイルへのpath)
Lensの画面でPodが起動するのを確認します。
以下のコマンドを実行し、アクセスするためのURLを確認します。
kubectl get ksvc
すると、以下のような結果が得られるので、このURLがLLM Webサービスを使うためのエンドポイントになります。
NAME URL LATESTCREATED LATESTREADY READY REASON
deepseek-14b-q4-v12 https://deepseek-14b-q4-v12.default.example.com deepseek-14b-q4-v12-00001 deepseek-14b-q4-v12-00001 True
クライアントからのアクセス
今回デプロイしたLLM Webサービスは OpenAIのAPIに準拠していますので、PythonのOpenAIクライアントをアクセス元のPCなどにインストールします。
pip install openai
uvなどのパッケージマネージャーを使用している場合は、以下のコマンドインストールしてください。
uv add openai
次に、アクセスするためのスクリプトを作成します。
ここで、api_keyには、KNativeのWebサービス定義のyamlに定義した、api_keyの値と一致させます。
またアクセスするためのURLとしては、kubectl get ksvc
で得られたURLの末尾に、/v1
を付与したものを使用するようにします。
if __name__ == '__main__':
from openai import OpenAI
client = OpenAI(
base_url="https://deepseek-14b-q4-v12.default.example.com/v1", # URLの末尾には/v1を付与する
api_key="xxxxxxxxxxxxx", # KNativeのapi_keyの値と一致させる。
)
completion = client.chat.completions.create(
model="/models/DeepSeek-R1-Distill-Qwen-14B-Japanese-Q4_K_M.gguf", # KNativeの--modelの引数に指定した値を設定する
temperature=0.1,
messages=[
{"role": "user", "content": "富士山について教えてください。"}
]
)
print(completion.choices[0].message.content)
すると以下のような結果が得られ、正しくDeepSeekがデプロイされていることを確認できました。
<think>
まず、富士山について調べる必要があります。基本的な情報から始めましょう。富士山は日本の山で、標高は約3,776メートルとされています。位置は静岡県と山梨県にまたがっていますね。活火山であることも重要です。噴火の歴史や最近の活動状況も調べる必要があります。
次に、富士山の文化や伝統について。富士山信仰や富士講、富士山の登拝の歴史などがあります。特に、富士山の登拝は観光客にも人気で、山頂へのアクセス方法や季節ごとの情報も必要でしょう。
自然環境や生態系についても触れるべきです。富士山周辺の植物や動物、高山植物の分布など。また、富士山の環境問題、例えばゴミ問題や観光客の増加による影響についても言及する必要があります。
歴史的な出来事や文学作品での富士山の描写も調べます。例えば、漱石の「こころ」や他の小説での登場、あるいは歴史的な噴火の記録など。また、富士山の絵画や写真、芸術的な表現についても触れると良いかもしれません。
観光情報として、富士山の周辺観光スポット、温泉、宿泊施設、食事の情報なども必要です。特に富士五湖や河口湖、箱根など周辺の観光地との関連も説明します。
最後に、富士山の保護や国際的な評価について。ユネスコの世界文化遺産への登録や、富士山の環境保全の取り組みについても調べます。また、富士山の未来や噴火予測に関する科学的な見解も含めると良いでしょう。
情報の正確性を確認するために、信頼できるソースを参照します。例えば、日本火山学会の資料や富士山の公式サイト、信頼できる観光ガイドブックなど。また、最新の噴火情報や環境問題に関する最新のニュースもチェックします。
全体として、富士山の地理的特徴、文化、自然、歴史、観光、環境問題など多角的な視点から情報を整理し、分かりやすくまとめる必要があります。専門用語を適宜説明し、読者が興味を持てるように具体例やデータを交えると良いでしょう。
</think>
**富士山についての詳細解説**
### 1. **基本情報**
- **標高**: 約3,776メートル(2022年測量)。日本最高峰。
- **位置**: 静岡県と山梨県にまたがる活火山。
- **噴火周期**: 最終噴火は1707年の宝永大噴火(約300年無活動)。
- **火山活動**: 現在も活発で、地震や地熱活動が観測される。
### 2. **文化・信仰**
- **富士山信仰**: 古くから神聖視され、富士山神社(静岡県)が鎮座。富士講(登山信仰)が発展し、17世紀以降、登山者が増加。
- **文学・芸術**: 漱石の『こころ』や写楽の浮世絵が象徴。国指定文化財「富士山の絵」など多数。
- **登拝**: 1日目から山頂まで約8時間。近年は外国人観光客も増加。
### 3. **自然環境**
- **高山植物**: ヤマツツジ、シラネアオイなど。世界自然遺産登録(2013年)の理由の一つ。
- **生態系**: ハクビグマやシマリスなど高山生物が生息。氷河や湿原が分布。
- **環境問題**: ゴミ問題(年間約100トン)や観光客増による生態破壊が課題。
### 4. **歴史的噴火**
- **宝永大噴火(1707年)**: 大規模な噴火で関東地方に被害。富士山の現在の形状が形成。
- **過去の噴火**: 1606年(天正噴火)、1511年(永正噴火)など、約800年に1回の噴火周期と推定。
### 5. **観光・周辺情報**
- **富士五湖**: 河口湖、山中湖、忍野八海など。観光名所として人気。
- **温泉**: 富士吉田温泉(静岡)や湯河原温泉(神奈川)が有名。
- **交通**: 富士山駅(富士山新幹線)や富士急ハイランド(テーマパーク)がアクセス拠点。
### 6. **国際的評価**
- **ユネスコ世界文化遺産**: 2013年登録。富士山の信仰と景観が評価。
- **国際火山会議**: 富士山は火山研究の重要な対象。火山噴火予測の研究が進む。
### 7. **未来の課題**
- **噴火予測**: 地震や地熱活動の増加に注意。火山警戒レベルが変動。
- **環境保全**: ゴミ撤去作業や登山ルートの管理が継続的に行われている。
### 8. **最新情報**
- **2023年現在**: 富士山の火山活動は「休火山」に分類されるが、地熱活動は継続。観光客数は年間約100万人超。
富士山は自然と文化が融合した象徴的な存在であり、その多面的な魅力が国内外から注目されています。
東京大学鈴村研究室について
Discussion