Tried training full-layer LoRA including Mamba-2 on Nemotron 9B × Megatron-Bridge using NVIDIA Brev H100

Tried training full-layer LoRA including Mamba-2 on Nemotron 9B × Megatron-Bridge using NVIDIA Brev H100

I attempted 100% LoRA fine-tuning including the Mamba-2 layers of Nemotron 9B-v2-Japanese using Megatron-Bridge. Since the previous HF PEFT only covered 53%, I tried cloud training on NVIDIA Brev's H100. The results were unexpected.
2026.02.25

This page has been translated by machine translation. View original

Introduction

Hello, I'm Morishige from Classmethod's Manufacturing Business Technology Division.

In my previous article, I fine-tuned Nemotron 9B-v2-Japanese with RAFT using the NTA FAQ and improved RAG answer accuracy. However, I was always bothered by the fact that LoRA couldn't pass through the Mamba-2 layers, meaning only 53% of all parameters were trained. The results themselves weren't bad — F1 improved by +8.9 points and answer refusals dropped dramatically from 47 to 3 — but still...

Of the 56 layers in Nemotron 9B-v2-Japanese, 27 are Mamba-2 (SSM). The in_proj and out_proj of these layers have no means of applying LoRA in the HuggingFace PEFT implementation at the time of writing. Results were achieved with only the remaining Attention layers and FFN layers, but the question "would results change if the remaining 47% were also trained?" never left my mind.

In this article, I used Megatron-Bridge v0.2.0 included in the NGC NeMo container to achieve 100% LoRA coverage including Mamba-2, and ran training on NVIDIA Brev's cloud H100. I'll cover the entire pipeline from bringing the trained adapter back to DGX Spark, converting to GGUF, and running inference with Ollama. The conclusion: it didn't turn out the way I expected.

What is Megatron-Bridge

At the time of writing, HuggingFace PEFT had a limitation where there was no way to apply LoRA to in_proj and out_proj of Mamba-2. This is a problem stemming from the HuggingFace implementation, while Megatron's training pipeline natively supports these layers. In other words, you convert the model to Megatron format, train it, and then convert it back to HuggingFace format. Megatron-Bridge is what bridges that gap.

Megatron-Bridge is included in the NeMo 25.11 series containers, and with 3 steps — import_ckptfinetuneexport_ckpt — you can train HuggingFace ecosystem models using Megatron's native pipeline and bring the results back in HuggingFace format.

The NeMo container includes a dedicated recipe (nemotron_nano_9b_v2_finetune_config) with pre-tuned LoRA target modules and hyperparameters. The LoRA targets included in the recipe are the following 6 types.

Target Layer Type HF PEFT Megatron-Bridge
linear_qkv / q_proj,k_proj,v_proj Self-Attention Yes Yes
linear_proj / o_proj Self-Attention Yes Yes
linear_fc1 / gate_proj,up_proj FFN Yes Yes
linear_fc2 / down_proj FFN Yes Yes
in_proj Mamba-2 No Yes
out_proj Mamba-2 No Yes

HF PEFT covers only the top 4 types at 53%. Megatron-Bridge covers all 6 types at 100%.

The DGX Spark Wall and Brev

The SM 12.1 Wall

In the previous article I broke through the SM 12.1 issue with the NGC NeMo container on DGX Spark, but with Megatron-Bridge's import_ckpt, I hit another wall where multiprocessing.Manager().Queue() causes an EOFError inside a Docker container. So this time I decided it would be faster to rent a cloud H100 rather than struggle locally.

NVIDIA Brev

https://developer.nvidia.com/brev

NVIDIA Brev is a cloud GPU instance service provided by NVIDIA. You can use H100s and A100s by the hour.

Brev H100 Instance Selection Screen

With a single H100 80GB PCIe configuration, at the time of writing the price was around $2.26/hr (Hyperstack provider), which is within reach for personal verification purposes. Since I was doing LoRA training with TP=1 (no tensor parallelism) this time, one instance was sufficient.

