summaryrefslogtreecommitdiffstats
path: root/docs/PERFORMANCE.md
blob: 6077920af49c6827d1ea49c0f630c976476938f0 (plain)
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
# Open-IOV Performance Testing & A/B Methodology

> 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):
```