From 8171f44ac9d9a0e64baca1555d00bc8becfbb880 Mon Sep 17 00:00:00 2001 From: bong-water-water-bong <277547417+bong-water-water-bong@users.noreply.github.com> Date: Tue, 10 Nov 2026 16:22:32 -0400 Subject: [PATCH] gguf_to_q4nx: name the gated linear-attention gate (engine#2013) blk.N.attn_gate.weight had no entry in LAYER, so the converter refused both of this box's bonsai2-27b GGUFs at the first tensor it could not name: "no Q4NX name for blk.0.attn_gate.weight". The container's name for it already exists and the forward already reads it -- don't invent one: npu/forward/src/model.c:1083 "model.layers.%d.self_attn.gate_proj.weight" npu/forward/include/model.h:81 TensorDesc self_attn_gate_proj_weight; // [128,8,8704] I8 npu/forward/include/q4nx_forward.h:592 int attn_gate_ = 0; // qwen3_5 attn_output_gate (looked up in model.c's linear-attn / GateDeltaNet section, which is the family this pack belongs to). Measured before adding it, on ~/models/bonsai2-27b/bonsai2-unrot-q4_0.gguf, by reading the GGUF header rather than writing a container: every blk.N.attn_gate.weight is [5120, 6144] type 2 (Q4_0), and 5120 x 6144 = 31,457,280 = 3840 x 8192 -- a whole number of 32x256 tiles, so the tiling check in quantize_layer() passes and the name is the only thing missing. Read with a 30-line header parser because gguf_to_q4nx has no info/list mode and `pack` would have written ~15 GB to find the same fact. Also measured: only some layers carry it -- 0, 1, 2, 4, 5, 6, 8, 9 in the first ten -- so the lookup stays per layer and the mapping never assumes presence. Verified: the module imports with the new key and YAML->LAYER['attn_gate'] == "self_attn.gate_proj" (20 entries). NOT verified: a full `pack` of the 27B, which would write a second container of the same order as the first; the second half of this issue (GGML quant type 143 on the PTQ1_0 GGUF) is untouched and stays open -- an unknown quant type is a different answer from a missing name. --- tools/gguf_to_q4nx.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/gguf_to_q4nx.py b/tools/gguf_to_q4nx.py index 6d8ab1a0c..1e8369a28 100755 --- a/tools/gguf_to_q4nx.py +++ b/tools/gguf_to_q4nx.py @@ -134,6 +134,13 @@ def to_q4_1(t): LAYER = { "attn_norm": "input_layernorm", "ffn_norm": "post_attention_layernorm", "attn_q_norm": "self_attn.q_norm", "attn_k_norm": "self_attn.k_norm", + # engine#2013: the GATED linear-attention (GateDeltaNet) families carry a per-layer attention gate under + # `blk.N.attn_gate.weight`; the container's name for it is `self_attn.gate_proj.weight`, which is what the forward + # looks up (npu/forward/src/model.c:1083, in the linear-attn section; q4nx_forward.h's `attn_output_gate`). Measured on + # this box's `bonsai2-unrot-q4_0.gguf`: every `blk.N.attn_gate.weight` is [5120, 6144] Q4_0, and 5120 x 6144 = + # 31,457,280 = 3840 x 8192, a whole number of 32 x 256 tiles, so the tiling check passes and the name is the only gap. + # Only some layers carry it (0, 1, 2, 4, 5, 6, 8, 9 in the first ten), so it is looked up per layer, never assumed. + "attn_gate": "self_attn.gate_proj", "attn_q": "self_attn.q_proj", "attn_k": "self_attn.k_proj", "attn_v": "self_attn.v_proj", "attn_output": "self_attn.o_proj", "ffn_gate": "mlp.gate_proj", "ffn_up": "mlp.up_proj", "ffn_down": "mlp.down_proj",