Skip to content

Commit b8deb81

Browse files
committed
add trace merge
1 parent 81cea0d commit b8deb81

3 files changed

Lines changed: 77 additions & 1 deletion

File tree

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ dependencies = [
2525
"rich",
2626
"matplotlib",
2727
"pandas",
28-
"seaborn"
28+
"seaborn",
29+
"typer",
2930
]
3031

3132
[project.optional-dependencies]
@@ -49,6 +50,9 @@ llama = [
4950
"float8_experimental",
5051
]
5152

53+
[project.scripts]
54+
merge-traces = "transformer_nuggets.utils.merge_traces:app"
55+
5256
# ---------- TOOL CONFIGURATIONS ------------
5357
[tool.hatch.version]
5458
source = "vcs"

transformer_nuggets/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@
1313
)
1414
from transformer_nuggets.utils.tracing import LoggingMode, NanInfDetect
1515
from transformer_nuggets.utils.triton import print_sass
16+
from transformer_nuggets.utils.merge_traces import merge_traces
1617
# from transformer_nuggets.utils.model_extraction import extract_attention_data
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Merge per-rank Chrome/Perfetto traces into a single multi-process trace."""
2+
3+
from __future__ import annotations
4+
5+
import gzip
6+
import json
7+
import sys
8+
from pathlib import Path
9+
from typing import Annotated
10+
11+
import typer
12+
13+
app = typer.Typer(help="Merge per-rank Chrome/Perfetto traces into one file.")
14+
15+
16+
def _open_trace(path: str, mode: str):
17+
if path.endswith(".gz"):
18+
return gzip.open(path, mode + "t", encoding="utf-8")
19+
return open(path, mode, encoding="utf-8")
20+
21+
22+
def merge_traces(input_paths: list[str], output_path: str) -> None:
23+
merged_events: list[dict] = []
24+
25+
for rank, path in enumerate(input_paths):
26+
with _open_trace(path, "r") as f:
27+
data = json.load(f)
28+
29+
events = data.get("traceEvents", data) if isinstance(data, dict) else data
30+
31+
merged_events.append(
32+
{
33+
"ph": "M",
34+
"name": "process_name",
35+
"pid": rank,
36+
"tid": 0,
37+
"args": {"name": f"Rank {rank}"},
38+
}
39+
)
40+
41+
for ev in events:
42+
if ev.get("ph") == "M" and ev.get("name") == "process_name":
43+
continue
44+
ev["pid"] = rank
45+
if "id" in ev and ev.get("ph") in ("s", "t", "f"):
46+
ev["id"] = ev["id"] + rank * (1 << 32)
47+
merged_events.append(ev)
48+
49+
with _open_trace(output_path, "w") as f:
50+
json.dump({"traceEvents": merged_events}, f, indent=0)
51+
52+
53+
@app.command()
54+
def main(
55+
traces: Annotated[list[Path], typer.Argument(help="Input trace files, one per rank, in rank order.")],
56+
output: Annotated[Path, typer.Option("-o", "--output", help="Output path.")] = Path(
57+
"merged_trace.json.gz"
58+
),
59+
):
60+
"""Merge per-rank Chrome/Perfetto traces into a single multi-process Perfetto trace."""
61+
for p in traces:
62+
if not p.exists():
63+
typer.echo(f"Error: {p} not found", err=True)
64+
raise typer.Exit(1)
65+
66+
merge_traces([str(p) for p in traces], str(output))
67+
typer.echo(f"Merged {len(traces)} traces -> {output}")
68+
69+
70+
if __name__ == "__main__":
71+
app()

0 commit comments

Comments
 (0)