Hyper-VでCUDAを使いたい!(願望)

Windows 10, 11 Pro以上のエディションにはHyper-Vという仮想マシン機能が搭載されています。昔はRemoteFXを使い仮想マシンでGPUを利用することができましたが、脆弱性により無効化されたので利用することができません。

しかし、GPU-Pという機能を利用することでHyper-Vを利用できるようです。

PowerShellで確認してみても、Add-VMGpuPartitionAdapterという項目が実装されていることが分かります。

今回は、実際にHyper-VでGPUを利用する方法を紹介していきます。

Youtubeでの解説動画
あらすじ

仮想マシンの作成

まずは仮想マシンを作成していきます。今回は実験環境なのでWindows11 Enterpriseでも入れてみます。クイック作成を開いて「オペレーティングシステムの選択」からWindows 11 開発環境を選択します。

そして、仮想マシンを作成をクリックします。

こんな感じでダウンロードが始まります。

補足 このGPU-Pという機能、Windows Sandboxで利用できます。vGPU 項目 そのため、Sandbox環境からドライバを拝借します。

ダウンロードが終わるとこのような画面になります。

ここから、設定を編集をクリックします。

チェックポイントを無効化しましょう。忘れるとすぐに仮想マシンが破損します。

そのあと、いったんウィンドウを閉じます。

次に、PowerShellで以下のコマンドを実行します。管理者権限が必要です。

Add-VMGpuPartitionAdapter -VMName "仮想マシン名"

Set-VMGpuPartitionAdapter -VMName "仮想マシン名" -MinPartitionVRAM 80000000 -MaxPartitionVRAM 100000000 -OptimalPartitionVRAM 100000000 -MinPartitionEncode 80000000 -MaxPartitionEncode 100000000 -OptimalPartitionEncode 100000000 -MinPartitionDecode 80000000 -MaxPartitionDecode 100000000 -OptimalPartitionDecode 100000000 -MinPartitionCompute 80000000 -MaxPartitionCompute 100000000 -OptimalPartitionCompute 100000000

Set-VM -GuestControlledCacheTypes $true -VMName "仮想マシン名"

Set-VM -LowMemoryMappedIoSpace 1GB -VMName "仮想マシン名"

Set-VM –HighMemoryMappedIoSpace 32GB –VMName "仮想マシン名"

そのあと、一回起動させてセットアップを済ませます。

追記

上記のパワーポイントを自動化するスクリプトがありました。

GPU-P_set.cmdと名前を付けて保存して実行するだけで済みます。

保存する際はANSI(Shift-JIS)を文字コードとして選択することを忘れないでください。

