Skip to content

Commit a272718

Browse files
jasainioFlux SplitWangLingxunXiaoming-AMDluiza-amd
authored
feat(flux): core runtime + Megatron adapter scaffolding (#807)
Base of an 18-PR series splitting the Flux diffusion-training feature (training Flux, a DiT text-to-image diffusion model, on Primus/Megatron) out of one large branch for reviewability. Targets `feat/flux/ci-env` and auto-retargets to `main` once that merges. Every other content PR stacks on this one. ## What this changes The shared runtime scaffolding the rest of the feature builds on: core runtime state + train-runtime wiring, the Megatron adapter and base/pretrain trainers, and a patch auto-loader (`patches/__init__.py`) that imports every `*_patches.py` in the package on import, so each later layer only drops in its own patch file with no registry edit. Also carries the shared test root (`tests/conftest.py`, `tests/utils.py`) and a one-line `.gitignore` change (the bare `data` ignore → root-anchored `/data/*`, and nothing else) that stops Git from ignoring the in-repo `data/` source and config directories the later layers add. Kept deliberately small so it can land first. ## Dependencies Sequenced after the CI-pins PR (`feat/flux/ci-env`); no functional dependency on the turbo bump. ## Test plan `pytest tests/unit_tests/core tests/unit_tests/backends/megatron`; lint/pre-commit clean. Validated locally on an AMD GPU container: 58 passed. ## Files 39 (core runtime, Megatron adapter/trainers, base patch loader, shared test root, `.gitignore`). --------- Co-authored-by: Flux Split <flux-split@local> Co-authored-by: WangLingxun <linxwang@amd.com> Co-authored-by: Xiaoming-AMD <Xiaoming.Peng@amd.com> Co-authored-by: luiza-amd <Luiza.Sayfullina@amd.com> Co-authored-by: Flux Split Trial <flux-split-trial@local>
1 parent 5901607 commit a272718

46 files changed

Lines changed: 2410 additions & 504 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ local/
1111
.gitmodules
1212
output
1313
experiment
14-
data
14+
/data/*
1515
pp_simulation_result

primus/backends/hummingbirdxt/hummingbirdxt_posttrain_trainer.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111

1212

1313
class HummingbirdXTPosttrainTrainer(BaseTrainer):
14-
def __init__(self, backend_args: Any):
15-
super().__init__(backend_args=backend_args)
14+
def __init__(self, backend_args: Any = None, **kwargs):
15+
# Accept and forward runtime context kwargs so BaseTrainer can filter them.
16+
super().__init__(backend_args=backend_args, **kwargs)
1617

1718
def setup(self):
1819
pass

primus/backends/maxtext/maxtext_pretrain_trainer.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ class MaxTextPretrainTrainer(BaseTrainer):
4141
Trainer class for MaxText pre-training.
4242
"""
4343

44-
def __init__(self, backend_args: Any):
45-
super().__init__(backend_args=backend_args)
44+
def __init__(self, backend_args: Any = None, **kwargs):
45+
# The core runtime instantiates every trainer with BaseModule-style
46+
# context kwargs (module_name, primus_config, module_rank, ...). MaxText
47+
# does not need them; accept and forward so BaseTrainer can filter them.
48+
super().__init__(backend_args=backend_args, **kwargs)
4649

4750
# Training state (populated in init())
4851
self.train_config: Optional[Any] = None

primus/backends/megatron/__init__.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
###############################################################################
2-
# Copyright (c) 2025, Advanced Micro Devices, Inc. All rights reserved.
2+
# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved.
33
#
44
# See LICENSE for license information.
55
###############################################################################
@@ -12,3 +12,22 @@
1212
BackendRegistry.register_adapter("megatron", MegatronAdapter)
1313
BackendRegistry.register_trainer_class(MegatronPretrainTrainer, "megatron")
1414
BackendRegistry.register_trainer_class(MegatronSFTTrainer, "megatron", "sft")
15+
16+
# Export trainers for convenience
17+
# Use lazy import for FluxPretrainTrainer to avoid Megatron dependency
18+
# when importing data pipeline components
19+
__all__ = [
20+
"MegatronAdapter",
21+
"FluxPretrainTrainer",
22+
"MegatronPretrainTrainer",
23+
"MegatronSFTTrainer",
24+
]
25+
26+
27+
def __getattr__(name):
28+
"""Lazy import for trainer classes to avoid Megatron dependency on module import."""
29+
if name == "FluxPretrainTrainer":
30+
from primus.backends.megatron.flux_pretrain_trainer import FluxPretrainTrainer
31+
32+
return FluxPretrainTrainer
33+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

primus/backends/megatron/megatron_adapter.py

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
###############################################################################
2-
# Copyright (c) 2025, Advanced Micro Devices, Inc. All rights reserved.
2+
# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved.
33
#
44
# See LICENSE for license information.
55
###############################################################################
66

7+
import importlib
8+
from typing import Optional
9+
710
import primus.backends.megatron.patches # noqa: F401 # Register patches
811
from primus.backends.megatron.argument_builder import MegatronArgBuilder
912
from primus.core.backend.backend_adapter import BackendAdapter
@@ -18,8 +21,27 @@ def __init__(self, framework="megatron"):
1821
super().__init__(framework)
1922
self.third_party_dir_name = "Megatron-LM"
2023

21-
def load_trainer_class(self, stage: str = "pretrain"):
22-
"""Return the trainer class for the specified training stage."""
24+
def load_trainer_class(self, stage: str = "pretrain", trainer_class: Optional[str] = None):
25+
"""
26+
Return the Trainer class for the specified training stage or trainer class name.
27+
28+
Args:
29+
stage: Training stage (e.g., "pretrain", "sft"). Defaults to "pretrain".
30+
trainer_class: Optional specific trainer class name for dynamic loading.
31+
If provided, this takes precedence over stage-based selection.
32+
33+
Returns:
34+
Trainer class
35+
36+
Raises:
37+
ImportError: If trainer class cannot be imported
38+
ValueError: If stage is invalid or trainer class is not found
39+
"""
40+
# If trainer_class is specified, attempt dynamic loading
41+
if trainer_class:
42+
return self._load_trainer_class_by_name(trainer_class)
43+
44+
# Fallback to stage-based selection via the backend registry
2345
try:
2446
trainer_cls = BackendRegistry.get_trainer_class(self.framework, stage=stage)
2547
except (ValueError, AssertionError) as exc:
@@ -31,6 +53,76 @@ def load_trainer_class(self, stage: str = "pretrain"):
3153
log_rank_0(f"[Primus:MegatronAdapter] Loaded trainer class: {trainer_cls.__name__}")
3254
return trainer_cls
3355

56+
def _load_trainer_class_by_name(self, trainer_class: str):
57+
"""
58+
Dynamically load trainer class by name.
59+
60+
Args:
61+
trainer_class: Trainer class name (e.g., "FluxPretrainTrainer", "MegatronPretrainTrainer")
62+
63+
Returns:
64+
Trainer class
65+
66+
Raises:
67+
ImportError: If trainer class cannot be imported
68+
ValueError: If trainer class is not found
69+
"""
70+
# Define trainer registry for Megatron backend
71+
# This maps trainer class names to their module paths
72+
MEGATRON_TRAINERS = {
73+
"MegatronPretrainTrainer": "primus.backends.megatron.megatron_pretrain_trainer.MegatronPretrainTrainer",
74+
"FluxPretrainTrainer": "primus.backends.megatron.flux_pretrain_trainer.FluxPretrainTrainer",
75+
}
76+
77+
if trainer_class not in MEGATRON_TRAINERS:
78+
# Try to load from common locations as fallback
79+
log_rank_0(
80+
f"[Primus:MegatronAdapter] Trainer '{trainer_class}' not in registry, attempting dynamic import..."
81+
)
82+
83+
possible_paths = [
84+
f"primus.backends.megatron.{trainer_class.lower()}.{trainer_class}",
85+
f"primus.modules.trainer.megatron.{trainer_class.lower()}.{trainer_class}",
86+
f"primus.backends.megatron.{trainer_class}",
87+
]
88+
89+
for module_path in possible_paths:
90+
try:
91+
module_name, class_name = module_path.rsplit(".", 1)
92+
module = importlib.import_module(module_name)
93+
trainer_cls = getattr(module, class_name)
94+
log_rank_0(
95+
f"[Primus:MegatronAdapter] Successfully loaded trainer: {trainer_class} from {module_name}"
96+
)
97+
return trainer_cls
98+
except (ImportError, AttributeError):
99+
continue
100+
101+
# If all attempts failed, provide helpful error message
102+
available = list(MEGATRON_TRAINERS.keys())
103+
raise ValueError(
104+
f"Trainer class '{trainer_class}' not found.\n"
105+
f"Available trainers: {', '.join(available) if available else 'none'}\n"
106+
f"Hint: Set 'trainer_class' in your experiment YAML to a registered trainer "
107+
f"(e.g. trainer_class: FluxPretrainTrainer), register it in MEGATRON_TRAINERS, "
108+
f"or ensure it is importable from standard locations."
109+
)
110+
111+
# Load from registry
112+
trainer_path = MEGATRON_TRAINERS[trainer_class]
113+
module_path, class_name = trainer_path.rsplit(".", 1)
114+
115+
try:
116+
module = importlib.import_module(module_path)
117+
trainer_cls = getattr(module, class_name)
118+
log_rank_0(f"[Primus:MegatronAdapter] Loaded trainer: {trainer_class} from {module_path}")
119+
return trainer_cls
120+
except (ImportError, AttributeError) as e:
121+
raise ImportError(
122+
f"Failed to load trainer class '{trainer_class}' from '{module_path}': {e}\n"
123+
f"Hint: Check that the module exists and the class name is correct."
124+
) from e
125+
34126
def detect_backend_version(self) -> str:
35127
"""Detect Megatron-LM version via AST parsing (avoids __init__.py execution)."""
36128
import ast

primus/backends/megatron/megatron_base_trainer.py

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
###############################################################################
2-
# Copyright (c) 2025, Advanced Micro Devices, Inc. All rights reserved.
2+
# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved.
33
#
44
# See LICENSE for license information.
55
###############################################################################
@@ -14,7 +14,6 @@
1414
)
1515
from primus.backends.megatron.training.mlflow_setup import upload_mlflow_artifacts
1616
from primus.core.trainer.base_trainer import BaseTrainer
17-
from primus.core.utils.env import flush_before_hard_exit
1817
from primus.modules.module_utils import log_rank_0, warning_rank_0
1918

2019

@@ -23,9 +22,84 @@ class MegatronBaseTrainer(BaseTrainer):
2322

2423
def setup(self):
2524
"""Setup Megatron runtime: set global vars and patch parse_args."""
25+
self._ensure_megatron_path()
2626
set_primus_global_variables(self.backend_args)
2727
self._patch_parse_args()
2828

29+
def _ensure_megatron_path(self):
30+
"""Ensure Megatron-LM path is in sys.path before any megatron imports."""
31+
import os
32+
import sys
33+
from pathlib import Path
34+
35+
# First, check if megatron is already importable
36+
try:
37+
import megatron # type: ignore
38+
39+
return # Path already set correctly
40+
except ImportError:
41+
pass
42+
43+
# Try multiple methods to find the Megatron-LM path
44+
megatron_paths = []
45+
46+
# Method 1: From PRIMUS_PATH environment variable (most reliable)
47+
primus_path = os.getenv("PRIMUS_PATH")
48+
if primus_path:
49+
megatron_path = Path(primus_path) / "third_party" / "Megatron-LM"
50+
if megatron_path.exists():
51+
megatron_paths.append(str(megatron_path))
52+
53+
# Method 2: From current working directory (works in container)
54+
try:
55+
cwd = Path.cwd()
56+
# Check if we're in /workspace/Primus or a subdirectory
57+
if "Primus" in str(cwd):
58+
# Find the Primus root
59+
primus_root = cwd
60+
while primus_root.name != "Primus" and primus_root != primus_root.parent:
61+
primus_root = primus_root.parent
62+
if primus_root.name == "Primus":
63+
megatron_path = primus_root / "third_party" / "Megatron-LM"
64+
if megatron_path.exists():
65+
megatron_paths.append(str(megatron_path))
66+
except Exception:
67+
pass
68+
69+
# Method 3: From current file location
70+
try:
71+
repo_root = Path(__file__).resolve().parents[3]
72+
megatron_path = repo_root / "third_party" / "Megatron-LM"
73+
if megatron_path.exists():
74+
megatron_paths.append(str(megatron_path))
75+
except Exception:
76+
pass
77+
78+
# Method 4: Check if already in sys.path
79+
for path in sys.path:
80+
path_obj = Path(path)
81+
if path_obj.exists():
82+
megatron_pkg = path_obj / "megatron"
83+
if megatron_pkg.exists() and megatron_pkg.is_dir():
84+
return # Path already set correctly
85+
86+
# Add paths to sys.path if not already present
87+
for path in megatron_paths:
88+
if path not in sys.path:
89+
sys.path.insert(0, path)
90+
log_rank_0(f"[Primus:MegatronBaseTrainer] Added Megatron-LM to sys.path: {path}")
91+
92+
# Verify the path was set correctly by trying to import
93+
try:
94+
import megatron # type: ignore
95+
96+
log_rank_0("[Primus:MegatronBaseTrainer] Successfully verified megatron import")
97+
except ImportError as e:
98+
log_rank_0(
99+
f"[Primus:MegatronBaseTrainer] WARNING: Failed to import megatron after path setup: {e}"
100+
)
101+
log_rank_0(f"[Primus:MegatronBaseTrainer] sys.path: {sys.path[:5]}")
102+
29103
def init(self):
30104
"""Initialize Megatron training components."""
31105
log_rank_0("Initializing Megatron training...")
@@ -53,7 +127,14 @@ def cleanup(self, on_error: bool = False):
53127

54128
if exit_fast and not on_error:
55129
log_rank_0("[MegatronBaseTrainer] PRIMUS_EXIT_FAST=1 -> os._exit(0)")
56-
flush_before_hard_exit()
130+
# Flush stdout/stderr so the final log lines are not lost.
131+
try:
132+
import sys
133+
134+
sys.stdout.flush()
135+
sys.stderr.flush()
136+
except Exception: # pragma: no cover
137+
pass
57138
os._exit(0)
58139

59140
def _finalize_mlflow_artifacts(self):

0 commit comments

Comments
 (0)