Repository navigation
model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support - #25444
Conversation
|
Hi @YanissAmz, thanks for your contribution! Per our contribution guidelines, the automated PR checker found the following issue(s) that need your attention:
Please note that maintainers reserve the right to make final decisions on PRs. If you believe there is a mistake, please comment below. |
|
Regarding the automated flags above:
The branch has been rebased onto current master (clean rebase, builds fine). Happy to run any additional test on this hardware on request. |
|
Is this also working with Nemotron 3 Super 120B? |
|
Super 120B should keep working, it was already supported before this PR (both are 88 layers, the PR just adds a check on the per-layer top-k to tell Puzzle apart — Super is uniform so it still resolves to 120B.A12B). I only tested Puzzle though, don't have a Super GGUF around. If you try it on this branch let me know. |
|
Some adoption signal: community GGUF quants of Puzzle-75B built with this branch are now published — https://huggingface.co/RemySkye/NVIDIA-Nemotron-Labs-3-Puzzle-75B-A9B-GGUF. Their README pins this PR (commit af49ef5) and notes that a build containing it is required to load the files. Their conversion notes also surfaced a compatibility gap worth folding in: NVIDIA's official BF16 upload names the trunk |
|
So.... is this just awaiting a final rebase then a review? |
|
In the interest of getting this merged, I suggest you pull out the mtp changes into its own pr. It would slim down the scope of this pr a lot. |
|
Chiming in, since I faced a similar issue and worked on extending the converter to Super. Validation Hardware: Intel Core Ultra 7 270K Plus (Arrow Lake, 24 cores host / 20 WSL2 , AVX2, no AVX-512), 121 GB DDR5, CPU-only.
I confirmed that it is functional but not a speed win without rollback. The mamba-rs-rollback-wip turns it into 36-44% speedup with 0.70 acceptance. This is evidence for the split @Green-Sky suggested. About the divergence: at temp 0 the MTP-on output differs from MTP-off on both builds (1030-char completion for on, 1055 for off). Is that expected from batched-verify numerics on CPU, or a rollback-WIP issue? The converter addition for the generic class is small, so I offer the following into the split PR: I can run further tests if needed. Disclosure: Conversion code and benchmarks assisted by AI (Claude). I directed and verified. Numbers measured on my hardware as described. |
497ec49 to
adeff9b
Compare
|
just adding a bump with interest; the model size seems to be well places for 72-128GB hardware, which fits a lot of consumer tech. |
1096e1c to
4767ffb
Compare
|
Still waiting after 2 months... |
|
@Premshay do you think Nvidia is planning to purchase ggml/Huggingface to get this pull request merged, seeing how fast everyone worked on the Qwen3.8 next? |
|
Is there any reason why this PR touches 55 files, including all kinds of models? |
10e7062 to
85d300a
Compare
* src : add n_expert_used_max function With Commit c61b98b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: #25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max
* src : add n_expert_used_max function With Commit c61b98b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: ggml-org/llama.cpp#25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max (cherry picked from commit 9a4843c)
* src : add n_expert_used_max function With Commit c61b98b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: ggml-org/llama.cpp#25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max
* src : add n_expert_used_max function With Commit c61b98b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: ggml-org/llama.cpp#25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max
* src : add n_expert_used_max function With Commit c61b98b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: ggml-org/llama.cpp#25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max
…ggml-org#25444) * hparams: add per-layer n_ff_exp/n_expert_used arrays with scalar-or-array loading G1/G2 infrastructure for variable-per-layer expert FFN size and top-k routing (required for Puzzle-75B which has 5 distinct n_ff_exp values and 7 top-k values across its 40 MoE layers). Design: rename scalar members to _impl suffix (following existing convention), add LLAMA_MAX_LAYERS arrays, add n_ff_exp(il)/n_expert_used(il) accessors with scalar fallback. No new GGUF keys: reuses existing expert_feed_forward_length and expert_used_count keys via get_key_or_arr (scalar -> broadcast, array -> per-layer). - llama-hparams.h: n_ff_exp -> n_ff_exp_impl, n_expert_used -> n_expert_used_impl; add n_ff_exp_arr / n_expert_used_arr arrays; add per-layer accessor declarations. - llama-hparams.cpp: implement n_ff_exp(il) and n_expert_used(il); out-of-range il returns impl safely (shared code, no abort). - llama-model.cpp: central n_expert_used load changed to get_key_or_arr; derive impl as max-of-array for validations and backward compat; zero both new arrays; HunyuanVL override also zeroes n_expert_used_arr. - llama-graph.cpp: aggregation loop in build_moe_ffn uses hparams.n_expert_used(il) so per-layer top-k bounds the ggml_view loop correctly. - All other files: mechanical rename hparams.n_{ff_exp,expert_used} -> *_impl. Scalar arches are unaffected (broadcast fills all array slots with the single value). (cherry picked from commit 269a81e) * nemotron-h: use per-layer n_ff_exp(il) and n_expert_used(il) at MoE call-sites Load n_ff_exp via get_key_or_arr into hparams.n_ff_exp_arr in load_arch_hparams; derive impl as max for existing uniform GGUFs. In load_arch_tensors, compute n_ff_exp_i = hparams.n_ff_exp(i) with fallback to n_ff(i)/n_expert_used(i) for GGUFs that omit expert_feed_forward_length. In build_ffn_layer, pass hparams.n_expert_used(il) to build_moe_ffn so per-layer top-k is used for expert routing selection. All other nemotron-h behaviour (mamba2, attention, shared-exp, latent projection, routed_scaling_factor, expert_weights_norm, sigmoid gating) is unchanged. (cherry picked from commit b1878a1) * arch/*.cpp + gguf-py: mechanical rename n_ff_exp->n_ff_exp_impl, n_expert_used->n_expert_used_impl All non-nemotron arch files continue using the scalar impl member directly. Behaviour is identical: the impl value is the broadcast value from the GGUF scalar. gguf_writer: add_expert_feed_forward_length and add_expert_used_count now accept int | Sequence[int], mirroring add_feed_forward_length, so converters can write per-layer arrays with the same existing GGUF keys. (cherry picked from commit 8f009f5) * convert: support NemotronHPuzzleForCausalLM (per-block MoE config) Parse block_configs/mtp_block_configs into per-layer arrays (scalar-or-array keys), append the MTP [attention, moe] sub-blocks as blk.88/blk.89 with nextn tensors, accept the backbone.* prefix, and register the arch. Also fix a pre-existing undeclared _experts attribute on NemotronHModel. (cherry picked from commit d1a592f) * nemotron-h: distinguish Nemotron 3 Puzzle (75B.A9B) from Super (120B.A12B) Both have 88 layers; the per-layer expert_used_count array (heterogeneous for Puzzle, broadcast-uniform for Super) is the discriminator. (cherry picked from commit f824e09) * convert: accept the official Puzzle BF16 checkpoint's tensor naming The officially distributed BF16 checkpoint (NVIDIA-Nemotron-Labs-3-Puzzle- 75B-A9B-BF16) names the trunk model.* (model.layers.*, model.embeddings, model.norm_f) where the original release used the NemotronH-style backbone.*, and spells the router bias e_score_correction_bias instead of e_score_correction.bias. Normalize both at the top of NemotronHPuzzleModel.modify_tensors so either checkpoint converts; every tensor name in the official index (42683 keys, MTP head included) resolves through the tensor map after normalization. (cherry picked from commit 189b67f) * laguna: use n_ff_exp_impl for the uniform-MoE FFN size Laguna landed after this branch was cut and reads hparams.n_ff_exp as a scalar. This series turns it into a per-layer array with an n_ff_exp(il) accessor, so the three scalar reads no longer compile. Laguna is a uniform MoE, so point them at the scalar fallback n_ff_exp_impl, same as deepseek2/qwen3moe/gemma4 in this series. No behaviour change. (cherry picked from commit dbedc9e) * arch: extend the n_ff_exp/n_expert_used rename to archs added upstream kimi-k3, dflash, bailingmoe3, deepseek4, granite-swa and the nemotron-h MTP block still referenced the scalar fields by their old names. n_ff_exp and n_expert_used are accessors now, so those reads no longer compile; point the non-per-layer archs at the _impl scalars and use the indexed form where the call site is per-layer. * convert: keep Puzzle opted out of the NemotronH MTP export path ggml-org#26725 added MTP export to NemotronHModel, keyed on num_nextn_predict_layers. Puzzle's config carries that key, but NemotronHPuzzleModel bypasses NemotronHModel.__init__ (its per-block config needs a different setup), so _mtp_bid was never assigned and modify_tensors raised AttributeError on any mtp.* tensor. Puzzle's head is also laid out by mtp_block_configs, not the mtp.layers.* form the base maps. Set _mtp_bid to None, drop mtp.* in filter_tensors, and declare supports_mtp_export = False so --mtp / --no-mtp fail at the CLI. * llama: replace n_ff_exp/n_expert_used scalars with per-layer accessors Follow-up to review feedback: the previous revision kept the scalar hparams fields alongside the new per-layer arrays, which duplicated state that get_key_or_arr already handles by broadcasting a scalar value over every layer. Drop both scalars and expose n_ff_exp(il) / n_expert_used(il) built exactly like the existing n_head_kv(il) and n_ff(il) accessors: they index the array and GGML_ABORT out of range, with il defaulting to 0 so genuinely uniform call sites stay a plain n_ff_exp(). Arch loaders now read both keys through get_key_or_arr over n_layer_all, and the n_expert_used validation checks the maximum across layers instead of a single field. * llama: restore per-key required flags on the expert hparam reads The scalar-to-array conversion passed required=false at every call site, which silently made mandatory keys optional. Each read now carries the same required flag it had before the conversion.
I got a Floating point exception (core dumped) running rocm 7.24, loading error from both rocm and vulkan backend. Please please please help fix this, this model is very interesting. I ran q4_k_m on the old PR I compiled before, it works but quite slow. But shockingly, it is the only model so far that understands a humor story that no model before (not even the closed AIs) was able to appreciate! Mindblowing for me. |
…ggml-org#25444) * hparams: add per-layer n_ff_exp/n_expert_used arrays with scalar-or-array loading G1/G2 infrastructure for variable-per-layer expert FFN size and top-k routing (required for Puzzle-75B which has 5 distinct n_ff_exp values and 7 top-k values across its 40 MoE layers). Design: rename scalar members to _impl suffix (following existing convention), add LLAMA_MAX_LAYERS arrays, add n_ff_exp(il)/n_expert_used(il) accessors with scalar fallback. No new GGUF keys: reuses existing expert_feed_forward_length and expert_used_count keys via get_key_or_arr (scalar -> broadcast, array -> per-layer). - llama-hparams.h: n_ff_exp -> n_ff_exp_impl, n_expert_used -> n_expert_used_impl; add n_ff_exp_arr / n_expert_used_arr arrays; add per-layer accessor declarations. - llama-hparams.cpp: implement n_ff_exp(il) and n_expert_used(il); out-of-range il returns impl safely (shared code, no abort). - llama-model.cpp: central n_expert_used load changed to get_key_or_arr; derive impl as max-of-array for validations and backward compat; zero both new arrays; HunyuanVL override also zeroes n_expert_used_arr. - llama-graph.cpp: aggregation loop in build_moe_ffn uses hparams.n_expert_used(il) so per-layer top-k bounds the ggml_view loop correctly. - All other files: mechanical rename hparams.n_{ff_exp,expert_used} -> *_impl. Scalar arches are unaffected (broadcast fills all array slots with the single value). (cherry picked from commit 269a81e) * nemotron-h: use per-layer n_ff_exp(il) and n_expert_used(il) at MoE call-sites Load n_ff_exp via get_key_or_arr into hparams.n_ff_exp_arr in load_arch_hparams; derive impl as max for existing uniform GGUFs. In load_arch_tensors, compute n_ff_exp_i = hparams.n_ff_exp(i) with fallback to n_ff(i)/n_expert_used(i) for GGUFs that omit expert_feed_forward_length. In build_ffn_layer, pass hparams.n_expert_used(il) to build_moe_ffn so per-layer top-k is used for expert routing selection. All other nemotron-h behaviour (mamba2, attention, shared-exp, latent projection, routed_scaling_factor, expert_weights_norm, sigmoid gating) is unchanged. (cherry picked from commit b1878a1) * arch/*.cpp + gguf-py: mechanical rename n_ff_exp->n_ff_exp_impl, n_expert_used->n_expert_used_impl All non-nemotron arch files continue using the scalar impl member directly. Behaviour is identical: the impl value is the broadcast value from the GGUF scalar. gguf_writer: add_expert_feed_forward_length and add_expert_used_count now accept int | Sequence[int], mirroring add_feed_forward_length, so converters can write per-layer arrays with the same existing GGUF keys. (cherry picked from commit 8f009f5) * convert: support NemotronHPuzzleForCausalLM (per-block MoE config) Parse block_configs/mtp_block_configs into per-layer arrays (scalar-or-array keys), append the MTP [attention, moe] sub-blocks as blk.88/blk.89 with nextn tensors, accept the backbone.* prefix, and register the arch. Also fix a pre-existing undeclared _experts attribute on NemotronHModel. (cherry picked from commit d1a592f) * nemotron-h: distinguish Nemotron 3 Puzzle (75B.A9B) from Super (120B.A12B) Both have 88 layers; the per-layer expert_used_count array (heterogeneous for Puzzle, broadcast-uniform for Super) is the discriminator. (cherry picked from commit f824e09) * convert: accept the official Puzzle BF16 checkpoint's tensor naming The officially distributed BF16 checkpoint (NVIDIA-Nemotron-Labs-3-Puzzle- 75B-A9B-BF16) names the trunk model.* (model.layers.*, model.embeddings, model.norm_f) where the original release used the NemotronH-style backbone.*, and spells the router bias e_score_correction_bias instead of e_score_correction.bias. Normalize both at the top of NemotronHPuzzleModel.modify_tensors so either checkpoint converts; every tensor name in the official index (42683 keys, MTP head included) resolves through the tensor map after normalization. (cherry picked from commit 189b67f) * laguna: use n_ff_exp_impl for the uniform-MoE FFN size Laguna landed after this branch was cut and reads hparams.n_ff_exp as a scalar. This series turns it into a per-layer array with an n_ff_exp(il) accessor, so the three scalar reads no longer compile. Laguna is a uniform MoE, so point them at the scalar fallback n_ff_exp_impl, same as deepseek2/qwen3moe/gemma4 in this series. No behaviour change. (cherry picked from commit dbedc9e) * arch: extend the n_ff_exp/n_expert_used rename to archs added upstream kimi-k3, dflash, bailingmoe3, deepseek4, granite-swa and the nemotron-h MTP block still referenced the scalar fields by their old names. n_ff_exp and n_expert_used are accessors now, so those reads no longer compile; point the non-per-layer archs at the _impl scalars and use the indexed form where the call site is per-layer. * convert: keep Puzzle opted out of the NemotronH MTP export path ggml-org#26725 added MTP export to NemotronHModel, keyed on num_nextn_predict_layers. Puzzle's config carries that key, but NemotronHPuzzleModel bypasses NemotronHModel.__init__ (its per-block config needs a different setup), so _mtp_bid was never assigned and modify_tensors raised AttributeError on any mtp.* tensor. Puzzle's head is also laid out by mtp_block_configs, not the mtp.layers.* form the base maps. Set _mtp_bid to None, drop mtp.* in filter_tensors, and declare supports_mtp_export = False so --mtp / --no-mtp fail at the CLI. * llama: replace n_ff_exp/n_expert_used scalars with per-layer accessors Follow-up to review feedback: the previous revision kept the scalar hparams fields alongside the new per-layer arrays, which duplicated state that get_key_or_arr already handles by broadcasting a scalar value over every layer. Drop both scalars and expose n_ff_exp(il) / n_expert_used(il) built exactly like the existing n_head_kv(il) and n_ff(il) accessors: they index the array and GGML_ABORT out of range, with il defaulting to 0 so genuinely uniform call sites stay a plain n_ff_exp(). Arch loaders now read both keys through get_key_or_arr over n_layer_all, and the n_expert_used validation checks the maximum across layers instead of a single field. * llama: restore per-key required flags on the expert hparam reads The scalar-to-array conversion passed required=false at every call site, which silently made mandatory keys optional. Each read now carries the same required flag it had before the conversion.
* src : add n_expert_used_max function With Commit c61b98b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (ggml-org#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: ggml-org#25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max
* src : add n_expert_used_max function With Commit c61b98b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: ggml-org/llama.cpp#25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max
…ggml-org#25444) * hparams: add per-layer n_ff_exp/n_expert_used arrays with scalar-or-array loading G1/G2 infrastructure for variable-per-layer expert FFN size and top-k routing (required for Puzzle-75B which has 5 distinct n_ff_exp values and 7 top-k values across its 40 MoE layers). Design: rename scalar members to _impl suffix (following existing convention), add LLAMA_MAX_LAYERS arrays, add n_ff_exp(il)/n_expert_used(il) accessors with scalar fallback. No new GGUF keys: reuses existing expert_feed_forward_length and expert_used_count keys via get_key_or_arr (scalar -> broadcast, array -> per-layer). - llama-hparams.h: n_ff_exp -> n_ff_exp_impl, n_expert_used -> n_expert_used_impl; add n_ff_exp_arr / n_expert_used_arr arrays; add per-layer accessor declarations. - llama-hparams.cpp: implement n_ff_exp(il) and n_expert_used(il); out-of-range il returns impl safely (shared code, no abort). - llama-model.cpp: central n_expert_used load changed to get_key_or_arr; derive impl as max-of-array for validations and backward compat; zero both new arrays; HunyuanVL override also zeroes n_expert_used_arr. - llama-graph.cpp: aggregation loop in build_moe_ffn uses hparams.n_expert_used(il) so per-layer top-k bounds the ggml_view loop correctly. - All other files: mechanical rename hparams.n_{ff_exp,expert_used} -> *_impl. Scalar arches are unaffected (broadcast fills all array slots with the single value). (cherry picked from commit 269a81e) * nemotron-h: use per-layer n_ff_exp(il) and n_expert_used(il) at MoE call-sites Load n_ff_exp via get_key_or_arr into hparams.n_ff_exp_arr in load_arch_hparams; derive impl as max for existing uniform GGUFs. In load_arch_tensors, compute n_ff_exp_i = hparams.n_ff_exp(i) with fallback to n_ff(i)/n_expert_used(i) for GGUFs that omit expert_feed_forward_length. In build_ffn_layer, pass hparams.n_expert_used(il) to build_moe_ffn so per-layer top-k is used for expert routing selection. All other nemotron-h behaviour (mamba2, attention, shared-exp, latent projection, routed_scaling_factor, expert_weights_norm, sigmoid gating) is unchanged. (cherry picked from commit b1878a1) * arch/*.cpp + gguf-py: mechanical rename n_ff_exp->n_ff_exp_impl, n_expert_used->n_expert_used_impl All non-nemotron arch files continue using the scalar impl member directly. Behaviour is identical: the impl value is the broadcast value from the GGUF scalar. gguf_writer: add_expert_feed_forward_length and add_expert_used_count now accept int | Sequence[int], mirroring add_feed_forward_length, so converters can write per-layer arrays with the same existing GGUF keys. (cherry picked from commit 8f009f5) * convert: support NemotronHPuzzleForCausalLM (per-block MoE config) Parse block_configs/mtp_block_configs into per-layer arrays (scalar-or-array keys), append the MTP [attention, moe] sub-blocks as blk.88/blk.89 with nextn tensors, accept the backbone.* prefix, and register the arch. Also fix a pre-existing undeclared _experts attribute on NemotronHModel. (cherry picked from commit d1a592f) * nemotron-h: distinguish Nemotron 3 Puzzle (75B.A9B) from Super (120B.A12B) Both have 88 layers; the per-layer expert_used_count array (heterogeneous for Puzzle, broadcast-uniform for Super) is the discriminator. (cherry picked from commit f824e09) * convert: accept the official Puzzle BF16 checkpoint's tensor naming The officially distributed BF16 checkpoint (NVIDIA-Nemotron-Labs-3-Puzzle- 75B-A9B-BF16) names the trunk model.* (model.layers.*, model.embeddings, model.norm_f) where the original release used the NemotronH-style backbone.*, and spells the router bias e_score_correction_bias instead of e_score_correction.bias. Normalize both at the top of NemotronHPuzzleModel.modify_tensors so either checkpoint converts; every tensor name in the official index (42683 keys, MTP head included) resolves through the tensor map after normalization. (cherry picked from commit 189b67f) * laguna: use n_ff_exp_impl for the uniform-MoE FFN size Laguna landed after this branch was cut and reads hparams.n_ff_exp as a scalar. This series turns it into a per-layer array with an n_ff_exp(il) accessor, so the three scalar reads no longer compile. Laguna is a uniform MoE, so point them at the scalar fallback n_ff_exp_impl, same as deepseek2/qwen3moe/gemma4 in this series. No behaviour change. (cherry picked from commit dbedc9e) * arch: extend the n_ff_exp/n_expert_used rename to archs added upstream kimi-k3, dflash, bailingmoe3, deepseek4, granite-swa and the nemotron-h MTP block still referenced the scalar fields by their old names. n_ff_exp and n_expert_used are accessors now, so those reads no longer compile; point the non-per-layer archs at the _impl scalars and use the indexed form where the call site is per-layer. * convert: keep Puzzle opted out of the NemotronH MTP export path ggml-org#26725 added MTP export to NemotronHModel, keyed on num_nextn_predict_layers. Puzzle's config carries that key, but NemotronHPuzzleModel bypasses NemotronHModel.__init__ (its per-block config needs a different setup), so _mtp_bid was never assigned and modify_tensors raised AttributeError on any mtp.* tensor. Puzzle's head is also laid out by mtp_block_configs, not the mtp.layers.* form the base maps. Set _mtp_bid to None, drop mtp.* in filter_tensors, and declare supports_mtp_export = False so --mtp / --no-mtp fail at the CLI. * llama: replace n_ff_exp/n_expert_used scalars with per-layer accessors Follow-up to review feedback: the previous revision kept the scalar hparams fields alongside the new per-layer arrays, which duplicated state that get_key_or_arr already handles by broadcasting a scalar value over every layer. Drop both scalars and expose n_ff_exp(il) / n_expert_used(il) built exactly like the existing n_head_kv(il) and n_ff(il) accessors: they index the array and GGML_ABORT out of range, with il defaulting to 0 so genuinely uniform call sites stay a plain n_ff_exp(). Arch loaders now read both keys through get_key_or_arr over n_layer_all, and the n_expert_used validation checks the maximum across layers instead of a single field. * llama: restore per-key required flags on the expert hparam reads The scalar-to-array conversion passed required=false at every call site, which silently made mandatory keys optional. Each read now carries the same required flag it had before the conversion.
* src : add n_expert_used_max function With Commit 3615d1b ("model: add NVIDIA Nemotron-3-Puzzle-75B-A9B (NemotronHPuzzle) support (ggml-org#25444)") it is now possible for each layer to have a specific number of experts but there are a few checks that need to be updated to handle this upon model loading. For example: ```console llama_model_load: error loading model: model has expert layers but no expert layers are used ``` And later: ```console /llama.cpp/src/llama-model-loader.cpp:955: GGML_ASSERT(n_ids_used > 0) failed ``` This commit adds the n_expert_used_max function so that these checks can use it. Refs: ggml-org#25444 (comment) * src : use hparams.n_expert_used_max in llama_model_base::load_hparams * src : use 0 as initial value for n_expert_used_max
|
@bartowski1182 the "no expert layers are used" error is fixed by #28323 (thanks @danbev). @glennhanks the floating point exception is my bug. GGUFs made with the review branch (mine and RemySkye's) divide by zero at load. I'll send a fix, but it only turns the crash into an error: those files need reconverting with the current converter. For now, your old build still works with them. @Premshay sorry for the silence, and thanks for the benchmarks. They confirm MTP needs partial rollback to pay off on this model, which is what #25691 does. I'll come back to it. |
… Puzzle) Cherry-picked upstream ggml-org#25444 renamed scalar hparams.n_expert_used and hparams.n_ff_exp to _impl fields and added per-layer accessors (matching the previous PR ggml-org#26869 rename of the scalar fields). CachyLLama missed a few call sites: - llama_model_n_expert_used (C API) - llama_moe_residency - llama_context stats/selection populators - qwen4exp load_arch_tensors MTP-layer loop (line 276, originally wrote n_ff_exp without the parenthesised accessor; pre-rename it worked because the field existed; post-rename it has to use the method) All bare hparams.n_expert_used / hparams.n_ff_exp references in CachyLLama code now use the accessors.
… Puzzle) Cherry-picked upstream ggml-org#25444 renamed scalar hparams.n_expert_used and hparams.n_ff_exp to _impl fields and added per-layer accessors (matching the previous PR ggml-org#26869 rename of the scalar fields). CachyLLama missed a few call sites: - llama_model_n_expert_used (C API) - llama_moe_residency - llama_context stats/selection populators - qwen4exp load_arch_tensors MTP-layer loop (line 276, originally wrote n_ff_exp without the parenthesised accessor; pre-rename it worked because the field existed; post-rename it has to use the method) All bare hparams.n_expert_used / hparams.n_ff_exp references in CachyLLama code now use the accessors.
Overview
Adds support for NVIDIA's Nemotron-3-Puzzle-75B-A9B (
NemotronHPuzzleForCausalLM), a hybrid mamba2/attention/MoE model with a per-layer heterogeneous MoE configuration.Puzzle reuses the existing
nemotron_h_moemachinery (latent MoE, relu2, routed scaling, hybrid cache) but is heterogeneous per layer: 88 blocks = 40 mamba2 + 8 attention (GQA 32/2, NoPE) + 40 MoE wheremoe_intermediate_size(1280–2688) andnum_experts_per_tok(4–18) vary per block.Scope note: this PR was slimmed down on request — the MTP/NEXTN draft-head support it originally carried has been removed and will come back as a separate follow-up PR once this lands, so the two can be reviewed independently. The removal covers the converter as well, not just the model code: it was emitting the
nextntensors, which would have produced GGUFs this loader rejects.mtp.*is now skipped for Puzzle. Note this is no longer the same as "what the other NemotronH variants do": #26725 landed MTP export onNemotronHModelafter this branch was written, so the base class now keeps the head. Puzzle stays opted out explicitly (supports_mtp_export = False), since its head is laid out bymtp_block_configsrather than themtp.layers.*form the base maps, and there is no Puzzle MTP inference path in tree.Changes:
n_ff_exp/n_expert_used— the existing GGUF keys become scalar-OR-array viaget_key_or_arr(same pattern asn_ff_arr). Scalar GGUFs broadcast, so this is 100% backward compatible and adds no new keys.build_moe_ffnaggregation is bounded by the per-layer top-k.n_ff_exp→n_ff_exp_impl,n_expert_used→n_expert_used_impl, in its own isolated commit. This is what makes most of the file count: the scalar accessors had to give up their names to the per-layer ones, so every arch touching them is updated in a single no-behaviour-change commit.NemotronHPuzzleModelparsesblock_configs, writes the per-layer arrays, dequantizes the FP8 checkpoint (weight_scale absorption), and accepts both tensor namings in the wild: the originalbackbone.*prefix and the officially distributed BF16 upload'smodel.*(model.layers.*,model.embeddings,model.norm_f) withe_score_correction_biasinstead ofe_score_correction.bias. Also fixes a pre-existing_expertsAttributeError onNemotronHModel.Additional information
Validation. The full-scope version of this branch was validated end to end on Strix Halo 128GB unified / Radeon 8060S: FP8 → Q8_0 conversion (83.4G, 781 tensors) clean with metadata verified, Q8_0 load and coherent generation under
-ngl 99, an 18K-token long-context needle retrieved, and a quant A/B (wikitext-2, 24 chunks) giving PPL 5.325 Q8_0 / 5.377 IQ4_XS-mix / 5.404 Q4_K_M at 17.7–19.9 t/s decode and 211–243 t/s prefill on ROCm.To be straight about what the slimmed branch has and has not been through: it is compile-gated, not load-gated. I no longer have the Puzzle GGUF or the HF checkout on this machine, so I have not re-run a conversion or a load smoke since the split. What is verified since: the tree builds clean, every commit builds individually (bisectable), the converter is lint- and syntax-clean at every commit, the model code has zero
nextnreferences left, and the converter emits exactly the 88 trunk blocks the loader expects. It has also been rebased repeatedly onto master without conflicts, most recently onto the current tip. Re-verified on the current tip: builds clean, converter flake8-clean, and themtp.*handling above is fixed and covered by the class docstring.GGUFs and a reusable imatrix: https://huggingface.co/YanissAmz/Nemotron-3-Puzzle-75B-A9B-GGUF
A separate converter extension to Nemotron-3-Super-120B has been proposed by another contributor in the comments below; it is small and independent, and best as its own PR.
Note unrelated to this diff: Vulkan (RADV gfx1151) decode collapses on 4-bit MoE quants (
mul_mat_idpath) while Q8_0 is fine — ROCm/HIP is the fast backend for this model on Strix Halo. Possibly worth a separate issue.Update — per-layer accessors
The scalar
n_ff_exp/n_expert_usedfields are gone. Both values are now read throughget_key_or_arrinto per-layer arrays and exposed asn_ff_exp(il)/n_expert_used(il), built exactly like the existingn_head_kv(il)andn_ff(il): array lookup,GGML_ABORTout of range,ildefaulting to 0 so uniform call sites stay a plainn_ff_exp(). Then_expert_usedvalidation now checks the maximum across layers.Verified locally on DeepSeek-V4-Flash and Qwen3.6-35B-A3B (expert counts match the GGUF metadata, both generate). The per-layer path was checked by injecting array-valued keys into the metadata shard: a wrong per-layer
n_ff_expis caught bycheck_tensor_dims, and an out-of-range entry on the last layer trips the validation assert. The Puzzle model itself was not re-run here — the conversion was verified in review.Review feedback of 09-02 is applied in full:
_implis gone in favour of the_arrreads, the accessors abort out of range instead of falling back, the per-key required flags are restored where they were load-bearing, and the remaining call sites that still carried the old column alignment (afmoe, bailingmoe 1/2/3, cohere2moe, deepseek/2ocr/32/4, dots1, and then_expert_usedline inllama-model.h) now match the suggested form. Rebased onto current master, no conflicts; builds clean.Requirements