El PCI passthrough asigna un dispositivo PCIe físico (una GPU, una tarjeta de red o una controladora) directamente a una máquina virtual. La VM usa el dispositivo con su driver nativo y con un rendimiento casi igual al del hardware real, algo imprescindible para cargas de IA con GPU. En este tutorial activarás IOMMU en un host Ubuntu 24.04, reservarás el dispositivo para el driver vfio-pci, crearás una máquina virtual KVM con libvirt que lo use y comprobarás que el sistema invitado lo detecta.
Requisitos previos
- Un servidor físico con Ubuntu 24.04 LTS, por ejemplo un servidor dedicado (bare metal) de CubePath. Un VPS no sirve: no expone las extensiones de virtualización ni el IOMMU del hardware.
- CPU con virtualización e IOMMU: Intel VT-x y VT-d, o AMD-V y AMD-Vi.
- VT-d o AMD-Vi (a veces llamado "IOMMU") activado en la BIOS/UEFI del servidor, además de la virtualización.
- El dispositivo que vas a pasar a la VM, y otro distinto (o la gráfica integrada/BMC) para la consola del host.
- Un usuario no root con privilegios
sudoy acceso a la consola remota del servidor (IPMI/KVM) por si un cambio de arranque falla.
En los ejemplos se usa una GPU NVIDIA en la dirección PCI 01:00.0 con su controlador de audio en 01:00.1. Sustituye las direcciones e identificadores por los de tu hardware.
Paso 1: Comprobar la virtualización y activar IOMMU
Comprueba que la CPU expone las extensiones de virtualización. El resultado debe ser mayor que 0:
grep -cE 'vmx|svm' /proc/cpuinfo
64
En CPUs Intel hay que activar el IOMMU con un parámetro del kernel. En AMD el kernel de Ubuntu 24.04 ya lo activa si está habilitado en la BIOS. En ambos casos conviene añadir iommu=pt, que deja en modo de paso directo a los dispositivos que el host sigue usando y evita perder rendimiento en ellos.
Edita la configuración de GRUB:
sudo nano /etc/default/grub
Añade los parámetros a la línea GRUB_CMDLINE_LINUX_DEFAULT, conservando los que ya tenga. En Intel:
GRUB_CMDLINE_LINUX_DEFAULT="intel_iommu=on iommu=pt"
En AMD:
GRUB_CMDLINE_LINUX_DEFAULT="iommu=pt"
Regenera la configuración de GRUB y reinicia:
sudo update-grub
sudo systemctl reboot
Tras el reinicio, confirma que el IOMMU está activo:
sudo dmesg | grep -iE 'DMAR: IOMMU enabled|AMD-Vi: .*IOMMU|iommu: Default domain'
[ 0.412345] DMAR: IOMMU enabled
[ 1.034567] iommu: Default domain type: Passthrough (set via kernel command line)
Si el directorio /sys/kernel/iommu_groups/ está vacío, el IOMMU no está activo: revisa que VT-d o AMD-Vi esté habilitado en la BIOS.
Paso 2: Identificar el dispositivo y su grupo IOMMU
El IOMMU agrupa los dispositivos que no puede aislar entre sí. Solo puedes pasar a una VM un grupo completo, así que el dispositivo debería estar solo en su grupo o acompañado únicamente de funciones propias (como el audio HDMI de una GPU).
Lista los grupos con sus dispositivos:
for d in /sys/kernel/iommu_groups/*/devices/*; do g=${d#/sys/kernel/iommu_groups/}; echo "Grupo ${g%%/*}: $(lspci -nns "${d##*/}")"; done | sort -V
Grupo 14: 0000:00:1f.0 ISA bridge [0601]: Intel Corporation C621 Series Chipset LPC/eSPI Controller [8086:a1c1]
Grupo 27: 0000:01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD102 [GeForce RTX 4090] [10de:2684] (rev a1)
Grupo 27: 0000:01:00.1 Audio device [0403]: NVIDIA Corporation AD102 High Definition Audio Controller [10de:22ba] (rev a1)
En este ejemplo el grupo 27 contiene solo la GPU y su audio, lo ideal. Anota dos datos:
- Las direcciones PCI:
01:00.0y01:00.1. - Los identificadores
fabricante:dispositivoentre corchetes al final:10de:2684y10de:22ba.
Si el grupo incluye otros dispositivos del sistema (un puerto raíz PCIe compartido, una controladora SATA), prueba a mover la tarjeta a otra ranura, que suele colgar de otro puerto raíz.
Paso 3: Reservar el dispositivo para vfio-pci
Si el host carga el driver normal de la GPU (nouveau o nvidia), la tarjeta queda ocupada. Configura el módulo vfio-pci para que reclame los dispositivos por su identificador y para que se cargue antes que esos drivers.
Crea el archivo de opciones del módulo:
sudo nano /etc/modprobe.d/vfio.conf
Añade tus identificadores separados por comas:
options vfio-pci ids=10de:2684,10de:22ba
softdep nouveau pre: vfio-pci
softdep nvidia pre: vfio-pci
Las líneas softdep obligan a cargar vfio-pci antes que los drivers de NVIDIA. Para una GPU AMD usa softdep amdgpu pre: vfio-pci.
Para que vfio-pci reclame el dispositivo durante el arranque temprano, añade los módulos al initramfs:
sudo nano /etc/initramfs-tools/modules
Añade al final:
vfio
vfio_iommu_type1
vfio_pci
Regenera el initramfs y reinicia:
sudo update-initramfs -u
sudo systemctl reboot
Comprueba qué driver está usando ahora cada función del dispositivo:
lspci -nnk -s 01:00
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD102 [GeForce RTX 4090] [10de:2684] (rev a1)
Kernel driver in use: vfio-pci
Kernel modules: nouveau
01:00.1 Audio device [0403]: NVIDIA Corporation AD102 High Definition Audio Controller [10de:22ba] (rev a1)
Kernel driver in use: vfio-pci
Kernel modules: snd_hda_intel
Kernel driver in use: vfio-pci en las dos funciones confirma que el dispositivo está listo para la VM.
Paso 4: Instalar KVM y libvirt
Instala QEMU, libvirt, virt-install y el firmware UEFI OVMF (las GPU modernas funcionan mejor con VMs UEFI):
sudo apt install qemu-system-x86 libvirt-daemon-system libvirt-clients virtinst ovmf
Añade tu usuario al grupo libvirt para gestionar VMs sin sudo y abre una sesión nueva para que surta efecto:
sudo usermod -aG libvirt "$USER"
Comprueba que libvirt responde y que el dispositivo está disponible como nodo PCI:
virsh -c qemu:///system nodedev-list --cap pci | grep 01_00
pci_0000_01_00_0
pci_0000_01_00_1
Estos nombres son los que usarás para asignar el dispositivo.
Paso 5: Crear la máquina virtual con el dispositivo
Descarga la ISO del sistema invitado en el directorio de imágenes de libvirt. En este ejemplo, Ubuntu Server 24.04:
sudo wget -P /var/lib/libvirt/images https://releases.ubuntu.com/24.04/ubuntu-24.04.3-live-server-amd64.iso
Notael nombre exacto de la ISO cambia con cada versión puntual. Consulta el actual en releases.ubuntu.com/24.04.
Crea la VM con arranque UEFI, 16 GB de RAM, 8 vCPU, un disco de 100 GB y las dos funciones de la GPU:
virt-install \
--connect qemu:///system \
--name vm-gpu \
--memory 16384 \
--vcpus 8 \
--cpu host-passthrough \
--os-variant ubuntu24.04 \
--boot uefi \
--disk size=100,format=qcow2 \
--cdrom /var/lib/libvirt/images/ubuntu-24.04.3-live-server-amd64.iso \
--network network=default \
--graphics vnc,listen=127.0.0.1 \
--host-device pci_0000_01_00_0 \
--host-device pci_0000_01_00_1 \
--noautoconsole
--host-device añade cada función PCI a la VM en modo gestionado: libvirt la desvincula del host al arrancar la VM y la devuelve al apagarla. --cpu host-passthrough expone a la VM el modelo de CPU real, lo que mejora el rendimiento.
La instalación se hace por VNC, que solo escucha en localhost. Averigua el puerto:
virsh -c qemu:///system vncdisplay vm-gpu
127.0.0.1:0
La pantalla :0 corresponde al puerto 5900. Desde tu equipo, abre un túnel SSH (sustituye your_user y your_server_ip) y conecta un cliente VNC a localhost:5900:
ssh -L 5900:127.0.0.1:5900 your_user@your_server_ip
Completa la instalación del sistema invitado como en cualquier servidor.
Paso 6: Verificar el dispositivo dentro de la VM
Comprueba desde el host que la VM tiene asignado el dispositivo:
virsh -c qemu:///system dumpxml vm-gpu | grep -A3 '<hostdev'
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</source>
Dentro de la VM, el dispositivo debe aparecer como hardware PCI normal:
lspci -nn | grep -i nvidia
06:00.0 VGA compatible controller [0300]: NVIDIA Corporation AD102 [GeForce RTX 4090] [10de:2684] (rev a1)
07:00.0 Audio device [0403]: NVIDIA Corporation AD102 High Definition Audio Controller [10de:22ba] (rev a1)
Instala el driver recomendado por Ubuntu dentro de la VM y reinicia:
sudo ubuntu-drivers install
sudo systemctl reboot
Tras el reinicio, nvidia-smi debe mostrar la GPU con su memoria y temperatura:
nvidia-smi
Paso 7: Mejorar el rendimiento con huge pages y CPU pinning (opcional)
Para cargas intensivas, dos ajustes reducen la latencia: respaldar la memoria de la VM con páginas de 2 MB (huge pages) y fijar cada vCPU a un núcleo físico concreto.
Reserva huge pages suficientes para la VM. Para 16 GB hacen falta 8192 páginas de 2 MB:
echo "vm.nr_hugepages = 8192" | sudo tee /etc/sysctl.d/80-hugepages.conf
sudo sysctl --system
Comprueba la reserva:
grep HugePages_Total /proc/meminfo
HugePages_Total: 8192
Si el valor es menor que el pedido, la memoria está fragmentada: reinicia el host para reservarla al arrancar.
Consulta la topología de la CPU para elegir núcleos que no uses en el host, preferiblemente del mismo nodo NUMA que la GPU:
lscpu -e=CPU,NODE,CORE
Edita la definición de la VM con la VM apagada:
virsh -c qemu:///system edit vm-gpu
Añade estos bloques dentro de <domain>, a la altura de <memory> y <vcpu>, y ajusta los números de CPU a los de tu máquina:
<memoryBacking>
<hugepages/>
</memoryBacking>
<cputune>
<vcpupin vcpu='0' cpuset='4'/>
<vcpupin vcpu='1' cpuset='5'/>
<vcpupin vcpu='2' cpuset='6'/>
<vcpupin vcpu='3' cpuset='7'/>
<vcpupin vcpu='4' cpuset='8'/>
<vcpupin vcpu='5' cpuset='9'/>
<vcpupin vcpu='6' cpuset='10'/>
<vcpupin vcpu='7' cpuset='11'/>
<emulatorpin cpuset='0-1'/>
</cputune>
Arranca la VM y comprueba la asignación:
virsh -c qemu:///system start vm-gpu
virsh -c qemu:///system vcpupin vm-gpu
Solución de problemas
lspci -k sigue mostrando nouveau o nvidia como driver en uso. El driver del host cargó antes que vfio-pci. Revisa los identificadores de /etc/modprobe.d/vfio.conf, confirma que añadiste los módulos a /etc/initramfs-tools/modules y vuelve a ejecutar sudo update-initramfs -u. Si tienes instalado el driver propietario de NVIDIA en el host y no lo necesitas, desinstálalo.
La VM no arranca con group N is not viable. Otro dispositivo del mismo grupo IOMMU sigue en uso por el host. Todos los dispositivos del grupo deben estar libres o asignados a la VM. Revisa el grupo con el comando del paso 2 y mueve la tarjeta de ranura si comparte grupo con dispositivos que el host necesita.
No hay grupos IOMMU. VT-d o AMD-Vi no está activado en la BIOS, o falta intel_iommu=on en Intel. Comprueba los parámetros del arranque actual con cat /proc/cmdline.
La GPU funciona la primera vez pero no tras reiniciar la VM. Algunas GPU no se reinician correctamente al pasar de una VM a otra (el llamado "reset bug"). Apaga la VM por completo en lugar de reiniciarla y, si persiste, reinicia el host.
Errores de QEMU al arrancar la VM. El detalle está en /var/log/libvirt/qemu/vm-gpu.log y en sudo journalctl -u libvirtd -n 50.
Conclusión
Has activado IOMMU, reservado un dispositivo PCIe para vfio-pci y creado una VM KVM que lo usa con su driver nativo, con ajustes opcionales de memoria y CPU para exprimir el rendimiento. Como siguientes pasos puedes:
- Instalar CUDA o un runtime de contenedores con soporte de GPU dentro de la VM para cargas de IA.
- Usar SR-IOV en tarjetas de red compatibles para repartir una sola NIC entre varias VMs.
- Automatizar la creación de VMs con plantillas de libvirt o con Terraform y el proveedor de libvirt.
