I tried running NeMo AutoModel, which can train Hugging Face models as-is, on DGX Spark

I tried running NeMo AutoModel, which can train Hugging Face models as-is, on DGX Spark

NeMo AutoModel's positioning and current status of DGX Spark support were organized, and LoRA and full SFT for Qwen3-8B were run on a single unit using the official container. Full SFT completed in 6 hours at 77.70 GiB, suggesting that BF16 full SFT is practical up to around the 8B class.
2026.09.15

This page has been translated by machine translation. View original

Introduction

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

NVIDIA's training library NeMo AutoModel had, before I knew it, become normally usable on DGX Spark. When I previously wrote an article surveying the NeMo Framework, I had left AutoModel's ARM64 support as "needs verification," but upstream recipes for DGX Spark are now in place, and CI is even running on actual GB10 hardware.

https://github.com/NVIDIA-NeMo/Automodel

Until now, I hadn't used AutoModel for fine-tuning on DGX Spark. For Japanese training of Nemotron 3 Nano, I used Hugging Face's PEFT and TRL directly, and when I set up Nemotron 3.5 Lightning as a judge for an LLM router, the cookbook assumed 8x H100s, so I ran Megatron-Bridge in the cloud. The motivation for this article is to verify whether it has become something that can be used straightforwardly on a single DGX Spark.

https://dev.classmethod.jp/articles/dgx-spark-nvidia-nemo-framework-overview-and-index/

I'll update the AutoModel row that I had marked △ in the survey article above (which is an article as of 2026-04-21) with this article.

This article introduces an overview of NeMo AutoModel and the current state of DGX Spark support, as well as the initial results from running Qwen3-8B LoRA and full SFT in the official container 26.08. I hope it resonates with people who have a single DGX Spark and want to train Hugging Face models locally.

NeMo AutoModel is a mechanism for training Hugging Face models as-is

NeMo AutoModel is a PyTorch-native training library published by NVIDIA. Borrowing the official description, it is "a PyTorch DTensor-native SPMD training library that supports Hugging Face models from day-0." It accepts models from Hugging Face Hub as-is via from_pretrained and handles SFT, PEFT such as LoRA and QLoRA, knowledge distillation, and VLM training all within the same framework. Training results also come out as Hugging Face-format safetensors, so they can be passed directly to inference engines like vLLM without any conversion step.

Since tools with "NeMo" in their name keep multiplying, let me first organize the positioning. The LLM-facing parts of NeMo Framework are split into separate repositories by role.

Role Component Relationship to AutoModel
Data preparation NeMo Curator, Data Designer Pass JSONL created here to AutoModel recipes
Training & customization NeMo AutoModel, Megatron-Bridge Two options at the same layer. HF format → AutoModel, Megatron format → Megatron-Bridge
Reinforcement learning NeMo RL Built on top of AutoModel, Megatron-Bridge, and vLLM
Evaluation NeMo Evaluator Scores post-training models via OpenAI-compatible API
Serving NIM, vLLM Reads safetensors and LoRA adapters output by AutoModel

The training layer has two options—AutoModel and Megatron-Bridge—and this is the most confusing part. Megatron-Bridge is Megatron-Core based, converts checkpoints to Megatron format, and is suited for large-scale pre-training and deep customization. AutoModel handles Hugging Face-compatible checkpoints as-is and lets you write the same recipe format from single-GPU LoRA to multi-node training. My understanding is: if you want to start from weights on Hugging Face and end in Hugging Face format, use AutoModel; if you want to organize training in the Megatron world, use Megatron-Bridge.

