Skip to content

[v0] support megatron-bridge for PT/SFT training#10645

Draft
sunyi0505 wants to merge 1 commit into
hiyouga:mainfrom
sunyi0505:megatron_bridge
Draft

[v0] support megatron-bridge for PT/SFT training#10645
sunyi0505 wants to merge 1 commit into
hiyouga:mainfrom
sunyi0505:megatron_bridge

Conversation

@sunyi0505

Copy link
Copy Markdown
Contributor

What does this PR do?

Implementation Objective

Add NVIDIA Megatron Bridge as a new training backend in LLaMA-Factory, alongside existing HuggingFace / MCA / HyperParallel backends. Enabled via environment variable USE_MEGATRON_BRIDGE=1, currently covering:

  • PT (Pre-training)
  • SFT (Full-parameter / LoRA)

Architecture & Data Flow

USE_MEGATRON_BRIDGE=1
        │
        ▼
launcher → FORCE_TORCHRUN=1
        │
        ▼
parser.get_train_args()
  · Parse MegatronBridgeArguments
  · Validate stage / parallelism / mutually exclusive backends
        │
        ▼
tuner._training_function()
  · stage=pt → megatron_bridge.run_pt
  · stage=sft → megatron_bridge.run_sft
        │
        ├─ Export LF-aligned data → JSONL (mb_dataset/)
        ├─ SFT: HF → Megatron checkpoint (reusable)
        ├─ Build ConfigContainer → pretrain() / finetune()
        └─ Optional: export HF after training

File-Level Specifications

Parameters & Entry Points

File Description
hparams/megatron_bridge_args.py New dataclass: TP/PP/EP/CP, sequence parallel, recompute, distributed optimizer, overlap, packed sequences, mixed precision, pretrained ckpt, export_hf_on_finish, extra_config
hparams/finetuning_args.py use_megatron_bridge + runtime-attached megatron_bridge_args
hparams/parser.py _parse_train_mbridge_args, parallelism validation, mutual exclusion with MCA/HP/quantization/DeepSpeed
extras/packages.py is_megatron_bridge_available()
launcher.py Force FORCE_TORCHRUN when enabled
train/tuner.py Dispatch PT/SFT branches to megatron_bridge workflow

Core Training Path (new train/megatron_bridge/)

workflow.py

run_pt / run_sft: Load LF dataset → export → build config → call Bridge API

Runtime patches:

  • Skip make for pre-compiled helpers_cpp
  • Change dist checkpoint to synchronous D2H (bypass async preload failures on certain GPUs)
  • Optional post-training HF export (when NCCL initialized, use load_megatron_model + save_hf_pretrained)

config_builder.py

  • LF TrainingArguments → Megatron ConfigContainer
  • Training steps / global batch (scaled by parallelism)
  • Optimizer / scheduler / DDP / LoRA PEFT
  • ensure_megatron_pretrained_checkpoint: HF→Megatron conversion, cleanup after conversion
    • Clear parallel / rerun global state
  • Disable gradient_accumulation_fusion when APEX fusion is missing
  • Supplement SFT loss / DDP settings when CP>1
  • extra_config dot-path overrides

dataset_export.py

LF-aligned samples → Bridge JSONL
  • SFT: Prefer HF messages (requires chat template containing {% generation %}), otherwise fallback to ShareGPT
  • PT: Export plain text

Compatibility with different Bridge versions for dataloader / chat template APIs

Examples & Tests

examples/megatron_bridge/llama3_sft.yaml: Llama-3.2-1B, TP=2, SP, bf16, export_hf_on_finish

Unit tests: args validation, schedule, fusion safety, dataset export, GPU config / AutoBridge (skip when megatron-bridge not installed)

Capability Boundaries (v0)

Supported Not Supported / Limitations
PT, SFT Non-pt/sft stages
Full / LoRA Quantized models
TP/PP/CP/EP, SP, packed seq DeepSpeed
HF↔Megatron ckpt Concurrent use with MCA / HyperParallel
Trainer callbacks (ignored with warning)

