こんにちは。サイオステクノロジーの塙です。
今回はEKS上でGPUを扱う生成AIソリューションのデプロイを試し、実際にGPUがどう使われてどう見えるのかを検証してみたいと思います。
概要
前回は、Kubernetes をベースとしたプラットフォームでGPUを扱っていくための手法について解説してみました。
KubernetesでGPUを扱うためにはどんな準備が必要となるのか、またどんな設定をすれば良いかをまとめています。
■前回の記事はこちら
前回までの記事は机上ベースでのまとめをしていたため、今回はEKSを用いて、実際にGPUの設定がどう見えるのかについてまとめてみました。
導入
EKS上での検証は以下の記事を参考にしています。
まずこの記事の内容を参考にして導入していきます。
■ 検証の概要
上記記事では、EKSに生成AIソリューションをデプロイします。
JARK Stack と呼ばれるモデルの構築と実行に利用出来るツールを用いており、アーキテクチャとしては、JupyterHub, Argo Workflows, Ray Serve, Kubernetes を指しています。(アーキテクチャの詳細は記事をご参考ください)
今回、それぞれのツールの詳細は割愛していますが、生成AIソリューションの構成時に使用するツール群の調査も追々行っていけたらと思います。
この時使用する生成AIは、Stable Diffusion というText-to-Imageの画像生成AIの拡散モデルを使用しています。
また、DreamBoothという特定の対象を事後学習させる学習方法を用いてトレーニングを行い、モデルを作成する一連の工程をEKSのPodで行っている形となります。
■ 前提情報
検証に必要な前提は以下となります。(バージョンは検証時のバージョン)
- AWS CLI v2.11.13
- kubectl
- Client Version: v1.24.1
Kustomize Version: v4.5.4
Server Version: v1.29.5-eks-1de2ab1
- Client Version: v1.24.1
- Terraform v1.8.4
- helm v3.13.1
- Hugging Face のトークン
- jq v1.6
検証準備
Aの記事の「Steps to deploy Stable Diffusion Model on Amazon EKS」から順次行っていきます。
1. data-on-eksのGitリポジトリをクローンします
$ git clone https://github.com/awslabs/data-on-eks.git
2.ブループリントをデプロイします
ai-ml/jark-stack/terraformのブループリントまで移動して、./install.sh スクリプトを実行して、terraformで生成AIソリューションを構築していきます。今回の構築用に不要なアドオンの削除、インスタンスタイプ、VPCネットワークなどを少し修正しデプロイを行います。デプロイは30分ほどかかるので完了するまで少し待ちます。
下記では、Hugging Faceのトークンを使用するためファイルのダミートークンを置き換える内容を加えています。
$ cd data-on-eks/ai-ml/jark-stack/terraform
# 必要に応じて、不要なアドオンの削除、インスタンスタイプ、VPCネットワークなどを修正
..(snip)..
# 必要に応じて、Hugging face tokenの修正
# data-on-eks/ai-ml/jark-stack/terraform/variables.tf
variable "huggingface_token" {
description = "Hugging Face Secret Token"
type = string
default = "DUMMY_TOKEN_REPLACE_ME" ## hugging face token に置き換える
sensitive = true
$ ./install.sh
3. 記事と同じように、Stable Diffusion モデルの調整を行っていきます
$ kubectl get svc proxy-public -n jupyterhub --output jsonpath='{.status.loadBalancer.ingress[0].hostname}
k8s-jupyterh-proxypub-xxx.elb.us-west-2.amazonaws.com
出力されたDNS ホスト名をWebブラウザ経由で開き、jupyterhubを起動します。
jupyterhub-values.yamlに記載されているユーザー名とパスワードを使用してログインします。(これにより新規のPodが立ち上がります)
# jupyter-xxx1 が立ち上がっていることを確認する
$ kubectl get pods -n jupyterhub
NAME READY STATUS RESTARTS AGE
continuous-image-puller-d4tqs 1/1 Running 0 90m
continuous-image-puller-m6ccv 1/1 Running 0 90m
hub-64f87f44dd-xlhd2 1/1 Running 0 90m
jupyter-admin1 1/1 Running 0 15m
proxy-8685586d98-wklvw 1/1 Running 0 90m
起動すると、Jupyter Notebook のコンソールにリダイレクトされるので、記事に従い Notebookで提供されているPythonを実行していきます。(Pythonの実行に関しては、ここでは割愛)
ここまでで今回の趣旨の準備段階が完了です。以降からデプロイ後の構成でGPUを確認していきます。
確認内容
それぞれいくつかの観点で設定の確認を行っていきます。
■ nvidia-device-plugin の確認
nvidia-device-plugin の確認を行うと、いくつかのリソースが動作していることが分かります。
$ kubectl get all -n nvidia-device-plugin
NAME READY STATUS RESTARTS AGE
pod/nvidia-device-plugin-gpu-feature-discovery-pkfff 1/1 Running 0 75m
pod/nvidia-device-plugin-node-feature-discovery-master-568b4977kb2j 1/1 Running 0 76m
pod/nvidia-device-plugin-node-feature-discovery-worker-8gddp 1/1 Running 1 (76m ago) 76m
pod/nvidia-device-plugin-node-feature-discovery-worker-xf9mp 1/1 Running 1 (76m ago) 76m
pod/nvidia-device-plugin-qwm44 1/1 Running 0 75m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nvidia-device-plugin-node-feature-discovery-master ClusterIP 10.100.136.70 <none> 8080/TCP 76m
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.apps/nvidia-device-plugin 1 1 1 1 1 <none> 76m
daemonset.apps/nvidia-device-plugin-gpu-feature-discovery 1 1 1 1 1 <none> 76m
daemonset.apps/nvidia-device-plugin-node-feature-discovery-worker 2 2 2 2 2 <none>
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nvidia-device-plugin-node-feature-discovery-master 1/1 1 1 76m
NAME DESIRED CURRENT READY AGE
replicaset.apps/nvidia-device-plugin-node-feature-discovery-master-568b497868 1 1 1 76m
今回用いたdata-on-eks では、daemonsetであるnvidia-device-pluginと、付随の機能として備わっているgpu feature discovery(以下、gfd), node feature discovery(以下、nfd)が動作する形となっています。
gfdとnfdはそれぞれ以下の機能を持つものとなっています。
- gfd
- nfdの一部であり、特にGPU関連の機能を検出するために用いるツールとなる
- nfd
- Kubernetesの各ノードのハードウェア機能やカーネル機能などの属性を自動的に検出し、それらの情報をラベルとしてKubernetes APIに公開するツールとなる
つまり、gfdとnfdがノードの機能を検出しラベルを付けることで、nvidia-device-pluginがそれらの情報を用いてGPUリソースを管理し適切なPodに割り当てることをしています。
またnvicia-device-pluginのログを確認してみると、GRPCを開始し、Kubeletにnvidia.com/gpu
のデバイスプラグインを登録していることが分かります。
$ kubectl logs daemonset.apps/nvidia-device-plugin -n nvidia-device-plugin
I0629 07:28:42.381694 1 server.go:165] Starting GRPC server for 'nvidia.com/gpu'
I0629 07:28:42.382522 1 server.go:117] Starting to serve 'nvidia.com/gpu' on /var/lib/kubelet/device-plugins/nvidia-gpu.sock
I0629 07:28:42.386109 1 server.go:125] Registered device plugin for 'nvidia.com/gpu' with Kubelet
※今回は対象外としていますが、NVIDIA GPU Operator には、gfdとNVIDIA デバイス プラグインの両方が含まれます。
■ Podから使用しているGPUの見え方の確認
準備段階で立ち上げた、jupyter-xxx1の内容を見てみます。確かにPodからGPUのLimits, Requests でGPUを要求していることが確認出来ます。
$ kubectl describe pod jupyter-admin1 -n jupyterhub
Containers:
notebook:
..(snip)..
Limits:
nvidia.com/gpu: 1
Requests:
memory: 5368709120
nvidia.com/gpu: 1
■ EKSのノードからのGPUの見え方の確認
1. kubernetes のコマンドからGPUの見え方の確認
kubectl からノードの状態を確認してみます。
Capacity, Allocatable から nvidia.com/gpu: 1
が確認できます。実際にPodからGPUを消費すると、Allocated resourcesのnvidia.com/gpu
の値も更新されるようになります。
$ kubectl get node
NAME STATUS ROLES AGE VERSION
ip-100-64-105-40.us-west-2.compute.internal Ready <none> 56m v1.29.3-eks-ae9a62a
ip-100-64-150-241.us-west-2.compute.internal Ready <none> 56m v1.29.3-eks-ae9a62a
# GPU搭載のノードをdescribeで出力
$ kubectl describe node ip-100-64-105-40.us-west-2.compute.internal
..(snip)..
Capacity:
cpu: 4
ephemeral-storage: 104845292Ki
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 16069056Ki
nvidia.com/gpu: 1
pods: 29
Allocatable:
cpu: 3920m
ephemeral-storage: 95551679124
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 15378880Ki
nvidia.com/gpu: 1
pods: 29
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.)
Resource Requests Limits
-------- -------- ------
cpu 180m (4%) 0 (0%)
memory 5494538240 (34%) 768Mi (5%)
ephemeral-storage 0 (0%) 0 (0%)
hugepages-1Gi 0 (0%) 0 (0%)
hugepages-2Mi 0 (0%) 0 (0%)
nvidia.com/gpu 1 1
..(snip)..
2. ノード上からnvidia-smi, deviceQueryコマンドでGPUの見え方の確認
AWS のセッションマネージャーから該当のノードにログインして確認してみます。
nvidia-smiコマンドでGPUの状態を出力すると以下のような情報が確認出来ます。この時、検証準備で行ったPythonアプリケーションを動作させているため、PythonのプロセスがGPUを使用していることが分かります。
また、deviceQueryコマンドを用いてGPUの情報が確認出来ます。
sh-4.2$ nvidia-smi
Sat Jun 29 08:57:36 2024
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.183.01 Driver Version: 535.183.01 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| 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 Tesla T4 On | 00000000:00:1E.0 Off | 0 |
| N/A 33C P0 32W / 70W | 14819MiB / 15360MiB | 21% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 90855 C /opt/conda/bin/python3.10 14816MiB |
+---------------------------------------------------------------------------------------+
sh-4.2$ /usr/local/cuda-12.2/extras/demo_suite/deviceQuery
/usr/local/cuda-12.2/extras/demo_suite/deviceQuery Starting...
..(snip)..
Device 0: "Tesla T4"
CUDA Driver Version / Runtime Version 12.2 / 12.2
CUDA Capability Major/Minor version number: 7.5
Total amount of global memory: 15102 MBytes (15835660288 bytes)
(40) Multiprocessors, ( 64) CUDA Cores/MP: 2560 CUDA Cores
GPU Max Clock rate: 1590 MHz (1.59 GHz)
Memory Clock rate: 5001 Mhz
Memory Bus Width: 256-bit
L2 Cache Size: 4194304 bytes
Maximum Texture Dimension Size (x,y,z) 1D=(131072), 2D=(131072, 65536), 3D=(16384, 16384, 16384)
Maximum Layered 1D Texture Size, (num) layers 1D=(32768), 2048 layers
Maximum Layered 2D Texture Size, (num) layers 2D=(32768, 32768), 2048 layers
Total amount of constant memory: 65536 bytes
Total amount of shared memory per block: 49152 bytes
Total number of registers available per block: 65536
Warp size: 32
Maximum number of threads per multiprocessor: 1024
Maximum number of threads per block: 1024
Max dimension size of a thread block (x,y,z): (1024, 1024, 64)
Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535)
Maximum memory pitch: 2147483647 bytes
Texture alignment: 512 bytes
Concurrent copy and kernel execution: Yes with 3 copy engine(s)
Run time limit on kernels: No
Integrated GPU sharing Host Memory: No
Support host page-locked memory mapping: Yes
Alignment requirement for Surfaces: Yes
Device has ECC support: Enabled
Device supports Unified Addressing (UVA): Yes
Device supports Compute Preemption: Yes
Supports Cooperative Kernel Launch: Yes
Supports MultiDevice Co-op Kernel Launch: Yes
Device PCI Domain ID / Bus ID / location ID: 0 / 0 / 30
Compute Mode:
< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >
deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 12.2, CUDA Runtime Version = 12.2, NumDevs = 1, Device0 = Tesla T4
Result = PASS
■ ノードグループからGPUの状態を確認
AWS コンソールからGPUをどう使用しているか確認してみます。
結論から言うと、AWSコンソールからはラベルで情報の確認が出来る程度でした。CUDAに関する情報、GPUに関する情報が見える程度です。
GPUへの共有アクセス方法を試す
デプロイ後の構成で、GPUへの共有アクセス方法も出来るか追加で試してみました。
GPUへの共有アクセス方法の設定は、以下の記事の内容を参考にして行っていきます。
■ Time Slicing の設定
今回は、前回の記事で紹介した方法の中からTime Slicingを試してみたいと思います。
今回の nvidia-device-plugin はhelmでデプロイされているため、helm をアップグレードする方法で試します。
まずリポジトリを追加します。
$ helm repo add nvdp https://nvidia.github.io/k8s-device-plugin
$ helm repo update
Time Slicingを有効にする前のノードで利用できるGPUの数を確認します。"nvidia.com/gpu": "1"
とまだ利用出来る数は1つです。
$ kubectl get nodes -o json | jq -r '.items[] | select(.status.capacity."nvidia.com/gpu" != null) | {name: .metadata.name, capacity: .status.capacity}'
{
"name": "ip-xxx.us-west-2.compute.internal",
"capacity": {
"cpu": "4",
"ephemeral-storage": "104845292Ki",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "16069060Ki",
"nvidia.com/gpu": "1",
"pods": "29"
}
}
nvidia-device-plugin の helmに渡すvalues.yamlとConfigMap の設定を行い、helm upgrade を行います。
$ cat nvidia-device-plugin-helm-values.yaml
gfd:
enabled: true
nfd:
worker:
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
- operator: "Exists"
$ cat ndp_helm_upgrade.sh
helm upgrade -i nvidia-device-plugin nvdp/nvidia-device-plugin \
--version=0.14.5 \
--namespace nvidia-device-plugin \
--values nvidia-device-plugin-helm-values.yaml \
--set config.name=time-slicing-config
$ ./ndp_helm_upgrade.sh
Time Slicing を行う設定をConfigMapとして投入します。今回はレプリカ数を4にして設定しています。
$ cat time-slicing-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: time-slicing-config
namespace: nvidia-device-plugin
data:
any: |-
version: v1
flags:
migStrategy: none
sharing:
timeSlicing:
renameByDefault: false
failRequestsGreaterThanOne: false
resources:
- name: nvidia.com/gpu
replicas: 4
$ kubectl apply -f time-slicing-config.yaml
ノードで利用できるGPUの数を確認します。"nvidia.com/gpu": "4"
と利用出来る数が増加したことを確認できます。
$ kubectl get nodes -o json | jq -r '.items[] | select(.status.capacity."nvidia.com/gpu" != null) | {name: .metadata.name, capacity: .status.capacity}'
{
"name": "ip-xxx.us-west-2.compute.internal",
"capacity": {
"cpu": "4",
"ephemeral-storage": "104845292Ki",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "16069060Ki",
"nvidia.com/gpu": "4",
"pods": "29"
}
}
■ Time Slicing の確認
それでは、サンプルのアプリケーションを実行して、増加させたGPUのレプリカ数をどう使用するのか確認してみます。
サンプルのアプリケーションは、参考にした記事にあるeks-gpu-sharing-demoを使用しています。今回はアプリケーションのレプリカ数は2に設定して適用しています。
$ kubectl create ns gpu-demonamespace/gpu-demo created
$ kubectl apply -f example-train.yaml
$ kubectl get pods -n gpu-demo
NAME READY STATUS RESTARTS AGE
tensorflow-cifar10-deployment-5df5f55756-k8vgp 1/1 Running 2 (3m50s ago) 6m57s
tensorflow-cifar10-deployment-5df5f55756-l4wfm 1/1 Running 2 (3m50s ago) 6m57s
sh-4.2$ nvidia-smi
Fri Jun 28 07:53:07 2024
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.183.01 Driver Version: 535.183.01 CUDA Version: 12.2 |
|-----------------------------------------+----------------------+----------------------+
| 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 Tesla T4 On | 00000000:00:1E.0 Off | 0 |
| N/A 39C P0 43W / 70W | 14903MiB / 15360MiB | 16% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 77803 C python 14074MiB |
| 0 N/A N/A 78014 C python 826MiB |
+---------------------------------------------------------------------------------------+
nvidia-smiコマンドで確認すると確かに、pythonのプロセスが2つ動作していることが確認出来ています。
Time Slicingを使用して、GPUへの共有アクセスを提供することを簡単に確認しました。
まとめ
今回は、EKS上でGPUを扱う生成AIソリューションのデプロイを試し、実際にGPUがどう使われてどう見えるのかをまとめてみました。
実際に検証してみることで、机上ベースより詳細な情報を確認することが出来ました。
また発展として、MLOpsなどを効率的に回していくための様々なツール群を調査してみるのも面白いと考えています。
本書の記載が読者のお役に立てれば幸いです。