Proxmox 7.4 GPU Passthrough 显卡直通

Self Hosted 自托管
Apr 9, 2023 ~

近期AI题材火爆,看着家里一堆闲置吃灰的显卡,想想也应该让他们起来干活了,于是想起来组建一个新的PVE服务器,把显卡用上。又因为显卡虚拟化的支持目前应该还是不太稳定的状态(想来老黄是不可能给消费级显卡官方虚拟化的能力的),而且即使服务器端驱动搞定了,License的问题应该还是会成为绊脚石,所以还是选择了更为稳妥的直通方案。计划是直通显卡给一个Ubuntu server,然后在其上通过Docker部署一些简单的应用。

之前在PVE 7.1以及更早的版本上面已经尝试过显卡直通,而且运行稳定,本以为这次应该是手拿把攥,结果却翻了车。特此记录一下翻车和修正过程。

PVE 7.1以及之前版本的显卡直通步骤

参考:目前最靠谱的帖子,比PVE官方文档清晰很多的reddit reference post

  • 先确认BIOS里面已经打开了所有跟虚拟化有关的选项,比如 vt-d,以及IOMMU分组之类的。

  • 然后需要确定服务器的启动的Boot loader是使用的 GRUB 还是 System Boot。这方面 PVE 官方有文档 doc。但是解释的不算非常清楚。总结一下:

    • 如果启动的时候有看到GRUB的界面让你选择启动的项目,则肯定是 GRUB。
    • 如果启动BIOS没有开启UEFI,而且PVE安装的时候选择的是EXT4的文件系统,则基本上肯定是GRUB
    • 如果PVE安装时选择的是ZFS的文件系统,则基本上肯定是System Boot
  • 确定了Boot loader之后,需要修改对应的文件:

    • 如果是GRUB,需要修改文件为 /etc/default/grub
      • GRUB_CMDLINE_LINUX_DEFAULT 原本应该是只有 "quite"。
      • 现在需要改为:"quiet amd_iommu=on iommu=pt pcie_acs_override=downstream,multifunction nofb nomodeseti video=vesafb:off,efifb:off"
      • nofb video=vesafb:off,efifb:off 应该是关闭 Framebuffer。不确定是不是必须的。
      • pcie_acs_override=downstream,multifunction 应该是针对消费级主板通病:“稀烂的IOMMU分组”所做的妥协。相当于打开Kernel里面的一个flag,让kernel根据情况override主板上面的IOMMU分组,强行根据功能分组。很多消费级主板上面的IOMMU分组非常糟糕,比如板载的网卡声卡以及一些PCIE借口都在一个分组里。而PCI直通实际都是以组为单位,所以直通的时候,本意是直通一个PCIE插槽,结果把板载网卡通过去了,而HOST母机直接就没有网卡了,这对于服务器来讲基本就挂了。需要注意的是,有时即使有了这个override,也不一定就能成功,之前在ASUS B460M Prime 这个主板上就发生过这个情况,后来发现需要修改并重新编译内核才能解决。
        • 注意这个不可以代替BIOS设置里面需要打开的IOMMU分组。
      • amd_iommu=on 这个跟平台有关,如果是Intel的CPU,需要改为 intel_iommu=on 才行。
      • 双引号不可以省略,所有Argument必须在同一行,不要分行。
    • 如果是 System Boot,则为 /etc/kernel/cmdline
      • 这个文件里面应该只有一行:root=ZFS=rpool/ROOT/pve-1 boot=zfs quite
      • 添加的内容跟上面 GRUB 的添加的 CMDLINE 内容一样,但是不要加双引号。同时一样不要换行。
  • 更新 grub 或者 system boot 的 image 参考 pve doc reference

    • GRUB:$ update-grub
    • System boot: $ proxmox-boot-tool refresh
  • 添加VFIO模块 (VFIO Modules)

    • 添加以下内容到 /etc/modules
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
  • IOMMU的中断映射
$ echo "options vfio_iommu_type1 allow_unsafe_interrupts=1" > /etc/modprobe.d/iommu_unsafe_interrupts.conf
$ echo "options kvm ignore_msrs=1" > /etc/modprobe.d/kvm.conf
  • 把GPU添加到VFIO里面

    • 这一步需要首先找到GPU确切的Vendor ID
    • 首先运行 $ lspci -v | less,在里面找到VGA的段落。肯定有个类似 01:00.0 这种格式的号。
    • 然后利用这个号码列出详情 $ lspci -n -s 01:00,这次不会输出很多,只会输出01:00相关设备的设备号。
    • 之后要把这个设备号添加进VFIO
  • 查询所有PCI设备

