Profiling Deep Learning Applications¶
On Aurora, we can use the unitrace profiler from Intel to profile deep learning applications. Refer to the unitrace documentation page for details.
PyTorch's own torch.profiler also works on Aurora and attributes time to PyTorch operators rather than device-level activity. The two are complementary; see PyTorch Profiler below.
Example Usage¶
We can use unitrace to trace an application running on multiple ranks and multiple nodes. A simple example, where we use a wrapper script to trace rank 0 on each node of a 4-node job running a PyTorch application, is below.
There are several important shell variables in the wrapper, which may require modification:
UNITRACE_EXE: Theunitraceon yourPATH, provided by thepti-gpumodule thatmodule load frameworksloads. The otherUNITRACE_*paths are derived from it, so they follow programming environment updates. To use a different build, setUNITRACE_EXEto its full path.UNITRACE_OPTS: These are the options thatunitraceuses to trace data at different levels. Based on the number of options, the sizes of the output profiles will vary. Usually, enabling more options leads to a larger profile (in terms of storage in MB).PROFRANK: As implemented, this variable is set by the user to trace the rank of choice. For example, this wrapper will trace rank 0 on each node.RANKCUTOFF: This variable is Aurora-specific. As we can run as many as 12 ranks per node (without using CCS), the first 4 nodes of a job will have 48 ranks running. This provides the upper cutoff of the label (in number) of ranks, beyond whichunitracewill not trace any rank. A user can change the number according to the number of maximum ranks running per node to set up how many ranks to be traced.unitracewill produce a profile (jsonfile, by default) per traced rank. This profile can be viewed using the Perfetto trace viewer.
Deployment¶
The wrapper above can be deployed using the following PBS job script:
PyTorch Profiler¶
unitrace traces at the Level Zero / oneCCL layer. When the question is "which PyTorch operator is slow" rather than "which SYCL kernel is slow", torch.profiler attributes time to operators and Python frames instead. It is common to use torch.profiler to find the expensive operator, then unitrace to see what the device is doing underneath it.
On Aurora, torch.profiler needs ProfilerActivity.XPU in its activity list:
ProfilerActivity.XPUis what captures device-side activity on Aurora's Intel GPUs. Without it the trace contains only CPU events, which is a common cause of "the profiler ran but shows no GPU work".- Profile one rank. Each rank writes its own trace file, and with 12 ranks per node those add up to gigabytes quickly.
- The schedule skips
waitsteps, runswarmupsteps to let the profiler settle, then recordsactivesteps. The first trace therefore appears only afterwait + warmup + activesteps -- six with these values. A loop shorter than that produces no output at all. prof.step()advances the schedule. If it is never called the profiler stays inwaitindefinitely and writes nothing, without raising an error -- the most common reason a profiled run produces no trace.
The resulting JSON files load in the Perfetto trace viewer, chrome://tracing, or TensorBoard -- the same viewer used for unitrace output above.
Using ezpz¶
ezpz wraps the above, including the device detection and rank gating, so the same command profiles on Aurora, Sunspot, Polaris, and Perlmutter:
module load frameworks
pip install "git+https://github.com/saforem2/ezpz" # (1)!
ezpz launch python3 -m ezpz.examples.profiler --profile --rank-zero-only
- Install from the repository, not PyPI: the name
ezpzon PyPI belongs to an unrelated package.
That runs a small distributed training loop, writes a Chrome trace per completed profiling cycle, and logs a key-averages table. get_torch_profiler() selects the activity set from what is available -- XPU on Aurora and Sunspot, CUDA on Polaris and Perlmutter -- so no Aurora-specific branch is needed in user code.
The same --profile flag works on the full training examples:
ezpz launch python3 -m ezpz.examples.fsdp --model small --profile --rank-zero-only
ezpz launch python3 -m ezpz.examples.fsdp_tp --model small --tp 2 --profile --rank-zero-only --no-with-stack # (1)!
-
--no-with-stackis required on Aurora forfsdp_tp. With Python stacks enabled,key_averages()walks them recursively in torch'sprofiler_util.py, and FSDP2 + TP call stacks exceed Python's 1000-frame recursion limit:The run exits 143 having written zero traces. Measured on 2 nodes (24 ranks): with stacks, 0 traces; with
--no-with-stack, 5 traces. Raisingsys.setrecursionlimit()does not help — it replaces the crash with a hang. See saforem2/ezpz#275.
Schedule shape is controlled by --pytorch-profiler-{wait,warmup,active,repeat}. Note that every rank profiles unless --rank-zero-only is passed. Full details, including how to shrink large traces, are in the ezpz profiling guide.
Profiling fsdp_tp is not portable today
The small ezpz.examples.profiler loop profiles cleanly everywhere, but fsdp_tp at --tp 2 does not:
| system | --profile on fsdp_tp --tp 2 |
|---|---|
| Polaris | works |
| Aurora | needs --no-with-stack (see above) |
| Perlmutter | hangs, killed at timeout, zero traces |
On Perlmutter the profiler alone is sufficient to wedge the run — a controlled 2×2 shows both profiled arms timing out and both unprofiled arms completing, on either NCCL transport. No flag avoids it today. Tracked in saforem2/ezpz#275.
From Python¶
The same wrapper is callable directly, for profiling your own training loop rather than one of the examples. get_profiling_context is the higher-level entry point -- it builds the schedule and a trace handler that writes a Chrome trace and logs a key-averages table:
| profile_with_ezpz.py | |
|---|---|
- With
rank_zero_only=True, every rank other than 0 receives acontextlib.nullcontext, so only rank 0 writes traces. - Required, not stylistic: on the non-profiling ranks
profisNone, so calling.step()unguarded raisesAttributeErroron every rank but one.
For full control over activities and the trace handler, get_torch_profiler is the thinner wrapper -- it selects ProfilerActivity.XPU / CUDA / CPU for you and applies the same rank gating, then passes everything else through to torch.profiler.profile:
Both are documented in the ezpz.profile API reference.