libvirt_guest_example_with_annotations.xml | ||
README.md |
PFS libvirt/qemu GPU Passthrough für Windows Gaming VM
Links
- Arch Linux Wiki: https://wiki.archlinux.org/title/PCI_passthrough_via_OVMF
- VFIO Subreddit: https://www.reddit.com/r/VFIO/
- Weitere Links, die ich in Kommentaren der XML gepackt hab.
Linux kernel command line args
vfio-pci.ids=10de:2684,10de:22ba
: Guest GPU PCI IDs "rausnehmen", dass der kernel damit nichts macht (GPU und HDMI Audio im Beispiel). Find ich bei mir vialspci -nn | grep NVIDIA
.- obsolet:
isolcpus=0-7,16-23
: CPUs für Guest vom System isolieren. Kann man mittlerweile zur Laufzeit via cgroups machen. SieheCPUs isolieren bei VM Start
- even obsoleter:
nohz_full=0-7,16-23
: Siehe isolcpus. Bei Interesse docs auf kernel.org lesen. Aber total obsolet. video=efifb:off
: Ich hatte Probleme, weil selbst mit vfio der efifb noch irgendwie die NVIDIA "gegrabt" hat, deshalb efifb:off.module_blacklist=nvidia
: Ich hab bei mir den NVIDIA Treiber UND den AMD Treiber auf dem Host installiert (AMD = Linux, NVIDIA = Win Guest), weil ich LLMs mit ollama lokal laufen lassen will und dafür unter Linux auch die NVIDIA Treiber brauche. Wenn ich aber hybrid boote, also VM nutzen will, dann blackliste ich noch zusätzlich das NVIDIA Modul, macht insgesamt dann weniger Probleme.systemd.unit=pfs-winvm-hybrid.target
: Custom boot target, siehe "rEFInd Config" und "Custom systemd targets".
rEfInd Config
$ cat /boot/refind_linux.conf
"Arch Linux vfio NVIDIA 4090 hybrid WinVM off" "[...] amd_iommu=on iommu=pt vfio-pci.ids=10de:2684,10de:22ba video=efifb:off module_blacklist=nvidia"
"Arch Linux vfio NVIDIA 4090 hybrid WinVM on" "[...] amd_iommu=on iommu=pt vfio-pci.ids=10de:2684,10de:22ba video=efifb:off module_blacklist=nvidia systemd.unit=pfs-winvm-hybrid.target"
"Arch Linux vfio NVIDIA 4090 WinVM only" "[...] amd_iommu=on iommu=pt vfio-pci.ids=10de:2684,10de:22ba video=efifb:off module_blacklist=nvidia systemd.unit=pfs-winvm.target"
"Arch Linux vfio NVIDIA 4090 Linux only NO NVIDIA" "[...] amd_iommu=on iommu=pt module_blacklist=nvidia"
"Arch Linux vfio NVIDIA 4090 Linux only" "[...] amd_iommu=on iommu=pt"
Custom systemd targets
Hybrid
$ systemctl cat pfs-winvm-hybrid.target
# /etc/systemd/system/pfs-winvm-hybrid.target
[Unit]
Description=PFS WinVM hybrid Target
Requires=graphical.target
Conflicts=rescue.service rescue.target
After=graphical.target rescue.service rescue.target
AllowIsolate=yes
$ systemctl cat pfs-winvm-hybrid.service
# /etc/systemd/system/pfs-winvm-hybrid.service
[Unit]
Description=PFS WinVM hybrid
After=libvirtd.service
[Service]
ExecStart=virsh start win11new
Restart=no
Type=oneshot
[Install]
WantedBy=pfs-winvm-hybrid.target
Nur VM
$ systemctl cat pfs-winvm.target
# /etc/systemd/system/pfs-winvm.target
[Unit]
Description=PFS WinVM Target
Requires=multi-user.target
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target
AllowIsolate=yes
$ systemctl cat pfs-winvm.service
# /etc/systemd/system/pfs-winvm.service
[Unit]
Description=PFS WinVM
After=pfs-winvm.target
[Service]
ExecStart=virsh start win11new
Restart=no
Type=oneshot
[Install]
WantedBy=pfs-winvm.target
IOMMU (wichtig)
Damit man einzelne PCI devices zum Guest durchreichen kann, muss im BIOS IOMMU aktiv sein (SVM sowieso für Virtualisierung).
Es können immer nur ganze IOMMU Gruppen durchgereicht werden, nicht einzelne Geräte innerhalb einer Gruppe.
IOMMU Group 13:
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD102 [GeForce RTX 4090] [10de:2684] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation AD102 High Definition Audio Controller [10de:22ba] (rev a1)
IOMMU Group 14:
02:00.0 Non-Volatile memory controller [0108]: Seagate Technology PLC FireCuda 530 SSD [1bb1:5018] (rev 01)
IOMMU Group 15:
03:00.0 PCI bridge [0604]: Advanced Micro Devices, Inc. [AMD] Device [1022:43f4] (rev 01)
Hier ist Group 13 die Gruppe in der mein NVIDIA VGA Controller und das NVIDIA Audio Device ist. Deshalb auch beide auf jeden Fall durchreichen!
USB Controller reiche ich auch durch, aber da musste ich schauen, welche überhaupt einzeln durchreichbar waren.
Skript um die IOMMU Gruppen schnell zu sehen:
$ cat iommu_groups.sh
#!/bin/bash
shopt -s nullglob
for iommu_group in /sys/kernel/iommu_groups/*; do
echo "IOMMU Group ${iommu_group##*/}:"
for d in "${iommu_group}/devices"/*; do
echo -e "\t$(lspci -nns "${d##*/}")"
done
done
CPU Topologie und Cache
lstopo
: Tool um schnell die Topologie der CPU zu sehen. Also um herauszufinden, welche Kerne auf welchem CCD liegen.
Bei meiner CPU haben die Cores 0-7 die virtuellen Cores 0-7 und 16-23 und die Cores 8-15 die virtuellen Cores 8-15 und 24-31.
Außerdem ist mit lstopo
erkennbar, dass die Cores 0-7 mit dem ersten L3 Cache verbunden sind, und die Cores 8-15 mit dem Zweiten.
Es ist ratsam für die Performance, dass sich die VM Cores und die Host Cores keinen Cache teilen, also in meinem Fall alle virtuellen
Cores der Cores 0-7 durchzureichen, sprich 0-7,16-23. Siehe XML File.
CPUs isolieren bei VM Start
$ cat /etc/libvirt/hooks/qemu
#!/usr/bin/bash
__systemctl_set_cpu_isolation_properties__() {
local prop="$1"
systemctl set-property --runtime -- "pfs-rust-compile.slice" "$prop"
systemctl set-property --runtime -- "user.slice" "$prop"
systemctl set-property --runtime -- "system.slice" "$prop"
systemctl set-property --runtime -- "init.scope" "$prop"
}
vmisolatecpus() {
__systemctl_set_cpu_isolation_properties__ "AllowedCPUs=8-15,24-31"
}
vmfreecpus() {
__systemctl_set_cpu_isolation_properties__ "AllowedCPUs=0-31"
}
VM_NAME="$1"
VM_ACTION="${2} ${3}"
LOG_FILE="/home/pfs/shm/libvirt_hook.log"
if [[ "$VM_NAME" == "win11new" ]]; then
if [[ "$VM_ACTION" == "started begin" ]]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] hook triggered: $* -> isolating CPUs 8-15,24-31" >> "$LOG_FILE"
vmisolatecpus
elif [[ "$VM_ACTION" == "release end" ]]; then
echo "[$(date "+%Y-%m-%d %H:%M:%S")] hook triggered: $* -> freeing all CPUs" >> "$LOG_FILE"
vmfreecpus
else
echo "[$(date "+%Y-%m-%d %H:%M:%S")] hook ignored: $*" >> "$LOG_FILE"
fi
fi