k8s部署vLLM

 

背景

k8s上部署推理服务很简便,vLLM作为一个流行的推理服务,自己也打包了镜像,可以比较方便地部署到k8s上,但还是免不了踩坑。

文档

部署

部署依赖

k8s上为了能够使用和分配GPU,需要安装一个daemonset: NVIDIA/k8s-device-plugin,建议用官方提供的helm chart安装。
安装完成后可以查看node的信息,查看gpu的分配情况:

Capacity:
  nvidia.com/gpu:         2
Allocatable:
  nvidia.com/gpu:         2

其中插件提供3种GPU分配方式:按GPU数量、按时间切片(Time-Slicing)、CUDA多进程服务(Multi-Process Service)。具体的设置在ConfigMap中进行配置,例如Time-Slicing:

apiVersion: v1
kind: ConfigMap
metadata:
  name: nvidia-device-plugin-configs
data:
  # timeSlicing.renameByDefault: 将nvidia.com/gpu重命名为nvidia.com/gpu.shared,避免概念混淆
  # timeSlicing.resources.replicas: 将一个gpu切成多少个切片。例如replicas=10,gpu有2个,则总的可分配的数量为20
  default: |-
    config:
      map:
        default: |-
          version: v1
          flags:
            migStrategy: none
          sharing:
            timeSlicing:
              renameByDefault: true
              resources:
              - name: nvidia.com/gpu
                replicas: 10

此时再看node信息为:

Capacity:
  nvidia.com/gpu:         2
  nvidia.com/gpu.shared:  20
Allocatable:
  nvidia.com/gpu:         2
  nvidia.com/gpu.shared:  20

版本检查

需要尽量保证宿主机的nvidia版本、cuda版本与pod镜像中是统一的,否则可能会出现向前兼容问题,从而报错。
因为vLLM镜像使用的基础镜像(nvidia/cuda)是固定的版本,所以要么考虑升级宿主机的nvidia driver,要么自己重新编译vLLM镜像。
如果实在不方便,可以采用替换动态库的方式来work around,下面会讲怎么做。但是最好还是保持版本统一,少走弯路。

实际部署

这边假设宿主机和pod的版本不统一,且采用Time-Slicing来做,使用2GPU,这样算是比较复杂的配置了。

apiVersion: v1
kind: Pod
metadata:
  labels:
    app.kubernetes.io/instance: vllm-openai
  name: vllm-openai
  namespace: openpie-model
spec:
  # 需要配置nvidia runtime
  # 一般装了 NVIDIA/k8s-device-plugin 插件就有了。可以检查一下: kubectl get runtimeclass
  runtimeClassName: nvidia
  containers:
    - name: vllm-openai
      # 这里选择了一个driver版本是550的镜像
      # 宿主机的nvidia driver版本是535,比较低,会遇到向前兼容的问题
      image: vllm/vllm-openai:v0.6.3.post1
      imagePullPolicy: IfNotPresent
      command: ["/bin/sh", "-c"]
      args:
        # 这里使用软连接强行替换为宿主机的535版本
        # 注意这里的 tensor-parallel-size 需要小于等于分配到的GPU个数。例如使用"nvidia.com/gpu":"1"时,tensor-parallel-size只能为1
        - |
          ln -sf /usr/lib/x86_64-linux-gnu/libcuda.so.535.183.01 /usr/lib/x86_64-linux-gnu/libcuda.so.1;
          ln -sf /usr/lib/x86_64-linux-gnu/libcudadebugger.so.535.183.01 /usr/lib/x86_64-linux-gnu/libcudadebugger.so.1;
          ln -sf /usr/lib/x86_64-linux-gnu/libnvidia-nvvm.so.535.183.01 /usr/lib/x86_64-linux-gnu/libnvidia-nvvm.so.4;
          ln -sf /usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.535.183.01 /usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so.1;
          vllm serve /model \
          --port 18000 \
          --served-model-name qwen2-7b \
          --tensor-parallel-size 2 \
          --gpu-memory-utilization 0.5 \
          --max-model-len 512
      env:
        # 因为使用的是NVIDIA GeForce RTX 4090
        # 40系不支持NCCL_IB和NCCL_P2P 需要在环境变量中disable掉
        # 其他系的卡可能不需要
        - name: NCCL_IB_DISABLE
          value: "1"
        - name: NCCL_P2P_DISABLE
          value: "1"
        # - name: OMP_NUM_THREADS
        #   value: "1"
      resources:
        limits:
          # 这边配置的是12
          # 要求是 这里的配置 加上 其他已配置的内容 要小于总数 2*10
          nvidia.com/gpu.shared: "12"
        requests:
          nvidia.com/gpu.shared: "12"
      volumeMounts:
      # 挂载本地模型路径
      - name: local-model-path
        mountPath: /model
      # 挂载共享内存
      # 使用多卡时 会使用 NCCL SHM
      # pod默认的64MB共享内存是不够用的 如果不挂载会报错
      - name: shm
        mountPath: /dev/shm
  volumes:
  - name: local-model-path
    hostPath:
      path: /data/models/Qwen/Qwen2-7B-Instruct
      type: Directory
  - name: shm
    emptyDir: {}

其他资料

  1. Multi-Instance GPU(MIG)
    可以将一个GPU分片成多个实例,需要GPU和k8s同时支持。single策略是将所有分片的大小都固定成一样的,mixed策略是允许分片显存大小不同,并且在pod申请资源时指定多个不同类型的分片

  2. 关于一个集群上多个GPU部分支持共享、部分支持独享的讨论