欢迎光临散文网 会员登陆 & 注册

Tesla P4 Linux NAS使用指北

2023-04-23 10:26 作者:hr3lxphr6j  | 我要投稿

0x00: 序

最近在闲鱼收了块Tesla P4给自己的NAS用,这里做下配置记录。宿主机依旧是Archlinux,别的发行版请自己想办法。


主要实现如下能力:

  1. 虚拟机GPU直通

  2. 容器能使用GPU加速(比如jellyfin的显卡硬解)

0x01: GPU虚拟机直通

参考:https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF

首先进主板的BIOS把IOMMU功能打开,然后在内核参数中添加 iommu=pt intel平台还要再加上 intel_iommu=on ,然后重启,在dmesg中看到iommu相关就算成了。

如何配置内核启动参数请看这:https://wiki.archlinux.org/title/Kernel_parameters

我是用virt-manger + libvirt + qemu + kvm来实现和管理虚拟机,安装的包如下

$ yay -Qqe | grep -e virt -e qemu -e ovmf
edk2-ovmf
libvirt
qemu-emulators-full
qemu-full
virt-manager
virtio-win

以上的玩意装好后打开virt-manger,新建虚拟机时候记得固件一定选择UEFI。然后先正常的装个windows,装好virtio的驱动,开启自带的远程桌面后关机。然后再添加一个PCI主机设备选择的你的显卡,然后进win装好驱动即可(驱动下载,自备梯子:https://cloud.google.com/compute/docs/gpus/grid-drivers-table?hl=zh-cn#windows_drivers)。之后你就可以把显卡选为none,删掉显示器,之后只从rdp来访问虚拟机了。

你甚至可以远程打狒

0x02:GPU容器

参考:https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html

安装如下的包:

  • nvidia-docker

  • nvidia-dkms (这个驱动貌似不装也行?)

装nvidia-docker的时候可能会提示你/etc/docker/daemon.json已经存在,你可以先把之前的daemon.json备份下删掉,在装好这个包后在覆盖回来,然后记得执行 sudo nvidia-ctk runtime configure --runtime=docker 在daemon.json中添加nvidia的runtime即可。然后重启docker,执行 docker run --rm --runtime=nvidia --gpus all nvidia/cuda:11.6.2-base-ubuntu20.04 nvidia-smi 能看到GPU信息就算成功。

$ docker run --rm --runtime=nvidia --gpus all nvidia/cuda:11.6.2-base-ubuntu20.04 nvidia-smi
Sun Apr 23 01:03:51 2023
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 530.41.03              Driver Version: 530.41.03    CUDA Version: 12.1     |
|-----------------------------------------+----------------------+----------------------+
| 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 P4                        Off| 00000000:04:00.0 Off |                    0 |
| N/A   44C    P8                7W /  75W|      0MiB /  7680MiB |      0%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+

+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|=======================================================================================|
|  No running processes found                                                           |
+---------------------------------------------------------------------------------------+

0x03: Jellyfin容器硬解

以jellyfin为例来看看如何部署一个能使用GPU的容器,直接上docker-compose.yaml

version: '3.5'

services:
  jellyfin:
  	# 这里选择nyanmisaka魔改的版本,官方镜像也是一样的
    image: nyanmisaka/jellyfin:230414-amd64
    restart: always
    container_name: jellyfin
    user: 1000:995
    volumes:
      # 根据个人习惯把/config和/cache目录挂载出去,jellyfin的配置存储在这里
      - /home/chigusa/.jellyfin/config:/config
      - /home/chigusa/.jellyfin/cache:/cache
    environment: # 这俩玩意记得加上,要不找不到GPU
      - NVIDIA_DRIVER_CAPABILITIES=all
      - NVIDIA_VISIBLE_DEVICES=all
    labels:
      # 这个是我个人用来区分哪个容器用到GPU的,会和下文的脚本配合,不是必须	
      gpu: true
    # runtime 和 deploy 直接照nv官方文档抄过来
    runtime: nvidia
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]

然后进jellyfin中在 控制台 - 播放 中硬件加速选择Nvidia NVENC就成了

轻松点草蓝光原盘

可以装个nvtop,看到GPU调用情况

0x04: GPU容器和虚拟机和谐共处