$ lspci -v | less
# Output:
01:00.0 VGA compatible controller: NVIDIA Corporation GA102 [GeForce RTX 3090] (rev a1) (prog-if 00 [VGA controller])
        Subsystem: ZOTAC International (MCO) Ltd. GA102 [GeForce RTX 3090]
        Flags: bus master, fast devsel, latency 0, IRQ 88, IOMMU group 18
        Memory at f6000000 (32-bit, non-prefetchable) [size=16M]
        Memory at e0000000 (64-bit, prefetchable) [size=256M]
        Memory at f0000000 (64-bit, prefetchable) [size=32M]
        I/O ports at e000 [size=128]
        Expansion ROM at 000c0000 [disabled] [size=128K]
        Capabilities: [60] Power Management version 3
        Capabilities: [68] MSI: Enable+ Count=1/1 Maskable- 64bit+
        Capabilities: [78] Express Legacy Endpoint, MSI 00
        Capabilities: [b4] Vendor Specific Information: Len=14 <?>
        Capabilities: [100] Virtual Channel
        Capabilities: [258] L1 PM Substates
        Capabilities: [128] Power Budgeting <?>
        Capabilities: [420] Advanced Error Reporting
        Capabilities: [600] Vendor Specific Information: ID=0001 Rev=1 Len=024 <?>
        Capabilities: [900] Secondary PCI Express
        Capabilities: [bb0] Physical Resizable BAR
        Capabilities: [c1c] Physical Layer 16.0 GT/s <?>
        Capabilities: [d00] Lane Margining at the Receiver <?>
        Capabilities: [e00] Data Link Feature <?>
        Kernel driver in use: vfio-pci
        Kernel modules: nvidiafb, nouveau

01:00.1 Audio device: NVIDIA Corporation GA102 High Definition Audio Controller (rev a1)
        Subsystem: ZOTAC International (MCO) Ltd. Device 1613
        Flags: bus master, fast devsel, latency 0, IRQ 87, IOMMU group 18
        Memory at f7080000 (32-bit, non-prefetchable) [size=16K]
        Capabilities: [60] Power Management version 3
        Capabilities: [68] MSI: Enable- Count=1/1 Maskable- 64bit+
        Capabilities: [78] Express Endpoint, MSI 00
        Capabilities: [100] Advanced Error Reporting
        Capabilities: [160] Data Link Feature <?>
        Kernel driver in use: vfio-pci
        Kernel modules: snd_hda_intel
  • 列出01:00所有设备号(通常显卡都在01:00)
$ lspci -n -s 01:00
# output
01:00.0 0000: 10de:1b81 (rev a1)
01:00.1 0000: 10de:10f0 (rev a1)
  • 添加GPU设备到VFIO
echo "options vfio-pci ids=10de:1b81,10de:10f0 disable_vga=1"> /etc/modprobe.d/vfio.conf
  • 更新 initramfs 并重启
$ update-initramfs -u
$ reboot now
  • 确认IOMMU分组可以用这个script将所有的IOMMU分组全部打印出来。
#!/bin/bash
for d in $(find /sys/kernel/iommu_groups/ -type l | sort -n -k5 -t/); do
        n=${d#*/iommu_groups/*}; n=${n%%/*}
        printf 'IOMMU Group %s ' "$n"
        lspci -nns "${d##*/}"
done;
  • 指定GPU给虚拟机,并修改虚拟机配置文件来保证虚拟机里面使用显卡的时候,不会被驱动识别出当前是虚拟机环境进而停止工作

    • 通过UI改:虚拟机的machine type应该为Q35。否则在指定PCI设备的时候无法选中PCI-E选项。
      • 这个需要在创建虚拟机的时候调,印象中在创建之后就不能改了。
    • 配置文件位置为 /etc/pve/qemu-server/<vmid>.conf
      • cpu: host,hidden=1,flags=+pcid
      • args: -cpu 'host,+kvm_pv_unhalt,+kvm_pv_eoi,hv_vendor_id=NV43FIX,kvm=off'
    • 之后就在WEB UI:虚拟机Hardware tab下,添加PCI设备,这个时候应该是可以找到GPU设备进行分配的。
      • 如果出现No IOMMU detected, please activate it.的警告,说明BIOS上面的IOMMU分组没开,或者kernel cmdline上面没有添加amd_iommu=on或者intel_iommu=on,或者干脆就是缺一次重启。
    • 在指定GPU给虚拟机的时候,大体如下(这里也可以选择 all function,只不过就是会同时把GPU上面的那个音频设备同时直通进去;另外如果不链接显示器作游戏机的话,没有太大必要把Primary GPU选上,一旦选上之后,虚拟机的主显示输出就改为了这个GPU,此时如果系统是桌面系统,那么在Proxmox的WebUI的console里面就看不到桌面了)
      nwqar7s93ko21
      7p4os0ie3ko21
  • ROM相关 参考Reddit上面的步骤

    • 这部分因为没有用到过,所以没有尝试过,所以省略过去。
  • 验证

    • 在VM里面(我用的是Ubuntu 22.04),应该是可以直接安装Nvidia的驱动的。
      • $ sudo apt-get install nvidia-driver-525
      • Test result: $ nvidia-smi (可能需要重启)
    • 如果是 Debian (tested on debian 11 aka bullseye)
      • Add non-free package sources to in /etc/apt/sources.list
        • By default the content should be like: deb http://ftp.ca.debian.org/debian bullseye main contrib
        • Change it to deb http://ftp.ca.debian.org/debian bullseye main contrib non-free
        • Do the same for both bullseye and bullseye-updates
    • $ apt update && apt install nvidia-detect && nvidia-detect。通常来讲输出会说让你安装 nvidia-driver,至于版本应该是自动选择的。
    • $ apt install nvidia-driver。之后可能需要重启然后用 nvidia-smi验证。

升级到PVE 7.2之后的翻车

(实测在7.4的版本上是翻车了,没有实地验证是不是7.2也会翻车)

按照之前的步骤,在虚拟机里面也确定了nvidia-smi可以输出正确的内容。但是,尝试使用Jellyfin硬解,结果是hang。一番折腾之后发现应该还是直通出现了问题。

我这里是用Jellyfin的ffmpeg试图进行硬解的时候发现了,检测的方法是:

  • 安装 Jellyfin
curl https://repo.jellyfin.org/install-debuntu.sh > ~/install-debuntu.sh

sudo bash ~/install-debuntu.sh
  • 使用Jellyfin的FFmpeg测试CUDA硬件加速
# Test
# 这一步会在输出一系列Capabilities测试通过之后,直接卡死。
# 推断应该是某一个测试一直卡住了。
/usr/lib/jellyfin-ffmpeg/ffmpeg -v debug -init_hw_device cuda

# 类似如下输出 (但是具体卡在哪一个位置并准确,只是形式类似)
ffmpeg version 5.1.2-Jellyfin Copyright (c) 2000-2022 the FFmpeg developers
  built with gcc 10 (Debian 10.2.1-6)
  configuration: --prefix=/usr/lib/jellyfin-ffmpeg --target-os=linux --extra-libs=-lfftw3f --extra-version=Jellyfin --disable-doc --disable-ffplay --disable-ptx-compression --disable-shared --disable-libxcb --disable-sdl2 --disable-xlib --enable-lto --enable-gpl --enable-version3 --enable-static --enable-gmp --enable-gnutls --enable-chromaprint --enable-libdrm --enable-libass --enable-libfreetype --enable-libfribidi --enable-libfontconfig --enable-libbluray --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libdav1d --enable-libwebp --enable-libvpx --enable-libx264 --enable-libx265 --enable-libzvbi --enable-libzimg --enable-libfdk-aac --arch=amd64 --enable-libsvtav1 --enable-libshaderc --enable-libplacebo --enable-vulkan --enable-opencl --enable-vaapi --enable-amf --enable-libmfx --enable-ffnvcodec --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvdec --enable-nvenc
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
Splitting the commandline.
Reading option '-v' ... matched as option 'v' (set logging level) with argument 'debug'.
Reading option '-init_hw_device' ... matched as option 'init_hw_device' (initialise hardware device) with argument 'cuda'.
Finished splitting the commandline.
Parsing a group of options: global .
Applying option v (set logging level) with argument debug.
Applying option init_hw_device (initialise hardware device) with argument cuda.
[AVHWDeviceContext @ 0x5567c1c98180] Loaded lib: libcuda.so.1
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuInit
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuDeviceGetCount
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuDeviceGet
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuDeviceGetAttribute
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuDeviceGetName
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuDeviceComputeCapability
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuCtxCreate_v2
[AVHWDeviceContext @ 0x5567c1c98180] Loaded sym: cuCtxSetLimit
  • 同样在尝试其他使用CUDA的app也会卡死。

之所以发现是Passthrough的问题,是因为Debug的时候尝试在PVE母机(Debian)上面直接安装Jellyfin,同样的ffmpeg测试就顺利通过。至此确定是GPU Passthrough的问题。

发现可能的原因以及问题解决

标签