GPT checkpoint recovery with Megatron-LM

This walkthrough trains a small GPT on mock data, completes an NVRx-backed async checkpoint, injects one worker failure, and resumes from a completed checkpoint. It uses fixed CLI section timeouts. There is no corpus or tokenizer model to download.

Requirements and validation status

Provide a GPU environment with compatible PyTorch, CUDA, NCCL, Transformer Engine, Megatron-LM, and this NVRx checkout installed. All nodes must see the same source paths and work directory. The example does not build a training container or install Megatron’s dependencies.

See Megatron-LM integration for the reviewed Megatron revision and version contract. The wrapper and command construction can be tested without GPUs; end-to-end GPU and Slurm execution must be qualified in the target environment. A successful dry run or documentation build is not evidence of training recovery.

Single-node run

From the NVRx repository root, choose a fresh work directory:

export MEGATRON_PATH=/workspace/Megatron-LM
export NVRX_WORK_DIR=/shared/$USER/nvrx-gpt
export NVRX_RUN_ID=gpt-recovery-001
export GPUS_PER_NODE=2

NVRX_DRY_RUN=1 bash examples/fault_tolerance/run_megatron_gpt.sh
bash examples/fault_tolerance/run_megatron_gpt.sh

The default trains a four-layer, hidden-size-512 GPT for 100 iterations and saves every 5 iterations. It uses BF16, a distributed optimizer, torch_dist, --async-save, and --use-persistent-ckpt-worker. Training/evaluation sections are enabled from the first step with --ft-num-warmup-iters=0. The fixed deadlines are setup:900,step:120,checkpointing:600 and 180 seconds out of section. Qualify these values for your stack.

The script prints the exact launcher/workload command in dry-run mode; it still validates paths and creates the artifact directories. This is not a submission script and does not allocate GPUs. For Slurm or containers, run it inside the allocated training environment.

Fault injection and repeated runs

NVRX_DEMO_FAULT=kill (default) sends SIGKILL to training rank 1 after the latest-checkpoint iteration file advances beyond its startup baseline. NVRX_DEMO_FAULT=hang uses SIGSTOP to exercise timeout detection instead. NVRX_DEMO_FAULT=none disables injection for an uninterrupted baseline. For fault injection on one GPU, set NVRX_FAULT_RANK=0; the default rank 1 needs at least two active GPUs. An uninterrupted baseline ignores the fault rank, so NVRX_DEMO_FAULT=none GPUS_PER_NODE=1 needs no rank override.

The wrapper runs Megatron’s unmodified entrypoint. It claims runs/<run-id>/fault_injected.json exclusively on shared storage before signalling itself, so replacement worker processes do not inject again. It does not modify training state, select checkpoint shards, or implement async saves. If the requested fault never occurs, the selected worker reports failure at normal exit instead of silently presenting an uninterrupted run as recovery.

Use a new run ID for every separate test. The submission shell rejects an old ID containing an injection marker; worker restarts use that marker to avoid repeated injection. To resume an existing training checkpoint into a new launch, also set NVRX_CHECKPOINT_DIR=/shared/path/to/checkpoints. The injector then waits for a new checkpoint beyond that directory’s initial iteration. Ensure the existing tracker is readable and contains an integer at startup: an empty, invalid, or unreadable tracker stops the wrapper before Megatron starts, rather than treating its contents as zero. Only an absent tracker establishes a fresh run’s zero baseline. Temporary read failures during subsequent polling are ignored without changing that baseline. Ensure TRAIN_ITERS exceeds the restored iteration by enough to complete multiple checkpoints. The fixed-timeout script rejects a directory containing calculated ft_state.json; archive that timeout-state file deliberately if appropriate.

Verify recovery

Artifacts live under $NVRX_WORK_DIR/runs/$NVRX_RUN_ID by default:

cat "$NVRX_WORK_DIR/runs/$NVRX_RUN_ID/fault_injected.json"
ls "$NVRX_WORK_DIR/runs/$NVRX_RUN_ID/cycle_infos"
cat "$NVRX_WORK_DIR/runs/$NVRX_RUN_ID/checkpoints/latest_checkpointed_iteration.txt"

