Skip to content

Commit 98fade0

Browse files
committed
fix(launcher): preserve native SFT recipes in CI
Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent 1e8a61d commit 98fade0

5 files changed

Lines changed: 105 additions & 5 deletions

File tree

‎scripts/performance/argument_parser.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ def parse_cli_args():
330330
"--data",
331331
type=str,
332332
default="mock",
333-
choices=["mock", "rp2", "squad", "squad_packed", "c4"],
334-
help="Dataset type to use",
333+
choices=["mock", "rp2", "squad", "squad_packed", "c4", "recipe"],
334+
help="Dataset type to use; recipe preserves the library recipe dataset",
335335
)
336336
data_args.add_argument(
337337
"--c4_root",

‎scripts/performance/run_recipe.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def _apply_training_argparse_overrides(config, args):
6969
config.optimizer.min_lr = args.min_lr
7070

7171
# Scheduler configuration
72-
if args.warmup_iters:
72+
if args.warmup_iters is not None:
7373
config.scheduler.lr_warmup_iters = args.warmup_iters
7474

7575
# Checkpoint configuration
@@ -95,7 +95,9 @@ def _apply_training_argparse_overrides(config, args):
9595
pad_seq_to_mult = cp_size * 2 if cp_size > 1 else 1
9696

9797
# Create dataset configuration based on type
98-
if args.data == "mock":
98+
if args.data == "recipe":
99+
pass # Preserve dataset type, native packing, and pinned source metadata.
100+
elif args.data == "mock":
99101
config.dataset = create_mock_dataset_config(seq_length=args.seq_length or 8192)
100102
elif args.data == "rp2":
101103
if not args.dataset_paths or not args.index_mapping_dir:
@@ -196,6 +198,8 @@ def _apply_training_argparse_overrides(config, args):
196198
config.checkpoint.save_interval = args.save_interval or 1000
197199

198200
# Validation configuration for convergence
201+
if args.data == "recipe":
202+
return config # Keep the recipe validation cadence and LR horizon.
199203
if args.max_steps <= 100:
200204
config.train.eval_interval = args.max_steps
201205
config.train.eval_iters = 0 # Disable evaluation for short convergence runs
@@ -312,6 +316,8 @@ def _run_training(args, cli_overrides: list[str]) -> None:
312316

313317
if args.model_family_name in DIFFUSION_FAMILIES:
314318
forward_step = _get_diffusion_step(args.model_family_name)
319+
elif args.domain == "vlm":
320+
from megatron.bridge.training.vlm_step import forward_step
315321
else:
316322
from megatron.bridge.training.gpt_step import forward_step
317323

@@ -322,6 +328,8 @@ def _run_training(args, cli_overrides: list[str]) -> None:
322328

323329
if args.model_family_name in DIFFUSION_FAMILIES:
324330
forward_step = _get_diffusion_step(args.model_family_name)
331+
elif args.domain == "vlm":
332+
from megatron.bridge.training.vlm_step import forward_step
325333
else:
326334
from megatron.bridge.training.gpt_step import forward_step
327335

‎scripts/performance/utils/utils.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,9 @@ def build_recipe_config(model_family_name: str, model_recipe_name: str, train_ta
890890
family_pkg_path = f"megatron.bridge.recipes.{model_family_name}"
891891
family_pkg = importlib.import_module(family_pkg_path)
892892

893-
if model_recipe_name == "deepseek_v3_32nodes" and train_task == "pretrain":
893+
if model_recipe_name.endswith("_config"):
894+
pass # Complete exported recipe names already identify task, hardware, and precision.
895+
elif model_recipe_name == "deepseek_v3_32nodes" and train_task == "pretrain":
894896
model_recipe_name = "deepseek_v3_pretrain_config_32nodes"
895897
elif train_task == "peft":
896898
model_recipe_name = f"{model_recipe_name}_peft_config"

‎tests/unit_tests/scripts/performance/test_recipe_environment.py‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,3 +1586,73 @@ def exec_runner(executable, argv, env):
15861586
assert calls[0] == ("environment", effective_recipe)
15871587
assert calls[1][0] == "exec"
15881588
assert calls[1][2][1].endswith("run_recipe.py")
1589+
1590+
1591+
def test_complete_library_recipe_name_is_not_suffixed(monkeypatch):
1592+
"""CI can select a hardware-specific library recipe without renaming it."""
1593+
from megatron.bridge.recipes.common import _sft_common
1594+
1595+
recipe = _sft_common()
1596+
name = "qwen35_text_35b_a3b_sft_long_context_16gpu_gb200_fp8mx_config"
1597+
module = SimpleNamespace(**{name: lambda: recipe})
1598+
monkeypatch.setattr(utils.importlib, "import_module", lambda _: module)
1599+
result = utils.build_recipe_config("qwen", name, "sft", "ci-test")
1600+
assert result is recipe
1601+
assert result.logger.wandb_exp_name == "ci-test"
1602+
1603+
1604+
@pytest.mark.parametrize("dataset_kind", ["text", "energon"])
1605+
def test_recipe_data_keeps_native_packing_tokenizer_and_schedule(dataset_kind):
1606+
"""A 100-step CI run must not replace SFT inputs or compress the LR schedule."""
1607+
from argument_parser import parse_cli_args
1608+
1609+
from megatron.bridge.data.builders import EnergonDatasetConfig, QwenVLEnergonTaskEncoderConfig
1610+
from megatron.bridge.recipes.common import _sft_common
1611+
from megatron.bridge.recipes.utils.dataset_utils import default_coderforge_config
1612+
1613+
args = parse_cli_args().parse_args(
1614+
[
1615+
"--model_family_name",
1616+
"qwen",
1617+
"--model_recipe_name",
1618+
"test_config",
1619+
"--num_gpus",
1620+
"16",
1621+
"--gpu",
1622+
"gb200",
1623+
"--task",
1624+
"sft",
1625+
"--use_recipes",
1626+
"--data",
1627+
"recipe",
1628+
"--max_steps",
1629+
"100",
1630+
"--warmup_iters",
1631+
"200",
1632+
]
1633+
)
1634+
cfg = _sft_common()
1635+
cfg.dataset = (
1636+
EnergonDatasetConfig(
1637+
seq_length=131072,
1638+
micro_batch_size=1,
1639+
packing_buffer_size=64,
1640+
task_encoder=QwenVLEnergonTaskEncoderConfig(hf_processor_path="test-model"),
1641+
)
1642+
if dataset_kind == "energon"
1643+
else default_coderforge_config(seq_length=131072, enable_offline_packing=True)
1644+
)
1645+
dataset, tokenizer = cfg.dataset, cfg.tokenizer
1646+
cfg.scheduler.lr_decay_iters = 300000
1647+
cfg.validation.eval_interval = 50
1648+
cfg.validation.eval_iters = 8
1649+
result = run_recipe._apply_training_argparse_overrides(cfg, args)
1650+
assert result.dataset is dataset
1651+
assert result.tokenizer is tokenizer
1652+
assert result.train.train_iters == 100
1653+
assert result.scheduler.lr_warmup_iters == 200
1654+
assert result.scheduler.lr_decay_iters == 300000
1655+
assert result.validation.eval_interval == 50
1656+
assert result.validation.eval_iters == 8
1657+
assert result.train.eval_iters is None
1658+
assert result.train.eval_interval is None

‎tests/unit_tests/scripts/performance/test_run_script_entrypoint.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,3 +286,23 @@ def test_run_recipe_defers_training_framework_imports():
286286

287287
assert "torch" not in top_level_imports
288288
assert not any(module.startswith("megatron.bridge") for module in top_level_imports)
289+
290+
291+
@pytest.mark.parametrize("domain", ["llm", "vlm"])
292+
def test_library_sft_dispatches_the_selected_domain(monkeypatch, domain):
293+
"""VL CI recipes must reach the image-aware training step."""
294+
import importlib
295+
296+
from megatron.bridge.training.gpt_step import forward_step as gpt_step
297+
from megatron.bridge.training.vlm_step import forward_step as vlm_step
298+
299+
cfg = object()
300+
calls = []
301+
monkeypatch.setattr(run_recipe, "_prepare_recipe", lambda *_args, **_kwargs: cfg)
302+
common = importlib.import_module("megatron.bridge.utils.common_utils")
303+
finetune_module = importlib.import_module("megatron.bridge.training.finetune")
304+
monkeypatch.setattr(common, "get_rank_safe", lambda: 1)
305+
monkeypatch.setattr(finetune_module, "finetune", lambda **kwargs: calls.append(kwargs))
306+
args = SimpleNamespace(dryrun=False, task="sft", model_family_name="qwen_vl", domain=domain)
307+
run_recipe._run_training(args, [])
308+
assert calls == [{"config": cfg, "forward_step_func": vlm_step if domain == "vlm" else gpt_step}]

0 commit comments

Comments
 (0)