Troubleshooting

How to Configure GPU Passthrough on Windows Server 2025 for a Linux VM

17 min read

The Back Room Tech is reader-supported. We may earn a commission when you buy through links on our site. Learn more.

GPU passthrough on Hyper-V has several failure points, and most sit below the operating system. A supported GPU won’t help if the PCIe slot lacks isolation or the firmware hides interrupt remapping.

This setup gives an Ubuntu 24.04 LTS VM full control of one GPU through Discrete Device Assignment (DDA). You’ll check the hardware, detach the GPU from Windows Server 2025, assign it to the VM, install the Linux driver, and test it with PyTorch.

What Is Hyper-V Discrete Device Assignment?

<strong>Discrete Device Assignment</strong> passes a whole PCI Express device to one Hyper-V VM. The guest loads the vendor’s Linux driver and talks to the GPU directly.

That access comes with a strict ownership rule. The Windows host and other VMs can’t use the GPU. To return it, shut down the guest, remove the assignment, and mount the device on the host again.

DDA and GPU Partitioning (GPU-P) solve different problems. GPU-P splits supported GPUs among VMs and can support live migration on suitable Windows Server 2025 clusters. DDA binds one physical device to one host and one VM. Changing that assignment requires a guest shutdown.

Don’t use Add-VMGpuPartitionAdapter during this procedure. That cmdlet belongs to GPU-P.

Microsoft documents both models in its Windows Server GPU acceleration planning guide.

Microsoft Learn page showing the distinction between whole-device DDA and GPU Partitioning

Prerequisites

Make sure you have:

  • A physical server running Windows Server 2025 Standard or Datacenter
  • A CPU and motherboard with Intel VT-x and VT-d, or AMD-V and AMD-Vi/IOMMU
  • Native PCI Express control, interrupt remapping, and suitable device isolation
  • SR-IOV enabled where required by the server platform
  • A GPU that both the server vendor and GPU vendor support for DDA
  • A second display adapter, remote PowerShell access, or out-of-band management such as iDRAC, iLO, or IPMI
  • Administrator access on Windows Server
  • Root or sudo access inside the Linux guest
  • A maintenance window for firmware changes and host restarts
  • A current backup of the VM and important host configuration

The working example uses Windows Server 2025 and an Ubuntu 24.04 LTS Generation 2 VM. Commands for RHEL, Rocky Linux, SUSE, and other distributions differ. Driver installation is the main change.

A rack server with a tested data-center GPU, enough power, and a UPS is the safer choice. Consumer hardware can work, but weak firmware and poor PCIe isolation often end the experiment.

Check the exact server model, PCIe slot, firmware release, GPU model, and guest driver with both vendors. A supported GPU in an unsupported slot is still an unsupported setup.

Step-by-Step Guide

Step 1: Confirm the Server and GPU Support DDA

Check the server manual for DDA, PCIe passthrough, ACS, IOMMU, and interrupt-remapping support. Read the notes for each slot. Two slots may look the same but use different PCIe paths. Only one may provide enough isolation.

Check the GPU vendor’s virtualization documents too. Device Manager can identify a GPU, but that doesn’t prove its firmware and driver support passthrough.

Microsoft provides a Machine Profile Script in its Discrete Device Assignment tools repository. Download SurveyDDA.ps1 to C:\Admin, open PowerShell as Administrator, and run:

Set-Location -Path 'C:\Admin'
& '.\SurveyDDA.ps1'

The report should list assignable PCIe devices and flag firmware, ACS, or interrupt-remapping faults. Stop if it marks the GPU as unassignable. The -Force flag used later can bypass a missing host partitioning driver. It can’t repair poor PCIe isolation.

This check saves time. Driver work inside Ubuntu won’t fix a device that Hyper-V can’t isolate on the host.

Step 2: Enable Virtualization and IOMMU in BIOS or UEFI

Restart the server and enter its BIOS or UEFI setup. Menu names vary across Dell, HPE, Lenovo, Supermicro, and motherboard vendors. Sometimes the manual is quicker than guessing.

Enable the available equivalents of:

  • CPU virtualization: Intel Virtualization Technology or AMD-V
  • DMA remapping: Intel VT-d or AMD-Vi/IOMMU
  • SR-IOV
  • Native PCIe control or ACS support, if exposed
  • Above 4G Decoding, if required by the GPU or server vendor

Save the changes and perform a cold boot. Some systems don’t apply PCIe isolation changes after a warm restart. Power down the server fully if the survey results don’t change.

Server BIOS or UEFI virtualization page showing enabled VT-d or AMD-Vi and SR-IOV controls, with a note that menu names vary by vendor

