Purpose: Document performance baselines, benchmarks, and optimization targets for the Social Navigation Benchmark platform.
Issue #2214 compared the pre-wave baseline
5eead086fceac0bbdd00bb10ec133a612dfc5b25 with current main
5bd87d58d4869e8420943301e45de1a8dc6513a1 after the simulator hot-path
allocation and snapshot-reuse PRs. The local smoke surfaces did not show a broad
end-to-end speedup: reset throughput was 4.8% lower and one-candidate policy
smoke runtime was 2.0% higher on the current revision, while environment
creation was 1.8% faster but still warning-classified.
Treat this as diagnostic, startup-dominated evidence rather than a performance
claim. The stronger recent wall-clock signal remains the issue #2172 worker
scaling diagnostic, where the 18-job h80 nominal-sanity run improved by 1.793x
from one to two workers. See
docs/context/issue_2214_hot_path_synthesis.md for the derived evidence and
follow-up direction.
Environment Creation:
- Target: < 2.0 seconds
- Measured: ~1.16 seconds
- Status: ✅ PASS
Environment Reset:
- Target: > 1 reset/second
- Measured: ~1,745 resets/second (0.6ms/reset)
- Status: ✅ PASS
From dev_guide.md expected ranges:
- Environment creation: < 1 second
- Model loading: 1–5 seconds
- Simulation performance: ~22 steps/second (~45ms/step)
- Build time: 2–3 minutes (first time)
- Test suite: 2–3 minutes (≈170 tests)
Location: scripts/validation/performance_smoke_test.py
Validation Criteria:
- Environment creation < 3.0s (allows headroom)
- Reset performance > 0.5 resets/sec (minimum acceptable)
- Consistent performance across multiple runs
- Advisory startup/steady attribution fields:
step_loop.first_step_secstep_loop.step_loop_secstep_loop.steady_step_loop_secstep_loop.steps_per_secstep_loop.steady_steps_per_secstep_loop.measurement_modestep_loop.warmup_excludedstep_loop.warmup_first_step_sec,step_loop.warmup_step_loop_sec, andstep_loop.warmup_steps_per_secwhen explicit warmup attribution is requested
Large-crowd profiling now emits an additive step_profile contract block:
step_profile.advisory(alwaystruefor this profile)step_profile.gating("non-gating")step_profile.scenario_id,step_profile.scenario_name,step_profile.scenario_pathstep_profile.densityandstep_profile.density_advisorywhen present in scenario metadatastep_profile.step_samples,step_profile.first_step_secstep_profile.step_loop_secstep_profile.steady_step_loop_sec,step_profile.steady_steps_per_secstep_profile.steps_per_secstep_profile.measurement_mode,step_profile.warmup_excludedstep_profile.warmup_first_step_sec,step_profile.warmup_step_loop_sec, andstep_profile.warmup_steps_per_secwhen explicit warmup attribution is requestedstep_profile.pedestrian_count(when observed after reset/step). The fast path tracked in #3025 and PR #3114 reuses the first measured step's pedestrian occupancy snapshot inscripts/validation/performance_smoke_test.py, with contract coverage intests/perf/test_large_crowd_step_profile_contract.py, so the profiler avoids an extra env create/reset/step pass solely for count collection.
These fields are advisory and are for diagnostic reproducibility; they are not benchmark gates.
The step-loop fields help classify local slowdowns but do not add hard smoke-test gates by
default. Use --step-samples to tune the advisory sample count for local diagnosis. By default,
first_step_sec, step_loop_sec, and steps_per_sec measure the cold first step after reset.
Use --warmup-steps only when you intentionally want warm-start attribution; warm-start output is
flagged with measurement_mode and warmup_excluded so it cannot be mistaken for cold-start
performance evidence.
Usage:
# Run performance smoke test
DISPLAY= MPLBACKEND=Agg SDL_VIDEODRIVER=dummy \
uv run python scripts/validation/performance_smoke_test.py --step-samples 10
# Results saved to: output/benchmarks/performance_smoke_test.json
# High-density advisory snapshot for [issue #3025](https://github.com/ll7/robot_sf_ll7/issues/3025)
DISPLAY= MPLBACKEND=Agg SDL_VIDEODRIVER=dummy \
uv run python scripts/validation/performance_smoke_test.py \
--scenario configs/scenarios/archetypes/classic_group_crossing.yaml \
--scenario-name classic_group_crossing_high \
--step-samples 3 --num-resets 1 \
--json-output output/benchmarks/perf/issue_3025_smoke.json
# Explicit warm-start attribution for startup/JIT diagnosis only; not a runtime speedup claim
DISPLAY= MPLBACKEND=Agg SDL_VIDEODRIVER=dummy \
uv run python scripts/validation/performance_smoke_test.py \
--scenario configs/scenarios/archetypes/classic_group_crossing.yaml \
--scenario-name classic_group_crossing_high \
--step-samples 3 --warmup-steps 1 --num-resets 1 \
--json-output output/benchmarks/perf/issue_3025_warmup_attribution.jsonLocation: robot_sf/benchmark/perf_cold_warm.py
This suite separates cold-start and steady-state runs and compares medians against
a tracked baseline snapshot (configs/benchmarks/perf_baseline_classic_cold_warm_v1.json).
DISPLAY= MPLBACKEND=Agg SDL_VIDEODRIVER=dummy \
uv run python -m robot_sf.benchmark.perf_cold_warm \
--scenario-config configs/scenarios/archetypes/classic_cross_trap.yaml \
--scenario-name classic_cross_trap_low \
--episode-steps 64 \
--cold-runs 1 \
--warm-runs 2 \
--baseline configs/benchmarks/perf_baseline_classic_cold_warm_v1.json \
--output-json output/benchmarks/perf/cold_warm_local.json \
--output-markdown output/benchmarks/perf/cold_warm_local.mdThe report includes:
env_create_secfirst_step_secepisode_secsteps_per_sec
and classifies regressions as startup-dominated vs steady-state-dominated.
CI integration:
- PR smoke:
.github/workflows/ci.yml(Cold/warm perf regression smoke)- advisory on PRs with a conservative profile:
- baseline:
configs/benchmarks/perf_baseline_classic_cold_warm_v1.json - thresholds:
max_slowdown_pct=0.75,max_throughput_drop_pct=0.60 - absolute deltas:
min_seconds_delta=0.20,min_throughput_delta=1.00
- baseline:
- enforced on
main/workflow_dispatchwith default profile:- baseline:
configs/benchmarks/perf_baseline_classic_cold_warm_v1.json - thresholds:
max_slowdown_pct=0.60,max_throughput_drop_pct=0.50 - absolute deltas:
min_seconds_delta=0.15,min_throughput_delta=0.75
- baseline:
- advisory on PRs with a conservative profile:
- Nightly broader checks:
.github/workflows/perf-nightly.yml
Location: robot_sf/benchmark/perf_trend.py
This suite executes a stable scenario matrix and emits a single schema-versioned report:
- matrix config:
configs/benchmarks/perf_trend_matrix_classic_v1.yaml - report schema:
benchmark-perf-trend-report.v1 - fixed KPI set per phase:
env_create_sec,first_step_sec,episode_sec,steps_per_sec
Local run:
DISPLAY= MPLBACKEND=Agg SDL_VIDEODRIVER=dummy \
uv run python -m robot_sf.benchmark.perf_trend \
--matrix configs/benchmarks/perf_trend_matrix_classic_v1.yaml \
--history-glob 'output/benchmarks/perf/trend/history/*.json' \
--output-json output/benchmarks/perf/trend/latest.json \
--output-markdown output/benchmarks/perf/trend/latest.mdNightly behavior:
- runs the matrix in
.github/workflows/perf-nightly.yml - restores prior trend reports from cache when available
- compares current run against recent history medians
- stores latest report back into history cache path
- uploads all perf artifacts from
output/benchmarks/perf/
Regression diagnostics include whether degradation is startup-dominated
(env_create_sec/first_step_sec) or steady-state-dominated (episode_sec/steps_per_sec).
If a warm episode_sec or steps_per_sec history regression is explained by same-phase
first_step_sec overhead, the history comparison reports a warning and lists the startup-coupled
steady metrics instead of failing the nightly gate as a steady-state regression.
High-density gate policy:
classic_cross_trap_lowandclassic_cross_trap_mediumare blocking in the matrix.classic_cross_trap_lowuses three warm samples so one transient first-step outlier cannot control an even-sample median.classic_cross_trap_highremains advisory (enforce_regression_gate: false) until enough nightly history exists to distinguish stable degradation from hardware/runtime noise.- Issue #513 local calibration on 2026-05-02 found no local history reports under
output/benchmarks/perf/trend/history/*.json; the high-density run stayed startup-onlywarnwith warm throughput above the stored medium baseline. Do not promote this scenario to blocking from a one-off local run.
Use the committed snapshot as the initial reference point. If hardware/runtime
changes make it stale, regenerate values from nightly artifacts and update
configs/benchmarks/perf_baseline_classic_cold_warm_v1.json and
configs/benchmarks/perf_baseline_classic_cold_warm_medium_v1.json in a dedicated PR.
Location: tests/perf/test_simulation_speed_perf.py
The throughput guard supports environment-specific calibration:
ROBOT_SF_SIM_STEPS_SOFT(default2.0)ROBOT_SF_SIM_STEPS_HARD(default0.5)
A measured breach fails on every lane; skips are reserved for missing prerequisites, so a regression can never pass silently (issue #8377).
On shared or heterogeneous cluster nodes, set tuned thresholds per hardware profile.
Location: robot_sf/benchmark/runner.py
Parallel Processing:
- Multi-worker support for episode generation
- Process-based parallelism with resume capabilities
- Manifest-driven episode deduplication
Expected Throughput:
- Single worker: ~1-2 episodes/second (varies by scenario complexity)
- 4 workers: ~4-6 episodes/second (near-linear scaling)
- Resume overhead: minimal (manifest lookup < 1ms per episode)
- Environment Initialization: Creation + first reset time
- Episode Generation: Episodes per second in benchmark runs
- Memory Usage: Peak RSS during multi-worker runs
- I/O Performance: JSONL write throughput for large episode batches
- Performance degradation > 50% should trigger investigation
- Compare against
configs/benchmarks/perf_baseline_classic_cold_warm_v1.json - Use
output/benchmarks/perf/*.jsonand nightly artifacts for trend monitoring - JSONL append throughput uses a lightweight synthetic guard in
tests/training/test_runtime_helpers.py. TuneROBOT_SF_JSONL_APPEND_RECORDSandROBOT_SF_JSONL_APPEND_SOFT_RECORDS_PER_SECfor local diagnosis; keepROBOT_SF_PERF_ENFORCE=0on heterogeneous machines unless a hardware-specific threshold is intentional.
Location: robot_sf/benchmark/perf_scenario_cache.py
Measures LRU cache hit/miss/eviction rates during scenario-switching training, isolating SVG parsing overhead from per-episode env-creation cost.
DISPLAY= MPLBACKEND=Agg SDL_VIDEODRIVER=dummy \
uv run python -m robot_sf.benchmark.perf_scenario_cache \
--scenario-config configs/scenarios/classic_interactions.yaml \
--repetitions 3 \
--output-json output/benchmarks/perf/scenario_cache_profile.json \
--output-markdown output/benchmarks/perf/scenario_cache_profile.mdFindings (2026-04-13, issue #806):
classic_interactions.yamlcontains 22 scenarios across 12 unique maps.- The previous
_load_map_definitionLRU cache hadmaxsize=8, guaranteeing cache eviction for at least 4 maps on every training run touching all scenarios. - Fix:
maxsizeraised to 256 — covers all current maps (75 SVGs repo-wide) and leaves room for growth without evictions. - The instrumentation helper
map_cache_info()(exported fromrobot_sf.training.scenario_loader) lets you verify hit/miss counts at runtime.
- Cold backend/map startup: first-process map parsing and FastPysf/JIT setup dominate
headless cold starts. Pygame/SDL is no longer imported by
make_robot_env(debug=False)after #1290; it is still initialized on explicit rendering, image-observation, occupancy-grid rendering, or video paths. - FastPysf compilation: JIT overhead on first use
- Large episode JSON: Serialization cost grows with trajectory length
- File I/O: JSONL append becomes slow with very large files
- Map definition cache eviction (fixed in #806): prior
maxsize=8caused repeated SVG parsing during multi-scenario SAC training runs with >8 unique maps.
- Environment Reuse: Keep environments alive across episodes when possible
- Batch Processing: Group similar scenarios to reduce setup overhead
- Memory Management: Monitor RSS growth in long-running processes
- Disk I/O: Use SSD storage for results directories
- CPU: M-series Apple Silicon (ARM64)
- Memory: 16+ GB recommended for multi-worker runs
- Storage: SSD for the repository's
output/artifact directory - Display: Headless mode using SDL_VIDEODRIVER=dummy
- Single-core: Environment creation and reset dominated by setup costs
- Multi-core: Near-linear scaling up to 4-8 workers depending on scenario complexity
- Memory: ~500MB per worker process typical
- ✅
test_basic_environment.sh: Environment creation smoke test - ✅
test_model_prediction.sh: Model loading and inference test - ✅
performance_smoke_test.py: Performance baseline measurement - ⏳
test_complete_simulation.sh: Full episode simulation (may timeout on complex scenarios)
# Run all validation scripts
./scripts/validation/test_basic_environment.sh
./scripts/validation/test_model_prediction.sh
DISPLAY= MPLBACKEND=Agg SDL_VIDEODRIVER=dummy \
uv run python scripts/validation/performance_smoke_test.py- Environment creation: 1.16s (target: < 2.0s)
- Environment reset: 1,745 resets/sec (target: > 1/sec)
- Platform: macOS ARM64, Python 3.13, headless mode
- Status: All performance targets met
Monitoring Schedule: Monthly performance validation recommended
Next Review: 2025-02-19
Contact: Reference dev team for performance regression issues