The skeleton of usage is simple: write a single YAML recipe specifying "which model, with which data, with which parallelization," and launch with automodel <recipe.yaml> --nproc-per-node N. The model is specified via nemo_automodel.NeMoAutoModelForCausalLM.from_pretrained, a Hugging Face-compatible class, and the recipe contents are readable in ordinary PyTorch and Hugging Face vocabulary. Distributed training is built on FSDP2 and DTensor, designed to layer tensor parallel, context parallel, and expert parallel according to model size and GPU count. Checkpoints support both DCP and safetensors, and FP8 training and speculative decoding drafter training are also among the recipe options. Hugging Face also has documentation on fine-tuning using AutoModel, and NVIDIA has published an article showing 3.4–3.7x throughput improvement over Transformers v5 for MoE models.

Reasons to choose AutoModel

If all you want is to LoRA a Hugging Face model, there's the path of using PEFT and TRL directly, or lightweight tools like Unsloth. I also used PEFT and TRL directly when training Nemotron 3 Nano in Japanese. Even so, I find myself choosing AutoModel more often now, for four reasons.

The first is that the entry and exit stay closed in Hugging Face format. The training input is a Hub model ID or a local snapshot, and the output is safetensors or a PEFT-format adapter. In separate verification, I was able to feed the adapter output by AutoModel directly into vLLM and get back a chat completion. Not having to insert format conversion between training and serving makes it easier to increase iteration speed.

The second is that NVIDIA takes care of the hardware. The GB10 in DGX Spark has compute capability 12.1, which is different from data center Blackwell. Keeping up with things like bitsandbytes builds tuned for GB10, or recipe settings that avoid paths cuDNN can't handle, quietly consumes time when you chase them yourself. With AutoModel, GB10 physical machines are in CI, and this kind of support arrives pre-baked into the container and recipes.

The third is that a recipe written for 1 GPU can be taken to a larger environment as-is. LoRA rank and learning rate are YAML values, and parallelization settings are isolated in the distributed block. There's no rewriting needed when you take a recipe you've validated on your local machine to cloud GPUs later.

The fourth is connectivity with other NeMo tools. I'm building a flow where I generate synthetic data with Data Designer, clean it with Curator, train with AutoModel, and score with Evaluator. Each is an independent tool so any can be swapped out individually, but the data formats and evaluation APIs align, so there are fewer seams when assembled as a pipeline.

Diagramming the flow I've built looks like this. AutoModel is the training box in the middle, with both inputs and outputs in Hugging Face format.

On the other hand, training requiring deep customization is Megatron-Bridge territory, and since the container contents update every two months, recipe compatibility needs to be verified per version. The version display quirk I mention later is one example of this. Excessive expectations aren't warranted, but for the use case of simply wanting to train Hugging Face models on DGX Spark, I think this is currently the least labor-intensive path.

DGX Spark support is visible through accumulated PRs, not release notes

The reason I missed the support is simple: there was only one line in the release notes saying "DGX Spark support," in v0.2.0. The actual support accumulated through PRs and recipe YAMLs, and tracing the GitHub history it looks like this:

Date Change Included in
2025-10-14 Discussion #640 announcing DGX Spark-oriented playbook
2025-10-15 PR #645 Adding SFT recipe for Qwen3-8B targeting DGX Spark v0.2.0 and later
2025-10-19 PR #657 Dequantization to run GPT-OSS on 1 GPU of DGX Spark v0.2.0 and later
2026-02-05 PR #1164 Streaming safetensors writer for unified memory v0.3.0 and later
2026-08-14 PR #3538 Moving DGX Spark recipes to GB10 physical CI runners v0.6.0 (NGC 26.08)
2026-08-14 PR #3553 Building bitsandbytes natively for SM121 v0.6.0 (NGC 26.08)
2026-08-18 PR #3533 Single-GPU DCP load fix and single-GPU LoRA recipe for Lightning main only
2026-09-02 PR #3766 Training support for 64GB RTX Spark main only

The turning point is the two entries on August 14, 2026. GB10 physical machines entered CI runners, and Qwen3-8B SFT, GPT-OSS-20B LoRA, and Llama-3.3-70B QLoRA became GB10 physical CI targets. On the same day, a change was merged to natively build bitsandbytes for SM121. These two were cherry-picked into v0.6.0's release branch, v0.6.0 was published on August 26, and the corresponding NGC tag is 26.08.

