Proxmox GPU passthrough: give one VM the whole card

Pass a GPU through to a Proxmox VM safely: verify IOMMU isolation, attach the card, install guest drivers, and prove Jellyfin or Ollama can use it.

On this page
  1. What the handoff actually changes
  2. Step 1: prove the host can isolate PCI devices
  3. Step 2: inspect the GPU’s whole IOMMU group
  4. Step 3: attach the device to a stopped VM
  5. Step 4: install the guest driver and prove the workload
  6. The old GPU-passthrough fixes I would not start with
  7. What I would choose for each workload
  8. 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.

First: make these values your own

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.

A GPU and its audio function move through the IOMMU boundary into one virtual machineThe Proxmox host keeps storage and networking, while one complete GPU IOMMU group is assigned exclusively to a guest that runs Jellyfin or Ollama.Proxmox hoststorage + networkother guestsGPU unavailable hereIOMMU group03:00.0 GPUdisplay / compute03:00.1 audioVM 200vendor guest driverJellyfintranscodeOllamainferenceOne complete isolation group → one guest owner

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

1Enable IOMMU in firmware10–20 min

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:

On the Proxmox host — verify IOMMU and groups

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.

Keep a way back into the host

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

2Find every function attached to the card5 min

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.

Identify the GPU and its related functions

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

3Use the Proxmox hardware panel5 min

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:

Assign the example GPU and audio functions

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

4Verify the device inside the guest10–30 min

After the VM boots, I install the current guest driver from Intel, NVIDIA, or AMD for that operating system. Then I confirm the guest sees the card before configuring Jellyfin or Ollama.

Inside a Linux guest — confirm the driver owns the card

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:

Inside the Ollama guest — verify model placement

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:


Recommended hardware:

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.