https://dev.classmethod.jp/articles/nvidia-brev-introduction/

Choosing VM Mode

Brev offers several ways to configure GPU environments.

Brev GPU Environment Settings

I initially tried to directly specify the NeMo container in Custom Container mode, but pip install wouldn't work because the Python environment inside the container was treated as externally managed. Switching to VM Mode and running docker run myself turned out to be more flexible, and I could work the same way as locally.

Brev Environment Setup

The entire pipeline is compiled into a shell script (n6-brev-run.sh).

SSH into the VM Mode instance and start the NeMo container.

# Pull and start the NeMo container
docker pull nvcr.io/nvidia/nemo:25.11.01
docker run --gpus all --ipc=host --ulimit memlock=-1 \
  --ulimit stack=67108864 -v /ephemeral:/workspace \
  nvcr.io/nvidia/nemo:25.11.01 bash /workspace/n6-brev-run.sh

/ephemeral is the temporary storage of the Brev instance, and training data and scripts are transferred here in advance using scp. --ipc=host and --ulimit memlock=-1 are flags required for Megatron's distributed training.

The H100's nvidia-smi output is as follows.

+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.195.03             Driver Version: 570.195.03     CUDA Version: 13.0     |
|-----------------------------------------+------------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id          Disp.A | Volatile Uncorr. ECC |
|   0  NVIDIA H100 PCIe               On  |   00000000:00:07.0 Off |                    0 |
| N/A   28C    P0             67W /  350W |       0MiB /  81559MiB |      0%      Default |
+-----------------------------------------+------------------------+----------------------+

Converting from HF to Megatron Format

Use AutoBridge.import_ckpt() to convert the HuggingFace format model to Megatron format.

from megatron.bridge import AutoBridge

AutoBridge.import_ckpt(
    "nvidia/NVIDIA-Nemotron-Nano-9B-v2-Japanese",
    "/workspace/megatron-ckpt",
    trust_remote_code=True,
    dtype=torch.bfloat16,
    device_map="cpu",
)

trust_remote_code=True is required. Since Nemotron 9B-v2-Japanese is a custom model, without this it gets rejected at can_handle(). This was a subtle gotcha I noticed during a dry run.

mp.Queue() Patch

I encountered a problem where multiprocessing.Manager().Queue() causes an EOFError during the conversion save step. It seems related to the /dev/shm size limit inside Docker containers, but I prioritized a workaround over investigating the root cause.

import multiprocessing as mp
import megatron.core.dist_checkpointing.strategies.filesystem_async as fs_async

# Use plain mp.Queue() instead of Manager().Queue()
fs_async._get_write_results_queue = lambda: mp.Queue()

For single-node configurations, mp.Queue() is functionally equivalent. With this patch applied on the H100, the conversion completed in 71 seconds. The checkpoint size is 16.6GB.

100% LoRA Training

Recipe Configuration

Megatron-Bridge has training recipes for each model. For Nemotron 9B-v2-Japanese, nemotron_nano_9b_v2_finetune_config can be used.

from megatron.bridge.recipes.nemotronh import nemotron_nano_9b_v2_finetune_config
from megatron.bridge.peft.lora import LoRA

lora_config = LoRA(
    target_modules=[
        "linear_qkv", "linear_proj",    # Attention
        "linear_fc1", "linear_fc2",     # FFN
        "in_proj", "out_proj",          # Mamba-2 ← Not possible with HF PEFT
    ],
    dim=32,
    alpha=32,
)

config = nemotron_nano_9b_v2_finetune_config(
    peft=lora_config,
    pretrained_checkpoint="/workspace/megatron-ckpt",
    train_iters=500,
    micro_batch_size=1,
    global_batch_size=8,
    seq_length=2048,
    finetune_lr=1e-4,
)

Here is a summary of the configuration differences from the previous run (HF PEFT).

