Don't optimize what you haven't measured
The PyTorch profiler captures op-level timing on CPU and GPU, exports a Chrome-trace JSON, and (with profile_memory=True) tracks tensor allocation. It's the right tool to answer "where is my training time actually going" and "why am I OOMing".
Two profilers, two purposes
torch.profiler.profile— modern, comprehensive. CPU + CUDA timing, memory tracking, Chrome trace export. The right default.torch.utils.bottleneck— older, lighter-weight Python wrapper. Useful for quick "what's slow" answers without setting up the full profiler.
The Chrome trace
The profiler can export a JSON that loads in Chrome's chrome://tracing viewer (or perfetto.dev). You see a timeline of every CPU and GPU op, with their durations and call relationships. Spotting "I have a huge cudaStreamSynchronize" or "this tiny op dispatches 10,000 times" becomes obvious visually.
Reproducibility — orthogonal but worth knowing
If you want bit-for-bit deterministic runs (for debugging or paper-style ablations), you have to set every RNG seed AND tell PyTorch to use deterministic algorithms. Not free — some ops have no deterministic implementation, and others are slower in deterministic mode. Use it for debugging, not for production training.