Labs
Every improvement is tested before it’s adopted: a hypothesis, a baseline configuration, a candidate configuration and repeated measurements.
Workflow
Section titled “Workflow”-
Open a lab with a title and a hypothesis. Optionally give the baseline and candidate a profile and an override.
Terminal window ./gpuvm.ps1 lab-new -Title 'More threads' -Hypothesis '10 threads raise FPS without hurting the host' `-BaselineProfile max-performance -CandidateProfile max-performance `-CandidateOverride @{ vm = @{ cpu = @{ count = 10 } } } -
Measure the baseline several times. Before each measurement you can run
guest/collect-info.ps1inside the VM: it saves the state of the GPU, the CPU and the power plan, plus adxdiag.Terminal window ./gpuvm.ps1 lab-add -Lab 002 -Variant baseline -Metric 'Time Spy GPU' -Value 10210 -Unit pts -
Apply the candidate and measure again with
-Variant candidate. Use-LowerIsBetterfor metrics such as latency, and-Notesfor observations.Terminal window ./gpuvm.ps1 lab-add -Lab 002 -Variant candidate -Metric 'Time Spy GPU' -Value 10480 -Unit pts -
Compare: averages per metric and the difference in %.
Terminal window ./gpuvm.ps1 lab -Lab 002 -
Close it as
adopted,rejectedorinconclusivewith a conclusion. When a lab is adopted, carry the candidate configuration to the matching profile inprofiles/.Terminal window ./gpuvm.ps1 lab-close -Lab 002 -Status adopted -Conclusion '+2.6 % GPU; moved to max-performance'
-Lab accepts the lab’s number (the start of its folder name). ./gpuvm.ps1 lab without -Lab lists all labs with their status and number of results.
Where labs live
Section titled “Where labs live”Each lab is a folder labs/NNN-name/ with lab.json (hypothesis, variants, results and conclusion) and notes.md (procedure and observations). From the repository they are in labs/; with the installed app they go to %ProgramData%\GpuVM\labs. The GPUVM_LABS_ROOT environment variable points them somewhere else.
Current labs
Section titled “Current labs”Labs 001–007 are open; 008 and 009 were adopted.
| # | Lab | Hypothesis | Candidate |
|---|---|---|---|
| 001 | GPU selection on Windows 10 | With two partitionable GPUs (an RTX 4060 and an integrated UHD 730), Windows 10 could give the VM the integrated one; disabling the UHD 730 before starting the VM makes sure it gets the RTX 4060. | Same profile (balanced); manual step. |
| 002 | Streaming with Sunshine and a virtual display | With the virtual display as the only display (Hyper-V video off), Sunshine captures what the GPU draws and encodes with NVENC; Moonlight shows the VM at 1080p60 with low latency. | Same profile (balanced). |
| 003 | Audio in the VM | Generation 2 has no sound card; without a virtual audio device Sunshine streams video without sound. A virtual audio driver fixes it. | Same profile (balanced). |
| 004 | Moonlight latency | Turning V-Sync off in Moonlight lowers total latency by about one frame (~16 ms at 60 FPS) without visible stutter. | streaming.client: vsync: false, performanceOverlay: true |
| 005 | Bitrate and codec | Host–VM traffic doesn’t leave for the physical network: 60 Mbps and HEVC improve sharpness without adding encode/decode latency. | streaming.client: codec: HEVC, bitrateKbps: 60000 |
| 006 | CPU reservation | On a Windows 10/11 host the hypervisor uses the root scheduler, where per-VM reserve and weight do nothing; only the number of virtual processors matters. 6 threads leave the host more room than 8 for the same game FPS. | vm.cpu.count: 6 |
| 007 | NVENC in Sunshine | Sunshine’s defaults are already preset 1, quarter-resolution two-pass and latency over power. Single pass, no VBV increase and no split encode should lower encode time further at 1080p. | streaming.sunshine: nvenc_twopass: disabled, nvenc_vbv_increase: 0, nvenc_split_encode: disabled |
| 008 | Stream start time | With the Hyper-V display off, Sunshine’s per-session display configuration (ensure_only_display) only adds work: every change makes it re-test all encoders before the first frame. Turning it off cuts the time to the first frame. |
streaming.sunshine: dd_configuration_option: disabled |
| 009 | Capture of exclusive fullscreen games | Desktop duplication (ddx) gets no new frames from the virtual display while a game is in exclusive fullscreen, so the stream stays black. Windows.Graphics.Capture (wgc), with Sunshine in the user’s session, captures it. |
streaming.sunshine: capture: wgc |
| 010 | Network isolation | Port ACLs on the VM’s virtual switch port, enforced by the host, keep the VM away from the host and the LAN while it keeps internet, DHCP and DNS; stateful rules let Moonlight’s connections through. | vm.network.isolation: true |
| 011 | Audio without Steam: VB-CABLE | Sunshine captures VB-CABLE’s output (stereo or 16 channels) and carries the VM’s sound without Steam Streaming Speakers. | streaming.audio: vbcable |
| 012 | Audio over a USB speaker of our own | A USB audio device served by GpuVM inside the VM and plugged in with usbip-win2 is driven by Windows’ built-in driver, so Sunshine captures real sound. | streaming.audio: usb |
Labs 004–012 use max-performance for both variants.
What the labs found so far
Section titled “What the labs found so far”- 002: in the first real test (Windows 10 22H2 VM) Sunshine installed, paired on its own and detected NVENC, but Moonlight showed black: Sunshine was capturing the Hyper-V display instead of the virtual one. The fix (set
output_nameto the virtual display anddd_configuration_option = ensure_only_display) is now done automatically on every start and open. Still pending: Sunshine reports “Display device configuration API is temporarily unavailable” and doesn’t change the resolution. - 003: VB-CABLE Pack45 was chosen over the VDD project’s Virtual Audio Driver (beta, needs test signing), Scream (needs Secure Boot off) and Steam Streaming Speakers (requires installing Steam, no pinned version). Replaced by GpuVM’s own USB speaker after lab 011 showed VB-CABLE is silent to Sunshine inside a VM; see Audio.
- 004: measured with the end-to-end meter in
tools/latency(1080p, 60 FPS, 40 presses on thewin10VM). Median 38.9 ms with V-Sync vs 33.8 ms without: about 5 ms less, under one frame, and the p95 halves (231.5 → 122 ms). Part of the p95 and the missed samples come from the meter itself. With V-Sync, Moonlight’s overlay showed 57.12 FPS received, 1 ms network and 0.32 ms decode. 120 and 144 FPS aren’t measured reliably yet: the stream stayed black because the refresh rate changed while the previous Sunshine session was still open; the tools now close it between variants. Still open. - 006: rewritten. The host reports the root scheduler (
Hypervisor scheduler type is 0x4), and Microsoft documents that caps, weights and reserves only apply with the classic or core scheduler, so 30 % vs 100 % reserve couldn’t show a difference. It now compares 8 vs 6 virtual processors. No results yet. - 007: rewritten. The first candidate (preset 1,
quarter_res, latency over power) equalled Sunshine’s defaults. The new candidate turns off two-pass, the VBV increase and split encode. No results yet. - 008 (adopted): opening without changing resolution or FPS went from 22–27 s to the first frame (26.8 s measured) to 2.1 s. Changing the mode still costs 10–15 s (13.3 s for 120 FPS after 60). With
ensure_only_display, Sunshine’s log showed “Encoder reenumeration is required” on every session.connectnow setsdd_configuration_option = disabledwhen GpuVM already turned the Hyper-V display off. See Streaming. - 009 (adopted): A game in exclusive fullscreen at 800×600 was black with
ddx(Sunshine got no new frames and re-sent the last one) and visible withwgc. Adopted as a per-VM option, off by default (Options → Captura):wgcdoesn’t show Windows’ administrator prompts (secure desktop). No security change: inwgcSunshine runs as the signed-in user (elevated) instead of SYSTEM. See Capture modes. - 010 (adopted): with the rules, none of the host’s TCP ports (4 before) and none of the router’s checks (3 before) were reachable from the VM, by IPv4 or IPv6; internet, DNS, DHCP and Sunshine kept working and connect time didn’t change (0.35 ms). On by default (Options → Red).
- 011 (rejected): both VB-CABLE outputs reach Sunshine as digital silence inside the VM while Windows’ meter shows sound; Moonlight received nothing. The sound that seemed to work came from Steam Streaming Speakers (0.265 in Moonlight).
- 012 (adopted): GpuVM’s speaker, Speakers (GpuVM Audio), was captured at the same level as Windows’ meter (0.356) and reached Moonlight at 0.351, louder than Steam’s 0.265, with no transfer errors. usbip-win2’s drivers are signed by Microsoft and it adds no certificate. See Audio.