前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >GPUManager虚拟化方案

GPUManager虚拟化方案

原创
作者头像
何猛
修改2020-10-14 18:52:09
20K86
修改2020-10-14 18:52:09
举报
文章被收录于专栏:TKEStackTKEStack

GPU虚拟化简介

GPU是一种用于矩阵计算的PCIe设备,一般用于解码、渲染和科学计算等并行计算场景,不同场景对GPU使用方式不同,使用的加速库也各不相同,本文提到的GPU虚拟化主要针对科学计算场景,使用的加速库为nvidia cuda。

从用户角度,GPU虚拟化可以简单分为两种类型,虚拟机层面的虚拟化和容器层面的虚拟化。虚拟机层面的虚拟化是将GPU硬件设备虚拟给多个KVM虚拟机使用,各个虚拟机独立安装驱动,这样既保证了虚拟机内的GPU功能完备又实现GPU资源的隔离和共享,唯一缺点就是资源损耗相对较大。容器层面的虚拟化则有两个思路,一个是将GPU纳入cgroup管理,目前尚未有成熟的提案,短期内难以实现,二是基于GPU驱动封装实现,用户根据需要对驱动的某些关键接口(如显存分配、cuda thread创建等)进行封装劫持,在劫持过程中限制用户进程对计算资源的使用,此类方案缺点是兼容性依赖于厂商驱动,但是整体方案较为轻量化,性能损耗极小。GPUManager即为第二类容器层面的虚拟化方案,本文主要介绍GPUManager方案原理和部署流程。

GPUManager架构介绍

GPUManager是一个运行在k8s上的GPU虚拟化方案,了解GPUManager方案架构前我们先看一下k8s对异构资源的支持。1.6版本开始,k8s的in-tree代码里开始引入Nvidia GPU相关的代码,但不支持GPU调度无法在实际生产环境中使用,为了满足越来越多的异构资源(如GPU、Infiniband、FPGA等)使用需求,1.8版本社区提出了Extended Resource和Device Plugin方案,以OutOfTree形式支持异构资源的调度和映射。

Device Plugin:K8s制定设备插件接口规范,定义异构资源的上报和分配,设备厂商只需要实现相应的API接口,无需修改kubelet源码即可实现对其他硬件设备的支持。

Extended Resource:Scheduler可以根据Pod的创建删除计算资源可用量,而不再局限于CPU和内存的资源统计,进而将有特殊资源需求的Pod调度到相应的节点上。

通过Device Plugin 异构资源调度流程如下:

  1. Device plugin 向kubelet上报当前节点资源情况
  2. 用户通过yaml文件创建负载,定义Resource Request
  3. kube-scheduler根据从kubelet同步到的资源信息和Pod的资源请求,为Pod绑定合适的节点
  4. kubelet监听到绑定到当前节点的Pod,调用Device plugin的allocate接口为Pod分配设备
  5. kubelet启动Pod内的容器,将设备映射给容器
图1. 异构资源调度示意图
图1. 异构资源调度示意图

至此,第三方设备厂商如Nvidia、AMD、Intel,可以通过各自的Device Plugin满足用户在k8s上使用异构资源的需求,Nvidia还针对GPU实现了自己的docker runtime以更优雅的形式解决设备映射和驱动目录映射问题。然而Nvidia的容器层GPU方案仅支持将整块卡映射到容器,无法在容器之间共享同一张卡,而GPU卡作为专用芯片算力强大且价格昂贵,无法共享情况下往往造成大量资源浪费,为此腾讯基于k8s的device plugin架构实现了自己的Plugin GPUManager。

GPUManager是腾讯自研的容器层GPU虚拟化方案,除兼容Nvidia 官方插件的GPU资源管理功能外,还增加碎片资源调度、GPU调度拓扑优化、GPU资源Quota等功能,在容器层面实现了GPU资源的化整为零,而在原理上仅使用了wrap library和linux动态库链接技术,就实现了GPU 算力和显存的上限隔离。

在工程设计上,GPUManager方案包括三个部分,cuda封装库vcuda、k8s device plugin 插件gpu-manager-daemonset和k8s调度插件gpu-quota-admission。

vcuda库是一个对nvidia-ml和libcuda库的封装库,通过劫持容器内用户程序的cuda调用限制当前容器内进程对GPU和显存的使用。

图2. vcuda调用示例
图2. vcuda调用示例