Item Previous (HF PEFT) This Time (Megatron-Bridge)
Training Framework trl SFTTrainer Megatron finetune()
LoRA Coverage 53% 100%
LoRA rank 16 32 (recipe default)
LoRA alpha 32 32
Learning rate 2e-4 1e-4 (recipe default)
Iterations 138 steps (1 epoch) 500 iter
Batch size 8 (bs=1 × grad_accum=8) 8 (mbs=1 × gbs=8)
Precision BF16 BF16
Training environment DGX Spark (GB10) Brev H100 80GB

Eval Batch Size Issue

Megatron-Bridge's eval crashes during batch splitting if the number of data items is less than global_batch_size. This time, with 100 eval items and global_batch_size=8, there shouldn't be a problem in principle, but it got caught by a bug in the remainder handling. Since the final evaluation is done on Ollama, I worked around it by making eval a no-op.

import megatron.bridge.training.train as _train_module
_train_module.evaluate_and_print_results = lambda *a, **kw: None

Loss Progress

Here is the lm loss progression over 500 iterations.

Megatron-Bridge 100% LoRA Training Loss

Loss decreased from an initial 0.858 to 0.095. Compared to the previous run's (HF PEFT) final loss of 6.64, the order of magnitude is different. Since the loss calculation methods differ between training frameworks, a direct comparison isn't possible, but we can see that training itself converged smoothly.

Training took approximately 17 minutes. You really feel the speed of the H100. Training on the same dataset took 55 minutes (138 steps) on DGX Spark, yet this is the speed even with 500 iterations. GPU memory was 23.3GB / 80GB, with plenty of headroom even at TP=1.

Bringing Back the Adapter

export_ckpt source_path

Once training is complete, export to HuggingFace format. The training checkpoint (iter_0000500/) only contains LoRA delta weights (.distcp), so you pass this to export_ckpt's source_path and merge with the base checkpoint.

bridge = AutoBridge.from_hf_pretrained(
    "nvidia/NVIDIA-Nemotron-Nano-9B-v2-Japanese",
    trust_remote_code=True,
)

bridge.export_ckpt(
    "/workspace/megatron-ckpt",      # Base Megatron checkpoint
    "/workspace/hf-export",           # Export destination (HF format)
    source_path="/workspace/output/n6-megatron-lora/checkpoints/iter_0000500",
)

Note that the bridge instance should be initialized with from_hf_pretrained. Using from_hf_config results in an error because save_artifacts is not implemented. I was fortunate to catch this during the dry run.

Export completed in 50 seconds. The total work time on Brev up to this point was about 20 minutes, costing approximately $2.26/hr × 1 hour ≈ $2.26.

GGUF Conversion and Ollama Registration

Transfer the exported HF model to DGX Spark via scp and convert to GGUF.

In the previous run, I converted the LoRA adapter alone to GGUF and combined it using Ollama's ADAPTER directive. That approach can't be used this time. llama.cpp's LoRA adapter GGUF doesn't support in_proj and out_proj of Mamba-2, and these tensors are ignored during conversion.

So this time I'm directly converting the fully merged HF model to GGUF.

# Convert fully merged HF model to GGUF Q4_K_M
python3 llama.cpp/convert_hf_to_gguf.py ./hf-export/ \
    --outtype q4_k_m \
    --outfile nemotron-9b-n6-Q4_K_M.gguf
Method Previous (HF PEFT) This Time (Megatron-Bridge)
GGUF format LoRA adapter GGUF (36MB) Full model GGUF (6.1GB)
Ollama registration ADAPTER directive FROM directive
Reason llama.cpp supports LoRA for Attention + FFN Mamba-2 LoRA adapter not supported

The Ollama Modelfile is as follows.

Modelfile
FROM ./nemotron-9b-n6-Q4_K_M.gguf

TEMPLATE """{{- range $i, $_ := .Messages }}
{{- if eq .Role "system" }}<extra_id_0>System
{{ .Content }}
{{ end }}
{{- if eq .Role "user" }}<extra_id_1>User
{{ .Content }}
{{ end }}
{{- if eq .Role "assistant" }}<extra_id_1>Assistant
{{ .Content }}
{{ end }}
{{- end }}<extra_id_1>Assistant
"""

