Skip to content
Draft
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
604 changes: 602 additions & 2 deletions megatron/core/datasets/data_schedule.py

Large diffs are not rendered by default.

423 changes: 423 additions & 0 deletions megatron/core/datasets/data_schedule_utils.py

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions megatron/core/datasets/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,26 @@ If the later training job does not specify `--global-batch-size` (which is neede

`tools/prepare_cache.py` does not support `--mock-data`, `--sft`, `--fim-data`, or `--step-batch-size-schedule`.

## Packing Scheduler

The packing scheduler reschedules variable-length sequences across DPxCP ranks to improve GPU utilization. It is built around the following modules:

### `data_schedule`

This module contains the high-level scheduling logic and entry points:

- **`BasePackingScheduler`**: Abstract base class for packing schedulers. Defines the interface for `get_groups_and_subsamples()` (scheduling algorithm) and `run()` (full scheduling pipeline including fetch, schedule, reroute, pack, TP synchronization, and VPP handling).

- **`DpBalancedScheduler`**: A concrete scheduler that packs sequences in their original order until reaching the max sequence length limit per DPxCP rank. Supports aligning the number of microbatches to DP size and VPP stage multiples.

- **`wrap_data_iterator()`**: Top-level entry point that wraps an existing `data_iterator`. Every TP-rank-0 PP stage schedules its local iterator, while scalar schedule results are synchronized inside each TP group. It returns the packed iterator, updated number of microbatches, and FLOPs statistics.

- **`get_batch_on_this_rank_for_sequence_packing()`**: Fetches a packed microbatch on TP rank 0, broadcasts it within the TP group, constructs `PackedSeqParams` (with `cu_seqlens`, `max_seqlen`, `qkv_format=thd`), and optionally partitions sequences across CP ranks using Transformer Engine's `thd_get_partitioned_indices`.

### `data_schedule_utils.py`

This module contains the utility functions used by the schedulers.

## Fast DataLoader initialization

Especially for large-scale runs, DataLoader initialization can take several minutes, since it involves opening and memory-mapping multiple files and can significantly stress the filesystem. To speed up this process, we have developed the following three optimizations, controlled by configuration flags:
Expand Down
13 changes: 13 additions & 0 deletions megatron/core/extensions/transformer_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -3396,3 +3396,16 @@ def set_save_original_input(module):
from transformer_engine.pytorch.float8_tensor import Float8Tensor
except ImportError:
Float8Tensor = None


def get_thd_partitioned_indices(
cu_seqlens: torch.Tensor, total_tokens: int, cp_size: int, cp_rank: int
) -> torch.Tensor:
"""Get partitioned indices for THD data in context parallelism."""
assert is_te_min_version("1.10.0"), (
"Please update Transformer Engine to >= 1.10 to use "
"Context Parallel with THD format data"
)
import transformer_engine_torch as tex

return tex.thd_get_partitioned_indices(cu_seqlens, total_tokens, cp_size, cp_rank)
8 changes: 7 additions & 1 deletion megatron/core/model_parallel_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ModelParallelConfig:
can handle without overflowing the memory. Typically, a good starting point is to set this
to maximum sequence length / context parallel size.
This is used to calculate the number and length of sub-samples assigned to
each rank when using hybrid_context_parallel.
each rank when hybrid_context_parallel or sequence_packing_scheduler is enabled.
"""

hybrid_context_parallel: bool = False
Expand All @@ -69,6 +69,12 @@ class ModelParallelConfig:
Please set max_seqlen_per_dp_cp_rank when using hybrid_context_parallel.
"""

sequence_packing_scheduler: Optional[Literal['dp_balanced']] = None
"""
Scheduler for packing variable-length THD batches.
dp_balanced: DP-balanced scheduler for sequence packing.
"""

expert_model_parallel_size: int = 1
"""Distributes Moe Experts across sub data parallel dimension."""

Expand Down
26 changes: 26 additions & 0 deletions megatron/core/transformer/transformer_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2829,6 +2829,32 @@ def _scope_to_str(s):
self.attention_backend == AttnBackend.flash
), "Batch invariant mode only supports FlashAttention"