Check all of the following, not just a launcher exit status:

  1. A checkpoint completed before the injected failure; record its iteration from the injection marker and correlate it with Megatron’s logs.

  2. The launcher records the failed cycle and starts replacement worker processes. For the hang case, correlate the section-timeout message with the failed rank.

  3. Megatron reports loading a completed training checkpoint, including optimizer state, rather than starting from iteration zero.

  4. Training commits optimizer steps beyond the restored checkpoint, a later async checkpoint completes, and the intended final iteration is reached.

  5. The run finishes successfully without repeated injection or a no-progress stop.

Application logs are logs/train_cycle<N>.log; launcher/monitor logs are routed through logs/launcher.log. Aggregation is best effort, so use cycle records and checkpoint state together. Measure failure-to-first-committed-step latency separately from failure detection and process startup.

Multinode TP/PP example

First validate single-node recovery. For two nodes with four GPUs each, this example uses TP=2, PP=2, DP=2; the active world stays eight GPUs after a restart. Use a shared reachable endpoint and a common run ID:

# Inside an allocation with two nodes and four GPUs per node:
export NNODES=2 GPUS_PER_NODE=4 TP=2 PP=2
export NVRX_RUN_ID=gpt-tp2-pp2-001
export RDZV_ENDPOINT="$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n 1):29400"
srun --ntasks=2 --ntasks-per-node=1 --gpus-per-node=4 \
  bash examples/fault_tolerance/run_megatron_gpt.sh

The script sets global batch size to the active GPU count and microbatch size to one, satisfying divisibility by DP for these shapes. Four model layers must divide by PP and eight attention heads by TP. Additional workload arguments can be appended, but changes to batch/model/checkpoint shape need validation.

This example demonstrates worker recovery within a fixed allocation; a failed physical node can invalidate a normal Slurm allocation. For independently releasable replacement groups, hot/cold spares, and rendezvous-host loss, continue with Singleton job-array deployment (SLURM). Do not substitute allocation shrinking for the array deployment’s replacement behavior.

Source

 1#!/usr/bin/env bash
 2# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 3# SPDX-License-Identifier: Apache-2.0
 4
 5# Small GPT recovery example. See docs/source/fault_tolerance/examples/megatron_gpt.rst.
 6set -euo pipefail
 7
 8SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
 9: "${MEGATRON_PATH:?Set MEGATRON_PATH to an installed Megatron-LM source checkout}"
10: "${NVRX_WORK_DIR:?Set NVRX_WORK_DIR to an absolute directory visible on all nodes}"
11[[ "$NVRX_WORK_DIR" == /* ]] || { echo 'NVRX_WORK_DIR must be absolute' >&2; exit 2; }
12[[ -f "$MEGATRON_PATH/pretrain_gpt.py" ]] || { echo 'pretrain_gpt.py not found' >&2; exit 2; }
13
14NNODES="${NNODES:-1}"
15GPUS_PER_NODE="${GPUS_PER_NODE:-2}"
16TP="${TP:-1}"
17PP="${PP:-1}"
18TRAIN_ITERS="${TRAIN_ITERS:-100}"
19SAVE_INTERVAL="${SAVE_INTERVAL:-5}"
20NVRX_DEMO_FAULT="${NVRX_DEMO_FAULT:-kill}"
21case "$NVRX_DEMO_FAULT" in
22    none|kill|hang) ;;
23    *) echo 'NVRX_DEMO_FAULT must be none, kill, or hang' >&2; exit 2 ;;
24esac
25for value in "$NNODES" "$GPUS_PER_NODE" "$TP" "$PP" "$TRAIN_ITERS" "$SAVE_INTERVAL"; do
26    [[ "$value" =~ ^[1-9][0-9]*$ ]] || { echo 'Shape and iteration settings must be positive integers' >&2; exit 2; }
27done
28WORLD_SIZE=$(( NNODES * GPUS_PER_NODE ))
29(( WORLD_SIZE % (TP * PP) == 0 && 4 % PP == 0 && 8 % TP == 0 )) || {
30    echo 'World size must be divisible by TP*PP; PP must divide four layers and TP eight heads' >&2; exit 2;
31}
32if (( NNODES > 1 )); then
33    : "${RDZV_ENDPOINT:?Set a common reachable host:port for all nodes}"
34    : "${NVRX_RUN_ID:=${SLURM_JOB_ID:-}}"
35    : "${NVRX_RUN_ID:?Set the same unique run ID on all nodes}"
36else
37    RDZV_ENDPOINT="${RDZV_ENDPOINT:-localhost:0}"
38    NVRX_RUN_ID="${NVRX_RUN_ID:-$(date -u +%Y%m%dT%H%M%S)-$$}"
39fi
40[[ "$NVRX_RUN_ID" =~ ^[a-zA-Z0-9_-]+$ ]] || { echo 'Use letters, digits, underscores or hyphens in NVRX_RUN_ID' >&2; exit 2; }
41RUN_DIR="${NVRX_WORK_DIR}/runs/${NVRX_RUN_ID}"
42CHECKPOINT_DIR="${NVRX_CHECKPOINT_DIR:-${RUN_DIR}/checkpoints}"
43[[ "$CHECKPOINT_DIR" == /* ]] || { echo 'NVRX_CHECKPOINT_DIR must be absolute' >&2; exit 2; }
44if [[ -e "$RUN_DIR/fault_injected.json" ]]; then
45    echo 'This run ID already has an injection marker; use a new NVRX_RUN_ID' >&2
46    exit 2
47fi
48mkdir -p "$RUN_DIR/logs" "$RUN_DIR/cycle_infos" "$CHECKPOINT_DIR"
49if [[ -e "$CHECKPOINT_DIR/ft_state.json" ]]; then
50    echo 'This fixed-timeout example requires a checkpoint directory without calculated ft_state.json' >&2
51    exit 2
52fi
53
54launcher=(
55    ft_launcher --nnodes="$NNODES" --nproc-per-node="$GPUS_PER_NODE"
56    --rdzv-backend=c10d --rdzv-endpoint="$RDZV_ENDPOINT" --rdzv-id="$NVRX_RUN_ID"
57    --max-restarts=3
58    --ft-rank-section-timeouts=setup:900,step:120,checkpointing:600
59    --ft-rank-out-of-section-timeout=180
60    --ft-checkpoint-iteration-file="$CHECKPOINT_DIR/latest_checkpointed_iteration.txt"
61    --ft-cycle-info-dir="$RUN_DIR/cycle_infos"
62    --ft-per-cycle-applog-prefix="$RUN_DIR/logs/train.log"
63    --ft-nvrx-logfile="$RUN_DIR/logs/launcher.log"
64)
65workload=(
66    "$SCRIPT_DIR/megatron_gpt_worker.py"
67    --megatron-path "$MEGATRON_PATH" --checkpoint-dir "$CHECKPOINT_DIR"
68    --fault-marker "$RUN_DIR/fault_injected.json" --fault "$NVRX_DEMO_FAULT"
69    --fault-rank "${NVRX_FAULT_RANK:-1}" --
70    --enable-ft-package --ft-num-warmup-iters=0
71    --mock-data --tokenizer-type=NullTokenizer --vocab-size=1024
72    --num-layers=4 --hidden-size=512 --num-attention-heads=8
73    --seq-length=256 --max-position-embeddings=256
74    --tensor-model-parallel-size="$TP" --pipeline-model-parallel-size="$PP"
75    --micro-batch-size=1 --global-batch-size="$WORLD_SIZE"
76    --train-iters="$TRAIN_ITERS" --lr=0.0001 --lr-decay-style=constant
77    --bf16 --use-distributed-optimizer
78    --load="$CHECKPOINT_DIR" --save="$CHECKPOINT_DIR" --save-interval="$SAVE_INTERVAL"
79    --ckpt-format=torch_dist --async-save --use-persistent-ckpt-worker
80    --eval-iters=0 --log-interval=1
81)
82
83printf 'Run artifacts: %s\nCheckpoint directory: %s\n' "$RUN_DIR" "$CHECKPOINT_DIR"
84if [[ "${NVRX_DRY_RUN:-0}" == 1 ]]; then
85    printf '%q ' "${launcher[@]}" "${workload[@]}" "$@"
86    printf '\n'
87    exit 0
88fi
89exec "${launcher[@]}" "${workload[@]}" "$@"

The once-only injection wrapper is examples/fault_tolerance/megatron_gpt_worker.py.