Skip to content

Add Nemotron Super 3.5 VL and Nemotron Bolt 74B A9B support - #30520

Merged
ggerganov merged 9 commits into
ggml-org:masterfrom
frozenblade1224:add-super-3-5+bolt-support
Oct 15, 2026
Merged

ggerganov merged 9 commits into
ggml-org:masterfrom
frozenblade1224:add-super-3-5+bolt-support

Conversation

@frozenblade1224

Copy link
Copy Markdown
Contributor

Overview

This PR adds support for two models - Nemotron Super 3.5 VL and Nemotron Bolt 74B A9B. It introduces the new architecture used by both models - NemotronH_Omni_Reasoning_V3 - and implements the vision graph for the new architecture in nemotron-omni.cpp. The new NemotronOmniVL class in conversion\nemotron.py will handle the checkpoint conversion for the new models.

Additional information

Conversion

# Super 3.5
python convert_hf_to_gguf.py <path/to/super-3.5-checkpoint> --outfile super-3.5.gguf
python convert_hf_to_gguf.py <path/to/super-3.5-checkpoint> --mmproj --outfile super-3.5-mmproj.gguf

# Bolt 74B A9B
python convert_hf_to_gguf.py <path/to/bolt-74b-a9b-checkpoint> --outfile bolt-74b-a9b.gguf
python convert_hf_to_gguf.py <path/to/bolt-74b-a9b-checkpoint> --mmproj --outfile bolt-74b-a9b-mmproj.gguf

Text-only

llama-cli -m model.gguf -p "Who are you?"

Image

llama-mtmd-cli -m model.gguf --mmproj model-mmproj.gguf --image <path/to/image.jpg> -p "Describe this image."

Video (CLI)

Start llama-mtmd-cli, then enter /video with the video path and your prompt:

llama-mtmd-cli -m model.gguf --mmproj model-mmproj.gguf
/video <path/to/video.mp4>
Summarize the video and describe the key events.

Video (Server API)

Start the server and allow access to the directory containing your video:

llama-server -m model.gguf --mmproj model-mmproj.gguf --media-path <path/to/media-directory> --host 127.0.0.1 --port 8080

Requirements

  • I have read and agree with the contributing guidelines
  • AI usage disclosure: Yes, for code review and supervised authoring

@frozenblade1224
frozenblade1224 requested review from a team and CISC as code owners October 14, 2026 13:48
Comment thread conversion/nemotron.py Resolved
Comment thread gguf-py/gguf/vocab.py Resolved
Comment thread tools/mtmd/mtmd.cpp Resolved
Comment thread tools/mtmd/mtmd-image.cpp Resolved
@github-actions github-actions Bot added model Model specific mtmd Related to multimodal functionality (video/image/audio) conversion labels Oct 14, 2026
Comment thread gguf-py/gguf/vocab.py Resolved
@frozenblade1224
frozenblade1224 requested a review from a team as a code owner October 15, 2026 09:26
Comment thread common/speculative.cpp