The DGX Spark-oriented recipes included in v0.6.0 are four: Qwen3-8B SQuAD SFT, GPT-OSS-20B single-GPU LoRA, Llama-3.3-70B QLoRA, and Nemotron 3 Nano 30B-A3B single-GPU LoRA. The last one is written with ep_size 1, and the MoE LoRA that I had previously avoided by judging "the cookbook assumes 8x H100s" officially exists as a single-machine recipe.

There are 3 installation paths but the container is the only choice

There are three paths to run AutoModel on DGX Spark: the NGC container, pip wheel, and the build.nvidia.com playbook. Lining up what I confirmed on my end:

Path Content freshness Track record on arm64 Notes
NGC nemo-automodel:26.08 2026-08-26, r0.6.0 branch Confirmed in this article. Multi-arch with linux/arm64 ~13GB compressed, 29.4GB expanded
pip nemo-automodel 0.6.0 (PyPI) Not confirmed Wheel is pure Python. aarch64 builds of TE and bitsandbytes needed separately
build.nvidia.com playbook 2026-03-04, specifies 26.02 Forum reports of 70B QLoRA getting "Killed" Procedure has the same structure. Tag is 3 generations old

The playbook is published as "Fine-tune with NeMo," and the procedure itself is the same structure as this time—enter the container via docker run and run a recipe. However, the specified tag is still 26.02, a version before GB10 CI and the bitsandbytes SM121 build were added. The pip wheel is py3-none-any and is pure Python inside, so the real issue becomes how to set up Transformer Engine and bitsandbytes for aarch64, not AutoModel itself. I didn't dive into that this time and used only the container.

Personally, I think if you're using DGX Spark, the container is the only choice. The NGC image has a linux/arm64 manifest and is ready with just docker pull, and recipes are also bundled at /opt/Automodel/examples/.

Making a short run of Qwen3-8B by converting the official Spark recipe to LoRA

The Spark-oriented recipe bundled in the container, examples/llm_finetune/qwen/qwen3_8b_squad_spark.yaml, is a full SFT configuration. Since my goal this time was to understand how to use it, I took this recipe as a base, added LoRA, and made a short version that stops at 30 steps. What I changed were: step count, batch size, validation and save intervals, the peft and checkpoint blocks, number of data samples, and model path. The ci block for CI use was removed. The GB10-specific settings for attention, loss function, and sequence packing were left as-is.

qwen3-8b-squad-spark-lora.yaml (excerpt, with explanatory comments added)
step_scheduler:
  global_batch_size: 2 # Official is 64
  local_batch_size: 1 # Official is 2
  ckpt_every_steps: 30
  val_every_steps: 30
  num_epochs: 1
  max_steps: 30

model:
  _target_: nemo_automodel.NeMoAutoModelForCausalLM.from_pretrained
  pretrained_model_name_or_path: /workspace/models/Qwen3-8B # Read pre-fetched weights as read-only mount
  # Keep packed training on the HF FlashAttention path for GB10. TE's packed THD
  # backward requests ragged LSE, which cuDNN does not support on SM12x GPUs.
  force_hf: true
  attn_implementation: flash_attention_2

peft:
  _target_: nemo_automodel.components._peft.lora.PeftConfig
  target_modules: '*_proj'
  dim: 8
  alpha: 32
  use_triton: false

checkpoint:
  enabled: true
  checkpoint_dir: /workspace/run/checkpoints
  model_save_format: safetensors
  save_consolidated: final

dataset:
  _target_: nemo_automodel.components.datasets.llm.squad.make_squad_dataset
  dataset_name: rajpurkar/squad
  split: train
  limit_dataset_samples: 512

