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
638 changes: 636 additions & 2 deletions megatron/core/datasets/data_schedule.py

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions megatron/core/datasets/gpt_dataset.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, we shouldn't be putting configs within configs. We shouldn't be passing around configs as json strings. Is there an alternative here?

Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@ class GPTDatasetConfig(BlendedMegatronDatasetConfig):
context_parallel_size: Optional[int] = None
"""The size of the context parallel group. Needed for padding in packed sequences."""

sft_mock_dataset_config_json: Optional[str] = None
"""This config provides the necessary information for the mock dataset."""

sequence_packing_scheduler: Optional[str] = None
"""Scheduler for sequence packing and hybrid context parallel.
dp_balanced: DP-balanced scheduler for sequence packing.
"""

varlen_mock_dataset_config_json: Optional[str] = None
"""Mock-dataset config (same JSON schema as ``sft_mock_dataset_config_json``)
used by the ``--use-varlen-dataset`` path; kept separate so the varlen path
does not implicitly inherit SFT-specific knobs."""

varlen_sbhd_validation: bool = False
"""When True, :class:`VarlenDataset.__getitem__` emits SBHD samples padded
to ``sequence_length`` (no ``cu_seqlens`` / ``original_seq_len`` /
``padded_seq_len``), bypassing the packed-sequence path. Used to obtain a
SBHD reference run that mirrors the THD path's tokenization but skips all
packing — useful for THD numerical-correctness validation."""

def __post_init__(self) -> None:
"""Do asserts and set fields post init"""
super().__post_init__()
Expand All @@ -86,6 +106,12 @@ def __post_init__(self) -> None:
assert self.reset_attention_mask is not None
assert self.eod_mask_loss is not None

if self.varlen_sbhd_validation:
assert not self.hybrid_context_parallel, (
"--varlen-sbhd-validation is incompatible with "
"--hybrid-context-parallel (SBHD mode is not packed)."
)

self.token_dtype_code = (
None
if self.tokenizer.vocab_size is None
Expand Down
22 changes: 22 additions & 0 deletions megatron/core/datasets/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,28 @@ 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 re-schedules variable-length sequences across DP×CP 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:

- **`HybridCPDataLoaderWrapper`**: A wrapper class for hybrid context parallel (CP) scheduling. For every `__next__` call, it: (1) pulls a batch of packed samples from each DP rank, (2) gathers sequence lengths across the DP group, (3) schedules sub-samples using the `BalancedCPScheduler`, (4) reroutes sub-samples to the correct DPxCP ranks via all-to-all communication.

- **`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, broadcast, 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`. It creates the appropriate scheduler, runs the scheduling pipeline, broadcast metadata and new num_microbatches, returns a new data iterator along with the updated number of microbatches and FLOPs statistics.

- **`get_batch_on_this_rank_for_sequence_packing()`**: Fetches and broadcasts a single packed microbatch for the current rank. Handles TP/PP broadcasting, 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
21 changes: 21 additions & 0 deletions megatron/core/extensions/transformer_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -3391,3 +3391,24 @@ 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, total_tokens, cp_size, cp_rank):
"""Get partitioned indices for THD format data in context parallel.

Args:
cu_seqlens: Cumulative sequence lengths tensor.
total_tokens: Total number of tokens.
cp_size: Context parallel world size.
cp_rank: Context parallel rank.

Returns:
Partitioned indices tensor.
"""
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 sequence_packing_scheduler is not None.
"""

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 sequence packing and hybrid context parallel.
dp_balanced: DP-balanced scheduler for sequence packing.
"""

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

