From 29f2e066259915319134975aa42953304d2b9ad1 Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Fri, 9 Jan 2026 22:35:22 +0000 Subject: [PATCH 1/9] Adding code to convert our old checkpoints from nemo to mbridge format Signed-off-by: John St. John --- .../recipes/evo2_megatron/README.md | 34 ++- .../recipes/evo2_megatron/pyproject.toml | 1 + .../src/bionemo/evo2/recipes/evo2.py | 1 - .../src/bionemo/evo2/run/train.py | 1 + .../evo2/utils/checkpoint/nemo2_to_mbridge.py | 286 ++++++++++++++++++ .../tests/bionemo/evo2/test_evo2.py | 123 +++++++- 6 files changed, 440 insertions(+), 6 deletions(-) create mode 100644 bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/utils/checkpoint/nemo2_to_mbridge.py diff --git a/bionemo-recipes/recipes/evo2_megatron/README.md b/bionemo-recipes/recipes/evo2_megatron/README.md index bd0e891e4c..f96d12567d 100644 --- a/bionemo-recipes/recipes/evo2_megatron/README.md +++ b/bionemo-recipes/recipes/evo2_megatron/README.md @@ -24,7 +24,7 @@ uv pip install -c pip-constraints.txt -e . --no-build-isolation ## 2. if on a6000s, you may need to disable p2p to avoid crashing export NCCL_P2P_DISABLE=1 ## 3. Run the job: -torchrun --nproc-per-node 8 --no-python \ +torchrun --nproc-per-node 2 --no-python \ train_evo2 \ --hf-tokenizer-model-path tokenizers/nucleotide_fast_tokenizer_256 \ --model-size striped_hyena_1b_nv_parallel --max-steps 12 --eval-interval 10 \ @@ -36,14 +36,42 @@ torchrun --nproc-per-node 8 --no-python \ --no-weight-decay-embeddings --cross-entropy-loss-fusion \ --align-param-gather --overlap-param-gather --grad-reduce-in-fp32 \ --decay-steps 100 --warmup-steps 10 \ - --mixed-precision-recipe bf16-mixed \ + --mixed-precision-recipe bf16_with_fp8_current_scaling_mixed \ --no-fp32-residual-connection --activation-checkpoint-recompute-num-layers 1 \ --attention-dropout 0.001 --hidden-dropout 0.001 \ --eod-pad-in-loss-mask --enable-preemption \ --log-interval 5 --debug-ddp-parity-freq 10 \ --wandb-project evo2-recipes-verification-tmp \ --wandb-run-name tmp_workstation_run_mock_data \ - --result-dir tmpbf16 --no-renormalize-loss + --result-dir tmpfp8 --no-renormalize-loss + +# fp8-cs: 69, 123 tflops/sec/gpu +# nemotron-h-fp8cs: 65, 120 tflops/sec/gpu +# bf16_v1: 58, 92 tflops/sec/gpu +``` + +``` +torchrun --nproc-per-node 2 --no-python \ + train_evo2 \ + --hf-tokenizer-model-path tokenizers/nucleotide_fast_tokenizer_256 \ + --model-size 1b --max-steps 12 --eval-interval 10 \ + --eval-iters 3 --mock-data \ + --micro-batch-size 32 --global-batch-size 256 --seq-length 1024 \ + --tensor-model-parallel 1 \ + --use-precision-aware-optimizer --dataset-seed 33 \ + --seed 41 --ckpt-async-save --spike-no-more-embedding-init \ + --no-weight-decay-embeddings --cross-entropy-loss-fusion \ + --align-param-gather --overlap-param-gather --grad-reduce-in-fp32 \ + --decay-steps 100 --warmup-steps 10 \ + --mixed-precision-recipe bf16_with_fp8_current_scaling_mixed \ + --no-fp32-residual-connection --activation-checkpoint-recompute-num-layers 1 \ + --attention-dropout 0.001 --hidden-dropout 0.001 \ + --eod-pad-in-loss-mask --enable-preemption \ + --log-interval 5 --debug-ddp-parity-freq 10 \ + --wandb-project evo2-recipes-verification-tmp \ + --wandb-run-name tmp_workstation_run_mock_data \ + --result-dir tmpfp8-ft --no-renormalize-loss \ + --finetune-ckpt-dir 1b_8k_bf16_weights ``` ## Docker build diff --git a/bionemo-recipes/recipes/evo2_megatron/pyproject.toml b/bionemo-recipes/recipes/evo2_megatron/pyproject.toml index 8f6cbc9b52..355d808460 100644 --- a/bionemo-recipes/recipes/evo2_megatron/pyproject.toml +++ b/bionemo-recipes/recipes/evo2_megatron/pyproject.toml @@ -40,6 +40,7 @@ train_evo2 = "bionemo.evo2.run.train:main" #predict_evo2 = "bionemo.evo2.run.predict:main" preprocess_evo2 = "bionemo.evo2.data.preprocess:main" splice_evo2 = "bionemo.evo2.data.transcript_extraction:main" +evo2_convert_nemo2_to_mbridge = "bionemo.evo2.utils.checkpoint.nemo2_to_mbridge:main" #evo2_convert_to_nemo2 = "bionemo.evo2.utils.checkpoint.convert_to_nemo:main" #evo2_nemo2_to_hf = "bionemo.evo2.utils.checkpoint.nemo2_to_hf:main" #evo2_remove_optimizer = "bionemo.evo2.utils.checkpoint.evo2_remove_optimizer:main" diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py index 6bc42c9e35..947d00e15b 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/recipes/evo2.py @@ -267,7 +267,6 @@ def _evo2_common( ), tokenizer=TokenizerConfig( tokenizer_type="HuggingFaceTokenizer", - hf_tokenizer_kwargs={"trust_remote_code": True}, tokenizer_model=hf_tokenizer_model_or_path or "EleutherAI/gpt-neox-20b", ), checkpoint=CheckpointConfig( diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py index bf789b7845..33497d0ef4 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py @@ -912,6 +912,7 @@ def train(args: argparse.Namespace) -> None: if args.finetune_ckpt_dir: cfg.checkpoint.finetune = True cfg.checkpoint.pretrained_checkpoint = args.finetune_ckpt_dir + cfg.checkpoint.dist_ckpt_strictness = "ignore_all" # necessary unfortunately to avoid extra_state issues. if args.nvidia_fault_tolerance: cfg.ft = FaultToleranceConfig( enable_ft_package=True, diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/utils/checkpoint/nemo2_to_mbridge.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/utils/checkpoint/nemo2_to_mbridge.py new file mode 100644 index 0000000000..dcc079c0c3 --- /dev/null +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/utils/checkpoint/nemo2_to_mbridge.py @@ -0,0 +1,286 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: LicenseRef-Apache2 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import os +from pathlib import Path +from typing import Any + +import torch +import torch.distributed.checkpoint as dcp +from megatron.bridge.training.checkpointing import save_tokenizer_assets +from megatron.bridge.training.config import ConfigContainer +from megatron.bridge.training.mixed_precision import MIXED_PRECISION_RECIPES +from megatron.bridge.training.tokenizers.tokenizer import build_tokenizer +from torch.distributed.checkpoint import FileSystemReader, FileSystemWriter +from torch.distributed.checkpoint.metadata import BytesStorageMetadata + +from bionemo.evo2.models.evo2_provider import HYENA_MODEL_OPTIONS, HyenaModelProvider +from bionemo.evo2.recipes.evo2 import evo2_1b_pretrain_config as pretrain_config + + +logger = logging.getLogger(__name__) + + +def convert_nemo2_dcp_to_megatron( + src_path: str | Path, + dest_path: str | Path, +): + """Convert a torch_dist format checkpoint with nemo2 style names to one with megatron bridge style names. + + Args: + src_path: Path to the source DCP checkpoint. + dest_path: Path to the destination DCP checkpoint. + """ + logger.info(f"Reading metadata from {src_path}...") + reader = FileSystemReader(str(src_path)) + metadata = reader.read_metadata() + + # 1. Pre-allocate state_dict based on metadata + # We need to construct the state_dict so dcp.load knows what to load. + state_dict = {} + total_size_bytes = 0 + + for key, item_meta in metadata.state_dict_metadata.items(): + if isinstance(item_meta, BytesStorageMetadata): + # Skip or handle non-tensor data if necessary + continue + + # Create empty tensor on CPU with correct shape/dtype + # DCP will load data into these tensors in-place + state_dict[key] = torch.empty(item_meta.size, dtype=item_meta.properties.dtype, device="cpu") + + # Track size to calculate shard count later + total_size_bytes += state_dict[key].numel() * state_dict[key].element_size() + + print(f"Loading {len(state_dict)} tensors into memory (Approx {total_size_bytes / 1e9:.2f} GB)...") + + # 2. Load directly from DCP to memory (no_dist=True for single process) + dcp.load(state_dict=state_dict, storage_reader=reader, no_dist=True) + + # 3. Munge Keys + # Removing "module." prefix as requested + prefix_len = len("module.") + new_state_dict = {} + for k, v in state_dict.items(): + # Safety check: ensure key actually has the prefix before slicing + if k.startswith("module."): + new_key = k[prefix_len:] + else: + new_key = k + new_state_dict[new_key] = v + + logger.info(f"Keys munged. saving to {dest_path}...") + + # 4. Save to DCP with Sharding + # Calculate required threads to achieve target shard size + # DCP FileSystemWriter writes one file per thread when single_file_per_rank=False + + writer = FileSystemWriter( + dest_path, + single_file_per_rank=False, # roughly one file per parameter + thread_count=os.cpu_count(), + ) + + dcp.save(state_dict=new_state_dict, storage_writer=writer, no_dist=True) + logger.info("Conversion complete.") + + +def _dummy_train_state() -> dict[str, torch.Tensor]: + """Use for train_state.pt file, and latest_train_state.pt file in mbridge checkpoint.""" + return { + "step": torch.tensor(1, dtype=torch.int32), + "consumed_train_samples": torch.tensor(0, dtype=torch.int32), + "skipped_train_samples": torch.tensor(0, dtype=torch.int32), + "consumed_valid_samples": torch.tensor(0, dtype=torch.int32), + "floating_point_operations_so_far": torch.tensor(0, dtype=torch.float64), + "do_train": torch.tensor(True, dtype=torch.bool), + "do_valid": torch.tensor(True, dtype=torch.bool), + "do_test": torch.tensor(True, dtype=torch.bool), + } + + +def _dummy_common_pt_dict() -> dict[str, Any]: + """Use for common.pt file in mbridge checkpoint.""" + return { + "checkpoint_version": 3.0, + "iteration": 1, + "optimizer": {"param_state_sharding_type": "dp_reshardable"}, + "opt_param_scheduler": { + "max_lr": 0.0003, + "lr_warmup_steps": 10, + "num_steps": 2560, + "lr_decay_style": "cosine", + "lr_decay_steps": 25600, + "min_lr": 3e-05, + "start_wd": 0.01, + "end_wd": 0.01, + "wd_incr_style": "constant", + "wd_incr_steps": 3072, + }, + "content_metadata": { + "singleton_local_shards": False, + "distrib_optim_sharding_type": "dp_reshardable", + "chained_optim_avoid_prefix": True, + }, + } + + +def _dummy_format_metadata() -> dict[str, Any]: + """Use for metadata.json file in mbridge checkpoint.""" + return { + "sharded_backend": "torch_dist", + "sharded_backend_version": 1, + "common_backend": "torch", + "common_backend_version": 1, + } + + +def nemo2_to_mbridge( + nemo2_ckpt_dir: Path, + tokenizer_path: Path, + mbridge_ckpt_dir: Path, + model_provider: HyenaModelProvider, + mixed_precision_recipe: str, + vortex_style_fp8: bool, +) -> Path: + """Convert a Nemo2 checkpoint to a Megatron Bridge checkpoint. + + Args: + nemo2_ckpt_dir: Path to the Nemo2 checkpoint directory. + tokenizer_path: Path to the tokenizer directory. + mbridge_ckpt_dir: Path to the Megatron Bridge checkpoint directory. + model_provider: Model provider to use for the model. + mixed_precision_recipe: Mixed precision recipe to use for the model. + vortex_style_fp8: Whether to use vortex style fp8? This is needed for the fp8 sensitive checkpoints from the + original evo2 training. For example the 1b model and the 40b models (not the nvidia bf16 finetuned + checkpoints). In general leave this as False though because it will only put a small number of layers in fp8. + + Returns: + Path to the Megatron Bridge checkpoint directory. + + Structure of a megatron bridge checkpoint: + + |-- latest_checkpointed_iteration.txt # the older megatron way of communicating the latest checkpointed iteration + |-- latest_train_state.pt # a copy of train_state.pt from the latest iteration, used by megatron bridge + ├── iter_0000001 + | ├── __*_*.distcp # distcp checkpoint files for each shard (sometiems rank sometimes arbitrary shards) + | ├── .metadata # metadata for the distcp checkpoint files + | ├── common.pt # common metadata (training configuration related) + | ├── metadata.json # metadata for the checkpoint format etc + | ├── run_config.yaml # training configuration + | ├── tokenizer # tokenizer assets + | ├── train_state.pt # training state, eg current step, etc. + """ + assert not mbridge_ckpt_dir.exists(), f"Checkpoint directory {mbridge_ckpt_dir} already exists" + mbridge_ckpt_dir.mkdir(parents=True, exist_ok=True) + mbridge_ckpt_iter_dir = mbridge_ckpt_dir / "iter_0000001" + nemo2_model_path = nemo2_ckpt_dir / "weights" + convert_nemo2_dcp_to_megatron(nemo2_model_path, mbridge_ckpt_iter_dir) + assert mbridge_ckpt_iter_dir.exists(), f"Checkpoint directory {mbridge_ckpt_iter_dir} does not exist" + with open(mbridge_ckpt_dir / "latest_checkpointed_iteration.txt", "w") as f: + f.write("1\n") + train_state = _dummy_train_state() + torch.save(train_state, mbridge_ckpt_iter_dir / "train_state.pt") + torch.save(train_state, mbridge_ckpt_dir / "latest_train_state.pt") + + common_pt_dict = _dummy_common_pt_dict() + torch.save(common_pt_dict, mbridge_ckpt_iter_dir / "common.pt") + format_metadata = _dummy_format_metadata() + with open(mbridge_ckpt_iter_dir / "metadata.json", "w") as f: + json.dump(format_metadata, f) + config_container: ConfigContainer = pretrain_config( + precision_config=mixed_precision_recipe, hf_tokenizer_model_or_path=tokenizer_path, mock=True + ) + tokenizer = build_tokenizer(config_container.tokenizer) + model_provider.vocab_size = tokenizer.vocab_size + model_provider.vortex_style_fp8 = vortex_style_fp8 + config_container.model = model_provider + config_container.to_yaml(str(mbridge_ckpt_iter_dir / "run_config.yaml")) + save_tokenizer_assets(tokenizer, config_container.tokenizer, str(mbridge_ckpt_iter_dir)) + return mbridge_ckpt_dir + + +def run_nemo2_to_mbridge( + nemo2_ckpt_dir: Path, + tokenizer_path: Path, + mbridge_ckpt_dir: Path, + model_size: str, + seq_length: int, + mixed_precision_recipe: str, + vortex_style_fp8: bool, +) -> Path: + """Convert a Nemo2 checkpoint to a Megatron Bridge checkpoint. + + Args: + nemo2_ckpt_dir: Path to the Nemo2 checkpoint directory. + tokenizer_path: Path to the tokenizer directory. + mbridge_ckpt_dir: Path to the Megatron Bridge checkpoint directory. + model_size: Model size to use for the model. + seq_length: Sequence length to use for the model. + mixed_precision_recipe: Mixed precision recipe to use for the model. + vortex_style_fp8: Whether to use vortex style fp8? This is needed for the fp8 sensitive checkpoints from the + original evo2 training. For example the 1b model and the 40b models (not the nvidia bf16 finetuned + checkpoints). In general leave this as False though because it will only put a small number of layers in fp8. + + Returns: + Path to the Megatron Bridge checkpoint directory. + """ + model_provider = HYENA_MODEL_OPTIONS[model_size](seq_length=seq_length) + res_dir = nemo2_to_mbridge( + nemo2_ckpt_dir, tokenizer_path, mbridge_ckpt_dir, model_provider, mixed_precision_recipe, vortex_style_fp8 + ) + logger.info(f"Megatron Bridge checkpoint saved to {res_dir}") + return res_dir + + +def main(): + """Main function for handling cli args and running the conversion.""" + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("--verbose", "-v", action="store_true") + parser.add_argument("--nemo2-ckpt-dir", type=Path, required=True) + parser.add_argument("--tokenizer-path", type=Path, required=True) + parser.add_argument("--mbridge-ckpt-dir", type=Path, required=True) + parser.add_argument("--model-size", type=str, choices=list(HYENA_MODEL_OPTIONS.keys()), required=True) + parser.add_argument("--seq-length", type=int, required=True) + parser.add_argument("--vortex-style-fp8", action="store_true", default=False) + parser.add_argument( + "--mixed-precision-recipe", + type=str, + choices=list(MIXED_PRECISION_RECIPES.keys()), + default="bf16_mixed", + help="Mixed precision recipe to use for training.", + ) + args = parser.parse_args() + if args.verbose: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + run_nemo2_to_mbridge( + args.nemo2_ckpt_dir, + args.tokenizer_path, + args.mbridge_ckpt_dir, + args.model_size, + args.seq_length, + args.mixed_precision_recipe, + args.vortex_style_fp8, + ) + + +if __name__ == "__main__": + main() diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py index f1f346830b..61dcb69a1d 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py @@ -30,8 +30,12 @@ import pandas as pd import pytest import torch +from megatron.bridge.training.checkpointing import ( + _load_model_weights_from_checkpoint, +) +from megatron.bridge.training.model_load_save import load_model_config from megatron.bridge.training.tokenizers.config import TokenizerConfig -from megatron.bridge.training.tokenizers.tokenizer import build_tokenizer +from megatron.bridge.training.tokenizers.tokenizer import _HuggingFaceTokenizer, build_tokenizer from megatron.core import dist_checkpointing, parallel_state from megatron.core.dist_checkpointing.mapping import ShardedTensor @@ -50,13 +54,14 @@ from pytest import MonkeyPatch from bionemo.core.data.load import load -from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH +from bionemo.evo2.data.dataset_tokenizer import DEFAULT_HF_TOKENIZER_MODEL_PATH, DEFAULT_HF_TOKENIZER_MODEL_PATH_512 from bionemo.evo2.models.evo2_provider import ( Hyena1bModelProvider, Hyena7bARCLongContextModelProvider, Hyena7bModelProvider, HyenaInferenceContext, ) +from bionemo.evo2.utils.checkpoint.nemo2_to_mbridge import run_nemo2_to_mbridge logger = logging.getLogger(__name__) @@ -277,6 +282,18 @@ def determine_memory_requirement_and_skip_if_not_met(ckpt_name: str, test_name: "seq_len_cap": 4000, "memory_needed_by_test": 21, }, # checked both variants in isolation + { + "test_name": "test_forward_ckpt_conversion", + "model_size": "1b", + "seq_len_cap": 6000, + "memory_needed_by_test": 18, + }, # checked both variants in isolation + { + "test_name": "test_forward_ckpt_conversion", + "model_size": "7b", + "seq_len_cap": 4000, + "memory_needed_by_test": 21, + }, # checked both variants in isolation { "test_name": "test_batch_generate", "model_size": "1b", @@ -755,6 +772,108 @@ def test_forward_manual(sequences: list[str], ckpt_name: str, expected_matchperc ) +@pytest.mark.parametrize( + "ckpt_name,expected_matchpercents,flash_decode", + [ + # Try flash decode with one and not the other to verify that both paths work. + ("evo2/1b-8k-bf16:1.0", [96.27, 67.93, 77.50, 80.30], True), + ("evo2/1b-8k:1.0", [96.27, 67.93, 77.50, 80.30], False), + ("evo2/7b-8k:1.0", [97.60, 89.63, 80.03, 84.57], False), + ("evo2/7b-1m:1.0", [97.60, 89.63, 80.03, 84.57], False), + ], +) +def test_forward_ckpt_conversion( + tmp_path: Path, sequences: list[str], ckpt_name: str, expected_matchpercents: list[float], flash_decode: bool +): + """Test the forward pass of the megatron model.""" + assert len(sequences) > 0 + seq_len_cap = determine_memory_requirement_and_skip_if_not_met( + ckpt_name, test_name=inspect.currentframe().f_code.co_name + ) + + is_fp8_supported, compute_capability, device_info = check_fp8_support(torch.cuda.current_device()) + skip = "evo2/1b-8k:" in ckpt_name and not is_fp8_supported + + # vortex_style_fp8 = is_fp8_supported and "bf16" not in ckpt_name + if skip: + # This checkpoint is sensitive to FP8, so we skip it if it is not supported on the current device. + pytest.skip(f"Skipping {ckpt_name} because it is not supported on {device_info} ({compute_capability})") + with distributed_model_parallel_state(), torch.no_grad(): + ckpt_path: Path = load(ckpt_name) + + mbridge_ckpt_dir = run_nemo2_to_mbridge( + nemo2_ckpt_dir=ckpt_path, + tokenizer_path=DEFAULT_HF_TOKENIZER_MODEL_PATH_512, + mbridge_ckpt_dir=tmp_path / "mbridge_checkpoint", + model_size="1b" if "1b" in ckpt_name else "7b" if "7b-8k" in ckpt_name else "7b_arc_longcontext", + seq_length=1048576 if "1m" in ckpt_name else 8192, + mixed_precision_recipe="bf16_mixed" if not is_fp8_supported else "bf16_with_fp8_current_scaling_mixed", + # The checkpoints from the original evo2 training that are "fp8 sensitive" require vortex_style_fp8=True + # to run correctly. If we set it in the config going into the conversion then at load time users will + # get this setting without having to think about it. + vortex_style_fp8=is_fp8_supported and "evo2/1b-8k:" in ckpt_name, + ) + + mbridge_ckpt_path = mbridge_ckpt_dir / "iter_0000001" + + model_config, mtron_args = load_model_config(mbridge_ckpt_path) + assert mtron_args is None, "mtron_args should be None since this is a Megatron Bridge checkpoint" + if flash_decode: + model_config.flash_decode = flash_decode + model_config.attention_backend = AttnBackend.flash + tokenizer = _HuggingFaceTokenizer(mbridge_ckpt_path / "tokenizer") + # tokenizer = load_tokenizer(mbridge_ckpt_path) # FIXME + # (Pdb) cfg + # TokenizerConfig(vocab_size=None, vocab_file=None, merge_file=None, vocab_extra_ids=0, + # tokenizer_type='HuggingFaceTokenizer', + # tokenizer_model=PosixPath('.'), <- fix this problem. When we save the tokenizer, we should somehow communicate the relative path to the saved tokenizer. + # tiktoken_pattern=None, tiktoken_num_special_tokens=1000, tiktoken_special_tokens=None, + # tokenizer_prompt_format=None, special_tokens=None, image_tag_type=None) + # tokenizer = AutoTokenizer.from_pretrained(mbridge_ckpt_path / "tokenizer") + model_config.finalize() # important to call finalize before providing the model, this does post_init etc. + raw_megatron_model = model_config.provide(pre_process=True, post_process=True).eval().cuda() + device = raw_megatron_model.parameters().__next__().device + _load_model_weights_from_checkpoint( + checkpoint_path=mbridge_ckpt_path, model=[raw_megatron_model], dist_ckpt_strictness="ignore_all" + ) + model = Float16Module(model_config, raw_megatron_model) + + if flash_decode: + inference_context = HyenaInferenceContext(max_batch_size=1, max_sequence_length=8192) + # Ensure full-sequence logits are materialized for tests expecting [B, S, V] + inference_context.materialize_only_last_token_logits = False + forward_kwargs = {"runtime_gather_output": True, "inference_context": inference_context} + else: + forward_kwargs = {} + matchrates = [] + for seq in sequences: + # TODO: artificial limit, megatron uses more memory. Vortex can process full sequences + partial_seq = seq[:seq_len_cap] + with torch.no_grad(): + # tokens = torch.tensor([tokenizer.tokenize(seq)], device=device) + input_ids = torch.tensor(tokenizer.text_to_ids(partial_seq)).int().unsqueeze(0).to(device) + attention_mask = None + # when labels is None, the model returns logits + logits = model( + input_ids=input_ids, + position_ids=None, + attention_mask=attention_mask, + labels=None, + **forward_kwargs, + ) + if flash_decode: + forward_kwargs["inference_context"].reset() + matchrate = _calc_matchrate(tokenizer=tokenizer, in_seq=partial_seq, logits=logits) + matchrates.append(matchrate) + _check_matchrate(ckpt_name=ckpt_name, matchrate=matchrate, assert_matchrate=False) + assert len(matchrates) == len(expected_matchpercents) + matchperc_print = [f"{m * 100.0:.1f}%" for m in matchrates] + matchperc_print_expected = [f"{ep:.1f}%" for ep in expected_matchpercents] + assert all(m * 100.0 >= 0.95 * ep for m, ep in zip(matchrates, expected_matchpercents)), ( + f"Expected at least 95% of {matchperc_print_expected=}, got {matchperc_print=}" + ) + + # def mid_point_split(*, seq, num_tokens: int | None = None, fraction: float = 0.5): # mid_point = int(fraction * len(seq)) # prompt = seq[:mid_point] From 0c3de1b27c7989bbc5b04d0ac3c1572c05d11e37 Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Fri, 9 Jan 2026 22:54:05 +0000 Subject: [PATCH 2/9] Adding more examples to readme that include checkpoint conversion Signed-off-by: John St. John --- .../recipes/evo2_megatron/README.md | 57 ++++++++++++++----- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/bionemo-recipes/recipes/evo2_megatron/README.md b/bionemo-recipes/recipes/evo2_megatron/README.md index f96d12567d..22ec05c685 100644 --- a/bionemo-recipes/recipes/evo2_megatron/README.md +++ b/bionemo-recipes/recipes/evo2_megatron/README.md @@ -19,6 +19,8 @@ uv pip install -c pip-constraints.txt -e . --no-build-isolation ## Usage +### Example job + ``` # 3. Run an example job ## 2. if on a6000s, you may need to disable p2p to avoid crashing @@ -29,10 +31,10 @@ torchrun --nproc-per-node 2 --no-python \ --hf-tokenizer-model-path tokenizers/nucleotide_fast_tokenizer_256 \ --model-size striped_hyena_1b_nv_parallel --max-steps 12 --eval-interval 10 \ --eval-iters 3 --mock-data \ - --micro-batch-size 32 --global-batch-size 256 --seq-length 1024 \ + --micro-batch-size 16 --global-batch-size 32 --seq-length 1024 \ --tensor-model-parallel 1 \ --use-precision-aware-optimizer --dataset-seed 33 \ - --seed 41 --ckpt-async-save --spike-no-more-embedding-init \ + --seed 41 --spike-no-more-embedding-init \ --no-weight-decay-embeddings --cross-entropy-loss-fusion \ --align-param-gather --overlap-param-gather --grad-reduce-in-fp32 \ --decay-steps 100 --warmup-steps 10 \ @@ -41,26 +43,53 @@ torchrun --nproc-per-node 2 --no-python \ --attention-dropout 0.001 --hidden-dropout 0.001 \ --eod-pad-in-loss-mask --enable-preemption \ --log-interval 5 --debug-ddp-parity-freq 10 \ - --wandb-project evo2-recipes-verification-tmp \ - --wandb-run-name tmp_workstation_run_mock_data \ --result-dir tmpfp8 --no-renormalize-loss +``` + +### Example fine-tune from an existing checkpoint + +First convert the checkpoint from nemo2 format (temporary step until we upload the new files) + +Good checkpoint names to try are: + +- evo2/1b-8k-bf16:1.0 (model_size: 1b) +- evo2/7b-1m:1.0 (model_size: 7b_arc_longcontext) +- evo2/40b-1m-fp8-bf16:1.0 (model_size: 40b_arc_longcontext) + +Other than the 7b version, the other two are checkpoints fine-tuned by the BioNeMo team to support both FP8 and BF16 +precision. The 7b version worked well on both FP8 and BF16 out of the box so it was not fine-tuned further. If you do +want to use one of the FP8 sensitive checkpoints, like `evo2/40b-1m` then be sure to add the `--vortex-style-fp8` +option to the checkpoint conversion step below. Also note that although 8k versions of the 7b and 40b checkpoints exist, +it is advisable to use the longer context versions since they were trained further and still run on shorter inputs. + +``` +CKPT_NAME=evo2/1b-8k-bf16:1.0 +CKPT_OUT_DIR=evo2_1b_8k_bf16_mbridge +evo2_convert_nemo2_to_mbridge \ + --mixed-precision-recipe bf16_with_fp8_current_scaling_mixed \ + --tokenizer-path tokenizers/nucleotide_fast_tokenizer_512 \ + --model-size 1b \ + --seq-length 8192 \ + --nemo2-ckpt-dir $(download_bionemo_data $CKPT_NAME) \ + --mbridge-ckpt-dir $CKPT_OUT_DIR -# fp8-cs: 69, 123 tflops/sec/gpu -# nemotron-h-fp8cs: 65, 120 tflops/sec/gpu -# bf16_v1: 58, 92 tflops/sec/gpu ``` +Now run like before, but include the fine-tuned checkpoint directory you converted in the previous step with +`--finetune-ckpt-dir $CKPT_OUT_DIR`. Also if you have problems with `bf16_with_fp8_current_scaling_mixed` try +`bf16_mixed`. + ``` torchrun --nproc-per-node 2 --no-python \ train_evo2 \ - --hf-tokenizer-model-path tokenizers/nucleotide_fast_tokenizer_256 \ + --hf-tokenizer-model-path tokenizers/nucleotide_fast_tokenizer_512 \ --model-size 1b --max-steps 12 --eval-interval 10 \ --eval-iters 3 --mock-data \ - --micro-batch-size 32 --global-batch-size 256 --seq-length 1024 \ + --micro-batch-size 16 --global-batch-size 32 --seq-length 1024 \ --tensor-model-parallel 1 \ --use-precision-aware-optimizer --dataset-seed 33 \ - --seed 41 --ckpt-async-save --spike-no-more-embedding-init \ - --no-weight-decay-embeddings --cross-entropy-loss-fusion \ + --seed 41 \ + --cross-entropy-loss-fusion \ --align-param-gather --overlap-param-gather --grad-reduce-in-fp32 \ --decay-steps 100 --warmup-steps 10 \ --mixed-precision-recipe bf16_with_fp8_current_scaling_mixed \ @@ -68,10 +97,8 @@ torchrun --nproc-per-node 2 --no-python \ --attention-dropout 0.001 --hidden-dropout 0.001 \ --eod-pad-in-loss-mask --enable-preemption \ --log-interval 5 --debug-ddp-parity-freq 10 \ - --wandb-project evo2-recipes-verification-tmp \ - --wandb-run-name tmp_workstation_run_mock_data \ - --result-dir tmpfp8-ft --no-renormalize-loss \ - --finetune-ckpt-dir 1b_8k_bf16_weights + --result-dir tmpfp8-ft-example --no-renormalize-loss \ + --finetune-ckpt-dir $CKPT_OUT_DIR ``` ## Docker build From 82cd0ddbb1189eb27af771020da70d9f361c4c5a Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Fri, 9 Jan 2026 23:07:01 +0000 Subject: [PATCH 3/9] Also add in fix for loading evo2 data Signed-off-by: John St. John --- .../recipes/evo2_megatron/src/bionemo/evo2/run/train.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py index 33497d0ef4..bf952c08c4 100644 --- a/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py +++ b/bionemo-recipes/recipes/evo2_megatron/src/bionemo/evo2/run/train.py @@ -710,9 +710,9 @@ def train(args: argparse.Namespace) -> None: recipe_kwargs["stride"] = args.stride recipe_kwargs["window_min_length_threshold"] = args.window_min_length_threshold recipe_kwargs["rc_aug"] = args.rc_aug - elif args.dataset_config_path: + elif args.dataset_config: recipe_kwargs["dataset_dir"] = args.dataset_dir - recipe_kwargs["dataset_config_path"] = args.dataset_config_path + recipe_kwargs["dataset_config_path"] = args.dataset_config recipe_kwargs["pad_eod_loss_mask"] = args.eod_pad_in_loss_mask From cf65fd7fb27dcb779c426fdbb3c1e71b9e2cec4d Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Sat, 10 Jan 2026 00:38:42 +0000 Subject: [PATCH 4/9] Add some more comments about finding useful items Signed-off-by: John St. John --- bionemo-recipes/recipes/evo2_megatron/README.md | 8 ++++++++ .../evo2_megatron/tests/bionemo/evo2/test_evo2.py | 10 ++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bionemo-recipes/recipes/evo2_megatron/README.md b/bionemo-recipes/recipes/evo2_megatron/README.md index 22ec05c685..aca405b4e3 100644 --- a/bionemo-recipes/recipes/evo2_megatron/README.md +++ b/bionemo-recipes/recipes/evo2_megatron/README.md @@ -62,6 +62,9 @@ want to use one of the FP8 sensitive checkpoints, like `evo2/40b-1m` then be sur option to the checkpoint conversion step below. Also note that although 8k versions of the 7b and 40b checkpoints exist, it is advisable to use the longer context versions since they were trained further and still run on shorter inputs. +See `download_bionemo_data --list-resources` for other checkpoint options and a list of available +downloadable resources. + ``` CKPT_NAME=evo2/1b-8k-bf16:1.0 CKPT_OUT_DIR=evo2_1b_8k_bf16_mbridge @@ -101,6 +104,11 @@ torchrun --nproc-per-node 2 --no-python \ --finetune-ckpt-dir $CKPT_OUT_DIR ``` +## Where do the custom command line programs come from? + +See `pyproject.toml` for where runnable programs like `train_evo2` and `evo2_convert_nemo2_to_mbridge` are implemented +in code. + ## Docker build ``` diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py index 61dcb69a1d..700419333b 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_evo2.py @@ -822,14 +822,8 @@ def test_forward_ckpt_conversion( model_config.flash_decode = flash_decode model_config.attention_backend = AttnBackend.flash tokenizer = _HuggingFaceTokenizer(mbridge_ckpt_path / "tokenizer") - # tokenizer = load_tokenizer(mbridge_ckpt_path) # FIXME - # (Pdb) cfg - # TokenizerConfig(vocab_size=None, vocab_file=None, merge_file=None, vocab_extra_ids=0, - # tokenizer_type='HuggingFaceTokenizer', - # tokenizer_model=PosixPath('.'), <- fix this problem. When we save the tokenizer, we should somehow communicate the relative path to the saved tokenizer. - # tiktoken_pattern=None, tiktoken_num_special_tokens=1000, tiktoken_special_tokens=None, - # tokenizer_prompt_format=None, special_tokens=None, image_tag_type=None) - # tokenizer = AutoTokenizer.from_pretrained(mbridge_ckpt_path / "tokenizer") + # FIXME replace above with below once bug is fixed https://github.com/NVIDIA-NeMo/Megatron-Bridge/issues/1900 + # tokenizer = load_tokenizer(mbridge_ckpt_path) model_config.finalize() # important to call finalize before providing the model, this does post_init etc. raw_megatron_model = model_config.provide(pre_process=True, post_process=True).eval().cuda() device = raw_megatron_model.parameters().__next__().device From 5eb1775cbd267e1f7c1e9f1e5dc273ce2a75e334 Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Mon, 12 Jan 2026 20:11:38 +0000 Subject: [PATCH 5/9] Adding a test that covers fine-tuning similarly to the stop/go test Signed-off-by: John St. John --- .../tests/bionemo/evo2/test_stop_and_go.py | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py index af9f02f151..e58eda0928 100644 --- a/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py +++ b/bionemo-recipes/recipes/evo2_megatron/tests/bionemo/evo2/test_stop_and_go.py @@ -240,3 +240,169 @@ def test_stop_and_go( assert first_loss_run2 - last_loss_run1 < 0.1, ( f"Run 2 first loss {first_loss_run2} is not better than run 1 last loss {last_loss_run1} by no worse than 0.1" ) + + +@pytest.mark.slow +def test_fine_tuning( + tmp_path: Path, + tp_size: int = 1, + cp_size: int = 1, + dp_size: int = 1, + dp_rank_check: bool = True, + precision_recipe: str = "bf16_mixed", + pp_size: int = 1, +): + """Test fine-tuning functionality, which should mirror stop/go but reset optimizer, data, and training state.""" + world_size = tp_size * pp_size * cp_size * dp_size + mbs = 32 + gbs = mbs * dp_size + num_gpus = torch.cuda.device_count() + if world_size > num_gpus: + pytest.skip(f"World size {world_size} is greater than the number of GPUs {num_gpus}") + if "nvfp4" in precision_recipe and not is_fp4_supported(): + pytest.skip("NVFP4 is not supported on this device") + if "mxfp8" in precision_recipe and not is_mxfp8_supported(): + pytest.skip("MXFP8 is not supported on this device") + if "fp8" in precision_recipe and not is_fp8_supported(): + pytest.skip("FP8 is not supported on this device") + if "bf16_with_fp8_delayed_scaling_mixed" == precision_recipe and is_fp8_supported(): + pytest.xfail(reason="FP8 delayed scaling is not currently working with Evo2, use another FP8 recipe.") + if "bf16_with_fp8_subchannel_scaling_mixed" == precision_recipe and is_fp8_supported(): + pytest.xfail(reason="FP8 subchannel scaling is not currently working with Evo2 on some GPUs.") + run_dir = tmp_path / f"run_tp{tp_size}_pp{pp_size}_cp{cp_size}_dp{dp_size}_rc{dp_rank_check}_pr{precision_recipe}" + run_dir.mkdir(parents=True, exist_ok=True) + master_port = find_free_network_port() + dp_rank_check_str = "--debug-ddp-parity-freq 5" if dp_rank_check else "" + cmd1 = f"""torchrun --nproc-per-node {world_size} --no-python --master_port {master_port} \ + train_evo2 \ + --hf-tokenizer-model-path {DEFAULT_HF_TOKENIZER_MODEL_PATH} \ + --model-size striped_hyena_1b_nv_parallel --num-layers 4 --hybrid-override-pattern SDH* \ + --max-steps 5 --eval-interval 5 \ + --eval-iters 3 --mock-data --result-dir {run_dir} \ + --micro-batch-size {mbs} --global-batch-size {gbs} --seq-length 512 \ + --tensor-model-parallel {tp_size} \ + --pipeline-model-parallel {pp_size} \ + --context-parallel {cp_size} \ + --mixed-precision-recipe {precision_recipe} \ + --overlap-param-gather \ + --overlap-grad-reduce \ + {dp_rank_check_str} \ + --use-precision-aware-optimizer --dataset-seed 33 \ + --seed 41 --spike-no-more-embedding-init \ + --no-weight-decay-embeddings --cross-entropy-loss-fusion \ + --grad-reduce-in-fp32 \ + --decay-steps 1000 --warmup-steps 10 \ + --eod-pad-in-loss-mask \ + --log-interval 1 \ + """ + + # Split the command and run it + cmd_parts = shlex.split(cmd1) + env = copy.deepcopy(PRETEST_ENV) + env["NCCL_P2P_DISABLE"] = "1" + result = subprocess.run(cmd_parts, check=False, capture_output=True, text=True, cwd=run_dir, env=env) + + stdout = result.stdout + stderr = result.stderr + returncode = result.returncode + + # For debugging, print the output + print(f"Return code: {returncode}") + print(f"STDOUT:\n{stdout}") + print(f"STDERR:\n{stderr}") + + # Assert the command succeeded + assert returncode == 0, f"Command failed with return code {returncode}\nSTDERR:\n{stderr}" + result_dir = run_dir / "evo2" + ckpt_dir = result_dir / "checkpoints" + tb_log_dir = result_dir / "tb_logs" + assert ckpt_dir.exists() and ckpt_dir.is_dir(), "Checkpoints directory not found" + assert tb_log_dir.exists() and tb_log_dir.is_dir(), "TensorBoard logs directory not found" + iter_5_dir = ckpt_dir / "iter_0000005" + assert iter_5_dir.exists() and iter_5_dir.is_dir(), f"No iterations 5 checkpoint found in {ckpt_dir}" + assert len(list(ckpt_dir.glob("iter_*"))) == 1, f"Expected 1 iterations, found {list(ckpt_dir.glob('iter_*'))}" + # Load tensorboard logs to verify they were written correctly + + # Find the events file(s) in tb_log_dir + event_files = list(tb_log_dir.rglob("events.out.*")) + assert len(event_files) > 0, f"No tensorboard event files found in {tb_log_dir}" + + # Load events from the event files + event_acc = EventAccumulator(str(tb_log_dir)) + event_acc.Reload() + + # 1. collect the last loss, as well as the average of the last step validation losses, as well as the last step + # Note: EventAccumulator.Scalars returns a list of ScalarEvent(wall_time, step, value) + lm_loss_events = event_acc.Scalars("lm loss") + + assert len(lm_loss_events) > 0, "No 'lm loss' events found in run 1" + last_lm_loss_step = lm_loss_events[-1].step + + assert last_lm_loss_step == 5, f"Expected run 1 to end at step 5, but got {last_lm_loss_step}" + + # 2. run the above training command a second time, this time set max_steps to 10. Verify that the run resumes from the last step. + # Do this by moving the tb_logs to a different directory from the first part so the second run makes fresh logs. + tb_log_dir_run1 = result_dir / "tb_logs_run1" + if tb_log_dir.exists(): + shutil.move(str(tb_log_dir), str(tb_log_dir_run1)) + + # Modify the command to increase max steps to 10 + # We reuse the same result_dir so it picks up the checkpoint + ft_run_dir = ( + tmp_path / f"ft_run_tp{tp_size}_pp{pp_size}_cp{cp_size}_dp{dp_size}_rc{dp_rank_check}_pr{precision_recipe}" + ) + ft_run_dir.mkdir(parents=True, exist_ok=True) + cmd2 = cmd1.rstrip().replace(f"--result-dir {run_dir}", f"--result-dir {ft_run_dir}") + cmd2 += f" --finetune-ckpt-dir {ckpt_dir} " + cmd_parts_2 = shlex.split(cmd2) + + print("Starting Run 2 (resuming to step 10)...") + result_2 = subprocess.run(cmd_parts_2, check=False, capture_output=True, text=True, cwd=run_dir, env=env) + + print(f"Run 2 Return code: {result_2.returncode}") + if result_2.returncode != 0: + print(f"Run 2 STDERR:\n{result_2.stderr}") + + assert result_2.returncode == 0, f"Run 2 failed with return code {result_2.returncode}" + + # 3. Load the new tb logs as before, and sanity check my recommendations as well as any others that make sense. + ft_result_dir = ft_run_dir / "evo2" + ft_tb_log_dir = ft_result_dir / "tb_logs" + assert ft_tb_log_dir.exists(), "TensorBoard logs directory not found after Run 2" + + event_acc_2 = EventAccumulator(str(ft_tb_log_dir)) + event_acc_2.Reload() + + lm_loss_events_2 = event_acc_2.Scalars("lm loss") + assert len(lm_loss_events_2) > 0, "No 'lm loss' events found in run 2" + + first_step_run2 = lm_loss_events_2[0].step + first_step_run1 = lm_loss_events[0].step + last_step_run2 = lm_loss_events_2[-1].step + + # Sanity checks: + # 1. Resumption: Should start after step 5 (e.g., step 6) + assert first_step_run2 == first_step_run1, ( + f"Run 2 FT steps should match run 1, but started at {first_step_run2} vs {first_step_run1}" + ) + + # 2. Completion: Should reach step 5 like run 1 + assert last_step_run2 == 5, f"Run 2 should reach step 5, but ended at {last_step_run2}" + + # 3. Loss Continuity check (basic): The first loss of run 2 should be reasonably close to the last loss of run 1, + # or at least not exploding, though optimization steps might cause fluctuations. + first_loss_run1 = lm_loss_events[0].value + first_loss_run2 = lm_loss_events_2[0].value + last_loss_run1 = lm_loss_events[-1].value + assert first_loss_run1 > last_loss_run1, ( + f"Run 1 first loss {first_loss_run1} is less than run 1 last loss {last_loss_run1}" + ) + assert first_loss_run2 < first_loss_run1, ( + f"Run 2 first loss {first_loss_run2} is greater than run 1 first loss {first_loss_run1}" + ) + assert abs(first_loss_run2 - first_loss_run1) > abs(last_loss_run1 - first_loss_run2), ( + f"Run 2 beginning {first_loss_run2} should be closer to end of run 1 {last_loss_run1} than beginning {first_loss_run1}." + ) + assert first_loss_run2 - last_loss_run1 < 0.1, ( + f"Run 2 first loss {first_loss_run2} is not better than run 1 last loss {last_loss_run1} by no worse than 0.1" + ) From 62cd318e8770e345ea82b845fd8573e473adf516 Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Mon, 12 Jan 2026 22:00:31 +0000 Subject: [PATCH 6/9] Adding new readme updates per feedback. Signed-off-by: John St. John --- .../recipes/evo2_megatron/README.md | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/bionemo-recipes/recipes/evo2_megatron/README.md b/bionemo-recipes/recipes/evo2_megatron/README.md index aca405b4e3..27b361d275 100644 --- a/bionemo-recipes/recipes/evo2_megatron/README.md +++ b/bionemo-recipes/recipes/evo2_megatron/README.md @@ -114,3 +114,43 @@ in code. ``` docker build -t evo2_megatron_recipe-$(git rev-parse --short HEAD) . ``` + +## Performance and accuracy comparisons + +NOTE: this section is largely a work in progress. This reflects the most updated information, but may not reflect the +current state of the code base at any given time. + +### Training accuracy convergence + +We ran a 12 hour 48 H100 GPU training run to compare megatron bridge with nemo2. We found that FP8 current scaling +converges by around the 5,000th step to the bf16 lines. And that bf16 is comparable with nemo2. Interestingly in nemo2 +bf16 and fp8 followed nearly identical trajectories for the first 5k steps as well. Note that in a typical training run +we are performing over 100k steps, so different behavior in the first 5k steps is less worrisome if the endpoints are +comparable. + +![Training Convergence Comparison](assets/mbridge_to_nemo_training_convergence_7ksteps.png) + +### Training performance comparisons + +FP8 current scaling which is supposed to have better convergence properties than delayed scaling, performs nearly as +well as delayed scaling in mbridge. Even leaving multiple transformer layers in bf16 precision trains faster than fp8 +delayed scaling in nemo2. + +| Evo2 1B Run | Seconds per step (lower is better) | Tokens/sec/GPU | Global Batch Size | Number of GPUs | Vocab Size | +| :----------------------------------------------: | :--------------------------------: | :------------: | :---------------: | :------------: | :--------: | +| MBridge BF16 | 6.10 | 26,859 | 960 | 48 | 256 | +| MBridge FP8 (delayed) | 5.38 | 30,453 | 960 | 48 | 256 | +| MBridge FP8 (current) | 5.44 | 28,755 | 960 | 48 | 512 | +| MBridge FP8 (current first/last two layers bf16) | 5.47 | 28,598 | 960 | 48 | 512 | +| Nemo2 FP8 (delayed) | 6.18 | 26,511 | 960 | 48 | 512 | + +Activation memory optimizations have enabled context parallelism to work better with evo2 style models in our mbridge +implementation than the previous nemo2 implementation. This enables significantly faster step timing at long context +as well as demonstrating up to 2M context length currently training on only 512 H100 GPUs for the 40b parameter model. + +| Configuration | TP | CP | Number of Nodes | Number of GPUs | Context Length | Global Batch Size | Seconds per Step | +| :---------------: | :-: | :-: | :-------------: | :------------: | :------------: | :---------------: | :--------------: | +| NeMo2 | 64 | 2 | 32 | 256 | 1M | 2 | 44 | +| NeMo2 | 8 | 16 | 32 | 256 | 1M | 2 | OOM | +| MBridge Optimized | 8 | 16 | 32 | 256 | 1M | 2 | 30 | +| 2M Stress Test | 8 | 32 | 64 | 512 | 2M | 2 | 48 | From 31aee03afda350d7479557020feb4a6c9ec8b008 Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Mon, 12 Jan 2026 22:01:44 +0000 Subject: [PATCH 7/9] Adding the convergence figure asset Signed-off-by: John St. John --- ...ge_to_nemo_training_convergence_7ksteps.png | Bin 0 -> 104002 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 bionemo-recipes/recipes/evo2_megatron/assets/mbridge_to_nemo_training_convergence_7ksteps.png diff --git a/bionemo-recipes/recipes/evo2_megatron/assets/mbridge_to_nemo_training_convergence_7ksteps.png b/bionemo-recipes/recipes/evo2_megatron/assets/mbridge_to_nemo_training_convergence_7ksteps.png new file mode 100644 index 0000000000000000000000000000000000000000..d289fdf7af5086bd228d922a3f7450dc1e28d0a8 GIT binary patch literal 104002 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%0DS(3pRcM|p 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
~Bpr-I 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 literal 0 HcmV?d00001 From a8e3559da62c3d27cdc2abd1ac9c8842a3673fa5 Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Mon, 12 Jan 2026 22:06:05 +0000 Subject: [PATCH 8/9] add a precision column to the long context training table Signed-off-by: John St. John --- bionemo-recipes/recipes/evo2_megatron/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bionemo-recipes/recipes/evo2_megatron/README.md b/bionemo-recipes/recipes/evo2_megatron/README.md index 27b361d275..1949bbc6d7 100644 --- a/bionemo-recipes/recipes/evo2_megatron/README.md +++ b/bionemo-recipes/recipes/evo2_megatron/README.md @@ -148,9 +148,9 @@ Activation memory optimizations have enabled context parallelism to work better implementation than the previous nemo2 implementation. This enables significantly faster step timing at long context as well as demonstrating up to 2M context length currently training on only 512 H100 GPUs for the 40b parameter model. -| Configuration | TP | CP | Number of Nodes | Number of GPUs | Context Length | Global Batch Size | Seconds per Step | -| :---------------: | :-: | :-: | :-------------: | :------------: | :------------: | :---------------: | :--------------: | -| NeMo2 | 64 | 2 | 32 | 256 | 1M | 2 | 44 | -| NeMo2 | 8 | 16 | 32 | 256 | 1M | 2 | OOM | -| MBridge Optimized | 8 | 16 | 32 | 256 | 1M | 2 | 30 | -| 2M Stress Test | 8 | 32 | 64 | 512 | 2M | 2 | 48 | +| Configuration | Precision | TP | CP | Number of Nodes | Number of GPUs | Context Length | Global Batch Size | Seconds per Step | +| :---------------: | :---------: | :-: | :-: | :-------------: | :------------: | :------------: | :---------------: | :--------------: | +| NeMo2 | fp8-delayed | 64 | 2 | 32 | 256 | 1M | 2 | 44 | +| NeMo2 | fp8-delayed | 8 | 16 | 32 | 256 | 1M | 2 | OOM | +| MBridge Optimized | bf16 | 8 | 16 | 32 | 256 | 1M | 2 | 30 | +| 2M Stress Test | bf16 | 8 | 32 | 64 | 512 | 2M | 2 | 48 | From 86c2d06187ef4201f40f2de96ef9ffecfebf9f21 Mon Sep 17 00:00:00 2001 From: "John St. John" Date: Mon, 12 Jan 2026 22:10:07 +0000 Subject: [PATCH 9/9] Add more background about the TP vs CP trade offs Signed-off-by: John St. John --- bionemo-recipes/recipes/evo2_megatron/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bionemo-recipes/recipes/evo2_megatron/README.md b/bionemo-recipes/recipes/evo2_megatron/README.md index 1949bbc6d7..5bc855f331 100644 --- a/bionemo-recipes/recipes/evo2_megatron/README.md +++ b/bionemo-recipes/recipes/evo2_megatron/README.md @@ -145,8 +145,12 @@ delayed scaling in nemo2. | Nemo2 FP8 (delayed) | 6.18 | 26,511 | 960 | 48 | 512 | Activation memory optimizations have enabled context parallelism to work better with evo2 style models in our mbridge -implementation than the previous nemo2 implementation. This enables significantly faster step timing at long context -as well as demonstrating up to 2M context length currently training on only 512 H100 GPUs for the 40b parameter model. +implementation than the previous nemo2 implementation. Since TP requires more node to node communication, you generally +want to limit TP to your fastest interconnects, which are typically configured in nodes of 8 GPUs. Evo2 would previously +OOM with these more ideal configurations, requiring much larger than typical levels of TP to handle long context +training. With our latest changes to the evo2 forward pass, we can now handle more typical TP vs CP configurations. +This enables significantly faster step timing at long context, as well as demonstrating up to 2M context length. We +have currently demonstrated small training runs at 2M context on only 512 H100 GPUs for the 40b parameter model. | Configuration | Precision | TP | CP | Number of Nodes | Number of GPUs | Context Length | Global Batch Size | Seconds per Step | | :---------------: | :---------: | :-: | :-: | :-------------: | :------------: | :------------: | :---------------: | :--------------: |