vGPU (SR-IOV) with Intel 12th Gen iGPU [Updated 2023]

Virtualize Intel iGPU for multiple VMs for hardware accelerated graphics and media encode/decode.

vGPU (SR-IOV) with Intel 12th Gen iGPU [Updated 2023]

[Update 2023-03-04] The following guide has been updated to reflect some detailed steps and up-to-date info with Proxmox kernel 6.2

Contents
[Part I: Foreword]
[Part II: Prerequisite]
[Part III: Proxmox Host Installation]
[Part IV: Linux VM Usage]
[Part V: Windows VM Usage]
[Part VI: LXC Usage]
[Part VII: Conclusion]

Part I: Foreword:

I’ve always dreamed about using my hardware as efficiently as possible. In today’s PC master race, it almost felt like we are only using less than 20% of what our PCs are really capable of. For example, Intel ships most of its CPUs with a built-in iGPU. Although not a 3D Mark champ, it still packs a punch when it comes to media decoding and encoding. Previously mentioned in my NAS build, I went with an Intel 12th gen i3-12100. It has an Intel UHD 730 iGPU which does wonders for media encoding and decoding, especially the support for AV1 decode, which is absent on all other GPUs on the market, other than the Intel ARC and NVIDIA RTX 40 series.

So naturally, I started looking for hacks that can enable me to virtualize this iGPU for various VM uses, such as Parsec on Windows guest VMs, and hardware accelerated media transcoding/decoding with Plex or Jellyfin.

This guide will walk you through all the necessary steps to enable Intel iGPU for virtualization by modifying the kernel and driver using DKMS, and create virtual functions to pass these virtual GPUs to VMs.

Special thanks to:

Strongz for this DKMS module that made it all possible (https://github.com/strongtz/i915-sriov-dkms)

Tteck for Proxmox Helper Scripts (https://tteck.github.io/Proxmox/)

Foxi (https://foxi.buduanwang.vip/virtualization/pve/2695.html/)

WARNING:

This guide is highly experimental. If you are trying to use this guide on your production machine, DON'T DO IT. Test it with a spare system first, work out all the kinks before you move on to your production environment. From my experience, even if it seems to be running fine, there could be a lot of instabilities that we just don't know about yet.

Part II: Prerequisite:

Motherboard: Turn on SRIOV and VT-d in BIOS.

Proxmox Virtual Environment 7+

6.1 or newer Kernel (6.2 has been released by Proxmox, nothing has changed) 5.19 Kernel

You can update the PVE kernel to 6.2 5.19 using these commands:

  1. Disable enterprise repo.
    nano /etc/apt/sources.list

  Then use the following repos from Proxmox:

💡
https://pve.proxmox.com/wiki/Package_Repositories

 Alternatively, just use Tteck's Proxmox Helper Script (PVE 7 ONLY):

💡
bash -c "$(wget -qLO - https://github.com/tteck/Proxmox/raw/main/misc/post-pve-install.sh)"
  1. Install 6.1 kernel (or 6.2).
      apt install pve-kernel-6.1
  2. Update apt and install 6.1 headers.
      apt update && apt install pve-headers-$(uname -r)
  3. Update initramfs and reboot.
      update-initramfs -u
         reboot

Part III.1: Proxmox Host Installation

After rebooting your system, do a kernel name check to see if 6.1 kernel has applied.

uname -r

If you can see this output, it means your kernel has been updated to 6.1 (or 6.2, it works).

6.1.10-1-pve

Now, we can proceed to installing the DKMS module.

  1. Install DKMS, Unzip and build tools.
    apt install git dkms build-* unzip -y
  2. Download the zip file for the modified DKMS module for Debian, place it under root directory, then:
wget https://mirrors.apqa.cn/d/proxmox-edge/intel_gpu_sriov/i915-sriov-dkms-6.1.zip
unzip i915-sriov-dkms-6.1.zip

3.  Install the module with the following command:

cd i915-sriov-dkms-6.1/
dkms add .
dkms install i915-sriov-dkms/6.1

When that’s done, use the following command to verify the installation:

dkms status

The correct output should look like this:

i915-sriov-dkms, 6.1, 6.1.10-1-pve, x86_64: installed
💡
To uninstall this DKMS module, do "dkms -uninstall i915-sriov-dkms/6.1"

Part III.2: Host Kernel Command Line Modification

Now the DKMS module is loaded, we will need to edit GRUB to enable the virtual functions.

  1. Edit GRUB:
      nano /etc/default/grub
  2. Look for “GRUB_CMDLINE_LINUX_DEFAULT” line, and add the following:
      intel_iommu=on i915.enable_guc=7
  3. Your line could look like this: GRUB_CMDLINE_LINUX_DEFAULT="quiet intel_iommu=on iommu=pt   i915.enable_guc=7"
  4. Update initramfs:
      update-initramfs -u
      pve-efiboot-tool refresh
  5. The second command is probably redundant but I am including it anyway.
  6. Install sysfsutils and configure sysfs.conf:
      apt install sysfsutils -y
      echo "devices/pci0000:00/0000:00:02.0/sriov_numvfs = 7" > /etc/sysfs.conf
  7. Please note, this is assuming your iGPU’s PCI address is the same as mine (0000:00:02). If not, you will have to change the command appropriately. If you are not sure which address your iGPU is assigned to, you can use the following command to verify:
      lspci | grep "VGA"
  8. If you have a discrete GPU installed, you may see two or more outputs. Identify the Intel iGPU address, and use that for the command above.
💡
IMPORTANT: Although you can have up to 7 VFs (Virtual Functions), however, each of them will reserve at least 16MB of RAM, and each VF can could use up to 2GB of system RAM as VRAM when used in VMs.

Part III.3: Download iGPU Firmware (Optional)

  1. Check if the firmware exists on your system:
    ls /lib/firmware/i915/tgl_guc_70.1.1.bin
  2. If the output is empty, download the firmware from here:
    wget https://mirrors.apqa.cn/d/proxmox-edge/intel_gpu_sriov/i915-firmware.tar.gz
  3. Then: tar -xf i915-firmware.tar.gz
  4. Finally, copy to the firmware folder: cp ./firmware/* /lib/firmware/i915/
  5. Note: this is the firmware for 12th/13th gen iGPUs (Alder Lake, Raptor Lake). If you are using 11th gen, you have to source your own firmware.
  6. Reboot your system
    reboot

Part III.4: Verification

After a system reboot, check dmesg to see if your iGPU has virtual functions enabled, or use the following:

lspci  | grep VGA

You should see a similar output under PCIe address your iGPU is assigned to:

00:02.0 VGA compatible controller: Intel Corporation Device 4692 (rev 0c)
00:02.1 VGA compatible controller: Intel Corporation Device 4692 (rev 0c)
00:02.2 VGA compatible controller: Intel Corporation Device 4692 (rev 0c)
00:02.3 VGA compatible controller: Intel Corporation Device 4692 (rev 0c)
...

If you don't see any VFs, do dmesg | grep i915 to see what kind of error messages you're getting.

Part IV: Linux VM Usage

If you can see the VFs are enabled, congratulations! Now we can move on to setting up the hosts to use these virtual GPUs.

If you are using the VFs in Linux VMs, you are in luck, because you have already practices every necessary steps before in the Host setup. Simply put:

  1. Use Q35+OVMF machine type for the Linux guest. Do not add the VF first, we will do that during step 7.
  2. After the OS has been installed and is requesting a reboot, proceed with the reboot. However, during POST, enter VM's UEFI settings, and turn off secure boot.
  3. Upgrade the Linux Guest to 6.2 Kernel along with the headers, which Intel has already mainlined many SRIOV features,
  4. Download and compile that DKMS module we have used in the Proxmox Host.
  5. When it comes to modifying the command line defaults, use i915.enable_nuc=3 instead of i915.enable_nuc=7.
  6. Update GRUB and initramfs, then shutdown the VM.
  7. Now you can add the VF, disable VM's emulated graphics, and start up the VM again to see if everything's working.

To verify the Guest VM has been configured correctly, do dmesg | grep i915, you should see an output that is similar to this:

Kernel reports the virtual GPU is indeed running in VF mode

Furthermore, we can verify by installing vainfo. Here's a sample output:

vainfo also picked up the VF correctly

And - last but not least, clinfo.  Before you can use OpenCL, you have to add Intel's repository to apt, then install the required packages. Here's how:

  1. Install wget and gpg-agent:
    apt install wget gpg-agent

  2. Download the keys:
    wget -qO - https://repositories.intel.com/graphics/intel-graphics.key | sudo gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg

  3. Add the Repo:
    echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | sudo tee /etc/apt/sources.list.d/intel.gpu.jammy.list

  4. Install Run-time Packages:
    apt install -y intel-opencl-icd intel-level-zero-gpu level-zero intel-media-va-driver-non-free libmfx1 libmfxgen1 libvpl2 libegl-mesa0 libegl1-mesa libegl1-mesa-dev libgbm1 libgl1-mesa-dev libgl1-mesa-dri libglapi-mesa libgles2-mesa-dev libglx-mesa0 libigdgmm12 libxatracker2 mesa-va-drivers mesa-vdpau-drivers mesa-vulkan-drivers va-driver-all

  5. Install Developer Packages:
    apt install -y libigc-dev intel-igc-cm libigdfcl-dev libigfxcmrt-dev level-zero-dev clinfo

  6. To use the VF for render/computing, you will need to be in the render user-group.
    stat -c "%G" /dev/dri/render*
    groups ${USER}

    If a group is listed for the render node which isn’t listed for the user, you will need to add the user to the group using gpasswd.
    sudo gpasswd -a ${USER} render
    newgrp render

Finally, if all things went well, you should be able to use clinfo to see a similar output to this:

clinfo identified the VF correctly and loaded the correct drivers

That's all there is to it. Have fun!

Part V: Windows VM Usage

You can now create a VM and use one of the virtual functions inside the VM. For example, this is my Windows 10 Pro VM configuration. It is necessary to use "Host" as the Processor Type:

Inside “PCI Device”, I assigned one of the virtual functions (virtualized GPU) to it:

It looks like this:

I won’t go into too much detail of how to create a Windows VM. For short:

  1. Machine type must be q35 with OVMF BIOS (UEFI boot), CPU Type set to "Host".
  2. Boot the VM with default Proxmox emulated graphics, then enable Remote Desktop.
  3. Shut down the VM, remove the Proxmox graphics (set it to "none"), then add the virtual function iGPU.
  4. Boot the VM, RDP to it, then install Intel official drivers. I am using gfx_win_101.4146 Intel Arc driver directly downloaded from the Intel website, without the dreaded “Code 43” error.
This screenshot is captured from Tiny11 VM, on i3-12100's UHD 730

This is my another successful setup on an i5-13600K. Handbrake Nightly running a transcoding with QuickSync encoder, on Windows 11 Pro VM with iGPU SR-IOV Virtual Function. (Sorry Lost the Image)

Running Furmark. Like I said, this iGPU is not going to win any benchmark trophies.

Running Unigine Heaven. 720p, Low Preset. iGPU is pegged at 100%. Parsec encode has slowed down significantly, too. From less than 4ms per frame up to almost 10-12 ms per frame.

This is i3-12100 with UHD 730. Score seemed pretty low, only used 2 cores (4 threads) and 4GB RAM in this VM
Parsec encoding time jumped up to 30+ ms per frame, barely hitting 60 fps when Heaven is running

Let's throw Jellyfin Hardware Transcoding into the mix, with Furmark and Parsec running on the Tiny11 VM:

Heaven is still hammering the iGPU but I think it handled it fine. Normally I can get up to 400 fps in Jellyfin transcoding
💡
IMPORTANT: If your iGPU is UHD 730, then there's only ONE media engine per Intel Spec. On the other hand, UHD 770 has TWO, so in theory, UHD 770 can handle twice as much VMs using media encoding simutaneously. However, I believe the 3D capabilities are also shared, meaning, you will get accelerated desktop GUI when using these VFs in a graphical VM.

Part VI: Jellyfin Hardware Acceleration

It is really just as simple as running this script (https://tteck.github.io/Proxmox/). Go to “Media - Photo” and look under “Jellyfin Media Server LXC”. You will see all the information needed.

Just to be on the safe side here, when you first boot up Jellyfin and go through the configuration stage, you have to go back to the admin panel to manually put in the ffmpeg path, which is listed on the help script page itself.

💡
Jellyfin LXC provided above is a "Priveleged Container" which has root access.

Now, time for some configuration.

  1. Out of all the VFs that you have, pick one. Let's say we are going with No.4, which has the PCIe Address of 02.4 (02.0 being the Primary Function, which is the iGPU itself. Never pass this to any VM).
  2. In Proxmox console, type ls -l /dev/dri/by-path to find out which devices are associated with your VF. In this example, I can see VF4 is associated with card4 and renderD132.

3.  Then, edit the Jellyfin LXC container config. nano /etc/pve/lxc/<JELLYFIN LXC ID>.conf

4.  Comment out these two lines: lxc.cgroup2.devices.allow%3A c 29%3A0 rwm  lxc.mount.entry%3A /dev/fb0 dev/fb0 none bind,optional,create=file

5.  Edit this line: lxc.cgroup2.devices.allow: c 226:4 rwm where the '4' in '226:4' is the VF number you have picked. If you picked '1', then it should read '226:1'.

6.  Edit this line: lxc.cgroup2.devices.allow: c 226:132 rwm where the '132' in '226:132' should be the renderD number we obtained earlier with ls -l /dev/dri/by-path

7.  Edit this line: lxc.mount.entry: /dev/dri/card4 dev/dri/card0 none bind,optional,create=file where the first argument /dev/dri/card4 is the VF you picked, the second argument maps it to card0 in the LXC container. I've tried leaving it the same as the host but after trial and error, only mapping it to card0 will work.

8. Edit this line: lxc.mount.entry: /dev/dri/renderD132 dev/dri/renderD128 none bind,optional,create=file pretty straight forward, we are mapping renderD132 from the host (Proxmox) to renderD128 in the LXC container.

SAMPLE CONFIG, FOR REFRENCE ONLY

#lxc.cgroup2.devices.allow%3A c 29%3A0 rwm
#lxc.mount.entry%3A /dev/fb0 dev/fb0 none bind,optional,create=file
arch: amd64
cores: 4
features: mount=nfs,nesting=1
hostname: jellyfin
memory: 3072
net0: name=eth0,bridge=vmbr2,gw=[redacted],hwaddr=[redacted],ip=[redacted],type=veth
onboot: 0
ostype: ubuntu
rootfs: local:136/vm-136-disk-0.raw,size=8G
swap: 1024
unused0: local-lvm:vm-136-disk-0
lxc.cgroup2.devices.allow: c 226:4 rwm
lxc.cgroup2.devices.allow: c 226:132 rwm
lxc.mount.entry: /dev/dri/card4 dev/dri/card0 none bind,optional,create=file
lxc.mount.entry: /dev/dri/renderD132 dev/dri/renderD128 none bind,optional,create=file

9.  Reboot the container, and don't forget to go into Jellyfin Dashboard to change the Hardware Acceleration type to QSV.

To verify if your Jellyfin server is actually using the iGPU VF for decode/encode, play a video that requires transcoding, or force the transcode by using a low bitrate playback, and go to the LXC console and use the following command:

ps aux | grep ffmpeg | grep accel

If you can see “-hwaccel”, then your Jellyfin server is indeed using hardware acceleration to transcode your video.

Part VII. Conclusion

With some tweaks and commands, we successfully used the iGPU's virtual functions in both a Windows VM and a Jellyfin server. I am really glad that I didn’t cheap out on the $15 difference by getting the i3-12100 for my NAS, instead of the i3-12100F. However, after discovering the UHD 730 on this CPU only has one media engine, I do have some buyer's remorse of not going up to i5-12500 for the UHD 770 iGPU. I mean, this iGPU takes up none of the PCIe slots, and provides a really nice functionality that we can only dream on with other higher end graphics cards. Granted, by using vgpu-unlock, we can virtualize an NVIDIA card (Ampere and up excluded) as well. However, for my application, the simplicity of Intel iGPU is hard to beat.

I am expecting an Intel ARC A380 in the next couple of days. I will try to use this DKMS module to see if I can get the A380 to enable virtual functions as well. Until then, good tinkering. Also, I am putting together a test bench to see how the iGPU performs under virtual functions versus just single PCIe passthrough. Stay tuned, in the meanwhile, good tinkering.

UPDATE 2023-03-06: The Intel Arc A380 is a NO GO. The PVE kernel is just not new enough to include the correct i915 driver for the A380. Even if it does, say, when Proxmox get Linux Kernel 6.2, Intel has confirmed that Alchemist WILL NOT SUPPORT SRIOV.