Configure GPU Passthrough for Hyper-V VMs
Mount a GPU to a VM
This example uses PowerShell to configure a VM to take the first GPU available by the manufacturer NVIDIA and assign it into the VM.
Verify that the host firmware exposes support for Discrete Device Assignment and that you have an out-of-band management path before detaching the GPU from the host OS.
Prerequisites
- Windows Server or Windows 10/11 with the Hyper-V role installed
- PowerShell running as administrator on the Hyper-V host
- Latest NVIDIA drivers available for both the host and the guest VM
- Target VM turned off prior to passthrough configuration
- Current backup or checkpoint of the VM state
# Configure the VM for a Discrete Device Assignment$vm = Read-Host "Enter the name of the VM"# Set automatic stop action to TurnOffSet-VM -Name $vm -AutomaticStopAction TurnOff# Enable Write-Combining on the CPUSet-VM -GuestControlledCacheTypes $true -VMName $vm# Configure 32 bit MMIO spaceSet-VM -LowMemoryMappedIoSpace 3Gb -VMName $vm# Configure Greater than 32 bit MMIO spaceSet-VM -HighMemoryMappedIoSpace 33280Mb -VMName $vm
# Find the Location Path and disable the Device# Enumerate all PNP Devices on the system$pnpdevs = Get-PnpDevice -presentOnly# Select only those devices that are Display devices manufactured by NVIDIA$gpudevs = $pnpdevs | Where-Object {$_.Class -like "Display" -and $_.Manufacturer -like "NVIDIA"}# Select the location path of the first device that's available to be dismounted by the host.$locationPath = ($gpudevs | Get-PnpDeviceProperty DEVPKEY_Device_LocationPaths).data[0]# Write the location path to a file for later GPU remove.$locationPath | Out-File -FilePath $PSScriptRoot\locationPath.txt
# Disable the PNP DeviceDisable-PnpDevice -InstanceId $gpudevs[0].InstanceId
# Dismount the Device from the HostDismount-VMHostAssignableDevice -Force -LocationPath $locationPath
# Assign the device to the guest VM.Add-VMAssignableDevice -LocationPath $locationPath -VMName $vmBest Practices
- Keep the Hyper-V host graphics drivers updated to avoid MMIO detection failures during dismount.
- Preserve a remote access method (RDP, PowerShell Direct, or serial console) because the host console may no longer render after passthrough.
- Restrict passthrough to workloads that need dedicated graphics acceleration so that the host retains stability for other VMs.
- Store the generated
locationPath.txtwith your VM artifacts so that revert scripts can locate the correct device.
Remove a device and return it to the host
If you want to return the device back to its original state, you must stop the VM and issue this command:
# Configure the VM for a Discrete Device Assignment$vm = Read-Host "Enter the name of the VM"# Set automatic stop action to TurnOffSet-VM -Name $vm -AutomaticStopAction TurnOff# Enable Write-Combining on the CPUSet-VM -GuestControlledCacheTypes $false -VMName $vm# Configure default MMIO spaceSet-VM -LowMemoryMappedIoSpace 128Mb -VMName $vm# Configure default MMIO spaceSet-VM -HighMemoryMappedIoSpace 512Mb -VMName $vm
# Find the Location Path and disable the Device$locationPath = Get-Content -Path $PSScriptRoot\locationPath.txt
# Remove the device from the VMRemove-VMAssignableDevice -LocationPath $locationPath -VMName $vm# Mount the device back in the hostMount-VMHostAssignableDevice -LocationPath $locationPathYou can then re-enable the device in Device Manager, and the host operating system is able to interact with the device again.
MMIO Configuration
For the correct MMIO memory specification you can visit the following page
There is also a script to run that calculates the correct memory requirement
curl -o SurveyDDA.ps1 https://raw.githubusercontent.com/MicrosoftDocs/Virtualization-Documentation/live/hyperv-tools/DiscreteDeviceAssignment/SurveyDDA.ps1.\SurveyDDA.ps1I adapted the script so that it only queries Nvidia GPUs
Re-run the survey whenever you add or remove PCIe devices because MMIO sizing can change when the bus topology is updated.
## These variables are device properties. For people who are very# curious about this, you can download the Windows Driver Kit headers and# look for pciprop.h. All of these are contained in that file.#$devpkey_PciDevice_DeviceType = "{3AB22E31-8264-4b4e-9AF5-A8D2D8E33E62} 1"$devpkey_PciDevice_BaseClass = "{3AB22E31-8264-4b4e-9AF5-A8D2D8E33E62} 3"$devpkey_PciDevice_RequiresReservedMemoryRegion = "{3AB22E31-8264-4b4e-9AF5-A8D2D8E33E62} 34"$devpkey_PciDevice_AcsCompatibleUpHierarchy = "{3AB22E31-8264-4b4e-9AF5-A8D2D8E33E62} 31"
$devprop_PciDevice_DeviceType_PciConventional = 0$devprop_PciDevice_DeviceType_PciX = 1$devprop_PciDevice_DeviceType_PciExpressEndpoint = 2$devprop_PciDevice_DeviceType_PciExpressLegacyEndpoint = 3$devprop_PciDevice_DeviceType_PciExpressRootComplexIntegratedEndpoint= 4$devprop_PciDevice_DeviceType_PciExpressTreatedAsPci = 5$devprop_PciDevice_BridgeType_PciConventional = 6$devprop_PciDevice_BridgeType_PciX = 7$devprop_PciDevice_BridgeType_PciExpressRootPort = 8$devprop_PciDevice_BridgeType_PciExpressUpstreamSwitchPort = 9$devprop_PciDevice_BridgeType_PciExpressDownstreamSwitchPort = 10$devprop_PciDevice_BridgeType_PciExpressToPciXBridge = 11$devprop_PciDevice_BridgeType_PciXToExpressBridge = 12$devprop_PciDevice_BridgeType_PciExpressTreatedAsPci = 13$devprop_PciDevice_BridgeType_PciExpressEventCollector = 14
$devprop_PciDevice_AcsCompatibleUpHierarchy_NotSupported = 0$devprop_PciDevice_AcsCompatibleUpHierarchy_SingleFunctionSupported = 1$devprop_PciDevice_AcsCompatibleUpHierarchy_NoP2PSupported = 2$devprop_PciDevice_AcsCompatibleUpHierarchy_Supported = 3
## These values are defined in the PCI spec, and are also published in wdm.h# of the Windows Driver Kit headers.#$devprop_PciDevice_BaseClass_DisplayCtlr = 3
Write-Host "Executing SurveyDDA.ps1, revision 1"
write-host "Generating a list of PCI Express endpoint devices"$pnpdevs = Get-PnpDevice -PresentOnly$pcidevs = $pnpdevs | Where-Object {$_.Class -like "Display" -and $_.Manufacturer -like "NVIDIA"}
foreach ($pcidev in $pcidevs) { Write-Host "" Write-Host "" Write-Host -ForegroundColor White -BackgroundColor Black $pcidev.FriendlyName
$rmrr = ($pcidev | Get-PnpDeviceProperty $devpkey_PciDevice_RequiresReservedMemoryRegion).Data if ($rmrr -ne 0) { write-host -ForegroundColor Red -BackgroundColor Black "BIOS requires that this device remain attached to BIOS-owned memory. Not assignable." continue }
$acsUp = ($pcidev | Get-PnpDeviceProperty $devpkey_PciDevice_AcsCompatibleUpHierarchy).Data if ($acsUp -eq $devprop_PciDevice_AcsCompatibleUpHierarchy_NotSupported) { write-host -ForegroundColor Red -BackgroundColor Black "Traffic from this device may be redirected to other devices in the system. Not assignable." continue }
$devtype = ($pcidev | Get-PnpDeviceProperty $devpkey_PciDevice_DeviceType).Data if ($devtype -eq $devprop_PciDevice_DeviceType_PciExpressEndpoint) { Write-Host "Express Endpoint -- more secure." } else { if ($devtype -eq $devprop_PciDevice_DeviceType_PciExpressRootComplexIntegratedEndpoint) { Write-Host "Embedded Endpoint -- less secure." } elseif ($devtype -eq $devprop_PciDevice_DeviceType_PciExpressLegacyEndpoint) { $devBaseClass = ($pcidev | Get-PnpDeviceProperty $devpkey_PciDevice_BaseClass).Data
if ($devBaseClass -eq $devprop_PciDevice_BaseClass_DisplayCtlr) { Write-Host "Legacy Express Endpoint -- graphics controller." } else { Write-Host -ForegroundColor Red -BackgroundColor Black "Legacy, non-VGA PCI device. Not assignable." continue } } else { if ($devtype -eq $devprop_PciDevice_DeviceType_PciExpressTreatedAsPci) { Write-Host -ForegroundColor Red -BackgroundColor Black "BIOS kept control of PCI Express for this device. Not assignable." } else { Write-Host -ForegroundColor Red -BackgroundColor Black "Old-style PCI device, switch port, etc. Not assignable." } continue } }
$locationpath = ($pcidev | get-pnpdeviceproperty DEVPKEY_Device_LocationPaths).data[0]
# # If the device is disabled, we can't check the resources, report a warning and continue on. # # if ($pcidev.ConfigManagerErrorCode -eq "CM_PROB_DISABLED") { Write-Host -ForegroundColor Yellow -BackgroundColor Black "Device is Disabled, unable to check resource requirements, it may be assignable." Write-Host -ForegroundColor Yellow -BackgroundColor Black "Enable the device and rerun this script to confirm." $locationpath continue }
# # Now do a check for the interrupts that the device uses. Line-based interrupts # aren't assignable. # $doubleslashDevId = "*" + $pcidev.PNPDeviceID.Replace("\","\\") + "*" $irqAssignments = gwmi -query "select * from Win32_PnPAllocatedResource" | Where-Object {$_.__RELPATH -like "*Win32_IRQResource*"} | Where-Object {$_.Dependent -like $doubleslashDevId}
#$irqAssignments | Format-Table -Property __RELPATH
if ($irqAssignments.length -eq 0) { Write-Host -ForegroundColor Green -BackgroundColor Black " And it has no interrupts at all -- assignment can work." } else { # # Find the message-signaled interrupts. They are reported with a really big number in # decimal, one which always happens to start with "42949...". # $msiAssignments = $irqAssignments | Where-Object {$_.Antecedent -like "*IRQNumber=42949*"}
#$msiAssignments | Format-Table -Property __RELPATH
if ($msiAssignments.length -eq 0) { Write-Host -ForegroundColor Red -BackgroundColor Black "All of the interrupts are line-based, no assignment can work." continue } else { Write-Host -ForegroundColor Green -BackgroundColor Black " And its interrupts are message-based, assignment can work." } }
# # Check how much MMIO space the device needs # not strictly an issue devices, but very useful when you want to set MMIO gap sizes #
$mmioAssignments = gwmi -query "select * from Win32_PnPAllocatedResource" | Where-Object {$_.__RELPATH -like "*Win32_DeviceMemoryAddress*"} | Where-Object {$_.Dependent -like $doubleslashDevId} $mmioTotal = 0 foreach ($mem in $mmioAssignments) { $baseAdd =$mem.Antecedent.SubString($mem.Antecedent.IndexOf("""")+1) $baseAdd=$baseAdd.SubString(0,$baseAdd.IndexOf("""")) $mmioRange = gwmi -query "select * from Win32_DeviceMemoryAddress" | Where-Object{$_.StartingAddress -like $baseAdd} $mmioTotal = $mmioTotal + $mmioRange.EndingAddress - $mmioRange.StartingAddress } if ($mmioTotal -eq 0) { Write-Host -ForegroundColor Green -BackgroundColor Black " And it has no MMIO space" } else { [int]$mmioMB = [math]::ceiling($mmioTotal / 1MB) Write-Host -ForegroundColor Green -BackgroundColor Black " And it requires at least:" $mmioMB "MB of MMIO gap space" }
# # Print out the location path, as that's the way to refer to this device that won't # change even if you add or remove devices from the machine or change the way that # the BIOS is configured. # $locationpath}
## Now look at the host as a whole. Asking whether the host supports SR-IOV# is mostly equivalent to asking whether it supports Discrete Device# Assignment.#if ((Get-VMHost).IovSupport -eq $false) { Write-Host "" write-host "Unfortunately, this machine doesn't support using them in a VM." Write-Host "" (Get-VMHost).IovSupportReasons}Troubleshooting
Dismount-VMHostAssignableDevicefails: Ensure no other virtualization stack (for example, WSLg or third-party hypervisors) claims the GPU and that the device is disabled in Device Manager before retrying.- Guest boots without display output: Connect using Enhanced Session mode or RDP, install the latest NVIDIA driver, and verify that RemoteFX or similar GPU-sharing features are disabled.
- GPU stays detached from the host: Run the removal script while the VM is off, re-enable the device in Device Manager, and allow for a full host reboot if the device reports a surprise removal.