Expand Down
9 changes: 9 additions & 0 deletions megatron/core/pipeline_parallel/schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,9 @@ def forward_backward_no_pipelining(
pg_collection.tp_dp_cp = parallel_state.get_tensor_and_data_parallel_group(
with_context_parallel=True
)
pg_collection.dp = parallel_state.get_data_parallel_group(
with_context_parallel=False, partial_data_parallel=False
)

elif pg_collection is not None:
assert hasattr(pg_collection, 'tp'), "pg_collection must have tp"
Expand Down Expand Up @@ -1001,6 +1004,9 @@ def forward_backward_pipelining_with_interleaving(
pg_collection.tp_dp_cp = parallel_state.get_tensor_and_data_parallel_group(
with_context_parallel=True
)
pg_collection.dp = parallel_state.get_data_parallel_group(
with_context_parallel=False, partial_data_parallel=False
)

elif p2p_communicator is not None and pg_collection is not None:
model_type = get_model_type(model[0])
Expand Down Expand Up @@ -2160,6 +2166,9 @@ def forward_backward_pipelining_without_interleaving(
pg_collection.tp_dp_cp = parallel_state.get_tensor_and_data_parallel_group(
with_context_parallel=True
)
pg_collection.dp = parallel_state.get_data_parallel_group(
with_context_parallel=False, partial_data_parallel=False
)

elif p2p_communicator is not None and pg_collection is not None:
assert hasattr(p2p_communicator, 'config'), "p2p_communicator must have a config"
Expand Down
34 changes: 34 additions & 0 deletions megatron/core/transformer/transformer_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2582,6 +2582,40 @@ def _scope_to_str(s):
self.attention_backend == AttnBackend.flash
), "Batch invariant mode only supports FlashAttention"

if self.sequence_packing_scheduler is not None:
# Check TE version.
if not HAVE_PACKAGING:
raise ImportError(
"packaging is not installed. Please install it with `pip install packaging`."
)
# TODO: remove this after we fix the convergence issue with TE < 2.9.
if not (
is_te_min_version("2.9.0") or get_te_version() == PkgVersion("2.9.0.dev0+5b3092a")
):
raise ValueError(
"SFT sequence packing requires Transformer Engine >= 2.9.0 "
f"but got {get_te_version()} (TE < 2.9.0 may have convergence issues)."
)

# Needed for passing variable sequences between pp stages.
self.variable_seq_lengths = True

# TODO(tailaim): add support for other dispatcher types
assert self.moe_token_dispatcher_type == "alltoall", (
f"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 is not None
and 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
142 changes: 133 additions & 9 deletions megatron/training/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def add_megatron_arguments(parser: argparse.ArgumentParser):
parser = _add_msc_args(parser)
parser = _add_kitchen_quantization_arguments(parser)
parser = _add_sft_args(parser)
parser = _add_varlen_dataset_args(parser)

parser = _add_fault_injector_args(parser)

Expand Down Expand Up @@ -1202,13 +1203,6 @@ def validate_args(args, defaults={}):
if args.rl_use_sequence_packing:
args.consumed_train_bins = 0

# Support for variable sequence lengths across batches/microbatches.
# set it if the dataloader supports generation of variable sequence lengths
# across batches/microbatches. Due to additional communication overhead
# during pipeline parallelism, it should not be set if sequence length
# is constant during training.
args.variable_seq_lengths = False

# Iteration-based training.
# Skip these checks when skip_train is set: LR config is irrelevant.
if args.train_iters and not args.skip_train:
Expand Down Expand Up @@ -1380,6 +1374,23 @@ def validate_args(args, defaults={}):
assert args.dataloader_type == 'single', 'Hybrid context parallelism only supported with single dataloader type'
assert args.calculate_per_token_loss, 'Hybrid context parallelism must be used with --calculate-per-token-loss'

# Support for variable sequence lengths across batches/microbatches.
# set it if the dataloader supports generation of variable sequence lengths
# across batches/microbatches. Due to additional communication overhead
# during pipeline parallelism, it should not be set if sequence length
# is constant during training.
args.variable_seq_lengths = False
if args.mock_data and args.sft and args.sft_mock_dataset_config_json is None:
args.sft_mock_dataset_config_json = json.dumps(
{
"mode": "distribution",
"type": "lognormal",
"min_seq_len": args.seq_length // 2,
"max_seq_len": args.seq_length,
"mean_seq_len": args.seq_length // 4 * 3,
"lognormal_sigma": 1.1,
}
)
# disable async_tensor_model_parallel_allreduce when
# model parallel memory optimization is enabled
if (args.tensor_model_parallel_size > 1 or args.context_parallel_size > 1) \
Expand Down Expand Up @@ -1493,6 +1504,47 @@ 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."

# --use-varlen-dataset: independent of --sft. Cannot be combined with --sft
# because they are mutually-exclusive top-level dataset selectors that both
# drive the packed-sequence (THD) path.
if args.use_varlen_dataset:
assert not args.sft, (
"--use-varlen-dataset and --sft are mutually exclusive; both "
"select the packed-sequence dataset family. Pick one."
)
if args.varlen_sbhd_validation:
assert args.sequence_packing_scheduler is None, (
"--varlen-sbhd-validation does not use a sequence packing "
"scheduler; drop --sequence-packing-scheduler."
)
# SBHD validation is a real-data numerical-reference path only;
# MockVarlenDataset does not implement it.
assert not args.mock_data, (
"--varlen-sbhd-validation is not supported with --mock-data; "
"SBHD validation requires a real dataset."
)
else:
# VarlenDataset emits one unpacked sample per __getitem__; it
# relies on an upstream packing scheduler to group variable-length
# samples into THD batches. Auto-pick a default scheduler when
# the user did not request one explicitly:
# Otherwise fall back to ``dp_balanced`` (static packing).
if args.sequence_packing_scheduler is None:
args.sequence_packing_scheduler = 'dp_balanced'

# Packed-sequence buffer-size check. Placed after varlen scheduler
# auto-select so it validates the final resolved scheduler.
if args.sequence_packing_scheduler is not None:
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"
)
total_cp_ranks = args.context_parallel_size
assert total_cp_ranks * args.max_seqlen_per_dp_cp_rank >= args.seq_length, (
f'Packed sequence buffer size ({total_cp_ranks * args.max_seqlen_per_dp_cp_rank}) '
f'must be >= single sequence max length ({args.seq_length})'
)

# Data blend checks
assert args.mock_data + \
bool(args.data_path) + \
Expand Down Expand Up @@ -2117,6 +2169,9 @@ def _add_network_size_args(parser):
"persist_layer_norm",
"bias_dropout_fusion",
"apply_rope_fusion",
"max_seqlen_per_dp_cp_rank",
"hybrid_context_parallel",
"sequence_packing_scheduler",
]
transformer_factory = ArgumentGroupFactory(TransformerConfig, exclude=exclude)
transformer_group = transformer_factory.build_group(parser, "transformer configuration")
Expand Down Expand Up @@ -2872,6 +2927,14 @@ 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('--max-seqlen-per-dp-cp-rank', type=int, default=None,
help='Maximum sequence length per CP rank. This is used to calculate the '
'number of sub-samples assigned to each CP rank when using heterogeneous context parallel.')
group.add_argument('--hybrid-context-parallel', action='store_true', default=False,
help='Enables hybrid context parallel. This is used to balance the workload '
'of each CP rank when we use packed samples with variable sequence lengths. '
'Requires --max-seqlen-per-dp-cp-rank to be set.')
group.add_argument('--sequence-packing-scheduler', type=str, default=None, choices=['dp_balanced'])
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 Expand Up @@ -3405,8 +3468,69 @@ def _add_kitchen_quantization_arguments(parser: argparse.ArgumentParser):
def _add_sft_args(parser):
group = parser.add_argument_group(title='sft')
group.add_argument('--sft', action="store_true", help='Megatron SFT training')
group.add_argument('--sft-tokenizer-prompt-format', type=str, default="nemotron-h-aligned",
help='SFT prompt format.')
group.add_argument(
'--sft-tokenizer-prompt-format',
type=str,
default="nemotron-h-aligned",
help='SFT prompt format.',
)
group.add_argument(
'--sft-mock-dataset-config-json',
type=str,
default=None,
help='This config provides the necessary information for the mock dataset. '
'Accepts either an inline JSON literal or a path to a JSON file containing '
'the same schema. You can either specify a CSV file that contains sequence lengths, '
'where each line stores the length of a sequence, for example: '
'{"mode":"file","path":"/path/to/file"}. Alternatively, you can specify a distribution '
'(currently only supporting lognormal distribution) along with the required parameters, '
'for example, {"mode":"distribution","type":"lognormal","min_seq_len":1024,'
'"max_seq_len":2048,"mean_seq_len":1536,"lognormal_sigma":1.1}, where sigma controls '
'the variability of the lognormal distribution. '
'If not specified and --mock-data is set, defaults to a lognormal distribution with '
'min_seq_len=seq_length//2, max_seq_len=seq_length, mean_seq_len=seq_length*3//4, lognormal_sigma=1.1.',
)
return parser


def _add_varlen_dataset_args(parser):
group = parser.add_argument_group(title='varlen dataset')
group.add_argument(
'--use-varlen-dataset',
action="store_true",
help='Train with VarlenDataset, a variable-length packed (THD) dataset '
'that consumes instruction-tuning data from a HuggingFace Hub repo id, '
'a local parquet file, or a local jsonl file. Schema (alpaca / sharegpt '
'/ openai-messages) is auto-detected from the dataset columns. '
'Mutually exclusive with --sft. Auto-picks a sequence packing '
'scheduler when none is given: ``dp_balanced``. '
'Combine with --mock-data for a synthetic lognormal sequence-length '
'distribution; see --varlen-mock-dataset-config-json.',
)
group.add_argument(
'--varlen-sbhd-validation',
action="store_true",
help='Reference SBHD mode for THD numerical verification. When set, '
'VarlenDataset emits SBHD-style samples right-padded to '
'--seq-length (no cu_seqlens, no packing scheduler), so the run can '
'be compared against the THD path to validate correctness. '
'Incompatible with --sequence-packing-scheduler.',
)
group.add_argument(
'--varlen-mock-dataset-config-json',
type=str,
default=None,
help='Mock-dataset config for --use-varlen-dataset --mock-data. '
'Accepts either an inline JSON literal or a path to a JSON file containing '
'the same schema as --sft-mock-dataset-config-json: either '
'{"mode":"file","path":"/path/to/lengths.csv"}, '
'{"mode":"distribution","type":"lognormal","min_seq_len":1024,'
'"max_seq_len":2048,"mean_seq_len":1536,"lognormal_sigma":1.1}, or '
'{"mode":"verification","data_path":"/prefix/of/IndexedDataset"}. '
'If not specified, defaults to a lognormal distribution with '
'min_seq_len=seq_length//2, max_seq_len=seq_length, '
'mean_seq_len=seq_length*3//4, lognormal_sigma=1.1.',
)
return parser

def _add_logits_distillation_args(parser):
Expand Down
Loading
Loading