The two lines force_hf: true and flash_attention_2 are GB10-specific settings included with comments in the official recipe. Because the cuDNN on GB10 cannot handle the backward used by Transformer Engine's packed attention, it's fixed to use Hugging Face's FlashAttention 2 path. I think the fact that this kind of background is written as comments in the recipe is a byproduct of running CI on physical hardware.

Execution is just launching the container once. I mounted pre-fetched model weights and retrieved only SQuAD from inside the container. The following is extracted from the actual run.sh I used, in a reproducible form, assuming config/ is placed directly under the working directory.

docker run --rm --gpus all --network=host --shm-size=32g \
  --ulimit memlock=-1 --ulimit stack=67108864 \
  --workdir /opt/Automodel \
  -v "$PWD:/workspace/run" \
  -v "$HOME/models/qwen3-8b-b968826d:/workspace/models/Qwen3-8B:ro" \
  -v "$HOME/.cache/huggingface:/root/.cache/huggingface" \
  nvcr.io/nvidia/nemo-automodel:26.08 \
  automodel /workspace/run/config/qwen3-8b-squad-spark-lora.yaml --nproc-per-node 1

The results were as follows. From loading through 30 steps, validation, and checkpoint saving, it took 90 seconds inside the container.

Item Value
Steps 30 (all with finite loss)
Training loss 7.9741 → 1.9950 (30 steps in this run)
Validation loss (64 samples) 2.7526
Max CUDA allocation 20.78 GiB
Time per step (median) 1.69 seconds
Throughput (median) 1,069.63 tok/s
Wall clock inside container 90 seconds
Adapter size 43,713,952 bytes (rank 8, alpha 32)

The loss going down is the result of running just 30 steps with SQuAD limited to 512 samples, so please don't read it as a measure of training quality. What I wanted to confirm here were three things: that the official Spark recipe path works as-is with the 26.08 arm64 image, that the LoRA adapter comes out as safetensors, and whereabouts within 128GB the memory usage lands. Under the conditions of this run, the maximum CUDA allocation for Qwen3-8B BF16 LoRA was 20.78 GiB.

Lining up my hands-on results for how much fits in 128GB unified memory

Since just 30 steps isn't very satisfying, I'll also line up my own results from running the previous AutoModel container (26.06.00). The conditions differ, so this isn't an apples-to-apples comparison—treat it purely as a guide for whether things will fit on a single machine.

Model and method Container Conditions Max CUDA allocation
Qwen3-8B BF16 LoRA, 30 steps 26.08 packing 1024, batch 2 20.78 GiB
Qwen3-8B BF16 LoRA, 360 steps, 720-sample 1 epoch 26.06.00 seq 1,024, batch 2, 6 min 09 sec 19.16 GiB
Qwen3-14B LoRA, 176 steps 26.06.00 context length 2,048, batch 2 42.85 GiB
Nemotron 3.5 Lightning 30B-A3B BF16 LoRA, 10 steps 26.06.00 Removing MTP layers, ~4 sec/step 63.25 GiB
Qwen3-8B BF16 full SFT, official recipe, 305 steps done 26.08 batch 64, packing 1024, ~72.54 sec/step 77.70 GiB

8B LoRA at 20.78 GiB, 14B at 42.85 GiB, 30B MoE at 63.25 GiB—all fit on a single GB10 under the conditions in the table. On the other hand, 8B full SFT used up to 77.70 GiB with the official recipe settings, and each step took about 72.54 seconds.

I ran this full SFT to completion. 305 steps (1 epoch of SQuAD) finished in 6 hours 11 minutes, with training loss dropping from 7.0378 to 0.0637 and validation loss from 4.4213 to 0.0960. Since validation loss was nearly flat from step 39 onward, the latter half was just running due to recipe settings. Upon completion, 5 safetensors files (~16.4GB total) consolidated in Hugging Face format plus split weights and optimizer state totaling 62GB were written as checkpoints. The consolidated weights are in a form that can be passed directly to vLLM. For 8B BF16 full SFT, a single DGX Spark is in the range of not just "fits" but "finishes if you wait 6 hours."

