This document describes fork/local additions for training and evaluating a video guardrail model on SafeWatch-style SFT JSONL (intervals + multi-label categories C1–C6), using the Nemotron Nano V2 VL LoRA recipe in Megatron-Bridge.
make_safewatch_datasetLocation: src/megatron/bridge/data/vlm_datasets/hf_dataset_makers.py (make_safewatch_dataset)
Role: Load SafeWatch SFT JSONL (e.g. sft_train_v2.jsonl / sft_eval_v2.jsonl) into conversation examples the Nemotron VL collate path expects: user turns include {"type":"video","path": "<mp4>"} and assistant targets carry structured JSON (shots, intervals, guardrails).
Typical YAML (examples/recipes/nemotron_vl/conf/nemotron_nano_v2_vl_safewatch.yaml):
dataset:
do_test: false # see §2 — avoids duplicate eval on the same eval JSONL
sequence_length: 8192
maker_name: make_safewatch_dataset
maker_kwargs:
train_jsonl_path: /path/to/sft_train_v2.jsonl
eval_jsonl_path: /path/to/sft_eval_v2.jsonl
max_examples: 100
require_video_exists: true
Provider wiring: HFDatasetConversationProvider in src/megatron/bridge/data/vlm_datasets/hf_provider.py registers make_safewatch_dataset and builds train / validation / test splits from the maker. SafeWatch maps both validation and test splits to the same eval_jsonl_path; without do_test: false, the training driver would run two full eval passes (validation + test) on the same data.
| Area | File(s) | Change |
|---|---|---|
| Skip duplicate test eval | hf_provider.py | do_test: bool — when false, no test dataset is built. |
| Safe loaders | src/megatron/bridge/data/loaders.py | Build val/test loaders only if valid_ds / test_ds is not None. |
| Eval-only + JSONL dump | src/megatron/bridge/training/eval.py | If skip_train and logger.eval_dump_dir + eval_gt_jsonl_path are set and eval_dump_max_records is a finite ≥1, the dataloader eval loop runs 0 steps; metrics come from maybe_dump_eval_gt_pred_json (GT JSONL–driven forwards). Keeps eval_iters>0 so validation still runs and dump executes. Do not set train.eval_iters=0 for this path — that disables validation entirely in loader flags. |
| Artifacts | src/megatron/bridge/training/eval_dump.py | Writes iter_<train_step>/records.jsonl under logger.eval_dump_dir (teacher-forced supervised span, not full autoregressive generation). |
| Config | src/megatron/bridge/training/config.py | Documents eval_dump_dir, eval_gt_jsonl_path, eval_dump_max_records. |
train.shexamples/recipes/nemotron_vl/finetune_nemotron_nano_v2_vl.py with nemotron_nano_v2_vl_safewatch.yaml.--pretrained-checkpoint (e.g. Megatron Nemotron VL checkpoint dir).torchrun --nproc-per-node=... for multi-GPU data parallel.WANDB_API_KEY in the environment (do not commit secrets); YAML/logger overrides for project name can be passed on the CLI.inference.shtrain.skip_train=true.checkpoint.load=<results_dir> and checkpoint.ckpt_step=<step> (e.g. step 100 → iter_0000100 checkpoint layout under that directory).--pretrained-checkpoint for architecture / tokenizer binding.logger.eval_gt_jsonl_path and logger.eval_dump_dir for dump location.results/.../eval_json_full/iter_<step>/records.jsonl (one JSON object per line).eval.shscripts/eval_interval_guardrail_metrics.py on one or more eval_dump roots (directories containing iter_*/records.jsonl).<eval_dump_dir>/
iter_0000100/
records.jsonl # one record per line: gt intervals, pred intervals, optional dump fields
Legacy flat iter_*_pred_*.json / iter_*/pred_*.json are still supported by the metrics script when records.jsonl is absent.
scripts/eval_interval_guardrail_metrics.py)Inputs: --dirs (one or more roots), --tiou-threshold or --tiou-thresholds, --unmatched-policy (strict | matched_only).
Notable outputs:
chunks= / bits=: guardrail rows (segment-level vector evals) vs total binary decisions.Optional: --csv, --json-out.
After running eval.sh or the Python command directly, you may see blocks similar to:
=== tIoU>=0.5 unmatched=strict ===
100 | f1=0.789 f1_all=0.752 tiou=0.860 cov=0.404 hit=0.44 n_f1=14/32
--- chunk_pool: counts, TN, accuracy, F1 ---
100 | chunks=83 bits=498 acc=0.970 | pool_f1=0.516 P=0.421 R=0.667 | tp=8 fp=11 fn=4 tn=475 | f1: C1(...)=1.000 ... | acc: C1(...)=1.000 ...
Numbers depend on dataset size, τ, checkpoint, and whether eval_dump used a subset (eval_dump_max_records).
With train_iters optimizer steps and global_batch_size samples per step:
[ \text{approx epochs} \approx \frac{\texttt{train_iters} \times \texttt{global_batch_size}}{N} ]
where (N) is the number of unique training rows after caps/filters (e.g. max_examples and require_video_exists).
| Component | Path |
|---|---|
| Finetune driver | examples/recipes/nemotron_vl/finetune_nemotron_nano_v2_vl.py |
| Recipe factory | src/megatron/bridge/recipes/nemotron_vl/nemotron_nano_v2_vl.py |
| SafeWatch maker | src/megatron/bridge/data/vlm_datasets/hf_dataset_makers.py |
| HF conversation provider | src/megatron/bridge/data/vlm_datasets/hf_provider.py |
| Pretrain / eval driver | src/megatron/bridge/training/pretrain.py |
| Eval + dump hook | src/megatron/bridge/training/eval.py |
3 commits
Python
99.1%
This document describes fork/local additions for training and evaluating a video guardrail model on SafeWatch-style SFT JSONL (intervals + multi-label categories C1–C6), using the Nemotron Nano V2 VL LoRA recipe in Megatron-Bridge.
make_safewatch_datasetLocation: src/megatron/bridge/data/vlm_datasets/hf_dataset_makers.py (make_safewatch_dataset)
Role: Load SafeWatch SFT JSONL (e.g. sft_train_v2.jsonl / sft_eval_v2.jsonl) into conversation examples the Nemotron VL collate path expects: user turns include {"type":"video","path": "<mp4>"} and assistant targets carry structured JSON (shots, intervals, guardrails).
Typical YAML (examples/recipes/nemotron_vl/conf/nemotron_nano_v2_vl_safewatch.yaml):
dataset:
do_test: false # see §2 — avoids duplicate eval on the same eval JSONL
sequence_length: 8192
maker_name: make_safewatch_dataset
maker_kwargs:
train_jsonl_path: /path/to/sft_train_v2.jsonl
eval_jsonl_path: /path/to/sft_eval_v2.jsonl
max_examples: 100
require_video_exists: true
Provider wiring: HFDatasetConversationProvider in src/megatron/bridge/data/vlm_datasets/hf_provider.py registers make_safewatch_dataset and builds train / validation / test splits from the maker. SafeWatch maps both validation and test splits to the same eval_jsonl_path; without do_test: false, the training driver would run two full eval passes (validation + test) on the same data.
| Area | File(s) | Change |
|---|---|---|
| Skip duplicate test eval | hf_provider.py | do_test: bool — when false, no test dataset is built. |
| Safe loaders | src/megatron/bridge/data/loaders.py | Build val/test loaders only if valid_ds / test_ds is not None. |
| Eval-only + JSONL dump | src/megatron/bridge/training/eval.py | If skip_train and logger.eval_dump_dir + eval_gt_jsonl_path are set and eval_dump_max_records is a finite ≥1, the dataloader eval loop runs 0 steps; metrics come from maybe_dump_eval_gt_pred_json (GT JSONL–driven forwards). Keeps eval_iters>0 so validation still runs and dump executes. Do not set train.eval_iters=0 for this path — that disables validation entirely in loader flags. |
| Artifacts | src/megatron/bridge/training/eval_dump.py | Writes iter_<train_step>/records.jsonl under logger.eval_dump_dir (teacher-forced supervised span, not full autoregressive generation). |
| Config | src/megatron/bridge/training/config.py | Documents eval_dump_dir, eval_gt_jsonl_path, eval_dump_max_records. |
train.shexamples/recipes/nemotron_vl/finetune_nemotron_nano_v2_vl.py with nemotron_nano_v2_vl_safewatch.yaml.--pretrained-checkpoint (e.g. Megatron Nemotron VL checkpoint dir).torchrun --nproc-per-node=... for multi-GPU data parallel.WANDB_API_KEY in the environment (do not commit secrets); YAML/logger overrides for project name can be passed on the CLI.inference.shtrain.skip_train=true.checkpoint.load=<results_dir> and checkpoint.ckpt_step=<step> (e.g. step 100 → iter_0000100 checkpoint layout under that directory).--pretrained-checkpoint for architecture / tokenizer binding.logger.eval_gt_jsonl_path and logger.eval_dump_dir for dump location.results/.../eval_json_full/iter_<step>/records.jsonl (one JSON object per line).eval.shscripts/eval_interval_guardrail_metrics.py on one or more eval_dump roots (directories containing iter_*/records.jsonl).<eval_dump_dir>/
iter_0000100/
records.jsonl # one record per line: gt intervals, pred intervals, optional dump fields
Legacy flat iter_*_pred_*.json / iter_*/pred_*.json are still supported by the metrics script when records.jsonl is absent.
scripts/eval_interval_guardrail_metrics.py)Inputs: --dirs (one or more roots), --tiou-threshold or --tiou-thresholds, --unmatched-policy (strict | matched_only).
Notable outputs:
chunks= / bits=: guardrail rows (segment-level vector evals) vs total binary decisions.Optional: --csv, --json-out.
After running eval.sh or the Python command directly, you may see blocks similar to:
=== tIoU>=0.5 unmatched=strict ===
100 | f1=0.789 f1_all=0.752 tiou=0.860 cov=0.404 hit=0.44 n_f1=14/32
--- chunk_pool: counts, TN, accuracy, F1 ---
100 | chunks=83 bits=498 acc=0.970 | pool_f1=0.516 P=0.421 R=0.667 | tp=8 fp=11 fn=4 tn=475 | f1: C1(...)=1.000 ... | acc: C1(...)=1.000 ...
Numbers depend on dataset size, τ, checkpoint, and whether eval_dump used a subset (eval_dump_max_records).
With train_iters optimizer steps and global_batch_size samples per step:
[ \text{approx epochs} \approx \frac{\texttt{train_iters} \times \texttt{global_batch_size}}{N} ]
where (N) is the number of unique training rows after caps/filters (e.g. max_examples and require_video_exists).
| Component | Path |
|---|---|
| Finetune driver | examples/recipes/nemotron_vl/finetune_nemotron_nano_v2_vl.py |
| Recipe factory | src/megatron/bridge/recipes/nemotron_vl/nemotron_nano_v2_vl.py |
| SafeWatch maker | src/megatron/bridge/data/vlm_datasets/hf_dataset_makers.py |
| HF conversation provider | src/megatron/bridge/data/vlm_datasets/hf_provider.py |
| Pretrain / eval driver | src/megatron/bridge/training/pretrain.py |
| Eval + dump hook | src/megatron/bridge/training/eval.py |
3 commits
Python
99.1%