gpu-manager-daemonset是标准的k8s device plugin,实现了GPU拓扑感知、设备和驱动映射等功能。GPUManager支持共享和独占两种模式,当负载里tencent.com/vcuda-core request 值在0~100情况下,采用共享模式调度,优先将碎片资源集中到一张卡上,当负载里的tencent.com/vcuda-core request为100的倍数时,采用独占模式调度,gpu-manager-daemonset会根据GPU拓扑结构生成GPU卡的拓扑树,选择最优的结构(距离最短的叶子节点)进行调度分配。需要注意的是GPUManager仅支持0~100和100的整数倍的GPU需求调度,无法支持150,220类的非100整数倍的GPU需求调度。

图3. GPU 拓扑结构
图3. GPU 拓扑结构

gpu-quota-admission是一个k8s Scheduler extender,实现了Scheduler的predicates接口,kube-scheduler在调度tencent.com/vcuda-core资源请求的Pod时,predicates阶段会调用gpu-quota-admission的predicates接口对节点进行过滤和绑定,同时gpu-quota-admission提供了GPU资源池调度功能,解决不同类型的GPU在namespace下的配额问题。

GPUManager整体方案如下:

图4. GPUManager方案
图4. GPUManager方案

在GPU数据采集方面,GPUManager通过grpc-gateway的方式对外暴露了metric接口,供用户采集gpu使用信息,目前支持:

数据种类:【显存request(总量)/用量】和【算力(utilization)(总量)/用量】

数据维度:容器名、命名空间、pod名、节点ip、gpu卡号

数据输出格式:prometheus采集格式,支持直接对接到prometheus。

手动获取metric数据方式如下(需要先安装socat):

kubectl port-forward svc/gpu-manager-metric -n kube-system 5678:5678

图5. metric接口结果
图5. metric接口结果

方案优缺点及同类方案对比

方案优点:

  1. 同时支持碎片和整卡调度,提高GPU资源利用率
  2. 支持同一张卡上容器间GPU和显存的使用隔离
  3. 基于拓扑感知,提供最优的调度策略
  4. 对用户程序无侵入,用户无感

方案缺点:

  1. 驱动和加速库的兼容性依赖于厂商
  2. 存在约5%的性能损耗

同类方案对比:

厂商方案

驱动管理

显存调度

显存隔离

算力隔离

拓扑感知

资源Quota

Nvidiak8s-device-plugin

支持

不支持

不支持

不支持

支持

不支持

华为volcano

不支持

支持

不支持

不支持

不支持

支持

阿里gpu-share

依赖nvidia-runtime

支持

不支持

不支持

不支持

不支持

腾讯gpu-manager

支持

支持

支持

支持

支持

支持

GPUManager部署

在TKEStack或者TKE企业版里支持以扩展组件形式控制台点击部署,原生k8s集群可以按如下步骤手动部署。

手动部署操作流程:

  1. 部署gpu-manager-daemonset和gpu-quota-admission服务,命令如下:

kubectl apply -f gpu-manager.yaml

gpu-manager.yaml文件内容:

代码语言:javascript
复制
apiVersion: v1
kind: ConfigMap
metadata:
  name: gpu-quota-admission
  namespace: kube-system
data:
  gpu-quota-admission.config: |
    {
         "QuotaConfigMapName": "gpuquota",
         "QuotaConfigMapNamespace": "kube-system",
         "GPUModelLabel": "gaia.tencent.com/gpu-model",
         "GPUPoolLabel": "gaia.tencent.com/gpu-pool"
     }

---
apiVersion: v1
kind: Service
metadata:
  name: gpu-quota-admission
  namespace: kube-system
spec:
  ports:
  - port: 3456
    protocol: TCP
    targetPort: 3456
  selector:
    k8s-app: gpu-quota-admission
  type: ClusterIP

