Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions megatron/core/extensions/transformer_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1754,6 +1754,9 @@ def __init__(
self.kept_packed_seq_params.discard("seq_idx")
self.kept_packed_seq_params.discard("tokens_per_sample")

if get_te_version() < PkgVersion("2.2.0"):
self.kept_packed_seq_params.discard("pad_between_seqs")

if config.qk_clip or config.log_max_attention_logit:
# qk-clip is only supported in TE 2.9.0 and later
assert is_te_min_version("2.9.0"), "qk-clip is only supported in TE 2.9.0 and later"
Expand Down
1 change: 1 addition & 0 deletions megatron/core/packed_seq_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class PackedSeqParams:
total_tokens: int = None
seq_idx: Tensor = None
tokens_per_sample: int = None
pad_between_seqs: bool = None

def __post_init__(self):
"""Pre-compute seq_idx for Mamba mixer CUDA graph compatibility.
Expand Down
108 changes: 108 additions & 0 deletions tests/unit_tests/transformer/test_cuda_graphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
destroy_num_microbatches_calculator,
init_num_microbatches_calculator,
)
from megatron.core.packed_seq_params import PackedSeqParams
from megatron.core.pipeline_parallel.schedules import set_current_microbatch
from megatron.core.process_groups_config import ProcessGroupCollection
from megatron.core.tensor_parallel.random import (
Expand All @@ -34,6 +35,7 @@
CudaGraphManager,
TECudaGraphHelper,
_CudagraphGlobalRecord,
create_cudagraphs,
)
from megatron.core.transformer.enums import CudaGraphModule, CudaGraphScope, InferenceCudaGraphScope
from megatron.core.transformer.mlp import MLPSubmodules
Expand Down Expand Up @@ -444,6 +446,112 @@ def test_gpu_cudagraph(self):
)


@pytest.mark.skipif(
not (HAVE_TE and is_te_min_version("1.5.0")),
reason="use_te_rng_tracker requires TransformerEngine version >= 1.5",
)
class TestPackedSeqCudagraphs:
"""Training CUDA graphs over thd input with padding between sequences.

The padded cu_seqlens describe a slot layout that differs from the actual lengths,
and pad_between_seqs is set explicitly so TE does spend a GPU sync inferring it.
cp_size == 2 additionally captures TE's ring-P2P context-parallel attention inside the graphs.
"""

SEQ_LENGTHS = [7, 5]
SLOT_STARTS = [0, 8, 16] # slot layout aligned to 2 * cp_size for every cp_size tested
BIN_SIZE = 32

def teardown_method(self, method):
Utils.destroy_model_parallel()
_CudagraphGlobalRecord.cudagraph_created = False
_CudagraphGlobalRecord.cudagraph_record = []
CudaGraphManager.global_mempool = None

def _build_packed_seq_params(self, device):
# Actual boundaries: each sequence's real tokens inside its slot; the trailing bin
# padding [SLOT_STARTS[-1], BIN_SIZE) forms a ghost slot of pad tokens.
boundaries = [0]
for length in self.SEQ_LENGTHS:
boundaries.append(boundaries[-1] + length)
boundaries.append(boundaries[-1] + self.BIN_SIZE - self.SLOT_STARTS[-1])
cu_seqlens = torch.tensor(boundaries, dtype=torch.int32, device=device)
cu_seqlens_padded = torch.tensor(
self.SLOT_STARTS + [self.BIN_SIZE], dtype=torch.int32, device=device
)
return PackedSeqParams(
qkv_format='thd',
cu_seqlens_q=cu_seqlens,
cu_seqlens_kv=cu_seqlens,
cu_seqlens_q_padded=cu_seqlens_padded,
cu_seqlens_kv_padded=cu_seqlens_padded,
max_seqlen_q=self.BIN_SIZE,
max_seqlen_kv=self.BIN_SIZE,
pad_between_seqs=True,
)

@pytest.mark.parametrize("cp_size", [1, 2])
def test_thd_capture_with_pad_between_seqs(self, cp_size):
initialize_rng_tracker(use_te_rng_tracker=True, force_reset=True)
Utils.initialize_model_parallel(context_parallel_size=cp_size)
model_parallel_cuda_manual_seed(123)

config = TransformerConfig(
num_layers=2,
hidden_size=64,
num_attention_heads=4,
context_parallel_size=cp_size,
bf16=True,
params_dtype=torch.bfloat16,
attention_dropout=0.0,
hidden_dropout=0.0,
cuda_graph_impl="local",
use_cpu_initialization=True,
)
block = TransformerBlock(config, get_gpt_layer_with_transformer_engine_spec()).cuda()
block.train()
# CUDA-graphed backward assumes DDP-style grad accumulation buffers.
for param in block.parameters():
param.main_grad = torch.zeros_like(param)

packed_seq_params = self._build_packed_seq_params(torch.device('cuda'))
# Each CP rank holds its 1/cp_size share of the bin's tokens.
hidden_states = torch.randn(
(self.BIN_SIZE // cp_size, 1, config.hidden_size),
dtype=torch.bfloat16,
device='cuda',
requires_grad=True,
)

eager_out = block(
hidden_states=hidden_states, attention_mask=None, packed_seq_params=packed_seq_params
)
eager_out.sum().backward()

# This is the primary function under test.
create_cudagraphs()

for layer in block.layers:
runners = layer.cudagraph_manager.cudagraph_runners
assert len(runners) == 1
assert runners[0].fwd_graph is not None

graphed_out = block(
hidden_states=hidden_states, attention_mask=None, packed_seq_params=packed_seq_params
)
assert torch.allclose(graphed_out.float(), eager_out.float(), rtol=1e-2, atol=1e-2)
graphed_out.sum().backward()

# Destroy captured graphs deterministically before parallel-state teardown.
for layer in block.layers:
for runner in layer.cudagraph_manager.cudagraph_runners:
if hasattr(runner, "fwd_graph"):
del runner.fwd_graph
if hasattr(runner, "bwd_graph"):
del runner.bwd_graph
torch.cuda.synchronize()


@pytest.mark.skipif(
not (HAVE_TE and is_te_min_version("1.5.0")),
reason="use_te_rng_tracker requires TransformerEngine version >= 1.5",
Expand Down
Loading