Skip to content
Open
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
63 changes: 62 additions & 1 deletion megatron/training/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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 @@ -1688,7 +1689,36 @@ 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."

# Scheduler-name and max-seqlen validation live in
# --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. These stay in validate_args: the
# selectors are CLI-level args, not core config fields.
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 ``dp_balanced`` when the
# user did not request one explicitly.
if args.sequence_packing_scheduler is None:
args.sequence_packing_scheduler = 'dp_balanced'

# Runs after the varlen auto-select above so it sees the final resolved
# scheduler. Scheduler-name and max-seqlen validation live in
# ModelParallelConfig.__post_init__; only the buffer-size check stays here
# because seq_length is not a core config field. The None case for
# max_seqlen_per_dp_cp_rank is rejected by the config check.
Expand Down Expand Up @@ -3853,6 +3883,37 @@ def _add_sft_args(parser):
'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):
group = parser.add_argument_group(title='Logits Distillation')

Expand Down
19 changes: 14 additions & 5 deletions megatron/training/datasets/data_samplers.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def build_pretraining_data_loader(dataset, consumed_samples):
data_parallel_rank=mpu.get_data_parallel_rank(),
data_parallel_size=mpu.get_data_parallel_world_size())
elif args.dataloader_type == 'single':
if args.hybrid_context_parallel:
if args.hybrid_context_parallel and args.sequence_packing_scheduler is None:

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.

not in this PR but we should change everything to "dynamic" now?

