Commit 0ef27309b for llama.cpp

commit 0ef27309b78d054832e44a95a6fb9314d4b7363c
Author: Daniel Bevenius <daniel.bevenius@gmail.com>
Date:   Tue Nov 3 02:40:51 2026 -0800

    conversion : use original model tensor names in _flush_nvfp4_experts (#31427)

    This commit updates the _flush_nvfp4_experts to take a new parameter
    which is a dictionary mapping the key to the original model tensor name.

    The motivation for this is that I noticed that the tensor names being
    looked up were not the original model tensor names, for example:
    ```console
      model.layers.1.mlp.experts.up_proj.weight
    ```
    But _nvfp4_layer_algo uses the original per-expert names:
    ```console
    (Pdb) p self._nvfp4_layer_algo
    {
    'backbone.layers.1.mixer.experts.0.up_proj': 'W4A16_NVFP4',
    ...
    ```
    This means that the lookup will never match for experts and fall back to
    _nvfp4_global_algo. If the global algo is W4A16_NVFP4 then this would
    go unnoticed, but for a model like
    nvidia/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-NVFP4 which is
    MIXED_PRECISION, the expert tensors will not get prec_a4=false so they
    would use the native W4A4 path on Blackwell even though the original
    model tensors declare them as W4A16_NVFP4.

diff --git a/conversion/base.py b/conversion/base.py
index 6347d7fd3..1c7aecec8 100644
--- a/conversion/base.py
+++ b/conversion/base.py
@@ -1277,6 +1277,7 @@ class ModelBase:
         expert_scales: dict[tuple[int, str], list[tuple[int, float]]] = {}
         expert_input_scales: dict[tuple[int, str], list[tuple[int, float]]] = {}
         expert_shapes: dict[tuple[int, str], list[int]] = {}
+        expert_names: dict[tuple[int, str], str] = {}
         n_experts = self.find_hparam(["num_local_experts", "num_experts", "n_routed_experts"], optional=True) or 0
         consumed: list[str] = []

@@ -1325,6 +1326,7 @@ class ModelBase:
                     expert_scales[key] = []
                     expert_input_scales[key] = []
                     expert_shapes[key] = shape
+                    expert_names[key] = name
                 expert_blocks[key].append((expert_id, raw.copy()))
                 # Collect per-expert scale2 (scalar per expert)
                 expert_scales[key].append((expert_id, float(scale2.float().sum())))
@@ -1333,13 +1335,13 @@ class ModelBase:

                 # Flush when all experts for this (layer, proj) are collected
                 if n_experts > 0 and len(expert_blocks[key]) >= n_experts:
-                    self._flush_nvfp4_experts(key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, bid, proj_type)
+                    self._flush_nvfp4_experts(key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, expert_names, bid, proj_type)
             else:
                 self._repack_nvfp4(name, weight, scale, scale2, input_scale)

         # Flush any remaining experts (fallback if n_experts was unknown)
         for bid, proj_type in list(expert_blocks.keys()):
-            self._flush_nvfp4_experts((bid, proj_type), expert_blocks, expert_scales, expert_input_scales, expert_shapes, bid, proj_type)
+            self._flush_nvfp4_experts((bid, proj_type), expert_blocks, expert_scales, expert_input_scales, expert_shapes, expert_names, bid, proj_type)

         # Remove consumed tensors so get_tensors/modify_tensors won't see them
         for name in consumed:
@@ -1350,7 +1352,7 @@ class ModelBase:
             if name.endswith((".k_scale", ".v_scale")):
                 del self.model_tensors[name]

-    def _flush_nvfp4_experts(self, key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, bid, proj_type):
+    def _flush_nvfp4_experts(self, key, expert_blocks, expert_scales, expert_input_scales, expert_shapes, expert_names, bid, proj_type):
         experts = expert_blocks.pop(key)
         scales = expert_scales.pop(key)
         input_scales = expert_input_scales.pop(key)
@@ -1362,7 +1364,7 @@ class ModelBase:
         new_name = self.map_tensor_name(merged_name)
         logger.info(f"Repacked {new_name} with shape [{len(experts)}, {shape[0]}, {shape[1]}] and quantization NVFP4")
         self.gguf_writer.add_tensor(new_name, merged, raw_dtype=gguf.GGMLQuantizationType.NVFP4)
-        self._tag_prec_a4(merged_name, new_name)
+        self._tag_prec_a4(expert_names.pop(key), new_name)

         scales.sort(key=lambda x: x[0])
         self._write_scales_tensor(new_name.replace(".weight", ".scale"), [s[1] for s in scales])