After Windows starts, check that Hyper-V detects the required hardware:

systeminfo.exe

Expected result:

Hyper-V Requirements: A hypervisor has been detected. Features required for Hyper-V will not be displayed.

That message is normal after the hypervisor starts. Before you install Hyper-V, systeminfo.exe should show Yes for virtualization firmware, Second Level Address Translation, VM Monitor Mode Extensions, and Data Execution Prevention.

Step 3: Install and Verify Hyper-V

Open Server Manager and select Manage > Add Roles and Features. Choose Role-based or feature-based installation, select the local server, and enable Hyper-V. Include the management tools, then restart when prompted.

The matching elevated PowerShell command is:

Install-WindowsFeature -Name Hyper-V -IncludeManagementTools -Restart

The -IncludeManagementTools flag installs the Hyper-V console and PowerShell cmdlets. Without it, the hypervisor can run while the management commands remain unavailable.

The server restarts on its own. After you sign back in, check the role:

Get-WindowsFeature -Name Hyper-V

Expected output includes:

Display Name Name Install State
———— —- ————-
[X] Hyper-V Hyper-V Installed

Windows Server 2025 Server Manager showing Hyper-V installed in the server roles list

Step 4: Create and Install the Linux VM

First, list the host’s virtual switches:

Get-VMSwitch

If no external switch exists, open Hyper-V Manager > Virtual Switch Manager > New virtual network switch > External. Select the physical network adapter. Leave Allow management operating system to share this network adapter enabled.

Warning: Creating or changing an external virtual switch can briefly interrupt host networking. Use out-of-band management when working remotely.

That warning earns its place. Rebuilding the network switch through the same remote session can leave the server working while locking you out.

Create the VM from an elevated PowerShell session. Change the storage path and switch name to match your server:

$vmName = 'Ubuntu-AI'
$vmPath = 'D:\Hyper-V\Ubuntu-AI'
$switchName = 'External'
$isoPath = 'D:\ISO\ubuntu-24.04-live-server-amd64.iso'

New-VM `
    -Name $vmName `
    -Generation 2 `
    -MemoryStartupBytes 32GB `
    -NewVHDPath "$vmPath\Ubuntu-AI.vhdx" `
    -NewVHDSizeBytes 150GB `
    -Path $vmPath `
    -SwitchName $switchName

Set-VMProcessor -VMName $vmName -Count 8
Set-VMMemory -VMName $vmName -DynamicMemoryEnabled $false
Set-VMFirmware `
    -VMName $vmName `
    -EnableSecureBoot On `
    -SecureBootTemplate 'MicrosoftUEFICertificateAuthority'

Add-VMDvdDrive -VMName $vmName -Path $isoPath
$dvdDrive = Get-VMDvdDrive -VMName $vmName
Set-VMFirmware -VMName $vmName -FirstBootDevice $dvdDrive
Start-VM -Name $vmName

This creates a Generation 2 VM with 8 virtual CPUs, 32 GB of fixed memory, and a 150 GB VHDX. Fixed memory gives DDA devices stable memory mappings. The Microsoft UEFI Certificate Authority template supports Ubuntu’s signed boot chain with Secure Boot.

Open Hyper-V Manager, right-click Ubuntu-AI, and select Connect. Complete the Ubuntu installer and create an administrator account. Install OpenSSH when offered, then apply the available updates:

sudo apt update
sudo apt full-upgrade -y
sudo reboot

Installing SSH now gives you a cleaner way to manage the guest during GPU driver work. Hyper-V’s console remains useful when networking or Secure Boot has other ideas.

Step 5: Identify the Exact GPU LocationPath

On the host, open Device Manager > Display adapters. Right-click the target GPU and select Properties. Open Details, select Location paths, and record the first value that starts with PCIROOT.

Selected GPU properties showing its exact PCI Express Location paths value, with unrelated asset information hidden

PowerShell gives you a repeatable lookup and lowers the risk of copying the wrong property. First, list the display devices:

Get-PnpDevice -Class Display |
    Format-Table -AutoSize Status, FriendlyName, InstanceId

Expected output resembles:

Status FriendlyName InstanceId
—— ———— ———-
OK NVIDIA RTX 6000 Ada PCI\VEN_10DE&DEV_26B1…
OK Microsoft Basic Display… PCI\VEN_1234&DEV_1111…

Copy the target GPU’s full InstanceId, then get its location path:

$gpu = Get-PnpDevice -InstanceId 'PASTE-THE-EXACT-GPU-INSTANCE-ID-HERE'

