npx skills add ...
npx skills add nvidia/nvidia-resiliency-ext --skill fault-injection-loop
Closed-loop fault injection and attribution accuracy benchmark. Draws from a prioritized pool of (fault_type, rank, iter, nodes) experiments and submits them 2 at a time via sbatch — waiting for each pair to finish before submitting the next — to bound filesystem load. GPU-related faults are front-loaded in the pool. After all jobs complete, runs /log-analysis and /fr-analysis on every experiment, scores attribution vs. ground truth, aggregates gaps, and iterates on attribution modules to close them.
npx skills add nvidia/nvidia-resiliency-ext --skill fault-injection-loop
Iterative closed-loop skill that runs a prioritized fault-injection experiment pool 2 jobs at a time, analyzes every artifact, scores attribution accuracy, aggregates gaps across the matrix, and proposes targeted improvements to attribution modules.
The pool is defined as an ordered list of FAULT_TYPE:RANK:ITER:NODES entries
inside scripts/prepare_node_alloc.sh. Default pool (34 experiments, 17 batches):
Rank coverage per node count (4 GPUs/node):
| Nodes | Total ranks | rank-0 | rank-1 | mid | last |
|---|---|---|---|---|---|
| 2 | 8 | 0 | 1 | 4 | 7 |
| 4 | 16 | 0 | 1 | 8 | 15 |
| 8 | 32 | 0 | 1 | 16 | 31 |
To run a custom subset, override POOL before calling the script:
Start from the tracked template:
Then edit scripts/user.env with cluster-specific settings. This file is
sourced by run_session.sh, prepare_node_alloc.sh, watch_and_analyze.sh,
l4_gb200_reduced.sh, and n3_super_gb200_fi.sh. It is required for this skill
to run and is intended to stay local and untracked.
Recommended contents:
Use user.env for stable site defaults such as partition, container image, and
host paths, plus local LLM credentials and endpoint settings for log-analysis
and the judge. Use per-run environment overrides for experiment-specific
controls such as POOL, WORKLOAD, BATCH_SIZE, FAULT_TYPE,
FAULT_AT_ITER, or FAULT_DELAY.
If you use local Triton/Inductor cache staging, set the cache variables in
scripts/user.env. See scripts/user.env.example for the supported
ENABLE_NFS_CACHE_STAGING, NFS_TRITON_CACHE, and NFS_INDUCTOR_CACHE
entries and workload-specific path examples.
Environment variables:
| Variable | Default | Description |
|---|---|---|
WORKLOAD | llama4_scout | Select a registered workload by name (see scripts/workloads.conf) |
ACCOUNT | (cluster default or scripts/user.env) | SLURM account |
PARTITION | (cluster default or scripts/user.env) | SLURM partition |
GPUS_PER_NODE | 4 | GPUs per node |
TIME | 00:30:00 | Per-job wall-clock limit |
BATCH_SIZE | 2 | Jobs submitted per round |
POLL_INTERVAL | 30 | Seconds between queue polls |
BASE_EXPERIMENTS_DIR | ${HOME}/nvrx-attr-experiments | Root for all output |
MEGATRON_REPO_HOST_PATH | ${HOME}/megatron-lm-main | Host path to the Megatron checkout mounted into the container |
SHARED_TMP_BASE_DIR | ${HOME}/tmp | Shared filesystem path used for cross-step coordination |
WORKSPACE_HOST_PATH | ${HOME}/tmp | Host path mounted at /workspace inside the container |
CONTAINER_IMAGE | nvcr.io/nvidia/nemo:26.04 | Container image used by the workload script |
LLM_API_KEY_FILE | unset | File containing the log-analysis API key |
JUDGE_API_KEY_FILE | unset | File containing the judge API key |
RESTART_AGENT_CONFIG | unset | Optional restart-agent config JSON; when set, it owns model-route behavior |
NVRX_LLM_MODEL | nvidia/nemotron-3-super-120b-a12b | Model for log-analysis |
NVRX_LLM_BASE_URL | https://integrate.api.nvidia.com/v1 | Base URL for log-analysis |
JUDGE_MODEL | qwen/qwen3.5-397b-a17b | Model for judge scoring |
JUDGE_BASE_URL | https://integrate.api.nvidia.com/v1 | Base URL for judge scoring |
FR_SEGMENT_SIZE | 32 | Ranks per segment for coarse FR scoring |
SBATCH_SCRIPT | scripts/l4_gb200_reduced.sh | Job script to submit |
POOL | (default pool above) | Space-separated experiment triplets |
scripts/workloads.conf)| Name | Script | Base dir | Description |
|---|---|---|---|
llama4_scout | l4_gb200_reduced.sh | ${HOME}/nvrx-attr-experiments | Llama4-Scout (reduced layers) on GB200; minimum supported size is 2 nodes |
n3_super | n3_super_gb200_fi.sh | ${HOME}/nvrx-attr-experiments | Nemotron3-Super on GB200; minimum supported size is 8 nodes |
Workload note:
llama4_scout requires at least 2 nodes.n3_super requires at least 8 nodes. Its default registered pool contains only 8-node experiments.The script loops: submit 2 jobs → poll squeue every 30 s until both finish →
submit next 2. Progress is printed inline:
A session directory and TSV tracking file are created at launch time:
Tracking file columns: JOB_ID FAULT_TYPE RANK ITER NODES EXPERIMENT_DIR
Run the watcher/analyzer — it reads the tracking file and processes each experiment as its job state leaves RUNNING/PENDING (works whether jobs are still running or already done):
The watcher:
python -m nvidia_resiliency_ext.attribution.restart_agent.cli
and parses the JSON decisionpython -m nvidia_resiliency_ext.attribution.trace_analyzer.fr_attribution --fr-path "${EXPERIMENT_DIR}/checkpoints" -p "_dump_*" and passes the raw table output to the judge<session>_report.md as a markdown table rowTo also run the sub-skills interactively for a single experiment:
Scoring is performed by scripts/score_attribution.py, an LLM judge that
receives the ground truth, the filtered raw log, the restart-agent attribution output, and the FR
analysis output, then returns structured JSON scores with a reasoning note.
| Column | Values | Meaning |
|---|---|---|
| restart_correct | true / false / N/A | Restart decision matches expected for this fault type |
| rank_primary | true / false / partial | Injected rank is the primary root-cause in attribution |
| rank_any | true / false | Injected rank mentioned anywhere in attribution |
| fault_described | true / false / partial | Fault nature (hang/crash/signal/exception) correctly described |
| fr_rank_correct | rank / node / segment / false / no_dumps | FR analysis narrows correctly to the injected rank, exactly one GPUS_PER_NODE rank block containing that rank, the configured FR_SEGMENT_SIZE rank block containing the injected rank, or fails to narrow usefully |
| judge_notes | string | One-sentence summary of the main gap or confirmation |
The judge is given:
fault_type, rank, iter, nodesscore_attribution.py:_RESTART_TABLE)fr_attribution.py --fr-path ... -p "_dump_*"GPUS_PER_NODE and FR_SEGMENT_SIZE to map the injected rank to exact node-sized and segment-sized scopes for FR scoringDefault judge model: qwen/qwen3.5-397b-a17b. Override with --model in score_attribution.py.
Default segment size for FR scope scoring: 32 ranks. Override with FR_SEGMENT_SIZE.
The canonical output of the loop is the markdown table in <session>_report.md.
When summarizing results for users, prefer linking to that file and reproducing the
same table shape rather than flattening the results into plain prose.
The report markdown table from watch_and_analyze.sh gives a matrix view. Look for
patterns across rows:
Common failure mode patterns and their meaning:
| Pattern | Interpretation |
|---|---|
rank_primary=false, rank_any=true | Rank detected but treated as collateral rather than the primary root cause |
rank_any=false for rank-0 | Rank-0 hang silences watchdog on other ranks; log attribution lacks rank-0 signal |
fault_described=partial for crash types | Crash keywords present but fault type not specifically named |
restart_correct=false for GPU_ERROR | LLM conflating hardware error with recoverable hang |
fr_rank_correct=no_dumps | NCCL watchdog did not fire before job ended — adjust TORCH_NCCL_HEARTBEAT_TIMEOUT_SEC |
fr_rank_correct=node | FR isolated exactly one GPUS_PER_NODE rank block containing the injected rank, but not the exact rank |
fr_rank_correct=segment | FR isolated the configured FR_SEGMENT_SIZE rank block containing the injected rank, but not the exact node/rank |
Deterministic graph algorithm — do not modify automatically. Note misidentifications and escalate to the team.
| Observation | Target location | Suggested fix |
|---|---|---|
| Wrong restart for hang | restart_agent/l0 or restart_agent/l4 | Strengthen NCCL timeout → RESTART mapping |
| Missing rank in attr text | restart_agent/l0 evidence extraction or L1 prompt/tooling | Extract rank from NCCL watchdog lines; add regex |
| Crash misclassified as hang | restart_agent/l0 evidence extraction or L1 prompt/tooling | Add SIGKILL/SEGFAULT/GPU_ERROR keyword patterns |
Missing primary_failure when errors exist | restart_agent/l0 evidence extraction or L1 tooling | Loosen failure-candidate extraction/filtering |
| rank-0 not detected | restart_agent/l0 evidence extraction or L1 prompt/tooling | Add explicit rank-0 hang heuristic (other ranks silent) |
| attr off by many iters | restart_agent/l0 progress/failure-position evidence or L1 prompt | Increase weight of iteration-stamped log lines |
| LLM wrong on GPU_ERROR | restart_agent/l1 prompt or recovery assessment | Distinguish cudaError → crash from NCCL timeout → hang |
Editable area: attribution/restart_agent/
After each patch, re-run the same pool subset that previously failed:
Increment experiment counter. Suggested sweep order across code-change iterations:
Stop condition: all cells pass all four scoring dimensions for two consecutive code-change iterations.
The feedback loop is not tied to l4_gb200_reduced.sh, but your sbatch script must
match a small contract so the loop can submit, analyze, and score each run.
Required changes for a custom workload script:
prepare_node_alloc.sh:
FAULT_TYPE, FAULT_RANK, FAULT_AT_ITER, EXPERIMENT_DIR, BASE_EXPERIMENTS_DIR,
and GPUS_PER_NODE.${EXPERIMENT_DIR}/logs/slurm/${SLURM_JOB_ID}.*.1.main_workload.log
so watch_and_analyze.sh can find it.${EXPERIMENT_DIR}/checkpoints/.watch_and_analyze.sh uses this to decide whether the run reached the injection point.logs/slurm/, checkpoints/, and tensorboard/.This has only been validated with Megatron-LM because the current run-valid check and
fault markers depend on Megatron's debug_fault_injection.py behavior. If you adapt the
loop to another framework, update both the sbatch script and watch_and_analyze.sh.
The example SBATCH_SCRIPT reads these env vars from prepare_node_alloc.sh via --export:
| Variable | Default | Description |
|---|---|---|
FAULT_AT_ITER | 5 | Training iteration at which to inject |
FAULT_DELAY | 15 | Delay in seconds before fault injection after the iteration anchor |
FAULT_RANK | 1 | Global rank to inject [0, total_ranks) |
FAULT_TYPE | GPU_SLEEP | Megatron fault type enum name |
GPUS_PER_NODE | 4 | GPUs per node (used to compute TOTAL_TASKS) |
EXPERIMENT_DIR | ${BASE_EXPERIMENTS_DIR}/fault_injection/n${SLURM_NNODES}_${FAULT_TYPE}_r${FAULT_RANK}_i${FAULT_AT_ITER} | Per-experiment output root |
BASE_EXPERIMENTS_DIR | ${HOME}/nvrx-attr-experiments | Shared root (datacache, triton/inductor caches) |
Valid FAULT_TYPE values:
GPU_ERROR, GPU_SLEEP, WORKLOAD_EXC, ASYNC_EXC, SIGNAL_EXC, OS_ABORT,
LOCK_GIL, SEGFAULT, SIGINT, SIGKILL, SIGTERM, SIGSTOP
Optional site-specific cleanup:
# GPU hangs — highest priority; full rank sweep across all node counts
GPU_SLEEP:1:5:2 GPU_SLEEP:0:5:2 # 2-node: rank-1, rank-0
GPU_SLEEP:4:5:2 GPU_SLEEP:7:5:2 # 2-node: mid-rank, last-rank
GPU_SLEEP:1:5:4 GPU_SLEEP:0:5:4 # 4-node: rank-1, rank-0
GPU_SLEEP:8:5:4 GPU_SLEEP:15:5:4 # 4-node: mid, last
GPU_SLEEP:1:5:8 GPU_SLEEP:0:5:8 # 8-node: rank-1, rank-0
GPU_SLEEP:16:5:8 GPU_SLEEP:31:5:8 # 8-node: mid, last
# GPU errors — high priority; rank-0 and rank-1 across all node counts
GPU_ERROR:1:5:2 GPU_ERROR:0:5:2
GPU_ERROR:1:5:4 GPU_ERROR:0:5:4
GPU_ERROR:1:5:8 GPU_ERROR:0:5:8
# Crash faults
SIGKILL:1:5:2 SIGKILL:0:5:2
SIGKILL:1:5:4 SIGKILL:1:5:8
SEGFAULT:1:5:2 SEGFAULT:0:5:2
SEGFAULT:1:5:4 OS_ABORT:1:5:2
# Python-level hangs
LOCK_GIL:1:5:2 LOCK_GIL:0:5:2
WORKLOAD_EXC:1:5:2 ASYNC_EXC:1:5:2
# Signals
SIGTERM:1:5:2 SIGINT:1:5:2
SIGSTOP:1:5:2 SIGNAL_EXC:1:5:2POOL="GPU_SLEEP:0:5:2 GPU_SLEEP:1:5:2" bash scripts/prepare_node_alloc.shcp scripts/user.env.example scripts/user.envPARTITION=gb-nvl-134-135
BASE_EXPERIMENTS_DIR="${HOME}/nvrx-attr-experiments"
MEGATRON_REPO_HOST_PATH="${HOME}/megatron-lm-main"
SHARED_TMP_BASE_DIR="${HOME}/tmp"
WORKSPACE_HOST_PATH="${HOME}/tmp"
CONTAINER_IMAGE="nvcr.io/nvidia/nemo:26.04"
LLM_API_KEY_FILE="${HOME}/.llm_api_key"
JUDGE_API_KEY_FILE="${HOME}/.llm_api_key"
NVRX_LLM_MODEL="nvidia/nemotron-3-super-120b-a12b"
NVRX_LLM_BASE_URL="https://integrate.api.nvidia.com/v1"
# RESTART_AGENT_CONFIG="/path/to/restart_agent.json"
JUDGE_MODEL="qwen/qwen3.5-397b-a17b"
JUDGE_BASE_URL="https://integrate.api.nvidia.com/v1"
FR_SEGMENT_SIZE=32# Run the full pool against the validated example workload
bash scripts/prepare_node_alloc.sh
# Run a custom subset against llama4_scout
POOL="GPU_SLEEP:1:5:2 SIGKILL:1:5:2" WORKLOAD=llama4_scout bash scripts/prepare_node_alloc.shbash scripts/prepare_node_alloc.sh>>> Batch 1: experiments 1–2 of 34
submitted: GPU_SLEEP rank=1 iter=5 nodes=2 -> job=1850
submitted: GPU_SLEEP rank=0 iter=5 nodes=2 -> job=1851
waiting for GPU_SLEEP:1:5:2 GPU_SLEEP:0:5:2 (1850,1851) ... 30s 60s done.
>>> Batch 2: experiments 3–4 of 34
...${BASE_EXPERIMENTS_DIR}/fault_injection/<YYYYMMDD_HHMMSS>/
experiments.tsv ← tracking file (all job IDs + paths)
n<N>_<FAULT>_r<R>_i<I>/ ← one subdir per experiment
logs/slurm/<JOB_ID>.launch.out
logs/slurm/<JOB_ID>.*.1.main_workload.log ← log-analysis input
checkpoints/ ← fr-analysis input (FR dumps)
tensorboard/
experiments_report.md ← generated by watch_and_analyze.shbash scripts/watch_and_analyze.sh \
${BASE_EXPERIMENTS_DIR}/fault_injection/<YYYYMMDD_HHMMSS>/experiments.tsvpython -m nvidia_resiliency_ext.attribution.restart_agent.cli \
"${EXPERIMENT_DIR}/logs/slurm/${JOB_ID}.*.1.main_workload.log" --job-id "${JOB_ID}"
python -m nvidia_resiliency_ext.attribution.trace_analyzer.fr_attribution \
--fr-path "${EXPERIMENT_DIR}/checkpoints" -p "_dump_*"| FAULT_TYPE | NODES | RANK | restart_correct | rank_primary | rank_any | fault_described | fr_rank_correct | judge_notes |
|------------|-------|------|-----------------|--------------|----------|-----------------|-----------------|-------------|
| GPU_SLEEP | 2 | 0 | true | false | true | true | true | rank-0 identified only in secondary issues |
| GPU_SLEEP | 2 | 1 | true | true | true | true | true | correct on all dimensions |
| GPU_ERROR | 2 | 1 | false | false | false | partial | true | LLM issued RESTART; rank not mentioned |
| SIGKILL | 2 | 0 | true | false | false | false | true | attribution describes timeout not kill signal |POOL="GPU_SLEEP:0:5:2 GPU_ERROR:1:5:2" bash scripts/prepare_node_alloc.sh# Manual runs land under fault_injection/manual/ by default (no session dir needed)
EXPERIMENT_DIR=${HOME}/nvrx-attr-experiments/fault_injection/manual/n2_GPU_SLEEP_r1_i5
mkdir -p ${EXPERIMENT_DIR}/logs/slurm ${EXPERIMENT_DIR}/checkpoints ${EXPERIMENT_DIR}/tensorboard
sbatch \
--nodes=2 \
--output=${EXPERIMENT_DIR}/logs/slurm/%j.launch.out \
--error=${EXPERIMENT_DIR}/logs/slurm/%j.launch.err \
--export=ALL,FAULT_TYPE=GPU_SLEEP,FAULT_RANK=1,FAULT_AT_ITER=5,FAULT_DELAY=15,GPUS_PER_NODE=4,EXPERIMENT_DIR=${EXPERIMENT_DIR} \
scripts/l4_gb200_reduced.shexport CONTAINER_CLEANUP_CMD='
ENROOT_DIR="/var/lib/enroot/data/$(id -u)"
rm -rf "${ENROOT_DIR:?}"/* 2>/dev/null || true
echo "$(hostname): / $(df -h / | tail -1 | awk "{print \$3\" used, \"\$4\" avail\"}")"
'