PARAMETER stop "<extra_id_1>"
PARAMETER num_ctx 8192
PARAMETER temperature 0.6
PARAMETER top_p 0.95
ollama create nemotron-9b-n6 -f Modelfile

This enables inference with ollama run nemotron-9b-n6.

Evaluation Results

I used the exact same evaluation scripts and test data as in the previous article to compare under equal conditions.

JCQ Regression Check

I checked for regression in general-purpose capabilities using JCommonsenseQA (1,119 questions).

Model JCQ Accuracy Difference
Baseline (no FT) 91.96% (1029/1119) -
RAFT FT (HF PEFT / 53% LoRA) 91.51% (1024/1119) -0.45pp
RAFT FT (Megatron-Bridge / 100% LoRA) 92.14% (1031/1119) +0.18pp

RAFT Domain F1 Evaluation

I measured token-level F1 on 200 items from the NTA FAQ test set.

Model F1 Refusal FP Refusal TN
Baseline (no FT) 0.5646 47 153
RAFT FT (HF PEFT / 53% LoRA) 0.6536 3 197
RAFT FT (Megatron-Bridge / 100% LoRA) 0.4884 69 131

Evaluation Results: Accuracy Degraded Despite 100% LoRA

Honestly, this result was unexpected. Despite achieving 100% LoRA coverage, F1 fell below the baseline. Answer refusals (FP) also reached 69 cases, the exact opposite of the previous run's dramatic drop from 47 to 3.

Qualitative Samples

I dug into some samples. What stood out first was "the F1 limited to samples where this model actually answered."

Model Answers Average F1 for Answered Samples
RAFT FT (HF PEFT / 53% LoRA) 197 0.6636
RAFT FT (Megatron-Bridge / 100% LoRA) 131 0.7457

Answer Quality Improved, But Overall F1 Degraded Due to Over-Refusal

The accuracy when this model does answer is actually higher for Megatron-Bridge (0.7457 vs 0.6636). The problem lies in "the decision of whether to answer," where this model has become overly conservative.

Examples where this model refused but the previous model answered (3 cases)

Of the 69 incorrect refusals, only 3 were cases where the previous model also refused. The remaining 66 were cases where the previous model answered correctly. Here are 3 typical patterns.

First, a question about the conversion of endowment insurance. Despite reference document 5 clearly stating "there is no objection to including it in necessary expenses for the year in which the conversion date falls," this model returned "I cannot answer from the provided information." The previous model cited the relevant passage and answered (F1: 0.418).

Next, a question about how to handle the end of the grace period for My Number. Again, reference document 5 contains a detailed answer, and the previous model answered with F1=0.808. This model also refused. The length of the reference document or the density of technical terms may be a factor, but the information needed to answer was properly provided.

The third case involves the application of a tax treaty to interest paid to an Indian corporation via an SPC. Reference document 1 clearly states that it is exempt, and the previous model answered with F1=0.744. This model refused as well.

What these cases have in common is that all reference documents clearly contain the answer. This model seems to understand the document content, but appears overly cautious in its judgment of "whether it can answer."

Analysis

Why Accuracy Dropped Despite 100% LoRA

As seen in the qualitative samples, when this model answers, F1 is higher (0.7457 vs 0.6636). In other words, the quality of the answers themselves has improved. The reason overall F1 degraded is that 69 cases occurred where the model refused to answer despite being able to. The RAFT training data includes samples where "if the answer is not in the reference documents, respond that you cannot answer," and the model appears to have overfit to this refusal pattern.

So why didn't this happen with the same data in the previous run? I think the biggest factor is the difference in the number of epochs. In the previous run, training was stopped at 138 steps (1 epoch), but this time I ran 500 iterations using the Megatron-Bridge recipe defaults as-is. This corresponds to approximately 3.6 epochs for the 1,100-item dataset. It might be natural for the threshold to drop excessively after repeatedly training on the refusal pattern 3.6 times.

