Fuse multi-rank traces in TraceLens#
2026-07-17
3 min read time
This topic shows how to merge per-rank PyTorch traces from a distributed run into
a single file that can be visualized together in Perfetto, using the TraceFuse
SDK.
Before you begin#
TraceLens installed (see Install TraceLens).
Per-rank PyTorch profiler traces (one trace per rank).
Why fuse traces#
By default, each rank in a distributed run produces its own trace. Inspecting
them separately makes it hard to diagnose cross-rank issues — straggling ranks,
load imbalance, or a rank stalling on a collective — because you can’t see how
ranks line up on a common timeline. TraceFuse merges the per-rank traces into a
single file so all ranks render together in Perfetto, with clear per-rank CPU/GPU
separation and correct flow linking.
TraceFuse is for visual analysis in the Perfetto UI only — not for
automated analysis. For quantitative cross-rank metrics, use a
collective-communication report instead.
Step 1: Fuse the traces#
Pass TraceFuse the per-rank trace files (a list ordered by rank, or a
{rank: filepath} dict), then call merge_and_save:
import os
from TraceLens import TraceFuse
profiles_root_dir = "path/to/your/profiles"
world_size = 8
list_profile_filepaths = [
os.path.join(profiles_root_dir, f"rank_{i}.json") for i in range(world_size)
]
fuser = TraceFuse(list_profile_filepaths)
output_file = os.path.join(profiles_root_dir, "merged_trace.json")
fuser.merge_and_save(output_file)
A ready-to-edit version of this script is at
examples/trace_fusion_example.py.
Expected output: A single merged trace file containing the events from all
ranks, aligned on a common timeline with per-rank RANK N - CPU / RANK N - GPU
process labels.
Note
Python-function category events (python_function) are skipped by default to
save memory. To keep them, pass include_pyfunc=True to merge_and_save.
Speed up loading with multiprocessing#
Loading many large per-rank traces is the slow part. Enable parallel loading (speedup is system-dependent):
# Defaults to os.cpu_count() workers
fuser = TraceFuse(list_profile_filepaths, use_multiprocessing=True)
# Or cap the worker count
fuser = TraceFuse(list_profile_filepaths, use_multiprocessing=True, max_workers=32)
Filter the events you merge#
For large runs, merging every event from every rank is heavy. Pass a filter_fn
to merge_and_save to keep only the events you care about — for example, NCCL
kernels:
def filter_nccl_kernels(event):
return (
event.get("cat") in ["kernel", "gpu_user_annotation"]
and "nccl" in event.get("name", "").lower()
)
fuser.merge_and_save(output_file, filter_fn=filter_nccl_kernels)
You can also narrow the run itself — pass a dict to merge only a subset of ranks (for instance, rank 0 of each node on a 64-rank job):
profile_files = {
i: os.path.join(profiles_root_dir, f"rank_{i}.json")
for i in range(0, 64, 8)
}
fuser = TraceFuse(profile_files)
Step 2: Visualize in Perfetto#
Open the merged trace in the Perfetto UI (or the
Chrome trace viewer) to inspect cross-rank behavior, such as exposed
communication and synchronization gaps. Each rank appears as separate
RANK N - CPU and RANK N - GPU tracks.
What the merge does#
TraceFuse combines the traceEvents from each rank into one list and adjusts
them so they render correctly together:
Process IDs (
pid) are offset per rank so ranks don’t collide in the UI, andprocess_namemetadata is added to label each trackRANK N - CPU/RANK N - GPU.Flow linking is preserved by offsetting the linking key (
correlationorExternal id) and theac2gflowids per rank, so CPU→GPU launch arrows stay correct.