mirror of
https://github.com/hanzo-ml/trainer.git
synced 2026-09-24 00:31:34 +00:00
* fix(e2e): bump Megatron notebook Complete-wait timeout to 10m The Megatron TP notebook waits for the TrainJob to reach Complete with timeout=120. On the oracle-vm-gpu-a10-1 CI runner the happy-path time from TrainJob creation to Complete is ~96s (measured on the last passing GPU E2E run, 2026-04-15). Any runner slowdown, image-pull delay, or GPU-advertisement latency on top of that pushes the test past the 120s budget and papermill raises TimeoutError, even though the TrainJob is still on track to finish. Every GPU E2E run has been failing here since 2026-04-16 ~16:00 UTC on every branch, with no functional repo change between the last pass and first fail. Bumping to 600s gives enough headroom for cold image pulls and scheduling variance without masking real failures (papermill's outer --execution-timeout is 1800s). Signed-off-by: XploY04 <[email protected]> * fix(examples): update Megatron tokenizer library to null-text for 0.17.0 megatron-core 0.17.0 (published 2026-04-16 20:22 UTC) tightened the set of accepted tokenizer library names in MegatronTokenizer.from_pretrained. The bare "null" value is no longer accepted; the null-tokenizer keys are now "null-text" and "null-multimodal": 0.16.1: if library not in ['byte-level', 'null']: assert tokenizer_path 0.17.0: if library not in ['byte-level', 'null-text', 'null-multimodal']: assert tokenizer_path The notebook's call with metadata_path={"library": "null"} therefore triggers `AssertionError: Tokenizer path must be specified.` on the GPU E2E runner, which now installs 0.17.0 by default. Renaming to "null-text" routes through the same NullTokenizer(vocab_size) library class the old "null" key used, so behavior is unchanged on 0.17.0. This explains why GPU E2E ran green on 2026-04-15 (installed 0.16.1) and started failing with the tokenizer assertion once 0.17.0 landed on PyPI. The previous commit's 120s -> 600s wait timeout bump is what made this assertion visible; with the old 120s budget the notebook timed out before the TrainJob reached Failed and the real error was hidden. Signed-off-by: XploY04 <[email protected]> --------- Signed-off-by: XploY04 <[email protected]>
15 KiB
15 KiB
In [ ]:
# !pip install -U kubeflowIn [ ]:
def train_megatron_gpt_tp():
import os
import torch
from torch.optim import Adam
from torch.utils.data import DataLoader
from functools import partial
from megatron.core import parallel_state
from megatron.core.pipeline_parallel.schedules import get_forward_backward_func
from megatron.core.tensor_parallel.random import model_parallel_cuda_manual_seed
from megatron.core.transformer.transformer_config import TransformerConfig
from megatron.core.models.gpt.gpt_model import GPTModel
from megatron.core.models.gpt.gpt_layer_specs import get_gpt_layer_local_spec
from megatron.core.datasets.utils import compile_helpers
from megatron.core.datasets.blended_megatron_dataset_builder import (
BlendedMegatronDatasetBuilder,
)
from megatron.core.datasets.gpt_dataset import GPTDatasetConfig, MockGPTDataset
from megatron.core.distributed import DistributedDataParallel
from megatron.core.distributed import DistributedDataParallelConfig
from megatron.core.distributed.finalize_model_grads import finalize_model_grads
from megatron.core.tokenizers import MegatronTokenizer
_SEQUENCE_LENGTH = 64
# ----------------------------------------------------------------
# Step 1: Initialize torch.distributed and Megatron model parallel
# ----------------------------------------------------------------
# tensor_model_parallel_size=2 means each layer's weight matrices
# are split across 2 GPUs. This is the core of Tensor Parallelism.
parallel_state.destroy_model_parallel()
rank = int(os.environ["RANK"])
world_size = int(os.environ["WORLD_SIZE"])
local_rank = int(os.environ["LOCAL_RANK"])
torch.cuda.set_device(local_rank)
torch.distributed.init_process_group(
backend="nccl", rank=rank, world_size=world_size
)
tensor_model_parallel_size = int(os.environ["TP_SIZE"])
pipeline_model_parallel_size = 1
parallel_state.initialize_model_parallel(
tensor_model_parallel_size, pipeline_model_parallel_size
)
# Set a fixed seed for reproducibility across tensor-parallel ranks.
model_parallel_cuda_manual_seed(123)
# ----------------------------------------------------------------
# Step 2: Build a small GPT model
# ----------------------------------------------------------------
transformer_config = TransformerConfig(
num_layers=2,
hidden_size=12,
num_attention_heads=4,
use_cpu_initialization=True,
pipeline_dtype=torch.float32,
)
gpt_model = GPTModel(
config=transformer_config,
transformer_layer_spec=get_gpt_layer_local_spec(),
vocab_size=100,
max_sequence_length=_SEQUENCE_LENGTH,
)
# Move model from CPU to GPU (use_cpu_initialization=True builds weights on CPU).
gpt_model.to("cuda")
# Wrap with DistributedDataParallel for gradient synchronization.
ddp_config = DistributedDataParallelConfig(
grad_reduce_in_fp32=False,
overlap_grad_reduce=False,
use_distributed_optimizer=False,
)
gpt_model = DistributedDataParallel(
config=transformer_config,
ddp_config=ddp_config,
module=gpt_model,
)
optim = Adam(gpt_model.parameters())
# ----------------------------------------------------------------
# Step 3: Prepare a mock dataset (no real data download needed)
# ----------------------------------------------------------------
# Install build tools needed to compile Megatron's C++ dataset helpers.
import subprocess as _sp
_sp.run(["apt-get", "update", "-qq"], capture_output=True)
_sp.run(["apt-get", "install", "-y", "-qq", "make", "g++"], capture_output=True)
import urllib.request as _urlreq
_datasets_dir = os.path.dirname(compile_helpers.__code__.co_filename)
_base_url = "https://raw.githubusercontent.com/NVIDIA/Megatron-LM/main/megatron/core/datasets/"
for _f in ["Makefile", "helpers.cpp"]:
_urlreq.urlretrieve(_base_url + _f, os.path.join(_datasets_dir, _f))
if torch.distributed.is_available() and torch.distributed.is_initialized():
if torch.distributed.get_rank() == 0:
compile_helpers()
torch.distributed.barrier()
else:
compile_helpers()
config = GPTDatasetConfig(
random_seed=0,
sequence_length=_SEQUENCE_LENGTH,
reset_position_ids=False,
reset_attention_mask=False,
eod_mask_loss=False,
tokenizer=MegatronTokenizer.from_pretrained(
metadata_path={"library": "null-text"},
vocab_size=_SEQUENCE_LENGTH,
),
mid_level_dataset_surplus=0.005,
)
datasets = BlendedMegatronDatasetBuilder(
MockGPTDataset, [1000, None, None], lambda: True, config
).build()
train_dataloader = DataLoader(datasets[0], batch_size=8, shuffle=True)
train_iterator = iter(train_dataloader)
# ----------------------------------------------------------------
# Step 4: Define forward step function
# ----------------------------------------------------------------
def forward_step_func(data_iterator, model):
def loss_func(loss_mask, output_tensor):
losses = output_tensor.float()
loss_mask = loss_mask.view(-1).float()
loss = torch.sum(losses.view(-1) * loss_mask) / loss_mask.sum()
return loss, {"lm loss": loss}
data = next(data_iterator)
tokens = data["tokens"].to("cuda")
attention_mask = data["attention_mask"].to("cuda")
position_ids = data["position_ids"].to("cuda")
labels = data["labels"].to("cuda")
loss_mask = data["loss_mask"].to("cuda")
output_tensor = model(tokens, position_ids, attention_mask, labels=labels)
return output_tensor, partial(loss_func, loss_mask)
# ----------------------------------------------------------------
# Step 5: Training loop — 5 iterations
# ----------------------------------------------------------------
forward_backward_func = get_forward_backward_func()
for iteration in range(5):
optim.zero_grad()
losses_reduced = forward_backward_func(
forward_step_func=forward_step_func,
data_iterator=train_iterator,
model=gpt_model,
num_microbatches=1,
seq_length=_SEQUENCE_LENGTH,
micro_batch_size=8,
decoder_seq_length=_SEQUENCE_LENGTH,
forward_only=False,
)
# Synchronize gradients across TP and DP groups.
finalize_model_grads([gpt_model])
optim.step()
print(f"Iteration {iteration}: Losses reduced: {losses_reduced}")
torch.distributed.destroy_process_group()In [ ]:
from kubeflow.trainer import TrainerClient, CustomTrainer
client = TrainerClient()
torch_runtime = None
for runtime in client.list_runtimes():
print(runtime)
if runtime.name == "torch-distributed":
torch_runtime = runtime
break
if torch_runtime is None:
raise ValueError("torch-distributed runtime not found")In [ ]:
num_nodes = 2
num_gpu = 1
tensor_model_parallel_size = 2In [ ]:
job_name = client.train(
trainer=CustomTrainer(
func=train_megatron_gpt_tp,
num_nodes=num_nodes,
resources_per_node={
"memory": "16Gi",
"gpu": num_gpu,
},
packages_to_install=["megatron-core", "pybind11"],
env={"TP_SIZE": str(tensor_model_parallel_size)},
),
runtime=torch_runtime,
)In [ ]:
# Wait for the running status.
client.wait_for_job_status(name=job_name, status={"Running"})In [ ]:
for c in client.get_job(name=job_name).steps:
print(f"Step: {c.name}, Status: {c.status}, Devices: {c.device} x {c.device_count}\n")In [ ]:
for logline in client.get_job_logs(job_name, follow=True):
print(logline)In [ ]:
client.wait_for_job_status(name=job_name, timeout=600)In [ ]:
# client.delete_job(job_name)