if self.sequence_packing_scheduler is not None:
if not HAVE_PACKAGING:
raise ImportError(
"packaging is not installed. Please install it with `pip install packaging`."
)
if not (
is_te_min_version("2.9.0") or get_te_version() == PkgVersion("2.9.0.dev0+5b3092a")
):
raise ValueError(
"THD sequence packing requires Transformer Engine >= 2.9.0 "
f"but got {get_te_version()} (TE < 2.9.0 may have convergence issues)."
)

self.variable_seq_lengths = True
assert self.num_moe_experts is None or self.moe_token_dispatcher_type == "alltoall", (
"sequence_packing only supports moe_token_dispatcher_type='alltoall', "
f"got '{self.moe_token_dispatcher_type}'"
)

supported_schedulers = ['dp_balanced']
if self.sequence_packing_scheduler not in supported_schedulers:
raise ValueError(
f"Unsupported scheduler: {self.sequence_packing_scheduler}. "
f"Available schedulers: {supported_schedulers}"
)


@dataclass
class MLATransformerConfig(TransformerConfig):
Expand Down
23 changes: 23 additions & 0 deletions megatron/training/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,25 @@ def validate_args(args, defaults={}):
if args.ckpt_format == "fsdp_dtensor":
assert args.use_megatron_fsdp, "--ckpt-format fsdp_dtensor is only tested with Megatron FSDP."

if args.sequence_packing_scheduler is not None:
assert not args.hybrid_context_parallel, (
"--sequence-packing-scheduler and --hybrid-context-parallel are "
"separate scheduling paths and cannot be enabled together"
)
assert args.calculate_per_token_loss, (
"Sequence packing requires --calculate-per-token-loss so gradients "
"do not depend on packing boundaries"
)
args.variable_seq_lengths = True
assert args.max_seqlen_per_dp_cp_rank is not None, (
"--max-seqlen-per-dp-cp-rank must be set when using sequence packing"
)
packed_capacity = args.context_parallel_size * args.max_seqlen_per_dp_cp_rank
assert packed_capacity >= args.seq_length, (
f"Packed sequence capacity ({packed_capacity}) must be at least "
f"--seq-length ({args.seq_length})"
)