@echo off && goto PreCheck
<# コマンドプロンプト----------------------------------
  :PreCheck
     setlocal
       for /f "tokens=3 delims=\ " %%A in ('whoami /groups^|find "Mandatory Label"') do set LEVEL=%%A
       if not "%LEVEL%"=="High"  goto GETadmin
     goto Excute
  :GETadmin
     endlocal
     echo "%~nx0": elevating self
     echo    Are you OK ?
     del "%temp%\getadmin.vbs"                                    2>NUL
       set vbs=%temp%\getadmin.vbs
       echo Set UAC = CreateObject^("Shell.Application"^)          >> "%vbs%"
       echo Dim stCmd                                              >> "%vbs%"
       echo stCmd = "/c """"%~s0"" " ^& "%~dp0" ^& Chr(34)         >> "%vbs%"
       echo UAC.ShellExecute "cmd.exe", stCmd, "", "runas", 1      >> "%vbs%"
       pause
       "%temp%\getadmin.vbs"
     del "%temp%\getadmin.vbs"
     goto :eof
  :Excute
     endlocal
     set "Dir=%~1"
     powershell -NoProfile -ExecutionPolicy Unrestricted "$s=[scriptblock]::create((gc \""%~f0"\"|?{$_.readcount -gt 1})-join\"`n\");&$s" "'%Dir%'" &exit /b
-------------------------------------------------------#>
  Param([string] $SptDirPATH)
  write-host "Script Start......`($SptDirPATH`)"
####################################
###   Powershell Script HERE!!   ###
####################################
<# バッファーサイズの調整 #>
# WindowSize の既定値を変数に格納
$defaultWS = $host.UI.RawUI.WindowSize

# BufferSize を 140 に変更
(Get-Host).UI.RawUI.BufferSize `
   = New-Object "System.Management.Automation.Host.Size" (140,$host.UI.RawUI.BufferSize.Height)

# WindowSize は既定値に設定
$host.UI.RawUI.WindowSize = $defaultWS


<# 0 #>
# すでに設定されているアダプターの確認
$0VMName = (Read-Host アダプターを追加するVM名を入力してください)

# セットしたVMが存在するか確認
$VMall = Get-VM
$VMalls = $VMall.Name | Out-String -Stream
# 条件分岐
if ($VMalls.Contains("$0VMName"))
  {Write-Host プロセスを進めます}
Else
  {# 入力された値が 不適合
   Write-Host 入力された文字が不適切です。処理を中断します。
   Write-Host "続行するには何かキーを押してください..."
   $host.UI.RawUI.ReadKey() | Out-Null
   exit
   } 

# すでに設定されているアダプターの削除
$ErrorActionPreference = 'Stop'
try {
    Remove-VMGpuPartitionAdapter -VMName "$0VMName"
    ""
    Write-Host "このVMに設定されていたGPU-Partitionが削除されました"
} catch {
    ""
    Write-Host "このVMにGPU-Partitionは設定されていません"
}
$ErrorActionPreference = "Continue"


<# 1 #>
# ユーザーから入力を受け付けて入力内容を $percent に格納
""
Write-Host "※整数値を入力してください"
[int]$1percentMIN =(Read-Host "MIN:分割する最小の量を入力してください(%:  0~100)")
[int]$1percentMAX =(Read-Host "MAX:分割する最大の量を入力してください(%:MIN~100)")
[int]$1percentOPT =(Read-Host "OPT:分割する最適の量を入力してください(%:MIN~MAX)")

# 条件分岐
if (($1percentMIN -ge 0) -and
    ($1percentMIN -le 100) -and

    ($1percentMAX -ge 1) -and
    ($1percentMAX -ge ($1percentMIN)) -and
    ($1percentMAX -le 100) -and

    ($1percentOPT -ge 1) -and
    ($1percentOPT -ge $1percentMIN) -and
    ($1percentOPT -le ($1percentMAX))){
	# 処理(if,elseそれぞれ何かしら必要)
    Write-Host プロセスを進めます
}Else{
    # 入力された数字が 不適合
    Write-Host 入力された文字が不適切です。処理を中断します。
    Write-Host "続行するには何かキーを押してください..."
    $host.UI.RawUI.ReadKey() | Out-Null
    exit
}


<# 2 #>
# インスタンスパス、GPU名、変数等を取得する

[int]$Count = (Get-VMHostPartitionableGpu).Count

# 接続されている GPU の数で分岐
If($Count -ge 2) {
   (Get-VMHostPartitionableGpu).Name `
      | ForEach-Object `
           -Begin { 
              # 変数を初期化
              Remove-Variable Add, Sub, End 2>${NULL}
              # 変数を宣言、準備、設定
              $Space = ($Host.UI.RawUI.WindowSize.Width - 11)
              [Array]$Sub = [System.Management.Automation.Host.ChoiceDescription]::new("(&整数を入力)", "説明")
              $Name_HashTable = [Ordered]@{}
              [int]$Num = 1
             } `
           -Process { 
             # PnP Util で使えるように調整
              $InsID = ($_ -split '\\?\',0,'Simplematch' -split '#{')[1].Replace('#','\')
              Set-Variable "2InsID$Num" "$InsID"
             # PnP Util から GPU名 を取得
              $GPUName = (Get-PnpDevice -InstanceId $InsID).FriendlyName
              Set-Variable "Name$Num" "$GPUName"
             # PromptForChoice の選択肢を準備
              $One = "$GPUName(&$Num)"
              $CRLF = $One.PadRight($Space)+"`b"
              [Array]$Add = [System.Management.Automation.Host.ChoiceDescription]::new("$CRLF", "$_")
             # ループの最後とそれ以外で分岐
              If($Num -ne $Count)
                {$Sub = @($Sub;$Add)}
              Else 
                {$CRLF = $One.PadRight($Space*2)+"`b"
                 $Sub = @($Sub;[System.Management.Automation.Host.ChoiceDescription]::new("$CRLF", "$_`r`n`b"))}
             # GPU-P 用のインスタンスパスを格納
              $Name_HashTable.Add("$Num","$_")
              $Num += 1
             } `
           -End { 
             # おまかせの選択肢の追加
              [Array]$End = [System.Management.Automation.Host.ChoiceDescription]::new("指定しない(&$Num)`r`n`b", "システムに任せます。`r`n`r`n`b")
              $Sub = @($Sub;$End)
             }

  # PromptForChoice で実行するものを設定
   1..($Num-1) | ForEach-Object `
                    -Begin { $Options = @('{') } `
                    -Process { 
                       $Arg1 = (Get-Variable "Name$_").Value
                       $Arg2 = $Name_HashTable["$_"]
                       $Options += "$_ `{Write-Host `"$Arg1 が選択されました`" ;`$AGPUName = `"$Arg1`" ;`$2gpuNAME = `"$Arg2`" ;break `};" 
                      } `
                    -End { $Options += @('}') }
  # 選択肢を画面表示
   $Result = $Host.UI.PromptForChoice("【確認】","--番号を選び、入力してください--`r`n ",$Sub,$Num)
  # 選択に応じたコマンドを実行
   If($Result -ne $Num)
     {. ([Scriptblock]::Create("switch ($Result) $Options"))}
}

# GPU が1つ 又は おまかせ にした場合に実行する
If(!($Count -ge 2) -OR ($Result -eq $Num)) {
   Write-Host "GPUを引数にセットします"
   $2gpuNAME `
      = ( Get-VMHostPartitionableGpu `
             | Select-Object Name `
             | Get-Member -Membertype NoteProperty `
             | Select-Object Definition `
             | Format-Table -AutoSize -Wrap `
             | Out-String -Stream `
             | Select-String string `
         )  -replace "string Name="

   # GPU名を取得
   $2InsID = ($2gpuNAME -split '\\?\',0,'Simplematch' -split '#{')[1].Replace('#','\')
   $AGPUName = (Get-PnpDevice -InstanceId $2InsID).FriendlyName
}

# 取得した内容を表示
""
Write-Host (@"
$0VMName に
GPU : $2gpuNAME
(※$AGPUName)
を GPU-Partition として適用します
"@)
""


<# 3 #>
# Get-VMHostPartitionableGpuから割り当てられるGPUの総量を取得
$3gpuAvailableVRAM = Get-VMHostPartitionableGpu | Out-String -Stream | Select-String AvailableVRAM
$3gpuAvailableEncode = Get-VMHostPartitionableGpu | Out-String -Stream | Select-String AvailableEncode
$3gpuAvailableDecode = Get-VMHostPartitionableGpu | Out-String -Stream | Select-String AvailableDecode
$3gpuAvailableCompute = Get-VMHostPartitionableGpu | Out-String -Stream | Select-String AvailableCompute

# GPUの各総量を数値としてそれぞれ格納
$3gpuAvailableVRAMA = [int]($3gpuAvailableVRAM -replace "[a-zA-Z]" -replace '[" *"]' -replace ":")
$3gpuAvailableEncodeA = [decimal]($3gpuAvailableEncode -replace "[a-zA-Z]" -replace '[" *"]' -replace ":")
$3gpuAvailableDecodeA = [int]($3gpuAvailableDecode -replace "[a-zA-Z]" -replace '[" *"]' -replace ":")
$3gpuAvailableComputeA = [int]($3gpuAvailableCompute -replace "[a-zA-Z]" -replace '[" *"]' -replace ":")


<# 4 #>
# LOG の保存先ディレクトリを作成
$DIRpath=$SptDirPATH.TrimEnd('\') + "\LOG"
if ( -not ( Test-Path -Path "$DIRpath" )){ New-Item "$DIRpath" -ItemType Directory > $null }
Get-Date | Out-String -Stream | ?{$_ -ne ""} | Out-File "$DIRpath\GPU-P_log.txt" -Append -Force


<# 5 #>
# パーティション分割値を求める
$VRAMdivideMIN = [Math]::Round($($3gpuAvailableVRAMA * ($1percentMIN / 100)),0)
$VRAMdivideMAX = [Math]::Round($($3gpuAvailableVRAMA * ($1percentMAX / 100)),0)
$VRAMdivideOPT = [Math]::Round($($3gpuAvailableVRAMA * ($1percentOPT / 100)),0)

$ENCODEdivideMIN = [Math]::Round($($3gpuAvailableEncodeA * ($1percentMIN / 100)),0)
$ENCODEdivideMAX = [Math]::Round($($3gpuAvailableEncodeA * ($1percentMAX / 100)),0)
$ENCODEdivideOPT = [Math]::Round($($3gpuAvailableEncodeA * ($1percentOPT / 100)),0)

$DECODEdivideMIN = [Math]::Round($($3gpuAvailableDecodeA * ($1percentMIN / 100)),0)
$DECODEdivideMAX = [Math]::Round($($3gpuAvailableDecodeA * ($1percentMAX / 100)),0)
$DECODEdivideOPT = [Math]::Round($($3gpuAvailableDecodeA * ($1percentOPT / 100)),0)

$COMPUTEdivideMIN = [Math]::Round($($3gpuAvailableComputeA * ($1percentMIN / 100)),0)
$COMPUTEdivideMAX = [Math]::Round($($3gpuAvailableComputeA * ($1percentMAX / 100)),0)
$COMPUTEdivideOPT = [Math]::Round($($3gpuAvailableComputeA * ($1percentOPT / 100)),0)


# 計算結果を表示する
$5VRAMdisplay = [pscustomobject]([ordered]@{"------VRAM------" = ""; MIN = " $VRAMdivideMIN"; OPT = " $VRAMdivideOPT"; MAX = " $VRAMdivideMAX"; Available = " $3gpuAvailableVRAMA";})
$5ENCODEdisplay = [pscustomobject]([ordered]@{"-----Encode-----" = ""; MIN = " $ENCODEdivideMIN"; OPT = " $ENCODEdivideOPT"; MAX = " $ENCODEdivideMAX"; Available = " $3gpuAvailableEncodeA";})
$5DECODEdisplay = [pscustomobject]([ordered]@{"-----Decode-----" = ""; MIN = " $DECODEdivideMIN"; OPT = " $DECODEdivideOPT"; MAX = " $DECODEdivideMAX"; Available = " $3gpuAvailableDecodeA";})
$5COMPUTEdisplay = [pscustomobject]([ordered]@{"-----Compute----" = ""; MIN = " $COMPUTEdivideMIN"; OPT = " $COMPUTEdivideOPT"; MAX = " $COMPUTEdivideMAX"; Available = " $3gpuAvailableComputeA";})

""
Write-Host "# confimation"

Write-Output "VM  : $0VMName" | Out-File "$DIRpath\GPU-P_log.txt" -Append
Write-Output "GPU : $GPUName" | Out-File "$DIRpath\GPU-P_log.txt" -Append
Write-Output "Instance ID A : $2InsID" | Out-File "$DIRpath\GPU-P_log.txt" -Append
Write-Output "Instance ID B : $2gpuNAME" | Out-File "$DIRpath\GPU-P_log.txt" -Append

$5VRAMdisplay | Out-String -Stream | ?{$_ -ne ""} | Tee-Object -FilePath "$DIRpath\GPU-P_log.txt" -Append
$5ENCODEdisplay | Out-String -Stream | ?{$_ -ne ""} | Tee-Object -FilePath "$DIRpath\GPU-P_log.txt" -Append
$5DECODEdisplay | Out-String -Stream | ?{$_ -ne ""} | Tee-Object -FilePath "$DIRpath\GPU-P_log.txt" -Append
$5COMPUTEdisplay | Out-String -Stream | ?{$_ -ne ""} | Tee-Object -FilePath "$DIRpath\GPU-P_log.txt" -Append

""
Write-Host "※ログの出力先は `"$DIRpath\GPU-P_log.txt`""

Write-Output "" | Out-File "$DIRpath\GPU-P_log.txt" -Append -Force
Write-Output "" | Out-File "$DIRpath\GPU-P_log.txt" -Append -Force


<# 6 #>
# 実行するか確認
$title = "【確認】"
$message = @"
上記の通りにGPUPartitionをセットします。
この操作を実行しますか?
"@

$tChoiceDescription = "System.Management.Automation.Host.ChoiceDescription"
$tOptions = @(
    New-Object $tChoiceDescription ("はい(&Yes)",     "この操作を実行し、次のステップへ進みます。")
    New-Object $tChoiceDescription ("いいえ(&No)",    "この操作をキャンセルし、中断します。")
)

$tResult = $host.ui.PromptForChoice($title, $message, $tOptions, 0)
switch ($tResult)
  {
    0 {"「はい」が選ばれました。"; break}
    1 {"「中断」が選ばれました。"; break}
  }

if ($tResult -ne 0) { exit }


<# 7 #>
# GPU-P.Adapterを追加する
If(!($Count -ge 2) -OR ($Result -eq $Num)) {
   # システムが指定
   Add-VMGpuPartitionAdapter -VMName "$0VMName"
}Else{
   # GPUを指定する場合
   Add-VMGpuPartitionAdapter -VMName "$0VMName" -InstancePath "$2gpuNAME"
}

# 追加したアダプターを設定する
#VRAM
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MinPartitionVRAM $VRAMdivideMIN
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MaxPartitionVRAM $VRAMdivideMAX
Set-VMGpuPartitionAdapter -VMName "$0VMName" -OptimalPartitionVRAM $VRAMdivideOPT

#Encode
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MinPartitionEncode $ENCODEdivideMIN
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MaxPartitionEncode $ENCODEdivideMAX
Set-VMGpuPartitionAdapter -VMName "$0VMName" -OptimalPartitionEncode $ENCODEdivideOPT

#Decode
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MinPartitionDecode $DECODEdivideMIN
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MaxPartitionDecode $DECODEdivideMAX
Set-VMGpuPartitionAdapter -VMName "$0VMName" -OptimalPartitionDecode $DECODEdivideOPT

#Compute
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MinPartitionCompute $COMPUTEdivideMIN
Set-VMGpuPartitionAdapter -VMName "$0VMName" -MaxPartitionCompute $COMPUTEdivideMAX
Set-VMGpuPartitionAdapter -VMName "$0VMName" -OptimalPartitionCompute $COMPUTEdivideOPT


<# 8 #>
# CPU Write-Combiningの有効化
Set-VM -GuestControlledCacheTypes $true -VMName "$0VMName"

# MMIO領域の構成
Set-VM -LowMemoryMappedIoSpace 1Gb -VMName "$0VMName"
Set-VM -HighMemoryMappedIoSpace 33280Mb -VMName "$0VMName"


<# 9 #>
# 確認( = Get-VMGpuPartitionAdapter "$0VMName" -verbose )
Get-VMGpuPartitionAdapter -VMName "$0VMName"

# 終了メッセージ
Write-Host "すべてのプロセスは終了しました"
####################################
###            FINISH            ###
####################################
  Write-Host "---Script Finish---"
  Write-Host "画面を閉じるには何かキーを押してください..."
  $host.UI.RawUI.ReadKey() | Out-Null
  EXIT

ドライバーの移植

最後にドライバーの移植をします。まず、Windows Sandboxを有効化しましょう

「Windowsサンドボックス」の使い方。初めてのアプリを安全な環境で試せる!【Windows Tips】 | できるネット (dekiru.net)

そのあと、Hyper-Vの仮想ハードディスクをマウントします。

ここから仮想ハードディスクの場所に飛ぶことができる

このようなエラーが出るかもしれません。

その場合は、ディスクの管理からドライブ文字を割り当てます

すると、このようにマウントできます。

次に、マウントしたドライブWindows\System32\にHostDriverStoreというフォルダを作成します

次に、マウントしたドライブ以外の場所に.wsbのファイルを作成します。内容は以下の通りです。

<Configuration>
<VGpu>Enable</VGpu>
<MappedFolders>
   <MappedFolder>
       <HostFolder>"マウントしたドライブ文字":\Windows\System32\HostDriverStore</HostFolder>
       <ReadOnly>false</ReadOnly>
   </MappedFolder>
</MappedFolders>
</Configuration>

そのあと、管理者権限のコマンドプロンプトからファイルを実行します。 ファイルから直接起動しないでください。

Sandboxが起動します。

そして、このC:\Windows\System32\HostDriverStoreにあるファイルをデスクトップにマッピングされたHostDriverStoreにコピーします。

NVIDIAでCUDAを利用する場合は親のC:\Windows\System32\にあるnvapi64.dllを子の同じディレクトリに、親のC:\Windows\System32\DriverStore\FileRepository\以下にあったnvapi64.dllは,子のC:\Windows\System32\HostDriverStore\FileRepository\にnvapi64.dllのあったフォルダごとコピーします。

これらがすべて終わったら、ドライブをアンマウントして仮想マシンを起動させましょう。

GPUが認識されているはずです。

実際に動いてるのか検証

実際に動くのか検証するためにこのサイトを使って負荷をかけました。

ちゃんと動いていますね。

GPUが有効化された仮想環境でゲームをしたい方は下の記事も参考にしてみてください。

あせて読みたい!

最後まで見ていただきありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


あらすじ