Skip to content

Commit a5879b9

Browse files
committed
plumb more memory stats into artifacts, rename gpu_diag -> runtime_resources
1 parent de98983 commit a5879b9

11 files changed

Lines changed: 115 additions & 50 deletions

tools/perf_smoke_test/aggregate.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,16 +126,22 @@ def _build_summary_table(rows: list[tuple]) -> str:
126126
"|---|---|---|---:|---:|---:|---:|---:|---|---|---|---|---|---|",
127127
]
128128
for result, bench_result in rows:
129-
gpu_diag = bench_result.gpu_diag or {}
129+
runtime_resources = bench_result.runtime_resources or {}
130130
launch_config = bench_result.launch_config or {}
131-
gpu_name = gpu_diag.get("gpu_name") or launch_config.get("gpu_model_raw") or launch_config.get("gpu_model", "")
131+
gpu_name = (
132+
runtime_resources.get("gpu_name")
133+
or launch_config.get("gpu_model_raw")
134+
or launch_config.get("gpu_model", "")
135+
)
132136
provenance = bench_result.provenance or {}
133137
software = provenance.get("software") or {}
134138
runtime = ", ".join(
135139
part
136140
for part in (
137-
f"cuda={gpu_diag.get('cuda_version')}" if gpu_diag.get("cuda_version") else "",
138-
f"driver={gpu_diag.get('nvidia_driver_version')}" if gpu_diag.get("nvidia_driver_version") else "",
141+
f"cuda={runtime_resources.get('cuda_version')}" if runtime_resources.get("cuda_version") else "",
142+
f"driver={runtime_resources.get('nvidia_driver_version')}"
143+
if runtime_resources.get("nvidia_driver_version")
144+
else "",
139145
f"warp={software.get('warp')}" if software.get("warp") else "",
140146
)
141147
if part

tools/perf_smoke_test/baseline_manager.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ def make_sample_metadata(
311311
provenance = bench_result.get("provenance") or {}
312312
git_info = provenance.get("git") or {}
313313
software = provenance.get("software") or {}
314-
gpu_diag = bench_result.get("gpu_diag") or {}
314+
runtime_resources = bench_result.get("runtime_resources") or {}
315315
metadata = {
316316
"schema_version": 1,
317317
"fps": float(fps),
@@ -345,8 +345,8 @@ def make_sample_metadata(
345345
"runtime": {
346346
"isaacsim": software.get("isaacsim"),
347347
"warp": software.get("warp"),
348-
"cuda": (gpu_diag or {}).get("cuda_version"),
349-
"driver": (gpu_diag or {}).get("nvidia_driver_version"),
348+
"cuda": (runtime_resources or {}).get("cuda_version"),
349+
"driver": (runtime_resources or {}).get("nvidia_driver_version"),
350350
},
351351
}
352352
metadata["sample_id"] = _stable_sample_id(metadata)

tools/perf_smoke_test/benchmark_result_adapter.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
refactor Part 1, PR #6197) serialized by
1111
:func:`~isaaclab.test.benchmark.serialize.write_bundle_file`. This module is the
1212
single point that reads that JSON and projects it into the flat
13-
``provenance`` / ``gpu_diag`` / fps / ``benchmark_info`` shapes the gate's
13+
``provenance`` / ``runtime_resources`` / fps / ``benchmark_info`` shapes the gate's
1414
:mod:`oracle` and :mod:`build_bench_result` consume, replacing the legacy
1515
phase-array parsing.
1616
@@ -198,11 +198,42 @@ def provenance(bundle: dict) -> dict:
198198
return {"hardware": hardware, "software": software, "git": git}
199199

200200

201-
def gpu_diag(bundle: dict) -> dict:
202-
"""Return the human/debug GPU diagnostics block (non-gating publish info)."""
201+
def _gb_to_mb(value: Any) -> float | None:
202+
"""Return ``value`` GB converted to MB (2 dp), or ``None`` if not numeric."""
203+
return round(float(value) * 1024, 2) if isinstance(value, (int, float)) else None
204+
205+
206+
def _pct(value: Any) -> float | None:
207+
"""Return ``value`` as a utilisation percent (2 dp), or ``None`` if not numeric."""
208+
return round(float(value), 2) if isinstance(value, (int, float)) else None
209+
210+
211+
def runtime_resources(bundle: dict) -> dict:
212+
"""Return the GPU-diagnostics + resource-utilisation block (non-gating publish info).
213+
214+
Combines GPU identity (name / total memory / CUDA / driver) with the run's
215+
measured resource utilisation from the bundle's ``resources`` section: VRAM
216+
(mean + peak), system RAM (mean + peak), and GPU/CPU utilisation (mean). Every
217+
field here is informational — it is published for humans but never feeds the
218+
gate verdict or the ``runtime_contract_hash``.
219+
220+
Memory is reported in MB (the schema stores GB); utilisation in percent. Two
221+
semantic caveats worth remembering when reading these values:
222+
223+
* ``gpu_mem_*`` is **device-wide** VRAM (``nvidia-smi memory.used`` includes
224+
any other process on the GPU) — accurate on a 1-benchmark-per-GPU runner.
225+
* ``system_ram_*`` is the benchmark **process** resident set size (psutil
226+
``memory_info().rss``), not whole-host RAM and excluding child processes.
227+
228+
Absent/malformed sub-sections drop their fields (``None`` filtered out) so a
229+
partial bundle degrades gracefully instead of crashing the projection.
230+
"""
203231
gpu = _current_gpu(bundle)
204-
gpu_mem = _as_dict(_as_dict(bundle.get("resources")).get("gpu_mem_gb"))
205-
mem_used_gb = gpu_mem.get("mean")
232+
resources = _as_dict(bundle.get("resources"))
233+
gpu_mem = _as_dict(resources.get("gpu_mem_gb"))
234+
ram = _as_dict(resources.get("ram_gb"))
235+
gpu_util = _as_dict(resources.get("gpu_util_pct"))
236+
cpu_util = _as_dict(resources.get("cpu_util_pct"))
206237
# schema-v1 Hardware has no CUDA-runtime field; use the CUDA bindings version
207238
# (Versions.cuda_bindings) as the closest available proxy for display.
208239
cuda_version = _as_dict(bundle.get("versions")).get("cuda_bindings")
@@ -211,7 +242,12 @@ def gpu_diag(bundle: dict) -> dict:
211242
"gpu_total_memory_gb": gpu.get("mem_gb"),
212243
"cuda_version": cuda_version,
213244
"nvidia_driver_version": gpu_driver_version(),
214-
"gpu_mem_used_mb": round(float(mem_used_gb) * 1024, 2) if isinstance(mem_used_gb, (int, float)) else None,
245+
"gpu_mem_used_mb": _gb_to_mb(gpu_mem.get("mean")),
246+
"gpu_mem_peak_mb": _gb_to_mb(gpu_mem.get("peak")),
247+
"gpu_util_pct": _pct(gpu_util.get("mean")),
248+
"system_ram_used_mb": _gb_to_mb(ram.get("mean")),
249+
"system_ram_peak_mb": _gb_to_mb(ram.get("peak")),
250+
"cpu_util_pct": _pct(cpu_util.get("mean")),
215251
}
216252
return {k: v for k, v in diag.items() if v is not None}
217253

@@ -261,5 +297,5 @@ def project_runtime(bundle: dict) -> RuntimeSample | None:
261297
render_backend=info.get("render_backend"),
262298
presets=info.get("presets") or [],
263299
provenance=provenance(bundle),
264-
gpu_diag=gpu_diag(bundle),
300+
runtime_resources=runtime_resources(bundle),
265301
)

tools/perf_smoke_test/build_bench_result.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,13 +236,13 @@ def main() -> int:
236236
observed_backend = backend_identity_from_benchmark_info(benchmark_info)
237237
runtime_contract, runtime_contract_hash = build_runtime_contract(
238238
provenance=sample.provenance,
239-
gpu_diag=sample.gpu_diag,
239+
runtime_resources=sample.runtime_resources,
240240
backend=expected_backend,
241241
policy=runtime_policy,
242242
)
243243
runtime_info = build_runtime_publish_info(
244244
provenance=sample.provenance,
245-
gpu_diag=sample.gpu_diag,
245+
runtime_resources=sample.runtime_resources,
246246
policy=runtime_policy,
247247
)
248248
config_mismatch = _config_drift(benchmark_info, launch_config)
@@ -278,7 +278,7 @@ def main() -> int:
278278
runtime_contract=runtime_contract,
279279
runtime_contract_hash=runtime_contract_hash,
280280
runtime_info=runtime_info,
281-
gpu_diag=(sample.gpu_diag or None) if sample else None,
281+
runtime_resources=(sample.runtime_resources or None) if sample else None,
282282
provenance=sample.provenance if sample else None,
283283
launch_config=launch_config,
284284
launch_config_hash=launch_config.get("launch_config_hash"),

tools/perf_smoke_test/contracts.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class RuntimeSample:
3030
"""Adapter projection of a schema-v1 ``RuntimeBundle`` into the gate's fields.
3131
3232
Aggregates (fps/startup) and run identity are typed; ``provenance`` and
33-
``gpu_diag`` remain dicts (open provenance payloads the gate carries through).
33+
``runtime_resources`` remain dicts (open provenance payloads the gate carries through).
3434
"""
3535

3636
fps_mean: float | None = None
@@ -48,7 +48,7 @@ class RuntimeSample:
4848
render_backend: str | None = None
4949
presets: list[str] = field(default_factory=list)
5050
provenance: dict[str, Any] = field(default_factory=dict)
51-
gpu_diag: dict[str, Any] = field(default_factory=dict)
51+
runtime_resources: dict[str, Any] = field(default_factory=dict)
5252

5353
def benchmark_info(self) -> dict[str, Any]:
5454
"""Return the run's self-reported identity as a dict.
@@ -108,7 +108,7 @@ class BenchResult:
108108
runtime_contract: dict[str, Any] | None = None
109109
runtime_contract_hash: str | None = None
110110
runtime_info: dict[str, Any] | None = None
111-
gpu_diag: dict[str, Any] | None = None
111+
runtime_resources: dict[str, Any] | None = None
112112
provenance: dict[str, Any] | None = None
113113
launch_config: dict[str, Any] = field(default_factory=dict)
114114
launch_config_hash: str | None = None

tools/perf_smoke_test/docs/example_smoke_test_result.json

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,21 @@
8484
"publish_only": {
8585
"hardware.gpu_compute_capability": "12.0",
8686
"hardware.gpu_total_memory_gb": 94.97,
87-
"gpu_diag.cuda_version": "12.8",
88-
"gpu_diag.nvidia_driver_version": "595.71.05"
87+
"runtime_resources.cuda_version": "12.8",
88+
"runtime_resources.nvidia_driver_version": "595.71.05"
8989
}
9090
},
91-
"gpu_diag": {
91+
"runtime_resources": {
9292
"gpu_name": "NVIDIA RTX PRO 6000 Blackwell Server Edition",
9393
"gpu_total_memory_gb": 94.97,
9494
"cuda_version": "12.8",
9595
"nvidia_driver_version": "595.71.05",
96-
"gpu_mem_used_mb": 798.72
96+
"gpu_mem_used_mb": 798.72,
97+
"gpu_mem_peak_mb": 851.97,
98+
"gpu_util_pct": 96.4,
99+
"system_ram_used_mb": 11894.27,
100+
"system_ram_peak_mb": 12408.83,
101+
"cpu_util_pct": 33.7
97102
},
98103
"provenance": { -> Audit context: records the machine/software we actually ran on. Useful for debugging hardware or environment differences.
99104
"hardware": {

tools/perf_smoke_test/docs/module-interfaces.md

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ Examples: `NVIDIA L40S -> l40s`, `RTX6000 -> rtx_6000`, `NVIDIA GeForce RTX 5090
3434
Builds the runtime compatibility contract used for baseline matching. The matching code only sees `runtime_contract_hash`; package/version field selection stays in `gate_config.py`.
3535

3636
```python
37-
def build_runtime_contract(*, provenance: dict | None, gpu_diag: dict | None, backend: BackendIdentity, policy: Mapping[str, Any]) -> tuple[dict, str]
38-
def build_runtime_publish_info(*, provenance: dict | None, gpu_diag: dict | None, policy: Mapping[str, Any]) -> dict
37+
def build_runtime_contract(*, provenance: dict | None, runtime_resources: dict | None, backend: BackendIdentity, policy: Mapping[str, Any]) -> tuple[dict, str]
38+
def build_runtime_publish_info(*, provenance: dict | None, runtime_resources: dict | None, policy: Mapping[str, Any]) -> dict
3939
```
4040

4141
Default compatibility fields smoke test on top-level active-path packages: IsaacSim, IsaacLab, Torch, Warp, the active physics package (`isaaclab_physx` or `isaaclab_newton`/`newton`), and `isaaclab_ov` for renderer backends. CUDA version, NVIDIA driver, GPU memory, and compute capability are published for humans but do not affect the compatibility hash by default.
@@ -124,7 +124,7 @@ class OracleResult:
124124
measured_fps: float | None # raw_fps_mean (steady-state mean); None on HARD_FAILURE
125125
baseline_fps: float | None # baseline.median_fps; None if no baseline
126126
regression_pct: float | None # ((measured - baseline) / baseline) × 100; None if no baseline
127-
gpu_mem_used_mb: float | None # From bench_result.gpu_diag [informational]
127+
gpu_mem_used_mb: float | None # From bench_result.runtime_resources [informational]
128128
startup_time_s: float | None # From bench_result [informational]
129129
wall_time_s: float | None # From bench_result [informational]
130130
was_retried: bool # Whether Phase 1 succeeded only after a retry
@@ -550,12 +550,17 @@ Each entry in `backends` becomes one `TaskConfig`. `backend_key = physics` when
550550
"raw_fps_std": 9800.0,
551551
"raw_fps_min": 1632000.0,
552552
"raw_fps_max": 1680000.0,
553-
"gpu_diag": {
553+
"runtime_resources": {
554554
"gpu_name": "NVIDIA L40S",
555555
"gpu_total_memory_gb": 45.62,
556556
"cuda_version": "12.1",
557557
"nvidia_driver_version": "550.54.15",
558-
"gpu_mem_used_mb": 18432.0
558+
"gpu_mem_used_mb": 18432.0,
559+
"gpu_mem_peak_mb": 19456.0,
560+
"gpu_util_pct": 95.0,
561+
"system_ram_used_mb": 30720.0,
562+
"system_ram_peak_mb": 31744.0,
563+
"cpu_util_pct": 40.0
559564
},
560565
"provenance": {
561566
"hardware": {
@@ -604,7 +609,7 @@ Each entry in `backends` becomes one `TaskConfig`. `backend_key = physics` when
604609
},
605610
"runtime_info": {
606611
"software": {"warp": "1.6.0"},
607-
"publish_only": {"gpu_diag.cuda_version": "12.1"}
612+
"publish_only": {"runtime_resources.cuda_version": "12.1"}
608613
},
609614
"task_config_snapshot": {
610615
"task_id": "Isaac-Velocity-Flat-G1-v0",
@@ -643,16 +648,18 @@ All fields below are projected from the `RuntimeBundle` by
643648
| `raw_fps_min` | `run.num_envs / runtime.iteration_time_s.peak` | Recovered from the slowest steady-state step |
644649
| `raw_fps_median`, `raw_fps_p5`, `raw_fps_p95`, `p99_over_median`, `outlier_count` | — (removed) | Not emitted: the schema keeps only aggregates (these needed the raw series), and they were never gating. `raw_fps_std`/`raw_fps_min` cover the tail |
645650
| `startup_time_s` | sum of `runtime.startup_time_s.*` phases | app_launch + env_creation + first_step + python_imports |
646-
| `gpu_diag.gpu_mem_used_mb` | `resources.gpu_mem_gb.mean` | Converted from GB |
647-
| `gpu_diag.gpu_name`, `.gpu_total_memory_gb` | `hardware.gpu_devices[0].{name,mem_gb}` | |
648-
| `gpu_diag.cuda_version` | `versions.cuda_bindings` | CUDA bindings version used as a display proxy (schema-v1 has no CUDA-runtime field) |
649-
| `gpu_diag.nvidia_driver_version` | `nvidia-smi` subprocess at post-processing time | `null` if nvidia-smi unavailable |
651+
| `runtime_resources.gpu_mem_used_mb`, `.gpu_mem_peak_mb` | `resources.gpu_mem_gb.{mean,peak}` | Device-wide VRAM (nvidia-smi); converted GB→MB |
652+
| `runtime_resources.system_ram_used_mb`, `.system_ram_peak_mb` | `resources.ram_gb.{mean,peak}` | Benchmark **process** RSS (psutil), not whole-host RAM; converted GB→MB |
653+
| `runtime_resources.gpu_util_pct`, `.cpu_util_pct` | `resources.{gpu_util_pct,cpu_util_pct}.mean` | Mean utilisation [%] |
654+
| `runtime_resources.gpu_name`, `.gpu_total_memory_gb` | `hardware.gpu_devices[0].{name,mem_gb}` | |
655+
| `runtime_resources.cuda_version` | `versions.cuda_bindings` | CUDA bindings version used as a display proxy (schema-v1 has no CUDA-runtime field) |
656+
| `runtime_resources.nvidia_driver_version` | `nvidia-smi` subprocess at post-processing time | `null` if nvidia-smi unavailable |
650657
| `provenance.hardware` | `hardware` snapshot | CPU, GPU, RAM identity |
651658
| `provenance.software` | `versions` map (verbatim, minus `git_*` keys) | Package versions |
652659
| `provenance.git` | `versions.git_commit`/`git_branch`/`git_dirty` | commit, branch, dirty flag |
653660

654661
Key fields the oracle reads: `perf_smoke_test_info_present`, `raw_fps_mean`,
655-
`failure_phase`, `config_mismatch`, `was_retried`, `gpu_diag.gpu_mem_used_mb`,
662+
`failure_phase`, `config_mismatch`, `was_retried`, `runtime_resources.gpu_mem_used_mb`,
656663
`startup_time_s`, `wall_time_s`.
657664

658665
`raw_fps_mean` is the steady-state mean the oracle gates on; `raw_fps_std`/`_min`/`_max`
@@ -720,7 +727,7 @@ objects or prefixed measurement names. Abbreviated (many `versions.*` fields omi
720727
`benchmark_result_adapter` reads this bundle: the gate metric `raw_fps_mean` comes from
721728
`runtime.total_fps.mean` (steady-state — warmup was excluded at the source by
722729
`perf_runtime.py --warmup_frames`), `raw_fps_min` from `run.num_envs /
723-
runtime.iteration_time_s.peak`, and provenance/`gpu_diag` from `versions`, `hardware`,
730+
runtime.iteration_time_s.peak`, and provenance/`runtime_resources` from `versions`, `hardware`,
724731
and `resources`. The bundle stores only aggregates, so the raw per-frame FPS series (and
725732
hence percentiles) is not available.
726733

tools/perf_smoke_test/docs/system-design.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ IsaacLab/
126126
│ Isaac Lab benchmark core; writes a schema-v1
127127
│ RuntimeBundle (benchmark_runtime_*.json)
128128
├── benchmark_result_adapter.py RuntimeBundle → typed RuntimeSample projection
129-
│ (FPS aggregates, provenance, gpu_diag)
129+
│ (FPS aggregates, provenance, runtime_resources)
130130
├── contracts.py typed gate artifacts (schema v1): RuntimeSample
131131
│ + BenchResult (perf_smoke_test_result.json)
132132
├── build_bench_result.py Phase 2: copies the runtime bundle, projects it
@@ -403,12 +403,17 @@ for local testing.
403403
"raw_fps_std": 9800.0,
404404
"raw_fps_min": 1632000.0,
405405
"raw_fps_max": 1680000.0,
406-
"gpu_diag": {
406+
"runtime_resources": {
407407
"gpu_name": "NVIDIA L40S",
408408
"gpu_total_memory_gb": 45.62,
409409
"cuda_version": "12.1",
410410
"nvidia_driver_version": "550.54.15",
411-
"gpu_mem_used_mb": 18432.0
411+
"gpu_mem_used_mb": 18432.0,
412+
"gpu_mem_peak_mb": 19456.0,
413+
"gpu_util_pct": 95.0,
414+
"system_ram_used_mb": 30720.0,
415+
"system_ram_peak_mb": 31744.0,
416+
"cpu_util_pct": 40.0
412417
},
413418
"provenance": {
414419
"hardware": { "cpu_name": "...", "gpu_name": "NVIDIA L40S", "cuda_version": "12.1", "..." : "..." },
@@ -489,8 +494,14 @@ the true run-to-run variance distribution.
489494

490495
### 13.3 Memory tracking scope
491496

492-
**Current state:** `gpu_diag.gpu_mem_used_mb` is captured and surfaced in `OracleResult`
493-
as an informational field. No memory regression threshold yet.
497+
**Current state:** the `runtime_resources` block carries an informational (non-gating)
498+
snapshot of the run's resource utilisation, projected from the bundle's `resources`
499+
section: VRAM (`gpu_mem_used_mb` mean + `gpu_mem_peak_mb`), process-RSS system RAM
500+
(`system_ram_used_mb` mean + `system_ram_peak_mb`), and GPU/CPU utilisation
501+
(`gpu_util_pct`, `cpu_util_pct`). `gpu_mem_used_mb` is additionally surfaced on
502+
`OracleResult`. None of these gate the verdict or feed `runtime_contract_hash` — no
503+
memory regression threshold yet. Note `gpu_mem_*` is device-wide (nvidia-smi) while
504+
`system_ram_*` is the benchmark process's RSS (psutil), not whole-host RAM.
494505

495506
---
496507

tools/perf_smoke_test/gate_config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@
5252
"publish_only": [
5353
"hardware.gpu_compute_capability",
5454
"hardware.gpu_total_memory_gb",
55-
"gpu_diag.cuda_version",
56-
"gpu_diag.nvidia_driver_version",
55+
"runtime_resources.cuda_version",
56+
"runtime_resources.nvidia_driver_version",
5757
],
5858
}
5959

tools/perf_smoke_test/oracle.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def compare(
143143
was_retried: bool = bool(bench_result.was_retried)
144144
startup_time_s: float | None = bench_result.startup_time_s
145145
wall_time_s: float | None = bench_result.wall_time_s
146-
gpu_mem_used_mb: float | None = (bench_result.gpu_diag or {}).get("gpu_mem_used_mb")
146+
gpu_mem_used_mb: float | None = (bench_result.runtime_resources or {}).get("gpu_mem_used_mb")
147147

148148
config_mismatch = bench_result.config_mismatch
149149
if config_mismatch or failure_phase == FailurePhase.CONFIG_MISMATCH.value:

0 commit comments

Comments
 (0)