Design Principles

  • Switch mechanism consistent with MCA: environment-variable driven, avoiding contamination of the default HF path.
  • Data bridging: reuse LF alignment logic, then export to Bridge-readable JSONL instead of rewriting the data loader.
  • Version compatibility: probe-based adaptation for Bridge config fields, chat templates, and dataloader types.
  • Engineering safeguards: clear global state after checkpoint conversion, downgrade APEX fusion, async D2H patch — prioritizing practical stability.

Before submitting

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request integrates the Megatron Bridge distributed training backend into LLaMA-Factory, enabling pretraining (PT) and supervised fine-tuning (SFT) with advanced parallelism features (tensor, pipeline, and context parallelism). Key additions include new configuration arguments, workflow orchestration, dataset exporting, and comprehensive unit tests. The review feedback highlights several important improvements: wrapping dataset exports in a local rank 0 check to prevent race conditions in distributed environments, capping warmup steps to avoid scheduler errors, warning users about ignored custom LoRA targets, supporting deeply nested overrides, stripping whitespace from JSON configurations, and validating that the world size is divisible by the total parallel size.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +164 to +171
export_dataset_for_megatron_bridge(
train_dataset=train_dataset,
output_dir=dataset_dir,
eval_dataset=eval_dict.get("validation") if eval_dict else None,
val_size=data_args.val_size,
seed=training_args.seed,
stage="pt",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

In distributed training, run_pt is executed on all ranks simultaneously. Since export_dataset_for_megatron_bridge writes to the same shared training.jsonl file, this causes a severe race condition and file corruption. We should wrap the dataset export in a local rank 0 check and use a file-based barrier to synchronize other ranks.

    import time
    done_file = os.path.join(dataset_dir, ".export_done")
    if training_args.local_process_index == 0:
        export_dataset_for_megatron_bridge(
            train_dataset=train_dataset,
            output_dir=dataset_dir,
            eval_dataset=eval_dict.get("validation") if eval_dict else None,
            val_size=data_args.val_size,
            seed=training_args.seed,
            stage="pt",
        )
        with open(done_file, "w", encoding="utf-8") as f:
            f.write("done")
    else:
        while not os.path.exists(done_file):
            time.sleep(1)

Comment on lines +202 to +211
export_dataset_for_megatron_bridge(
train_dataset=train_dataset,
output_dir=dataset_dir,
eval_dataset=eval_dict or None,
val_size=data_args.val_size if not eval_dict else 0.0,
seed=training_args.seed,
stage="sft",
model_name_or_path=model_args.model_name_or_path,
trust_remote_code=model_args.trust_remote_code,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

In distributed training, run_sft is executed on all ranks simultaneously. Since export_dataset_for_megatron_bridge writes to the same shared training.jsonl file, this causes a severe race condition and file corruption. We should wrap the dataset export in a local rank 0 check and use a file-based barrier to synchronize other ranks.

    import time
    done_file = os.path.join(dataset_dir, ".export_done")
    if training_args.local_process_index == 0:
        export_dataset_for_megatron_bridge(
            train_dataset=train_dataset,
            output_dir=dataset_dir,
            eval_dataset=eval_dict or None,
            val_size=data_args.val_size if not eval_dict else 0.0,
            seed=training_args.seed,
            stage="sft",
            model_name_or_path=model_args.model_name_or_path,
            trust_remote_code=model_args.trust_remote_code,
        )
        with open(done_file, "w", encoding="utf-8") as f:
            f.write("done")
    else:
        while not os.path.exists(done_file):
            time.sleep(1)

Comment on lines +54 to +55
if warmup_steps > 0:
return warmup_steps

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If warmup_steps is greater than train_iters (e.g., when training with a very small number of steps like max_steps: 5), it can cause unexpected behavior or division-by-zero errors in the Megatron scheduler. We should cap warmup_steps at train_iters.

Suggested change
if warmup_steps > 0:
return warmup_steps
if warmup_steps > 0:
return min(warmup_steps, train_iters)

Comment on lines +149 to +159
def _create_peft_config(finetuning_args: "FinetuningArguments"):
if finetuning_args.finetuning_type != "lora":
return None

from megatron.bridge.peft.lora import LoRA

return LoRA(
target_modules=["linear_qkv", "linear_proj", "linear_fc1", "linear_fc2"],
dim=finetuning_args.lora_rank,
alpha=finetuning_args.lora_alpha,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Megatron Bridge uses Megatron-specific module names for PEFT/LoRA. If a user specifies a custom lora_target in their configuration, it is silently ignored. We should add a warning to inform the user that custom lora_target settings are not supported and default Megatron targets are used.

def _create_peft_config(finetuning_args: "FinetuningArguments"):
    if finetuning_args.finetuning_type != "lora":
        return None

    from megatron.bridge.peft.lora import LoRA

    if finetuning_args.lora_target != ["all"]:
        logger.warning_rank0(
            f"Custom lora_target {finetuning_args.lora_target} is not supported by Megatron Bridge. "
            "Using default Megatron target modules: ['linear_qkv', 'linear_proj', 'linear_fc1', 'linear_fc2']."
        )

    return LoRA(
        target_modules=["linear_qkv", "linear_proj", "linear_fc1", "linear_fc2"],
        dim=finetuning_args.lora_rank,
        alpha=finetuning_args.lora_alpha,
    )

Comment on lines +374 to +380
def _apply_extra_overrides(cfg, extra: dict) -> None:
for key, value in extra.items():
if "." in key:
target, attr = key.split(".", 1)
setattr(getattr(cfg, target), attr, value)
else:
setattr(cfg, key, value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of _apply_extra_overrides only supports exactly one level of nesting (e.g., target.attr). If a dot-path has multiple levels of nesting (e.g., target.subtarget.attr), it fails or sets a literal attribute named "subtarget.attr" on the intermediate object. We should implement recursive traversal to support arbitrary nesting.

Suggested change
def _apply_extra_overrides(cfg, extra: dict) -> None:
for key, value in extra.items():
if "." in key:
target, attr = key.split(".", 1)
setattr(getattr(cfg, target), attr, value)
else:
setattr(cfg, key, value)
def _apply_extra_overrides(cfg, extra: dict) -> None:
for key, value in extra.items():
parts = key.split(".")
obj = cfg
for part in parts[:-1]:
obj = getattr(obj, part)
setattr(obj, parts[-1], value)

Comment on lines +107 to +108
if isinstance(self.extra_config, str) and self.extra_config.startswith("{"):
self.extra_config = _convert_str_dict(json.loads(self.extra_config))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If self.extra_config is a JSON string with leading whitespace, self.extra_config.startswith("{") will evaluate to False. It will then fail to parse as JSON and instead be treated as a file path, leading to a ValueError. We should strip the string before checking.

Suggested change
if isinstance(self.extra_config, str) and self.extra_config.startswith("{"):
self.extra_config = _convert_str_dict(json.loads(self.extra_config))
if isinstance(self.extra_config, str):
config_str = self.extra_config.strip()
if config_str.startswith("{"):
self.extra_config = _convert_str_dict(json.loads(config_str))

Comment on lines +314 to +315
if parallel_size > world_size:
raise ValueError(f"Total Megatron Bridge parallel size ({parallel_size}) exceeds `world_size` ({world_size}).")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Megatron strictly requires that the total world_size is divisible by the product of parallel sizes (so that the data parallel size is an integer). We should validate world_size % parallel_size == 0 to catch configuration errors early.

Suggested change
if parallel_size > world_size:
raise ValueError(f"Total Megatron Bridge parallel size ({parallel_size}) exceeds `world_size` ({world_size}).")
if parallel_size > world_size:
raise ValueError(f"Total Megatron Bridge parallel size ({parallel_size}) exceeds `world_size` ({world_size}).")
if world_size % parallel_size != 0:
raise ValueError(
f"Total Megatron Bridge parallel size ({parallel_size}) must divide `world_size` ({world_size})."
)

@sunyi0505

Copy link
Copy Markdown
Contributor Author

Test Environment

Hardware & System

Item Version
GPU Tesla V100-PCIE-32GB × 1
NVIDIA Driver 580.105.08
OS Linux (AutoDL)
Python 3.12.3

Core Dependencies (verified working)

Package Version Notes
megatron-bridge 0.5.0 Core backend; install with pip install --no-build-isolation megatron-bridge
megatron-core 0.18.0 Required by megatron-bridge
torch 2.12.1+cu126 Installed from the PyTorch cu126 index
torchvision 0.27.1+cu126
transformer_engine 2.17.0 Installed from NVIDIA PyPI
transformers 5.6.0 megatron-bridge requires >=5.8.1; we used DISABLE_VERSION_CHECK=1 to skip the check
llamafactory 0.9.6.dev0 Editable install via pip install -e .
accelerate 1.11.0
datasets 4.0.0
peft 0.18.1 Required for LoRA training
flashinfer-python 0.6.8.post1 Pinned dependency of megatron-bridge 0.5.0
nvidia-resiliency-ext 0.6.0 Required by megatron-core 0.18.0; not on PyPI yet, installed locally
triton 3.7.1
nccl 2.29.3 (cu12)

Installation Steps

# 1. Create virtual environment
python3 -m venv megatron-bridge-env
source megatron-bridge-env/bin/activate

# 2. Install PyTorch (CUDA 12.6)
pip install torch==2.12.1+cu126 torchvision==0.27.1+cu126 \
  --index-url https://download.pytorch.org/whl/cu126

# 3. Install Transformer Engine (requires NVIDIA PyPI)
pip install --no-build-isolation \
  --extra-index-url https://pypi.nvidia.com \
  transformer_engine[pytorch]

# 4. Install nvidia-resiliency-ext (required by megatron-core 0.18.0; 0.6.0 not on PyPI)
git clone -b v0.6.0 https://github.com/NVIDIA/nvidia-resiliency-ext.git
pip install -e ./nvidia-resiliency-ext

# 5. Install megatron-bridge
pip install --no-build-isolation megatron-bridge==0.5.0

# 6. Upgrade megatron-core to 0.18.0
pip install megatron-core==0.18.0

# 7. Install LLaMA-Factory
cd LLaMA-Factory
pip install -e .

Training Command

export USE_MEGATRON_BRIDGE=1
export DISABLE_VERSION_CHECK=1   # Skip version check when transformers doesn't match megatron-bridge constraints

llamafactory-cli train examples/megatron_bridge/llama3_sft.yaml

Setting USE_MEGATRON_BRIDGE=1 automatically enables torchrun for distributed launch (including single-GPU runs).


Verified Test Case

Item Value
Config examples/megatron_bridge/llama3_sft.yaml
Model Llama-3.2-1B-Instruct
Dataset alpaca_en_demo (999 samples)
Stage SFT, full finetuning
Parallelism TP=1, PP=1, CP=1
Precision bf16_mixed
Steps 5 (smoke test)
Result Training completed; HF checkpoint export succeeded

Known Limitations

  1. Only pt and sft stages are supported — DPO/RM/PPO and other stages are not supported.
  2. DeepSpeed, MCA, and HyperParallel are incompatible with Megatron Bridge.
  3. Quantized models are not supported.
  4. Trainer callbacks are not supported — a warning is logged and callbacks are ignored.
  5. Apex is optional — without it, Megatron falls back to Torch Norm; Apex CUDA extensions are needed only if gradient_accumulation_fusion is enabled.
  6. V100 compatibility — a blocking GPU→CPU copy patch is applied for distributed checkpoint saving to avoid cudaErrorInvalidValue on some GPUs (e.g. V100).
  7. transformers version mismatch — megatron-bridge 0.5.0 requires >=5.8.1,<5.9.0, which may conflict with LLaMA-Factory; use DISABLE_VERSION_CHECK=1 as a workaround.

train_megatron_bridge.log

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant