Skip to content

Commit 36c6ddd

Browse files
committed
fix(performance): preserve recipe env self-exec
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
1 parent 4208c94 commit 36c6ddd

10 files changed

Lines changed: 174 additions & 100 deletions

File tree

docs/fern/versions/nightly/pages/recipe-usage.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ cfg.env_vars.update(
317317

318318
Values may be strings, integers, floats, or booleans and are converted to strings when exported. Existing shell, container, or launcher values take precedence over recipe defaults. This lets the same mechanism work for library recipes under `megatron.bridge.recipes` and flat performance recipes under `megatron.bridge.perf_recipes` without preventing cluster-specific overrides.
319319

320-
When launching through `scripts/performance/setup_experiment.py`, a rank-local pre-exec wrapper resolves library or flat performance recipe defaults inside the worker container, then replaces itself with the selected training entry point. This keeps recipe imports off dependency-light launcher nodes. Explicit `--env`, `--custom_env_vars`, shell, and container values take precedence.
320+
When launching through `scripts/performance/setup_experiment.py`, the selected rank-local training script resolves its library or flat performance recipe defaults inside the worker container, exports them, and then replaces itself with a clean interpreter running that same script. This keeps recipe imports off dependency-light launcher nodes and ensures training-framework imports observe recipe-owned process settings. Explicit `--env`, `--custom_env_vars`, shell, and container values take precedence.
321321

322322
Environment variables are serialized with the rest of the recipe and can be added or overridden through a Hydra-style override such as `'++env_vars={TORCHINDUCTOR_WORKER_START:fork,QUANTIZATION_TYPE_DEBUG:1}'`. Do not store credentials or other secrets in recipe configs.
323323

docs/recipe-usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ cfg.env_vars.update(
318318

319319
Values may be strings, integers, floats, or booleans and are converted to strings when exported. Existing shell, container, or launcher values take precedence over recipe defaults. This lets the same mechanism work for library recipes under `megatron.bridge.recipes` and flat performance recipes under `megatron.bridge.perf_recipes` without preventing cluster-specific overrides.
320320

321-
When launching through `scripts/performance/setup_experiment.py`, a rank-local pre-exec wrapper resolves library or flat performance recipe defaults inside the worker container, then replaces itself with the selected training entry point. This keeps recipe imports off dependency-light launcher nodes. Explicit `--env`, `--custom_env_vars`, shell, and container values take precedence.
321+
When launching through `scripts/performance/setup_experiment.py`, the selected rank-local training script resolves its library or flat performance recipe defaults inside the worker container, exports them, and then replaces itself with a clean interpreter running that same script. This keeps recipe imports off dependency-light launcher nodes and ensures training-framework imports observe recipe-owned process settings. Explicit `--env`, `--custom_env_vars`, shell, and container values take precedence.
322322

323323
Environment variables are serialized with the rest of the recipe and can be added or overridden through a Hydra-style override such as `'++env_vars={TORCHINDUCTOR_WORKER_START:fork,QUANTIZATION_TYPE_DEBUG:1}'`. Do not store credentials or other secrets in recipe configs.
324324

scripts/performance/run_recipe.py

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,30 +21,13 @@
2121
import os
2222
import sys
2323

24-
import torch
2524
from argument_parser import parse_cli_args
26-
from utils.datasets import (
27-
create_mock_dataset_config,
28-
create_rp2_dataset_config,
29-
create_squad_dataset_config,
30-
)
31-
from utils.utils import (
32-
apply_library_argparse_overrides,
33-
apply_library_target_topology_environment,
34-
explicit_environment_override_names,
35-
finalize_library_config_overrides,
36-
get_library_recipe,
37-
)
38-
39-
from megatron.bridge.recipes.utils.determinism_utils import apply_determinism_overrides
40-
from megatron.bridge.training.config import apply_environment_variables
41-
from megatron.bridge.training.utils.omegaconf_utils import process_config_with_overrides
42-
from megatron.bridge.utils.common_utils import get_rank_safe
4325

4426

4527
# Diffusion model families manage their own dataset configs and require
4628
# a dedicated forward step function rather than the standard GPT step.
4729
DIFFUSION_FAMILIES = frozenset({"flux", "wan"})
30+
ENV_BOOTSTRAP_MARKER = "_MB_PERF_ENV_BOOTSTRAPPED"
4831

4932

5033
def _get_diffusion_step(model_family_name: str):
@@ -62,6 +45,9 @@ def _get_diffusion_step(model_family_name: str):
6245

6346
def set_user_overrides(config, args):
6447
"""Apply CLI arguments to ConfigContainer fields."""
48+
from utils.datasets import create_mock_dataset_config, create_rp2_dataset_config, create_squad_dataset_config
49+
from utils.utils import apply_library_argparse_overrides
50+
6551
is_diffusion = args.model_family_name in DIFFUSION_FAMILIES
6652
config = apply_library_argparse_overrides(config, args)
6753

@@ -194,6 +180,8 @@ def set_user_overrides(config, args):
194180
config.logger.wandb_save_dir = args.wandb_save_dir
195181

196182
if args.deterministic:
183+
from megatron.bridge.recipes.utils.determinism_utils import apply_determinism_overrides
184+
197185
apply_determinism_overrides(config)
198186

199187
# Handle convergence mode configuration
@@ -220,37 +208,87 @@ def set_user_overrides(config, args):
220208
return config
221209

222210

223-
def main():
224-
"""Main entry point for the training script."""
225-
226-
# Parse known args and capture unknown ones for Hydra-style config overrides
227-
# (e.g. model.hidden_size=15360 model.num_moe_experts=8)
228-
parser = parse_cli_args()
229-
args, cli_overrides = parser.parse_known_args()
211+
def _get_library_recipe(args):
212+
"""Resolve the selected library recipe without importing it at module load time."""
213+
from utils.utils import get_library_recipe
230214

231-
recipe = get_library_recipe(
215+
return get_library_recipe(
232216
model_family_name=args.model_family_name,
233217
model_recipe_name=args.model_recipe_name,
234218
train_task=args.task,
235219
wandb_experiment_name=args.wandb_experiment_name,
236220
)
237221

238-
base_env_vars = dict(recipe.env_vars)
239-
recipe = set_user_overrides(recipe, args)
240222

223+
def _process_library_hydra_overrides(recipe, cli_overrides: list[str]):
224+
"""Apply Hydra overrides using the same ordering in both self-exec passes."""
225+
from megatron.bridge.training.utils.omegaconf_utils import process_config_with_overrides
226+
227+
return process_config_with_overrides(recipe, cli_overrides=cli_overrides)
228+
229+
230+
def _apply_library_recipe_overrides(recipe, cli_overrides: list[str], args):
231+
"""Apply env-relevant argparse and Hydra overrides before self-exec."""
232+
from utils.utils import apply_library_argparse_overrides, finalize_library_config_overrides
233+
234+
recipe = apply_library_argparse_overrides(recipe, args)
241235
if cli_overrides:
242-
logging.info("Applying %d CLI config override(s)", len(cli_overrides))
243-
recipe = process_config_with_overrides(recipe, cli_overrides=cli_overrides)
236+
recipe = _process_library_hydra_overrides(recipe, cli_overrides)
237+
return finalize_library_config_overrides(recipe)
238+
239+
240+
def _apply_target_environment(recipe, base_env_vars: dict, cli_overrides: list[str], args) -> None:
241+
"""Finalize target-dependent recipe environment defaults."""
242+
from utils.utils import apply_library_target_topology_environment, explicit_environment_override_names
244243

245-
recipe = finalize_library_config_overrides(recipe)
246244
protected_env_names = explicit_environment_override_names(cli_overrides, base_env_vars, recipe.env_vars)
247245
apply_library_target_topology_environment(
248246
recipe,
249247
gpu=args.gpu,
250248
protected_env_names=protected_env_names,
251249
)
250+
251+
252+
def _apply_recipe_environment(recipe) -> None:
253+
"""Project recipe env defaults onto the current process."""
254+
from megatron.bridge.training.config import apply_environment_variables
255+
252256
apply_environment_variables(recipe)
253257

258+
259+
def _bootstrap_recipe_environment(args, cli_overrides: list[str]) -> None:
260+
"""Apply the effective recipe env and replace this process with a clean interpreter."""
261+
recipe = _get_library_recipe(args)
262+
base_env_vars = dict(recipe.env_vars)
263+
recipe = _apply_library_recipe_overrides(recipe, cli_overrides, args)
264+
_apply_target_environment(recipe, base_env_vars, cli_overrides, args)
265+
_apply_recipe_environment(recipe)
266+
267+
environment = dict(os.environ)
268+
environment[ENV_BOOTSTRAP_MARKER] = str(os.getpid())
269+
os.execvpe(sys.executable, [sys.executable, __file__, *sys.argv[1:]], environment)
270+
271+
272+
def _run_training(args, cli_overrides: list[str]) -> None:
273+
"""Build the final recipe and run training after environment bootstrap."""
274+
import torch
275+
276+
from megatron.bridge.utils.common_utils import get_rank_safe
277+
278+
recipe = _get_library_recipe(args)
279+
base_env_vars = dict(recipe.env_vars)
280+
recipe = set_user_overrides(recipe, args)
281+
282+
if cli_overrides:
283+
logging.info("Applying %d CLI config override(s)", len(cli_overrides))
284+
recipe = _process_library_hydra_overrides(recipe, cli_overrides)
285+
286+
from utils.utils import finalize_library_config_overrides
287+
288+
recipe = finalize_library_config_overrides(recipe)
289+
_apply_target_environment(recipe, base_env_vars, cli_overrides, args)
290+
_apply_recipe_environment(recipe)
291+
254292
if args.dryrun:
255293
save_path = args.save_config_filepath or "ConfigContainer.yaml"
256294
os.makedirs(os.path.dirname(os.path.abspath(save_path)), exist_ok=True)
@@ -291,5 +329,16 @@ def main():
291329
torch.distributed.destroy_process_group()
292330

293331

332+
def main() -> None:
333+
"""Bootstrap recipe env on the first pass and train in the self-exec process."""
334+
parser = parse_cli_args()
335+
args, cli_overrides = parser.parse_known_args()
336+
337+
if os.environ.get(ENV_BOOTSTRAP_MARKER) != str(os.getpid()):
338+
_bootstrap_recipe_environment(args, cli_overrides)
339+
else:
340+
_run_training(args, cli_overrides)
341+
342+
294343
if __name__ == "__main__":
295344
main()

scripts/performance/run_script.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,16 @@ def __init__(self) -> None:
136136
self.env_vars = os.environ
137137

138138

139-
def _bootstrap_recipe_environment(args, cli_overrides: list[str]) -> None:
140-
"""Install recipe env and re-exec this script in a clean interpreter."""
139+
def _apply_perf_recipe_overrides(recipe, cli_overrides: list[str], args):
140+
"""Apply the same CLI and argparse overrides in both self-exec passes."""
141141
from utils.overrides import set_cli_overrides, set_user_overrides
142142

143+
recipe = set_cli_overrides(recipe, cli_overrides)
144+
return set_user_overrides(recipe, args)
145+
146+
147+
def _bootstrap_recipe_environment(args, cli_overrides: list[str]) -> None:
148+
"""Install recipe env and re-exec this script in a clean interpreter."""
143149
recipe = get_perf_recipe_for_environment(
144150
model_recipe_name=args.model_recipe_name,
145151
task=args.task,
@@ -148,8 +154,7 @@ def _bootstrap_recipe_environment(args, cli_overrides: list[str]) -> None:
148154
precision=args.compute_dtype,
149155
config_variant=args.config_variant,
150156
)
151-
recipe = set_cli_overrides(recipe, cli_overrides)
152-
recipe = set_user_overrides(recipe, args)
157+
recipe = _apply_perf_recipe_overrides(recipe, cli_overrides, args)
153158
workload_base_config = _workload_base_config_from_recipe(recipe, num_gpus=args.num_gpus)
154159
plugin = PerfEnvPlugin(
155160
deterministic=args.deterministic,
@@ -166,7 +171,6 @@ def _bootstrap_recipe_environment(args, cli_overrides: list[str]) -> None:
166171
def _run_training(args, cli_overrides: list[str]) -> None:
167172
"""Import the training stack after env bootstrap and run the workload."""
168173
import torch
169-
from utils.overrides import set_cli_overrides, set_user_overrides
170174

171175
from megatron.bridge.diffusion.models.wan.wan_step import WanForwardStep
172176
from megatron.bridge.models.qwen_vl.qwen3_vl_step import forward_step as qwen3_vl_forward_step
@@ -184,8 +188,7 @@ def _run_training(args, cli_overrides: list[str]) -> None:
184188
config_variant=getattr(args, "config_variant", None),
185189
)
186190

187-
recipe = set_cli_overrides(recipe, cli_overrides)
188-
recipe = set_user_overrides(recipe, args)
191+
recipe = _apply_perf_recipe_overrides(recipe, cli_overrides, args)
189192
apply_environment_variables(recipe)
190193

191194
if args.dump_env:

scripts/performance/setup_experiment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
def _filter_run_script_args(argv: List[str]) -> List[str]:
6767
"""Drop launcher-only args before forwarding argv to the rank-local script.
6868
69-
The launcher (this script) and the rank-local pre-exec wrapper share one
69+
The launcher (this script) and the rank-local training scripts share one
7070
parser, but some args are meaningful only to the launcher and must not
7171
reach the rank-local scripts:
7272

scripts/performance/utils/executors.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ def kubeflow_executor(
238238
"""
239239
# K8s/Kubeflow jobs deliberately do NOT inherit the Slurm orchestration
240240
# defaults in PERF_ENV_VARS. Recipe-owned process settings are applied by
241-
# the worker wrapper; the cluster supplies fabric tuning explicitly via
242-
# KUBEFLOW_ENV_LIST_JSON (-> custom_env_vars / env_list).
241+
# each training script's self-exec bootstrap; the cluster supplies fabric
242+
# tuning explicitly via KUBEFLOW_ENV_LIST_JSON (-> custom_env_vars / env_list).
243243
env_vars: Dict[str, str] = {}
244244
if wandb_key is not None:
245245
env_vars["WANDB_API_KEY"] = wandb_key

scripts/performance/utils/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ def apply_library_target_topology_environment(
216216
def apply_library_argparse_overrides(config: Any, args: Any) -> Any:
217217
"""Apply argparse values that affect library recipe configuration.
218218
219-
Both the worker pre-exec wrapper and the final library runner call this
220-
helper before Hydra overrides so their effective training configs match.
219+
Both passes of the library runner's self-exec bootstrap call this helper
220+
before Hydra overrides so their effective training configs match.
221221
"""
222222
if getattr(args, "nccl_ub", False):
223223
config.ddp.nccl_ub = True

tests/functional_tests/test_groups/recipes/test_perf_config_integration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def test_workload_base_config_derives_from_flat_recipe(self):
194194
(
195195
"llama",
196196
"megatron.bridge.perf_recipes.llama.h100.llama3:llama3_8b_pretrain_8gpu_h100_fp8cs_config",
197-
{"NVTE_FWD_LAYERNORM_SM_MARGIN": 16, "NCCL_CTA_POLICY": 1},
197+
{"NVTE_FWD_LAYERNORM_SM_MARGIN": 20, "NCCL_CTA_POLICY": 1},
198198
),
199199
(
200200
"nemotronh",
@@ -224,7 +224,7 @@ def test_workload_base_config_derives_from_flat_recipe(self):
224224
(
225225
"wan",
226226
"megatron.bridge.perf_recipes.wan.h100.wan:wan_14b_pretrain_32gpu_h100_bf16_config",
227-
{"NVTE_FWD_LAYERNORM_SM_MARGIN": 16, "CUDA_DEVICE_MAX_CONNECTIONS": 1},
227+
{"NVTE_FWD_LAYERNORM_SM_MARGIN": 20, "CUDA_DEVICE_MAX_CONNECTIONS": 1},
228228
),
229229
],
230230
)

0 commit comments

Comments
 (0)