起因就是家里两个小家伙总是争抢一台电脑玩游戏,抢不到就唧唧歪歪。想想 Minecraft 也不是什么吃显卡的游戏,目前显卡价格虽然降了一些,但还是很贵。而且 Intel UHD 核显也的确性能拉跨。家里正好躺着一块 Nvidia GT 1030 “亮机卡”,性能足够两个人同时玩 Minecraft。
搜刮了一下互联网,看来我不是第一个研究“把一块显卡掰成两半使用”的人,不仅已经有人实现了,还记录了详细的步骤,另外还有人做了视频教程。
说搞就搞,打开 Kijiji 开始捡垃圾。CPU 得要个强劲的,核心数量优势在 AMD Ryzen 这边,相应的主板就选择 B450,内存 32GB。于是捡了一圈垃圾,又从我的电脑上拆了两条内存,加上刚换下来的电源,组装了这么一台奇怪的机器:
- AMD Ryzen 7 2700 (8-core, 16-thread)
- Asrock B450M-HDV R4.0
- 2x16GB Vulcan T-force 3200MHz
- Nvidia GT 1030 2GB (D5)
硬件搞定,接下来就是软件部分了。遵照教程,软件使用 Proxmox 7.2 虚拟环境 + Windows 10。
-1. 原理介绍
其实拆分显卡利用的是 Nvidia 给数据中心提供的驱动(破解版),把消费级显卡当作同代核心的商用显卡使用。比如把使用 GP108 核心的 GT 1030(Pascal 架构)识别成 Tesla P40。原教程里面提到,目前只有 Pascal 和 Turing 架构的消费级显卡可以拆分,所以 Ampere 架构的 30 系显卡目前还没法拆分使用。
消费级显卡拆分的最大限制是显存。拆分之后的虚拟显卡可以使用 GPU 全部可用的算力,但是只能使用预先设置好的显存容量。而显存容量又会影响 3D 游戏的渲染性能,所以在装机的时候就要先考虑好究竟买多大显存的显卡。对于我家这种只玩 Minecraft 的情况,就算 2GB 显存对半分,每人 1GB 显存也绰绰有余了。
0. 准备工作
在新窗口/标签页打开 Proxmox 7 vGPU 教程。最好通读一遍,虽然本文记录了详细过程,但是源教程里面还有一些背景知识介绍。
记得去 BIOS/UEFI 里面将所有跟虚拟化有关的项目都打开。
- Intel VT-d 或者 AMD SVM
- IOMMU
- SR-IOV
- Above 4G decode + Resizeable BAR
接着安装好 Proxmox 7.2,通过 https:// <ip> :8006 登录管理页面,上传 Windows 10 安装镜像,上传 VirtIO 驱动光盘镜像。
SSH 进入后台,将 apt 源更换为非氪金用户专用地址。
$ mv /etc/apt/sources.list.d/pve-enterprise.list ~
$ echo "deb http://download.proxmox.com/debian/pve bullseye pve-no-subscription" | tee /etc/apt/sources.list.d/pve-no-subscription.list
安装接下来必需的工具包。
apt update && apt upgrade -y
apt install -y git build-essential pve-headers-`uname -r` dkms jq cargo mdevctl unzip uuid
1. 设置虚拟化环境
首先,编辑 /etc/default/grub
文件, 根据 CPU 品牌,将 GRUB_CMDLINE_LINUX_DEFAULT
一项改为
# Intel
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"
# AMD
GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt"
然后更新 grub。
update-grub
然后,将下面几行添加到 /etc/modules-load.d/modules.conf
最后。
vfio
vfio_iommu_type1
vfio_pci
vfio_virqfd
再设置一些启动参数。
$ 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
$ echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.conf
更新 initramfs 并重启
$ update-initramfs -u
$ reboot
2. 在主机端安装(破解版)显卡驱动
这是先人已经做好的破解版显卡驱动,版本定格在 460.73.01。需要其他版本的话,需要参考原教程里面的 The hard way 部分,给新版驱动打了补丁才能使用。
chmod +x NVIDIA-Linux-x86_64-460.73.01-grid-vgpu-kvm-v5.run
./NVIDIA-Linux-x86_64-460.73.01-grid-vgpu-kvm-v5.run --dkms
然后安装 vgpu_unlock
和对应的启动程序。
$ git clone https://github.com/DualCoder/vgpu_unlock
$ chmod -R +x vgpu_unlock
$ git clone https://github.com/mbilker/vgpu_unlock-rs.git
$ cd vgpu_unlock-rs
$ cargo build --release
$ cp target/release/libvgpu_unlock_rs.so /lib/nvidia/libvgpu_unlock_rs.so
重启主机加载显卡驱动。
$ reboot
3. 设置虚拟显卡
首先,先生成 UUID,原则是需要拆分几个虚拟显卡,就生成几个 UUID。我这里只生成两个 UUID。打开记事本,把生成的 UUID 粘贴进去,接下来要用。
$ uuid -n 2
接下来,查看一下显卡的 PCI 地址。这个地址在每台机器上都不一样,所以照例粘贴到记事本里面去备用。我的显卡 PCI 地址是 09:00.0
,前面多加几个零,记录成 0000:09:00.0
。
$ lspci | grep -i nvidia
09:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1)
09:00.1 Audio device: NVIDIA Corporation GP108 High Definition Audio Controller (rev a1)
然后查看可用的虚拟显卡预设。
$ mdevctl types
预设的命名规则是:显卡型号-显存容量 使用目的。比如我选的 P40-1Q,就是说这块显卡被识别成 P40,给虚拟显卡分配 1GB 显存,用于工作站。
nvidia-46
Available instances: 0
Device API: vfio-pci
Name: GRID P40-1Q
Description: num_heads=4, frl_config=60, framebuffer=1024M, max_resolution=5120x2880, max_instance=24
选好了预设之后,就可以创建虚拟显卡了。(-u 后面是 UUID,-p 后面是 PCI 地址,-t 后面是预设)
$ mdevctl start -u <uuid> -p 0000:09:00.0 -t nvidia-46
$ mdevctl define -a -u <uuid>
每一个 UUID 都要运行一遍上面两行命令
4. 创建虚拟机
按照正常流程创建虚拟机,注意以下几点:
- 设置 CPU 的时候,CPU 类型要选择 Host(列表最下面那个)
- 电脑类型要设置成 q35
- BIOS 类型要选择 UEFI
- SCSI 控制器选择 Default(因为 VirtIO 会造成主机死机)
5. 分配虚拟显卡
编辑虚拟机配置文件,例如 /etc/pve/qemu-server/100.conf
,加入下面一行。将 <uuid> 替换成虚拟显卡的 UUID。
args: -device 'vfio-pci,sysfsdev=/sys/bus/mdev/devices/<uuid>,display=off,id=hostpci0.0,bus=ich9-pcie-port-1,addr=0x0.0,x-pci-vendor-id=0x10de,x-pci-device-id=0x1c31' -uuid 00000000-0000-0000-0000-000000000100
然后用同样的方法修改 101, 102, ... 等等虚拟机的配置文件,注意更换虚拟显卡的 UUID。也要注意最后那个一串 0 的虚拟机 UUID 也要跟虚拟机编号对应上。
注意,上面那一行代码会把虚拟显卡标记成 Quadro P2200。若使用 Turing 架构的显卡,需要去原教程里面寻找对应的分配代码(标记成 Quadro RTX6000).
此时,确认显卡上没有接任何显示器。如果接了显示器,需要拔掉显示器并重启主机。
$ reboot
6. 在虚拟机上安装驱动
去 Nvidia 官网下载对应的 Quadro 驱动(Pascal 架构下载 P2200 驱动,Turing 架构下载 RTX6000 驱动),安装。
在 Windows 设置中开启远程桌面。
注意:如果登录微软账户时使用了验证器而不是输入密码,需要先同步账户密码才能登录远程桌面。在虚拟机中打开命令行,执行下面命令,强制 Windows 从微软账户服务器同步密码。
> runas /u:MicrosoftAccount\<Microsoft account email> cmd.exe
关闭虚拟机电源。
然后在管理页面上找到虚拟机,选择 Hardware,双击 Display,把类型改成 none。
重新启动虚拟机,通过远程桌面登录,确认驱动正常工作。
到这一步,已经可以通过远程桌面运行游戏了。
7. 连接键盘和物理显示器
远程桌面毕竟还需要一台电脑,如果能直接把鼠标和显示器接到虚拟机上,就省了一台远程桌面客户机。
连接键盘很简单,将 USB 接收器插到主机上,然后去虚拟机设置里面选择 Hardware,添加 USB 设备,选择 Use USB Vendor/Device ID,然后在列表里面选择 USB 接收器即可。
连接物理显示器需要一个 USB-to-HDMI 转接器,推荐使用 DisplayLink 方案的转接器,然后用上面的方法把 USB-to-HDMI 转接器直通给虚拟机即可。亚马逊上卖的使用 Fresco Logic 方案的转接器经测试无法工作,该系列转接器的特点是使用了 USB-to-VGA + VGA-to-HDMI 两块芯片。
注意:连接物理显示器之后,首次启动时,显示器会黑屏,这是因为 Windows 系统把挂载在虚拟显卡上的虚拟显示器当成主显示器,而物理显示器则是当成第二显示器。此时要么盲操作登录系统进入桌面,要么按 Win + P 组合键切换显示器。
8. vsync(垂直同步)
在进行 Minecraft 测试时,使用 Win + G 调用 Xbox 覆盖界面发现游戏只有可怜的 30 FPS,同时 CPU 和 GPU 的占用只有区区 20%。这就是传说中的“垂直同步逆向优化”。需要打开 Nvidia 控制面板,在图形设置里面把 vertical sync 设置成 Off(全局强制关闭)。反正 GT 1030 也用不上什么垂直同步嘛。
9. 电源设置
在进行系统测试的时候,发现虚拟机放了一会儿,进入睡眠状态之后就再也活不过来了。这是 Proxmox VE 虚拟机系统的问题,严格说是 Qemu 的问题。需要到虚拟机的 Windows 电源设置里面把进入睡眠状态的时间调整为 Never(永不)。
50. 系统更新
如果更新了 PVE 系统的内核,需要重新使用新内核生成并安装驱动。
# 安装新内核的头文件
$ apt install -y pve-headers-`uname -r`
# 重新安装 NVIDIA 驱动
$ ./NVIDIA-Linux-x86_64-460.73.01-grid-vgpu-kvm-v5.run --dkms
# 重新安装 vgpu_unlock-rs
$ cd vgpu_unlock-rs
$ cargo clean
$ cargo build --release
$ cp target/release/libvgpu_unlock_rs.so /lib/nvidia/libvgpu_unlock_rs.so
# 重启系统
$ reboot
51. 更换显卡
如果 460.73.01 驱动也支持新显卡(10xx/16xx/20xx 系列显卡),则不需要重新安装驱动,只需要重新配置虚拟显卡即可。
首先需要移除现有的虚拟显卡。
$ mdevctl list
$ mdevctl stop <uuid>
$ mdevctl undefine <uuid>
然后根据第 3 步重新设置虚拟显卡。推荐重用之前的 UUID,否则还需要根据第 5 步重新分配虚拟显卡。
如果从显卡从 10xx 升级到 20xx(从 P40 升级到 RTX 6000),则需要根据第 5 步重新设置伪装的硬件识别码,并在虚拟机中更新对应型号的驱动。
99. 后记
从一个点子,开始找资料,捡垃圾,拼好之后调整设置,到实际放到桌子上给娃使用,用了整整一个星期时间。两个小家伙都有了自己的“电脑”,可以同时玩游戏不打架,小家伙们也很高兴。另外还有个好处,现在两个“电脑”可以分开进行家长控制了,之后应该不会再听到“他玩多了我没玩够”的抱怨了。
100. Q&A?
Q:AMD 的显卡能拆分么?
A:目前不能,因为 AMD 没有提供虚拟化驱动。AMD 显卡只能直通(passthrough)。
Q:为啥不用 Aster?
A:的确这个拆分显卡就是 multiseat,但是 Aster 要花钱啊!另外,如果两个人同时玩一个游戏,使用 Aster 还要配沙盒软件 Sandboxie……
Q:为啥不用 Parsec?
A:同上,花钱啊,而且不仅仅是 Parsec 花钱,远程桌面客户端还需要再准备一台电脑。
Q:为啥不用 Hyper-V?
A:因为 Hyper-V 没法给虚拟机接物理显示器,而且还要浪费一个 Windows 序列号。
Q:为什么不直接插两块 GT 1030,全部做直通?这样还能节省 DisplayLink 转接器的花销。
A:主要原因是为了今后升级显卡考虑,升级一块显卡和升级两块显卡的开销差别还是很大的。另外就是主板上的插槽也不够插两块卡的。