int32_t predecessor = 0;
for (int32_t i = 1; i < n_block_tokens; ++i) {
for (int32_t i = 1; i < (is_dflash2 ? n_block_tokens : params.n_max + 1); ++i) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

oh wait, what is this change here? We just merged lilicorr, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure why it wasn't caught earlier.

In the LiliCorr PR, LiliCorr calculates the entire candidate lattice even when n_draft < 7. The head's tensors are specifically made to run attention between 56 candidates, so it seemed inadvisable to slice it. In any case, not within in the already-burgeoning scope of this PR.

For that case, doing the loop from 1 to n_block_tokens is wrong; since that will always output 7 draft tokens, even when we have set n_draft to < 7.

That hits this assert later on in llama-context.cpp:2384

GGML_ASSERT(n_outputs_max <= cparams.n_outputs_max);

The change keeps us from passing more than n_max tokens even if the lattice is larger.

Comment thread src/models/dflash.cpp

ggml_tensor * inp_anchor = nullptr;
if (model.lilicorr_pass_hidden_proj && ubatch.embd_state) {
if (model.lilicorr_pass_hidden_proj && ubatch.embd_state && n_tokens == (int64_t) hparams.dflash_block_size * ubatch.n_seqs_unq) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Here as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Again, not sure how it passed by earlier!

AFAIK before starting llama-server does a dummy pass with 2 tokens and directly calls the decoder; which causes the LiliCorr head to try to make the lattice with 2 tokens. It can only make the lattice for 8 tokens currently, since we have implemented it that way in #30506.

So the dummy pass fails this assert in (dflash.cpp:664), and the server stops -

GGML_ASSERT(tokens_per_block == block_size);

@ggerganov

Copy link
Copy Markdown
Member

@ngxson Is this good to merge on your end?

NickM-27 pushed a commit to NickM-27/llama.cpp that referenced this pull request Oct 15, 2026
@ggerganov

Copy link
Copy Markdown
Member

Will fast-track this to get the model supported in the v0.7.0 release today.

danielhanchen added a commit to unslothai/unsloth that referenced this pull request Oct 15, 2026
Adds Studio support for the NVIDIA Nemotron 3.5 Bolt and Super GGUFs:
[unsloth/NVIDIA-Nemotron-3.5-Bolt-74B-A9B-GGUF](https://huggingface.co/unsloth/NVIDIA-Nemotron-3.5-Bolt-74B-A9B-GGUF)
and
[unsloth/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-GGUF](https://huggingface.co/unsloth/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-GGUF).
Both are `nemotron_h_moe` (hybrid Mamba2 + a few attention layers +
latent MoE), supported in llama.cpp by ggml-org/llama.cpp#30520.

## Variant picker and drafters

- **`NVFP4` is a quant token.** Only `MXFP*` was known, so Super's
`NVFP4` variant resolved to `LiLiCorr/lilicorr-...-NVFP4.gguf`, a 1.3 GB
drafter, and loaded it as the main model. The quant regex now accepts
`(?:MX|NV)FP[0-9]+` in all of its copies (backend and
`model-identity.ts`).
- **`lilicorr-*.gguf` is a drafter kind.** Super's LiLiCorr drafter was
listed as a selectable quant. Super uses its embedded MTP head, which
measured faster, so LiLiCorr is now excluded from variant menus and
quant lookups and is never launched. Added to `_DRAFTER_KINDS` in its
three mirrors.
- **NVFP4-suffixed sidecars pair with their weights on local loads.**
`dspark-<model>-NVFP4.gguf` / `dflash-<model>-NVFP4.gguf` kept `-nvfp4`
in the pairing stem, so a local folder load attached no drafter. Bolt
ships its LiLiCorr drafter as
`dflash-NVIDIA-Nemotron-3.5-Bolt-74B-A9B-NVFP4.gguf` (GGUF architecture
`dflash`).

With this, Auto mode launches:

| Model | Drafter | Flags |
|---|---|---|
| Super | embedded MTP head | `--spec-type draft-mtp`, no
`--model-draft` |
| Bolt | LiLiCorr (`dflash-...gguf`) | `--model-draft dflash-...
--spec-type draft-dflash` |

## Sampling defaults

New `nemotron-3.5` family in `inference_defaults.json`: temperature 1.0,
top_p 0.95 (NVIDIA's recommendation for both models). It matches every
id shape (repo, `repo:quant`, cache snapshot path, local `.gguf`) and
sits before `nemotron`, so older Nemotrons keep 1.0 / 1.0.

## Memory planner

Measured against llama-server allocations for Bolt IQ2_XXS and Super
UD-IQ2_XXS, the default path was safe but mis-priced the fixed costs:

- **Embedded MTP head counted when MTP is off.** The context fit charged
Super about 3 GB for the nextn block, which llama.cpp does not load
without `--spec-type draft-mtp`, so Super got a much shorter auto
context on tight cards. It is now excluded unless MTP is engaged
(`nemotron_h_moe` only, the arch measured).
- **Latent MoE width.** The routed-expert compute term now uses
`moe_latent_size` when present, and per-layer `expert_used_count` /
`expert_feed_forward_length` arrays are read as their max (Bolt stores
them per layer; they were read as missing).
- **DFlash / DSpark drafter compute floor.** The drafter context
re-reserves to a flat buffer once the dflash implementation initialises;
the reserve was up to 332 MiB short. Floored at the measured
draft-plus-verify graph minus the reserve Studio already charges beside
it, for dflash-architecture drafters only. On a 24 GB card Bolt's DFlash
drafter does not fit the fit budget at any context (measured 23.4 GB
minimum), so Auto keeps it off there; from 32 GB it keeps `draft-dflash`
at full context (measured 26.4 GiB peak, 0.917 acceptance).
- **Smart-offload layout** (`UNSLOTH_SMART_OFFLOAD=1`): per-layer
`head_count_kv` raised a `TypeError`, and would otherwise have counted
every layer as attention with no recurrent state. KV is now charged to
the attention layers only and Mamba state to the recurrent ones, for
`nemotron_h` / `nemotron_h_moe` only. Bolt's layout now matches the
measured resident size exactly.

| MiB | Measured | Before | After |
|---|---|---|---|
| Super weights, MTP off | 45757 | 48752 | 45765 |
| Super compute 32k / 262k | 224 / 448 | 840 / 1064 | 550 / 774 |
| Bolt compute 32k / 262k | 148 / 372 | 143 / 367 | 260 / 484 |
| Bolt DSpark drafter reserve 32k / 262k | 2540 / 3884 | 2208 / 3707 |
2548 / 3892 |
| Bolt DFlash (LiLiCorr) drafter, any context | 1750 | 2085 | 1862 |
| Super MTP reserve 32k / 262k | 590 / 1038 | 1366 / 1814 | 1075 / 1523
|

Every component now estimates at or above the measured allocation. Other
architectures are unchanged; the new tests pin that for a hybrid with
`full_attention_interval` and for a dense model.

## Tests

- `tests/test_nemotron35_bolt_super.py` and
`tests/test_nemotron35_planner.py`: 39 tests, all pass. On `main` 13 of
the picker/sampling tests fail, as expected.
- The related existing suites (GGUF, variants, drafters, MTP, DSpark,
DFlash, sampling, offload, fit, context, memory, layout): identical
failing test ids on `main` and on this branch, all pre-existing
(diffusion offload, training offload layers, one TTS test).
- Live llama-server runs of the published files on llama.cpp with
#30520: Super UD-Q4_K_XL with the Auto-mode MTP flags, draft acceptance
0.897; Bolt IQ2_XXS with the LiLiCorr drafter, acceptance 0.836 and
128.7 to 224.2 tok/s versus no drafter.

## Not covered

- Studio's pinned `gguf==0.19.0` cannot read the `Q2_0` type that Super
UD-IQ1_M through UD-Q3_K_XL use. Only the opt-in smart-offload layout
reads tensor types this way; the default path sizes from offsets and is
unaffected.
- The 24 / 48 / 80 GB fit decisions were checked by calling the planner
with simulated card sizes, not on those cards.
apollo-mg added a commit to apollo-mg/Project-Apollo that referenced this pull request Oct 17, 2026
…pp#30520, nemotron-h.cpp only) cherry-picked into buun 34ef0ef7c -- Bolt loads, 58/60 same M1 grade as upstream, 2.6% slower; buun head needs an AMX header-guard workaround on CUDA 12.4 + gcc 13.4

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pbonnard added a commit to pbonnard/llama.cpp that referenced this pull request Oct 21, 2026
…3.5 VL / Bolt 74B)

Hybrid MoE placement is skipped when the MoE cache is active; Vulkan host
buffers keep refusing uncached memory.
ByCzech pushed a commit to ByCzech/llama.cpp that referenced this pull request Oct 24, 2026
Backport the text loader and MoE graph changes from upstream
cd0a5b6 (llama.cpp ggml-org#30520).
Keep the per-layer router, bias and expert weights consistent with
their GGUF dimensions and validate the count against top-k and the
declared maximum. Build the MoE graph with the loaded layer count.
The original upstream change is by Kartik Gulia <kgulia@nvidia.com>.

Assisted-by: Codex
auRose94 pushed a commit to auRose94/llama.cpp that referenced this pull request Oct 28, 2026
…gml-org#30520)

* add support

* remove unneeded bos fix

* add video flag

* fix flake error

* remove video-specific budgeting

* convert : fix lilicorr-dflash bid clash

* spec : enforce lilicorr n_tokens condition

* spec : fix lilicorr walk

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

Labels

conversion model Model specific mtmd Related to multimodal functionality (video/image/audio)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants