如何在香港服务器的Windows Server 2022 Hyper-V 中配置vGPU直通支持AI训练

凌晨 2:40,我站在香港荃湾机房 7 楼的过道,客户在电话那头催:“早上 8 点要把第一版模型训起来,数据已经就位了。”我对着一台刚上架的 2U 机器,拎着一个 A10 和一把十字螺丝刀,心里盘着两件事:用 Hyper-V 的 DDA(离散设备直通)把整卡给到 Windows 训练虚机,同时留一条“Plan B”:如果要多人共享,就走 NVIDIA vGPU(SR-IOV/授权)。
这篇文章就是那一晚我从 BIOS 到驱动、从直通到训练、一路把坑踩完的完整记录。我会用第一人称把每一步的命令、参数、表格、报错与解决办法交代清楚,希望你照着做,能在自己的香港服务器上顺利把卡“喂”给训练环境。
场景与目标
场景:香港机房、单/多张 NVIDIA 数据中心 GPU(A10/T4/L40S/RTX 6000 Ada 等),宿主系统 Windows Server 2022 Datacenter,虚拟化平台 Hyper-V。
目标:
- 以 DDA(Discreet Device Assignment)直通整卡 到 Windows 客户机,跑 CUDA 训练;
- 可选:在许可允许的前提下,部署 NVIDIA vGPU 以在多虚机之间共享一张卡;
- 过程里记录我真实遇到的坑和可复现的修复步骤。
1. 我用到的硬件与拓扑(可对照替换)
| 项 | 型号/参数 | 备注 |
|---|---|---|
| 机型 | 2U 双路(如 Supermicro/戴尔 R 系列) | 前 8 盘位 NVMe,后置风扇 |
| CPU | Xeon Silver 4310 ×2(或 Gold/Platinum) | 需要 VT-d/IOMMU |
| 主板 | 支持 Above 4G Decoding / SR-IOV | 建议更新到最新 BIOS |
| 内存 | 256–512 GB DDR4/DDR5 ECC | 训练任务常驻大内存 |
| 系统盘 | 2 × NVMe (RAID1) | 宿主机 OS |
| 数据盘 | 4–8 × NVMe (RAID10/单盘直通) | 训练数据/缓存 |
| GPU | NVIDIA A10(或 T4/L40S/RTX6000 Ada 等) | 建议数据中心驱动线 |
| 网卡 | 2 × 25/10 GbE(SR-IOV 能开) | 训练/数据同步 |
| 电源与散热 | 1+1 冗余 / 风墙 | 香港机房温度偏高,风道要正 |
注意:消费级卡可做 DDA,但更容易踩驱动/Code 43 的坑;如果要稳定生产,优先 A10/T4/A30/A100 这类 DC SKU。
2. 方案选择:DDA vs vGPU vs GPU-P(我当晚的选择与权衡)
| 方案 | 能力 | 适用 OS | 许可 | 适配难度 | 适配 AI 训练 | 我的结论 |
|---|---|---|---|---|---|---|
| DDA 直通整卡 | 把整张 PCIe 设备直通给单个 VM | Win/Linux Guest | 无额外授权 | 低 | 最佳(原生 CUDA) | 当晚采用 |
| NVIDIA vGPU(SR-IOV) | 一卡多 VM,profile 切分 | Win/Linux Guest | 需 vGPU 授权 + 许可服务器 | 中-高 | 佳(需按 profile 选版驱动) | 适合共享/多用户 |
| GPU-P 分区 | Windows 的 GPU 分区 | Windows Guest | 无 | 中(文档少) | 不稳定/受限 | 实验/桌面渲染为主 |
想要今天就训起来且最稳:DDA。需要多人共享才考虑 vGPU。GPU-P 我只在容器/渲染场景做过试验,这里不展开。
3. 前置准备(BIOS/固件/机房侧)
升级 BIOS / BMC / NVMe / GPU 固件 到厂商推荐版本。
BIOS 打开:
- Intel VT-d / IOMMU:Enabled
- Above 4G Decoding:Enabled(大显存/多设备必开)
- Resizable BAR:Auto/Disabled(遇到 MMIO 紧张时我会关)
- CSM:Disabled(UEFI 更稳)
电源策略:OS 与 BIOS 都设 Performance;风扇曲线拉高一点。
机房网络:准备一张 out-of-band 管理口和一张数据口,SR-IOV 能开的网卡先开着(供 vNIC 提升)。
4. 宿主:Windows Server 2022 基线
4.1 装 Hyper-V 与基本角色
# 以管理员进入 PowerShell
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All
Restart-Computer
4.2 安装 NVIDIA 数据中心驱动(宿主)
我倾向在 宿主也装上对应线的驱动,便于后续 vGPU/监控。但 DDA 直通前会“卸载/脱挂(dismount)”,宿主不会再占用。
安装后执行 nvidia-smi 确认能看到卡(仅用于校验,直通前会移除宿主占用)。
4.3 电源/更新策略
# 电源:高性能
powercfg /S SCHEME_MIN
# 关闭“自动更新立即重启”
sconfig # -> 5: Windows Update 设置为手动/维护窗口
5. 用 DDA 给 VM 直通整卡(完整、可复现脚本)
我的实践顺序:定位 GPU → 让宿主“放手”(dismount)→ 准备 VM(Gen 2、静态内存、MMIO)→ 把设备绑给 VM → 在客人机里装驱动 & CUDA → 验证。
5.1 创建/准备虚机(Generation 2 + 静态内存)
# 参数自定
$VmName = "win-train-01"
$VmPath = "D:\HyperV\$VmName"
$Vhdx = "D:\HyperV\$VmName\$VmName.vhdx"
New-VM -Name $VmName -Generation 2 -MemoryStartupBytes 128GB -NewVHDPath $Vhdx -NewVHDSizeBytes 1024GB -Path $VmPath -SwitchName "vSwitch-Data"
# 直通 GPU 需要“静态内存”,关掉动态内存
Set-VM -Name $VmName -DynamicMemoryEnabled $false
# CPU 给足一些,训练更稳
Set-VMProcessor -VMName $VmName -Count 24 -Reserve 80 -Maximum 100
# 开启 Guest 控制缓存,设置 MMIO 空间(依 GPU 调整)
# A10/RTX6000 Ada 建议:Low = 512MB,High = 32GB(显存越大 High 越大)
Set-VM -Name $VmName -GuestControlledCacheTypes $true -LowMemoryMappedIoSpace 512MB -HighMemoryMappedIoSpace 32GB
# 如有需要关闭安全启动(部分旧驱动/测试场景)
Set-VMFirmware -VMName $VmName -EnableSecureBoot On # 出问题时可改 Off
MMIO 经验值(我踩出来的表):
| GPU | 显存 | Low MMIO | High MMIO |
|---|---|---|---|
| T4 | 16 GB | 256 MB | 16 GB |
| A10 | 24 GB | 512 MB | 32 GB |
| RTX 6000 Ada | 48 GB | 512 MB | 64 GB |
| L40S | 48 GB | 512 MB | 64 GB |
如果启动报 MMIO/资源不足,先把 Resizable BAR 关掉,再把 High MMIO 提高一档。
5.2 找到 GPU 的 LocationPath,并让宿主“放手”
# 找到 NVIDIA 设备(含显示和音频功能)
$gpu = Get-PnpDevice -PresentOnly | Where-Object { $_.FriendlyName -like "*NVIDIA*" -and $_.Class -eq "Display" }
$gpuAudio = Get-PnpDevice -PresentOnly | Where-Object { $_.FriendlyName -like "*NVIDIA*" -and $_.Class -eq "Media" }
# 拿到 LocationPath(可能多条,尤其是带 Audio Function)
$locGpu = (Get-PnpDeviceProperty -InstanceId $gpu.InstanceId -KeyName DEVPKEY_Device_LocationPaths).Data
$locAud = (Get-PnpDeviceProperty -InstanceId $gpuAudio.InstanceId -KeyName DEVPKEY_Device_LocationPaths).Data
# 温柔点,先禁用宿主对设备的占用
Disable-PnpDevice -InstanceId $gpu.InstanceId -Confirm:$false
Disable-PnpDevice -InstanceId $gpuAudio.InstanceId -Confirm:$false
# 把设备 dismount,标记为可分配(Host Assignable)
$locGpu | % { Dismount-VMHostAssignableDevice -LocationPath $_ -Force }
$locAud | % { Dismount-VMHostAssignableDevice -LocationPath $_ -Force }
# 确认现在宿主能看到可分配设备
Get-VMHostAssignableDevice
这里最容易踩的坑是只 dismount 了 Display Function 没有 dismount Audio Function,Add-VMAssignableDevice 会失败。两条都要 dismount。
5.3 把 GPU 绑给 VM
# VM 必须关机状态
Stop-VM -Name $VmName
# 把两条 LocationPath 都加到 VM 上
$locGpu | % { Add-VMAssignableDevice -LocationPath $_ -VMName $VmName }
$locAud | % { Add-VMAssignableDevice -LocationPath $_ -VMName $VmName }
# 再确认一次
Get-VMAssignableDevice -VMName $VmName
# 启动 VM
Start-VM -Name $VmName
5.4 在客户机(Guest)里安装驱动 & CUDA
进到 VM(Windows 10/11/Server 2022 都可):
- 装对应版本的 NVIDIA Data Center Driver(与计划用的 CUDA/框架匹配)。
- nvidia-smi 能看到设备且 Compute Mode: Default。
- 装 CUDA Toolkit/cuDNN 或者直接用 PyTorch/TensorFlow 的 CUDA 绑定版本。
我的“保守兼容”矩阵(训练第一晚不追新):
| 框架 | 版本 | CUDA | cuDNN | 备注 |
|---|---|---|---|---|
| PyTorch | 2.2.x | 12.1 | 内置 | pip3 install torch torchvision --index-url https://download.pytorch.org/whl/cu121 |
| TensorFlow | 2.13/2.14 | 11.8 | 8.x | Windows 上更稳的是 2.13 + CUDA 11.8 |
快速自测(Guest 里):
# PowerShell
python - << 'PY'
import torch, time
print("CUDA avail:", torch.cuda.is_available())
x = torch.randn(10240, 10240, device="cuda")
tic=time.time(); y = x @ x; torch.cuda.synchronize(); print("GEMM ms:", (time.time()-tic)*1000)
print("Device:", torch.cuda.get_device_name(0))
PY
6. 多卡/多 VM 调度(我在香港机房是这样分的)
多卡单 VM:把多张卡的 LocationPath 全绑到一个 VM,做数据并行。
一卡多 VM(不共享):每卡固定给一台 VM,DDA 不支持共享。
一卡多 VM(共享):必须走 NVIDIA vGPU(授权),按 profile 切分(比如 8G/12G/24G slice)。
7. 可选:NVIDIA vGPU(SR-IOV/授权)部署要点(我做共享时的做法)
只给关键要点,不同代卡/许可版本细节略有差异,但抓住这几条就不迷路:
确认 GPU 支持 vGPU(A10、A16、L40S、A100 等)并准备 vGPU 许可服务器(NLS)。
宿主装 vGPU Host Driver(不同于通用 DC Driver),重启后 nvidia-smi -q | findstr -i vgpu 可见相关字段。
在 Hyper-V 给 VM 选择/注入 vGPU Profile(显存/编码能力/数量),并确保 Guest Driver 版本与 Host 对齐。
Guest 内安装 vGPU 对应的 NVIDIA 驱动,激活 许可(指向 NLS)。
训练框架里确保每个进程只占自己的 slice(用 CUDA_VISIBLE_DEVICES 或框架内设备选择)。
vGPU 的收益:同一张卡给多组用户/多台 VM;代价是要管理许可/配置,并且某些 profile 对 FP16/FP8 特性有约束,选型前看清 Profile 说明。
8. 性能与稳定性调优(我在生产里默认开的)
电源:BIOS/OS 高性能;Windows 里关闭 Core Parking。
NUMA 亲和:多路 CPU 时,把 VM vCPU 绑在同一 NUMA Node(Hyper-V 自动,但大 VM 时我会控制 vCPU 数量)。
存储:NVMe 直通或将数据盘给 VM 使用 固定大小 VHDX(避免动态膨胀)。
网络:vSwitch 用 SR-IOV 的物理 NIC,VM 网卡开启 RSS/VMQ,大包支持。
监控:宿主和 Guest 里都部署 nvidia-smi dmon/Telegraf Exporter,盯显存/温度/功耗。
9. 故障与坑位手记(当晚我真遇到的)
| 现象/报错 | 场景 | 解决办法(我当时的操作) |
|---|---|---|
| Add-VMAssignableDevice 失败 | 只 dismount 了 Display,忘了 Audio Function | 把 Audio Function 的 LocationPath 一并 Dismount-VMHostAssignableDevice,再 Add-VMAssignableDevice |
| VM 启动报资源不足 / MMIO 错误 | High/Low MMIO 不够 | 关 Resizable BAR;把 HighMMIO 提到 32–64GB;Low 256→512MB |
| Guest 内 Code 43 | 消费级卡/驱动不匹配 | 改用 Data Center Driver;优先 A10/T4 等 DC SKU;必要时更换驱动小版本 |
| 宿主更新后卡“回来”了 | Windows 更新驱动 | 更新完重新执行 dismount;把自动驱动更新设为“仅通知” |
| nvidia-smi 看不到卡 | VM 内驱动不对 | Host/Guest 驱动线要一致(DDA 宿主可无驱动,但 Guest 必须对版) |
| 训练速度异常慢 | CPU 省电/内存频率 | BIOS/OS 统一 Performance;确认内存四通道以上;关闭节能策略 |
| vGPU 许可报错 | NLS 未激活/网络 | 检查 NLS 状态与端口,Guest 指向正确的 License Server;Host/Guest 驱动匹配 |
10. 我常用的一键“拆卡回宿主”脚本(回滚/维护用)
$VmName = "win-train-01"
# 1) 从 VM 移除可分配设备
Get-VMAssignableDevice -VMName $VmName | Remove-VMAssignableDevice -VMName $VmName
Stop-VM -Name $VmName
# 2) 把设备挂回宿主
Get-VMHostAssignableDevice | % { Mount-VMHostAssignableDevice -LocationPath $_.LocationPath }
# 3) 重新启用宿主的 NVIDIA 设备
$gpu = Get-PnpDevice -PresentOnly | Where-Object { $_.FriendlyName -like "*NVIDIA*" }
$gpu | % { Enable-PnpDevice -InstanceId $_.InstanceId -Confirm:$false }
11. 交付清单与复盘(我给客户的标准落地)
- BIOS:VT-d、Above 4G、(必要时)关 ReBAR
- 宿主:Hyper-V、(可选)DC 驱动安装校验
- VM(Gen2):静态内存、MMIO(Low/High)、CPU 资源
- DDA:Display + Audio Function 双通道 dismount → add 到 VM
- Guest:DC 驱动 + CUDA/框架;nvidia-smi 自检
- 训练样例跑通(GEMM/Base 模型)
- 监控与告警(显存/温度/功耗)
- 回滚脚本(维护窗口)
12. 收尾:机房的黎明与第一条 Loss 曲线
天色发白的时候,我把最后一条 Add-VMAssignableDevice 回车,虚机起来了。nvidia-smi 打印出 A10 的 24GB 显存,PyTorch 的小脚本跑完,GEMM 延迟照着预期。客户把数据路径切到 NVMe,第一轮训练启了四个 worker。风墙的噪音突然没那么刺耳了,像是背景里的白色海浪。
我合上笔记本,把那张记满 LocationPath、MMIO 配额和报错代码的便签纸塞回工具包。vGPU 的活儿留到下一班——当他们要多人共享时我再回来。
你如果也在香港、也对着一台 2U 的机器急着把训练跑起来,就按上面的步骤做。能用 DDA 先稳住交付,再慢慢把 vGPU/多用户场景补齐。别忘了那两条 LocationPath:Display 和 Audio,一次 dismount 干净。
祝你凌晨的第一条 loss 曲线,也像那天一样,顺着预期滑下去。
附:一次成功的最小命令清单(可直接抄)
# Gen2 + 静态内存 + MMIO
New-VM -Name win-train-01 -Generation 2 -MemoryStartupBytes 128GB -NewVHDPath D:\HyperV\win-train-01\win-train-01.vhdx -NewVHDSizeBytes 1024GB -Path D:\HyperV\win-train-01 -SwitchName vSwitch-Data
Set-VM -Name win-train-01 -DynamicMemoryEnabled $false -GuestControlledCacheTypes $true -LowMemoryMappedIoSpace 512MB -HighMemoryMappedIoSpace 32GB
Set-VMProcessor -VMName win-train-01 -Count 24 -Reserve 80 -Maximum 100
# 定位并 dismount GPU(含音频功能)
$gpu = Get-PnpDevice -PresentOnly | ? { $_.FriendlyName -like "*NVIDIA*" -and $_.Class -eq "Display" }
$aud = Get-PnpDevice -PresentOnly | ? { $_.FriendlyName -like "*NVIDIA*" -and $_.Class -eq "Media" }
$lg = (Get-PnpDeviceProperty -InstanceId $gpu.InstanceId -KeyName DEVPKEY_Device_LocationPaths).Data
$la = (Get-PnpDeviceProperty -InstanceId $aud.InstanceId -KeyName DEVPKEY_Device_LocationPaths).Data
Disable-PnpDevice -InstanceId $gpu.InstanceId -Confirm:$false
Disable-PnpDevice -InstanceId $aud.InstanceId -Confirm:$false
$lg | % { Dismount-VMHostAssignableDevice -LocationPath $_ -Force }
$la | % { Dismount-VMHostAssignableDevice -LocationPath $_ -Force }
# 绑定到 VM 并启动
Stop-VM win-train-01
$lg | % { Add-VMAssignableDevice -LocationPath $_ -VMName win-train-01 }
$la | % { Add-VMAssignableDevice -LocationPath $_ -VMName win-train-01 }
Start-VM win-train-01
有了这些,你就具备了在 Windows Server 2022 + Hyper-V 上把 vGPU/整卡直通用于 AI 训练 的全部关键步骤与可复现脚本。