Hardware acceleration
Reference for setup_hwaccel in lib/hwaccel.func and the GPU passthrough the Proxmox VE host side sets up for it.
Source: lib/hwaccel.func
for the container side and
pve/backend.func
for the host side.
GPU support has two halves that run in different places:
- On the Proxmox VE host, while the container is created, the engine detects the GPUs and writes their device nodes into the container's config.
- Inside the container, the install script calls
setup_hwaccel, which installs the userspace drivers that match the devices it finds.
lib/hwaccel.func has exactly one public function, setup_hwaccel. Everything else
in the file is internal.
Turning it on
Both halves are controlled by one setting, var_gpu. A script whose app benefits
from a GPU sets the default in its ct/ file, for example ct/agentdvr.sh:
var_gpu="${var_gpu:-yes}"The user can override it on the command line (var_gpu=yes or var_gpu=no), in a
defaults file, or in the advanced settings dialog "Enable GPU Passthrough?". The
engine turns the result into ENABLE_GPU and exports it into the container.
setup_hwaccel returns immediately when ENABLE_GPU is no, so calling it in an
install script is safe whether or not the user wants a GPU.
What the host passes through
With var_gpu=yes, pve/backend.func does the following before the container's
first start.
Detection. It lists the host's PCI devices of class VGA, XGA, 3D or Display controller. If there are none, nothing is passed through.
| Type | How it is recognised | Device nodes collected |
|---|---|---|
| Intel | /dev/dri/renderD* and /dev/dri/card* whose /sys/class/drm/<node>/device/vendor is 0x8086 | Those nodes |
| AMD | Same, vendor 0x1002 or 0x1022 | Those nodes, plus /dev/kfd if it exists |
| NVIDIA | A PCI entry with vendor ID 10de | Every character device matching /dev/nvidia* and /dev/nvidia-caps/* |
An NVIDIA card without /dev/nvidia* nodes is skipped with a warning: the host
driver is not loaded.
Selection. One detected type is configured automatically. With several types the
host asks which to pass through; the prompt accepts numbers, comma-separated, or A
for all, and falls back to all after 60 seconds. With several NVIDIA cards a second
prompt of the same kind selects the cards. The shared NVIDIA nodes (nvidiactl,
nvidia-uvm*, nvidia-caps/*) are always added.
Config entries. Each selected node becomes a line in
/etc/pve/lxc/<CTID>.conf, numbered across all types:
dev0: /dev/dri/renderD128,gid=44
dev1: /dev/dri/card0,gid=44Group IDs. The gid=44 is a placeholder. After the first start the host reads
the container's real video and render group IDs (creating the groups if they are
missing), stops the container, rewrites the entries so that renderD* and /dev/kfd
belong to render and card* to video, and starts the container again. If that
restart fails, the script exits with code 227.
Disk. When an AMD GPU is among the selected types, the root disk is enlarged by 4 GB to make room for ROCm.
Exports. The host exports GPU_TYPE with the configured types, space-separated,
for example INTEL NVIDIA. Into the container go ENABLE_GPU and CTTYPE
(0 privileged, 1 unprivileged).
setup_hwaccel
Detects the GPUs visible in the container, installs the matching userspace drivers and sets device permissions.
setup_hwaccel [service_user]| Argument | Default | Meaning |
|---|---|---|
service_user | empty | User that runs the application. It is added to the render and video groups |
| Variable | Default | Meaning |
|---|---|---|
ENABLE_GPU | no | Set by the engine from var_gpu. no makes the function return at once |
CTTYPE | 0 | Set by the engine. /dev/dri permissions are only changed in a privileged container (0) |
INSTALL_NVIDIA_DRIVERS | unset | yes or no answers the NVIDIA driver question without a prompt |
| Sets | Value |
|---|---|
HWACCEL_VENDOR (exported) | nvidia, amd, amd_apu, intel or none |
HWACCEL_VENDOR is nvidia only when the NVIDIA card turned out to be usable:
device nodes present, libcuda.so.1 loadable, and its driver series equal to the
host's. Otherwise it is the first Intel or AMD type that was configured, or none.
amd_apu is kept apart from amd because an APU gets Mesa only, not ROCm.
Return value. setup_hwaccel has no error code of its own; every exit is a
return 0 or the end of the function. Hardware acceleration is optional, so a
missing GPU, a missing package or an NVIDIA version mismatch produces a warning and
the install continues. Check HWACCEL_VENDOR if the script has to know the outcome.
The one step that is not guarded is the apt update after the Debian non-free
sources are added: if that fails, the install script's error handler stops the
install.
Examples. install/jellyfin-install.sh passes the service user:
setup_hwaccel "jellyfin"install/audiomuse-ai-install.sh calls it without an argument and then picks its
backend from the result:
setup_hwaccelelif [[ "${HWACCEL_VENDOR:-none}" == "nvidia" ]]; then
var_backend="gpu"Step by step
- Returns if
ENABLE_GPUisno. - Returns with a warning if none of
/dev/dri,/dev/nvidia0,/dev/kfdexists: no GPU was passed through. - Installs
pciutilsiflspciis missing. - Builds the GPU list from
lspci(see Detection). - Selects which GPUs to configure (see Selection).
- Installs drivers per GPU type (see the vendor sections).
- Sets device permissions and group memberships.
- Exports
HWACCEL_VENDOR.
Detection
lspci inside a container shows the host's whole PCI bus, so the list is filtered
to VGA, XGA, 3D and Display controllers and classified by name:
| Type | Matched by | Driver path |
|---|---|---|
INTEL_ARC | Intel, name contains Arc, DG1 or DG2 | Intel Arc |
INTEL_GEN9+ | Intel, name contains UHD, Iris or HD Graphics with a three-digit number 5xx/6xx | Intel Gen 9+ |
INTEL_LEGACY | Intel, HD Graphics with a four-digit number 2xxx to 5xxx | Intel legacy |
INTEL | Any other Intel controller | Intel Gen 9+ |
AMD | Name contains AMD, ATI, Radeon or Advanced Micro Devices | AMD discrete |
NVIDIA | Name contains NVIDIA and a /dev/nvidia<N> node exists in the container | NVIDIA |
AMD_APU | No GPU found at all, and the CPU vendor is AuthenticAMD | AMD APU |
An NVIDIA card that is on the host's bus but has no /dev/nvidia* node in the
container is skipped with a notice. If nothing is found and the CPU is not AMD, the
function warns and returns.
Selection
- One GPU: configured without asking.
- Several GPUs of the same type: all configured without asking, since they share one driver stack.
- Several types: a numbered menu. Enter numbers separated by commas, or
Afor all. After 60 seconds without input, or on invalid input, all are configured.
If an NVIDIA GPU is selected and INSTALL_NVIDIA_DRIVERS is not set, the function
asks Install NVIDIA driver libraries in the container? [Y/n] and assumes yes after
60 seconds. With no, the NVIDIA step is skipped and only permissions are set.
Intel
All Intel paths install VA-API drivers and tools through apt. Packages that a release does not carry are skipped with a warning naming them.
| Path | Ubuntu | Debian |
|---|---|---|
| Arc | intel-media-va-driver-non-free, intel-opencl-icd, libmfx-gen1.2, mesa-vulkan-drivers, vulkan-tools, vainfo, intel-gpu-tools, libvpl2 | Adds the non-free components, then the same VA-API, Vulkan and oneVPL packages. OpenCL: see below |
| Gen 9+ | va-driver-all, intel-media-va-driver, ocl-icd-libopencl1, mesa-vulkan-drivers, vulkan-tools, vainfo, intel-gpu-tools, plus intel-media-va-driver-non-free, intel-opencl-icd, libmfx-gen1.2, libvpl2 if available | Adds the non-free components, then intel-media-va-driver-non-free and the same set. OpenCL: see below |
| Legacy | va-driver-all, i965-va-driver, mesa-va-drivers, ocl-icd-libopencl1, vainfo, intel-gpu-tools, and beignet-opencl-icd where the release still has it | Same |
Intel OpenCL on Debian depends on the release:
- bookworm:
intel-opencl-icdandlibigdgmm12from the Debian repository. - trixie and sid: the packages are downloaded from the GitHub releases of
intel/compute-runtimeandintel/intel-graphics-compiler. The compiler version is read from the compute-runtime release notes, because compute-runtime only works with the exact version it was built against. If that version cannot be read, OpenCL is skipped; VA-API still works. These packages are amd64 only.
AMD
| Path | What is installed |
|---|---|
| Discrete GPU | mesa-va-drivers, mesa-vdpau-drivers, mesa-opencl-icd, ocl-icd-libopencl1, libdrm-amdgpu1, vainfo, clinfo; on Debian also firmware-amd-graphics from non-free-firmware; then ROCm |
| APU | mesa-va-drivers, mesa-vdpau-drivers, mesa-opencl-icd, ocl-icd-libopencl1, vainfo; on Debian also firmware-amd-graphics. No ROCm |
ROCm 7.2 is installed for discrete GPUs on amd64 only, from repo.radeon.com:
rocm-opencl-runtime, rocm-hip-runtime and rocm-smi-lib. Debian bookworm and
Ubuntu jammy use the jammy repository, Debian trixie/sid and Ubuntu noble the
noble one; other releases skip ROCm with a warning. The function adds /opt/rocm/bin
to PATH through /etc/profile.d/rocm.sh and registers /opt/rocm/lib with the
dynamic linker. Kernel drivers are not installed; they belong to the host.
NVIDIA
The NVIDIA userspace libraries have to match the kernel module loaded on the host.
The function reads the host's driver version from /proc/driver/nvidia/version. If
it is not there, only va-driver-all and vainfo are installed.
Debian. It tries NVIDIA's CUDA repositories in an order that depends on the release and uses the first one that carries the host's driver series:
| Container release | Repositories tried |
|---|---|
| bullseye | debian11, debian12 |
| bookworm | debian12 |
| trixie, sid | debian13, debian12 |
If none does, Debian's own non-free packages are checked last. The exact host version
is preferred; otherwise the newest package of the same series is taken and a warning
states both versions. The function then installs libcuda1, libnvcuvid1,
libnvidia-encode1 and libnvidia-ml1 at that version and writes apt pins to
/etc/apt/preferences.d/nvidia-cuda-pin. If no repository has the host's series,
nothing is installed and the function says so, rather than installing a newer
userspace that cannot work.
Ubuntu. It adds the CUDA repository for 22.04 or 24.04 and installs the same four
libraries in the host's driver series. If that fails it tries Ubuntu's own
libnvidia-decode-<series>, libnvidia-encode-<series> and
nvidia-utils-<series>.
Result check. At the end the function reports one of:
| Message | Meaning |
|---|---|
NVIDIA GPU configured (host driver ..., N device nodes) | Device nodes exist, libcuda.so.1 is loadable and its series matches the host |
NVIDIA GPU not usable: no /dev/nvidia* device nodes in this container | The devices were never passed through; check the container config on the host |
NVIDIA GPU not usable: libcuda.so.1 is not available in this container | No matching userspace library could be installed |
NVIDIA GPU not usable: libcuda ... is not the host driver's ... series | A library is present but from another driver series |
A missing nvidia-smi is reported separately and does not affect CUDA: the CUDA
repository ships that package as an empty transitional one.
Permissions
After the drivers, for every vendor:
- In a privileged container,
/dev/dri/*is set to mode660, the current user is added tovideoandrender, andvainfois run as a check. /dev/nvidia*and/dev/nvidia-caps/*get groupvideoand mode660./dev/kfdgets grouprenderand mode660.- The
service_userargument, if given, is added torenderandvideo.
Other platforms
On Incus, incus/tools.func defines its own, smaller setup_hwaccel. The devices
are attached when the instance is created; inside it the function only checks that
they are visible and sets HWACCEL_VENDOR. Install scripts call it the same way.
Alpine containers load lib/alpine.func, which has no setup_hwaccel.
Internal functions
These are called by setup_hwaccel. Do not call them from a script.
| Function | What it does |
|---|---|
_cs_apt_install_optional | Installs a package list in one apt run; on failure retries one by one and names the packages that are missing |
_setup_intel_arc | Intel Arc driver path |
_setup_intel_modern | Intel Gen 9+ driver path |
_setup_intel_legacy | Intel Gen 6 to 8 driver path |
_resolve_igc_tag | Reads the Intel Graphics Compiler version that the fetched compute-runtime release pins |
_setup_amd_gpu | AMD discrete GPU path, calls _setup_rocm |
_setup_amd_apu | AMD APU path |
_setup_rocm | Adds the ROCm repository and installs the runtime packages |
_setup_nvidia_gpu | NVIDIA path, including the final usability check |
_cuda_pick_version | Picks the libcuda1 version: the host's exact release, else the same series |
_nvidia_version_pin | Prints the apt pin stanza for one NVIDIA version |
_nvidia_apt_pins | Prints the complete pin file |
_add_debian_nonfree | Adds a non-free source for bullseye, bookworm or trixie |
_add_debian_nonfree_firmware | Adds a non-free-firmware source for the same releases |
_setup_gpu_permissions | Device modes, groups and the vainfo / nvidia-smi checks |