型落ちゲーミングPCからはじめる自宅Kubernetes + GPU環境
この記事は whywaita Advent Calendar 2023 の23日目です。
皆さんは whywaita Advent Calendar とは何かご存じでしょうか?私はよくわからないまま参加しています。whywaitaらしき人物に事情を伺うと 2019年のほうれん草に関する記事 のURLを貼られて、さらによくわからなくなりました。
一方、近年は大規模言語モデルや生成AIの研究開発・それらを利用したサービスが極めて活発になっており、whywaita氏と関係が1mmもないとは辛うじて言い切れない株式会社サイバーエージェントが 日本語LLMを一般公開する など、国内外で大きな盛り上がりを見せています。
また、現代の一般のITエンジニアの自宅には「世代交代などで今は使われなくなったGPUマシン」が眠っている確率が68.3%程度ある[要出展]と言われており、廃棄するのが勿体ないor面倒くさいという理由で計算資源が放置されているケースが多々あります。
そこで今回はそうした休眠中の計算資源を再利用し、サイバーエージェントの公開する日本語LLMである OpenCalm の動作環境を整備して、 「whywaita advent calendar とは何か?」と聞いてみることにしました。
真面目に向き合う気持ちはないので どんな環境で動かしてもよいのですが、折角なので自宅にある型落ちのゲーミングPCを使って自宅にUbuntu Serverを立てて、その上でKubernetes + GPU環境を構築しましょう。
今時の一般のご家庭には型落ちのゲーミングPCはその辺に無限個くらい転がっているはずですからね。*1
- 1. Ubuntu Server と NVIDIA Driver のインストール
- 2. Docker + NVIDIA Container Toolkit のインストール
- 3. GPU + Kubernetes (Minikube) 環境の構築
- 4. OpenCalm のモデルを動かしてみる
- 5. OpenCalm に whywaita Advent Calendar について聞く
- 完走した感想
1. Ubuntu Server と NVIDIA Driver のインストール
Ubuntu 22.04 インストール
whywaita Advent Calendar の読者でUbuntuのインストール作業を見たい人はあまりいないと思われるので、大幅に割愛します。 下の記事を参考にUbuntuインストール用のUSBメディアを作って Ubuntu Server 22.04 を入れました。
WindowsでUbuntuのインストールUSBメディアを作成する – diagnose-fix.com
マシン名は baccano
にしました。理由はちょっと前の週末に平成の超名作アニメ作品 Baccano! を見返したからです。超名作なので超オススメです。*2
NVIDIA Driver のインストール
NVIDIA Driverは当初はaptでインストールしようと思っていましたが、Ubuntuの公式を見るとNVIDIA Driverのインストール手順の記載がありました。どうやら ubuntu-drivers
を使って簡単に導入できるようなのでやってみましょう。
https://ubuntu.com/server/docs/nvidia-drivers-installation
root@baccano:~# ubuntu-drivers devices == /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 == modalias : pci:v000010DEd00001F08sv000010DEsd000012FDbc03sc00i00 vendor : NVIDIA Corporation model : TU106 [GeForce RTX 2060 Rev. A] driver : nvidia-driver-535-server-open - distro non-free driver : nvidia-driver-525 - distro non-free driver : nvidia-driver-525-server - distro non-free driver : nvidia-driver-450-server - distro non-free driver : nvidia-driver-535-server - distro non-free ...
型落ちゲーミングPCには GeForce RTX2060 が刺さっていたらしいです。全然覚えてませんでした。
とりあえず最新の nvidia-driver-535-server
を入れておきましょう。
root@baccano:~# ubuntu-drivers install nvidia:535-server
導入後は忘れずに再起動しておきます。
2. Docker + NVIDIA Container Toolkit のインストール
下書き時点ではひとつひとつ実行コマンド等を書いていたのですが「そんなに細かく書く必要ないな?」と思ってきたので、細かいコマンド実行は省いて巻いていきましょう。
Docker と NVIDIA Container Toolkit は以下のURLから手順通りに入れていきます。
Install Docker Engine on Ubuntu | Docker Docs
Installing the NVIDIA Container Toolkit — NVIDIA Container Toolkit 1.14.3 documentation
導入後に docker run --gpus=all
でGPUが認識できているか確認します。
root@baccano:~# docker run --gpus=all --rm -it nvcr.io/nvidia/cuda:12.3.1-runtime-ubuntu22.04 nvidia-smi ... +---------------------------------------------------------------------------------------+ | NVIDIA-SMI 535.129.03 Driver Version: 535.129.03 CUDA Version: 12.3 | |-----------------------------------------+----------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+======================+======================| | 0 NVIDIA GeForce RTX 2060 Off | 00000000:01:00.0 Off | N/A | | 20% 46C P0 23W / 160W | 0MiB / 6144MiB | 0% Default | | | | N/A | +-----------------------------------------+----------------------+----------------------+ ...
ちゃんとDockerのコンテナからもGPU認識ができていそうです。
3. GPU + Kubernetes (Minikube) 環境の構築
Minikube
Kubernetes環境の作成の仕方は色々ありますが、今回は別にKubernetesで真面目にクラスタを組んだりすることが目的ではないので簡単な方法を選びます。 一般に、Kubernetes環境を簡単に試すだけであれば Minikube や microk8s を使うのが楽です。今回はMinikubeを使います。
複数ノードを扱ってクラスタを組むことを見越した場合は microk8s や他の方法を取るべきですが、今回は型落ちゲーミングPC1台の予定なのでその時のことはその時の自分が考えます。
とりあえずさっと手順通りインストールしてから起動してみます。デフォルトではdockerのdriverが使われるはずです。
$ minikube start 😄 minikube v1.32.0 on Ubuntu 22.04 ✨ Automatically selected the docker driver. Other choices: none, ssh 📌 Using Docker driver with root privileges 👍 Starting control plane node minikube in cluster minikube 🚜 Pulling base image ... 💾 Downloading Kubernetes v1.28.3 preload ... > gcr.io/k8s-minikube/kicbase...: 453.90 MiB / 453.90 MiB 100.00% 29.61 M > preloaded-images-k8s-v18-v1...: 403.35 MiB / 403.35 MiB 100.00% 19.19 M 🔥 Creating docker container (CPUs=2, Memory=3900MB) ... 🐳 Preparing Kubernetes v1.28.3 on Docker 24.0.7 ... ▪ Generating certificates and keys ... ▪ Booting up control plane ... ▪ Configuring RBAC rules ... 🔗 Configuring bridge CNI (Container Networking Interface) ... ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🔎 Verifying Kubernetes components... 🌟 Enabled addons: storage-provisioner, default-storageclass 💡 kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A' 🏄 Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
CPUとMemoryが心許なさそうですが、Minikubeの起動には成功していそうです。 Minikubeの環境で動いているPodとNodeを確認してみましょう。
$ minikube kubectl -- get pod -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-5dd5756b68-p887f 1/1 Running 0 98s kube-system etcd-minikube 1/1 Running 0 111s kube-system kube-apiserver-minikube 1/1 Running 0 111s kube-system kube-controller-manager-minikube 1/1 Running 0 111s kube-system kube-proxy-t7fm8 1/1 Running 0 99s kube-system kube-scheduler-minikube 1/1 Running 0 114s kube-system storage-provisioner 1/1 Running 1 (68s ago) 111s $ minikube kubectl -- get node -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME minikube Ready control-plane 2m5s v1.28.3 192.168.49.2 <none> Ubuntu 22.04.3 LTS 5.15.0-91-generic docker://24.0.7
なるほど、こんな感じなんですね(はじめてMinikubeをまともに触った顔)
Minikube + GPU
公式ページを見てみたところ、Minikube で GPUを認識させる方法は利用しているdriverによって異なるようです。
今回はデフォルトの docker
をdriverとして利用しています。
上記のドキュメントを軽く見た感じだと、おそらくこんな感じでしょうか?
docker
の場合は Docker + NVIDIA Container Toolkit の機能をそのまま使うnone
(bare-metal) の場合は NVIDIA Container Toolkit + NVIDIA Device Plugin (ドキュメントが更新されていない気がしますが)kvm
の場合は PCIパススルー + NVIDIA Container Toolkit + NVIDIA Device Plugin
今回のように driver が docker
の場合はMinikube起動時の引数の指定だけで実現できそうなので、やってみましょう。ついでにCPUとMemoryの割り当ても増やします。
ちなみに minikube 環境を一度削除 (delete)まで しないと minikube start
に渡す引数の設定が反映されないので注意です。
# 停止と削除 $ minikube stop $ minikube delete
$ minikube start --cpus 6 --memory 8g --driver docker --container-runtime docker --gpus all 😄 minikube v1.32.0 on Ubuntu 22.04 ✨ Using the docker driver based on user configuration 📌 Using Docker driver with root privileges 👍 Starting control plane node minikube in cluster minikube 🚜 Pulling base image ... 🔥 Creating docker container (CPUs=6, Memory=8192MB) ... 🐳 Preparing Kubernetes v1.28.3 on Docker 24.0.7 ... ▪ Generating certificates and keys ... ▪ Booting up control plane ... ▪ Configuring RBAC rules ... 🔗 Configuring bridge CNI (Container Networking Interface) ... 🔎 Verifying Kubernetes components... ▪ Using image nvcr.io/nvidia/k8s-device-plugin:v0.14.2 ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5 🌟 Enabled addons: storage-provisioner, nvidia-device-plugin, default-storageclass 💡 kubectl not found. If you need it, try: 'minikube kubectl -- get pods -A'
CPU, Memory, GPUの引数で指定した設定がちゃんと反映されていそうな雰囲気を感じます。
このあとCUDAイメージのPodを作成して、Podの中のコンテナで nvidia-smi
が実行できることを確認しました。
4. OpenCalm のモデルを動かしてみる
PyTorchイメージのデプロイ
さて、ようやくKubernetes環境ができました。次はOpenCalmのモデルを動かすコンテナ環境を整えましょう。 と言っても難しいことはなく、NVIDIAのPytorchイメージのコンテナをKubernetesのPodで作成して、必要なPyPIパッケージを入れるだけです。
本来のKubernetesの流儀では継続的にデプロイできるようにマニフェストを作成していくべきですが、アドカレにそんな真面目に向き合うのもどうかと思うのでコマンドでデプロイしていきましょう。
alias kubectl="minikube kubectl --" # NVIDIAのPytorchイメージのPodを作成 kubectl run --image=nvcr.io/nvidia/pytorch:23.11-py3 pytorch --command -- sleep INF # Podのコンテナにbashで接続 kubectl exec -it -n default pytorch -- bash # コンテナの中で JupyterLab を8888ポートで起動 jupyter lab --allow-root --ip=* --port=8888 --no-browser --NotebookApp.token='' --NotebookApp.allow_origin='*' --notebook-dir=/ # 別のターミナルでPodの8888番をポートフォワード kubectl port-forward pod/pytorch 8888:8888
ついでにVSCodeのSSH機能でGPUマシンの8888ポートをポートフォワードして、手元のメインPCのブラウザから別PCのKubernetes環境上で動いているPodのJupyterLabまで繋げるようにします。
OpenCalmの実行
ここまでくればあともう少し。次はOpenCalmの実行テストです。 まずは一番小さいモデルから動作確認をしていきましょう。
pipで実行に必要な依存関係を入れていきます。
$ pip install transformers $ pip install accelerate
あとはHuggingFaceのページにあるPythonコードを実行すれば動いてくれるはずです。
import torch from transformers import AutoModelForCausalLM, AutoTokenizer model = AutoModelForCausalLM.from_pretrained("cyberagent/open-calm-small", device_map="auto", torch_dtype=torch.float16) tokenizer = AutoTokenizer.from_pretrained("cyberagent/open-calm-small") inputs = tokenizer("Whywaitaによって私たちの暮らしは", return_tensors="pt").to(model.device) with torch.no_grad(): tokens = model.generate( **inputs, max_new_tokens=64, do_sample=True, temperature=0.7, top_p=0.9, repetition_penalty=1.05, pad_token_id=tokenizer.pad_token_id, ) output = tokenizer.decode(tokens[0], skip_special_tokens=True) print(output)
サンプルをちょっと変更して「Whywaitaによって私たちの暮らしは」の続きの文章を生成してもらいましょう。
root@pytorch:/workspace/calm# python3 open-calm-small.py pytorch_model.bin: 72%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▌ | 273M/381M [00:09<00:02, 44.8MB/s] pytorch_model.bin: 77%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████▉ | 294M/381M [00:09<00:02, 41.6MB/s] pytorch_model.bin: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 381M/381M [00:11<00:00, 31.9MB/s] generation_config.json: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 116/116 [00:00<00:00, 314kB/s] tokenizer_config.json: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 326/326 [00:00<00:00, 900kB/s] tokenizer.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3.23M/3.23M [00:01<00:00, 3.21MB/s] special_tokens_map.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 129/129 [00:00<00:00, 368kB/s] ...
次の文章が生成されました。
Whywaitaによって私たちの暮らしは、日々少しずつ変わっていきます。 そして、その変化が私たちの生活にどのような影響を与えるのかを考えるとき、私たちは何を拠りどころとして生活をしているのかを考えないといけません。 私たちは、自分がどのように感じているのか、どういう生き方をしているのか、何を大切にしているのかをまずは自分の内側に問いかけてみてください。 そうすると、自分のあり方が...
どうやら Whywaita とは自己の在り様に影響を与える哲学的な存在のようですね。 やや長かったこの記事も遂に核心に近付いてきた感じがします。
5. OpenCalm に whywaita Advent Calendar について聞く
遂に OpenCalm に whywaita Advent Calendarについて問う刻(とき)がやってきました。
whywaita Advent Calendar とはいったい何なのか、永らく謎に包まれていた存在の秘密が遂に明かされます。
ちなみにGPUのメモリ消費量的には余裕があったので、もう少しパラメータ数の大きい Open-calm-1B
(1.4B) にモデルを変更しました。
( open-calm-3b
は流石にそのままだとメモリに乗り切らなくてダメでした)
なお、下の記事によると open-calm-3b
のモデルのメモリ消費量は 6.5GB くらいだそうです。GeForce RTX 2060 のVRAMは6GBなので流石に厳しい。
CyberAgentの日本語言語モデルを試してみる - きしだのHatena
ということで先程と同じようにして open-calm-1b
のモデルに「whywaita Advent Calendar とは、」の続きの文章を生成してもらいます。
root@pytorch:/workspace/calm# python3 open-calm-1b.py config.json: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 610/610 [00:00<00:00, 1.59MB/s] pytorch_model.bin: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 2.94G/2.94G [00:34<00:00, 86.0MB/s] generation_config.json: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 116/116 [00:00<00:00, 312kB/s] tokenizer_config.json: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 323/323 [00:00<00:00, 887kB/s] tokenizer.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 3.23M/3.23M [00:00<00:00, 4.69MB/s] special_tokens_map.json: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 129/129 [00:00<00:00, 351kB/s] ...
次の文章が生成されました。
whywaita Advent Calendar とは、Kotlin で書かれた Learn オープンソースのプログラミング言語 Koin のカレンダーアプリだ。 このカレンダーは Google Apps Script から起動され(そして当然のように AWS API Gateway と Route53 が使われている)、ユーザは CLI を介してブラウザからスケジュールやリマインダーを
もう一回やってみましょう
whywaita Advent Calendar とは、JavaScript の開発者向けのカレンダーです。このアドベントカレンダーでは各社の JavaScript エンジニアが交代でブログ記事を書き(今年は私が担当しました)、カレンダーに沿って1日ずつ紹介していくという趣向になっています。(なお去年 「PHPer-naoのHTML5とかWordPressな日々」というタイトルでした) それでは
ということで、whywaita Advent Calenderとは、Kotolin で書かれたカレンダーアプリ または JavaScript 関連の開発者向けカレンダー らしいです!
いかがでしたか?私は何もわかりませんでした。しかし、今回の記事の執筆を通して「気軽に遊べる自宅Kubernetes + GPU環境を構築する」という本当の目的は無事に果たせたので私は満足しています。
それでは、皆様よい年末年始と、よい whywaita Advent Calenderを!