当有容器在使用GPU的时候,开启GPU直通的虚拟机是没法启动的,你需要先把容器停掉在启动虚拟机。每次都要人肉在虚拟机开启前关掉GPU容器、关机后再打开有点脑残,所以这里用libvirt的hook机制来做这个事情。你需要为所有使用GPU的容器打一个 gpu=true 的label(看上边jellyfin的例子),然后把下面这个脚本放到 /etc/libvirt/hooks/qemu ,赋予执行权限。

注意这是文件,不是目录
#!/usr/bin/env python3

import sys
from functools import partial
import subprocess
import json

_hooks = []


def hook(fn=None, domain: str = None, action: str = None, stage: str = None):
    if fn is None:
        return partial(hook, domain=domain, action=action, stage=stage)
    _hooks.append((domain, action, stage, fn))


class DockerUtils:
    @staticmethod
    def is_docker_running():
        '''
        用 systemd 来判断 docker 是否在运行
        '''
        p = subprocess.run(
            ['systemctl', 'is-active', 'docker.service'], capture_output=True)
        if p.returncode != 0:
            return False
        return p.stdout.decode().strip() == 'active'

    @staticmethod
    def get_gpu_containers(**kwargs):
        '''
        获取所有打了 gpu=true 的容器的信息
        '''
        def __filter(data):
            for k, w in kwargs.items():
                if k in data and data[k] != w:
                    return False
            return True
        p = subprocess.run(
            ['docker', 'container', 'ls',
             '-f', 'label=gpu=true',
             '--format=json', '--all',
             '--no-trunc'
             ], capture_output=True, check=True)
        for item in p.stdout.decode().splitlines():
            data = json.loads(item)
            if kwargs and not __filter(data):
                continue

            yield data


# 注意把domain改成你虚拟机的名字,支持数组
@hook(domain='win11', action='prepare', stage='begin')
def win11_prepaer_begin():
    '''
    在虚拟机启动前执行,停掉所有打了 gpu=true 的容器
    '''
    if not DockerUtils.is_docker_running():
        return
    for item in DockerUtils.get_gpu_containers(State='running'):
        subprocess.run(['docker', 'stop', item['ID']])


# 注意把domain改成你虚拟机的名字,支持数组
@hook(domain=['win11'], action='release', stage='end')
def win11_release_end():
    '''
    在虚拟机关机后执行,启动所有打了 gpu=true 的容器
    '''
    if not DockerUtils.is_docker_running():
        return
    for item in DockerUtils.get_gpu_containers(State='exited'):
        subprocess.run(['docker', 'start', item['ID']])


def main():
    '''
    libvirt 会传入的参数如下:
        
        /etc/libvirt/hooks/qemu guest_name prepare begin -
    
    其中 guest_name 是你虚拟机的名字,后两个参数的意义参考libvirt的官方文档:
        https://libvirt.org/hooks.html#etc-libvirt-hooks-qemu
    '''
    if len(sys.argv) != 5:
        return
    domain, action, stage = sys.argv[1:4]
    for _hook in _hooks:
        if domain == _hook[0] if not isinstance(_hook[0], list) else domain in _hook[0] \
                and action == _hook[1] and stage == _hook[2]:
            _hook[3]()


if __name__ == '__main__':
    main()

0x04: Prometheus + Grafana GPU监控

dcgm文档:https://docs.nvidia.com/datacenter/dcgm/latest/contents.html

grafana的板子:https://grafana.com/grafana/dashboards/12239-nvidia-dcgm-exporter-dashboard/

在容器中部署,同样打 gpu=true 的标,方便上边的hook识别,最终效果如下

version: "3"

services:
  dcgm-exporter:
    labels:
      gpu: true
    environment:
      - NVIDIA_DRIVER_CAPABILITIES=all
      - NVIDIA_VISIBLE_DEVICES=all
    restart: always
    image: nvcr.io/nvidia/k8s/dcgm-exporter:2.1.4-2.3.1-ubuntu20.04
    runtime: nvidia
    cap_add:
      - SYS_ADMIN
    deploy:
      resources:
        reservations:
          devices:
            - capabilities: [gpu]


Tesla P4 Linux NAS使用指北的评论 (共 条)

分享到微博请遵守国家法律