Profiling Solvers#
All solver backends use the same profiling interface. Named regions become
gradient and denoise for PnP inference, and forward and backward
for unrolled training. At the end of an iteration, current measurements are
returned through the BenchOpt objective alongside reconstruction metrics.
The common recording window is controlled by:
profiler_mode(default:custom)One of
None,custom,torch, ornvidia. In YAML, usenullfor PythonNone.profiler_warmup(default:0)Number of complete solver iterations to execute before recording. Warmup is particularly important for CUDA initialization and
torch.compile().profiler_active(default:0)Number of iterations to record after warmup. Zero records every remaining iteration.
profiler_save_file(default:False)Write profiler CSV data under
outputs/when the backend supports it. BothPnPandUnrolledPnPexpose this parameter; metrics are still returned to BenchOpt when file output is disabled.
How Profiling Works#
Each solver wraps its named regions in profiler.track_step(name) and calls
profiler.end_iteration() once per BenchOpt iteration. track_step opens the
recording context for that region (wall-clock timing on every backend, plus
operator- and communication-level capture on the torch backend);
end_iteration closes the iteration, computes total_time_sec and
max_gpu_mb, and stores the per-iteration values that BenchOpt reads through
get_current_metrics(). Recording is limited to the window set by
profiler_warmup and profiler_active; outside that window the solver runs
at full speed and reports nothing.
No Profiling#
Principle. profiler_mode: null selects a no-op profiler. Named sections
remain valid context managers but do not synchronize devices, collect metrics,
or create files.
What it measures. Nothing. Use it when measuring through an external system or when profiler overhead would interfere with the experiment.
Custom Profiler#
Principle. profiler_mode: custom surrounds named regions with a
high-resolution wall clock. On CUDA it synchronizes the selected GPU around
each region and resets/reads PyTorch peak allocated memory statistics.
What it measures. Each active iteration reports total_time_sec and
max_gpu_mb. It also reports <section>_time_sec and
<section>_max_gpu_mb for every named region. total_time_sec spans the
whole iteration, including the BenchOpt objective callback that scores the
reconstruction (e.g. PSNR/SSIM against the ground truth) and the optimizer step,
so it exceeds the sum of the <section>_time_sec values. GPU
memory is peak tensor memory allocated through PyTorch, not total
device usage or reserved allocator capacity. On CPU, memory fields are zero.
Output and trade-offs. Metrics are returned directly to BenchOpt. With file
saving enabled, one row per active iteration is written to
outputs/<run-name>_gpu_metrics.csv. Explicit CUDA synchronization makes the
timings easy to interpret. In distributed runs the denoiser/physics collectives already synchronize the ranks,
so the added cost is negligible. This is the preferred backend for lightweight
scaling and memory studies.
PyTorch Profiler#
Principle. profiler_mode: torch uses torch.profiler. Named solver
regions are recorded as user annotations, and their child PyTorch operators are
collected from the CPU/CUDA event tree. Communication operators are identified
from NCCL, Gloo, and c10d annotations and attributed to their containing region
where possible.
What it measures. In per-step mode, BenchOpt receives
<section>_cpu_sec, <section>_cuda_sec, and
<section>_comm_sec together with comm_cuda_sec for communication inside
named regions, comm_sync_sec for communication outside them,
total_time_sec, and max_gpu_mb. Detailed operator rows contain
cpu_sec, cuda_sec, self_cuda_sec, mem_mb, self_mem_mb, and
call count grouped by iteration, section, and operator.
Modes and output. The additional parameters are:
profiler_per_step(default:True)When true, each iteration is a separate profiler cycle. Section summaries are returned to BenchOpt and optional CSV rows retain the iteration index. When false, it runs
torch.profilerin standard scheduled mode: one cycle over the whole window, operator rows aggregated withiter=agg(a lighter CSV), and only wall time and peak memory returned per iteration. This mode can export a Chrome trace viaprofiler_trace_dir.profiler_repeat(default:1)With
profiler_per_step: false, number of warmup/active schedule cycles. Zero repeats indefinitely. It has no effect in per-step mode.profiler_trace_dir(default:None)With
profiler_per_step: false, export a Chrome trace namedrank_<rank>.pt.trace.jsonto this directory. A trace directory is incompatible with per-step mode because the profiler is reset each iteration.
With file saving enabled, detailed rows are written to
outputs/<run-name>_gpu_metrics.csv. PyTorch profiling has substantially more
overhead than the custom backend; select a short active window representative of
steady-state execution.
NVIDIA Nsight Systems Profiler#
Principle. profiler_mode: nvidia adds NVTX ranges for every named region
and for each active iteration (iter_N). It also calls the CUDA profiler start
and stop APIs at the recording-window boundaries. NVIDIA Nsight Systems,
launched around the actual worker process, captures these markers and the CUDA
timeline.
What it measures. Internally, the backend returns per-iteration
total_time_sec and max_gpu_mb. The external nsys report provides the
detailed CPU/GPU timeline, kernel activity, memory activity, idle gaps, and NVTX
region summaries. Without nsys attached, the markers are inert and only the
two internal metrics are available.
Output and distributed use. With file saving enabled, internal metrics are
written to outputs/<run-name>_gpu_metrics.csv. Nsight writes its own
.nsys-rep output according to the worker launch command. With Submitit, do
not wrap the outer benchopt run command: it only submits and polls for a
remote job. Instead, prefix the real SLURM worker through slurm_python. The
repository provides benchmark_training/configs/config_parallel_nsys.yml as
an example that creates rank-specific reports. For local execution, wrap the
normal run directly:
nsys profile -o my_run --force-overwrite=true \
--trace=cuda,nvtx --trace-fork-before-exec=true \
--cuda-memory-usage=true --sample=none \
benchopt run benchmark_training/. --config <experiment.yml>
Warmup and active parameters control both the NVTX iteration ranges and the CUDA capture range. Nsight Systems is the best choice when a scalar timing does not explain synchronization, overlap, or communication behavior.