batch_sampler = HybridCPMegatronPretrainingSampler(
total_samples=len(dataset),
consumed_samples=consumed_samples,
Expand All @@ -61,7 +61,8 @@ def build_pretraining_data_loader(dataset, consumed_samples):
data_parallel_rank=mpu.get_data_parallel_rank(),
data_parallel_size=mpu.get_data_parallel_world_size())
else:
# Megatron sampler
# Megatron sampler. Packing schedulers consume one microbatch at a
# time and form packed global batches themselves.
batch_sampler = MegatronPretrainingSampler(
total_samples=len(dataset),
consumed_samples=consumed_samples,
Expand Down Expand Up @@ -103,9 +104,17 @@ def close_nvidia_fds():
maybe_worker_init_fn = (
worker_init_fn if args.num_workers > 0 else None
)
# Torch dataloader.
if args.hybrid_context_parallel:
extra_kwargs = {"collate_fn": lambda x: x,}
# Identity collate for VarlenDataset and packing-scheduler paths; they emit
# one variable-length dict per sample, not stack-able by the default
# collate. --varlen-sbhd-validation is excluded: it bypasses packing and
# emits fixed-length [seq_length] samples that the default collate stacks
# normally.
if (
(args.use_varlen_dataset and not args.varlen_sbhd_validation)
or args.hybrid_context_parallel
or args.sequence_packing_scheduler is not None
):
extra_kwargs = {"collate_fn": lambda x: x}
else:
extra_kwargs = {}
return torch.utils.data.DataLoader(
Expand Down
62 changes: 54 additions & 8 deletions megatron/training/training.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
# First-party.
from megatron.core._rank_utils import safe_get_rank
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 @@ -261,6 +261,7 @@
# never call ``update_*`` so the flag stays ``False`` and no collective fires.
_seqlen_stats_in_iteration: Optional[torch.Tensor] = None
_seqlen_stats_active: bool = False
_seqlen_stats_are_global: bool = False

# Only report memory for first 3 checkpoint saves.
num_checkpoints_memory_reported = 0
Expand Down Expand Up @@ -728,7 +729,7 @@ def update_seqlen_stats_from_cu_seqlens(cu_seqlens):
the all-reduce; BSHD callers that never invoke this function leave the
flag at ``False`` and pay zero collective cost.
"""
global _seqlen_stats_in_iteration, _seqlen_stats_active
global _seqlen_stats_in_iteration, _seqlen_stats_active, _seqlen_stats_are_global
if cu_seqlens is None or cu_seqlens.numel() < 2:
return
# Pin the accumulator to the current CUDA device when available so the
Expand All @@ -747,6 +748,21 @@ def update_seqlen_stats_from_cu_seqlens(cu_seqlens):
_seqlen_stats_in_iteration[0] += seqlens.sum()
_seqlen_stats_in_iteration[1] += (seqlens * seqlens).sum()
_seqlen_stats_active = True
_seqlen_stats_are_global = False


def set_seqlen_stats_in_iteration(total_real_tokens, seqlen_squared_sum):
"""Seed per-iteration THD FLOPs stats that were already computed globally."""
global _seqlen_stats_in_iteration, _seqlen_stats_active, _seqlen_stats_are_global
if total_real_tokens is None or seqlen_squared_sum is None:
return
if _seqlen_stats_in_iteration is None:
device = torch.device(f'cuda:{torch.cuda.current_device()}') if torch.cuda.is_available() else 'cpu'
_seqlen_stats_in_iteration = torch.zeros(2, dtype=torch.float64, device=device)
_seqlen_stats_in_iteration[0] = float(total_real_tokens)
_seqlen_stats_in_iteration[1] = float(seqlen_squared_sum)
_seqlen_stats_active = True
_seqlen_stats_are_global = True


def consume_seqlen_stats_in_iteration() -> Tuple[Optional[float], Optional[float]]:
Expand Down Expand Up @@ -774,13 +790,15 @@ def consume_seqlen_stats_in_iteration() -> Tuple[Optional[float], Optional[float
replicated across TP/CP/PP); the world all-reduce therefore overcounts by a
factor of ``TP * CP * PP``, which we divide out.
"""
global _seqlen_stats_in_iteration, _seqlen_stats_active
global _seqlen_stats_in_iteration, _seqlen_stats_active, _seqlen_stats_are_global
if not _seqlen_stats_active:
# BSHD path: never allocated the tensor; tell the caller to use the
# closed-form defaults.
return None, None
t = _seqlen_stats_in_iteration
if torch.distributed.is_initialized() and mpu.model_parallel_is_initialized():
if _seqlen_stats_are_global:
dedup = 1
elif torch.distributed.is_initialized() and mpu.model_parallel_is_initialized():
torch.distributed.all_reduce(t)
tp_size = max(mpu.get_tensor_model_parallel_world_size(), 1)
cp_size = max(mpu.get_context_parallel_world_size(), 1)
Expand All @@ -796,6 +814,7 @@ def consume_seqlen_stats_in_iteration() -> Tuple[Optional[float], Optional[float
# iterations reuse it without reallocating.
t.zero_()
_seqlen_stats_active = False
_seqlen_stats_are_global = False
return total_real_tokens / dedup, seqlen_squared_sum / dedup


Expand Down Expand Up @@ -3089,6 +3108,20 @@ def train_step(forward_step_func, data_iterator, model, optimizer, opt_param_sch
for optim_instance in mxfp8_overlap_optimizers:
optim_instance._copy_main_params_to_param_buffer()

if getattr(config, "sequence_packing_scheduler", None) is not None:
(
data_iterator,
scheduled_num_microbatches,
total_real_tokens_in_batch,
seqlen_squared_sum_in_batch,
) = wrap_data_iterator(data_iterator, config, get_num_microbatches())
set_seqlen_stats_in_iteration(
total_real_tokens_in_batch,
seqlen_squared_sum_in_batch,
)
else:
scheduled_num_microbatches = get_num_microbatches()

# Forward pass.
if save_activations_in_this_iteration:
enable_activation_logging(model, args.save)
Expand All @@ -3098,15 +3131,15 @@ def train_step(forward_step_func, data_iterator, model, optimizer, opt_param_sch
enable_dgrad_logging(model, args.save)
grad_context, forward_only = _forward_backward_grad_context(args)
_fb_cm = (
span_cm("megatron.train.iteration.forward_backward", tracer=_otel_step_tracer, num_microbatches=get_num_microbatches())
span_cm("megatron.train.iteration.forward_backward", tracer=_otel_step_tracer, num_microbatches=scheduled_num_microbatches)
if _otel_sg_enabled('forward_backward') and _otel_step_tracer is not None else nullcontext()
)
with grad_context, _fb_cm:
losses_reduced = forward_backward_func(
forward_step_func=forward_step_func,
data_iterator=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 @@ -4660,6 +4693,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 @@ -5196,13 +5232,23 @@ 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:
try:
(packed_data_iterator, scheduled_eval_num_microbatches, _, _) = (
wrap_data_iterator(data_iterator, config, eval_num_microbatches)
)
except StopIteration:
break
else:
packed_data_iterator = data_iterator
scheduled_eval_num_microbatches = eval_num_microbatches
with _otel_managed_span('evaluate', 'megatron.evaluate.step',
**{'megatron.eval_iteration': iteration}):
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
Loading
Loading