So how large can full SFT realistically go? Since 8B used 77.70 GiB, applying the same calculation to 14B gives about 136 GiB, which exceeds the 121 GiB visible to Linux. Options like offloading optimizer state to CPU or using 8-bit optimizers exist, but I haven't tried them on my end. The official playbook also presents the DGX Spark combination as "8B for full SFT, 70B for QLoRA," so my current assessment is: BF16 full SFT up to the 8B class, and for larger models, LoRA or QLoRA.

What personally surprised me was Lightning. I had previously judged "BF16 LoRA won't fit in unified memory" and chose 4-bit QLoRA or H100, but building on the ep_size 1 and target module narrowing from the bundled Nemotron 3 Nano recipe, and removing Lightning-specific MTP layers, it ran at 63.25 GiB. I plan to cover this Lightning run in a separate article, so I'll leave just the numbers here.

Things to watch out for in 26.08, noted upfront

That covers the usage, but I noticed a few things while looking inside the container that I'll note upfront for anyone trying the same thing.

First, the version display. The NGC tag is 26.08, which should correspond to the release v0.6.0, but checking nemo_automodel.__version__ inside the container shows 0.5.0+922e83e42. The startup banner also says "NVIDIA Release 26.06." Tracing on GitHub, this commit is on v0.6.0's release branch and was built three commits before the tag. The diff consists of 1 LoRA backward fix and 2 version number bumps, and the GB10 CI and bitsandbytes SM121 build are included. It seems safe to consider the contents equivalent to v0.6.0, but trusting the version string will lead to confusion.

Next, CUDA. The container uses CUDA 13.3, and the kernel driver on my DGX Spark was 580.159.03. At startup "CUDA Forward Compatibility mode ENABLED" appears, and this combination completed 30 steps without issue. This is the mechanism for running a newer container without upgrading the driver, and it wasn't a problem this time.

Log output also has quirks. Training loss, mem, and tps appear as INFO lines for each step, but these go to stderr. Stdout receives recipe contents and progress bars. If you split with 2>, the loss becomes invisible, so it's safest to keep both.

There are two points regarding checkpoints. Since the container runs as root, files under checkpoints/ are written as root-owned. I ran chown from the same image before retrieving them. The other point is a warning at startup: specifying save_consolidated: final produces a warning that "since v4_compatible=False, there may be incompatibility with transformers v4." The container's transformers is version 5, so I proceeded as-is this time, but if you plan to read with older transformers, it's worth looking at checkpoint.v4_compatible.

Note that the single-GPU LoRA recipe for Lightning added in PR #3533 exists in main but is not included in the 26.08 image. You'll need to write it yourself based on the bundled nemotron_nano_v3_singlegpu_lora.yaml.

Summary

NeMo AutoModel has reached a state where it has official recipes for DGX Spark and physical GB10 CI. The NGC 26.08 container has a linux/arm64 image, Qwen3-8B training ran with a YAML that merely added LoRA to the official Spark recipe at 20.78 GiB max CUDA allocation, and full SFT with the same recipe also completed 305 steps at 77.70 GiB. The row I had marked △ in my previous survey article can be advanced to "8B LoRA and full SFT complete with the official container."

On the other hand, what I confirmed this time extends only to 30 steps of LoRA and full SFT completion, plus a rough memory guide for what fits on one machine. I haven't looked at post-training model quality, pip-based installation, or FP8. The fact that the container's version display points to a midpoint in the r0.6.0 branch requires caution when communicating numbers to others.

I've already taken Nemotron 3.5 Lightning single-GPU LoRA through the full cycle from training to serving with vLLM using the previous container. I plan to cover that in a separate article, including training and evaluation using Japanese Tool Calling as a subject.


AI白書2026 配布中

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

AI白書2026

無料でダウンロードする

Share this article

DevelopersIO 2026