Running VMs with GPU Passthrough

Running VMs with GPU Passthrough

This section demonstrates how to deploy virtual machines (VMs) with GPU passthrough using Cozystack. First, we’ll deploy the GPU Operator to configure the worker node for GPU passthrough Then we will deploy a KubeVirt VM that requests a GPU.

By default, to provision a GPU Passthrough, the GPU Operator will deploy the following components:

  • VFIO Manager to bind vfio-pci driver to all GPUs on the node.
  • Sandbox Device Plugin to discover and advertise the passthrough GPUs to kubelet.
  • Sandbox Validator to validate the other operands.

Prerequisites

  • A Cozystack cluster with at least one GPU-enabled node.
  • kubectl installed and cluster access credentials configured.

1. Install the GPU Operator

Follow these steps:

  1. Label the worker node explicitly for GPU passthrough workloads:

    kubectl label node <node-name> --overwrite nvidia.com/gpu.workload.config=vm-passthrough
    
  2. Enable the GPU Operator in your Platform Package by adding it to the enabled packages list:

    kubectl patch packages.cozystack.io cozystack.cozystack-platform --type=json \
      -p '[{"op": "add", "path": "/spec/components/platform/values/bundles/enabledPackages/-", "value": "gpu-operator"}]'
    

    This will deploy the components (operands).

  3. Ensure all pods are in a running state and all validations succeed with the sandbox-validator component:

    kubectl get pods -n cozy-gpu-operator
    

    Example output (your pod names may vary):

    NAME                                            READY   STATUS    RESTARTS   AGE
    ...
    nvidia-sandbox-device-plugin-daemonset-4mxsc    1/1     Running   0          40s
    nvidia-sandbox-validator-vxj7t                  1/1     Running   0          40s
    nvidia-vfio-manager-thfwf                       1/1     Running   0          78s
    

To verify the GPU binding, access the node using kubectl node-shell -n cozy-system -x or kubectl debug node and run:

lspci -nnk -d 10de:

The vfio-manager pod will bind all GPUs on the node to the vfio-pci driver. Example output:

3b:00.0 3D controller [0302]: NVIDIA Corporation Device [10de:2236] (rev a1)
       Subsystem: NVIDIA Corporation Device [10de:1482]
       Kernel driver in use: vfio-pci
86:00.0 3D controller [0302]: NVIDIA Corporation Device [10de:2236] (rev a1)
       Subsystem: NVIDIA Corporation Device [10de:1482]
       Kernel driver in use: vfio-pci

The sandbox-device-plugin will discover and advertise these resources to kubelet. In this example, the node shows two A10 GPUs as available resources:

kubectl describe node <node-name>

Example output:

...
Capacity:
  ...
  nvidia.com/GA102GL_A10:         2
  ...
Allocatable:
  ...
  nvidia.com/GA102GL_A10:         2
...

2. Update the KubeVirt Custom Resource

Next, we will update the KubeVirt Custom Resource, as documented in the KubeVirt user guide, so that the passthrough GPUs are permitted and can be requested by a KubeVirt VM.

Adjust the pciVendorSelector and resourceName values to match your specific GPU model. Setting externalResourceProvider=true indicates that this resource is provided by an external device plugin, in this case the sandbox-device-plugin which is deployed by the Operator.

kubectl edit kubevirt -n cozy-kubevirt

example config:

  ...
  spec:
    configuration:
      permittedHostDevices:
        pciHostDevices:
        - externalResourceProvider: true
          pciVendorSelector: 10DE:2236
          resourceName: nvidia.com/GA102GL_A10
  ...

3. Create a Virtual Machine