# Data blend checks
assert args.mock_data + \
bool(args.data_path) + \
Expand Down Expand Up @@ -2148,6 +2167,7 @@ def _add_network_size_args(parser):
"bias_dropout_fusion",
"apply_rope_fusion",
"mamba_training_ssm_states_dtype",
"sequence_packing_scheduler",
]
transformer_factory = ArgumentGroupFactory(TransformerConfig, exclude=exclude)
transformer_group = transformer_factory.build_group(parser, "transformer configuration")
Expand Down Expand Up @@ -2919,6 +2939,9 @@ def _add_distributed_args(parser):
'all layers will share the same communication type. Users can also '
'specify separated types for each layer like '
'--cp-comm-type p2p p2p a2a a2a a2a+p2p a2a+p2p')
group.add_argument('--sequence-packing-scheduler', type=str, default=None,
choices=['dp_balanced'],
help='Pack variable-length sequences across DP x CP ranks.')
group.add_argument('--fake-process-group', action='store_true', default=False,
help='If set, initialize with fake distributed process group and all distributed communication operations will be skipped. \
This is quite useful for profiling memory usage of distributed training with just one GPU. \
Expand Down
8 changes: 5 additions & 3 deletions megatron/training/datasets/data_samplers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

from megatron.core import mpu
from megatron.core.datasets.utils import Split

from megatron.training import get_args
from megatron.training.dist_signal_handler import DistributedSignalHandler

Expand Down Expand Up @@ -98,8 +97,11 @@ def close_nvidia_fds():
worker_init_fn if args.num_workers > 0 else None
)
# Torch dataloader.
if args.hybrid_context_parallel:
extra_kwargs = {"collate_fn": lambda x: x,}
if (
args.hybrid_context_parallel
or getattr(args, "sequence_packing_scheduler", None) is not None
):
extra_kwargs = {"collate_fn": lambda x: x}
else:
extra_kwargs = {}
return torch.utils.data.DataLoader(
Expand Down
92 changes: 81 additions & 11 deletions megatron/training/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

# First-party.
from megatron.core import mpu, nccl_allocator, tensor_parallel
from megatron.core.datasets.data_schedule import HybridCPDataLoaderWrapper
from megatron.core.datasets.data_schedule import HybridCPDataLoaderWrapper, wrap_data_iterator
from megatron.core.distributed import DistributedDataParallel as DDP
from megatron.core.distributed import (
DistributedDataParallelConfig,
Expand Down Expand Up @@ -2297,6 +2297,7 @@ def train_step(forward_step_func, data_iterator, model, optimizer, opt_param_sch
"""
args = get_args()
timers = get_timers()
scheduled_num_microbatches = get_num_microbatches()

rerun_state_machine = get_rerun_state_machine()
save_params_in_this_iteration = (args.save_params_interval is not None and
Expand All @@ -2309,7 +2310,8 @@ def train_step(forward_step_func, data_iterator, model, optimizer, opt_param_sch
(iteration + 1) % args.save_wgrads_interval == 0)
save_dgrads_in_this_iteration = (args.save_dgrads_interval is not None and
(iteration + 1) % args.save_dgrads_interval == 0)
while rerun_state_machine.should_run_forward_backward(data_iterator):
source_data_iterator = data_iterator
while rerun_state_machine.should_run_forward_backward(source_data_iterator):
# Set grad to zero.
for model_chunk in model:
model_chunk.zero_grad_buffer()
Expand Down Expand Up @@ -2351,6 +2353,26 @@ def train_step(forward_step_func, data_iterator, model, optimizer, opt_param_sch
if isinstance(optim_instance, DistributedOptimizer):
optim_instance._copy_main_params_to_param_buffer()

if getattr(config, "sequence_packing_scheduler", None) is not None:
scheduler_pg_collection = get_attr_wrapped_model(model[0], "pg_collection")
assert isinstance(scheduler_pg_collection, ProcessGroupCollection), (
"sequence packing requires the model to expose a ProcessGroupCollection"
)
(
scheduled_data_iterator,
scheduled_num_microbatches,
_total_real_tokens_in_batch,
_seqlen_squared_sum_in_batch,
) = wrap_data_iterator(
source_data_iterator,
config,
get_num_microbatches(),
pg_collection=scheduler_pg_collection,
)
else:
scheduled_data_iterator = source_data_iterator
scheduled_num_microbatches = get_num_microbatches()

# Forward pass.
if save_activations_in_this_iteration:
enable_activation_logging(model, args.save)
Expand All @@ -2360,9 +2382,9 @@ def train_step(forward_step_func, data_iterator, model, optimizer, opt_param_sch
enable_dgrad_logging(model, args.save)
losses_reduced = forward_backward_func(
forward_step_func=forward_step_func,
data_iterator=data_iterator,
data_iterator=scheduled_data_iterator,
model=model,
num_microbatches=get_num_microbatches(),
num_microbatches=scheduled_num_microbatches,
seq_length=args.seq_length,
micro_batch_size=args.micro_batch_size,
decoder_seq_length=args.decoder_seq_length,
Expand Down Expand Up @@ -2411,7 +2433,17 @@ def _save_state_dict(attr_name, label):

should_checkpoint, should_exit, exit_code = rerun_state_machine.should_checkpoint_and_exit()
if should_exit:
return {}, True, should_checkpoint, should_exit, exit_code, None, None, 0
return (
{},
True,
should_checkpoint,
should_exit,
exit_code,
None,
None,
0,
scheduled_num_microbatches,
)

# Empty unused memory.
if args.empty_unused_memory_level >= 1:
Expand Down Expand Up @@ -2505,8 +2537,19 @@ def _save_state_dict(attr_name, label):
grad_norm,
num_zeros_in_grad,
log_max_attention_logit,
scheduled_num_microbatches,
)
return {}, skipped_iter, should_checkpoint, should_exit, exit_code, grad_norm, num_zeros_in_grad, log_max_attention_logit
return (
{},
skipped_iter,
should_checkpoint,
should_exit,
exit_code,
grad_norm,
num_zeros_in_grad,
log_max_attention_logit,
scheduled_num_microbatches,
)


def training_log(
Expand All @@ -2525,6 +2568,7 @@ def training_log(
is_first_iteration=False,
seqlen_squared_sum_in_batch: float | None = None,
total_real_tokens_in_batch: float | None = None,
num_microbatches: int | None = None,
):
"""Log training information such as losses, timing, ...."""
args = get_args()
Expand Down Expand Up @@ -2694,7 +2738,7 @@ def training_log(
# Log MoE metrics.
moe_log_string = ""
if args.num_experts is not None:
moe_loss_scale = 1 / get_num_microbatches()
moe_loss_scale = 1 / (num_microbatches or get_num_microbatches())
track_names = []
if "aux_loss" in args.moe_router_load_balancing_type:
track_names.append("load_balancing_loss")
Expand Down Expand Up @@ -2733,7 +2777,7 @@ def training_log(

# Log MTP metrics.
if args.mtp_num_layers is not None:
mtp_loss_scale = 1 / get_num_microbatches()
mtp_loss_scale = 1 / (num_microbatches or get_num_microbatches())
MTPLossLoggingHelper.track_mtp_metrics(
mtp_loss_scale, iteration, writer, wandb_writer, total_loss_dict
)
Expand Down Expand Up @@ -3671,7 +3715,7 @@ def trace_handler(p):
# Skip automatic checkpoint on microbatch changes when sequence packing is active
# as it intentionally reconfigures microbatches
if get_num_microbatches() != num_microbatches and iteration != 0:
if args.rl_use_sequence_packing:
if args.rl_use_sequence_packing or args.sequence_packing_scheduler is not None:
print_rank_0(
f"[Sequence Packing] Skipping automatic checkpoint at iteration {iteration} "
f"(microbatch change: {num_microbatches} -> {get_num_microbatches()})"
Expand Down Expand Up @@ -3709,6 +3753,9 @@ def trace_handler(p):

# Completely skip iteration if needed.
if (iteration + 1) in args.iterations_to_skip:
assert (
getattr(config, "sequence_packing_scheduler", None) is None
), "Sequence packing scheduler is not supported in skip iteration mode"
# Dummy train_step to fast forward train_data_iterator.
dummy_train_step(train_data_iterator)
if iteration == start_iteration:
Expand Down Expand Up @@ -3755,6 +3802,7 @@ def trace_handler(p):
grad_norm = 0.0
num_zeros_in_grad = 0
max_attention_logit = None
num_microbatches = get_num_microbatches()
else:
ft_integration.on_training_step_start()
(
Expand All @@ -3766,6 +3814,7 @@ def trace_handler(p):
grad_norm,
num_zeros_in_grad,
max_attention_logit,
num_microbatches,
) = train_step(
forward_step_func, train_data_iterator, model, optimizer, opt_param_scheduler, config, forward_backward_func, iteration=iteration,
pg_collection=pg_collection,
Expand Down Expand Up @@ -3911,6 +3960,7 @@ def trace_handler(p):
is_first_iteration=is_first_iteration,
seqlen_squared_sum_in_batch=seqlen_squared_sum_in_batch,
total_real_tokens_in_batch=total_real_tokens_in_batch,
num_microbatches=num_microbatches,
)
is_first_iteration = False

Expand Down Expand Up @@ -4151,11 +4201,31 @@ def evaluate(
# Don't care about timing during evaluation
config.timers = None
ft_integration.on_eval_step_start()
if getattr(config, "sequence_packing_scheduler", None) is not None:
assert isinstance(
eval_pgc, ProcessGroupCollection
), "sequence packing requires the model to expose a ProcessGroupCollection"
try:
(packed_data_iterator, scheduled_eval_num_microbatches, _, _) = (
wrap_data_iterator(
data_iterator,
config,
eval_num_microbatches,
pg_collection=eval_pgc,
)
)
except StopIteration:
ft_integration.on_eval_step_end()
config.timers = get_timers()
break
else:
packed_data_iterator = data_iterator
scheduled_eval_num_microbatches = eval_num_microbatches
loss_dicts = forward_backward_func(
forward_step_func=forward_step_func,
data_iterator=data_iterator,
data_iterator=packed_data_iterator,
model=model,
num_microbatches=eval_num_microbatches,
num_microbatches=scheduled_eval_num_microbatches,
seq_length=args.seq_length,
micro_batch_size=eval_micro_batch_size,
decoder_seq_length=args.decoder_seq_length,
Expand Down
1 change: 1 addition & 0 deletions tests/unit_tests/models/test_hybrid_moe_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@
"use_transformer_engine_op_fuser": False,
"moe_single_grouped_weight": False,
"moe_single_grouped_bias": False,
"sequence_packing_scheduler": None,
}
# Fields to ignore entirely (ephemeral, environment-specific, very large).
SKIP_FIELDS = set()
Expand Down
Loading
Loading