TL;DR
Intel iGPU passthrough from Proxmox to a k3s VM enables hardware video transcoding with minimal CPU overhead. This guide covers the complete process: enabling IOMMU, configuring VFIO, blacklisting the i915 driver, rebuilding the VM with q35/OVMF, and verifying VA-API inside the VM. The Intel UHD 630 in the M920q handles H.264 and HEVC encode/decode at real-time speeds.
Why GPU Passthrough?
Software transcoding (libx264/libx265) on a 6-core i5-8500T is limited:
- 1 stream of 4K HEVC → 1080p H.264: 100% CPU, barely real-time
- 2 simultaneous streams: Not possible without frame drops
- CPU contention: Other workloads on the same node suffer
Hardware transcoding via Intel Quick Sync Video:
- 1 stream of 4K HEVC → 1080p H.264: ~5% CPU
- 5+ simultaneous streams: No problem
- Zero contention: GPU handles transcoding independently
The Challenge
Proxmox runs as the hypervisor on bare metal. The Intel UHD 630 is an integrated GPU — part of the CPU package, not a discrete card. Passing it through to a VM requires:
- IOMMU (Input/Output Memory Management Unit) for device isolation
- VFIO (Virtual Function I/O) driver to claim the device from the host
- Blacklisting the i915 (Intel graphics) driver so the host does not grab the GPU first
- q35 machine type and OVMF (UEFI) BIOS in the VM to support PCIe passthrough
Phase 0: BIOS Configuration
The Lenovo M920q BIOS needs VT-d (Intel Virtualization Technology for Directed I/O) enabled:
BIOS → Security → Virtualization
→ Intel VT-d = Enabled
This is a prerequisite. Without VT-d, IOMMU will not initialize.
Phase 1: Proxmox Host Preparation
I automated this with an Ansible playbook (proxmox-gpu-prep.yml) that configures:
GRUB Configuration
# /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt"
intel_iommu=on: Enable IOMMUiommu=pt: Passthrough mode — only devices explicitly assigned to VFIO get IOMMU protection. Other devices use direct DMA for performance.
Kernel Modules
# /etc/modules-load.d/vfio.conf
vfio
vfio_iommu_type1
vfio_pci
Driver Blacklist
# /etc/modprobe.d/blacklist-gpu.conf
blacklist i915
blacklist snd_hda_intel
The i915 blacklist prevents the Proxmox host from claiming the GPU. snd_hda_intel is the HDMI audio device on the same IOMMU group — it must also be blacklisted.
Apply and Reboot
update-grub
update-initramfs -u
reboot
Verify IOMMU
After reboot:
dmesg | grep -i iommu
# DMAR: IOMMU enabled
lspci -nn | grep VGA
# 00:02.0 VGA compatible controller: Intel Corporation CoffeeLake-S GT2 [UHD Graphics 630] [8086:3e92]
# Verify the GPU is in its own IOMMU group
find /sys/kernel/iommu_groups/ -type l | grep 00:02.0
Phase 2: VM Rebuild with Terraform
Standard Proxmox VMs use the pc-i440fx machine type with SeaBIOS. PCIe passthrough requires the q35 machine type with OVMF (UEFI) firmware. This is not a hot-reconfigurable setting — it requires destroying and recreating the VM.
# terraform.tfvars
{
name = "k3s-agent-3"
vm_id = 122
proxmox_node = "pve-3"
ip_address = "192.168.20.32/24"
cores = 4
memory = 8192
machine_type = "q35"
bios = "ovmf"
gpu_passthrough = [
{
device = "0000:00:02.0"
pcie = true
rombar = true
xvga = false
}
]
}
Before applying:
# Drain workloads from the node
kubectl drain k3s-agent-3 --ignore-daemonsets --delete-emptydir-data
# Apply Terraform (destroys and recreates the VM)
terraform apply -target='module.k3s_agents["k3s-agent-3"]'
Authentication Requirement
The Proxmox Terraform provider’s hostpci block requires root@pam authentication (username + password). API tokens cannot assign PCI devices. This is a Proxmox API limitation, not a Terraform limitation.
Phase 3: GPU Worker Provisioning
After Terraform recreates the VM, an Ansible playbook (gpu-worker.yml) configures the guest:
ansible-playbook -i inventory/homelab playbooks/gpu-worker.yml --limit k3s-agent-3
The playbook installs:
intel-media-va-driver-non-free: VA-API driver for Intel GPUs (the non-free version supports HEVC encoding)vainfo: VA-API diagnostic toolintel-gpu-tools: GPU monitoring utilities
Verify GPU in VM
ssh debian@k3s-agent-3
ls -la /dev/dri/
# card0 renderD128
vainfo
# vainfo: VA-API version: 1.20
# vainfo: Driver version: Intel iHD driver - 25.2.3
# vainfo: Supported profile and entrypoints
# VAProfileH264Main : VAEntrypointVLD
# VAProfileH264Main : VAEntrypointEncSlice
# VAProfileHEVCMain : VAEntrypointVLD
# VAProfileHEVCMain : VAEntrypointEncSlice
# VAProfileVP9Profile0 : VAEntrypointVLD
The VLD (decode) and EncSlice (encode) entries confirm hardware acceleration is working.
Phase 4: Kubernetes Labels
The GPU node gets labeled so workloads can request GPU scheduling:
kubectl label node k3s-agent-3 gpu=intel-uhd-630 cpu-tier=high
Jellyfin’s deployment uses nodeSelector: gpu: intel-uhd-630 to land on this node.
Scaling to Additional Nodes
The M920q nodes all have the same Intel UHD 630 iGPU. The process to add GPU passthrough to additional nodes:
- Run
proxmox-gpu-prep.yml --limit pve-1(or pve-2) - Add
gpu_passthroughconfiguration to the corresponding agent interraform.tfvars terraform apply(destroys + recreates that agent VM)- Run
gpu-worker.yml --limit <node> - Label the node:
kubectl label node <node> gpu=intel-uhd-630
Each additional GPU node increases transcoding capacity. With 3 GPU nodes, the cluster could handle 15+ simultaneous transcoding streams.
Troubleshooting
| Issue | Fix |
|---|---|
dmesg shows no IOMMU | Check VT-d in BIOS. Rerun gpu-prep playbook |
| VM will not start after GPU add | Verify IOMMU group isolation. Check qm config <vmid> |
/dev/dri missing in VM | Ensure hostpci0 in VM config. Check lspci inside VM |
vainfo shows no profiles | Install intel-media-va-driver-non-free. Rerun gpu-worker playbook |
Terraform cannot set hostpci | Verify proxmox_username = "root@pam" (not API token) |
| OVMF boot fails | Ensure cloud-init template supports UEFI or create new UEFI template |
Performance Comparison
| Scenario | Software (CPU) | Hardware (iGPU) |
|---|---|---|
| 1080p H.264 decode | 15% CPU | ~0% CPU |
| 4K HEVC decode | 80% CPU | ~2% CPU |
| 1080p H.264 encode | 60% CPU | ~3% CPU |
| 4K→1080p transcode | 100% CPU (barely real-time) | ~5% CPU |
| Concurrent streams | 1-2 max | 5+ comfortable |
Lessons Learned
- iGPU passthrough is completely viable on the M920q platform. The Intel UHD 630 handles Jellyfin transcoding workloads with ease.
- q35 + OVMF is mandatory for PCIe passthrough. Plan for this from the start if GPU passthrough is in your roadmap.
- The
non-freeVA-API driver is essential for HEVC encoding. The open-source driver only supports decode. root@pamauthentication is required for PCI device assignment in Proxmox. API tokens will not work.- Automate everything with Ansible. GPU host preparation involves modifying GRUB, kernel modules, and driver blacklists — all error-prone if done manually. The playbook makes it reproducible and auditable.
GPU passthrough is one of those homelab capabilities that feels like magic when it works. The Intel UHD 630 is not a powerhouse, but for real-time transcoding of typical media files, it delivers exactly what is needed.