We are now ready to create a VM.

  1. Create a sample virtual machine using the following VMI specification that requests the nvidia.com/GA102GL_A10 resource.

    vmi-gpu.yaml:

    ---
    apiVersion: apps.cozystack.io/v1alpha1
    appVersion: '*'
    kind: VirtualMachine
    metadata:
      name: gpu
      namespace: tenant-example
    spec:
      running: true
      instanceProfile: ubuntu
      instanceType: u1.medium
      systemDisk:
        image: ubuntu
        storage: 5Gi
        storageClass: replicated
      gpus:
      - name: nvidia.com/GA102GL_A10
      cloudInit: |
        #cloud-config
        password: ubuntu
        chpasswd: { expire: False }
    
    kubectl apply -f vmi-gpu.yaml
    

    Example output:

    virtualmachines.apps.cozystack.io/gpu created
    
  2. Verify the VM status:

    kubectl get vmi
    
    NAME                       AGE   PHASE     IP             NODENAME        READY
    virtual-machine-gpu        73m   Running   10.244.3.191   luc-csxhk-002   True
    
  3. Log in to the VM and confirm that it has access to GPU:

    virtctl console virtual-machine-gpu
    

    Example output:

    Successfully connected to vmi-gpu console. The escape sequence is ^]
    
    vmi-gpu login: ubuntu
    Password:
    
    ubuntu@virtual-machine-gpu:~$ lspci -nnk -d 10de:
    08:00.0 3D controller [0302]: NVIDIA Corporation GA102GL [A10] [10de:26b9] (rev a1)
            Subsystem: NVIDIA Corporation GA102GL [A10] [10de:1851]
            Kernel driver in use: nvidia
            Kernel modules: nvidiafb, nvidia_drm, nvidia
    

GPU Sharing for Virtual Machines (vGPU)

GPU passthrough assigns an entire physical GPU to a single VM. To share one GPU between multiple VMs, you can use NVIDIA vGPU, which creates virtual GPUs from a single physical GPU using mediated devices (mdev).

Prerequisites

  • A GPU that supports vGPU (e.g., NVIDIA L40S, A100, A30, A16)
  • An NVIDIA vGPU Software license (NVIDIA AI Enterprise or vGPU subscription)
  • Access to the NVIDIA Licensing Portal to download the vGPU Manager driver

1. Build the vGPU Manager Image

The GPU Operator expects a pre-built driver container image — it does not install the driver from a raw .run file at runtime.

  1. Download the vGPU Manager driver from the NVIDIA Licensing Portal (Software Downloads → NVIDIA AI Enterprise → Linux KVM)
  2. Build the driver container image using NVIDIA’s Makefile-based build system:
# Clone the NVIDIA driver container repository
git clone https://gitlab.com/nvidia/container-images/driver.git
cd driver

# Place the downloaded .run file in the appropriate directory
cp NVIDIA-Linux-x86_64-550.90.05-vgpu-kvm.run vgpu/

# Build using the provided Makefile
make OS_TAG=ubuntu22.04 \
  VGPU_DRIVER_VERSION=550.90.05 \
  PRIVATE_REGISTRY=registry.example.com/nvidia

# Push to your private registry
docker push registry.example.com/nvidia/vgpu-manager:550.90.05

2. Install the GPU Operator with vGPU Variant

The GPU Operator provides a vgpu variant that enables the vGPU Manager and vGPU Device Manager instead of the VFIO Manager used in passthrough mode.

  1. Label the worker node for vGPU workloads:

    kubectl label node <node-name> --overwrite nvidia.com/gpu.workload.config=vm-vgpu
    
  2. Create the GPU Operator Package with the vgpu variant, providing your vGPU Manager image coordinates:

    apiVersion: cozystack.io/v1alpha1
    kind: Package
    metadata:
      name: cozystack.gpu-operator
    spec:
      variant: vgpu
      components:
        gpu-operator:
          values:
            gpu-operator:
              vgpuManager:
                repository: registry.example.com/nvidia
                version: "550.90.05"
    

    If your registry requires authentication, create an imagePullSecret in the cozy-gpu-operator namespace first, then reference it:

    gpu-operator:
      vgpuManager:
        repository: registry.example.com/nvidia
        version: "550.90.05"
        imagePullSecrets:
        - name: nvidia-registry-secret
    
  3. Verify all pods are running:

    kubectl get pods -n cozy-gpu-operator
    

    Example output:

    NAME                                            READY   STATUS    RESTARTS   AGE
    ...
    nvidia-vgpu-manager-daemonset-xxxxx             1/1     Running   0          60s
    nvidia-vgpu-device-manager-xxxxx                1/1     Running   0          45s
    nvidia-sandbox-validator-xxxxx                  1/1     Running   0          30s
    

3. Configure NVIDIA License Server (NLS)

vGPU requires a license to operate. Create a Secret with the NLS client configuration:

apiVersion: v1
kind: Secret
metadata:
  name: licensing-config
  namespace: cozy-gpu-operator
stringData:
  gridd.conf: |
    ServerAddress=nls.example.com
    ServerPort=443
    FeatureType=1  # 1 for vGPU (vPC/vWS), 2 for Virtual Compute Server (vCS)
    # ServerPort depends on your NLS deployment (commonly 443 for DLS or 7070 for legacy NLS)

Then reference the Secret in the Package values:

gpu-operator:
  vgpuManager:
    repository: registry.example.com/nvidia
    version: "550.90.05"
  driver:
    licensingConfig:
      secretName: licensing-config

4. Update the KubeVirt Custom Resource

Configure KubeVirt to permit mediated devices. The mediatedDeviceTypes field specifies which vGPU profiles to use, and permittedHostDevices makes them available to VMs:

kubectl edit kubevirt -n cozy-kubevirt
spec:
  configuration:
    mediatedDevicesConfiguration:
      mediatedDeviceTypes:
      - nvidia-592    # Example: NVIDIA L40S-24Q
    permittedHostDevices:
      mediatedDevices:
      - mdevNameSelector: NVIDIA L40S-24Q
        resourceName: nvidia.com/NVIDIA_L40S-24Q

To find the correct type ID and profile name for your GPU, consult the NVIDIA vGPU User Guide.

5. Create a Virtual Machine with vGPU

apiVersion: apps.cozystack.io/v1alpha1
appVersion: '*'
kind: VirtualMachine
metadata:
  name: gpu-vgpu
  namespace: tenant-example
spec:
  running: true
  instanceProfile: ubuntu
  instanceType: u1.medium
  systemDisk:
    image: ubuntu
    storage: 5Gi
    storageClass: replicated
  gpus:
  - name: nvidia.com/NVIDIA_L40S-24Q
  cloudInit: |
    #cloud-config
    password: ubuntu
    chpasswd: { expire: False }
kubectl apply -f vmi-vgpu.yaml

Once the VM is running, log in and verify the vGPU is available:

virtctl console virtual-machine-gpu-vgpu
ubuntu@virtual-machine-gpu-vgpu:~$ nvidia-smi
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.90.05              Driver Version: 550.90.05    CUDA Version: 12.4       |
|                                                                                         |
| GPU  Name              ...   MIG M.                                                     |
|  0   NVIDIA L40S-24Q   ...   N/A                                                        |
+-----------------------------------------------------------------------------------------+

vGPU Profiles

Each GPU model supports specific vGPU profiles that determine how the GPU is partitioned. Common profiles for NVIDIA L40S:

ProfileFrame BufferMax InstancesUse Case
NVIDIA L40S-1Q1 GB48Light 3D / VDI
NVIDIA L40S-2Q2 GB24Medium 3D / VDI
NVIDIA L40S-4Q4 GB12Heavy 3D / VDI
NVIDIA L40S-6Q6 GB8Professional 3D
NVIDIA L40S-8Q8 GB6AI/ML inference
NVIDIA L40S-12Q12 GB4AI/ML training
NVIDIA L40S-24Q24 GB2Large AI workloads
NVIDIA L40S-48Q48 GB1Full GPU equivalent

Open-Source vGPU (Experimental)

NVIDIA is developing open-source vGPU support for the Linux kernel. Once merged, this could enable GPU sharing without a commercial license.