1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
# Open-IOV Performance Testing & A/B Methodology
> **Status: superseded (historical).** Part of the pre-2026-09-23 docs; kept
> as a record. Current entry point: [docs/README.md](README.md). Performance
> replacement:
> [05-operations/01-performance-correctness-security.md](05-operations/01-performance-correctness-security.md).
> How we measure vGPU performance against bare metal, interpret the results,
> and keep overhead on the critical path honest. **Run correctness first**
> (`docs/DEBUGGING.md` §5) — numbers from a half-working stack are noise.
---
## 1. What overhead is acceptable (targets)
| Path | Target | Why |
|------|--------|-----|
| GSP RPC round-trip (doorbell→response→MSI-X) | < 50 µs (stretch: < 10 µs) | every driver op goes through here |
| MSI-X injection (eventfd → guest handler) | < 5 µs | completion latency dominates |
| Guest BAR0 MMIO read/write | < 2 µs (shadow RAM) | hot path for register polling |
| CUDA kernel launch (added overhead vs bare metal) | < 10% | user-visible |
| Memory alloc/map RPCs | < 100 µs | setup-time, amortized |
| CUDA compute throughput | ≥ 95% of bare metal | GPU-bound, should be ~free |
| Graphics (frame rate) | ≥ 90% of bare metal | swapchain + submission overhead |
## 2. The A/B rule
Every measurement runs **twice on the same machine, same boot if possible**:
- **A (bare metal)**: workload on the physical GPU via the host's stock
driver (or our OpenRM nvidia.ko directly on the hardware).
- **B (vGPU)**: identical workload in the guest on the emulated vGPU.
Same binary, same input sizes, same environment (governor, clocks, hugepages).
Report **B/A ratio %** — never absolute numbers alone.
One variable at a time. If B regresses, you changed the stack, the workload,
or the machine — find which before optimizing.
## 3. Microbenchmarks (build these first)
1. **RPC round-trip**: from the guest, submit a DEBUG/NOOP-ish RPC in a loop;
time doorbell-write → response-visible. Host side: `gsp_emu_get_stats()`
gives `total_latency_ns` and counts — divide for per-RPC host cost.
(Add `trace_printk` in `gsp_emu_dispatch_rpc` entry/exit for distribution;
read via `/sys/kernel/debug/tracing/trace`.)
2. **MSI-X latency**: timestamp (guest TSC) at RPC submit → in the guest
driver's interrupt handler. Compare with the host eventfd_signal timestamp.
3. **BAR0 shadow read latency**: guest loops reading a shadow register;
expect ~memory speed (uncached, so ~100-300 ns) — anything ≥ 1 µs means
you're trapping somewhere you shouldn't.
4. **Doorbell polling cost**: host `top`/`perf` on `gsp_emu_N` — the adaptive
poll (busy → 50-100 µs sleep) should sit at ~0% CPU when idle, and react
within one sleep quantum under load. Tune `GSP_EMU_POLL_*` only with data.
## 4. Workload benchmarks
Runner: `tools/benchmark_infrastructure.sh` (extend it; keep the A/B pattern).
In-guest execution goes through the harness (`vgpu_vm.py exec`) or the guest
test agent (`tools/guest_agent/`).
| Suite | What | Pass criterion |
|-------|------|----------------|
| `cuda-samples vectorAdd` | smoke + element-wise throughput | correct result; ≥ 95% B/A |
| `simpleCUBLAS` | SGEMM TFLOPS | ≥ 95% B/A |
| `nvbandwidth` (if available) | H2D/D2H/device copy GB/s | report; DMA path validation |
| `glxgears -info` / a real GL app | frame rate | ≥ 90% B/A |
| `nvenc`/`nvdec` sample | transcode fps | functional (perf later) |
| PyTorch CUDA op (matmul loop) | end-to-end framework | ≥ 90% B/A |
Always log: guest/host driver versions, GPU clocks (`nvidia-smi -q -d CLOCK`
both sides — throttling skews everything), and the command lines.
## 5. Where the overhead lives (profile in this order)
1. **RPC dispatch** — the single biggest lever. Everything the guest driver
does funnels through the emulation thread.
2. **Doorbell polling cadence** — too slow = latency; too hot = host CPU burn.
3. **eventfd/MSI-X delivery** — measure, don't assume.
4. **BAR0 uncached semantics** — shadow is UC for the guest; that's
MMIO-correct but slow for large reads. If the driver bulk-reads shadow,
consider caching-safe sub-regions.
5. **Memory model gap** (ALLOC host-pages vs BAR1 direct VRAM) — see
`docs/GSP_EMULATION_ARCHITECTURE.md` §3. Until unified, DMA-heavy
workloads are not representative.
## 6. Optimizing safely
- Change one thing, re-run the A/B pair, keep the numbers with the commit.
- Never optimize off bare-metal-only data: the goal is B/A, not A.
- If a change makes B faster but breaks the §5 validation checklist in
`docs/DEBUGGING.md`, it doesn't count.
- Record results in `STATE.md` (date, versions, B/A ratios) so regressions
are visible across commits.
## 7. Reporting template
```
Date / commit / versions (guest=host=firmware=emulation):
Machine / GPU / clocks locked?:
Test, unit, A (bare), B (vGPU), B/A %:
Notes (what changed vs last run):
```
|