---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: gpu-quota-admission
  name: gpu-quota-admission
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: gpu-quota-admission
  template:
    metadata:
      labels:
        k8s-app: gpu-quota-admission
      namespace: kube-system
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - preference:
              matchExpressions:
              - key: node-role.kubernetes.io/master
                operator: Exists
            weight: 1
      containers:
      - env:
        - name: LOG_LEVEL
          value: "4"
        - name: EXTRA_FLAGS
          value: --incluster-mode=true
        image: ccr.ccs.tencentyun.com/tkeimages/gpu-quota-admission:latest
        imagePullPolicy: IfNotPresent
        name: gpu-quota-admission
        ports:
        - containerPort: 3456
          protocol: TCP
        resources:
          limits:
            cpu: "2"
            memory: 2Gi
          requests:
            cpu: "1"
            memory: 1Gi
        volumeMounts:
        - mountPath: /root/gpu-quota-admission/
          name: config
      dnsPolicy: ClusterFirstWithHostNet
      initContainers:
      - command:
        - sh
        - -c
        - ' mkdir -p /etc/kubernetes/ && cp /root/gpu-quota-admission/gpu-quota-admission.config
          /etc/kubernetes/'
        image: busybox
        imagePullPolicy: Always
        name: init-kube-config
        securityContext:
          privileged: true
        volumeMounts:
        - mountPath: /root/gpu-quota-admission/
          name: config
      priority: 2000000000
      priorityClassName: system-cluster-critical
      restartPolicy: Always
      serviceAccount: gpu-manager
      serviceAccountName: gpu-manager
      terminationGracePeriodSeconds: 30
      tolerations:
      - effect: NoSchedule
        key: node-role.kubernetes.io/master
      volumes:
      - configMap:
          defaultMode: 420
          name: gpu-quota-admission
        name: config

---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: gpu-manager
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: gpu-manager
  namespace: kube-system

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gpu-manager
  namespace: kube-system

---
apiVersion: v1
kind: Service
metadata:
  name: gpu-manager-metric
  namespace: kube-system
  annotations:
    prometheus.io/scrape: "true"
  labels:
    kubernetes.io/cluster-service: "true"
spec:
  clusterIP: None
  ports:
    - name: metrics
      port: 5678
      protocol: TCP
      targetPort: 5678
  selector:
    name: gpu-manager-ds

---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: gpu-manager-daemonset
  namespace: kube-system
spec:
  updateStrategy:
    type: RollingUpdate
  selector:
    matchLabels:
      name: gpu-manager-ds
  template:
    metadata:
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ""
      labels:
        name: gpu-manager-ds
    spec:
      serviceAccount: gpu-manager
      tolerations:
        - key: CriticalAddonsOnly
          operator: Exists
        - key: tencent.com/vcuda-core
          operator: Exists
          effect: NoSchedule
      priorityClassName: "system-node-critical"
      nodeSelector:
        nvidia-device-enable: enable
      hostPID: true
      initContainers:
        - image: menghe.tencentcloudcr.com/public/alpine-uvm:0.1
          imagePullPolicy: IfNotPresent
          name: nvidia-uvm-enable
          securityContext:
            capabilities:
              add:
              - ALL
          volumeMounts:
          - mountPath: /lib/modules
            name: lib-modules
            readOnly: true
          - mountPath: /dev
            name: dev
      containers:
        - image: tkestack/gpu-manager:v1.0.4
          imagePullPolicy: IfNotPresent
          name: gpu-manager
          securityContext:
            privileged: true
          ports:
            - containerPort: 5678
          volumeMounts:
            - name: device-plugin
              mountPath: /var/lib/kubelet/device-plugins
            - name: vdriver
              mountPath: /etc/gpu-manager/vdriver
            - name: vmdata
              mountPath: /etc/gpu-manager/vm
            - name: log
              mountPath: /var/log/gpu-manager
            - mountPath: /var/run/docker.sock
              name: docker 
              readOnly: true 
            - name: run-dir
              mountPath: /var/run
            - name: cgroup
              mountPath: /sys/fs/cgroup
              readOnly: true
            - name: usr-directory
              mountPath: /usr/local/host
              readOnly: true
          env:
            - name: LOG_LEVEL
              value: "4"
            - name: EXTRA_FLAGS
              value: --incluster-mode=true
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: spec.nodeName
      volumes:
        - name: device-plugin
          hostPath:
            type: Directory
            path: /var/lib/kubelet/device-plugins
        - name: vmdata
          hostPath:
            type: DirectoryOrCreate
            path: /etc/gpu-manager/vm
        - name: vdriver
          hostPath:
            type: DirectoryOrCreate
            path: /etc/gpu-manager/vdriver
        - name: log
          hostPath:
            type: DirectoryOrCreate
            path: /etc/gpu-manager/log
        - name: docker
          hostPath:
            type: File
            path: /var/run/docker.sock
        - name: cgroup
          hostPath:
            type: Directory
            path: /sys/fs/cgroup
        - name: usr-directory
          hostPath:
            type: Directory
            path: /usr
        - name: run-dir
          hostPath:
            type: Directory
            path: /var/run
        - name: lib-modules
          hostPath:
            type: Directory
            path: /lib/modules
        - name: dev
          hostPath:
            type: Directory
            path: /dev/

