探索Kubernetes與AI的結合:PyTorch訓練任務在k8s上調度實踐
概述
Kubernetes的核心優勢在于其能夠提供一個可擴展、靈活且高度可配置的平臺,使得應用程序的部署、擴展和管理變得前所未有的簡單。通用計算能力方面的應用已經相對成熟,云原生化的應用程序、數據庫和其他服務可以輕松部署在Kubernetes環境中,實現高可用性和彈性。
然而,當涉及到異構計算資源時,情形便開始變得復雜。異構計算資源如GPU、FPGA和NPU,雖然能夠提供巨大的計算優勢,尤其是在處理特定類型的計算密集型任務時,但它們的集成和管理卻不像通用計算資源那樣簡單。由于硬件供應商提供的驅動和管理工具差異較大,Kubernetes在統一調度和編排這些資源方面還存在一些局限性。這不僅影響了資源的利用效率,也給開發者帶來了額外的管理負擔。
下面分享下如何在個人筆記本電腦上完成K8s GPU集群的搭建,并使用kueue、kubeflow、karmada在具有GPU節點的k8s集群上提交pytorch的訓練任務。
k8s支持GPU
- kubernetes對于GPU的支持是通過設備插件的方式來實現,需要安裝GPU廠商的設備驅動,通過POD調用GPU能力。
- Kind、Minikube、K3d等常用開發環境集群構建工具對于GPU的支持也各不相同,Kind暫不支持GPU,Minikube和K3d支持Linux環境下的NVIDIA的GPU
RTX3060搭建具有GPU的K8s
GPU K8s
先決條件
- Go 版本 v1.20+
- kubectl 版本 v1.19+
- Minikube 版本 v1.24.0+
- Docker 版本v24.0.6+
- NVIDIA Driver 最新版本
- NVIDIA Container Toolkit 最新版本
備注:
- ubuntu 系統的 RTX3060+顯卡(不能是虛擬機系統,除非你的虛擬機支持pve或則esxi顯卡直通功能), windows的wsl 是不支持的,因為wsl的Linux內核是一個自定義的內核,里面缺失很多內核模塊,導致NVIDIA的驅動調用有問題
- 需要Github、Google、Docker的代碼和倉庫訪問能力
GPU Docker
完成以上操作后,確認Docker具備GPU的調度能力,可以通過如下的方式來進行驗證
- 創建如下的docker compose 文件
services:
test:
image: nvidia/cuda:12.3.1-base-ubuntu20.04
command: nvidia-smi
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
- 使用Docker啟動cuda任務
docker compose up
Creating network "gpu_default" with the default driver
Creating gpu_test_1 ... done
Attaching to gpu_test_1
test_1 | +-----------------------------------------------------------------------------+
test_1 | | NVIDIA-SMI 450.80.02 Driver Version: 450.80.02 CUDA Version: 11.1 |
test_1 | |-------------------------------+----------------------+----------------------+
test_1 | | GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
test_1 | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
test_1 | | | | MIG M. |
test_1 | |===============================+======================+======================|
test_1 | | 0 Tesla T4 On | 00000000:00:1E.0 Off | 0 |
test_1 | | N/A 23C P8 9W / 70W | 0MiB / 15109MiB | 0% Default |
test_1 | | | | N/A |
test_1 | +-------------------------------+----------------------+----------------------+
test_1 |
test_1 | +-----------------------------------------------------------------------------+
test_1 | | Processes: |
test_1 | | GPU GI CI PID Type Process name GPU Memory |
test_1 | | ID ID Usage |
test_1 | |=============================================================================|
test_1 | | No running processes found |
test_1 | +-----------------------------------------------------------------------------+
gpu_test_1 exited with code 0
GPU Minikube
配置Minikube,啟動kubernetes集群
minikube start --driver docker --container-runtime docker --gpus all
驗證集群的GPU能力
- 確認節點具備GPU信息
kubectl describe node minikube
...
Capacity:
nvidia.com/gpu: 1
...
- 測試在集群中執行CUDA
$ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
restartPolicy: Never
containers:
- name: cuda-container
image: nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda10.2
resources:
limits:
nvidia.com/gpu: 1 # requesting 1 GPU
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
EOF
$ kubectl logs gpu-pod
[Vector addition of 50000 elements]
Copy input data from the host memory to the CUDA device
CUDA kernel launch with 196 blocks of 256 threads
Copy output data from the CUDA device to the host memory
Test PASSED
Done
使用kueue提交pytorch訓練任務
kueue簡介
kueue是k8s特別興趣小組(SIG)的一個開源項目,是一個基于 Kubernetes 的任務隊列管理系統,旨在簡化和優化 Kubernetes 中的作業管理。 主要具備以下功能:
- 作業管理:支持基于優先級的作業隊列,提供不同的隊列策略,如StrictFIFO和BestEffortFIFO。
- 資源管理:支持資源的公平分享和搶占,以及不同租戶之間的資源管理策略。
- 動態資源回收:一種釋放資源配額的機制,隨著作業的完成而動態釋放資源。
- 資源靈活性:在 ClusterQueue 和 Cohort 中支持資源的借用或搶占。
- 內置集成:內置支持常見的作業類型,如 BatchJob、Kubeflow 訓練作業、RayJob、RayCluster、JobSet 等。
- 系統監控:內置 Prometheus 指標,用于監控系統狀態。
- 準入檢查:一種機制,用于影響工作負載是否可以被接受。
- 高級自動縮放支持:與 cluster-autoscaler 的 provisioningRequest 集成,通過準入檢查進行管理。
- 順序準入:一種簡單的全或無調度實現。
- 部分準入:允許作業以較小的并行度運行,基于可用配額。
kueue的架構圖如下:
圖片
通過拓展workload來支持BatchJob、Kubeflow 訓練作業、RayJob、RayCluster、JobSet 等作業任務,通過ClusterQueue來共享LocalQueue資源,任務最終提交到LocalQueue進行調度和執行,而不同的ClusterQueue可以通過Cohort進行資源共享和通信,通過Cohort->ClusterQueue->LocalQueue->Node實現不同層級的資源共享已支持AI、ML等Ray相關的job在k8s集群中調度。 在kueue中區分管理員用戶和普通用戶,管理員用戶負責管理ResourceFlavor、ClusterQueue、LocalQueue等資源,以及管理資源池的配額(quota)。普通用戶負責提批處理任務或者各類的Ray任務。
運行PyTorch訓練任務
安裝kueue
需要k8s 1.22+,使用如下的命令安裝
kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.6.0/manifests.yaml
配置集群配額
git clone https://github.com/kubernetes-sigs/kueue.git && cd kueue
kubectl apply -f examples/admin/single-clusterqueue-setup.yaml
其實不安裝kueue也是能夠提交Pytorch的訓練任務,因為這個PytorchJob是kubeflow traning-operator的一個CRD,但是安裝kueue的好處是,他可以支持更多任務。
除了kubeflow的任務,還可以支持kuberay的任務,并且它內置了管理員角色,方便對于集群的配置和集群的資源做限額和管理,支持優先級隊列和任務搶占,更好的支持AI、ML等任務的調度和管理。上面安裝的集群配額就是設置任務的限制,避免一些負載過高的任務提交,在任務執行前快速失敗。
安裝kubeflow的training-operator
kubectl apply -k "github.com/kubeflow/training-operator/manifests/overlays/standalone"
運行FashionMNIST的訓練任務
FashionMNIST 數據集是一個用于圖像分類任務的常用數據集,類似于經典的 MNIST 數據集,但是它包含了更加復雜的服裝類別。
- FashionMNIST 數據集包含了 10 個類別的服裝圖像,每個類別包含了 6,000 張訓練圖像和 1,000 張測試圖像,共計 60,000 張訓練圖像和 10,000 張測試圖像。
- 每張圖像都是 28x28 像素的灰度圖像,表示了不同類型的服裝,如 T 恤、褲子、襯衫、裙子等。
在kueue上提交PyTorchJob類型的任務,為了能夠保存訓練過程中的日志和結果,我們需要使用openebs的hostpath來將訓練過程的數據保存到節點上,因為任務訓練結束后,不能登錄到節點查看。所以創建如下的資源文件
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pytorch-results-pvc
spec:
storageClassName: openebs-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: "kubeflow.org/v1"
kind: PyTorchJob
metadata:
name: pytorch-simple
namespace: kubeflow
spec:
pytorchReplicaSpecs:
Master:
replicas: 1
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch-mnist:2.2.1-cuda12.1-cudnn8-runtime
imagePullPolicy: IfNotPresent
command:
- "python3"
- "/opt/pytorch-mnist/mnist.py"
- "--epochs=10"
- "--batch-size"
- "32"
- "--test-batch-size"
- "64"
- "--lr"
- "0.01"
- "--momentum"
- "0.9"
- "--log-interval"
- "10"
- "--save-model"
- "--log-path"
- "/results/master.log"
volumeMounts:
- name: result-volume
mountPath: /results
volumes:
- name: result-volume
persistentVolumeClaim:
claimName: pytorch-results-pvc
Worker:
replicas: 1
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch-mnist:2.2.1-cuda12.1-cudnn8-runtime
imagePullPolicy: IfNotPresent
command:
- "python3"
- "/opt/pytorch-mnist/mnist.py"
- "--epochs=10"
- "--batch-size"
- "32"
- "--test-batch-size"
- "64"
- "--lr"
- "0.01"
- "--momentum"
- "0.9"
- "--log-interval"
- "10"
- "--save-model"
- "--log-path"
- "/results/worker.log"
volumeMounts:
- name: result-volume
mountPath: /results
volumes:
- name: result-volume
persistentVolumeClaim:
claimName: pytorch-results-pvc
其中pytorch-mnist:v1beta1-45c5727是一個在pytorch上運行CNN訓練任務的代碼,具體的代碼如下:
from __future__ import print_function
import argparse
import logging
import os
from torchvision import datasets, transforms
import torch
import torch.distributed as dist
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
WORLD_SIZE = int(os.environ.get("WORLD_SIZE", 1))
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 20, 5, 1)
self.conv2 = nn.Conv2d(20, 50, 5, 1)
self.fc1 = nn.Linear(4*4*50, 500)
self.fc2 = nn.Linear(500, 10)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2, 2)
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2, 2)
x = x.view(-1, 4*4*50)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.log_softmax(x, dim=1)
def train(args, model, device, train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.nll_loss(output, target)
loss.backward()
optimizer.step()
if batch_idx % args.log_interval == 0:
msg = "Train Epoch: {} [{}/{} ({:.0f}%)]\tloss={:.4f}".format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item())
logging.info(msg)
niter = epoch * len(train_loader) + batch_idx
def test(args, model, device, test_loader, epoch):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.nll_loss(output, target, reductinotallow="sum").item() # sum up batch loss
pred = output.max(1, keepdim=True)[1] # get the index of the max log-probability
correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader.dataset)
logging.info("{{metricName: accuracy, metricValue: {:.4f}}};{{metricName: loss, metricValue: {:.4f}}}\n".format(
float(correct) / len(test_loader.dataset), test_loss))
def should_distribute():
return dist.is_available() and WORLD_SIZE > 1
def is_distributed():
return dist.is_available() and dist.is_initialized()
def main():
# Training settings
parser = argparse.ArgumentParser(descriptinotallow="PyTorch MNIST Example")
parser.add_argument("--batch-size", type=int, default=64, metavar="N",
help="input batch size for training (default: 64)")
parser.add_argument("--test-batch-size", type=int, default=1000, metavar="N",
help="input batch size for testing (default: 1000)")
parser.add_argument("--epochs", type=int, default=10, metavar="N",
help="number of epochs to train (default: 10)")
parser.add_argument("--lr", type=float, default=0.01, metavar="LR",
help="learning rate (default: 0.01)")
parser.add_argument("--momentum", type=float, default=0.5, metavar="M",
help="SGD momentum (default: 0.5)")
parser.add_argument("--no-cuda", actinotallow="store_true", default=False,
help="disables CUDA training")
parser.add_argument("--seed", type=int, default=1, metavar="S",
help="random seed (default: 1)")
parser.add_argument("--log-interval", type=int, default=10, metavar="N",
help="how many batches to wait before logging training status")
parser.add_argument("--log-path", type=str, default="",
help="Path to save logs. Print to StdOut if log-path is not set")
parser.add_argument("--save-model", actinotallow="store_true", default=False,
help="For Saving the current Model")
if dist.is_available():
parser.add_argument("--backend", type=str, help="Distributed backend",
choices=[dist.Backend.GLOO, dist.Backend.NCCL, dist.Backend.MPI],
default=dist.Backend.GLOO)
args = parser.parse_args()
# Use this format (%Y-%m-%dT%H:%M:%SZ) to record timestamp of the metrics.
# If log_path is empty print log to StdOut, otherwise print log to the file.
if args.log_path == "":
logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
datefmt="%Y-%m-%dT%H:%M:%SZ",
level=logging.DEBUG)
else:
logging.basicConfig(
format="%(asctime)s %(levelname)-8s %(message)s",
datefmt="%Y-%m-%dT%H:%M:%SZ",
level=logging.DEBUG,
filename=args.log_path)
use_cuda = not args.no_cuda and torch.cuda.is_available()
if use_cuda:
print("Using CUDA")
torch.manual_seed(args.seed)
device = torch.device("cuda" if use_cuda else "cpu")
if should_distribute():
print("Using distributed PyTorch with {} backend".format(args.backend))
dist.init_process_group(backend=args.backend)
kwargs = {"num_workers": 1, "pin_memory": True} if use_cuda else {}
train_loader = torch.utils.data.DataLoader(
datasets.FashionMNIST("./data",
train=True,
download=True,
transform=transforms.Compose([
transforms.ToTensor()
])),
batch_size=args.batch_size, shuffle=True, **kwargs)
test_loader = torch.utils.data.DataLoader(
datasets.FashionMNIST("./data",
train=False,
transform=transforms.Compose([
transforms.ToTensor()
])),
batch_size=args.test_batch_size, shuffle=False, **kwargs)
model = Net().to(device)
if is_distributed():
Distributor = nn.parallel.DistributedDataParallel if use_cuda \
else nn.parallel.DistributedDataParallelCPU
model = Distributor(model)
optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)
for epoch in range(1, args.epochs + 1):
train(args, model, device, train_loader, optimizer, epoch)
test(args, model, device, test_loader, epoch)
if (args.save_model):
torch.save(model.state_dict(), "mnist_cnn.pt")
if __name__ == "__main__":
main()
將訓練任務提交到k8s集群
kubectl apply -f sample-pytorchjob.yaml
提交成功后會出現兩個訓練任務,分別是master和worker的訓練任務,如下:
? ~ kubectl get po
NAME READY STATUS RESTARTS AGE
pytorch-simple-master-0 1/1 Running 0 5m5s
pytorch-simple-worker-0 1/1 Running 0 5m5s
再查看宿主機的顯卡運行情況,發現能夠明顯聽到集群散熱的聲音,運行nvida-smi可以看到有兩個Python任務在執行,等待執行完后,會生成模型文件mnist_cnn.pt。
? ~ nvidia-smi
Mon Mar 4 10:18:39 2024
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.161.07 Driver Version: 535.161.07 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 NVIDIA GeForce RTX 3060 ... Off | 00000000:01:00.0 Off | N/A |
| N/A 39C P0 24W / 80W | 753MiB / 6144MiB | 1% Default |
| | | N/A |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 1674 G /usr/lib/xorg/Xorg 219MiB |
| 0 N/A N/A 1961 G /usr/bin/gnome-shell 47MiB |
| 0 N/A N/A 3151 G gnome-control-center 2MiB |
| 0 N/A N/A 4177 G ...irefox/3836/usr/lib/firefox/firefox 149MiB |
| 0 N/A N/A 14476 C python3 148MiB |
| 0 N/A N/A 14998 C python3 170MiB |
+---------------------------------------------------------------------------------------+
在提交和執行任務的時候,要注意cuda的版本和pytorch的版本要保持對應,官方demo中的dockerfile是這樣的
FROM pytorch/pytorch:1.0-cuda10.0-cudnn7-runtime
ADD examples/v1beta1/pytorch-mnist /opt/pytorch-mnist
WORKDIR /opt/pytorch-mnist
# Add folder for the logs.
RUN mkdir /katib
RUN chgrp -R 0 /opt/pytorch-mnist \
&& chmod -R g+rwX /opt/pytorch-mnist \
&& chgrp -R 0 /katib \
&& chmod -R g+rwX /katib
ENTRYPOINT ["python3", "/opt/pytorch-mnist/mnist.py"]
這個要求你要使用pytorch1.0和cuda10的版本進行訓練,而我們實際的使用的cuda12,所以直接用這個基礎鏡像去構建是不行,任務會一致處于運行中,永遠結束不了,為了能夠避免每次重復下載mnist的數據集,我們需要提前下載然后將數據集打包到容器里面,所以修改后的Dockerfile如下:
FROM pytorch/pytorch:2.2.1-cuda12.1-cudnn8-runtime
ADD . /opt/pytorch-mnist
WORKDIR /opt/pytorch-mnist
# Add folder for the logs.
RUN mkdir /katib
RUN chgrp -R 0 /opt/pytorch-mnist \
&& chmod -R g+rwX /opt/pytorch-mnist \
&& chgrp -R 0 /katib \
&& chmod -R g+rwX /katib
ENTRYPOINT ["python3", "/opt/pytorch-mnist/mnist.py"]
使用最終的訓練結束后mnist_cnn.pt模型文件,進行模型預測和測試得到的結果如下:{metricName: accuracy, metricValue: 0.9039};{metricName: loss, metricValue: 0.2756}, 即這個模型的準確性為90.39%,模型損失值為0.2756,說明我們訓練的模型在FashionMNIST 數據集上表現良好,在訓練過程中epoch參數比較重要,它代表訓練的輪次,過小會出現效果不好,過大會出現過擬合問題,在測試的時候我們可以適當調整這個參數來控制模型訓練運行的時間。
通過kueue通過webhook的方式對于的進行AI、ML等GPU任務進行準入控制和資源限制,提供租戶隔離的概念,為k8s對于GPU的支持提供了根據豐富的場景。如果筆記本的顯卡能力夠強,可以將chatglm等開源的大模型部署到k8s集群中,從而搭建自己個人離線專屬的大模型服務。
karmada多集群提交pytorch訓練任務
創建多集群k8s
在多集群的管控上,我們可以使用karamda來實現管理,其中member2作為控制面主集群,member3、member4作為子集群。在完成minikube的nvidia的GPU配置后,使用如下的命令創建3個集群。
docker network create --driver=bridge --subnet=xxx.xxx.xxx.0/24 --ip-range=xxx.xxx.xxx.0/24 minikube-net
minikube start --driver docker --cpus max --memory max --container-runtime docker --gpus all --network minikube-net --subnet='xxx.xxx.xxx.xxx' --mount-string="/run/udev:/run/udev" --mount -p member2 --static-ip='xxx.xxx.xxx.xxx'
minikube start --driver docker --cpus max --memory max --container-runtime docker --gpus all --network minikube-net --subnet='xxx.xxx.xxx.xxx' --mount-string="/run/udev:/run/udev" --mount -p member3 --static-ip='xxx.xxx.xxx.xxx'
minikube start --driver docker --cpus max --memory max --container-runtime docker --gpus all --network minikube-net --subnet='xxx.xxx.xxx.xxx' --mount-string="/run/udev:/run/udev" --mount -p member4 --static-ip='xxx.xxx.xxx.xxx'
? ~ minikube profile list
|---------|-----------|---------|-----------------|------|---------|---------|-------|--------|
| Profile | VM Driver | Runtime | IP | Port | Version | Status | Nodes | Active |
|---------|-----------|---------|-----------------|------|---------|---------|-------|--------|
| member2 | docker | docker | xxx.xxx.xxx.xxx | 8443 | v1.28.3 | Running | 1 | |
| member3 | docker | docker | xxx.xxx.xxx.xxx | 8443 | v1.28.3 | Running | 1 | |
| member4 | docker | docker | xxx.xxx.xxx.xxx | 8443 | v1.28.3 | Running | 1 | |
|---------|-----------|---------|-----------------|------|---------|---------|-------|--------|
在3個集群分別安裝Training Operator、karmada,并且需要在karmada的控制面安裝Training Operator,這樣才能在控制面提交pytorchjob的任務。由于同一個pytorch任務分布在不同的集群在服務發現和master、worker交互通信會存在困難,所以我們這邊只演示將同一個pytorch任務提交到同一個集群,通過kosmos的控制面實現將多個pytorch任務調度到不同的集群完成訓練。 在karmada的控制面上創建訓練任務
apiVersion: "kubeflow.org/v1"
kind: PyTorchJob
metadata:
name: pytorch-simple
namespace: kubeflow
spec:
pytorchReplicaSpecs:
Master:
replicas: 1
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch-mnist:2.2.1-cuda12.1-cudnn8-runtime
imagePullPolicy: IfNotPresent
command:
- "python3"
- "/opt/pytorch-mnist/mnist.py"
- "--epochs=30"
- "--batch-size"
- "32"
- "--test-batch-size"
- "64"
- "--lr"
- "0.01"
- "--momentum"
- "0.9"
- "--log-interval"
- "10"
- "--save-model"
- "--log-path"
- "/opt/pytorch-mnist/master.log"
Worker:
replicas: 1
restartPolicy: OnFailure
template:
spec:
containers:
- name: pytorch
image: pytorch-mnist:2.2.1-cuda12.1-cudnn8-runtime
imagePullPolicy: IfNotPresent
command:
- "python3"
- "/opt/pytorch-mnist/mnist.py"
- "--epochs=30"
- "--batch-size"
- "32"
- "--test-batch-size"
- "64"
- "--lr"
- "0.01"
- "--momentum"
- "0.9"
- "--log-interval"
- "10"
- "--save-model"
- "--log-path"
- "/opt/pytorch-mnist/worker.log"
在karmada的控制面上創建傳播策略
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: pytorchjob-propagation
namespace: kubeflow
spec:
resourceSelectors:
- apiVersion: kubeflow.org/v1
kind: PyTorchJob
name: pytorch-simple
namespace: kubeflow
placement:
clusterAffinity:
clusterNames:
- member3
- member4
replicaScheduling:
replicaDivisionPreference: Weighted
replicaSchedulingType: Divided
weightPreference:
staticWeightList:
- targetCluster:
clusterNames:
- member3
weight: 1
- targetCluster:
clusterNames:
- member4
weight: 1
然后我們就可以看到這個訓練任務成功的提交到member3和member4的子集群上執行任務
? pytorch kubectl karmada --kubeconfig ~/karmada-apiserver.config get po -n kubeflow
NAME CLUSTER READY STATUS RESTARTS AGE
pytorch-simple-master-0 member3 0/1 Completed 0 7m51s
pytorch-simple-worker-0 member3 0/1 Completed 0 7m51s
training-operator-64c768746c-gvf9n member3 1/1 Running 0 165m
pytorch-simple-master-0 member4 0/1 Completed 0 7m51s
pytorch-simple-worker-0 member4 0/1 Completed 0 7m51s
training-operator-64c768746c-nrkdv member4 1/1 Running 0 168m
總結
通過搭建本地的k8s GPU環境,可以方便的進行AI相關的開發和測試,也能充分利用閑置的筆記本GPU性能。利用kueue、karmada、kuberay和ray等框架,讓GPU等異構算力調度在云原生成為可能。目前只是在單k8s集群完成訓練任務的提交和運行,在實際AI、ML或者大模型的訓練其實更加復雜,組網和技術架構也需要進行精心的設計。要實現千卡、萬卡的在k8s集群的訓練和推理解決包括但不僅限于
- 網絡通信性能:傳統的數據中心網絡一般是10Gbps,這個在大模型訓練和推理中是捉襟見肘的,所以需要構建RDMA網絡(Remote Direct Memory Access)
- GPU調度和配置:多云多集群場景下,如何進行GPU的調度和管理
- 監控和調試:如何進行有效地監控和調試訓練任務,以及對異常情況進行處理和服務恢復
參考資料
1. [Go](https://go.dev/)
2. [Docker](https://docker.com)
3. [minikube](https://minikube.sigs.k8s.io/docs/tutorials/nvidia/)
4. [nvidia](https://docs.nvidia.com/datacenter/tesla/tesla-installation-notes/index.html)
5. [kubernetes](https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/)
6. [kueue](https://github.com/kubernetes-sigs/kueue)
7. [kubeflow](https://github.com/kubeflow/training-operator)
8. [ray](https://github.com/ray-project/ray)
9. [kuberay](https://github.com/ray-project/kuberay)
10. [karmada](https://github.com/karmada-io/karmada)
11. [kind](https://kind.sigs.k8s.io/)
12. [k3s](https://github.com/k3s-io/k3s)
13. [k3d](https://github.com/k3d-io/k3d)