On this page
- What the handoff actually changes
- Step 1: prove the host can isolate PCI devices
- Step 2: inspect the GPU’s whole IOMMU group
- Step 3: attach the device to a stopped VM
- Step 4: install the guest driver and prove the workload
- The old GPU-passthrough fixes I would not start with
- What I would choose for each workload
- What’s next
The frustrating version of a media or AI server is a machine with a perfectly good GPU sitting idle while every heavy job lands on the CPU. If I want one Proxmox VM to use that card properly, I do not try to make the GPU vaguely visible everywhere. I give the whole PCI device to one guest, then prove the guest—not the host—owns it.
That is what PCI passthrough does. The payoff can be Jellyfin hardware transcoding or Ollama model acceleration. The cost is exclusivity: while the VM owns the card, the Proxmox host and other guests do not.
This guide uses VM ID 200 and example PCI addresses 0000:03:00.0 and 0000:03:00.1. Replace them with your VM ID and the addresses reported by your own host. Firmware labels also vary by motherboard. If a value looks specific to one machine, it is a placeholder to change—not a literal to copy.
What the handoff actually changes
Proxmox VE uses Linux VFIO—Virtual Function I/O—to expose a physical device directly to a VM. The IOMMU, or input-output memory management unit, places a boundary around the device’s direct memory access. The Linux kernel documentation calls the IOMMU group the unit of ownership, which is why I check the whole group before touching a VM configuration.
The diagram is also the rule I use when troubleshooting: firmware creates the isolation boundary, VFIO owns the group on the host, Proxmox assigns it, and the guest driver finally makes it useful. Skipping a layer leaves a card that appears in a menu but cannot do work.
Step 1: prove the host can isolate PCI devices
Before changing Proxmox, I open the motherboard firmware and enable Intel VT-d or AMD IOMMU. I also enable Above 4G Decoding when the board offers it. Names vary, so the motherboard manual wins over any screenshot from another machine.
The current Proxmox PCI passthrough documentation requires IOMMU support in the CPU, chipset, firmware, and host kernel. After the firmware change, I boot Proxmox and inspect reality instead of assuming the toggle worked:
dmesg | grep -Ei 'DMAR|IOMMU|AMD-Vi'
find /sys/kernel/iommu_groups -type l | wc -l
A non-zero group count is the useful result. If it stays at zero, I check the active kernel command line with cat /proc/cmdline. Proxmox documents two bootloader paths: GRUB reads GRUB_CMDLINE_LINUX_DEFAULT from /etc/default/grub and applies it with update-grub; systemd-boot reads one line from /etc/kernel/cmdline and applies it with proxmox-boot-tool refresh. I add the vendor parameter—intel_iommu=on or amd_iommu=on—without deleting the existing options, then reboot and repeat the check.
Do not pass through the only display adapter until you have working network access and, ideally, a physical or out-of-band console. Once VFIO owns that GPU, the Proxmox host should no longer use it for its own display.
The paired GPU passthrough preflight playbook turns these read-only checks into one report. I run it before changing a VM.
Step 2: inspect the GPU’s whole IOMMU group
I list display and audio functions, then map them back to their IOMMU groups. A discrete GPU commonly exposes one function for graphics and another for HDMI or DisplayPort audio.
lspci -nnk | grep -A3 -Ei 'VGA|3D|Display|Audio'
for device in /sys/kernel/iommu_groups/*/devices/*; do
printf 'Group %s ' "$(basename "$(dirname "$(dirname "$device")")")"
lspci -nns "$(basename "$device")"
done | sort -V
I am looking for a group containing only the GPU’s related functions. The kernel’s VFIO documentation explains why: an IOMMU group is the smallest set the platform can isolate safely. If an unrelated storage or network controller sits in that group, I stop and try another slot or firmware setting.
I do not paste in pcie_acs_override just to make the list prettier. That option can split the software-visible groups without changing the physical PCIe topology underneath. A homelab service is not worth weakening the host’s DMA boundary.
Step 3: attach the device to a stopped VM
I shut down VM 200, open Hardware → Add → PCI Device, choose Raw Device, and select the GPU. If its display and audio functions share the same slot and group, I enable All Functions. For a q35 VM I enable PCI-Express; I select Primary GPU only when the guest should drive a physical display.
The equivalent CLI form is useful because it leaves an easy-to-audit configuration line:
qm set 200 --hostpci0 0000:03:00.0,pcie=1
qm set 200 --hostpci1 0000:03:00.1,pcie=1
qm config 200 | grep '^hostpci'
The two explicit addresses make the handoff auditable. If my GPU and audio function are not a coherent group, I do not paper over that with a copied flag—I go back to the group inspection.
The handoff is intentionally exclusive. It also ties the VM to a node with that device, so I treat the GPU as part of the workload’s placement plan rather than an invisible cluster resource.
Step 4: install the guest driver and prove the workload
lspci -nnk | grep -A3 -Ei 'VGA|3D|Display'
ls -l /dev/dri 2>/dev/null || true
nvidia-smi 2>/dev/null || true
For Jellyfin, I follow its hardware-acceleration guide and vendor page. Intel graphics normally use Quick Sync Video or VA-API on Linux; NVIDIA uses NVENC; AMD uses VA-API. I enable only codecs the card actually supports, force a real transcode, then check the FFmpeg log and the vendor monitor. A device node alone is not an acceptance test.
For Ollama, I first check the card against Ollama’s current hardware support matrix. I load a model, send one prompt, and run:
ollama run gemma3:4b 'Reply with exactly: GPU check complete.'
ollama ps
The official Ollama FAQ defines the PROCESSOR column: 100% GPU is a full GPU load, 100% CPU is system memory, and a CPU/GPU split is partial offload. That is a much stronger check than assuming a faster response means acceleration.
The old GPU-passthrough fixes I would not start with
Old guides often begin with NVIDIA Code 43 concealment, ROM files, framebuffer disabling, unsafe interrupts, or AMD reset workarounds. Those were real fixes for particular combinations of card, driver, firmware, and kernel—but they are poor defaults.
I start with current firmware, a current Proxmox kernel, a clean IOMMU group, and the current guest driver. Then I troubleshoot the error I can reproduce. The Proxmox administration guide, the vendor driver documentation, and the guest’s logs are a better ladder than a five-year-old block of kernel parameters.
If a card works once but fails after the guest restarts, that is when I investigate reset behavior for that exact model. If the Windows guest reports a driver error, I check the current vendor release notes before adding a virtualization-hiding workaround. Specific evidence first; folklore second.
What I would choose for each workload
For a media VM, I favor a card with a well-supported fixed-function encoder rather than the biggest gaming GPU. Jellyfin’s current documentation validates Intel Quick Sync, NVIDIA NVENC/NVDEC, and AMD VA-API paths, with codec support varying by generation.
For local AI, VRAM and Ollama’s support matrix matter more than video engines. Ollama currently documents NVIDIA CUDA support and selected AMD GPUs through ROCm, with additional GPU support through Vulkan. I check the exact model before buying; a card that physically fits is not automatically supported by the software stack.
That leaves one honest tradeoff: the best Jellyfin card and the best Ollama card may not be the same purchase. Passthrough makes either workload fast, but it does not turn one physical GPU into two independently schedulable devices.
What’s next
Once the VM passes its workload test, I add it to backups and document the PCI slot as a node-local dependency. If I later move the service, I remove the hostpci assignment before expecting the VM to boot on hardware without that card.
This guide joins the Proxmox Virtualization Basics series: build the VM first, then add passthrough only for a workload that can prove it needs the device.
Related posts:
- Creating Your First Proxmox VM: A Complete Walkthrough — build the q35 guest before adding physical hardware
- Install Windows on Proxmox with VirtIO Drivers — get a fast Windows guest running before you pass it a GPU
- Proxmox VM vs LXC: When to Use Each — why whole-device passthrough is a strong reason to choose a VM
- Install Jellyfin on a NAS or Proxmox LXC — install the media server before enabling its accelerator
- Running Ollama on a 3-Node Proxmox LXC Cluster — compare CPU-first local AI with a GPU-backed guest
- Proxmox Security Hardening: SSH Keys, Firewall, and More — protect the hypervisor that owns the DMA boundary
- Proxmox Backup Server: Automated Container Backups — back up the guest configuration before experimenting
Recommended hardware:
- Intel Arc A310 low-profile GPU — a compact Intel option from the Arc family covered by Jellyfin’s hardware guide
- NVIDIA RTX A2000 12GB low-profile GPU — a compact CUDA card listed in Ollama’s support matrix
- NVIDIA RTX 3060 12GB GPU — a widely available 12 GB CUDA option listed in Ollama’s support matrix
This post contains Amazon affiliate links (tag: buildahomelab-20). I earn a small commission on qualifying purchases at no extra cost to you.
Comments
Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.