2. 给GPU节点打nvidia-device-enable=enable 标签

代码语言:javascript
复制
kubectl label node *.*.*.* nvidia-device-enable=enable

3. 验证gpu-manager-daemonset是否正确派发到GPU节点

代码语言:javascript
复制
kubectl get pods -n kube-system

4. 配置kube-scheduler static pod dns policy为ClusterFirstWithHostNet

图6. dnspolicy配置示例
图6. dnspolicy配置示例

5. kube-scheduler 开启policy-config选项,增加如下启动参数:

代码语言:javascript
复制
--policy-config-file=/etc/kubernetes/scheduler-policy-config.json
--use-legacy-policy-config=true

/etc/kubernetes/scheduler-policy-config.json配置文件内容:

代码语言:javascript
复制
{
   "apiVersion" : "v1",
   "extenders" : [
      {
         "apiVersion" : "v1beta1",
         "enableHttps" : false,
         "filterVerb" : "predicates",
         "managedResources" : [
            {
               "ignoredByScheduler" : false,
               "name" : "tencent.com/vcuda-core"
            }
         ],
         "nodeCacheCapable" : false,
         "urlPrefix" : "http://gpu-quota-admission.kube-system:3456/scheduler"
      }
   ],
   "kind" : "Policy"
}

方案测试

方案测试采用Tensorflow框架,内置了Mnist,cifar10和Alexnet benchmark等测试数据集,可以根据需要选择不同的测试方案。

测试步骤:

  1. 使用TensorFlow框架+minst数据集进行测试验证,TensorFlow镜像:
代码语言:javascript
复制
menghe.tencentcloudcr.com/public/tensorflow-gputest:0.2

2. 创建一个测试负载,yaml文件如下:

代码语言:javascript
复制
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    k8s-app: vcuda-test
    qcloud-app: vcuda-test
  name: vcuda-test
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: vcuda-test
  template:
    metadata:
      labels:
        k8s-app: vcuda-test
        qcloud-app: vcuda-test
    spec:
      containers:
      - command:
        - sleep
        - 360000s
        env:
        - name: PATH
          value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
        image: menghe.tencentcloudcr.com/public/tensorflow-gputest:0.2
        imagePullPolicy: IfNotPresent
        name: tensorflow-test
        resources:
          limits:
            cpu: "4"
            memory: 8Gi
            tencent.com/vcuda-core: "50"
            tencent.com/vcuda-memory: "32"
          requests:
            cpu: "4"
            memory: 8Gi
            tencent.com/vcuda-core: "50"
            tencent.com/vcuda-memory: "32"

3. 进入测试容器(在默认default namespace下,如修改了测试yaml,按需指定namespace)

kubectl exec -it `kubectl get pods -o name | cut -d '/' -f2` -- bash

4. 执行测试命令,可以根据需求选择不同训练框架/数据集

a. Mnist

代码语言:javascript
复制
cd /data/tensorflow/mnist && time python convolutional.py

b. AlexNet

代码语言:javascript
复制
cd /data/tensorflow/alexnet && time python alexnet_benchmark.py

c. Cifar10

代码语言:javascript
复制
cd /data/tensorflow/cifar10 && time python cifar10_train.py

5. 在物理机上通过nvidia-smi pmon -s u -d 1命令查看GPU资源使用情况

图7. nvidia-smi 命令结果示例
图7. nvidia-smi 命令结果示例

资料:

https://github.com/kubernetes/community/pull/844/files

https://www.linux-kvm.org/images/5/59/02x03-Neo_Jia_and_Kirti_Wankhede-vGPU_on_KVM-A_VFIO_based_Framework.pdf

https://kernelgo.org/vfio-mdev.html

https://lists.freedesktop.org/archives/dri-devel/2020-April/262141.html

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • GPU虚拟化简介
  • GPUManager架构介绍
  • 方案优缺点及同类方案对比
  • GPUManager部署
  • 方案测试
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档