There are other possible factors. The GGUF conversion method is different (the previous run layered LoRA adapter GGUF on top of the base, while this time it's Q4_K_M quantization after full merging), and the subtle weight changes learned during training may have been rounded off by quantization. Alternatively, applying LoRA to Mamba-2's SSM layers may have prioritized memorizing training data patterns over the RAG behavior of "searching for answers in reference documents." Verifying the latter would require a comparative experiment with only the Mamba-2 layers frozen, but I wasn't able to go that far this time.

For a fair comparison with the previous run, an experiment with rank=16, 1 epoch, and the same GGUF conversion method would be needed.

Cost-Effectiveness of Brev

Here is a summary of Brev usage costs this time.

Item Time Cost
Dry run ~1h ~$2.26
Production run ~1h ~$2.26
CoT generation (Claude Haiku, done previously) - ~$2.00
Total ~$6.52

There's a possibility I could have solved it on DGX Spark with more time, but renting an H100 for $2.26/hr and breaking through the problem in one go seems like it was a reasonable decision. The training itself finished in 17 minutes, and most of the time was spent on trial and error like environment verification.

The pattern of "train in the cloud, infer at the edge" feels like a good fit with DGX Spark. Cloud H100 shines for training, while DGX Spark's 128GB unified memory shines for inference. The workflow of renting an H100 on Brev for spot training and bringing results back locally seems useful going forward, and I'm also excited about the coming soon integration between DGX Spark and NVIDIA Brev, which seems designed exactly with this workflow in mind.

coming soon

Impressions of Megatron-Bridge

Honestly, Megatron-Bridge has sparse documentation. Most of the time I had to read the source code and figure things out through trial and error. The 3-step flow of import_ckptfinetuneexport_ckpt itself is simple, which is a good impression.

However, each step had its own pitfalls. import_ckpt had the Manager().Queue() EOFError, finetune had the eval data batch size mismatch, and export_ckpt had the issue where not passing source_path would corrupt the model. Each workaround was a one-liner, so I hope this article helps anyone who gets stuck in the same places. This is the experience as of NeMo 25.11.01, so future versions may have improvements. (I also suspect there may be issues with my usage as well...)

Summary

Using Megatron-Bridge v0.2.0, I performed 100% LoRA training including Nemotron 9B-v2-Japanese's Mamba-2 layers on Brev H100.

Item Previous (HF PEFT) This Time (Megatron-Bridge)
Training Framework HF PEFT + trl Megatron-Bridge v0.2.0
LoRA Coverage 53% 100%
Training Environment DGX Spark (GB10) Brev H100 80GB
Training Time 55 minutes 17 minutes
GGUF Method LoRA adapter (36MB) Full model (6.1GB)
Additional Cost ~$2 (CoT only) ~$6.52 (Brev + CoT)
JCQ Regression -0.45pp +0.18pp (no regression)
RAFT F1 0.6536 0.4884 (degraded)

Technically, I was able to achieve 100% LoRA training including Mamba-2 using Brev's cloud H100 with Megatron-Bridge. The workflow of "train in the cloud and bring back to the edge" also worked without issues.

However, the crucial RAFT accuracy degraded compared to the previous run (53% LoRA). The lesson here is that increasing LoRA coverage doesn't necessarily improve accuracy. Since the F1 for the cases where the model did answer actually improved, if I can suppress the overfitting to the refusal pattern by aligning the number of epochs in a re-experiment, the results might change.

Megatron-Bridge itself, while having many pitfalls, felt like an interesting bridge connecting the HuggingFace ecosystem with Megatron's training pipeline. I look forward to improvements in documentation, and I hope this is helpful to anyone trying the same thing.

The full scripts are published in the following repository.

https://github.com/himorishige/dgx-spark-blog/tree/main/n6-megatron-bridge

References


国内企業 AI活用実態調査2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

国内企業 AI活用実態調査2026

無料でダウンロードする

Share this article