$locationPath = (
    Get-PnpDeviceProperty `
        -InstanceId $gpu.InstanceId `
        -KeyName 'DEVPKEY_Device_LocationPaths'
).Data[0]

$locationPath

Expected output resembles:

PCIROOT(20)#PCI(0300)#PCI(0000)

Keep the $gpu and $locationPath variables in the same PowerShell session. Later commands use both.

Some GPUs expose separate display, audio, USB, or management functions. Check the vendor’s DDA instructions before you assign related functions. Don’t infer a second function from a nearby path. PCIe numbering rewards precision and punishes optimism.

Step 6: Shut Down and Prepare the VM

Shut down the guest cleanly from Linux:

sudo poweroff

On the host, confirm that its state is Off:

Get-VM -Name $vmName |
    Select-Object Name, State

Expected output:

Name State
—- —–
Ubuntu-AI Off

Hyper-V Manager showing the Ubuntu-AI Linux VM selected and in the Off state

Set the options commonly required by GPU DDA:

Set-VM -Name $vmName -AutomaticStopAction TurnOff
Set-VM -Name $vmName -GuestControlledCacheTypes $true

TurnOff stops Hyper-V from saving a VM state that includes direct hardware ownership. Guest-controlled cache types allow the memory behavior that many accelerators need.

Large GPUs may also need more memory-mapped I/O (MMIO) space. Use the values from the GPU vendor or the Machine Profile Script. Forum values may fit somebody else’s card and do nothing useful for yours.

# Example only: replace both values with the requirements for your GPU.
$lowMmio = 3GB
$highMmio = 32GB

Set-VM `
    -Name $vmName `
    -LowMemoryMappedIoSpace $lowMmio `
    -HighMemoryMappedIoSpace $highMmio

A GPU with a large Base Address Register may need more than 32 GB of high MMIO space. Too little can stop the VM during startup. Extra space consumes address space without making the GPU faster.

Step 7: Disable and Dismount the GPU from Windows

Warning: The host immediately loses access to this GPU. Confirm remote or out-of-band access before continuing, especially if it drives the local console.

If the target GPU drives the only local display, expect the screen to go dark. Check your remote PowerShell or BMC session before you run the next command.

Disable the exact Plug and Play device:

Disable-PnpDevice -InstanceId $gpu.InstanceId -Confirm:$false

Expected result: the command returns to the prompt without output, and Device Manager shows the device as disabled. The -Confirm:$false flag skips the prompt. This makes the command suitable for a prepared maintenance script.

Dismount it from the host:

Dismount-VMHostAssignableDevice `
    -LocationPath $locationPath `
    -Force

-Force allows the dismount when the vendor doesn’t provide a host partitioning driver. It doesn’t make unsupported hardware safe. Use it only after you check vendor support and test your recovery path.

A successful command usually produces no output. PowerShell’s silence is useful here, but verification is better:

Get-VMHostAssignableDevice -LocationPath $locationPath |
    Format-List LocationPath, InstanceId

The returned location must match the value from Step 5.

Elevated PowerShell showing a successful Dismount-VMHostAssignableDevice command and the GPU LocationPath

Step 8: Assign the GPU to the Linux VM

Attach the dismounted device with the same LocationPath:

Add-VMAssignableDevice `
    -LocationPath $locationPath `
    -VMName $vmName

A successful assignment returns no output. Check it before you start the guest:

Get-VMAssignableDevice -VMName $vmName |
    Format-List VMName, LocationPath, InstanceId

The displayed LocationPath must match the one from Step 5. If it doesn’t, stop and remove the assignment before you do anything else.

Elevated PowerShell showing successful Add-VMAssignableDevice assignment to Ubuntu-AI using the same LocationPath

Start the VM:

Start-VM -Name $vmName
Get-VM -Name $vmName |
    Select-Object Name, State

Expected output:

Name State
—- —–
Ubuntu-AI Running

A Running state proves that Hyper-V accepted the VM settings. You still need to check whether Linux can find the GPU and load its driver.

Step 9: Confirm Linux Can See the PCIe Device

Sign in to Ubuntu and install the PCI tools:

sudo apt update
sudo apt install -y pciutils
lspci -nn

For an NVIDIA GPU, narrow the output without hiding PCI errors:

lspci -nn -d 10de:

Expected output resembles:

3b:00.0 VGA compatible controller [0300]: NVIDIA Corporation Device [10de:26b1]

The 10de value is NVIDIA’s PCI vendor ID. AMD commonly uses 1002, while Intel uses 8086:

lspci -nn -d 1002:
lspci -nn -d 8086:

If the device is absent from lspci, return to the host settings. More guest drivers won’t help when Linux has no PCIe device to bind.

Step 10: Install the Vendor-Supported Linux Driver

For NVIDIA on Ubuntu 24.04, check the driver branches that Ubuntu recommends:

sudo apt install -y ubuntu-drivers-common
ubuntu-drivers devices

Install the recommended branch automatically:

sudo ubuntu-drivers install
sudo reboot

Don’t choose a driver only because its version number is higher. The branch must support the GPU, Ubuntu kernel, and CUDA runtime that your AI framework needs. Those three needs don’t always point to the newest package.

After the reboot, run:

nvidia-smi

Expected output should show the GPU model, driver version, temperature, and memory totals:

+———————————————————————————-+
| NVIDIA-SMI … Driver Version: … CUDA Version: … |
| GPU Name Memory-Usage GPU-Util |
| 0 NVIDIA … 0MiB / …MiB 0% |
+———————————————————————————-+

The CUDA version shown by nvidia-smi is the highest runtime level that the installed driver supports. It doesn’t prove that you’ve installed a CUDA toolkit or GPU-enabled Python package.

Linux terminal showing nvidia-smi with the passed-through GPU model, driver version, and memory total visible

For AMD or Intel hardware, install the vendor-supported compute stack and use its documented tool. Package names and supported kernels change. Use the vendor’s compatibility matrix instead of translating the NVIDIA commands line by line.

Step 11: Test an Actual AI Workload

PCIe detection and nvidia-smi prove that the operating system and driver can see the card. Your framework can still install a CPU-only build or choose the wrong device.

Create a Python virtual environment:

sudo apt install -y python3-venv
python3 -m venv "$HOME/gpu-test"
source "$HOME/gpu-test/bin/activate"
python3 -m pip install --upgrade pip
python3 -m pip install torch

The virtual environment keeps this test separate from Ubuntu’s system Python packages. Cleanup is easier, and you won’t break tools that need the distribution’s packaged dependencies.

Check that the installed build exposes CUDA:

python3 -c "import torch; print('CUDA available:', torch.cuda.is_available()); print('Device:', torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'CPU only')"

Expected output:

CUDA available: True
Device: NVIDIA RTX 6000 Ada Generation

Run a matrix multiplication large enough to create visible load:

python3 -c "import torch; d=torch.device('cuda'); a=torch.randn((8192,8192),device=d); b=torch.randn((8192,8192),device=d); c=a@b; torch.cuda.synchronize(); print(c.device, c.shape)"

Expected output:

cuda:0 torch.Size([8192, 8192])

Two 8192-by-8192 float32 input matrices use about 512 MiB together, before the output and framework overhead. That’s enough to exercise the GPU, but it isn’t a useful benchmark.

In a second terminal, watch the GPU while you repeat the test:

watch -n 1 nvidia-smi

You should see allocated GPU memory and use above 0%. A fast card may finish between the one-second samples, so repeat the test if the first screen stays idle.

Linux terminal showing nonzero GPU utilization and memory use in nvidia-smi while the PyTorch matrix test runs

Configuration

SettingRecommended patternWhy it matters
VM power stateFully shut down for assignment changesA running, paused, or saved VM cannot safely move a DDA device
Dynamic MemoryDisabledFixed memory avoids unsupported or unpredictable device-memory mappings
Guest-controlled cacheEnable with Set-VM -GuestControlledCacheTypes $truePermits cache behavior required by many accelerators
Low and high MMIOUse vendor or survey-script valuesIncorrect values can stop the VM from starting
GPU ownershipOne VM exclusivelyWindows cannot use the device while it is assigned
Host managementSeparate GPU, remote session, or BMCPrevents loss of administrative access
VM migrationPlanned shutdown and reassignmentDDA does not use the GPU-P live-migration workflow

Keep the instance ID and location path in your build notes. Hardware or firmware changes can alter PCIe enumeration. Check both values before you reuse an old script.

To return the GPU to Windows, shut down the VM and run:

Stop-VM -Name $vmName

Remove-VMAssignableDevice `
    -LocationPath $locationPath `
    -VMName $vmName

Mount-VMHostAssignableDevice `
    -LocationPath $locationPath

Enable-PnpDevice `
    -InstanceId $gpu.InstanceId `
    -Confirm:$false

Stop-VM performs a normal guest shutdown when integration services respond. Confirm that the VM reaches Off before you remove the device.

After you mount and enable the GPU, check Device Manager and the vendor tool. Both should see it again. If $gpu is gone because you opened a new PowerShell session, find the device by its exact instance ID before you run Enable-PnpDevice.

Tips and Troubleshooting

The GPU Does Not Appear as Assignable

Cause: VT-d or AMD-Vi is disabled, interrupt remapping is unavailable, the PCIe slot lacks isolation, or the platform doesn’t support DDA.

Fix: Recheck the BIOS or UEFI settings, update the server firmware, and cold-boot the host. Then rerun SurveyDDA.ps1. Moving the GPU to another physical slot can change its PCIe path.

Don’t force the assignment when the survey says the platform can’t isolate the device. You’ll likely get a failed VM start or an unsupported DMA boundary. Persistence won’t improve either result.

“The Device Cannot Be Found” During Dismount

Cause: The LocationPath is incomplete, stale, or belongs to another GPU function.

Fix: Retrieve it again from DEVPKEY_Device_LocationPaths:

Get-PnpDeviceProperty `
    -InstanceId $gpu.InstanceId `
    -KeyName 'DEVPKEY_Device_LocationPaths'

Use the exact first location-path value for dismount, assignment, removal, and remount operations. Don’t shorten it, normalize it, or replace it with the Plug and Play instance ID.

Add-VMAssignableDevice Fails

Cause: The VM is running, the device wasn’t dismounted, the path is wrong, or another VM owns it.

Fix: Check all three states:

Get-VM -Name $vmName
Get-VMHostAssignableDevice -LocationPath $locationPath
Get-VMAssignableDevice -VMName $vmName

The VM must report Off. The GPU must appear as host-assignable before you attach it. If another VM lists the device, remove that assignment first. GPU-P cmdlets won’t fix a DDA ownership conflict.

The VM Will Not Start After Assignment

Cause: The GPU needs more MMIO space, a related PCIe function is missing, or the firmware layout is unsupported.

Fix: Check the Hyper-V-Worker logs under Event Viewer > Applications and Services Logs > Microsoft > Windows > Hyper-V-Worker > Admin. The event details usually say more than the short Hyper-V Manager error.

Apply the MMIO values for the exact GPU. Also check whether the vendor requires related audio, USB, or management functions. Guessing upward in 32 GB steps is a poor replacement for the device documentation.

Linux Sees the GPU but nvidia-smi Is Missing

Cause: The PCIe device is present, but the NVIDIA kernel driver and user-space tools aren’t installed.

Fix: Run ubuntu-drivers devices, install its recommended branch, and reboot. Then run nvidia-smi again.

If Secure Boot blocks the kernel module, follow Ubuntu’s Machine Owner Key enrollment process. You can also use the GPU vendor’s supported Secure Boot procedure. Disabling Secure Boot may work, but you lose boot-chain checks to avoid one enrollment step.

nvidia-smi Works but the AI Application Uses the CPU

Cause: The framework may have a CPU-only build, an incompatible runtime, or an explicit CPU setting.

Fix: Run the framework’s own availability check. For PyTorch, check torch.cuda.is_available() and inspect torch.version.cuda.

Install a compatible GPU build from the framework’s official package selector. Then watch nvidia-smi during a real workload. An idle GPU with a healthy driver usually points back to the application environment.

The Windows Host Lost Access to the GPU

This is expected. DDA gives the whole PCIe device to the VM, so Windows can’t share it. Use another display adapter, Remote Desktop, PowerShell remoting, or the server’s management controller.

If the host must keep GPU access, choose GPU-P on supported hardware. DDA gives one VM steady access, but it’s awkward on a single-GPU server that also needs local graphics.

Live Migration Is Unavailable

DDA binds the VM to a physical device on one Hyper-V host. It can’t use Windows Server 2025’s GPU-P live-migration process.

Schedule downtime and shut down the VM. Remove the DDA device, move the VM, and assign a compatible GPU on the destination host. If you need live mobility, use GPU-P or a supported vendor vGPU platform. Both add hardware and driver limits, so test the whole stack first.

Wrapping Up

StepActionApplies To
1Validate firmware, topology, and GPU supportPhysical server
2Enable virtualization, IOMMU, and relevant SR-IOV settingsBIOS or UEFI
3Install Hyper-V and create the Linux VMWindows Server 2025
4Dismount and assign the GPUElevated PowerShell
5Install the driver and test an AI workloadLinux guest

The Ubuntu VM now owns the whole GPU, and the PyTorch test confirms that work reaches CUDA. Keep the PCIe path, MMIO values, firmware version, and driver branch in the VM’s build notes. You’ll need them when the hardware changes.

DDA works well when one VM needs steady access to one GPU. The costs are blunt: no sharing, no GPU-P live migration, and a shutdown for each ownership change. That’s often fine for a fixed AI worker. Shared or mobile workloads fit GPU-P or a supported vGPU stack better.