Skip to content

Commit e89da8f

Browse files
committed
add cudagraph annotation
1 parent 74485ea commit e89da8f

8 files changed

Lines changed: 135 additions & 21 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@ CLAUDE.local.md
4949
/debug_*.py
5050
CLAUDE_CONTEXT/
5151
/.claude/settings.local.json
52+
agent_space/

torchtitan/experiments/graph_trainer/compile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ def apply_compile(
240240
return model
241241

242242
torch._inductor.config.reorder_for_peak_memory = False
243+
torch._inductor.config.triton.cudagraph_kernel_annotations = True
243244
torch._dynamo.config.capture_scalar_outputs = True
244245

245246
fsdp_reshard_after_forward = get_fsdp_reshard_after_forward_policy(

torchtitan/experiments/graph_trainer/cudagraph.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __init__(self) -> None:
3131
self._initialized = False
3232
self._cudagraph_wrappers: list["CUDAGraphWrapper"] = []
3333
self._teardown_called = False
34+
self.all_annotations: dict[int, list] = {}
3435

3536
def maybe_initialize(self) -> None:
3637
if self._initialized:
@@ -100,6 +101,11 @@ def cudagraph_teardown() -> None:
100101
_cg_manager.teardown()
101102

102103

104+
def get_cudagraph_annotations() -> dict[int, list]:
105+
"""Return all kernel annotations accumulated across CUDA graph captures."""
106+
return _cg_manager.all_annotations
107+
108+
103109
class CUDAGraphWrapper:
104110
"""Wraps a callable with cudagraph. It warms up the callable, records cudagraph,
105111
and replays cudagraph during runtime. It also handles static input tensors, which
@@ -206,10 +212,16 @@ def __call__(self, *args):
206212
self._cudagraph,
207213
pool=_cg_manager.graph_pool,
208214
stream=_cg_manager.stream,
215+
enable_annotations=True,
209216
):
210217
# `output` is managed by pytorch's cudagraph pool
211218
self._output = self._runnable(*args)
212219

220+
# Save kernel annotations for trace post-processing.
221+
from torch.cuda._graph_annotations import get_kernel_annotations
222+
223+
_cg_manager.all_annotations.update(get_kernel_annotations())
224+
213225
if self._should_check_address:
214226
self._check_static_inputs_address()
215227

torchtitan/experiments/graph_trainer/graph_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,11 @@ def get_compiler_passes_from_config(
519519
)
520520
)
521521
else:
522+
# Auto-insert annotation pass before cudagraph capture.
523+
if pass_name == "cudagraph":
524+
compiler_passes.append(
525+
AVAILABLE_COMPILER_PASSES["insert_kernel_annotations"]
526+
)
522527
compiler_passes.append(AVAILABLE_COMPILER_PASSES[pass_name])
523528

524529
if pass_names:

torchtitan/experiments/graph_trainer/passes.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ def construct_default_graph_passes(
8282
regional_inductor_pass,
8383
]
8484

85+
# Insert kernel annotation markers before cudagraph capture.
86+
passes.append(insert_kernel_annotations_pass)
87+
8588
# cudagraph should be the last pass.
8689
from torchtitan.experiments.graph_trainer.cudagraph import is_cudagraph_compatible
8790

@@ -260,6 +263,74 @@ def _get_fake_mode_from_gm(gm: torch.fx.GraphModule):
260263
return gm
261264

262265

266+
def _mark_kernels_enter(annotation: dict) -> object:
267+
"""Helper called from FX graph to enter a mark_kernels scope."""
268+
from torch.cuda._graph_annotations import mark_kernels
269+
270+
ctx = mark_kernels(annotation)
271+
ctx.__enter__()
272+
return ctx
273+
274+
275+
def _mark_kernels_exit(ctx: object) -> None:
276+
"""Helper called from FX graph to exit a mark_kernels scope."""
277+
ctx.__exit__(None, None, None) # type: ignore[union-attr]
278+
279+
280+
def insert_kernel_annotations_pass(
281+
gm: torch.fx.GraphModule,
282+
example_inputs: tuple | None = None,
283+
) -> torch.fx.GraphModule:
284+
"""Insert mark_kernels() calls at component boundaries in the FX graph.
285+
286+
Reads ``node.meta["custom"]["component"]`` (set via
287+
``torch.fx.traceback.annotate``) and inserts enter/exit calls so that
288+
CUDA graph capture records the annotations.
289+
"""
290+
graph = gm.graph
291+
current_component: str | None = None
292+
current_ctx_node = None
293+
294+
for node in list(graph.nodes):
295+
component = (node.meta.get("custom") or {}).get("component")
296+
297+
if component != current_component:
298+
# Close previous scope
299+
if current_ctx_node is not None:
300+
with graph.inserting_before(node):
301+
exit_node = graph.call_function(
302+
_mark_kernels_exit, (current_ctx_node,)
303+
)
304+
exit_node.meta["custom"] = {}
305+
current_ctx_node = None
306+
307+
# Open new scope
308+
if component is not None:
309+
with graph.inserting_before(node):
310+
enter_node = graph.call_function(
311+
_mark_kernels_enter,
312+
({"component": component},),
313+
)
314+
enter_node.meta["custom"] = {}
315+
current_ctx_node = enter_node
316+
317+
current_component = component
318+
319+
# Close any trailing scope (before output/return)
320+
if current_ctx_node is not None:
321+
output_nodes = [n for n in graph.nodes if n.op == "output"]
322+
if output_nodes:
323+
with graph.inserting_before(output_nodes[0]):
324+
exit_node = graph.call_function(
325+
_mark_kernels_exit, (current_ctx_node,)
326+
)
327+
exit_node.meta["custom"] = {}
328+
329+
graph.lint()
330+
gm.recompile()
331+
return gm
332+
333+
263334
def cudagraph_pass(
264335
gm: torch.fx.GraphModule,
265336
example_inputs: tuple,
@@ -680,6 +751,7 @@ def tlparse_log_graph_pass(
680751
"auto_bucketing": autobucketing_reordering_pass,
681752
"transformer_block_bucketing": transformer_block_bucketing_reordering_pass,
682753
"regional_inductor": regional_inductor_pass,
754+
"insert_kernel_annotations": insert_kernel_annotations_pass,
683755
"cudagraph": cudagraph_pass,
684756
"full_inductor_compilation": full_inductor_compilation_pass,
685757
}

torchtitan/models/common/attention.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,19 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7+
import os
8+
9+
10+
# TODO: Re-enable once we have closed
11+
# https://github.com/pytorch/torchtitan/issues/2722
12+
os.environ.setdefault("DISABLE_LLVM_OPT", "1")
13+
714
from collections.abc import Callable
815
from dataclasses import dataclass, field
916
from typing import ClassVar, NamedTuple
1017

1118
import torch
19+
from torch.fx.traceback import annotate
1220
import torch.nn.functional as F
1321
from torch.distributed.tensor import DTensor, Shard
1422
from torch.distributed.tensor.experimental import local_map
@@ -636,7 +644,8 @@ def forward(
636644
positions: torch.Tensor | None = None,
637645
) -> torch.Tensor:
638646
bs, seqlen, _ = x.shape
639-
xq, xk, xv = self.wq(x), self.wk(x), self.wv(x)
647+
with annotate({"component": "qkv_proj"}):
648+
xq, xk, xv = self.wq(x), self.wk(x), self.wv(x)
640649

641650
# Use -1 instead of `n_heads` (or `n_kv_heads`) to infer the actual
642651
# local heads from sizes of xq, xk, and xv as TP may have sharded them
@@ -653,25 +662,30 @@ def forward(
653662

654663
# Apply rotary embeddings
655664
if self.use_rope:
656-
if self.rope_backend == "cos_sin":
657-
xq, xk = apply_rotary_emb_cos_sin(xq, xk, rope_cache, positions)
658-
else:
659-
xq, xk = apply_rotary_emb_complex(
660-
xq, xk, freqs_cis=rope_cache, positions=positions
661-
)
665+
with annotate({"component": "rope"}):
666+
if self.rope_backend == "cos_sin":
667+
xq, xk = apply_rotary_emb_cos_sin(
668+
xq, xk, rope_cache, positions
669+
)
670+
else:
671+
xq, xk = apply_rotary_emb_complex(
672+
xq, xk, freqs_cis=rope_cache, positions=positions
673+
)
662674

663675
# Handle iRoPE dict masks (Llama4)
664676
if isinstance(attention_masks, dict):
665677
mask_key = "rope" if self.use_rope else "nope"
666678
attention_masks = attention_masks[mask_key]
667679

668-
output = self.inner_attention(
669-
xq,
670-
xk,
671-
xv,
672-
attention_masks=attention_masks,
673-
scale=self.scaling,
674-
enable_gqa=self.enable_gqa,
675-
).contiguous()
680+
with annotate({"component": "sdpa"}):
681+
output = self.inner_attention(
682+
xq,
683+
xk,
684+
xv,
685+
attention_masks=attention_masks,
686+
scale=self.scaling,
687+
enable_gqa=self.enable_gqa,
688+
).contiguous()
676689
output = output.view(bs, seqlen, -1)
677-
return self.wo(output)
690+
with annotate({"component": "o_proj"}):
691+
return self.wo(output)

torchtitan/models/common/feed_forward.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import torch
1010
import torch.nn.functional as F
11+
from torch.fx.traceback import annotate
1112

1213
from torchtitan.models.common.linear import Linear
1314
from torchtitan.protocols.module import Module
@@ -51,4 +52,7 @@ def __init__(self, config: Config):
5152
self.w3 = config.w3.build()
5253

5354
def forward(self, x: torch.Tensor) -> torch.Tensor:
54-
return self.w2(F.silu(self.w1(x)) * self.w3(x))
55+
with annotate({"component": "silu_mul"}):
56+
h = F.silu(self.w1(x)) * self.w3(x)
57+
with annotate({"component": "down_proj"}):
58+
return self.w2(h)

torchtitan/models/llama3/model.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import torch
1313
from torch import nn
14+
from torch.fx.traceback import annotate
1415

1516
from torchtitan.models.common.attention import AttentionMasksType, VarlenAttention
1617
from torchtitan.models.common.decoder import Decoder, TransformerBlock
@@ -48,10 +49,14 @@ def forward(
4849
attention_masks: AttentionMasksType | None,
4950
positions: torch.Tensor | None = None,
5051
):
51-
h = x + self.attention(
52-
self.attention_norm(x), freqs_cis, attention_masks, positions
53-
)
54-
out = h + self.feed_forward(self.ffn_norm(h))
52+
with annotate({"component": "attention_norm"}):
53+
h_norm = self.attention_norm(x)
54+
with annotate({"component": "attention"}):
55+
h = x + self.attention(h_norm, freqs_cis, attention_masks, positions)
56+
with annotate({"component": "ffn_norm"}):
57+
h_norm = self.ffn_norm(h)
58+
with annotate({"component": "feed_forward"}):
59+
out = h + self.feed_forward(h_norm)
5560
return out
5661

5762

0 commit comments

Comments
 (0)