GPU passthrough lets a VM directly access a physical GPU. No emulation, no virtualization overhead — the VM sees real hardware and gets real performance. It’s the only way to run GPU workloads (gaming, machine learning, transcoding) in VMs without massive performance loss.
It’s also one of the most finicky things to configure. Hardware compatibility, IOMMU groups, driver issues — any of these can break passthrough completely. When it works, it’s magical. When it doesn’t, debugging is painful.
Passthrough is hardware compatibility plus attention to detail.
Prerequisites
Hardware Requirements
CPU:
- Intel: VT-d (IOMMU support)
- AMD: AMD-Vi (IOMMU support)
Check BIOS for “VT-d,” “AMD-Vi,” “IOMMU,” or “Virtualization Technology for Directed I/O.”
Motherboard:
- Must support IOMMU
- Consumer boards often have poor IOMMU groups
- Server/workstation boards usually work better
GPU:
- Most discrete GPUs work
- NVIDIA consumer cards have “Code 43” issues (we’ll address)
- AMD cards generally work well
- Intel integrated graphics: limited support
Check IOMMU Support
# Inteldmesg | grep -e DMAR -e IOMMU
# AMDdmesg | grep AMD-Vi
# Should see messages like:# DMAR: IOMMU enabled# AMD-Vi: Enabling IOMMUIf no messages, enable IOMMU in BIOS.
Enable IOMMU
Edit GRUB Configuration
# Edit GRUB confignano /etc/default/grubFor Intel:
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"For AMD:
GRUB_CMDLINE_LINUX_DEFAULT="quiet amd_iommu=on iommu=pt"Apply changes:
update-grubrebootVerify IOMMU Enabled
dmesg | grep -e DMAR -e IOMMU -e AMD-Vi
# Should see:# DMAR: IOMMU enabled# or# AMD-Vi: AMD IOMMUv2 loadedIOMMU Groups
IOMMU groups are sets of devices that must be passed through together. You can’t pass a single device if it’s in a group with other devices.
View IOMMU Groups
#!/bin/bash# Save as /root/iommu-groups.shshopt -s nullglobfor g in $(find /sys/kernel/iommu_groups/* -maxdepth 0 -type d | sort -V); do echo "IOMMU Group ${g##*/}:" for d in $g/devices/*; do echo -e "\t$(lspci -nns ${d##*/})" donedoneExample output:
IOMMU Group 1: 00:01.0 PCI bridge [0604]: Intel Corporation... 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation... [10de:2204] 01:00.1 Audio device [0403]: NVIDIA Corporation... [10de:1aef]The GPU (01:00.0) and its audio device (01:00.1) are in the same group. You must pass both.
ACS Override (If Needed)
Poor IOMMU grouping (everything in one group) can be fixed with ACS override patch. Use with caution — it reduces isolation security.
# Add to GRUBGRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt pcie_acs_override=downstream,multifunction"
update-grubrebootAfter reboot, check groups again. They should be smaller.
VFIO Configuration
VFIO (Virtual Function I/O) binds devices for passthrough.
Identify Device IDs
lspci -nn | grep -i nvidia# 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA102 [GeForce RTX 3090] [10de:2204]# 01:00.1 Audio device [0403]: NVIDIA Corporation GA102 High Definition Audio Controller [10de:1aef]Device IDs: 10de:2204 (GPU), 10de:1aef (Audio)
Configure VFIO
# Add VFIO modulesecho "vfio" >> /etc/modulesecho "vfio_iommu_type1" >> /etc/modulesecho "vfio_pci" >> /etc/modulesecho "vfio_virqfd" >> /etc/modules
# Bind devices to VFIO (use YOUR device IDs)echo "options vfio-pci ids=10de:2204,10de:1aef disable_vga=1" > /etc/modprobe.d/vfio.conf
# Blacklist host drivers (so host doesn't grab GPU)echo "blacklist nouveau" >> /etc/modprobe.d/blacklist.confecho "blacklist nvidia" >> /etc/modprobe.d/blacklist.confecho "blacklist nvidia_drm" >> /etc/modprobe.d/blacklist.confecho "blacklist nvidiafb" >> /etc/modprobe.d/blacklist.conf
# For AMDecho "blacklist amdgpu" >> /etc/modprobe.d/blacklist.confecho "blacklist radeon" >> /etc/modprobe.d/blacklist.conf
# Update initramfsupdate-initramfs -u
# RebootrebootVerify VFIO Binding
lspci -nnk -s 01:00# Should show:# Kernel driver in use: vfio-pciIf it shows nvidia or nouveau, the blacklist didn’t work. Check modprobe configuration.
Create VM with GPU Passthrough
VM Configuration
# Create VMqm create 100 --name gpu-vm --memory 16384 --cores 8 --sockets 1 \ --bios ovmf --machine q35 \ --net0 virtio,bridge=vmbr0 \ --scsihw virtio-scsi-pci
# Add EFI diskqm set 100 --efidisk0 local-zfs:1,format=raw
# Add main diskqm set 100 --scsi0 local-zfs:100,ssd=1
# CPU settings (important for passthrough)qm set 100 --cpu host,hidden=1,flags=+pcid
# Add PCI devicesqm set 100 --hostpci0 01:00,pcie=1,x-vga=1
# Add audio device if in same IOMMU groupqm set 100 --hostpci1 01:00.1,pcie=1Important Settings
BIOS: OVMF (UEFI)
- Required for modern GPUs
- Enables PCI passthrough features
Machine: q35
- Modern chipset with proper PCIe support
CPU: host,hidden=1
host: Pass through all CPU featureshidden=1: Hide hypervisor from VM (needed for NVIDIA)
hostpci: pcie=1,x-vga=1
pcie=1: Use PCIe mode (required)x-vga=1: Primary graphics device
NVIDIA-Specific Fixes
NVIDIA drivers detect virtualization and refuse to work (“Error 43”). Several workarounds:
Hide Hypervisor
Already done with cpu: host,hidden=1. For older NVIDIA drivers, you may also need:
# In /etc/pve/qemu-server/100.conf, add to args (if Error 43 persists):args: -cpu 'host,hv_vendor_id=NV43FIX,+kvm_pv_unhalt,+kvm_pv_eoi'Note: Modern NVIDIA drivers (535+) usually work with just hidden=1.
Vendor ID Spoofing
# Add to VM configqm set 100 --args "-cpu 'host,hv_vendor_id=randomid'"ROM File (Sometimes Needed)
Some GPUs need their VBIOS dumped and passed:
# Dump ROM (from another system or Windows)# Or download from TechPowerUp
# Add to VM configqm set 100 --hostpci0 01:00,pcie=1,x-vga=1,romfile=gpu.romPlace ROM file in /usr/share/kvm/.
AMD GPU Passthrough
AMD is generally easier:
# VFIO config (use AMD device IDs)echo "options vfio-pci ids=1002:xxxx,1002:xxxx" > /etc/modprobe.d/vfio.conf
# Blacklistecho "blacklist amdgpu" >> /etc/modprobe.d/blacklist.confecho "blacklist radeon" >> /etc/modprobe.d/blacklist.conf
update-initramfs -urebootAMD GPUs usually work without additional tweaks.
AMD Reset Bug
Some AMD GPUs (Polaris, Navi) have reset bugs — VM shutdown leaves GPU in bad state, requiring host reboot.
Workaround:
# Install build dependenciesapt install pve-headers-$(uname -r) git build-essential dkms
# Clone and build vendor-reset modulegit clone https://github.com/gnif/vendor-reset.gitcd vendor-resetdkms install .
# Verify module loadsmodprobe vendor-reset
# Make persistent after successful testecho "vendor-reset" >> /etc/modulesOther PCI Devices
Passthrough works for any PCI device, not just GPUs:
USB Controller
Pass entire USB controller for low-latency USB:
# Find USB controllerlspci | grep USB
# Pass throughqm set 100 --hostpci2 00:14.0,pcie=1NVMe Controller
Pass NVMe for direct storage access:
# Find NVMelspci | grep NVMe
# Pass throughqm set 100 --hostpci3 03:00.0,pcie=1Network Card
Pass dedicated NIC:
qm set 100 --hostpci4 04:00.0,pcie=1Troubleshooting
Device Not Bound to VFIO
# Check current driverlspci -nnk -s 01:00
# If not vfio-pci:# 1. Check blacklistcat /etc/modprobe.d/blacklist.conf
# 2. Check VFIO configcat /etc/modprobe.d/vfio.conf
# 3. Rebuild initramfsupdate-initramfs -u -k all
# 4. RebootVM Won’t Start
# Check QEMU logcat /var/log/pve/qemu-server/100.log
# Common issues:# - IOMMU not enabled# - Device in use by host# - Wrong device IDNo Display Output
# Verify x-vga=1 is setgrep hostpci /etc/pve/qemu-server/100.conf
# Try different video output# Monitor on GPU should show VM boot
# Check if VM is actually runningqm status 100NVIDIA Error 43
# Verify hidden flaggrep cpu /etc/pve/qemu-server/100.conf# Should include hidden=1
# Try vendor ID spoof# Add to args: hv_vendor_id=NV43FIX
# Ensure BIOS is OVMF, not SeaBIOSPoor Performance
# Inside VM:# Check if GPU is using correct drivernvidia-smi # Should show GPU
# Check PCIe link speedlspci -vv -s 01:00 | grep -i width# Should show x16 or at least x8
# Ensure cpu type is 'host'Single GPU Passthrough
Using your only GPU in a VM (no display on host):
# Host boots headless# VM gets GPU# Reconnect display to VM
# Challenges:# - Host has no display# - Must manage via SSH/remote# - GPU must unbind from host console
# Scripts needed for:# 1. Unbind GPU from host# 2. Start VM# 3. Stop VM# 4. Rebind GPU to hostThis is complex. Search for “single GPU passthrough scripts” for examples.
Live Migration Limitations
VMs with passthrough cannot live migrate:
- Hardware is physically on one host
- Must stop VM, move, start on new host
- Not compatible with HA auto-failover
Plan accordingly: critical passthrough VMs can’t be HA.
The Lesson
Passthrough is hardware compatibility plus attention to detail.
When passthrough doesn’t work, it’s usually:
- IOMMU not enabled: Check BIOS and kernel parameters
- Bad IOMMU groups: ACS override or different hardware
- Driver conflict: Host driver grabs device before VFIO
- NVIDIA detection: Hidden flags and vendor ID spoof
- Reset bugs: AMD GPUs need vendor-reset module
The debugging process:
- Verify IOMMU enabled (
dmesg | grep IOMMU) - Check IOMMU groups (all needed devices in one group)
- Verify VFIO binding (
lspci -nnk) - Check VM logs (
/var/log/pve/qemu-server/) - Try minimal config, add features one at a time
Passthrough isn’t guaranteed to work with all hardware. Some motherboards have terrible IOMMU groups. Some GPUs have bugs. Do research before buying hardware specifically for passthrough.
When it works, you get bare-metal GPU performance in a VM. When it doesn’t, you need patience and systematic debugging. There’s no magic fix — just working through each requirement methodically.