Published on

k8s-device-plugin으로 NVIDIA GPU Multi-Process Service 사용하기

Authors
  • avatar
    Name
    Jay
    Twitter

네이버 공공 클라우드에서 사용할 수 있는 옵션

NVIDIA GPU는 자원을 공유하기 위한 방법으로 아래와 같은 방법들을 제공한다.

  • Time slicing
  • Multi-Process Service
  • Multi-instance GPU

네이버 공공 클라우드에서 사용한 가능한 GPU에는 Tesla V100(arch: Volta)과 Tesla T4(arch: Turing)가 있다. Hardware단에서 Partition을 하여 격리할 수 있는 MIG(Multi-instance GPU)A100과 같은 Ampere 아키텍쳐에서 가능하다. 따라서 T4와 V100에서는 MIG를 사용할 수가 없다. 대신에 MPS(Multi-Process Service)는 T4와 V100 모두에서 사용이 가능하다. Time slicing 방식은 GPU를 많이 사용하는 다수의 프로세스가 공유해서 사용할 때, Context Switch에 의한 overload가 상당히 발생할 수 있다. 반면에 MPS는 하나의 Cuda Context를 공유하기 때문에, Time slicing보다 Context switch에 대한 overload를 줄일 수 있다. 하지만 이렇게 하나의 Cuda Context를 공유하기 때문에 error가 발생했을 때 모든 MPS client에 영향을 미칠 수 있다. (Volta MPS에서는 좀 제한적인 영향을 미치는 것 같다.)

k8s-device-plugin

Kubernetes에서 GPU를 kubelet이 인지하고 사용할 수 있도록, NVIDIA에서 공식적으로 device plugin을 제공하고 있다. 그리고 Helm chart를 같이 제공한다. 따라서 Helm으로 k8s-device-plugin을 설치를 했다.

helm repo add nvdp https://nvidia.github.io/k8s-device-plugin

원하는 Worker node에 Daemonset이 실행될 수 있도록 affinitytolerations 설정을 추가하였다.

values.yml

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
        - matchExpressions:
            - key: serverType
              operator: In
              values:
                - gpu
tolerations:
  - key: nvidia.com/gpu
    operator: Exists
    effect: NoSchedule

그리고 device plugin 설정파일에 MPS 설정을 추가하였다.

config.yaml

version: v1
sharing:
  mps:
    resources:
      - name: nvidia.com/gpu
        replicas: 10

NVIDIA의 k8s-device-plugin에서 v0.15.0부터 MPS를 지원하기 시작했다. 지금 이 글을 쓰는 시점에 v0.15.0-rc.2가 최신 버전이고, 아직 rc 환경으로 제공되고 있다. 따라서 Helm으로 설치를 할 때 아래와 같이 version flag를 통해서 0.15.0-rc.2를 설정해준다.

helm install k8s-device-plugin nvdp/nvidia-device-plugin \
--version 0.15.0-rc.2 \
--namespace nvidia-device-plugin \
--create-namespace \
--values values.yaml \
--set-file config.map.config=config.yaml

Helm으로 설치하고 나면 아래와 같이 두개의 Daemonset Object가 만들어진 것을 확인할 수 있다. Deamonset k8s-device-plugin-nvidia-device-plugin-mps-control-daemon가 실행되기 위해서는 Worker Node에 nvidia.com/mps.capable=true가 설정되어야 한다.

$ kubectl get ds -n nvidia-device-plugin
NAMESPACE              NAME                                                        DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR
nvidia-device-plugin   k8s-device-plugin-nvidia-device-plugin                      1         1         1       1            1           <none>                        5s
nvidia-device-plugin   k8s-device-plugin-nvidia-device-plugin-mps-control-daemon   0         0         0       0            0           nvidia.com/mps.capable=true   5s

해당 Label때문에 MPS daemon이 실행되지 못했고, 그래서 k8s-device-plugin-nvidia-device-plugin Pod에 아래와 같은 에러 로그가 남게 되었다.

Failed to start plugin: error waiting for MPS daemon: error checking MPS daemon health: failed to send command to MPS daemon: exit status 1

이제 GPU worker node에 label을 추가해주면 MPS daemon이 실행되고, 정상적으로 MPS가 적용된다.

kubectl label node gpu-node nvidia.com/mps.capable=true

MPS에서 replica를 10으로 설정했기 때문에, Node의 nvida.com/gpu 자원을 확인하면 1에서 10으로 증가한 것을 확인할 수 있다.

$ kubectl describe node gpu-node
Allocatable:
  cpu:                3920m
  ephemeral-storage:  47267522073
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             17413608Ki
  nvidia.com/gpu:     10
  pods:               110

해당 노드에 nvida.com/gpu resource request가 1인 Pod 하나만 스케쥴링이 될 수 있었는데, 이제 10으로 늘어나서 다수의 Pod를 해당 노드에서 실행할 수 있게 되었다.

궁금증

MPS를 통해서 GPU core 1을 10개로 나누면, 예를 들어서 하나의 Container는 8개로 요청하여 GPU 자원을 80%를 사용하고, 다른 하나는 2개로 요청하여 20%를 사용하도록 하고 싶었다. 하지만 v0.15.0-rc.2 release note를 보면 failRequestsGreaterThanOnetrue로 설정되어 있는 것을 확인할 수 있다. 그래서 1를 초과하여 요청할 수가 없다.

이제 MPS로 10개로 나눴을 때 GPU와 Memory 자원을 1/10만큼 사용할 수 있기 때문에, container가 1/10보다 더 사용하도록 설정할 수가 없다.

Furthermore, each of these resources -- either nvidia.com/gpu or nvidia.com/gpu.shared -- would have access to the same fraction (1/10) of the total memory and compute resources of the GPU.

이 글을 작성하는 시점으로 3일전에 v0.15.0이 release 되었는데, 아래와 같은 내용이 설명되어 있다.

It is not possible to "combine" MPS GPU requests to allow for access to more memory by a single container.

따라서 GPU 자원을 계속 사용하지 않아서 두 개의 Container가 GPU 자원을 공유하고, 가능하면 Full로 GPU 자원을 사용하고 싶다면, MPS 방법은 적합하지 않다. 이럴때는 Context switch가 문제가 되지 않는 범위에서 time slicing으로 자원을 공유해야 하는 것인가?🤔