Skip to main content

frink_models/
capability.rs

1//! Explicit architecture capability registry for the generic GGUF path.
2//!
3//! Mirrors the pinned llama.cpp `llm_arch` / `LLM_ARCH_NAMES` inventory
4//! (`.scratch/llama.cpp/src/llama-arch.{h,cpp}`) with Frink-side
5//! classification into decoder families, memory kinds, and scope.
6//! Unknown strings and detected-but-unimplemented features fail closed
7//! (`LoadError`) instead of silently defaulting into fluent-but-wrong
8//! logits.
9//!
10//! Architecture names are registry keys only. Hot-path kernels never
11//! branch on them; load-time resolution produces an [`ArchProfile`]
12//! whose fields the decoder reads as plain data.
13
14use crate::config::RopeLayout;
15
16/// How far this architecture is in Frink's delivery scope (plan:
17/// text-generation parity; encoder/multimodal/diffusion/audio deferred).
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ArchScope {
20    /// Autoregressive / encoder-decoder text generation -- in scope.
21    TextGeneration,
22    /// Encoder / embedding / pooling models -- deferred.
23    DeferredEncoderEmbedding,
24    /// Vision / multimodal projector paths -- deferred.
25    DeferredMultimodal,
26    /// Diffusion / masked-LM samplers -- deferred.
27    DeferredDiffusion,
28    /// Audio tokenizers / codecs -- deferred.
29    DeferredAudio,
30    /// Enum present in llama.cpp but not a real serve target here.
31    EnumOnly,
32}
33
34/// Shared execution family (maps many GGUF strings onto one engine path).
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum DecoderFamily {
37    /// Standard GQA (+ optional MoE) with whole-vector optional QK-norm.
38    StandardGqa,
39    /// Qwen3-style: explicit head_dim + per-head Q/K RMSNorm before RoPE.
40    Qwen3Family,
41    /// Gemma-family: embedding scale, post-norms, softcap, SWA pattern, GeGLU.
42    GemmaFamily,
43    /// Phi-family: fused QKV and/or fused gate+up SwiGLU.
44    PhiFamily,
45    /// DeepSeek-2 / Mistral4 MLA (not generic GQA).
46    Mla,
47    /// Attn + SSM / delta-net hybrids.
48    Hybrid,
49    /// Pure recurrent (Mamba / RWKV) -- no KV cache.
50    Recurrent,
51    /// T5-style encoder-decoder.
52    EncoderDecoder,
53    /// Dedicated Frink stacks (GLM DSA, DeepSeek V4, Kimi).
54    Dedicated,
55    /// In-repo synthetic fixtures.
56    TestFixture,
57}
58
59/// Memory / KV backend selected once at load (llama.cpp `create_memory`).
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum MemoryKind {
62    KvGqa,
63    KvIswa,
64    KvMla,
65    KvDsa,
66    KvDsv4,
67    Recurrent,
68    Hybrid,
69    None,
70}
71
72/// How `attn_q_norm` / `attn_k_norm` weights are applied (when present).
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
74pub enum QkNormStyle {
75    /// OLMoE: one RMSNorm over the full Q/K projection width.
76    #[default]
77    WholeVector,
78    /// Qwen3 / Gemma3: RMSNorm per head with weight length `head_dim`.
79    PerHead,
80    /// Talkie (`talkie.cpp:26,82-91`): RMSNorm per head, then ONE
81    /// learned scalar per head for Q (`attn_q_norm` is `{1, n_head}`),
82    /// and the same per-head RMSNorm with NO weight for K (`:90`,
83    /// `build_norm(Kcur, nullptr, ...)`); there is no `attn_k_norm`
84    /// tensor. Decided by architecture (`PER_HEAD_SCALAR_QK_GAIN`), not
85    /// by the weight's length: a file whose `n_head == head_dim` would
86    /// make the length ambiguous. Applied after RoPE, as the graph does.
87    PerHeadScalar,
88    /// PLaMo-2 (`plamo2.cpp:92-93,163,166`): RMSNorm per head with a
89    /// DISTINCT weight per head -- `attn_q_norm` is `{head_dim, n_head}`
90    /// and `attn_k_norm` `{head_dim, n_head_kv}`, and `build_norm` over
91    /// the 3-d `{head_dim, n_head, n_tokens}` view norms each head and
92    /// multiplies by that head's row. The weight is `n_heads * head_dim`
93    /// long, the same length as [`QkNormStyle::WholeVector`]'s, which is
94    /// why it is decided by architecture ([`PER_HEAD_DISTINCT_QK_NORM`])
95    /// and not by the length rule.
96    PerHeadDistinct,
97}
98
99/// Architectures whose Q/K norm is the per-head RMSNorm with one weight
100/// row per head ([`QkNormStyle::PerHeadDistinct`]). Measured over the
101/// 155 graphs: `attn_q_norm` created `{n_embd_head_k, n_head}` in five
102/// (`chameleon`, `command-r`, `stablelm`, which norm with LLM_NORM and
103/// are `crate::qk_layer_norm`'s; `talkie`, whose weight is `{1,
104/// n_head}`; and `plamo2`, the one RMS row).
105pub const PER_HEAD_DISTINCT_QK_NORM: &[&str] = &["plamo2"];
106
107/// See [`PER_HEAD_DISTINCT_QK_NORM`].
108pub fn uses_per_head_distinct_qk_norm(arch: &str) -> bool {
109    PER_HEAD_DISTINCT_QK_NORM.contains(&arch)
110}
111
112/// Architectures whose Q norm weight is one scalar per head and whose K
113/// norm has no weight ([`QkNormStyle::PerHeadScalar`]). Measured:
114/// `attn_q_norm` created `{1, n_head}` in one of 155 graphs,
115/// `talkie.cpp:26`.
116pub const PER_HEAD_SCALAR_QK_GAIN: &[&str] = &["talkie"];
117
118/// See [`PER_HEAD_SCALAR_QK_GAIN`].
119pub fn uses_per_head_scalar_qk_gain(arch: &str) -> bool {
120    PER_HEAD_SCALAR_QK_GAIN.contains(&arch)
121}
122
123/// How much work admitting one UNAUDITED architecture to the generic
124/// path would actually be.
125///
126/// Every architecture on the generic path that is not in
127/// [`AUDITED_GENERIC_GQA`] refuses with
128/// `LoadError::UnauditedArchitecture`, and that message used to say the
129/// same thing for all 47 of them. It hid a real difference:
130/// `bailingmoe2` needs a test fixture and nothing else, `deepseek` needs
131/// one name added to one list, and `olmo2` needs a decoder that can skip
132/// the two pre-norms it does not have. A user reading "nobody has
133/// checked this" cannot tell a one-line fix from a new attention
134/// implementation.
135///
136/// **A verdict here is a reading of BOTH trees, never a guess.** Every
137/// non-[`TriageClass::Unknown`] verdict names the `src/models/*.cpp`
138/// line that decides it and the frink file that would change.
139/// `Unknown` is a legitimate answer and says what would settle it. The
140/// precedent this rule exists for: four architectures in this very file
141/// once refused while naming a blocker that was not the real one --
142/// `glm4moe` was told it lacked an MLA hyper-parameter it must not have,
143/// and `minimax-m2` was blamed on MTP weights no converter can emit.
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
145pub enum TriageClass {
146    /// Frink already implements everything this architecture needs.
147    /// What is missing is EVIDENCE: a fixture, or a parity run against
148    /// llama.cpp on a real checkpoint.
149    FixtureAway,
150    /// One small, nameable piece is missing: an activation, a norm slot,
151    /// a routing flag, an ordering. Nameable is the bar -- if the blocker
152    /// cannot be written as a sentence naming the thing, it is not this
153    /// class.
154    OneMatchArm,
155    /// A different attention or residual structure: a norm the decoder
156    /// unconditionally applies and this model does not have, a scaled
157    /// residual, ALiBi, MLA, block-sparse, recurrent, hybrid.
158    NewCode,
159    /// Not decidable from reading the two trees. The blocker says what
160    /// would settle it.
161    Unknown,
162}
163
164impl TriageClass {
165    /// Short slug used in the refusal message.
166    pub fn label(self) -> &'static str {
167        match self {
168            TriageClass::FixtureAway => "FIXTURE-AWAY",
169            TriageClass::OneMatchArm => "ONE MATCH ARM",
170            TriageClass::NewCode => "NEW CODE",
171            TriageClass::Unknown => "UNKNOWN",
172        }
173    }
174
175    /// One sentence saying what the class means, so the message stands
176    /// alone without this doc comment.
177    pub fn headline(self) -> &'static str {
178        match self {
179            TriageClass::FixtureAway => {
180                "frink already implements everything this architecture needs; what is \
181                 missing is EVIDENCE, not capability"
182            }
183            TriageClass::OneMatchArm => {
184                "one small, named piece is missing -- an activation, a norm slot, a \
185                 routing flag or an ordering"
186            }
187            TriageClass::NewCode => {
188                "a different attention or residual structure than the generic decoder \
189                 computes; this is not a fixture away"
190            }
191            TriageClass::Unknown => {
192                "reading both trees did not settle this one; the note below says what \
193                 would"
194            }
195        }
196    }
197}
198
199/// One architecture's triage verdict, carried on its own catalog row.
200///
201/// Deliberately NOT a second table keyed by architecture name. This repo
202/// has fixed three separate bugs caused by two structures disagreeing
203/// about the same architecture, so the verdict lives on the
204/// [`ArchProfile`] the loader already resolves, and
205/// `every_unaudited_generic_architecture_is_triaged_or_listed_as_pending`
206/// pins that no generic row can exist without one or the other.
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub struct UnauditedTriage {
209    pub class: TriageClass,
210    /// What is missing, with the llama.cpp `src/models/*.cpp` line that
211    /// decides it and the frink file that would change.
212    pub blocker: &'static str,
213}
214
215/// Unaudited generic-path architectures nobody has read against
216/// llama.cpp's graph yet.
217///
218/// This is a TO-DO, not cover. A name here means the refusal honestly
219/// says "not triaged" rather than inventing a class; a name leaves this
220/// list only by gaining an [`UnauditedTriage`] on its catalog row, and
221/// the two tests below make it impossible for a name to be on both or on
222/// neither.
223pub const TRIAGE_PENDING: &[&str] = &[
224    // Norm-RoPE group.
225    // NEOX-RoPE group.
226];
227
228/// This architecture's triage verdict, or `None` when it has not been
229/// triaged (see [`TRIAGE_PENDING`]) or does not need one.
230pub fn unaudited_triage(arch: &str) -> Option<UnauditedTriage> {
231    resolve_profile(arch).and_then(|p| p.triage)
232}
233
234/// The triage half of the `UnauditedArchitecture` refusal, rendered for
235/// the user.
236///
237/// Appended to the generic "nobody has verified this" sentence so the
238/// message says which of the three classes the architecture is in and
239/// what specifically is missing, rather than the same paragraph for all
240/// 47.
241pub fn unaudited_refusal_detail(arch: &str) -> String {
242    match unaudited_triage(arch) {
243        Some(t) => format!(
244            "TRIAGE ({}): {}. {}.",
245            t.class.label(),
246            t.class.headline(),
247            t.blocker
248        ),
249        None => format!(
250            "TRIAGE: not done for `{arch}` yet -- nobody has read llama.cpp's \
251             src/models/*.cpp for it against the generic decoder, so this refusal names \
252             no blocker and you should not read it as one. Triaging the remaining \
253             architectures is docs/plans/llama-cpp-gap-inventory.md section 8, item 6."
254        ),
255    }
256}
257
258/// Architectures on the shared generic-GQA path that somebody has
259/// actually PROVEN, and the evidence for each.
260///
261/// The generic path is a guess: it assumes an architecture is plain GQA
262/// because nothing said otherwise. That guess has already been wrong
263/// five times. `gpt2`, `mpt`, `refact`, `bloom` and `jais` all sat here
264/// computing ALiBi or learned absolute position embeddings as though
265/// they were NEOX RoPE, and every downstream guard missed them: two
266/// hardcode their ALiBi slope with no GGUF key, one leaves no unread
267/// tensor, and the RoPE pin excluded their group by construction.
268///
269/// So membership here is not "we think this works", it is "there is a
270/// benchmark row, a pinned logit comparison against llama.cpp, or a
271/// fixture". Everything else on the generic path is UNAUDITED and says
272/// so at load time rather than running and hoping.
273///
274/// Adding a name here without evidence defeats the entire point.
275pub const AUDITED_GENERIC_GQA: &[&str] = &[
276    // Bench rows in benchmarks/suite.json, measured against llama.cpp
277    // on the same host and file.
278    "llama",    // TinyLlama, Mistral, Mixtral, SmolLM2, Llama-3.x all tag llama
279    "qwen2",    // Qwen2.5-0.5B
280    "qwen2moe", // Qwen1.5-MoE-A2.7B
281    "qwen3",    // Qwen3-0.6B
282    "olmoe",    // OLMoE-1B-7B
283    "gemma2",   // Gemma-2-2B
284    "gemma3",   // Gemma-3-1B
285    "phi3",     // Phi-4-mini tags phi3
286    // Pinned against real libllama logits in tests/.
287    "gpt-oss",
288    "dots1",
289    // tests/qwen3moe_graph.rs: a synthetic 2-layer fixture
290    // (scripts/make_qwen3moe_fixture.py) compared against llama.cpp's
291    // own qwen3moe graph via libllama, on all three forward paths.
292    // Carries per-head QK norm before RoPE, head_dim * n_head != n_embd,
293    // GQA, NEOX RoPE, softmax gating with renormalised top-k, and
294    // n_ff != n_ff_exp.
295    "qwen3moe",
296    // tests/one_match_arm_graphs.rs: five architectures that were
297    // triaged ONE MATCH ARM, each admitted with the same evidence
298    // qwen3moe has -- a synthetic fixture whose golden logits come from
299    // llama.cpp's own graph via libllama, checked on all three forward
300    // paths. The arm each one needed is named beside it; every fixture
301    // is built so that getting that arm wrong moves the logits by orders
302    // of magnitude more than the comparison tolerance.
303    //
304    // `deepseek` (V1, not the MLA deepseek2): top-k weights are NOT
305    // renormalised (deepseek.cpp:145-155 passes norm_w=false and no
306    // converter writes expert_weights_norm), so the fixture carries no
307    // such key and the answer has to come from
308    // NO_TOPK_RENORMALIZE_ARCHITECTURES.
309    "deepseek",
310    // `bailingmoe`: llama.cpp reads leading_dense_block_count and never
311    // branches on it (bailingmoe.cpp:5 vs :39-54). The fixture sets the
312    // key to 1 and ships NO dense FFN on layer 0.
313    "bailingmoe",
314    // `seed_oss`: the pre-FFN norm is stored as post_attention_norm and
315    // there is no ffn_norm (seed-oss.cpp:36-37,113-115) -- gpt-oss's
316    // slot, now a named list rather than an `arch == "gpt-oss"` flag.
317    "seed_oss",
318    // `maincoder` and `hunyuan-moe`: per-head QK norm applied AFTER RoPE
319    // (maincoder.cpp:78-95, hunyuan-moe.cpp:93-118). Both fixtures use
320    // QK-norm weights centred near 1.5 so the ordering is visible.
321    "maincoder",
322    "hunyuan-moe",
323    // `hunyuan-dense`: the same post-RoPE QK-norm order (it has no graph
324    // of its own -- models.h:1830-1834 derives it from
325    // llama_model_hunyuan_vl) PLUS the NTK-alpha RoPE base rescale at
326    // hunyuan-vl.cpp:8-12, which is now `rope_ntk_alpha`. Its fixture
327    // carries `hunyuan-dense.rope.scaling.alpha` explicitly, because the
328    // HUNYUAN_DENSE converter does that arithmetic in Python and writes
329    // the already-scaled base (conversion/hunyuan.py:254-281) -- the
330    // `add_rope_scaling_alpha` at :356 is HunyuanVLTextModel, i.e. the
331    // separate `hunyuan-vl` row. The triage verdict cited that line for
332    // this architecture and was wrong about it.
333    "hunyuan-dense",
334    // `ernie4_5-moe`: the MoE sibling of the audited `ernie4_5`. Its
335    // interleave step is a REFUSAL rather than an implementation, and
336    // that is the finding, not a shortcut: llama.cpp's tensor loader
337    // (ernie4-5.cpp:49) creates expert tensors for every layer past the
338    // leading-dense prefix with NO step in the condition, while its
339    // graph (ernie4-5-moe.cpp:64) takes the dense branch when
340    // `(il + 1) % step != 0`, so a checkpoint whose interleave really
341    // interleaves cannot be loaded by llama.cpp at all -- measured, on a
342    // two-step fixture, as `check_tensor_dims: tensor
343    // 'blk.2.ffn_gate_inp.weight' not found`. Both published ERNIE-4.5
344    // MoE checkpoints carry a step of 1, which is what the golden
345    // fixture pins; `moe_interleave` refuses anything else by name.
346    "ernie4_5-moe",
347    // tests/fixture_away_graphs.rs: architectures that were triaged
348    // FIXTURE-AWAY -- frink already built their graph, and only the
349    // evidence was missing. Same standard as the rows above: a synthetic
350    // fixture from `scripts/make_<arch>_fixture.py` whose golden values
351    // come from llama.cpp's own graph via libllama, compared on prefill,
352    // decode and continuous batching, with a sabotage test per row
353    // proving the fixture can SEE the fact its architecture turns on.
354    //
355    // Each was checked, against the C, on the six things this repo has
356    // lost at least once: RoPE variant, SWA pattern and phase,
357    // `attention_scale`, the two post-norm slots, and QK-norm ordering.
358    //
359    // `internlm2` (internlm2.cpp:3-11,25-33,59-122): plain llama, NORM
360    // RoPE, `1/sqrt(head_dim)` scale, no post-norms, no QK-norm, no SWA.
361    // Its fixture carries the OPTIONAL q/k/v projection biases real
362    // InternLM2 exports ship.
363    "internlm2",
364    // `xverse` (xverse.cpp:3-12,14-35,59-121): the same, with no biases.
365    "xverse",
366    // `gemma` (gemma.cpp:3-11,13-34,41-138): Gemma-1, the oldest row of
367    // the family and the last one that was not evidenced. Its three
368    // Gemma-specific pieces were already implemented for `GemmaFamily`
369    // and the fixture is what proves each of them: the sqrt(n_embd)
370    // embedding scale (:49), GeGLU rather than SwiGLU (:112,
371    // LLM_FFN_GELU) and a `1/sqrt(head_dim)` attention scale that
372    // llama.cpp reaches by scaling Q at :86 and passing kq_scale = 1.0f
373    // at :91, which is what leaving `attention_scale` as None already
374    // produces. Its lm_head is TIED with no fallback (:20), so the
375    // fixture ships no `output.weight` and the embedding scale is not
376    // cancelled downstream. Gemma-1 declares no softcap and no sliding
377    // window, so the Gemma-2/3 machinery must resolve to inert, and
378    // `tests/fixture_away_graphs.rs` asserts that rather than assuming
379    // it.
380    "gemma",
381    // `ernie4_5` DENSE (ernie4-5.cpp:36-69,95-149): NORM RoPE, head_dim
382    // decoupled from n_embd/n_head. `ernie4_5-moe` is a different row
383    // with its own fixture, above.
384    "ernie4_5",
385    // `baichuan` (baichuan.cpp:5-14,17-40,64-137): the 7B ONLY. The 13B
386    // is a different model under the same string and is refused by name
387    // on `block_count == 40` in loader.rs before this list is consulted,
388    // because llama.cpp picks ALiBi-and-no-RoPE off the layer count with
389    // no GGUF key to declare it. The fixture therefore has 32 layers: a
390    // 2-layer one would be LLM_TYPE_UNKNOWN and get no RoPE at all.
391    "baichuan",
392    // `exaone` (exaone.cpp:3-10,12-40,65-121): EXAONE 3.x, NEOX RoPE,
393    // tied lm_head. NOT `exaone4` (no pre-norms) and NOT `exaone-moe`
394    // (no RoPE on the full-attention layers); both stay refusing.
395    "exaone",
396    // `plamo3` (plamo3.cpp:3-60,91-193): the sandwich-norm row, and the
397    // only one here with a sliding window. Its verdict was FIXTURE-AWAY
398    // and was WRONG by one tensor name: plamo3 is the sole architecture
399    // upstream that creates ATTN_POST_NORM / FFN_POST_NORM through the
400    // two-argument `tn` overload (:52,55), so it asks for
401    // `blk.N.post_attention_norm` and `blk.N.post_ffw_norm` with NO
402    // `.weight`, and gguf-py emits exactly those names for it. frink
403    // read only the suffixed spelling; `load_norm_vec_either_spelling`
404    // in loader.rs now reads both, and says why.
405    //
406    // Its SWA is a real pattern with a real phase -- period from
407    // `attention.sliding_window_pattern`, `dense_first = false` from
408    // `set_swa_pattern`'s default -- and the fixture sets a window
409    // narrower than the prompt so the mask actually bites.
410    "plamo3",
411    // tests/granite_family_graphs.rs: the Granite family, which was
412    // triaged NEW CODE on four SCALAR MULTIPLIERS the generic decoder
413    // did not apply -- `logit_scale`, `residual_scale`,
414    // `embedding_scale` and `attention.scale`. They are hparams rather
415    // than tensors, so `assert_every_tensor_consumed` cannot see them
416    // and a Granite checkpoint would otherwise have loaded and answered
417    // at the wrong scale. `crate::scalar_multipliers` implements all
418    // four ONCE, parameterised by architecture, and
419    // `capability::unsupported_scaling_keys` is now DERIVED from that
420    // same table rather than restated beside it.
421    //
422    // `granite` (granite.cpp:5-10,180,225,235-238,288-292) is the dense
423    // row. `granitemoe` has no graph of its own -- `models.h:1583-1591`
424    // is `using graph = llama_model_granite::graph` -- so the two differ
425    // in the FFN and in nothing else, and its fixture carries the MoE
426    // branch, an UNGATED shared expert, and expert tensors sized from
427    // `n_ff` rather than `n_ff_exp`.
428    //
429    // `granite-moe` is a frink-only alias: `llama-arch.cpp:101` spells
430    // the architecture `granitemoe` and no GGUF anywhere says
431    // `granite-moe`, so there is no libllama golden for it and there
432    // never can be. Its evidence is a SECOND fixture, byte-identical
433    // except for the architecture string and key prefixes, asserted
434    // against `granitemoe`'s libllama golden -- which is the only thing
435    // that can keep an alias nothing outside frink would ever exercise
436    // from drifting away from the row it aliases.
437    //
438    // The `rope_finetuned` half of the verdict landed as a REFUSAL
439    // (`crate::rope_finetuned`) and was SERVED on 2026-09-14 as
440    // `RopeLayers::Never` when Granite-4.0 needed it: granite.cpp:33-35
441    // reads `{arch}.rope.scaling.finetuned` as a switch for RoPE itself,
442    // and a file declaring it false runs UNROTATED, which the fixture
443    // that had evidenced the refusal now matches.
444    "granite",
445    "granitemoe",
446    "granite-moe",
447    // `bailingmoe2` (bailingmoe2.cpp:23-87,111-198): Ling-2.0. The one
448    // MoE row in this batch, so the two MoE facts do arise and both are
449    // asserted: SIGMOID gating, read from the file's REQUIRED
450    // `expert_gating_func` (:11) against frink's softmax default, and
451    // `expert_weights_norm` (:10), also read from the file. Its shared
452    // expert is `n_ff_shexp * n_expert_shared` wide (:58), not
453    // `n_ff_shexp`. Per-head QK norm BEFORE RoPE (:123-135), fused
454    // attn_qkv, leading dense layers that llama.cpp really does branch
455    // on (:57) -- unlike `bailingmoe`, which reads the same key and
456    // ignores it.
457    "bailingmoe2",
458    // tests/post_norm_only_graphs.rs: the POST-NORM-ONLY family, two
459    // architectures and ONE implementation (`crate::norm::NormOp`).
460    // Neither has an `attn_norm` or an `ffn_norm` tensor; both read the
461    // raw residual at both sublayers and norm each branch's output
462    // before its residual add. Same evidence standard as the rows
463    // above: a synthetic fixture per row whose golden logits come from
464    // llama.cpp's own graph via libllama, on all three forward paths.
465    //
466    // `olmo2` (olmo2.cpp:45-52,92,160-165,169,177-182): WHOLE-VECTOR
467    // QK-norm -- :45-46 sizes the norms `{n_embd}` and
468    // `{n_head_kv * n_embd_head}` and :106-112 applies them to the 2-D
469    // projections before `ggml_reshape_3d`. An `olmo2` file carrying
470    // BOTH a sliding window and a rope scaling is Olmo-3, ropes its two
471    // kinds of layer differently (:120-146), and is refused by name in
472    // loader.rs.
473    "olmo2",
474    // `exaone4` (exaone4.cpp:60-67,118,152-169): the same graph with
475    // PER-HEAD QK-norm instead -- :61-62 sizes them `{n_embd_head_k}`
476    // and :127-128 applies them to what `build_qkv` already reshaped.
477    // NOT the audited `exaone` row, which is EXAONE 3.x and a plain
478    // pre-norm llama.
479    //
480    // BOTH SIZES run. EXAONE-4 32B (`block_count == 64`) used to be
481    // refused by name: llama.cpp turns SWA on off the layer count
482    // (:4-9) and then ropes only the sliding layers (:116), so its
483    // full-attention layers get no rotation. `crate::rope_layers`
484    // implements that rule and `capability::swa_disabled_by_arch`
485    // carries the layer-count gate it depends on, with a 64-layer
486    // libllama-golden fixture in `tests/no_rope_layer_graphs.rs`.
487    "exaone4",
488    // tests/no_rope_layer_graphs.rs: the PER-LAYER-RoPE group, three
489    // rows on one rule (`crate::rope_layers`). llama.cpp gates rotation
490    // per layer in six architectures and frink had no way to say so,
491    // which cost `smollm3` and `exaone-moe` an outright refusal and
492    // EXAONE-4 32B a refusal by name.
493    //
494    // `exaone-moe` (exaone-moe.cpp:136,155-161): `is_swa(il)` around
495    // both `ggml_rope_ext` calls, with `swa_type` pinned to STANDARD at
496    // :4 -- which is `exaone4.cpp:116` with the second disjunct nailed
497    // false, i.e. the same rule and not a similar one. Its MoE half
498    // (:72-93) is machinery frink already had and the fixture carries
499    // all of it: leading dense, `exp_probs_b`, a shared expert sized by
500    // `expert_shared_feed_forward_length`, sigmoid gating from
501    // metadata, `expert_weights_scale`/`_norm`.
502    "exaone-moe",
503    // `smollm3` (smollm3.cpp:5,69): `(il + 1) % 4 != 0`, nine layers of
504    // a 36-layer SmolLM3-3B unrotated, from a literal with no GGUF key.
505    // The graph is otherwise the plain pre-norm llama one, so this is
506    // the row where the rule is the ONLY thing -- which is why it was
507    // in the "No RoPE at all" refusal group beside the ALiBi
508    // architectures until the rule existed.
509    "smollm3",
510    // tests/one_match_arm_graphs.rs: the FUSED-`attn_qkv.bias` pair.
511    // `create_tensor_qkv` (llama-model.cpp:2886-2900) creates the bias
512    // beside a fused `wqkv`, and `build_qkv` (llama-graph.cpp:1605-1609)
513    // adds it to the fused projection before splitting. frink split the
514    // fused WEIGHT and then looked for the bias only under the split
515    // `attn_q.bias` names, so it was dropped and all three projections
516    // ran unbiased. Both halves now come out of one decision in
517    // `qkv_fused`, sliced by the same spans.
518    //
519    // `chatglm` (chatglm.cpp:25-52,58-161) was the LAST ONE MATCH ARM
520    // row anywhere in this file. It also pins the two things that made
521    // it look fixture-away and are not: PARTIAL RoPE
522    // (conversion/chatglm.py:151 writes `rope_dimension_count` as
523    // `head_dim * 0.5`) and the fused gate+up SwiGLU
524    // (chatglm.cpp:48,128-133), which is phi3's call shape.
525    "chatglm",
526    // `qwen` is Qwen-1 (`QWenLMHeadModel`), not qwen2/qwen3. Its
527    // `attn_qkv.bias` is REQUIRED (qwen.cpp:28, flag `0`), which is
528    // stronger than chatglm's optional one, and it needed a SECOND arm
529    // the chatglm verdict did not name: qwen.cpp:33-35 sizes every FFN
530    // matrix at `n_ff / 2`, because Qwen-1's `intermediate_size` counts
531    // gate and up together. See `FFN_LENGTH_COUNTS_GATE_AND_UP` in
532    // loader.rs.
533    "qwen",
534    // tests/minicpm_graphs.rs: MiniCPM, which was never an unaudited
535    // row -- it was refused BY NAME, because the thing it does is
536    // invisible in the file. `models.h:1594-1601` is
537    // `using graph = llama_model_granite::graph`, so it is the Granite
538    // graph object verbatim; what `minicpm.cpp:5-7` adds is DEFAULTS,
539    // assigning an embedding multiplier of 12.0, a residual multiplier
540    // of `1.4/sqrt(n_layer)` and a logit multiplier of `256/n_embd`
541    // before `:12-14` lets the file override them. A MiniCPM export
542    // carrying none of the three keys is still scaled by all three, so
543    // `unsupported_scaling_keys` -- a key-PRESENCE gate -- can see
544    // nothing to refuse. `scalar_multipliers::MultiplierDefaults` is
545    // that hook, and the fixture that evidences it declares NO key at
546    // all, which is the only fixture shape that can tell the hook from
547    // its absence. A second fixture declares all three and pins that
548    // the file still wins.
549    //
550    // It is Granite's arithmetic minus one column: `minicpm.cpp:3-24`
551    // never reads `{arch}.attention.scale`, so that key stays refused
552    // for this row by the derived list.
553    "minicpm",
554    // tests/olmo_graphs.rs: OLMo-1, the THIRD norm shape and the reason
555    // `crate::norm::NormOp` has three variants rather than two. It is
556    // pre-norm like `llama` -- `olmo.cpp:65-67` before attention,
557    // :104-106 before the FFN -- so it is NOT the post-norm-only
558    // topology `olmo2` and `exaone4` share. What differs is the norm
559    // FUNCTION: all three sites are
560    // `build_norm(x, NULL, NULL, LLM_NORM, il)`, a non-parametric
561    // LayerNorm, and `olmo.cpp:15-36` creates no norm tensor at all --
562    // no `attn_norm`, no `ffn_norm`, no `output_norm`.
563    //
564    // Its lm_head is TIED with a fallback (:21-25) and its RoPE is NORM
565    // (llama-model.cpp:2585). Its CLAMP -- `olmo.cpp:5` reads
566    // `{arch}.attention.clamp_kqv`, `llama-graph.cpp:1611-1652` applies
567    // it to Q, K and V inside `build_qkv`, and `conversion/olmo.py:23-25`
568    // really writes it for OLMo-7B-Twin-2T and OLMo-1.7-7B -- was a
569    // refusal by name and is implemented now (`crate::clamp_kqv`), with
570    // the clamped fixture matched against libllama rather than refused:
571    // `dbrx` below needed the same clamp as a REQUIRED key.
572    "olmo",
573    // tests/dbrx_graphs.rs: DBRX, NEW CODE on three blockers that each
574    // extended a seam landed the day before. `dbrx.cpp:69-71`, `:110-112`
575    // and `:140-142` norm with `LLM_NORM` and a weight but no bias, which
576    // is `crate::norm::NormOp::LayerNorm` -- the variant the OLMo-1 work
577    // deliberately left unwritten until a row called it; `dbrx.cpp:5`
578    // reads `attention.clamp_kqv` as REQUIRED, which `crate::clamp_kqv`
579    // applies after the bias through the ONE helper every host body
580    // shares (`decoder/qkv_bias.rs`); and `dbrx.cpp:34,110-113` keep the
581    // pre-FFN norm under `blk.N.attn_output_norm`, which
582    // `crate::norm_sites` reads into the same slot `gpt-oss` keeps under
583    // `post_attention_norm`. Fused `attn_qkv` (:31), NEOX RoPE
584    // (llama-model.cpp:2617), SiLU MoE with softmax gating and top-k
585    // renormalisation (:115-125), untied lm_head (:24).
586    "dbrx",
587    // tests/grok_graphs.rs: Grok-1, NEW CODE on the MiniCPM shape --
588    // `grok.cpp:5-12` seeds SEVEN hyper-parameters before `:14-27` let
589    // the file override them, so a file declaring none is still scaled
590    // by all of them and a key-presence gate sees nothing.
591    // `scalar_multipliers::MultiplierDefaults::Grok` is that hook:
592    // `embedding_scale` (78.38), `logit_scale` as a MULTIPLY (:211, the
593    // `LogitScaleUse::AsIs` variant the module had named as absent),
594    // `attention.output_scale` (0.0884, a fifth key that resolves into
595    // the `attention_scale` slot) and the attention softcap default of
596    // 30. The attention itself is `kq_scale = 1.0f` (:137) with the
597    // real scale inside the tanh (llama-graph.cpp:2572-2582), which is
598    // exactly "pre-scale Q, then softcap"; `router_logit_softcapping`
599    // and `attention.temperature_length` are read at :20,:23 and applied
600    // NOWHERE in the graph (measured: no other reference in `src/`), so
601    // frink ignores them the same way. `blk.N.attn_output_norm` is the
602    // POST-attention norm here (:143-146, before the residual add at
603    // :148) and `layer_output_norm` / `post_ffw_norm` the post-FFN one
604    // (:75-78, :185-188): a `crate::norm_sites` row. GELU MoE with
605    // softmax gating (:158-168), NEOX RoPE (llama-model.cpp:2616),
606    // tied-with-fallback lm_head (:46-51). Grok-2's parallel dense FFN
607    // (`:171-184`, `sqrt(2)/2` on the sum) is refused BY NAME in
608    // `loader.rs`, so this row is admitted for Grok-1.
609    "grok",
610    // tests/ungated_ffn_graphs.rs: Arcee AFM, NEW CODE on ONE fact --
611    // the FFN has no gate. `arcee.cpp:39-40` creates `ffn_up` and
612    // `ffn_down` only, and `:123-128` is `build_ffn` with a NULL gate,
613    // `LLM_FFN_RELU_SQR` and `LLM_FFN_SEQ`: `down(relu(up(x))^2)`.
614    // frink spells that as `FfnActivation::ReluSqr`, which the loader
615    // serves by ALIASING the expert's gate to its up matrix and
616    // `frink_moe::GluAct::ReluSqr` (reads `up` alone), so the gated
617    // struct and every gated path are untouched and `relu(up)^2` is
618    // what they compute; the dense hot paths skip the aliased matmul.
619    // (`GluAct::Reglu`, `relu(gate) * up`, served it until
620    // `smallthinker` needed that op on a REAL gate; see `uses_reglu`.)
621    // No fused device kernel spells it, so `fused_kernel_gelu_flag`
622    // returns `None` and `metal_can_serve_model` keeps the model off
623    // the stacks -- which replaced six `gelu = !is_swiglu()` sites
624    // that would have run a third activation as GELU. Everything else
625    // is `llama` (:6 says so): NORM RoPE (llama-model.cpp:2600),
626    // optional `output.weight` with a tied fallback (:20-25),
627    // `n_embd_head == n_rot` asserted (:51-52). The same FFN is in
628    // `plm`, `nemotron`, `jais2` and `nemotron-h`, each of which refuses
629    // for something else; see `uses_relu_sqr`.
630    "arcee",
631    // tests/per_layer_shape_graphs.rs: the PER-LAYER-SHAPE pair, two
632    // rows on one seam (`crate::layer_shapes`). llama.cpp reads
633    // `head_count`, `head_count_kv` and `feed_forward_length` as
634    // scalar-or-array for every architecture and hands most graphs
635    // layer 0; these two index the arrays in both their tensor loader
636    // and their graph, and frink carried all three as scalars. The
637    // scan that sized the seam is recorded in
638    // `layer_shapes::PER_LAYER_SHAPE_ARCHS`.
639    //
640    // `deci` (deci.cpp:30-34 loader, :103-105 graph): all three per
641    // layer AND a three-way branch on them -- `n_head == 0` passes the
642    // residual through with no norm (:107-109), `n_head_kv == 0` runs
643    // `attn_norm` then `wo` alone (:115-118), `n_ff == 0` skips the FFN
644    // (:147-149). `AttnShape::{Gqa, Linear, Absent}` and
645    // `LayerShape::ffn_dim` are those, and the fixture has one layer of
646    // each kind. A second fixture is the DeciLM-7B shape
647    // (conversion/deci.py:114-118: `head_count_kv` alone as an array).
648    // The FFN-free layer WITH attention is refused: `:147-149`
649    // `continue`s before the residual add, and scaling that layer's
650    // attention weights by 3 leaves libllama's logits byte-identical
651    // (measured), so the branch is dead in the reference graph and
652    // frink will not pin it. NORM RoPE (llama-model.cpp:2576).
653    "deci",
654    // `openelm` (openelm.cpp:26-28 loader, :67-69 graph): all three per
655    // layer, one fused `wqkv` per layer sized `(2*n_head_kv(i) +
656    // n_head(i)) * n_embd_head_k` (:34) -- `qkv_fused::FusedQkvRows::of`
657    // takes the layer now -- per-head QK-norm before RoPE (:82-102),
658    // NEOX RoPE (llama-model.cpp:2650), a tied lm_head with no fallback
659    // (:22). The fixture's three layers share no KV width and no FFN
660    // width. Its converter writes the arrays (conversion/openelm.py:
661    // 57-59), which `layer_shapes::read_u64_per_layer` reads where
662    // `GgufValue::as_u64` used to die on them.
663    "openelm",
664    // tests/gated_attention_graphs.rs: the GATED-ATTENTION pair, two
665    // rows on one seam (`crate::attn_gate`). `afmoe.cpp:73,154,183-185`,
666    // `laguna.cpp:110-124,211,246-257` and `step35.cpp:96,268-284` each
667    // project a gate from the SAME normed input Q/K/V read and multiply
668    // the attention output by it BEFORE `wo`; they differ in the
669    // activation (sigmoid / softplus), in the width (per element / per
670    // head / decided by the tensor's shape) and in whether the tensor
671    // may be absent. Read side by side before being called one cause:
672    // the three graphs, six create sites measured over all 155.
673    //
674    // `afmoe` (afmoe.cpp:73,120,154,183-185): sigmoid, per element,
675    // REQUIRED. The other afmoe-only fact is `:120`, `sqrt(n_embd)` on
676    // the embeddings from arithmetic -- the only non-Gemma graph that
677    // does it (`embeddings_scaled_by_sqrt_n_embd`). Everything else it
678    // needs it already had, and the fixture carries all of it: dual
679    // norms on both blocks, per-head QK norm before RoPE, leading
680    // dense, `exp_probs_b`, one shared expert, sigmoid gating with NO
681    // key (`:29-30`), the NoPE layer from `crate::rope_layers`, and a
682    // window with its own `rope.freq_base_swa`. NEOX RoPE
683    // (llama-model.cpp:2676-2677).
684    "afmoe",
685    // `laguna` (laguna.cpp:110-124,211,246-257): SOFTPLUS, per head OR
686    // per element -- `:112-123` reads the width off the stored tensor
687    // and aborts on any other -- REQUIRED. Two fixtures, one per width:
688    // the M.1 shape (no window, per element, uniform heads) and the
689    // XS.2 shape (window, period 4 dense-first, per head, and
690    // `head_count` as a per-layer ARRAY, which `crate::layer_shapes`
691    // carries). One thing stays refused by name in `loader.rs`, from a
692    // fixture that has it: a window together with a RoPE scaling
693    // (`:48,184-192` run the sliding layers with YaRN off, the Olmo-3
694    // rule). `rope.dimension_count_swa` (`:50`) differing from
695    // `rope.dimension_count` -- a second rotary width -- was the other
696    // and is SERVED since `step35` closed on the same two-valued width
697    // (`ModelConfig::rope_dim_swa`, `crate::swa_geometry`); the
698    // XS.2-shaped fixture that carries it matches libllama. NEOX RoPE
699    // (llama-model.cpp:2676-2677).
700    "laguna",
701    // `mellum` (mellum.cpp:12-17,45-68,108-197): the per-layer
702    // sliding-window ARRAY, honoured -- the scalar overload of
703    // `get_key_or_arr` first, the array overload on its `false`, and
704    // `conversion/mellum.py:28` always writes the array. The one
705    // generic-path graph that honours it, so the fixture's array
706    // [T, T, F, T] deliberately disagrees with the seeded period-4
707    // [T, T, T, F] on two layers and the golden is the file's layout,
708    // not the seed's. Everything else is machinery it already had: NEOX
709    // RoPE (llama-model.cpp:2682), per-head QK norm before RoPE
710    // (`:50-51,120-124`), softmax top-k renormalised (`:186`), the
711    // expert width from its own key (`:5`). A window together with a
712    // RoPE scaling -- `:128-142`, the Olmo-3 rule, and what every real
713    // Mellum2 export declares -- stays refused by name
714    // (`crate::swa_geometry`).
715    "mellum",
716    // tests/per_layer_activation_graphs.rs: `apertus` (apertus.cpp:6-9,
717    // 45-46, 93-96, 129-142), the first architecture whose FFN
718    // activation takes PARAMETERS THAT VARY BY LAYER -- xIELU with four
719    // `n_layer`-long arrays (`xielu.alpha_n`, `.alpha_p`, `.beta`,
720    // `.eps`, no architecture prefix) that `ggml_xielu` folds through
721    // a softplus at graph build. `crate::act_layers` reads them exactly
722    // as `get_key_or_arr` does (an array at `n_layer` length or a
723    // scalar broadcast; a second fixture carries the scalar form and
724    // libllama honours the broadcast), `frink_moe::GluAct::Xielu`
725    // carries one layer's four, and `ModelConfig::layer_ffn_act(il)`
726    // replaced the model-wide `GluAct::from(ffn_activation)` at every
727    // FFN body, so no site can take the activation without saying
728    // which layer's. The FFN is UNGATED like `arcee`'s and takes the
729    // same gate-to-up alias; per-head RMS QK-norm before RoPE; NEOX.
730    // Its optional `attn_q_norm.bias` / `attn_k_norm.bias` are created
731    // and never read upstream (`crate::unread_tensors`, measured). No
732    // fused Metal kernel spells xIELU, so every Metal launch refuses
733    // it through `ModelConfig::model_ffn_act`.
734    "apertus",
735    "step35",
736    // tests/gated_attention_graphs.rs: `spark2_5` (Spark-2.5 1.7B),
737    // the first architecture closed against the pin moved on
738    // 2026-09-19. Its one blocker was the per-head sigmoid attention
739    // gate, which is `crate::attn_gate`'s existing pair with the
740    // tensor REQUIRED (`src/models/spark2-5.cpp:41,97-105`); the
741    // fixture carries the window ARRAY with its own RoPE base, the
742    // per-layer head counts that size the gate, and a full-attention
743    // layer in the middle of sliding ones.
744    "spark2_5",
745    // tests/no_rope_layer_graphs.rs: `maple` (Maple-20B), the second
746    // row closed against the pin moved on 2026-09-19. Its one blocker
747    // was the per-layer RoPE gate -- `maple.cpp:88` rotates the
748    // sliding layers and not the full ones, `RopeLayers::SlidingOnly`
749    // -- and the fixture carries the window array, the per-layer
750    // expert widths, the per-head QK norm and the clamp arrays beside
751    // it, with layer 2 the unrotated one.
752    "maple",
753    // tests/granite_swa_graphs.rs: `granite_swa` (Granite 4.1), the
754    // third row closed against the moved pin. Its blockers were two
755    // per-layer tables: the `expert_used_count` ARRAY, which the
756    // loader reads scalar-or-array since the pin moved, and
757    // `attention.rope_pattern`, the FIRST upstream graph that lets the
758    // file say which layers rotate (`RopeLayers::FileMask`). The
759    // fixture's rope pattern and window array disagree about which
760    // layer is special, so a loader that read one into the other is
761    // caught.
762    "granite_swa",
763    // tests/muse_glimmer_graphs.rs: `muse-glimmer`, the fourth row
764    // closed against the moved pin. Two norm facts no other
765    // architecture has -- a weightless RMS on the EMBEDDINGS and a
766    // post-norm epsilon that is a literal in the graph rather than the
767    // model's key -- on top of four tables that each gained one name.
768    "muse-glimmer",
769    // tests/hrm_text_graphs.rs: `hrm_text` (DFM Mimir 1B), the fifth
770    // row closed against the moved pin and the first decoder here with
771    // TWO residual streams. `crate::hrm` holds them and
772    // `crate::layer_loops::LayerLoops::Hrm` is the schedule that says
773    // which stack a logical layer runs and which stream it writes.
774    "hrm_text",
775    // tests/attn_temperature_graphs.rs: `mistral3` (mistral3.cpp:5,
776    // 14-17, 153-156), every Ministral-3 export. Its one blocker was
777    // the PER-POSITION ATTENTION TEMPERATURE, `attention.temperature_scale`,
778    // which llama-graph.cpp:163-167 turns into `log(floor(pos /
779    // floor_scale) + 1) * scale + 1` per token and the graph multiplies
780    // into Q after RoPE; `crate::attn_temperature` is the seam, with
781    // the census (three graphs of 155 build the input, this the only
782    // generic-path one) and the floor resolved as `llama-model.cpp:
783    // 1164-1165` resolves it -- `context_length` first, the YaRN key
784    // over it -- which the second fixture measures. Two corrections
785    // to its verdict: the graph is either dense or MoE on every layer
786    // with NO leading-dense split and NO shared expert (`:64-84` create
787    // `_shexp` only under an `n_ff_shexp` its hparams never set, and
788    // no graph line reads them); and `rope.scaling.yarn_log_multiplier`
789    // (`:9`) adjusts a YaRN MAGNITUDE term frink turned out not to
790    // apply at all -- `crate::yarn_magnitude`, evidenced on two more
791    // fixtures with the factor at 4. NORM RoPE, `1/sqrt(head_dim)`.
792    "mistral3",
793    // tests/router_input_graphs.rs: `smallthinker`, NEW CODE on the
794    // ROUTER OPERAND -- `smallthinker.cpp:111` computes the router
795    // logits from `inpL`, the raw layer input before `attn_norm` and
796    // before attention, and `:151-161` passes them into `build_moe_ffn`
797    // as a precomputed `probs` with a NULL `ffn_gate_inp`. Four graphs
798    // of 155 pass `probs_in` (measured, `crate::router_input`); this is
799    // the only one on the generic path whose operand is not the normed
800    // FFN input the experts read. `RouterInput::RawLayerInput`, captured
801    // in ONE function (`Decoder::router_operand`) where each host body
802    // applies `attn_norm`; the GPU router paths refuse it through
803    // `gpu_router_matches_host_routing`. Its experts are `LLM_FFN_RELU`
804    // (`:158`) with a REAL gate -- `ggml_reglu_split`, `relu(gate) *
805    // up`, `FfnActivation::Reglu` -- which is NOT `arcee`'s ungated
806    // `relu(up)^2`; the one graph that passes it (`uses_reglu`). `:8`
807    // pins `n_swa = 4096` over whatever window the file declares
808    // (`swa_window_override`; libllama's logits are byte-identical for
809    // a declared 3 and a declared 4096, measured). NoPE on `il % 4 ==
810    // 0` from the `n_no_rope_layer_step` default (`crate::rope_layers`),
811    // everything rotated without a window (`:18`). Sigmoid or softmax
812    // gating from `expert_gating_func` (`conversion/smallthinker.py:
813    // 27-30`), `norm_w = true` literal, no shared expert, NEOX RoPE.
814    // Three fixtures: the window-declared shape, the no-window shape,
815    // and a hand-written `sliding_window_pattern = 2` with
816    // `rope.freq_base_swa` that pins the SWA period reading the key
817    // while the NoPE step stays the literal 4.
818    "smallthinker",
819    // tests/sub_norm_graphs.rs: `bitnet`, NEW CODE on the two norms
820    // INSIDE the blocks. `bitnet.cpp:24,36` require `attn_sub_norm`
821    // `{n_embd}` and `ffn_sub_norm` `{n_ff}`; `:101-106` RMS-norm the
822    // attention output BEFORE `wo` (the other side of that matmul from
823    // Gemma's `post_attention_norm`), and `:127-141` call `build_ffn`
824    // with a NULL down projection, norm the `silu(gate) * up` product,
825    // and apply `ffn_down` by hand. One graph of 155 has either tensor
826    // (measured, `crate::sub_norms`). `ModelConfig::block_sub_norms` is
827    // the one fact: the loader REQUIRES the pair on it, every fused
828    // Metal launch refuses on it, and the arithmetic sits in the one
829    // attention tail (`attn_out_to_residual_rows`) and the one dense
830    // FFN row body (`frink_moe::run_expert_sub_normed`, which shares
831    // its gate/up half with `run_expert` and cannot reach the fused
832    // on-device SwiGLU). No `output` tensor (`:14-17,164`: the LM head
833    // is `tok_embd`), `rope.scaling.type = linear` at factor 1
834    // (`conversion/bitnet.py:19-20`), NEOX RoPE (llama-model.cpp:2625),
835    // plain SwiGLU, `1/sqrt(head_dim)`. Its optional per-projection
836    // `.scale` tensors (`:27-43`), which llama.cpp multiplies in and the
837    // current converter no longer writes, are REFUSED by name
838    // (`crate::weight_scales`) from a fixture that carries them and
839    // whose libllama logits differ from the unscaled file's (measured).
840    "bitnet",
841    // tests/split_kv_head_dim_graphs.rs: `mimo2` (MiMo-V2-Flash), NEW
842    // CODE on a V HEAD WIDTH THAT DIFFERS FROM THE K HEAD WIDTH --
843    // `head_dim: 192, v_head_dim: 128` in every real export
844    // (`conversion/mimo.py:154`), `mimo2.cpp:47-48,132-140,152-154`
845    // sizing and viewing K and V separately and `wo` at `n_embd_head_v
846    // * n_head` (`:52`). `crate::kv_head_dims` is the seam: fourteen
847    // converters write `value_length`, three write it apart from
848    // `key_length`, one on this engine (measured). `ModelConfig::
849    // v_head_dim` is the one value; `KvCache` / `PagedKvStore` size V by
850    // it, `causal_gqa_attention_row` -- ONE kernel now for the plain,
851    // windowed, softcapped and sink-bearing arms, which were three
852    // copies -- and the batched prefill kernel accumulate over it, the
853    // projection check and the fused-QKV cut read it, and every fused
854    // Metal launch, the CUDA resident hook, the slot file and the KV
855    // block file refuse a model whose two widths differ. Its second
856    // half, `attention.value_scale` (`:14-17,180-183`, 0.707 on every
857    // export), is `crate::attn_value_scale`: one reader of 155,
858    // applied after `wo` in the one attention tail. Everything else the
859    // row needs had landed: the per-layer `head_count_kv` array, the
860    // per-layer window array with `rope.freq_base_swa`, sinks by
861    // tensor, NextN blocks inside `block_count`, sigmoid gating with
862    // `exp_probs_b` and `expert_weights_scale`, dense-or-MoE per layer
863    // by tensor presence, partial NEOX RoPE. `mimo2.cpp:227` passes the
864    // SIGMOID literal into `build_moe_ffn`, so the key is never read
865    // (`GATING_LITERAL_ARCHITECTURES`, measured over every call). Three
866    // fixtures: the converter's fused `attn_qkv` (K rows at 12, V rows
867    // at 8), the split spelling, and the same file without the value
868    // scale.
869    "mimo2",
870    // tests/llama4_graphs.rs: `llama4` (Llama 4 Scout 17B-16E, Maverick
871    // 17B-128E), NEW CODE on the CHUNKED window: `llama4.cpp:13-14`
872    // set `LLAMA_SWA_TYPE_CHUNKED` at a literal 8192 on the branch
873    // every export takes, and `llama-hparams.h:419-425` mask every key
874    // before the query's own chunk, so a query at `p` sees `p % 8192 +
875    // 1` positions where a sliding layer sees a constant. One graph of
876    // 140 sets the type (`crate::chunked_swa`); the row's other three
877    // facts each landed on a seam that existed with a per-layer gate:
878    // the literal temperature 0.1 / 8192 / 1.0 on the layers that do
879    // NOT rotate (`:15-17,175-176`, `attn_temperature::
880    // LITERAL_ATTN_TEMPERATURE`), a weightless per-head RMS on Q and K
881    // AFTER RoPE on the layers that do, for every expert count but 128
882    // (`:43,182-188`, `crate::weightless_qk_norm`), and the interleave
883    // step the TENSOR LOADER honours (`:64`, unlike ERNIE's,
884    // `moe_interleave::INTERLEAVE_STEP_HONOURED_BY_LOADER`) with a
885    // shared expert at `n_ff_exp` on the MoE layers, SIGMOID from a
886    // literal with `norm_w = false` (`:228-230`). A declared window of
887    // ZERO (`:8-11`, the converter's spelling for an all-full-attention
888    // MobileLLM) is refused by name because libllama aborts on it
889    // (llama-graph.cpp:159), and zero experts because `:49-51` throw.
890    // Two fixtures: 16 experts at step 2 and 128 experts at step 1
891    // with a separate `output.weight` (no QK norm).
892    "llama4",
893    // tests/cohere2moe_graphs.rs: `cohere2moe` (Cohere2 MoE, the 49-layer
894    // 30B-A3B), the `cohere2` graph -- the shared-norm parallel
895    // residual, a REQUIRED window and `logit_scale`, NORM RoPE -- with
896    // routed experts on three rows: a layer rotates when it slides OR
897    // sits in the dense prefix (`cohere2moe.cpp:177-179,192`,
898    // `RopeLayers::SlidingOrLeadingDense`); `(moe_out + shexp) * 0.5`
899    // on a layer with a shared expert (`:248-260`,
900    // `parallel_dense_ffn::SHARED_EXPERT_SUM_SCALE`); the norm FUNCTION
901    // from which epsilon key the file carries (`:4-11,166`,
902    // `norm::NORM_BY_RMS_EPS_KEY`: LayerNorm for every real export, RMS
903    // under a nonzero `layer_norm_rms_epsilon`). Sigmoid when the gating
904    // key is absent, `expert_weights_norm` / `_scale` read, the
905    // per-layer window array, an MTP block skipped. Four fixtures:
906    // LayerNorm, RMS, the MTP block (libllama byte-identical to the
907    // trunk's golden), softmax with `norm_w = true`.
908    "cohere2moe",
909    // tests/layer_loop_graphs.rs: `nanbeige`, NEW CODE on RUNNING THE
910    // SAME PHYSICAL LAYERS MORE THAN ONCE. `nanbeige.cpp:6-12` read
911    // `num_loops` / `skip_loop_final_norm`, `:19-31` set `n_layer_all =
912    // n_phys * n_loops` and replicate the per-layer arrays, `:69-73`
913    // alias `layers[i + j * n_phys] = layers[i]`, and `:167-175` norm
914    // the residual with `output_norm` after every pass but the last
915    // unless the flag skips it. One graph of 155 reads either key
916    // (measured, `crate::layer_loops`). The weights are shared and the
917    // KV is not, and the seam says that rather than copying weights:
918    // `Decoder::layers` stays physical, `ModelConfig::n_layers` is the
919    // logical count every KV cache and per-layer table is sized by,
920    // `Decoder::layer_for(l)` / `physical_index(l)` are the ONE mapping
921    // the three host bodies, the gpt-oss side table and the residency
922    // plan go through, and the loop norm sits at the end of BOTH FFN
923    // bodies so every caller gets it. The fused Metal launches refuse a
924    // looped model (one `l` for weights and KV). Everything inside a
925    // pass is plain Llama (NORM RoPE, `LlamaModel` converter). Three
926    // fixtures: two passes over two layers with the loop norm, the same
927    // with `skip_loop_final_norm`, and `num_loops = 1`, which is the
928    // plain path every real export without looping takes.
929    "nanbeige",
930    // tests/skip_stream_graphs.rs: `talkie`, NEW CODE on FOUR things,
931    // each one graph of 155 (measured). No norm weights: every
932    // `build_norm` is `(x, nullptr, nullptr, LLM_NORM_RMS)` (`talkie.cpp:
933    // 50,68,90,110,137`) -- `NormOp::RmsNoParams`, the RMS twin of
934    // OLMo-1's `LayerNormNoParams`, through the same `NormFunction`
935    // table, so no site loads a tensor the file does not have. A
936    // per-head SCALAR Q gain (`attn_q_norm` is `{1, n_head}`, `:26`)
937    // applied AFTER RoPE with a weightless per-head K norm beside it
938    // (`:82-91`) -- `QkNormStyle::PerHeadScalar`, decided by
939    // architecture because the weight's length is ambiguous with
940    // `head_dim`. The embedding skip stream: the embeddings normed
941    // before layer 0 (`:50`) and added into every layer's output times
942    // `layer_output_scale` (`:123-126`) -- `crate::skip_stream`, one
943    // `bool` for both halves, the norm at the ONE embedding site and the
944    // add at the end of BOTH FFN bodies. And the two `{1}` companions
945    // its converter writes (`conversion/talkie.py:26-31`),
946    // `attn_output.scale` / `ffn_down.scale`, multiplied onto `wo` and
947    // `down` as `build_lora_mm` multiplies them -- `AttnWeights::o_scale`
948    // / `MoeWeights::down_scale`, the two `crate::weight_scales` serves
949    // for any architecture, the rest still refused. `logit_scale`
950    // REQUIRED and multiplied (`:5,141`; `MultiplierSupport::TALKIE`, the
951    // `grok` use). Every fused Metal launch refuses the model. Two
952    // fixtures: the converter's shape with the gains, and the same file
953    // without them, whose golden differs.
954    "talkie",
955    // tests/parallel_dense_ffn_graphs.rs: a dense SiLU FFN sized
956    // `{n_embd, n_embd}` on EVERY layer (`arctic.cpp:38-42`) summed with
957    // the routed experts (`:154`), and the routed branch -- router and
958    // experts -- reading `ffn_norm_exps(inpSA)`, the layer INPUT under
959    // a second norm (`:45,135-152`), while the dense half reads
960    // `ffn_norm(ffn_inp)` (`:118-132`). `crate::parallel_dense_ffn`
961    // (two rows, `grok` the other) and `RouterInput::NormedLayerInput`
962    // (one row). `norm_w = true` literal, softmax,
963    // `expert_weights_scale` read by nothing (`:3-14`; a second fixture
964    // declares it and libllama's logits are byte-identical). NORM RoPE
965    // (llama-model.cpp:2588). Every fused Metal MoE launch refuses the
966    // model (shared experts on every layer, a non-default router
967    // operand).
968    "arctic",
969    // tests/glm4moe_graphs.rs: GLM-4.5 / GLM-4.5-Air / GLM-4.6. Plain
970    // GQA with Q/K/V biases (`glm4-moe.cpp:62`), an OPTIONAL per-head
971    // Q/K RMSNorm before RoPE (`:68-71,175-182`, the 355B variant),
972    // NEOX RoPE (llama-model.cpp:2700), and its pre-FFN norm stored as
973    // `blk.N.post_attention_norm` with no `ffn_norm` (`:75,215`;
974    // `norm_sites::PRE_FFN_NORM_IS_POST_ATTENTION_NORM`). The FFN is
975    // DeepSeek-V3's: a leading dense block, sigmoid routing with
976    // `exp_probs_b`, `expert_weights_norm` and `expert_weights_scale`
977    // read from the file (`:13-17`), a shared expert `n_ff_exp *
978    // n_expert_shared` wide (`:96-104`), summed with the routed output
979    // (`:252`). NextN blocks inside `block_count` are skipped
980    // (`crate::mtp_blocks`). Two fixtures: the 355B shape with the Q/K
981    // norms and the Air shape without. A file whose
982    // `rope.dimension_sections` declare M-RoPE (a GLM-4.5V text tower)
983    // rotates NEOX here, which is what M-RoPE computes on text
984    // positions (measured byte-identical; `crate::mrope`).
985    "glm4moe",
986    // tests/glm4_graphs.rs: GLM-4-0414 (9B, 32B), GLM-Z1, GLM-OCR.
987    // Plain GQA with Q/K/V biases (`glm4.cpp:42`), NORM RoPE over the
988    // first half of each head (`partial_rotary_factor = 0.5`,
989    // llama-model.cpp:2699), Gemma-2's `post_attention_norm` and
990    // `post_ffw_norm` in Gemma-2's slots (`:144-148,166-169`) beside the
991    // ordinary `attn_norm` / `ffn_norm` (`:41,48`), a FUSED SwiGLU `ffn_up`
992    // of `{n_embd, 2 * n_ff}` with no gate (`:50,158-163`, the Phi-3
993    // split), NextN blocks inside `block_count` for GLM-OCR (`:8,54-64`,
994    // `crate::mtp_blocks`), a tied lm_head when `output` is absent. A
995    // GLM-4.1V text tower's `rope.dimension_sections` is REFUSED
996    // (`crate::mrope`): llama.cpp rotates that file M-RoPE over weights
997    // the converter permuted to NEOX, and its logits differ from the
998    // plain file's by 0.72 (measured).
999    "glm4",
1000    // tests/biased_layer_norm_graphs.rs: the two rows of the old
1001    // "LayerNorm-with-bias group" that needed only the norm
1002    // (`BIASED_LAYER_NORM`, `NormOp::LayerNormBias`). `orion`
1003    // (Orion-14B): a Llama whose every norm is `build_norm(x, w, b,
1004    // LLM_NORM)` (`orion.cpp:63-66,104-107,127-130`), NEOX RoPE with no
1005    // `rope.dimension_count` and no `rope.freq_base` in the file. `nemotron`
1006    // (Nemotron-4, Minitron): the same norm (`nemotron.cpp:71-74,111-114,
1007    // 136-139`), the ungated ReLU-squared FFN (`:118-123`), partial NEOX
1008    // RoPE, `rope.scaling.type` `none` or `linear`; its OPTIONAL
1009    // `attn_output.bias` / `ffn_up.bias` / `ffn_down.bias` (`:31,40-41`)
1010    // are refused as unread when a file carries them.
1011    "orion",
1012    "nemotron",
1013    // tests/proj_bias_graphs.rs: the three rows of the old
1014    // "LayerNorm-with-bias group" whose other blocker was the projection
1015    // biases (`crate::proj_bias`: `attn_output.bias`, `ffn_up.bias`,
1016    // `ffn_down.bias`, all REQUIRED). `starcoder2` (StarCoder2-3B/7B/15B):
1017    // the biased LayerNorm, Q/K/V biases, an ungated GELU FFN
1018    // (`FfnActivation::GeluUngated`, `starcoder2.cpp:125-131`), NEOX RoPE.
1019    // `codeshell` (CodeShell-7B): the same shape with a partial rotary
1020    // (`codeshell.cpp:26,81-95`). `jais2` (Jais-2): the biased LayerNorm,
1021    // Q/K/V biases, the ungated ReLU-squared FFN (`jais2.cpp:130-136`),
1022    // NEOX RoPE, a tied lm_head when `output` is absent (`:16-19`).
1023    "starcoder2",
1024    "codeshell",
1025    "jais2",
1026    // tests/stablelm_graphs.rs: `stablelm` (StableLM-2-1.6B, StableLM-3B-
1027    // 4E1T), the sixth row of the old group, on the same
1028    // `NormOp::LayerNormBias` with the OPTIONAL `ffn_norm.bias`
1029    // (`stablelm.cpp:39`) required beside its weight, Q/K/V biases
1030    // through `create_tensor_qkv`, partial NEOX RoPE, SwiGLU. Two shapes
1031    // behind the same string are refused by name: a layer with no
1032    // `ffn_norm` is the PARALLEL residual (`:129-138`,
1033    // `crate::parallel_residual`) and a layer with `attn_q_norm` applies
1034    // a per-head LAYERNORM (`:34-35,84-97`, `crate::qk_layer_norm`);
1035    // StableLM-2-12B has both. `use_parallel_residual` is read by
1036    // nothing in the graph and ignored here as there (measured).
1037    "stablelm",
1038    // tests/parallel_residual_graphs.rs: the PARALLEL residual
1039    // (`crate::parallel_residual`). `gptneox` (Pythia, GPT-NeoX-20B):
1040    // `x + attn(ln1(x)) + ffn(ln2(x))` under `use_parallel_residual`
1041    // (`gptneox.cpp:5,143-166`) and the sequential form under `false`
1042    // (`:167-195`), both matched; the biased LayerNorm, a fused
1043    // `attn_qkv` with its bias, REQUIRED `attn_output.bias` and FFN
1044    // biases (`crate::proj_bias`), the ungated GELU FFN, a partial NEOX
1045    // rotary, no `head_count_kv` in the file. `plamo` (PLaMo-13B): a
1046    // Llama whose FFN reads the vector attention read (`plamo.cpp:
1047    // 64,97-98,111-112`), one RMSNorm per layer, GQA 8:1, NEOX.
1048    "gptneox",
1049    "plamo",
1050    // tests/command_r_graphs.rs: `command-r` (Command-R 35B, Aya-23).
1051    // `command-r.cpp:68` is `build_norm(inpL, attn_norm, NULL, LLM_NORM)`,
1052    // the weighted LayerNorm without a bias `dbrx` gave its caller
1053    // (`WEIGHTED_LAYER_NORM`); `:106-119` the shared-norm parallel
1054    // residual (`crate::parallel_residual`); `:137-138` a `logit_scale`
1055    // MULTIPLY on the logits (`crate::scalar_multipliers`, the `grok`
1056    // use, optional); a tied lm_head (`:21`, `TENSOR_DUPLICATED`), NORM
1057    // RoPE, `rope.scaling.type = none` written by its converter.
1058    // Command-R+ (64 layers) carries the per-head LayerNorm QK norm
1059    // `:28-31` REQUIRE at that depth and is refused by name from a
1060    // 64-layer fixture libllama runs (`crate::qk_layer_norm`).
1061    "command-r",
1062    // tests/falcon_graphs.rs: `falcon` (Falcon-7B / 40B / 180B).
1063    // `falcon.cpp:71-74,124-135` the shared-norm parallel residual over
1064    // the biased LayerNorm; `:35-36,79-85` the OPTIONAL `attn_norm_2`
1065    // that Falcon-40B carries, which norms the layer input FOR
1066    // ATTENTION while `attn_norm` keeps feeding the FFN -- the two-norm
1067    // arm with the names crossed (`norm_sites::
1068    // ATTN_NORM_2_FEEDS_ATTENTION`, per layer); `:38` a fused
1069    // `attn_qkv` with no bias, multi-query at 7B; `:127-131` the
1070    // ungated GELU with no biases; NEOX over the whole head; `output`
1071    // optional. Both shapes matched.
1072    "falcon",
1073    // tests/phi2_graphs.rs: `phi2` (Phi-2, Phi-1.5). `phi2.cpp:67,108,
1074    // 116-117` the shared-norm parallel residual over the biased
1075    // LayerNorm; `:30` Q/K/V biases through `create_tensor_qkv` (split
1076    // or fused, both matched); `:33,36,39` REQUIRED `attn_output.bias`,
1077    // `ffn_down.bias`, `ffn_up.bias` (`crate::proj_bias`); `:108-114`
1078    // the ungated GELU; `:22,136` an `output.bias` on the LM head,
1079    // REQUIRED, added right after the head (`Decoder::output_bias`);
1080    // `rope.dimension_count = partial_rotary_factor * head_dim`, NEOX.
1081    "phi2",
1082    // tests/cohere2_graphs.rs: `cohere2` (Command-R7B, Command-A).
1083    // `command-r.cpp` with a window: `cohere2.cpp:78` the weighted
1084    // LayerNorm without a bias, `:120-134` the shared-norm parallel
1085    // residual, `:14,153-154` `logit_scale` REQUIRED and multiplied,
1086    // `:4-7,13` `swa_type = STANDARD`, period 4 seeded and overridable by
1087    // the scalar key, the window REQUIRED (refused when absent,
1088    // `swa_geometry::window_required`), `:9-12` the sliding layers' base
1089    // following the model's, `:72,91` ONLY the sliding layers rotated
1090    // (`rope_layers::SlidingOnly`, the `exaone-moe` rule the first
1091    // census missed), a tied lm_head, NORM RoPE, no biases.
1092    "cohere2",
1093    // tests/phimoe_graphs.rs: `phimoe` (Phi-3.5-MoE-instruct). `phi3`'s
1094    // graph (`models.h:632`) on `phimoe.cpp`'s tensors: the RMSNorm with
1095    // a bias at every site (`:20-21,28-29,35-36`, `NormOp::RmsBias`),
1096    // Q/K/V biases through `create_tensor_qkv`, `attn_output.bias` and
1097    // `output.bias` REQUIRED (`crate::proj_bias`), softmax top-2
1098    // routing renormalised (`phi3.cpp:153-163`), LongRoPE's
1099    // `rope_factors_long` / `_short` pair with `rope.scaling.attn_factor`,
1100    // NEOX. `phimoe.cpp:3-10` read no window key, so the
1101    // `attention.sliding_window` every export writes is dead metadata
1102    // (`swa_window_override`, the `phi3` answer; libllama `n_swa = 0`,
1103    // measured).
1104    "phimoe",
1105    // tests/position_embd_graphs.rs: `gpt2` (GPT-2) and `starcoder`
1106    // (StarCoder, SantaCoder), ONE graph (`gpt2.cpp` and `starcoder.cpp`
1107    // differ in `head_count_kv 1` and a size table): the biased
1108    // LayerNorm, a fused `attn_qkv` with its bias, REQUIRED
1109    // `attn_output.bias` and FFN biases, the ungated GELU, a sequential
1110    // residual, `output` tied when absent, and `position_embd.weight`
1111    // `{n_embd, n_ctx_train}` ADDED to the token embedding before layer 0
1112    // (`:19,74-77`) with no `ggml_rope` anywhere (`crate::position_embd`,
1113    // `rope_layers::RopeLayers::Never`).
1114    "gpt2",
1115    "starcoder",
1116    // tests/alibi_graphs.rs: the four ALiBi rows (`crate::alibi`), no
1117    // rotation (`rope_layers::RopeLayers::Never`), the bias `slope_h *
1118    // (p_key - p_query)` on every score. `refact.cpp:12` (the literal 8;
1119    // RMSNorm, split Q/K/V, SwiGLU, multi-query), `bloom.cpp:18` (the
1120    // literal; the biased LayerNorm on the embeddings and every site, a
1121    // fused `attn_qkv` with bias, the required projection biases, the
1122    // ungated GELU), `mpt.cpp:6` (`attention.max_alibi_bias`; the
1123    // weighted LayerNorm, its biases and `position_embd` optional, the
1124    // ungated GELU, `clamp_kqv`), `jais.cpp:5` (the key; the biased
1125    // LayerNorm, the required projection biases with `ffn_gate.bias`,
1126    // SwiGLU). Baichuan-13B is the same seam on a row that was audited
1127    // for the 7B: `baichuan.cpp:11-14` at 40 layers.
1128    "refact",
1129    "bloom",
1130    "mpt",
1131    "jais",
1132    // tests/minimax_m2_graphs.rs: `minimax-m2` (MiniMax-M2, 230B MoE).
1133    // `minimax-m2.cpp:26,30-31,96-106,131-141`: plain GQA, ONE RMSNorm
1134    // over the whole Q projection and one over K (`attn_q_norm` is
1135    // `n_embd_head_k * n_head` wide), partial NEOX RoPE (`n_rot 64` of
1136    // `head_dim 128`), one SiLU MoE on every layer with `exp_probs_b`,
1137    // `norm_w = true` and the gating function from the key (SIGMOID on
1138    // every real export; the default aborts upstream). No dense layer,
1139    // no shared expert, no biases; `expert_weights_scale` is never read
1140    // by its hparams. Its refusal had said "a fixture away" for a week
1141    // while the fixture sat in `tests/fixtures/`.
1142    "minimax-m2",
1143    // tests/minimax_01_graphs.rs: `minimax-01` (MiniMax-Text-01). The
1144    // lightning-attention block (`crate::lightning`) on the layers
1145    // `attention.recurrent_layers` / `full_attention_interval` name
1146    // (`minimax-01.cpp:11-17`), plain GQA with partial NEOX RoPE
1147    // elsewhere, a softmax MoE on every layer, and the pre-norm
1148    // residual topology (`crate::normed_residual`) its REQUIRED
1149    // `residual_scale` multiplies.
1150    "minimax-01",
1151    // tests/lfm2_graphs.rs: `lfm2` (LFM2-350M / 700M / 1.2B / 2.6B), the
1152    // first HYBRID row on the generic path. `lfm2.cpp:9-11` marks a
1153    // layer recurrent when `n_head_kv(il) == 0`, and `:192-208` is ONE
1154    // residual topology for both kinds: `attn_norm`, the short
1155    // convolution (`crate::shortconv`, `AttnShape::ShortConv`) or GQA,
1156    // the residual add, `ffn_norm`, SwiGLU. The attention layers have a
1157    // PER-HEAD RMS QK norm (`{n_embd_head_k}`, :74-75), NEOX RoPE
1158    // (llama-model.cpp:2666), a fused or split QKV; the final norm is
1159    // stored as `token_embd_norm` (`norm_sites::
1160    // OUTPUT_NORM_UNDER_EMBEDDING_NAME`); `output` tied when absent.
1161    // Four fixtures: split, the converter's fused `attn_qkv`, a separate
1162    // `output.weight`; the fourth declares a window and is REFUSED by
1163    // name (lfm2.cpp:24-29 windows the attention layers alone).
1164    "lfm2",
1165    // tests/lfm2_graphs.rs: `lfm2moe` (LFM2-8B-A1B, LFM2-24B-A2B) is
1166    // `lfm2`'s graph (`models.h:1899`) with `leading_dense_block_count`
1167    // dense layers and a sigmoid MoE on the rest, `exp_probs_b` REQUIRED
1168    // (`lfm2moe.cpp:8,38-47`), `norm_w = true` (lfm2.cpp:118); the
1169    // gating function comes from the key, which the converter writes
1170    // as SIGMOID (`conversion/lfm2.py:109`). `expert_weights_scale` is
1171    // read by nothing in its hparams (the fixture declares 2.5 and the
1172    // golden is unscaled).
1173    "lfm2moe",
1174    // tests/pangu_embedded_graphs.rs: `pangu-embedded` (openPangu-
1175    // Embedded-1B / 7B), a decoder LLM that had been filed as an
1176    // embedding model from its name. `pangu-embed.cpp` is `llama.cpp`'s
1177    // graph with a REQUIRED `attn_output.bias` (`:37`), NEOX RoPE,
1178    // `n_rot == n_embd_head` (`:59`), fused or split QKV, `output` tied
1179    // when absent. Three fixtures: split, fused, separate `output`.
1180    "pangu-embedded",
1181    // tests/granite_hybrid_graphs.rs: `granitehybrid` (Granite-4.0-H
1182    // Micro / Tiny / Small) and its frink alias. `granite.cpp`'s four
1183    // multipliers and optional biases with a MAMBA-2 block on the
1184    // zero-KV layers (`granite-hybrid.cpp:17-19,163`; `crate::mamba2`,
1185    // `AttnShape::Mamba2`, the state as `RecurrentState` beside the
1186    // layer's cache), dense or MoE with the shared expert, and
1187    // `rope.scaling.finetuned = false` (every real export) rotating
1188    // nothing. Three fixtures: NoPE dense, rotated dense (Bamba's
1189    // shape), NoPE MoE with the shared expert.
1190    "granitehybrid",
1191    "granite-hybrid",
1192    // tests/nemotron_h_graphs.rs: `nemotron_h` (Nemotron-H 8B / 47B /
1193    // 56B, Nemotron-3 Nano dense). Every layer ONE block -- Mamba-2
1194    // (`n_head_kv == 0 && n_ff == 0`), attention (`n_ff == 0`, no RoPE,
1195    // optional `attn_output.bias`) or the ungated ReLU-squared FFN
1196    // (optional biases) -- under `attn_norm` with one residual add
1197    // (`nemotron-h.cpp:9-11,143-158`). Three fixtures: plain, the three
1198    // optional biases, a separate `output.weight`.
1199    "nemotron_h",
1200    // tests/nemotron_h_graphs.rs: `nemotron_h_moe` (Nemotron-3 Nano
1201    // 30B-A3B). The same layers with the FFN layer a sigmoid MoE
1202    // (`nemotron-h.cpp:206-231`: the gating function a LITERAL, the
1203    // router bias REQUIRED, `expert_weights_norm` / `_scale` from the
1204    // file) of UNGATED ReLU-squared experts, plus an ungated
1205    // ReLU-squared shared expert; the gate is aliased to `up` on both
1206    // as the dense ungated FFN's is. `moe_latent_size` (Nemotron-3
1207    // Super) is refused by name.
1208    "nemotron_h_moe",
1209    // tests/falcon_h1_graphs.rs: `falcon-h1` (Falcon-H1 0.5B to 34B).
1210    // Attention and the Mamba-2 block IN PARALLEL on every layer, both
1211    // reading `attn_norm(x)`, summed before the one residual add
1212    // (`falcon-h1.cpp:137-161`); NEOX RoPE; `ssm_norm` optional (`:70`);
1213    // `attn_output.bias` created and never read (`:76,154`,
1214    // `crate::unread_tensors`); `ffn_norm` under the two-argument
1215    // `LLM_TN` spelling (`:80`, no `.weight`). Every multiplier is folded
1216    // into the weights by the converter. Three fixtures: plain, without
1217    // `ssm_norm`, a separate `output.weight`.
1218    "falcon-h1",
1219    // tests/mamba_graphs.rs: `jamba` (AI21 Jamba-v0.1 / 1.5): the
1220    // Mamba-1 block (`crate::mamba1`, `mamba-base.cpp:4-148`, with the
1221    // REQUIRED dt / B / C norms, `jamba.cpp:49,52-53`) where
1222    // `head_count_kv` is 0, attention with no RoPE elsewhere (`:98`),
1223    // dense or MoE per layer by the router's presence (`:89-101,152`;
1224    // softmax, `norm_w = false`, `:164`). `mamba` (Mamba-130M to 2.8B,
1225    // FalconMamba-7B: `ssm.dt_b_c_rms`, the weightless dt / B / C
1226    // norms) and `mamba2` (Mamba-Codestral-7B): every layer the block,
1227    // no attention, no FFN, head_dim 0 (`layer_shapes::PURE_RECURRENT`).
1228    "jamba",
1229    "mamba",
1230    "mamba2",
1231    // tests/plamo2_graphs.rs: `plamo2` (PLaMo-2 1B / 2B / 8B). PLaMo-2's
1232    // own SSM block (`crate::plamo2_ssm`: Mamba-1's dt / B / C path,
1233    // B-C-dt order, REQUIRED norms, feeding Mamba-2's per-head scan;
1234    // z / x interleaved per head) where the KV count is zero, attention
1235    // with the per-head QK RMSNorm with a distinct row per head
1236    // (`QkNormStyle::PerHeadDistinct`) elsewhere.
1237    "plamo2",
1238    // tests/qwen35_graphs.rs: `qwen35` (Qwen3.5 0.8B to 27B). The gated
1239    // delta net (`crate::gdn`: `qwen35.cpp:236-317` over
1240    // `delta-net-base.cpp:289-365`, V heads TILED over K heads) on the
1241    // layers `attention.recurrent_layers` / `full_attention_interval`
1242    // name (`:17-24`), gated full attention elsewhere (`:186-234`: the
1243    // gate interleaved in `wq`, per-head QK norm, partial IMROPE over
1244    // `rope.dimension_sections`, NEOX on text positions), the pre-FFN
1245    // norm stored as `post_attention_norm` (`:65,146-148`), SwiGLU,
1246    // `nextn_predict_layers` skipped as an MTP block. Three fixtures:
1247    // the interval, the array, a separate `output.weight`.
1248    "qwen35",
1249    // tests/qwen35_graphs.rs: `qwen35moe` (Qwen3.5-35B-A3B and up), the
1250    // same layers with `qwen2moe`'s FFN on every one
1251    // (`qwen35moe.cpp:98-107,496-538`: softmax, `norm_w = true`, a
1252    // shared expert scaled by `sigmoid(ffn_gate_inp_shexp . x)`).
1253    "qwen35moe",
1254    // tests/qwen35_graphs.rs: `qwen3next` (Qwen3-Next-80B-A3B), the
1255    // same layers with the V heads GROUPED over the K heads
1256    // (`qwen3next.cpp:521-539`, `HeadMap::Grouped`), beta and alpha in
1257    // one `ssm_ba` projection (`:96,422-436`, `BetaAlpha::Fused`) and
1258    // plain NEOX RoPE (`:282-291`). The legacy fused `ssm_in` is refused
1259    // by name.
1260    "qwen3next",
1261];
1262
1263/// Is this architecture's use of the shared generic path backed by
1264/// evidence?
1265pub fn is_audited_generic(arch: &str) -> bool {
1266    AUDITED_GENERIC_GQA.contains(&arch)
1267}
1268
1269/// Architectures whose layers have **no pre-attention norm and no
1270/// pre-FFN norm at all**: the post-norm-only residual topology.
1271///
1272/// ```text
1273/// ffn_inp = x       + post_attn_norm(attn(x))
1274/// out     = ffn_inp + post_ffn_norm(ffn(ffn_inp))
1275/// ```
1276///
1277/// Not a family resemblance -- the two graphs were read side by side
1278/// and are the same statement for statement. `src/models/olmo2.cpp`
1279/// creates only `attn_q_norm`, `attn_k_norm`, `attn_post_norm` and
1280/// `ffn_post_norm` per layer (:45-52) and reads the raw residual at
1281/// both sublayers (`cur = inpL` at :92, `build_ffn(ffn_inp, ...)` at
1282/// :169), norming each branch's OUTPUT before its residual add
1283/// (:160-165, :177-182). `src/models/exaone4.cpp` is the same list
1284/// (:60-67) and the same four lines (:118, :159, :152-155, :166-169).
1285///
1286/// Both are refused unless [`AUDITED_GENERIC_GQA`] names them, and
1287/// `crate::norm::NormOp` is the one implementation they share.
1288/// Adding a third name here means having read a third `*.cpp`: this
1289/// list decides whether `loader.rs` demands `blk.N.attn_norm.weight`
1290/// from a file, so a wrong entry is a load that fails or a norm that
1291/// silently disappears.
1292pub const POST_NORM_ONLY_ARCHITECTURES: &[&str] = &["olmo2", "exaone4"];
1293
1294/// Does this architecture read the raw residual at both sublayers?
1295/// See [`POST_NORM_ONLY_ARCHITECTURES`].
1296pub fn is_post_norm_only(arch: &str) -> bool {
1297    POST_NORM_ONLY_ARCHITECTURES.contains(&arch)
1298}
1299
1300/// Architectures that normalise with a **non-parametric LayerNorm** --
1301/// subtract the mean, divide by the standard deviation, no learned
1302/// weight and no bias -- at every norm site.
1303///
1304/// `olmo` (OLMo-1), and llama.cpp has no second one. `olmo.cpp:27-35`
1305/// creates Q/K/V, `attn_output` and gate/up/down and NOT ONE norm
1306/// tensor, and its graph is `build_norm(x, NULL, NULL, LLM_NORM, il)`
1307/// at :65-67 (pre-attention), :104-106 (pre-FFN) and :128-130 (final).
1308///
1309/// It is pre-norm like `llama`, so this is orthogonal to
1310/// [`POST_NORM_ONLY_ARCHITECTURES`]: the difference is the norm
1311/// FUNCTION, not the residual wiring, and a name cannot be on both
1312/// lists (`loader.rs`'s
1313/// `the_norm_slot_and_function_lists_cannot_contradict`).
1314///
1315/// **This list will not grow, and that is a measured claim rather than
1316/// an expectation.** Every `build_norm` call in all of llama.cpp's
1317/// `src/models/*.cpp` graphs was scanned for a null weight argument:
1318/// three calls pass one to `LLM_NORM`, and all three are `olmo.cpp`.
1319/// `talkie.cpp` passes a null weight to `LLM_NORM_RMS` at five sites,
1320/// which is a non-parametric RMSNorm -- a different function, and a row
1321/// this list does not serve.
1322///
1323/// The LayerNorm *function* with a learned weight is a different list,
1324/// [`WEIGHTED_LAYER_NORM`], and it exists now because `dbrx` gave it a
1325/// caller.
1326pub const NON_PARAMETRIC_LAYER_NORM: &[&str] = &["olmo"];
1327
1328/// Does this architecture normalise without any learned parameters?
1329/// See [`NON_PARAMETRIC_LAYER_NORM`].
1330pub fn uses_non_parametric_layer_norm(arch: &str) -> bool {
1331    NON_PARAMETRIC_LAYER_NORM.contains(&arch)
1332}
1333
1334/// Architectures that normalise with a **non-parametric RMSNorm** --
1335/// `build_norm(x, nullptr, nullptr, LLM_NORM_RMS, il)` -- at every norm
1336/// site: no `attn_norm`, `ffn_norm` or `output_norm` tensor in the file.
1337///
1338/// The RMS twin of [`NON_PARAMETRIC_LAYER_NORM`], and measured the same
1339/// way: every `build_norm` call with a null weight across all 155
1340/// graphs is `olmo.cpp` (three, `LLM_NORM`) and `talkie.cpp` (five,
1341/// `LLM_NORM_RMS`: the embeddings at `:50`, `attn_norm` at `:68`, the K
1342/// norm at `:90`, `ffn_norm` at `:110`, the final norm at `:137`).
1343/// `NormOp::RmsNoParams` is the function; `crate::skip_stream` is the
1344/// rest of `talkie`.
1345///
1346/// **Re-measured 2026-09-19, when the pin moved to `5b59b83`, and the
1347/// answer CHANGED**: two graphs that landed upstream in the six weeks
1348/// since the last census pass a null weight to `LLM_NORM_RMS` too --
1349/// `hrm-text.cpp` at three sites (`:107,144,162`) and
1350/// `muse-glimmer.cpp` at one (`:69`, the embeddings). So the function
1351/// is no longer one architecture's, and `talkie` is no longer the
1352/// hoped-for lone row; both new ones refuse for OTHER reasons today
1353/// (`capability::NEOX_ROPE_TRIAGED`, `NORM_ROPE_TRIAGED`) and neither
1354/// is admitted here, because a name in this list is a promise that
1355/// every norm site of that architecture is served, which nobody has
1356/// checked for either. `muse-glimmer`'s is also the first WEIGHTLESS
1357/// norm at the EMBEDDING site, where `crate::norm_sites`' row is
1358/// `bloom`'s weighted one.
1359pub const NON_PARAMETRIC_RMS_NORM: &[&str] = &["talkie", "hrm_text"];
1360
1361/// See [`NON_PARAMETRIC_RMS_NORM`].
1362pub fn uses_non_parametric_rms_norm(arch: &str) -> bool {
1363    NON_PARAMETRIC_RMS_NORM.contains(&arch)
1364}
1365
1366/// Architectures that normalise with a **LayerNorm with a learned
1367/// weight and no bias** -- `build_norm(x, w, NULL, LLM_NORM, il)` -- at
1368/// every norm site.
1369///
1370/// `dbrx`: `src/models/dbrx.cpp:4` reads `LLM_KV_ATTENTION_LAYERNORM_EPS`
1371/// (not the RMS one) and the graph passes `LLM_NORM` with a weight and
1372/// a null bias at all three sites -- `:69-71` pre-attention, `:110-112`
1373/// pre-FFN (on `attn_out_norm`, its pre-FFN tensor; see
1374/// `crate::norm_sites`) and `:140-142` final. `dbrx.cpp:29,34,23`
1375/// create the three weights and no bias tensor at all.
1376///
1377/// The variant is `crate::norm::NormOp::LayerNorm`. It was deliberately
1378/// not written alongside the parameterless one, because every row that
1379/// needed it refused for more than the norm; `dbrx` needed two more
1380/// things and both were one implementation each (`crate::clamp_kqv`,
1381/// `crate::norm_sites`), which is what made it worth landing.
1382///
1383/// **What this list does NOT close**, so nobody adds a name on the
1384/// strength of "it is LayerNorm too": the `nemotron` / `orion` /
1385/// `stablelm` / `codeshell` / `jais2` / `starcoder` / `starcoder2` /
1386/// `phimoe` group all create `*_norm.bias` as REQUIRED and `build_norm`
1387/// adds it after the multiply. That is the `LayerNorm(w, b)` variant,
1388/// [`BIASED_LAYER_NORM`], which arrived on 2026-09-12 when `orion` and
1389/// `nemotron` turned out to need nothing else; six of the group are on
1390/// it now and `starcoder` / `phimoe` still refuse for something on top.
1391///
1392/// The second caller of THIS variant is `command-r` (Command-R 35B):
1393/// `command-r.cpp:68,127` pass `attn_norm` / `output_norm` with a NULL
1394/// bias to `LLM_NORM`, over the shared-norm parallel residual
1395/// (`crate::parallel_residual`) with a `logit_scale` MULTIPLY
1396/// (`crate::scalar_multipliers`); `tests/command_r_graphs.rs`. The
1397/// third is `cohere2` (Command-R7B), the same graph with a window
1398/// (`cohere2.cpp:78,147`); `tests/cohere2_graphs.rs`.
1399pub const WEIGHTED_LAYER_NORM: &[&str] = &["dbrx", "command-r", "cohere2", "cohere2moe", "mpt"];
1400
1401/// Does this architecture normalise with a weighted LayerNorm?
1402/// See [`WEIGHTED_LAYER_NORM`].
1403pub fn uses_weighted_layer_norm(arch: &str) -> bool {
1404    WEIGHTED_LAYER_NORM.contains(&arch)
1405}
1406
1407/// Architectures that normalise with a **LayerNorm with a learned
1408/// weight AND bias** -- `build_norm(x, w, b, LLM_NORM, il)` -- at every
1409/// norm site, the weights and the biases all REQUIRED.
1410///
1411/// The `(w, b)` variant [`WEIGHTED_LAYER_NORM`] had named as having no
1412/// caller. It has two now, and they are the two rows of the old
1413/// "LayerNorm-with-bias group" that need NOTHING ELSE of the generic
1414/// decoder (`NormOp::LayerNormBias`, `tests/biased_layer_norm_graphs.rs`):
1415///
1416/// - `orion` (Orion-14B): `orion.cpp:17-18,24-25,30-31` create the six
1417///   tensors and `:63-66,104-107,127-130` pass each pair to `LLM_NORM`;
1418///   the rest is a Llama with NEOX RoPE (llama-model.cpp's NEOX group),
1419///   no `rope.dimension_count` and no `rope.freq_base` in the file
1420///   (`conversion/orion.py:13-37` writes neither).
1421/// - `nemotron` (Nemotron-4, Minitron): `nemotron.cpp:18-19,25-26,33-34`
1422///   the same six, plus the ungated ReLU-squared FFN `arcee` already
1423///   serves (`uses_relu_sqr`), partial NEOX RoPE, and OPTIONAL
1424///   `attn_output.bias` / `ffn_up.bias` / `ffn_down.bias` (`:31,40-41`,
1425///   `TENSOR_NOT_REQUIRED`) that a file carrying them leaves UNREAD
1426///   here, which `assert_every_tensor_consumed` refuses.
1427///
1428/// Three more closed the same day once `crate::proj_bias` served the
1429/// REQUIRED `attn_output.bias` / `ffn_up.bias` / `ffn_down.bias` that
1430/// had been their other blocker: `starcoder2` (`starcoder2.cpp:23,35,44`,
1431/// an ungated GELU FFN), `codeshell` (`codeshell.cpp:24,31,39`, the
1432/// same with a partial rotary) and `jais2` (`jais2.cpp:20,30,44`, the
1433/// ReLU-squared FFN); `tests/proj_bias_graphs.rs`.
1434///
1435/// `stablelm` followed (`stablelm.cpp:20-21,27-28,38-39`; the pre-FFN
1436/// pair is `TENSOR_NOT_REQUIRED`, and its absence is the shared-norm
1437/// parallel residual `crate::parallel_residual` serves), with its
1438/// per-head LayerNorm QK norm refused by name
1439/// (`crate::qk_layer_norm`); `tests/stablelm_graphs.rs`. `gptneox`
1440/// (`gptneox.cpp:57-58,63-64,72-73`, all six REQUIRED) followed on the
1441/// parallel residual's other arm; `tests/parallel_residual_graphs.rs`.
1442/// `falcon` (`falcon.cpp:20-21,32-33`, plus the OPTIONAL `attn_norm_2`
1443/// pair at `:35-36`) followed it; `tests/falcon_graphs.rs`. `phi2`
1444/// (`phi2.cpp:19-20,27-28`) followed on `output.bias`;
1445/// `tests/phi2_graphs.rs`.
1446///
1447/// The two the group still holds, each for something ELSE on top of
1448/// this norm (the norm is done for both): `starcoder` a learned
1449/// `position_embd` with no RoPE; `phimoe` an `output.bias` on the LM
1450/// head and LongRoPE. `tests/attn_bias.rs` pins both as refused with
1451/// the bias named.
1452pub const BIASED_LAYER_NORM: &[&str] = &[
1453    "orion",
1454    "nemotron",
1455    "starcoder2",
1456    "codeshell",
1457    "jais2",
1458    "stablelm",
1459    "gptneox",
1460    "falcon",
1461    "phi2",
1462    "gpt2",
1463    "starcoder",
1464    "bloom",
1465    "jais",
1466];
1467
1468/// See [`BIASED_LAYER_NORM`].
1469pub fn uses_biased_layer_norm(arch: &str) -> bool {
1470    BIASED_LAYER_NORM.contains(&arch)
1471}
1472
1473/// Architectures that normalise with an **RMSNorm with a learned weight
1474/// AND bias** -- `build_norm(x, w, b, LLM_NORM_RMS, il)` -- at every
1475/// norm site, all REQUIRED: `phimoe` (Phi-3.5-MoE), whose tensors are
1476/// `phimoe.cpp:20-21,28-29,35-36` and whose graph is `phi3`'s
1477/// (`phi3.cpp:99-102,137-139,174-177` pass the bias; `phi3` never
1478/// creates one). Measured: `grep -B3 LLM_NORM_RMS src/models/*.cpp |
1479/// grep norm_b` is `phi3` (this row's graph), `chameleon` (passes NULL),
1480/// and `deepseek32` / `glm-dsa` / `rwkv6qwen2` / `arwkv7` on other
1481/// engines. [`crate::norm::NormOp::RmsBias`]; `tests/phimoe_graphs.rs`.
1482pub const BIASED_RMS_NORM: &[&str] = &["phimoe"];
1483
1484/// See [`BIASED_RMS_NORM`].
1485pub fn uses_biased_rms_norm(arch: &str) -> bool {
1486    BIASED_RMS_NORM.contains(&arch)
1487}
1488
1489/// How the generic `Decoder` / `ModelConfig::from_gguf` path treats a
1490/// GGUF architecture string.
1491#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1492pub enum ArchPath {
1493    /// Standard GQA (+ optional MoE) decoder; RoPE layout is known.
1494    GenericGqa { rope: RopeLayout },
1495    /// In-repo test fixtures (`ferroxtest*`) -- not a real model
1496    /// family.
1497    ///
1498    /// The `ferrox` spelling is DELIBERATE and is the one thing the
1499    /// 2026-09-19 rename to Frink did not touch: these are
1500    /// `general.architecture` VALUES written inside committed binary
1501    /// GGUF fixtures (`tests/fixtures/frink_real_*.gguf`), and a GGUF
1502    /// string is length-prefixed, so renaming them means regenerating
1503    /// the fixtures and the goldens that go with them. A wire value is
1504    /// not branding; it is data that has to match what the file says.
1505    TestFixture { rope: RopeLayout },
1506    /// Real architecture, but must not be loaded through the generic
1507    /// GQA decoder (wrong attention / residual math).
1508    DedicatedOnly { reason: &'static str },
1509    /// In the llama.cpp inventory but out of Frink scope for now.
1510    Deferred { reason: &'static str },
1511}
1512
1513/// Load-time resolved profile for one GGUF `general.architecture` string.
1514#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1515pub struct ArchProfile {
1516    pub gguf_name: &'static str,
1517    pub scope: ArchScope,
1518    pub family: DecoderFamily,
1519    pub memory: MemoryKind,
1520    pub rope: RopeLayout,
1521    pub path: ArchPath,
1522    /// Default QK-norm style when norm tensors are present; loader may
1523    /// refine from tensor length.
1524    pub qk_norm: QkNormStyle,
1525    /// For an UNAUDITED [`ArchPath::GenericGqa`] row: how far it is from
1526    /// running, read against llama.cpp's own graph. `None` on audited
1527    /// rows (which run) and on rows still in [`TRIAGE_PENDING`].
1528    pub triage: Option<UnauditedTriage>,
1529}
1530
1531impl ArchProfile {
1532    /// Attach a triage verdict to a catalog row. Private on purpose:
1533    /// verdicts are data of the catalog, not something a caller supplies.
1534    fn triaged(mut self, class: TriageClass, blocker: &'static str) -> Self {
1535        self.triage = Some(UnauditedTriage { class, blocker });
1536        self
1537    }
1538}
1539
1540fn prof(
1541    name: &'static str,
1542    scope: ArchScope,
1543    fam: DecoderFamily,
1544    mem: MemoryKind,
1545    rope: RopeLayout,
1546    path: ArchPath,
1547    qk: QkNormStyle,
1548) -> ArchProfile {
1549    ArchProfile {
1550        gguf_name: name,
1551        scope,
1552        family: fam,
1553        memory: mem,
1554        rope,
1555        path,
1556        qk_norm: qk,
1557        triage: None,
1558    }
1559}
1560
1561fn gqa_norm(name: &'static str) -> ArchProfile {
1562    prof(
1563        name,
1564        ArchScope::TextGeneration,
1565        DecoderFamily::StandardGqa,
1566        MemoryKind::KvGqa,
1567        RopeLayout::Norm,
1568        ArchPath::GenericGqa {
1569            rope: RopeLayout::Norm,
1570        },
1571        QkNormStyle::WholeVector,
1572    )
1573}
1574
1575fn gqa_neox(name: &'static str) -> ArchProfile {
1576    prof(
1577        name,
1578        ArchScope::TextGeneration,
1579        DecoderFamily::StandardGqa,
1580        MemoryKind::KvGqa,
1581        RopeLayout::Neox,
1582        ArchPath::GenericGqa {
1583            rope: RopeLayout::Neox,
1584        },
1585        QkNormStyle::WholeVector,
1586    )
1587}
1588
1589fn dedicated(name: &'static str, reason: &'static str) -> ArchProfile {
1590    prof(
1591        name,
1592        ArchScope::TextGeneration,
1593        DecoderFamily::Dedicated,
1594        MemoryKind::KvGqa,
1595        RopeLayout::Norm,
1596        ArchPath::DedicatedOnly { reason },
1597        QkNormStyle::WholeVector,
1598    )
1599}
1600
1601fn deferred_scope(name: &'static str, scope: ArchScope, reason: &'static str) -> ArchProfile {
1602    prof(
1603        name,
1604        scope,
1605        DecoderFamily::StandardGqa,
1606        MemoryKind::None,
1607        RopeLayout::Neox,
1608        ArchPath::Deferred { reason },
1609        QkNormStyle::WholeVector,
1610    )
1611}
1612
1613/// Triaged rows of the generic **Norm**-RoPE group, with the llama.cpp
1614/// line that decides each verdict. Consumed by
1615/// [`architecture_catalog`]; a name here must not also appear in the
1616/// untriaged list above it or in [`TRIAGE_PENDING`], which
1617/// `catalog_has_unique_names` and
1618/// `every_unaudited_generic_architecture_is_triaged_or_listed_as_pending`
1619/// between them enforce.
1620const NORM_ROPE_TRIAGED: &[(&str, TriageClass, &str)] = &[
1621    // --- Landed upstream AFTER the 2026-08-04 pin, read on 2026-09-19
1622    // when the pin moved to `5b59b83` (792 commits, 15 new graphs).
1623    // None of the four below has a fixture yet; each says what it
1624    // needs, measured against the graph, not guessed from the name.
1625    // `granite_swa` (Granite 4.1) was HERE for one PR, NEW CODE on two
1626    // small per-layer tables, and is audited now: the
1627    // `expert_used_count` ARRAY is read scalar-or-array by the loader
1628    // (a fix that came out of the same pin move), and
1629    // `attention.rope_pattern` is `RopeLayers::FileMask` -- the first
1630    // upstream graph that lets the FILE say which layers rotate, one
1631    // line of 155 (`rope_layers::ROPE_PATTERN_READERS`). Everything
1632    // else it needed was served and each table gained one name: the
1633    // four Granite multipliers, the window ARRAY, the REQUIRED
1634    // per-layer sinks, the optional projection biases and the
1635    // `attention.scale` override. `tests/granite_swa_graphs.rs`.
1636    (
1637        "graniteswitch",
1638        TriageClass::NewCode,
1639        "a per-token ADAPTER selection. `src/models/granite-switch.cpp` threads an `adapter_ids` \
1640         tensor through the layer body so each token's FFN reads a different expert \
1641         adapter, which is a second indexing dimension the MoE layer here does not have \
1642         (frink routes tokens to experts; this routes them to adapters OF an expert). \
1643         Its other half is small and named: `:9-12` read `rope.scaling.finetuned` and fill \
1644         `rope_pattern` with it, which is `rope_finetuned::unrotated` plus the per-layer \
1645         array `granite_swa` needs",
1646    ),
1647    // `muse-glimmer` was HERE for one PR, NEW CODE on two norm facts,
1648    // and is audited now: the WEIGHTLESS embedding norm is
1649    // `norm_sites::WEIGHTLESS_EMBEDDING_NORM` with `NormOp::
1650    // RmsNoParams` at the site `bloom`'s weighted one already had, and
1651    // the post-norm epsilon literal is `norm::POST_NORM_EPS_LITERAL`
1652    // read through `ModelConfig::post_norm_eps()` at the three host
1653    // post-norm sites, with the fused Metal launches and the CUDA
1654    // prefill refusing a model whose two epsilons differ. The rest was
1655    // served and each table gained one name: the per-element sigmoid
1656    // gate, `RopeLayers::SlidingOnly`, the `logit_scale` multiply with
1657    // the final tanh softcap, and the window pattern read scalar-then-
1658    // array. `tests/muse_glimmer_graphs.rs`.
1659    // `ernie4_5-moe` was HERE, ONE MATCH ARM on
1660    // `{arch}.interleave_moe_layer_step`. Building its fixture found the
1661    // arm is not implementable against a reference: llama.cpp's tensor
1662    // loader (ernie4-5.cpp:49) and its graph (ernie4-5-moe.cpp:64)
1663    // disagree about which layers are MoE, and only the graph has the
1664    // step, so a checkpoint whose interleave interleaves cannot be
1665    // loaded by llama.cpp at all. The arm landed as a REFUSAL
1666    // (`crate::moe_interleave`) and the step every real checkpoint
1667    // carries is audited (`tests/one_match_arm_graphs.rs`), so the row
1668    // is in AUDITED_GENERIC_GQA and carries no verdict.
1669    //
1670    // `granite`, `granitemoe` and the `granite-moe` alias were HERE,
1671    // NEW CODE on the four scalar multipliers. All three are audited
1672    // now: `crate::scalar_multipliers` implements the multipliers ONCE,
1673    // parameterised by architecture, and `tests/granite_family_graphs.rs`
1674    // is the libllama-golden evidence. The `rope_finetuned` half of that
1675    // verdict landed as a REFUSAL rather than an implementation
1676    // (`crate::rope_finetuned`), because llama.cpp runs such a file with
1677    // no rotation at all and frink cannot express that.
1678    //
1679    // `chatglm` was HERE, ONE MATCH ARM on the fused `attn_qkv.bias`.
1680    // The arm landed (`crate::qkv_fused`, which now resolves the
1681    // projections and their biases from ONE decision about which
1682    // spelling the file uses) and is evidenced against libllama in
1683    // `tests/one_match_arm_graphs.rs`, so the row is in
1684    // AUDITED_GENERIC_GQA and carries no verdict.
1685    //
1686    // Its verdict said implementing the arm "closes chatglm and qwen
1687    // together". It did not, and that is the finding: `qwen` needs the
1688    // same bias AND a second, unrelated arm the verdict did not name --
1689    // `qwen.cpp:33-35` sizes every FFN matrix `n_ff/2`, because
1690    // Qwen-1's `intermediate_size` counts gate and up together. `qwen`
1691    // stays refused, with that added to its reason.
1692    // `deci` was HERE, NEW CODE on PER-LAYER SHAPES, and is audited
1693    // now with `openelm` on one seam (`crate::layer_shapes`,
1694    // `tests/per_layer_shape_graphs.rs`). Its three layer kinds are
1695    // `AttnShape::{Gqa, Linear, Absent}` plus `ffn_dim == 0`; the one
1696    // combination llama.cpp's graph handles by discarding a computed
1697    // branch (`deci.cpp:147-149` before `:150-153`) is refused by name
1698    // from a fixture that has it, with the drop MEASURED rather than
1699    // read.
1700    // `olmo` was HERE, NEW CODE on the non-parametric LayerNorm, and is
1701    // audited now: `crate::norm::NormOp::LayerNormNoParams` implements
1702    // the function and `tests/olmo_graphs.rs` carries the fixture. Its
1703    // verdict called the clamp "an optional key nothing here applies";
1704    // that half stayed a REFUSAL rather than an implementation, because
1705    // `llama-graph.cpp:1611-1652` really does clamp Q, K and V and
1706    // `conversion/olmo.py:23-25` really does write the key. See
1707    // `crate::clamp_kqv`.
1708    // `arctic` was HERE, NEW CODE on a PARALLEL dense + MoE layer whose
1709    // MoE branch reads the pre-attention residual, and is audited now
1710    // on two seams (`tests/parallel_dense_ffn_graphs.rs`): the dense FFN
1711    // summed with the experts is `crate::parallel_dense_ffn` -- the
1712    // shared-expert slot under the dense names plus the row's scale on
1713    // the sum, whose second row is Grok-2, refused by name until then --
1714    // and the branch operand `ffn_norm_exps(inpSA)` is
1715    // `RouterInput::NormedLayerInput`, one graph of 155. The verdict had
1716    // said `router_input` "does not reach it" because the operand feeds
1717    // a whole expert bank; it reaches it as a third variant carrying
1718    // that fact (`experts_read_router_operand`).
1719    // `mistral3` was HERE, NEW CODE on the PER-POSITION ATTENTION
1720    // TEMPERATURE, and is audited now: `crate::attn_temperature` is the
1721    // seam and `tests/attn_temperature_graphs.rs` carries five
1722    // fixtures. Its verdict's "leading-dense + MoE + shared expert" was
1723    // wrong on two counts (see the AUDITED entry), and its
1724    // `yarn_log_multiplier` half found that YaRN's magnitude term was
1725    // missing for every architecture (`crate::yarn_magnitude`). The
1726    // reach was measured before a line was written: `llama4` seeds the
1727    // same three constants from literals and gates the multiply on
1728    // its no-RoPE layers (`llama4.cpp:15-17,175-176`), and `deepseek2`
1729    // / `mistral4` read the same key with `attention.temperature_length`
1730    // as the floor (`deepseek2.cpp:46-49`) -- the MLA loader REFUSES
1731    // that by name now, where it used to drop it, because that engine
1732    // has no golden to check an implementation against.
1733    // `nanbeige` was HERE, NEW CODE on running the same physical layers
1734    // more than once, and is audited now: `crate::layer_loops` is the
1735    // seam and `tests/layer_loop_graphs.rs` carries three fixtures.
1736    // The verdict's last sentence was the design: "the copy has no
1737    // home" -- it has one now, and it is a mapping, not a copy. See
1738    // `AUDITED_GENERIC_GQA`.
1739    // `arcee` was HERE, NEW CODE on `UNGATED_RELU_SQR`, and is audited
1740    // now: the FFN is `FfnActivation::ReluSqr` and
1741    // `tests/ungated_ffn_graphs.rs` carries the fixture.
1742    // `plm` was HERE, NEW CODE on `UNGATED_RELU_SQR`'s second half,
1743    // DeepSeek-2 MLA attention on a dense model, and it is on the MLA
1744    // engine now (`DecoderFamily::Mla`, below): the engine gained the
1745    // direct-Q form (`crate::mla_q_proj`), the per-architecture table
1746    // (`crate::mla_arch`) and its FIRST libllama-golden fixture
1747    // (tests/plm_graphs.rs) with it. That row is not in
1748    // AUDITED_GENERIC_GQA because it never ran on the generic path.
1749];
1750
1751/// Shared by the three frink-only alias rows `mistral`, `mixtral` and
1752/// `yi`: the reason none of them is an architecture at all.
1753///
1754/// These were UNKNOWN, and the open question was "what would settle
1755/// it?" -- a real GGUF whose `general.architecture` is literally one of
1756/// the three. **The investigation that settled it (2026-09-10) did not
1757/// find one, and found the reason no such file exists.** Three
1758/// measurements, not readings:
1759///
1760///   1. `grep '"mistral' src/llama-arch.cpp` returns `mistral3` and
1761///      `mistral4` and nothing else; `mixtral` and `yi` return nothing.
1762///      Neither is in gguf-py's `MODEL_ARCH_NAMES` either.
1763///   2. A GGUF written with `general.architecture = "mistral"` (and
1764///      `mixtral`, and `yi`) is REFUSED by libllama with
1765///      `llama_model_load: error loading model: unknown model
1766///      architecture: 'mistral'`. So no golden reference for these rows
1767///      can ever exist, at the evidence standard every audited row in
1768///      this file meets.
1769///   3. The two real checkpoints in `models/` --
1770///      `Mistral-7B-Instruct-v0.2-Q4_K_M.gguf` and
1771///      `Yi-1.5-6B-Chat-Q4_K_M.gguf` -- both declare
1772///      `general.architecture = llama`, which is audited and runs.
1773///
1774/// So the rows are refused as strings rather than triaged as
1775/// architectures, and the refusal says the actionable thing: your file
1776/// is spelled `llama`. Leaving them on the generic path would have kept
1777/// a live hazard: the catalog gave all three NEOX RoPE while `llama`,
1778/// the graph they really are, is in `llama_model_rope_type`'s NORM
1779/// group (llama-model.cpp, the `case LLM_ARCH_LLAMA:` arm), so a file
1780/// spelling `mistral` would have been rotated on the wrong pairs of
1781/// every Q/K head -- the exact defect behind the Llama-3.1-8B
1782/// wrong-logits bug -- and `rope_layout_matches_llama_cpp` cannot see
1783/// it, because its lookup miss on a frink-only name is a `continue`.
1784///
1785/// `phi4` is the same shape and is deliberately NOT changed here: it is
1786/// still `GenericGqa` + UNKNOWN, because unlike these three it names a
1787/// concrete, checkable hypothesis (phi3's fused-QKV graph) that a real
1788/// file would confirm or refute. These three name none.
1789const NO_UPSTREAM_ARCH: &str =
1790    "this is not a GGUF architecture. `mistral`, `mixtral` and `yi` appear in neither      LLM_ARCH_NAMES (src/llama-arch.cpp lists `mistral3` and `mistral4` and nothing else      under that prefix) nor gguf-py's MODEL_ARCH_NAMES, and libllama REFUSES a file      declaring one of them: `unknown model architecture: 'mistral'` -- measured, on a      synthetic llama-shaped file written under each of the three strings. Every real      Mistral, Mixtral and Yi checkpoint converts to `llama` instead, which frink audits      and runs: the two in this repo's own models/ directory      (Mistral-7B-Instruct-v0.2-Q4_K_M.gguf, Yi-1.5-6B-Chat-Q4_K_M.gguf) both declare      `general.architecture = llama`. IF YOUR FILE REALLY SPELLS THIS, it came from a      converter neither engine has read, so its RoPE variant, its norm placement and its      FFN shape are all undetermined and frink will not guess -- re-convert it with      llama.cpp's convert_hf_to_gguf.py and it will load as `llama`. These rows used to sit      on the generic path with NEOX RoPE, while `llama` is in llama_model_rope_type's NORM      group, so such a file would have been rotated on the wrong pairs of every Q/K head";
1791
1792// `UNGATED_RELU_SQR` was here: the verdict `arcee` and `plm` shared,
1793// and after `arcee` closed (2026-09-11) the one that said why `plm` had
1794// not -- DeepSeek-2 MLA attention on a dense model, `plm.cpp:16-19,
1795// 32-36,84-166`, which frink had only inside the dedicated `MlaEngine`,
1796// arch-gated to `deepseek2` / `mistral4`, with no dense ReLU-squared FFN
1797// and no libllama-golden evidence of its own. `plm` closed on that
1798// engine on 2026-09-12 (`crate::mla_arch`, `crate::mla_q_proj`,
1799// tests/plm_graphs.rs), and the same fixture is the engine's first
1800// golden. The FFN half is `uses_relu_sqr` below.
1801
1802/// Triaged rows of the generic **NEOX**-RoPE group. Same rules as
1803/// [`NORM_ROPE_TRIAGED`].
1804const NEOX_ROPE_TRIAGED: &[(&str, TriageClass, &str)] = &[
1805    // --- Landed upstream AFTER the 2026-08-04 pin (see the NORM group).
1806    // `maple` was HERE for one PR, ONE MATCH ARM on the per-layer RoPE
1807    // gate, and is audited now: `src/models/maple.cpp:88` rotates only
1808    // the sliding layers, which is `RopeLayers::SlidingOnly` --
1809    // `cohere2`'s rule, one row of `crate::rope_layers`. The other
1810    // things its verdict listed were already served and each table
1811    // gained one name: the window ARRAY (`crate::swa_layers`), the
1812    // per-layer `expert_feed_forward_length` array
1813    // (`crate::layer_shapes`) and the SwiGLU clamp arrays
1814    // (`crate::act_layers`). `tests/no_rope_layer_graphs.rs`.
1815    // `spark2_5` was HERE for one PR, ONE MATCH ARM on the attention
1816    // gate, and is audited now: `src/models/spark2-5.cpp:41,97-105`
1817    // is `step35`'s corner of `crate::attn_gate` with the tensor
1818    // REQUIRED instead of optional, which is one row of
1819    // `ATTN_GATE_ARCHS`, and the rest of its graph (the window ARRAY
1820    // with `rope.freq_base_swa`, per-layer head counts, a gated GELU
1821    // FFN, NEOX RoPE) was already served. The libllama golden is
1822    // `tests/gated_attention_graphs.rs`; it is the first row closed
1823    // against the MOVED pin, and it took a fixture and an hour, which
1824    // is what a ONE MATCH ARM verdict is supposed to mean.
1825    // `hrm_text` (DFM Mimir 1B) was HERE for one PR, NEW CODE on its
1826    // two-stack cycle schedule, and is audited now. The schedule is a
1827    // second variant of `crate::layer_loops` -- two stacks of `lps`
1828    // blocks replayed over `h * (l + 1)` passes, each pass aliasing
1829    // one of the two -- and the TWO residual streams it recombines at
1830    // every stack boundary are `crate::hrm`, one type the four host
1831    // bodies call rather than four copies of "hold two vectors". Its
1832    // other facts were served or one table row each: weightless RMS
1833    // norms (`NON_PARAMETRIC_RMS_NORM`), a per-element sigmoid gate,
1834    // an `embedding_scale`, and NO `output_norm` tensor at all
1835    // (`norm_sites::NO_OUTPUT_NORM`, because the last stack's own norm
1836    // is the final one). `tests/hrm_text_graphs.rs`.
1837    (
1838        "qwen4exp",
1839        TriageClass::NewCode,
1840        "the largest graph upstream has (`src/models/qwen4exp.cpp`, 1297 lines). A gated delta-net (`:1`, \
1841         `build_layer_attn_linear`, the fourth caller of the helper `crate::gdn` serves \
1842         for the other three) over a hybrid memory INDEX (`llama-memory-hybrid-idx.h`, a \
1843         new memory class), with an attention gate, MoE, and an IMROPE rotation. The \
1844         delta-net half is the seam frink has; the memory index is not, and it decides \
1845         which state a layer reads",
1846    ),
1847    // `mellum` was HERE, NEW CODE on two things. The first -- its
1848    // sliding layers roped with the model's YaRN switched OFF
1849    // (`mellum.cpp:128-142`), the Olmo-3 rule -- is a REFUSAL BY NAME
1850    // in `crate::swa_geometry` for a file declaring both a window and
1851    // a RoPE scaling, which every real Mellum2 export does. The second
1852    // -- the per-layer sliding-window ARRAY that `:12-17` honour and
1853    // `conversion/mellum.py:28` always writes -- is `crate::swa_layers`
1854    // now, and `mellum` is the ONE generic-path architecture whose
1855    // graph honours the array, so it is the row that evidences that
1856    // branch against libllama (`tests/window_array_graphs.rs`). A
1857    // Mellum without a scaling runs; a Mellum2 stops on the first
1858    // thing, by name.
1859    // `talkie` was HERE, NEW CODE on four things, and is audited now:
1860    // the weightless norms are `NormOp::RmsNoParams`, the per-head
1861    // scalar Q gain and weightless K norm are `QkNormStyle::
1862    // PerHeadScalar`, the embedding skip stream is `crate::skip_stream`,
1863    // and the two projection gains its converter writes are the two
1864    // `crate::weight_scales` serves. `tests/skip_stream_graphs.rs`
1865    // carries two fixtures. See `AUDITED_GENERIC_GQA`.
1866    // `mimo2` was HERE, NEW CODE on the split K/V head width, and is
1867    // audited now: `crate::kv_head_dims` is the seam,
1868    // `crate::attn_value_scale` its small second half, and
1869    // `tests/split_kv_head_dim_graphs.rs` carries three fixtures. Its
1870    // verdict had already said the NextN blocks, the window array, the
1871    // sinks and the per-layer shapes were no longer blockers; they were
1872    // not, and the fixture carries all four. See `AUDITED_GENERIC_GQA`.
1873    // `afmoe` was HERE, NEW CODE on the gated attention (`afmoe.cpp:73`)
1874    // and the `sqrt(n_embd)` embedding scale (`:120`). Both are
1875    // implemented -- `crate::attn_gate` and
1876    // `embeddings_scaled_by_sqrt_n_embd` -- and the row is audited on a
1877    // libllama-golden fixture (`tests/gated_attention_graphs.rs`). Its
1878    // sigmoid default for `expert_gating_func` (`:29-30`) had been in
1879    // `SIGMOID_GATING_ARCHITECTURES` since 2026-09-01; the fixture
1880    // declares no gating key so that default is what it measures.
1881    // `apertus` was HERE, NEW CODE on xIELU with four PER-LAYER
1882    // parameter arrays (`apertus.cpp:6-9,132-138`). The arrays are
1883    // `crate::act_layers` (read as `get_key_or_arr` reads them, an
1884    // array at `n_layer` length or a scalar broadcast), the activation
1885    // is `frink_moe::GluAct::Xielu` carrying that layer's four, and
1886    // `ModelConfig::layer_ffn_act(il)` is the one accessor every FFN
1887    // body asks -- the row is audited on a libllama-golden fixture
1888    // (`tests/per_layer_activation_graphs.rs`). The verdict's third
1889    // sentence was wrong: `:50,52` CREATE `attn_q_norm.bias` /
1890    // `attn_k_norm.bias` and `:93,96` pass `NULL` as the bias, so they
1891    // are never read; measured (libllama's logits byte-identical with
1892    // and without them) and recorded in `crate::unread_tensors`.
1893    (
1894        "grovemoe",
1895        TriageClass::NewCode,
1896        "a SECOND bank of experts, not just a scale. src/models/grovemoe.cpp:57-59 creates \
1897         `ffn_gate_chexps` / `ffn_down_chexps` / `ffn_up_chexps` -- `n_expert / \
1898         n_group_experts` \"chunk\" experts with their own width n_ff_chexp -- and the graph \
1899         runs build_moe_ffn TWICE (:137 over the ordinary experts, :153 over the chunk \
1900         experts) before :167 adds `scale(moe_out, expert_group_scale)` to the residual. The \
1901         inventory recorded only the post-sum group scale and called this small; the second \
1902         expert bank with its own routing is the larger half and frink's MoE layer holds \
1903         one bank. Both n_group_experts and expert_group_scale are REQUIRED keys (:6-7). \
1904         QK-norm is before RoPE (:100-109), which is the one thing that would otherwise have \
1905         been a blocker. READ ON 2026-09-12 AGAINST THE REFERENCE MODEL, and not closed \
1906         for a reason the count cannot show: llama.cpp's graph disagrees with \
1907         `modeling_grove_moe.py` in two places. (1) grovemoe.cpp:148-149 sets `cur = \
1908         moe_out` and :152 feeds THAT -- the routed experts' OUTPUT -- into the chunk \
1909         experts, where the reference (`GroveMoeSparseMoeBlock.forward`:369) feeds them \
1910         the same `hidden_states` the routed experts read; upstream PR #15510's own debug \
1911         dump shows `MUL_MAT_ID(ffn_gate_chexps, ffn_moe_out)`. (2) llama-graph.cpp: \
1912         2035-2039 divides the selected expert ids by `n_group_experts` and then gathers \
1913         the weights from the softmax probs AT THE CHUNK INDEX, where the reference (:324, \
1914         :370) gathers them at the ORIGINAL expert index; the two agree only when the \
1915         selected expert's index equals its chunk's. Both are shipped upstream (master \
1916         2026-09) and neither was discussed in the PR. So there is no single graph to \
1917         match: reproducing llama.cpp reproduces a divergence from the model, and matching \
1918         the model has no libllama golden. Refused by name until upstream settles it; the \
1919         reach of the mechanism (a precomputed `probs`) is `crate::router_input`'s census",
1920    ),
1921    // `hunyuan-dense` was HERE, ONE MATCH ARM on the NTK-alpha RoPE
1922    // base rescale. The arm landed (`crate::rope_ntk_alpha`), the
1923    // post-RoPE QK-norm half was already implemented, and both are
1924    // evidenced against libllama in `tests/one_match_arm_graphs.rs`, so
1925    // the row is audited and carries no verdict. Its verdict cited
1926    // `conversion/hunyuan.py:356` as the line that writes
1927    // `{arch}.rope.scaling.alpha` for this architecture; that line is in
1928    // HunyuanVLTextModel, whose model_arch is HUNYUAN_VL. The
1929    // HUNYUAN_DENSE converter (:254-281) does the same arithmetic in
1930    // Python and writes the already-scaled base instead.
1931    // `laguna` was HERE, NEW CODE on the gated attention (`laguna.cpp:124`,
1932    // softplus, per head or per element) and a second rotary width
1933    // (`:50`). The gate is `crate::attn_gate` and the row is audited on
1934    // two libllama-golden fixtures, one per width
1935    // (`tests/gated_attention_graphs.rs`). The second rotary width is a
1936    // REFUSAL by name in `loader.rs` for one day -- a file whose
1937    // `rope.dimension_count_swa` differs from `rope.dimension_count` --
1938    // and is served now (`ModelConfig::rope_dim_swa`, with `step35`);
1939    // a window with a RoPE scaling (`:48,184-192`, the Olmo-3 rule,
1940    // `swa_layers_unscaled_rope`) stays refused, from a fixture that
1941    // has it. Real Laguna-M.1 has neither; real Laguna-XS.2 has both
1942    // and stops at the scaling.
1943    // `step35` was HERE, NEW CODE on its per-layer SwiGLU clamp arrays
1944    // (`step35.cpp:28-29`, applied by llama.cpp's generic
1945    // `build_moe_ffn` / `build_ffn` at `llama-graph.cpp:2146-2164` /
1946    // `:1751-1768`) and its half-width rotary on the full layers
1947    // (`:9`). The clamp is the second body on the per-layer activation
1948    // seam `apertus` opened -- `crate::act_layers::SwigluClamps`, read
1949    // by SITE, `frink_moe::GluAct::SwigluClamped` -- and the width is
1950    // `ModelConfig::rope_dim_swa` (`crate::swa_geometry`, the same
1951    // two-valued `n_rot(il)` that lifted Laguna-XS.2's refusal). Three
1952    // libllama-golden fixtures (`tests/clamped_swiglu_graphs.rs`):
1953    // clamped, unclamped, and with a NextN block. Everything else the
1954    // verdict had crossed off is carried by them rather than assumed.
1955    // `mistral`, `mixtral` and `yi` were HERE, UNKNOWN on
1956    // NO_UPSTREAM_ARCH. The question that verdict asked -- "is there a
1957    // real GGUF spelling one of these?" -- was answered NO, with a
1958    // measurement: libllama refuses all three strings outright. They
1959    // are refused as strings now, not triaged as architectures. See
1960    // NO_UPSTREAM_ARCH.
1961    // `grok` and `dbrx` were HERE, both NEW CODE, and both closed on
1962    // seams that had landed the day before. `grok`'s verdict named
1963    // five hardcoded defaults (`grok.cpp:5-12` -- there are seven at
1964    // this checkout), a `kq_scale = 1.0f` attention with the real
1965    // scale folded into a tanh softcap, and `blk.N.attn_output_norm`
1966    // as an unread tensor name: the defaults are a
1967    // `scalar_multipliers::MultiplierDefaults` variant like MiniCPM's,
1968    // the attention is `attention_scale` plus the existing softcap, and
1969    // the tensor name is a `crate::norm_sites` row. Its Grok-2 shape --
1970    // a dense GELU FFN summed with the MoE at sqrt(2)/2 (`:171-184`)
1971    // -- is refused BY NAME in `loader.rs`, so the row is admitted for
1972    // Grok-1. `dbrx`'s verdict named the weighted LayerNorm, the
1973    // REQUIRED `attention.clamp_kqv`, and `attn_output_norm` as its
1974    // pre-FFN norm: `crate::norm::NormOp::LayerNorm`, `crate::clamp_kqv`
1975    // (which closed `olmo`'s clip_qkv sub-refusal with it) and the
1976    // same `norm_sites` table. See `AUDITED_GENERIC_GQA`.
1977    // `smallthinker` was HERE, NEW CODE on the ROUTER OPERAND, and is
1978    // audited now (`crate::router_input`, `tests/router_input_graphs.rs`).
1979    // Its verdict named three things and all three landed: the router
1980    // reading `inpL` (`RouterInput::RawLayerInput`), the gated
1981    // `LLM_FFN_RELU` experts (`FfnActivation::Reglu`, which the verdict
1982    // called "one match arm" and which needed a variant because
1983    // `ffn_is_ungated` and `layer_ffn_acts` must agree about whether
1984    // the gate is real), and the `n_swa = 4096` pin
1985    // (`swa_window_override`). The reach was measured before a line
1986    // was written and came back with ONE: `grovemoe` also passes a
1987    // precomputed `probs` but routes on the normed FFN input, and the
1988    // two graphs whose operand really differs (`gemma4`, `nemotron-h`)
1989    // are on other engines. See `AUDITED_GENERIC_GQA`.
1990    // `bitnet` was HERE, NEW CODE on the two norms INSIDE the blocks
1991    // (`bitnet.cpp:24,36`), and is audited now: `crate::sub_norms` is
1992    // the seam and `tests/sub_norm_graphs.rs` carries the fixture. Its
1993    // verdict's third sentence, the per-projection `.scale` tensors, is
1994    // a refusal by name now (`crate::weight_scales`) rather than an
1995    // unread-tensor error, and its fourth (no `output` tensor) was
1996    // already served by the tied lm_head. See `AUDITED_GENERIC_GQA`.
1997    // `openelm` was HERE, NEW CODE on PER-LAYER SHAPES, and is audited
1998    // now with `deci` (`crate::layer_shapes`,
1999    // `tests/per_layer_shape_graphs.rs`). The misleading missing-hparam
2000    // message its verdict named is gone with it:
2001    // `layer_shapes::read_u64_per_layer` reads the arrays
2002    // `conversion/openelm.py:57-59` writes.
2003];
2004
2005/// Full inventory keyed by GGUF `general.architecture` string.
2006/// Kept in sync with `.scratch/llama.cpp/src/llama-arch.cpp` `LLM_ARCH_NAMES`.
2007pub fn architecture_catalog() -> &'static [ArchProfile] {
2008    use std::sync::OnceLock;
2009    use ArchScope::*;
2010    use DecoderFamily::*;
2011    use MemoryKind::*;
2012    use QkNormStyle::*;
2013    use RopeLayout::*;
2014
2015    static CAT: OnceLock<Vec<ArchProfile>> = OnceLock::new();
2016    CAT.get_or_init(|| {
2017        let mut v = Vec::with_capacity(160);
2018        // --- Verified / standard GQA (Norm RoPE) ---
2019        //
2020        // `llama` is the only untriaged name left in this group: it is
2021        // audited, so it runs and needs no verdict. Every other
2022        // Norm-RoPE row moved into `NORM_ROPE_TRIAGED` below when it was
2023        // read against llama.cpp's graph.
2024        v.push(gqa_norm("llama"));
2025        // Audited too, each by a libllama-golden fixture -- see
2026        // `AUDITED_GENERIC_GQA` for the arm each one needed and
2027        // `tests/one_match_arm_graphs.rs` for the evidence.
2028        for n in ["bailingmoe", "deepseek", "maincoder"] {
2029            v.push(gqa_norm(n));
2030        }
2031        // Were FIXTURE-AWAY in this group and now have the fixture:
2032        // `tests/fixture_away_graphs.rs`, same evidence standard.
2033        for n in ["baichuan", "ernie4_5", "internlm2", "xverse"] {
2034            v.push(gqa_norm(n));
2035        }
2036        // `ernie4_5-moe` was ONE MATCH ARM in `NORM_ROPE_TRIAGED` and is
2037        // audited now: the step every real checkpoint carries has a
2038        // libllama-golden fixture (`tests/one_match_arm_graphs.rs`) and
2039        // any other step is refused by name (`crate::moe_interleave`).
2040        v.push(gqa_norm("ernie4_5-moe"));
2041        // `chatglm` was the LAST ONE MATCH ARM row anywhere in this
2042        // file. Its arm -- the fused `attn_qkv.bias` -- landed in
2043        // `crate::qkv_fused` and has a libllama-golden fixture
2044        // (`tests/one_match_arm_graphs.rs`), so the class is empty now.
2045        v.push(gqa_norm("chatglm"));
2046        // `nanbeige` was NEW CODE in `NORM_ROPE_TRIAGED` on the layer
2047        // loop (`nanbeige.cpp:19-31`), audited now on
2048        // `crate::layer_loops` (`tests/layer_loop_graphs.rs`). NORM RoPE:
2049        // its converter is `LlamaModel` (`conversion/nanbeige.py:8`) and
2050        // `LLM_ARCH_NANBEIGE` sits in the NORM group, which
2051        // `tests/rope_layout.rs` pins.
2052        v.push(gqa_norm("nanbeige"));
2053        // The Granite family. All three were NEW CODE in
2054        // `NORM_ROPE_TRIAGED` on the four scalar multipliers, which
2055        // `crate::scalar_multipliers` now implements once for all of
2056        // them (`tests/granite_family_graphs.rs`). `granite-moe` is a
2057        // frink-only alias -- `llama-arch.cpp:101` spells it
2058        // `granitemoe` -- and is here rather than anywhere else so it
2059        // cannot be given a different path from the row it aliases.
2060        for n in ["granite", "granitemoe", "granite-moe"] {
2061            v.push(gqa_norm(n));
2062        }
2063        // Granite 4.0 (`granitehybrid`; `granite-hybrid` is the frink
2064        // alias every Granite row carries). `granite-hybrid.cpp` is the
2065        // Granite graph with a Mamba-2 block where `head_count_kv` is 0
2066        // (`crate::mamba2`, `AttnShape::Mamba2`), and its converter
2067        // writes `rope.scaling.finetuned = false` for every export with
2068        // a Mamba layer, so the attention layers rotate NOTHING
2069        // (`crate::rope_finetuned`, `RopeLayers::Never`). Audited on
2070        // tests/granite_hybrid_graphs.rs.
2071        for n in ["granitehybrid", "granite-hybrid"] {
2072            v.push(prof(
2073                n,
2074                TextGeneration,
2075                DecoderFamily::Hybrid,
2076                MemoryKind::Hybrid,
2077                Norm,
2078                ArchPath::GenericGqa { rope: Norm },
2079                WholeVector,
2080            ));
2081        }
2082        // Nemotron-H (`nemotron_h`: Nemotron-H 8B / 47B / 56B, Nemotron-3
2083        // Nano dense). One block per layer -- Mamba-2, attention or an
2084        // ungated ReLU-squared FFN -- under ONE `attn_norm` and one
2085        // residual add (`nemotron-h.cpp:143-158`; `layer_shapes::
2086        // BLOCK_WITHOUT_FFN_KEEPS_ITS_OUTPUT`, `ZeroKvLayer::
2087        // Mamba2UnlessFfn`, `norm_sites::ONE_NORM_PER_LAYER`). Its
2088        // attention never calls `ggml_rope_ext` (`:181-193`): the NEOX
2089        // group entry (llama-model.cpp:2671) is a filler and
2090        // `rope_layers` answers `Never`. Audited on
2091        // tests/nemotron_h_graphs.rs. `nemotron_h_moe` (Nemotron-3 Nano
2092        // 30B-A3B) is the same graph with a sigmoid MoE of UNGATED
2093        // ReLU-squared experts and an ungated shared expert on the FFN
2094        // layers (`:206-231`); its latent variant (`moe_latent_size`,
2095        // Nemotron-3 Super) is refused by name
2096        // (`unsupported_feature_keys`).
2097        for n in ["nemotron_h", "nemotron_h_moe"] {
2098            v.push(prof(
2099                n,
2100                TextGeneration,
2101                DecoderFamily::Hybrid,
2102                MemoryKind::Hybrid,
2103                Neox,
2104                ArchPath::GenericGqa { rope: Neox },
2105                WholeVector,
2106            ));
2107        }
2108        // Falcon-H1 (`falcon-h1`: 0.5B / 1.5B / 3B / 7B / 34B): attention
2109        // AND the Mamba-2 block on EVERY layer, in parallel on the same
2110        // `attn_norm` output, summed before the residual
2111        // (`falcon-h1.cpp:137-161`; `crate::mamba2::
2112        // PARALLEL_WITH_ATTENTION`, `ModelConfig::parallel_ssm`). NEOX
2113        // RoPE (llama-model.cpp:2615). Audited on
2114        // tests/falcon_h1_graphs.rs.
2115        v.push(prof(
2116            "falcon-h1",
2117            TextGeneration,
2118            DecoderFamily::Hybrid,
2119            MemoryKind::Hybrid,
2120            Neox,
2121            ArchPath::GenericGqa { rope: Neox },
2122            WholeVector,
2123        ));
2124        // OLMo-1 was NEW CODE in `NORM_ROPE_TRIAGED` on its
2125        // non-parametric LayerNorm, which `crate::norm::NormOp` now
2126        // implements (`tests/olmo_graphs.rs`). NORM RoPE:
2127        // `llama_model_rope_type` puts LLM_ARCH_OLMO in the
2128        // consecutive-pairs group (llama-model.cpp:2585), which is also
2129        // why `conversion/olmo.py:33-36` permutes q_proj and k_proj the
2130        // way `LlamaModel` does. Its `olmo.attention.clamp_kqv` was
2131        // refused by name and is applied now (`crate::clamp_kqv`),
2132        // since `dbrx` needed the same clamp.
2133        v.push(gqa_norm("olmo"));
2134        // `smollm3` was refused OUTRIGHT, in the "No RoPE at all" group
2135        // below, and it was the only row there whose graph is the plain
2136        // pre-norm llama one. What it needed was a way to say WHICH
2137        // LAYERS ROTATE: `smollm3.cpp:5,69` skip `(il + 1) % 4 == 0`,
2138        // nine layers of a 36-layer SmolLM3-3B, with no GGUF key.
2139        // `crate::rope_layers` says it now, once, for the six
2140        // architectures llama.cpp gates per layer, and
2141        // `tests/no_rope_layer_graphs.rs` is the libllama-golden
2142        // evidence. NORM RoPE: llama-model.cpp puts LLM_ARCH_SMOLLM3 in
2143        // the consecutive-pairs group (:2600).
2144        v.push(gqa_norm("smollm3"));
2145        // `arcee` was NEW CODE in `NORM_ROPE_TRIAGED` on the ungated
2146        // ReLU-squared FFN, which `FfnActivation::ReluSqr` implements
2147        // (`tests/ungated_ffn_graphs.rs`). NORM RoPE: LLM_ARCH_ARCEE is
2148        // in the consecutive-pairs group (llama-model.cpp:2600).
2149        v.push(gqa_norm("arcee"));
2150        // `deci` was NEW CODE in `NORM_ROPE_TRIAGED` on per-layer
2151        // shapes, which `crate::layer_shapes` implements
2152        // (`tests/per_layer_shape_graphs.rs`). NORM RoPE: LLM_ARCH_DECI
2153        // is in the consecutive-pairs group (llama-model.cpp:2576).
2154        v.push(gqa_norm("deci"));
2155        // `mistral3` was NEW CODE in `NORM_ROPE_TRIAGED` on the
2156        // per-position attention temperature, which
2157        // `crate::attn_temperature` implements
2158        // (`tests/attn_temperature_graphs.rs`). NORM RoPE:
2159        // LLM_ARCH_MISTRAL3 is in the consecutive-pairs group
2160        // (llama-model.cpp:2604), which `tests/rope_layout.rs` pins.
2161        v.push(gqa_norm("mistral3"));
2162        // `arctic` was NEW CODE in `NORM_ROPE_TRIAGED` on the parallel
2163        // dense + MoE layer, audited now (`crate::parallel_dense_ffn`,
2164        // `RouterInput::NormedLayerInput`, tests/parallel_dense_ffn_graphs.rs).
2165        // NORM RoPE: llama-model.cpp:2588.
2166        v.push(gqa_norm("arctic"));
2167        // `glm4` was a `dedicated` refusal sent to the GLM-5.2 MLA loader;
2168        // audited now on the generic NORM path (tests/glm4_graphs.rs).
2169        // NORM RoPE: llama-model.cpp:2699 (M-RoPE files refused,
2170        // `crate::mrope`).
2171        v.push(gqa_norm("glm4"));
2172        // `orion` and `nemotron` were DedicatedOnly on their REQUIRED
2173        // LayerNorm biases; audited now on `NormOp::LayerNormBias`
2174        // (tests/biased_layer_norm_graphs.rs). NEOX RoPE:
2175        // llama-model.cpp:2653-2654.
2176        v.push(gqa_neox("orion"));
2177        v.push(gqa_neox("nemotron"));
2178        // The three whose LAST blocker was the projection biases
2179        // (`crate::proj_bias`, tests/proj_bias_graphs.rs). NEOX RoPE:
2180        // llama-model.cpp:2649 (starcoder2), :2652 (codeshell), :2662
2181        // (jais2).
2182        v.push(gqa_neox("starcoder2"));
2183        v.push(gqa_neox("codeshell"));
2184        v.push(gqa_neox("jais2"));
2185        // `stablelm` was DedicatedOnly on its REQUIRED LayerNorm biases;
2186        // audited now for the sequential shape, its parallel residual
2187        // (`crate::parallel_residual`) and per-head LayerNorm QK norm
2188        // (`crate::qk_layer_norm`) refused by name from fixtures libllama
2189        // runs (tests/stablelm_graphs.rs). NEOX RoPE: llama-model.cpp:2624.
2190        v.push(gqa_neox("stablelm"));
2191        // The parallel residual's two arms, each on a real graph
2192        // (`crate::parallel_residual`, tests/parallel_residual_graphs.rs):
2193        // `gptneox` (Pythia, GPT-NeoX-20B) under `use_parallel_residual`
2194        // with two norms, `plamo` (PLaMo-13B) with the one shared norm.
2195        // NEOX RoPE: llama-model.cpp:2651 (gptneox), :2639 (plamo).
2196        v.push(gqa_neox("gptneox"));
2197        v.push(gqa_neox("plamo"));
2198        // `command-r` (Command-R 35B, Aya-23): the shared-norm parallel
2199        // residual over the weighted LayerNorm WITHOUT a bias
2200        // (`WEIGHTED_LAYER_NORM`'s second caller) and a `logit_scale`
2201        // multiply (tests/command_r_graphs.rs). NORM RoPE:
2202        // llama-model.cpp:2582.
2203        v.push(gqa_norm("command-r"));
2204        // `falcon` (Falcon-7B / 40B / 180B): the shared-norm parallel
2205        // residual at 7B and the two-norm one at 40B, whose second norm
2206        // is `attn_norm_2` FOR ATTENTION (`norm_sites::
2207        // ATTN_NORM_2_FEEDS_ATTENTION`), over the biased LayerNorm, a
2208        // fused `attn_qkv` with no bias, the ungated GELU FFN
2209        // (tests/falcon_graphs.rs). NEOX RoPE: llama-model.cpp:2651.
2210        v.push(gqa_neox("falcon"));
2211        // `phi2` (Phi-2, Phi-1.5): the shared-norm parallel residual
2212        // over the biased LayerNorm, Q/K/V biases, `attn_output.bias`
2213        // and the FFN biases, the ungated GELU, and an `output.bias` on
2214        // the LM head (`proj_bias::OUTPUT_BIAS_CREATORS`); partial NEOX
2215        // rotary (tests/phi2_graphs.rs). NEOX RoPE: llama-model.cpp:2636.
2216        v.push(gqa_neox("phi2"));
2217        // `cohere2` (Command-R7B, Command-A): `command-r`'s graph with a
2218        // REQUIRED window whose sliding layers alone are rotated
2219        // (`crate::rope_layers::RopeLayers::SlidingOnly`), the
2220        // `logit_scale` REQUIRED (tests/cohere2_graphs.rs). NORM RoPE:
2221        // llama-model.cpp:2583.
2222        v.push(gqa_norm("cohere2"));
2223        // `phimoe` (Phi-3.5-MoE): `phi3`'s graph on routed experts with
2224        // the biased RMSNorm (`BIASED_RMS_NORM`), `attn_output.bias`
2225        // and `output.bias` (`crate::proj_bias`), LongRoPE, its window
2226        // key dead metadata as `phi3`'s (tests/phimoe_graphs.rs). NEOX
2227        // RoPE: llama-model.cpp:2638.
2228        v.push(gqa_neox("phimoe"));
2229        // `gpt2` and `starcoder` (GPT-2, StarCoder / SantaCoder): one
2230        // graph, the `gptneox` sequential layer with a learned position
2231        // table added to the embeddings and NO rotation
2232        // (`crate::position_embd`, `rope_layers::RopeLayers::Never`;
2233        // tests/position_embd_graphs.rs). The layout here is a filler
2234        // nothing reads: `llama_model_rope_type` answers NONE for `gpt2`
2235        // and NORM for `starcoder`, and neither graph calls `ggml_rope`.
2236        v.push(gqa_norm("gpt2"));
2237        v.push(gqa_norm("starcoder"));
2238        // The ALiBi rows (`crate::alibi`; tests/alibi_graphs.rs): no
2239        // rotation, the bias added to every score. The layout is a
2240        // filler nothing reads. `refact` (Refact-1.6B): RMSNorm, split
2241        // Q/K/V, SwiGLU, multi-query, the literal 8. `bloom` (BLOOM):
2242        // the biased LayerNorm on the embeddings too
2243        // (`norm_sites::EMBEDDING_NORM_ARCHITECTURES`), fused `attn_qkv`
2244        // with bias, the required projection biases, the ungated GELU,
2245        // the literal 8. `mpt` (MPT-7B / 30B): the weighted LayerNorm
2246        // (its biases are all optional and MPT has none), fused
2247        // `attn_qkv`, optional projection biases, the ungated GELU,
2248        // `attention.max_alibi_bias` from the key with `clamp_kqv` and
2249        // an optional `position_embd`. `jais` (Jais-13B / 30B): the
2250        // biased LayerNorm, fused `attn_qkv` with bias, the required
2251        // projection biases INCLUDING `ffn_gate.bias`, SwiGLU, the key.
2252        v.push(gqa_norm("refact"));
2253        v.push(gqa_norm("bloom"));
2254        v.push(gqa_norm("mpt"));
2255        v.push(gqa_norm("jais"));
2256        // Same generic Norm-RoPE path, but READ against llama.cpp's own
2257        // graph -- see [`TriageClass`]. Each row below refuses with its
2258        // class and its blocker instead of the generic
2259        // "nobody has checked this" paragraph.
2260        for (n, class, blocker) in NORM_ROPE_TRIAGED {
2261            v.push(gqa_norm(n).triaged(*class, blocker));
2262        }
2263        for n in [
2264            "olmoe", "qwen2", "qwen2moe",
2265            // llama-model.cpp `llama_model_rope_type`: LLM_ARCH_OPENAI_MOE
2266            // falls in the `return LLAMA_ROPE_TYPE_NEOX` group, and a live
2267            // load of a gpt-oss GGUF prints `rope type = 2` (= NEOX).
2268            // frink had it on the interleaved (NORM) list, which rotates
2269            // the wrong pairs of every Q/K head.
2270            "gpt-oss",
2271            // Same audit, run over every arch at once against
2272            // `llama_model_rope_type`'s NEOX group
2273            // (llama-model.cpp:2613-2683). These 24 were on frink's
2274            // interleaved (NORM) list and reach the generic GQA decoder,
2275            // so every one of them rotated the wrong pairs of every Q/K
2276            // head and answered fluently and wrongly. Pinned by
2277            // `rope_layout_matches_llama_cpp` below; dots1 additionally
2278            // checked end-to-end against llama.cpp's own logits in
2279            // `tests/moe_routing_bias.rs`.
2280            "dots1",
2281            // Audited by libllama-golden fixtures in
2282            // `tests/one_match_arm_graphs.rs`: `hunyuan-moe` needed the
2283            // post-RoPE QK-norm order, `seed_oss` the gpt-oss pre-FFN
2284            // norm slot.
2285            "hunyuan-moe",
2286            "seed_oss",
2287            // `hunyuan-dense` was ONE MATCH ARM in `NEOX_ROPE_TRIAGED`
2288            // and is audited now: the NTK-alpha RoPE base rescale
2289            // (`crate::rope_ntk_alpha`) plus the post-RoPE QK-norm order
2290            // it shares with `hunyuan-moe`, both against libllama's own
2291            // logits.
2292            "hunyuan-dense",
2293            // Were FIXTURE-AWAY and now have the fixture
2294            // (`tests/fixture_away_graphs.rs`). EXAONE 3.x only:
2295            // `exaone4` and `exaone-moe` are different graphs and stay
2296            // in `NEOX_ROPE_TRIAGED` below. `bailingmoe2` is Ling-2.0
2297            // and is unrelated to the NORM-RoPE `bailingmoe` row above.
2298            "exaone",
2299            "bailingmoe2",
2300            "plamo3",
2301            // Were NEW CODE in `NEOX_ROPE_TRIAGED` and are audited now.
2302            // One residual topology, `crate::norm`, shared by both:
2303            // no pre-attention norm and no pre-FFN norm, each branch's
2304            // OUTPUT normed before its residual add. The evidence is
2305            // `tests/post_norm_only_graphs.rs`, one libllama-golden
2306            // fixture each. `olmo2` with a sliding window AND a RoPE
2307            // scaling (Olmo-3) and `exaone4` with 64 layers (the 32B)
2308            // are refused by name in `loader.rs` and are NOT covered by
2309            // these two rows.
2310            "olmo2",
2311            "exaone4",
2312            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on ONE blocker: its
2313            // GLOBAL layers get no RoPE (`exaone-moe.cpp:136,155-161`).
2314            // That is the SAME RULE as `exaone4`'s -- :4 pins
2315            // `swa_type` to STANDARD, which makes `exaone4.cpp:116`'s
2316            // second disjunct false and the two predicates identical --
2317            // so both rows take one implementation,
2318            // `crate::rope_layers`, with a libllama-golden fixture each
2319            // in `tests/no_rope_layer_graphs.rs`. Everything else it
2320            // needed (leading dense, `exp_probs_b`, shared expert,
2321            // sigmoid gating from metadata, a per-head QK-norm) frink
2322            // already had, and its fixture carries all of it rather
2323            // than asserting so. The two things a REAL export carries
2324            // on top -- K-EXAONE's one NextN block inside `block_count`
2325            // (`exaone.py:132,146`) and the window pattern as a bool
2326            // ARRAY (`:84`) that `exaone-moe.cpp:7` never reads -- are
2327            // `crate::mtp_blocks` and `crate::swa_layers`, with a
2328            // fixture carrying both (`tests/window_array_graphs.rs`).
2329            "exaone-moe",
2330            // Was a `DedicatedOnly` bias refusal, not an unaudited row:
2331            // its only dropped bias was the FUSED `attn_qkv.bias`, which
2332            // `crate::qkv_fused` applies now. Qwen-1 only; qwen2 and
2333            // later store the split spelling and were already audited.
2334            "qwen",
2335            // Were NEW CODE in `NEOX_ROPE_TRIAGED` and are audited now,
2336            // each on seams that landed the day before: `dbrx` on the
2337            // weighted LayerNorm (`crate::norm`), the QKV clamp
2338            // (`crate::clamp_kqv`) and the `attn_output_norm` slot
2339            // (`crate::norm_sites`); `grok` on the defaults hook
2340            // (`scalar_multipliers::MultiplierDefaults::Grok`), the
2341            // scale-inside-softcap attention and the same `norm_sites`
2342            // table. NEOX RoPE: llama-model.cpp:2616-2617 put both in
2343            // the `n_rot/2`-offset group. `tests/dbrx_graphs.rs`,
2344            // `tests/grok_graphs.rs`.
2345            "dbrx",
2346            "grok",
2347            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on per-layer shapes,
2348            // audited now with `deci` on `crate::layer_shapes`
2349            // (`tests/per_layer_shape_graphs.rs`). NEOX RoPE:
2350            // llama-model.cpp:2650.
2351            "openelm",
2352            // Were NEW CODE in `NEOX_ROPE_TRIAGED` on the gated
2353            // attention, audited now on `crate::attn_gate`
2354            // (`tests/gated_attention_graphs.rs`). NEOX RoPE:
2355            // llama-model.cpp:2676-2677.
2356            "afmoe",
2357            "laguna",
2358            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on the sliding-window
2359            // ARRAY (`mellum.cpp:12-17`), audited now on
2360            // `crate::swa_layers` (`tests/window_array_graphs.rs`); its
2361            // window-with-YaRN half stays refused by name. NEOX RoPE:
2362            // llama-model.cpp:2682.
2363            "mellum",
2364            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on xIELU's per-layer
2365            // parameter arrays, audited now on `crate::act_layers`
2366            // (`tests/per_layer_activation_graphs.rs`). NEOX RoPE:
2367            // llama-model.cpp:2671.
2368            "apertus",
2369            "step35",
2370            // Was ONE MATCH ARM in `NEOX_ROPE_TRIAGED` for one PR on
2371            // the per-head attention gate, audited now on
2372            // `crate::attn_gate` (`tests/gated_attention_graphs.rs`).
2373            // NEOX RoPE: `llama_model_rope_type` puts
2374            // LLM_ARCH_SPARK2_5 in the NEOX group, which
2375            // `tests/rope_layout.rs` pins.
2376            "spark2_5",
2377            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on the router
2378            // operand (`smallthinker.cpp:111`), audited now on
2379            // `crate::router_input` (`tests/router_input_graphs.rs`).
2380            // NEOX RoPE: llama-model.cpp puts LLM_ARCH_SMALLTHINKER in
2381            // the `LLAMA_ROPE_TYPE_NEOX` group, which
2382            // `tests/rope_layout.rs` pins.
2383            "smallthinker",
2384            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on the two norms
2385            // INSIDE the blocks (`bitnet.cpp:24,36`), audited now on
2386            // `crate::sub_norms` (`tests/sub_norm_graphs.rs`). NEOX
2387            // RoPE: llama-model.cpp:2625.
2388            "bitnet",
2389            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on the split K/V head
2390            // width (`mimo2.cpp:47-48`), audited now on
2391            // `crate::kv_head_dims` (`tests/split_kv_head_dim_graphs.rs`).
2392            // NEOX RoPE: `LLM_ARCH_MIMO2` is in the NEOX group,
2393            // `tests/rope_layout.rs` pins it.
2394            "mimo2",
2395            // Was NEW CODE in `NEOX_ROPE_TRIAGED` on its weightless norms,
2396            // per-head scalar Q gain, skip stream and projection gains,
2397            // audited now (`crate::skip_stream`,
2398            // `tests/skip_stream_graphs.rs`). NEOX RoPE:
2399            // llama-model.cpp:2681.
2400            "talkie",
2401            // Was a `dedicated` refusal on its pre-FFN norm slot; audited
2402            // now (`norm_sites::PRE_FFN_NORM_IS_POST_ATTENTION_NORM`,
2403            // tests/glm4moe_graphs.rs). NEOX RoPE: llama-model.cpp:2700
2404            // (M-RoPE when `rope.dimension_sections` says so, which on
2405            // text positions is the same rotation; `crate::mrope`).
2406            "glm4moe",
2407        ] {
2408            v.push(gqa_neox(n));
2409        }
2410        // `minimax-01` (MiniMax-Text-01, 456B-A45B) was NEW CODE in
2411        // `NEOX_ROPE_TRIAGED` and is audited now. Its recurrent mask is
2412        // the Qwen3.5 one -- the same two keys, read by
2413        // `crate::gdn::recurrent_layers`, with the interval defaulting
2414        // to 8 instead of 4 -- and the BLOCK those layers run is
2415        // lightning attention (`crate::lightning`, `AttnShape::
2416        // Lightning`), whose state is one `head_dim x head_dim` KV per
2417        // head. Two things the tensor shapes do not show, both from
2418        // `minimax-01.cpp:303-309`: the fused `attn_qkv` runs through
2419        // SiLU BEFORE it is split, and it is HEAD-major (`[q|k|v]` per
2420        // head) rather than three blocks. And the residual topology is
2421        // its own (`crate::normed_residual`): each sublayer's pre-norm
2422        // output, times a REQUIRED `residual_scale`, REPLACES the
2423        // stream its branch joins, so the layer input is discarded.
2424        // `tests/minimax_01_graphs.rs`.
2425        v.push(prof(
2426            "minimax-01",
2427            TextGeneration,
2428            DecoderFamily::Hybrid,
2429            MemoryKind::Hybrid,
2430            Neox,
2431            ArchPath::GenericGqa { rope: Neox },
2432            QkNormStyle::WholeVector,
2433        ));
2434        // Triaged NEOX-RoPE rows; see `NORM_ROPE_TRIAGED` above.
2435        for (n, class, blocker) in NEOX_ROPE_TRIAGED {
2436            v.push(gqa_neox(n).triaged(*class, blocker));
2437        }
2438        // --- No RoPE at all -------------------------------------------
2439        //
2440        // `llama_model_rope_type` opens with a `LLAMA_ROPE_TYPE_NONE`
2441        // group, and five of its rows sat on frink's NEOX list once:
2442        // each loaded, ran at full speed, and answered fluently from
2443        // positions the checkpoint never encodes that way. They were
2444        // refused by name here until the position they DO encode was
2445        // served: `gpt2`'s learned table (`crate::position_embd`) and
2446        // the ALiBi bias of `mpt`, `refact`, `bloom` and `jais`
2447        // (`crate::alibi`, whose table also carries Baichuan-13B), with
2448        // `rope_layers::RopeLayers::Never` as the other half of each.
2449        // `tests/rope_layout.rs`'s `LLAMA_NO_ROPE` pins that a row of
2450        // that group reaches the generic path ONLY under `Never`.
2451        // `hrm_text` was NEW CODE in `NEOX_ROPE_TRIAGED` for one PR on
2452        // its two-stack schedule and is audited now
2453        // (`tests/hrm_text_graphs.rs`). NEOX RoPE:
2454        // `llama_model_rope_type` puts LLM_ARCH_HRM_TEXT in the NEOX
2455        // group, which `tests/rope_layout.rs` pins.
2456        v.push(prof(
2457            "hrm_text",
2458            TextGeneration,
2459            StandardGqa,
2460            KvGqa,
2461            Neox,
2462            ArchPath::GenericGqa { rope: Neox },
2463            WholeVector,
2464        ));
2465        // `muse-glimmer` was NEW CODE in `NORM_ROPE_TRIAGED` for one
2466        // PR on its two norm facts and is audited now
2467        // (`tests/muse_glimmer_graphs.rs`). NORM RoPE:
2468        // `llama_model_rope_type` puts LLM_ARCH_MUSE_GLIMMER in the
2469        // NORM group, which `tests/rope_layout.rs` pins. Per-head QK
2470        // norm: `muse-glimmer.cpp:40-41` stores `{n_embd_head_k}`
2471        // weights and `:106-107` apply them per head.
2472        v.push(prof(
2473            "muse-glimmer",
2474            TextGeneration,
2475            StandardGqa,
2476            KvIswa,
2477            RopeLayout::Norm,
2478            ArchPath::GenericGqa {
2479                rope: RopeLayout::Norm,
2480            },
2481            PerHead,
2482        ));
2483        // `granite_swa` was NEW CODE in `NORM_ROPE_TRIAGED` for one PR
2484        // on its two per-layer tables and is audited now
2485        // (`tests/granite_swa_graphs.rs`). NORM RoPE:
2486        // `llama_model_rope_type` puts LLM_ARCH_GRANITE_SWA in the
2487        // NORM group, which `tests/rope_layout.rs` pins.
2488        v.push(prof(
2489            "granite_swa",
2490            TextGeneration,
2491            StandardGqa,
2492            KvIswa,
2493            RopeLayout::Norm,
2494            ArchPath::GenericGqa {
2495                rope: RopeLayout::Norm,
2496            },
2497            WholeVector,
2498        ));
2499        // `maple` was ONE MATCH ARM in `NEOX_ROPE_TRIAGED` for one PR
2500        // on the per-layer RoPE gate and is audited now on
2501        // `crate::rope_layers` (`tests/no_rope_layer_graphs.rs`). It is
2502        // pushed here rather than in the `gqa_neox` list above because
2503        // its QK norm is PER HEAD (`maple.cpp:49-50,84-88`, a
2504        // `{head_dim}` weight applied to each head), and `gqa_neox`
2505        // hands out `WholeVector` -- which loads, runs and normalises
2506        // over the whole projection, the silent-wrong shape this
2507        // column exists to prevent.
2508        v.push(prof(
2509            "maple",
2510            TextGeneration,
2511            StandardGqa,
2512            KvIswa,
2513            Neox,
2514            ArchPath::GenericGqa { rope: Neox },
2515            PerHead,
2516        ));
2517        v.push(prof(
2518            "qwen3",
2519            TextGeneration,
2520            Qwen3Family,
2521            KvGqa,
2522            Neox,
2523            ArchPath::GenericGqa { rope: Neox },
2524            PerHead,
2525        ));
2526        v.push(prof(
2527            "qwen3moe",
2528            TextGeneration,
2529            Qwen3Family,
2530            KvGqa,
2531            Neox,
2532            ArchPath::GenericGqa { rope: Neox },
2533            PerHead,
2534        ));
2535        // `gemma` was FIXTURE-AWAY here until it got its fixture
2536        // (`tests/fixture_away_graphs.rs`); it is audited now and
2537        // carries no verdict at all.
2538        v.push(prof(
2539            "gemma",
2540            TextGeneration,
2541            GemmaFamily,
2542            KvGqa,
2543            Neox,
2544            ArchPath::GenericGqa { rope: Neox },
2545            PerHead,
2546        ));
2547        v.push(prof(
2548            "gemma2",
2549            TextGeneration,
2550            GemmaFamily,
2551            KvIswa,
2552            Neox,
2553            ArchPath::GenericGqa { rope: Neox },
2554            PerHead,
2555        ));
2556        v.push(prof(
2557            "gemma3",
2558            TextGeneration,
2559            GemmaFamily,
2560            KvIswa,
2561            Neox,
2562            ArchPath::GenericGqa { rope: Neox },
2563            PerHead,
2564        ));
2565        // Gemma-4 text GGUFs (E2B): per-layer embeddings, shared-KV
2566        // layers, and split SWA/full head dims -- dedicated
2567        // [`crate::gemma4_engine::Gemma4Engine`] (not GenericGqa).
2568        for n in ["gemma4", "gemma4-assistant"] {
2569            v.push(prof(
2570                n,
2571                TextGeneration,
2572                GemmaFamily,
2573                KvIswa,
2574                Neox,
2575                ArchPath::DedicatedOnly {
2576                    reason: "use load_gemma4_engine_from_path / ServedEngine::Gemma4",
2577                },
2578                PerHead,
2579            ));
2580        }
2581        // The parallel residual `x + attn(norm(x)) + ffn(norm(x))` is
2582        // SERVED (`crate::parallel_residual`), and every row that was
2583        // refused for it is audited now: `gptneox`, `plamo`
2584        // (tests/parallel_residual_graphs.rs), `command-r`
2585        // (tests/command_r_graphs.rs), `falcon` (tests/falcon_graphs.rs),
2586        // `phi2` (tests/phi2_graphs.rs), `cohere2`
2587        // (tests/cohere2_graphs.rs), and `cohere2moe`
2588        // (tests/cohere2moe_graphs.rs, 2026-09-14): the `cohere2` graph
2589        // with routed experts, on `rope_layers::RopeLayers::
2590        // SlidingOrLeadingDense`, `parallel_dense_ffn::
2591        // SHARED_EXPERT_SUM_SCALE`, `norm::NORM_BY_RMS_EPS_KEY`.
2592        v.push(gqa_norm("cohere2moe"));
2593        // MiniCPM was the case `unsupported_scaling_keys` cannot catch:
2594        // `src/models/minicpm.cpp:5-7` *hardcodes* an embedding
2595        // multiplier of 12.0, a residual multiplier of
2596        // `1.4/sqrt(n_layer)` and a logit multiplier of `256/n_embd`,
2597        // and only then (`:12-14`) lets the GGUF override them. An older
2598        // MiniCPM export carrying none of the three keys is still scaled
2599        // by all three, so a key-presence gate sees nothing.
2600        //
2601        // It is generic now, on the same evidence every other row here
2602        // has: `scalar_multipliers::MultiplierDefaults` applies the
2603        // three, and `tests/minicpm_graphs.rs` drives a fixture that
2604        // declares NONE of them against llama.cpp's own logits. Its RoPE
2605        // is NORM (`llama_model_rope_type`, llama-model.cpp:2580, the
2606        // consecutive-pairs group), and it is deliberately NOT in
2607        // `rope_finetuned::ROPE_GATED_ON_FINETUNED`: it runs Granite's
2608        // graph, whose RoPE is gated on `hparams.rope_finetuned`, but
2609        // `minicpm.cpp:17` pins that true with no key read at all, so
2610        // the switch Granite exposes is unreachable here.
2611        v.push(gqa_norm("minicpm"));
2612        v.push(prof(
2613            "phi3",
2614            TextGeneration,
2615            PhiFamily,
2616            KvGqa,
2617            Neox,
2618            ArchPath::GenericGqa { rope: Neox },
2619            WholeVector,
2620        ));
2621        // Phi-4 GGUFs share the phi3 fused-QKV / fused gate+up graph
2622        // (PhiFamily). Many community checkpoints still tag `phi3`; admit
2623        // `phi4` the same way so either string can load. Receipts / head-dim
2624        // FA-vec coverage remain P6 evidence work -- not a speed claim.
2625        v.push(
2626            prof(
2627                "phi4",
2628                TextGeneration,
2629                PhiFamily,
2630                KvGqa,
2631                Neox,
2632                ArchPath::GenericGqa { rope: Neox },
2633                WholeVector,
2634            )
2635            .triaged(
2636                TriageClass::Unknown,
2637                "there is no llama.cpp graph to diff against. `phi4` is NOT in LLM_ARCH_NAMES \
2638                 -- src/llama-arch.cpp:44 lists \"phi3\" and there is no phi4 entry -- so this \
2639                 row is a frink-only alias and no llama.cpp-produced GGUF can carry the \
2640                 string. frink admits it as PhiFamily/NEOX, i.e. phi3's fused-QKV and fused \
2641                 gate+up graph, on the assumption that a file spelling it means the same \
2642                 thing. WHAT WOULD SETTLE IT: a real GGUF whose general.architecture is \
2643                 literally `phi4`. If its blk.0 carries attn_qkv.weight it is phi3's graph \
2644                 and this row is fixture-away behind an already-audited phi3; if it carries \
2645                 split attn_q/attn_k/attn_v it is a Llama-shaped graph and belongs on a \
2646                 different row",
2647            ),
2648        );
2649        // Llama 4 (Scout, Maverick): was a `DedicatedOnly` refusal on
2650        // an engine that never existed, audited now on
2651        // tests/llama4_graphs.rs. The chunked window is
2652        // `crate::chunked_swa`, the literal temperature on the unrotated
2653        // layers `attn_temperature::LITERAL_ATTN_TEMPERATURE`, the
2654        // weightless post-RoPE QK norm `crate::weightless_qk_norm`, the
2655        // honoured interleave step `moe_interleave::
2656        // INTERLEAVE_STEP_HONOURED_BY_LOADER`. NORM RoPE:
2657        // llama-model.cpp's `LLM_ARCH_LLAMA4` sits in the NORM group,
2658        // pinned by `tests/rope_layout.rs`.
2659        v.push(gqa_norm("llama4"));
2660        // MiniMax M2 and M3 are two DIFFERENT architectures and were
2661        // wrong to share one reason. Both used to refuse with "256-expert
2662        // sigmoid MoE + MTP"; neither clause is true.
2663        //
2664        // MTP: `minimax-m2.cpp` and `minimax-m3.cpp` create no `nextn.*`
2665        // tensor at all, and `gguf-py/gguf/constants.py`'s
2666        // `MODEL_ARCH.MINIMAXM2` / `.MINIMAXM3` tensor lists contain no
2667        // `NEXTN_*` entry -- so no converter can even emit MTP weights for
2668        // these files. `minimax-m3.cpp:9` says it outright: "MTP is not
2669        // in released model weights."
2670        //
2671        // Sigmoid MoE: frink HAS it. `loader.rs` reads
2672        // `{arch}.expert_gating_func` into `GatingFunction::Sigmoid`,
2673        // loads `blk.N.exp_probs_b.bias`, and reads
2674        // `expert_weights_scale` / `expert_weights_norm`. Expert count is
2675        // an hparam, not a ceiling.
2676        //
2677        // llama-arch.cpp puts both in the NEOX RoPE group.
2678        // `minimax-m2` was HERE as "UNAUDITED, not unimplemented" -- plain
2679        // GQA, whole-vector QK-norm, partial NEOX RoPE, a sigmoid MoE
2680        // with `exp_probs_b` -- and it is audited now on the fixture that
2681        // had evidenced the claim (tests/minimax_m2_graphs.rs). NEOX
2682        // RoPE: llama-model.cpp:2672.
2683        v.push(gqa_neox("minimax-m2"));
2684        // `pangu-embedded` is openPangu-Embedded-1B / 7B (Huawei), a
2685        // DECODER LLM: `PanguEmbeddedForCausalLM`, `conversion/pangu.py`
2686        // is a `TextModel` with an `lm_head`, and "Embedded" means edge
2687        // devices. It was filed here as "embedding variant; deferred"
2688        // and in `embedding_model::NOT_YET` from the name alone.
2689        // `pangu-embed.cpp` is `llama.cpp`'s graph with one REQUIRED
2690        // `attn_output.bias` (`:37`; `proj_bias::ATTN_OUT_BIAS_CREATORS`),
2691        // NEOX RoPE (llama-model.cpp:2675). Audited on
2692        // tests/pangu_embedded_graphs.rs.
2693        v.push(gqa_neox("pangu-embedded"));
2694        v.push(prof(
2695            "minimax-m3",
2696            TextGeneration,
2697            Dedicated,
2698            KvGqa,
2699            Neox,
2700            ArchPath::DedicatedOnly {
2701                reason: "minimax-m3 needs MiniMax Sparse Attention: a per-layer indexer \
2702                         (index_q_proj/index_k_proj/index_q_norm/index_k_norm, minimax-m3.cpp:76-82) \
2703                         driving its own MSA KV cache (llama-kv-cache-msa.h) with position<->cell \
2704                         maps, plus SWIGLU_OAI experts and shared experts. frink has only the \
2705                         block-selection rule (frink_core::block_sparse), none of the rest",
2706            },
2707            // minimax-m3.cpp:53-55 -- `{n_embd_head_k}`, with llama.cpp's
2708            // own comment "per-head QK-norm: a single head_dim vector
2709            // applied to every head". M2 and M3 DIFFER here, which is why
2710            // the shared entry was wrong for M3.
2711            PerHead,
2712        ));
2713        // MiniCPM3 is MLA, not generic GQA, and the catalog said
2714        // otherwise: it claimed `StandardGqa`/`KvGqa`, which is false
2715        // about the model rather than merely unaudited.
2716        // `src/models/minicpm3.cpp:5-6` requires `q_lora_rank` and
2717        // `kv_lora_rank`, and `:41-46` creates
2718        // `attn_q_a`/`attn_q_b`/`attn_kv_a_mqa`/`attn_kv_b` -- the
2719        // DeepSeek-2 tensor set. There is no `attn_q.weight` in any
2720        // MiniCPM3 checkpoint, so the generic path could never have
2721        // loaded one whatever the audit said.
2722        //
2723        // Reclassified 2026-09-01 by the unaudited-refusal triage. This
2724        // is a MESSAGE-QUALITY fix, not a correctness one: the old
2725        // failure was already a clean missing-tensor error. It stops the
2726        // user being told "unaudited" for something that is not merely
2727        // unaudited.
2728        v.push(prof(
2729            "minicpm3",
2730            TextGeneration,
2731            Mla,
2732            KvMla,
2733            Neox,
2734            ArchPath::DedicatedOnly {
2735                reason: "MiniCPM3 is an MLA model (src/models/minicpm3.cpp:5-6,41-46 -- \
2736                         q_lora_rank/kv_lora_rank and the attn_q_a/attn_q_b/attn_kv_a_mqa/\
2737                         attn_kv_b tensor set), so it needs the MLA engine and not the \
2738                         generic GQA decoder. It ALSO hardcodes MiniCPM's multipliers with \
2739                         no GGUF key to read them from -- scale_embd = 12.0, \
2740                         scale_depth = 1.4, n_embd_base = 256 at :65-67, applied at :81 -- \
2741                         which is the same blind spot `minicpm` is refused for",
2742            },
2743            WholeVector,
2744        ));
2745        v.push(prof(
2746            "deepseek2",
2747            TextGeneration,
2748            Mla,
2749            KvMla,
2750            Norm,
2751            ArchPath::DedicatedOnly {
2752                reason: "DeepSeek-2 MLA needs the MLA engine, not generic GQA",
2753            },
2754            WholeVector,
2755        ));
2756        // PLM-1.8B: `deepseek2.cpp`'s naive MLA branch on a dense
2757        // ReLU-squared model with a direct `attn_q` and a tied lm_head
2758        // (`plm.cpp`); the three differences are `crate::mla_arch`'s
2759        // row. Checked against libllama in tests/plm_graphs.rs, NORM
2760        // RoPE (llama-model.cpp:2592).
2761        v.push(prof(
2762            "plm",
2763            TextGeneration,
2764            Mla,
2765            KvMla,
2766            Norm,
2767            ArchPath::DedicatedOnly {
2768                reason: "PLM is DeepSeek-2 MLA attention on a dense model and runs on the MLA \
2769                         engine (`mla_gguf_loader`), not generic GQA",
2770            },
2771            WholeVector,
2772        ));
2773        v.push(prof(
2774            "deepseek32",
2775            TextGeneration,
2776            Mla,
2777            KvDsa,
2778            Norm,
2779            ArchPath::DedicatedOnly {
2780                reason: "DeepSeek-3.2 DSA/MLA needs the dedicated sparse/MLA stack",
2781            },
2782            WholeVector,
2783        ));
2784        v.push(prof(
2785            "mistral4",
2786            TextGeneration,
2787            Mla,
2788            KvMla,
2789            Norm,
2790            ArchPath::DedicatedOnly {
2791                reason: "mistral4 reuses DeepSeek-2 MLA loader/graph in llama.cpp",
2792            },
2793            WholeVector,
2794        ));
2795        // The three frink-only alias rows. Refused as STRINGS, not
2796        // triaged as architectures: libllama refuses all three outright
2797        // and every real checkpoint of all three declares `llama`. See
2798        // `NO_UPSTREAM_ARCH` for the three measurements. Note the
2799        // `dedicated` helper gives them NORM RoPE, which is at least the
2800        // layout of the graph they claim to be; they had NEOX while
2801        // sitting on the generic path.
2802        for n in ["mistral", "mixtral", "yi"] {
2803            v.push(dedicated(n, NO_UPSTREAM_ARCH));
2804        }
2805        v.push(dedicated(
2806            "glm-dsa",
2807            "use frink_models::glm52_decoder / glm52_gguf_loader (DSA), not the generic GQA Decoder",
2808        ));
2809        // `glm4` -- GLM-4-0414 9B / 32B, GLM-Z1, GLM-OCR -- was HERE,
2810        // sent to the GLM-5.2 MLA loader for four keys `glm4.cpp:3-9`
2811        // never read: the `glm4moe` defect a second time. It is plain
2812        // GQA with Gemma-2's two post norms in Gemma-2's slots and a
2813        // fused SwiGLU, audited on the generic NORM path
2814        // (`tests/glm4_graphs.rs`); see `AUDITED_GENERIC_GQA`.
2815        // `glm4moe` -- GLM-4.5 / GLM-4.5-Air / GLM-4.6 -- was HERE as a
2816        // `dedicated` refusal, twice over: first pointing at
2817        // `glm52_gguf_loader` (which asks for a `q_lora_rank` no glm4moe
2818        // file carries; it is not MLA), then naming the ONE thing that
2819        // was missing, its pre-FFN norm stored as
2820        // `blk.N.post_attention_norm` (`glm4-moe.cpp:75,215`, gpt-oss's
2821        // slot). That slot is one row in
2822        // `norm_sites::PRE_FFN_NORM_IS_POST_ATTENTION_NORM` now and the
2823        // row is audited on the generic NEOX path
2824        // (`tests/glm4moe_graphs.rs`); see `AUDITED_GENERIC_GQA`.
2825        v.push(dedicated(
2826            "deepseek4",
2827            "DeepSeek V4 needs CSA/HCA + mHC assembly; generic GQA Decoder is not valid",
2828        ));
2829        v.push(dedicated(
2830            "kimi-linear",
2831            "use frink_models::kimi_decoder / kimi_loader, not the generic GQA Decoder",
2832        ));
2833        // `kimi-k3`, with a HYPHEN. This row spelled it `kimi_k3` until
2834        // 2026-09-19, and `src/llama-arch.cpp:155` writes
2835        // `{ LLM_ARCH_KIMI_K3, "kimi-k3" }` -- so the refusal could not
2836        // fire on any real file, and a Kimi-K3 export fell through to
2837        // the unknown-architecture message instead of the one naming
2838        // its loader. frink's own preset and Kimi loader
2839        // (`frink-cli/src/main.rs:509`, `kimi_gguf_loader.rs:1022`)
2840        // had the hyphen all along, which is the disagreement this
2841        // repo keeps paying for: two spellings of one name with
2842        // nothing comparing them.
2843        v.push(dedicated(
2844            "kimi-k3",
2845            "use frink_models::kimi_decoder / kimi_loader, not the generic GQA Decoder. \
2846             Upstream's own graph (src/models/kimi-k3.cpp:3-12, new since the 2026-08-04 \
2847             pin) is kimi-linear's KDA + MLA hybrid plus five things it lists itself: \
2848             cross-layer residual attention, a latent MoE, a `situ` activation in place of \
2849             SwiGLU everywhere, a sigmoid gate on the MLA output, and a full-rank KDA gate",
2850        ));
2851        // Landed upstream after the pin, each needing an attention this
2852        // engine does not have; `dedicated` rather than a triaged
2853        // generic row because the generic decoder is not a candidate.
2854        v.push(dedicated(
2855            "bailingmoe3",
2856            "MLA and KDA in one model (src/models/bailingmoe3.cpp:5-14: the `_mla` key \
2857             lengths, `attention.kv_lora_rank`, an SSM conv kernel and `kda.head_dim`). \
2858             The MLA half is frink_models::mla; the KDA half is a linear-attention block \
2859             the gated-delta-net seam does not cover, and the two alternate by layer",
2860        ));
2861        v.push(dedicated(
2862            "dots3note",
2863            "a DSA indexer in front of an absorbed MLA (src/models/dots3note.cpp:2-3 \
2864             includes llama-kv-cache-dsa.h; its own header says it is deepseek32.cpp's \
2865             indexer with step35.cpp's head-wise output gate). frink's DSA lives in the \
2866             GLM-5.2 engine and its MLA in frink_models::mla; this needs the pair plus \
2867             the gate",
2868        ));
2869        v.push(dedicated(
2870            "hy_v4",
2871            "independent hyper-connections: several residual streams reduced before each \
2872             layer and redistributed after (src/models/hy-v4.cpp:6-8, the DeepSeek-V4 \
2873             hyper-connection layout without the comb term), over a DSA cache. Every \
2874             decoder here carries ONE residual stream",
2875        ));
2876        // Qwen3.5 dense (`qwen35`: 0.8B / 2B / 4B / 9B / 27B) left the
2877        // hybrid group on 2026-09-14: the gated delta net is a block
2878        // where attention would be (`crate::gdn`, `AttnShape::Gdn`,
2879        // decided by `gdn::recurrent_layers`), its full-attention layers
2880        // gate through a double-width `wq` (`attn_gate::
2881        // Q_INTERLEAVED_GATE_ARCHS`), per-head QK norm, partial IMROPE
2882        // (NEOX on text positions, `crate::mrope`), the pre-FFN norm
2883        // under `post_attention_norm` (`norm_sites`). Audited on
2884        // tests/qwen35_graphs.rs.
2885        // `qwen35moe` (Qwen3.5-35B-A3B, 122B-A10B, 397B-A17B) is the same
2886        // layers with `qwen2moe`'s FFN (`qwen35moe.cpp:496-538`: softmax,
2887        // `norm_w = true`, the shared expert scaled by its own sigmoid
2888        // gate), which the generic path has served since OLMoE.
2889        // `qwen3next` (Qwen3-Next-80B-A3B) is `qwen35moe`'s layers with
2890        // the V heads GROUPED over the K heads and beta / alpha in one
2891        // `ssm_ba` projection (`gdn::GROUPED_HEAD_ARCHITECTURES`,
2892        // `gdn::BetaAlpha::Fused`), plain NEOX RoPE with no sections
2893        // (llama-model.cpp:2678).
2894        for n in ["qwen35", "qwen35moe", "qwen3next"] {
2895            v.push(prof(
2896                n,
2897                TextGeneration,
2898                DecoderFamily::Hybrid,
2899                MemoryKind::Hybrid,
2900                Neox,
2901                ArchPath::GenericGqa { rope: Neox },
2902                PerHead,
2903            ));
2904        }
2905        // PLaMo-2 (`plamo2`: PLaMo-2 1B / 2B / 8B). Its own SSM block
2906        // where the KV count is zero (`crate::plamo2_ssm`, `ZeroKvLayer::
2907        // Plamo2`), attention elsewhere with a fused `attn_qkv`, the
2908        // per-head QK RMSNorm with a DISTINCT row per head
2909        // (`QkNormStyle::PerHeadDistinct`, `plamo2.cpp:92-93,163,166`),
2910        // NEOX RoPE (llama-model.cpp:2640), post-attention and post-FFN
2911        // norms, the Phi-3 fused `ffn_up`. `kq_scale` is `1/sqrt(v_dim)`
2912        // (`:171`), which is `1/sqrt(head_dim)` on every export
2913        // (`conversion/plamo.py:99-100` write one width for both); a file
2914        // whose two widths differ is refused (`crate::kv_head_dims`).
2915        // Audited on tests/plamo2_graphs.rs.
2916        v.push(prof(
2917            "plamo2",
2918            TextGeneration,
2919            DecoderFamily::Hybrid,
2920            MemoryKind::Hybrid,
2921            Neox,
2922            ArchPath::GenericGqa { rope: Neox },
2923            PerHeadDistinct,
2924        ));
2925        // `lfm2` left the hybrid group on 2026-09-14: its recurrent
2926        // block is a short convolution at the attention site
2927        // (`crate::shortconv`), served on the generic path with a
2928        // per-head QK norm (`lfm2.cpp:74-75`) and NEOX RoPE
2929        // (llama-model.cpp:2666). `lfm2moe` shares its graph
2930        // (`models.h:1899`) and followed on the same seam.
2931        for n in ["lfm2", "lfm2moe"] {
2932            v.push(prof(
2933                n,
2934                TextGeneration,
2935                DecoderFamily::Hybrid,
2936                MemoryKind::Hybrid,
2937                Neox,
2938                ArchPath::GenericGqa { rope: Neox },
2939                PerHead,
2940            ));
2941        }
2942        // `mamba` and `mamba2` (Mamba-130M to 2.8B, FalconMamba-7B;
2943        // Mamba-Codestral-7B) left the recurrent group on 2026-09-14:
2944        // every layer is the one block and no FFN
2945        // (`layer_shapes::PURE_RECURRENT`), served by `crate::mamba1` /
2946        // `crate::mamba2` on the generic path with head_dim 0 and no
2947        // attention anywhere. `jamba` (AI21 Jamba) left the hybrid
2948        // group with them: Mamba-1 where `head_count_kv` is 0
2949        // (`ZeroKvLayer::Mamba1`), attention with NO RoPE elsewhere
2950        // (`jamba.cpp:98`; `rope_layers` answers `Never`, the NEOX entry
2951        // below is a filler as `gpt2`'s), dense or MoE per layer by the
2952        // router's presence (`moe_interleave::
2953        // DENSE_LAYER_BY_ROUTER_ABSENCE`). Audited on
2954        // tests/mamba_graphs.rs.
2955        for n in ["mamba", "mamba2"] {
2956            v.push(prof(
2957                n,
2958                TextGeneration,
2959                DecoderFamily::Recurrent,
2960                MemoryKind::Recurrent,
2961                Neox,
2962                ArchPath::GenericGqa { rope: Neox },
2963                WholeVector,
2964            ));
2965        }
2966        v.push(prof(
2967            "jamba",
2968            TextGeneration,
2969            DecoderFamily::Hybrid,
2970            MemoryKind::Hybrid,
2971            Neox,
2972            ArchPath::GenericGqa { rope: Neox },
2973            WholeVector,
2974        ));
2975        for n in ["rwkv6", "rwkv6qwen2", "rwkv7", "arwkv7"] {
2976            v.push(prof(
2977                n,
2978                TextGeneration,
2979                DecoderFamily::Recurrent,
2980                MemoryKind::Recurrent,
2981                Neox,
2982                ArchPath::DedicatedOnly {
2983                    reason: "recurrent engine not yet on the serve path",
2984                },
2985                WholeVector,
2986            ));
2987        }
2988        v.push(prof(
2989            "t5",
2990            TextGeneration,
2991            EncoderDecoder,
2992            None,
2993            Neox,
2994            ArchPath::DedicatedOnly {
2995                reason: "T5 encoder-decoder engine not yet on the serve path",
2996            },
2997            WholeVector,
2998        ));
2999        for (n, scope, reason) in [
3000            (
3001                "t5encoder",
3002                DeferredEncoderEmbedding,
3003                "encoder-only; deferred from text-generation parity",
3004            ),
3005            // Deferred from the *decoder* path, and that is still
3006            // right: a `bert` GGUF has no output head, so
3007            // `ensure_generic_decoder` must keep refusing it. It is no
3008            // longer deferred outright -- it loads and embeds through
3009            // `bert_gguf_loader` / `bert_encoder`, checked against
3010            // llama.cpp by `tests/bert_llama_cpp_parity.rs`.
3011            (
3012                "bert",
3013                DeferredEncoderEmbedding,
3014                "encoder; no output head, so never a decoder -- served by \
3015                 frink_models::EmbeddingModel on /v1/embeddings",
3016            ),
3017            (
3018                "modern-bert",
3019                DeferredEncoderEmbedding,
3020                "encoder/embedding; deferred",
3021            ),
3022            // Served since 2026-09-19 on the SAME encoder as `bert`:
3023            // its two deltas from that graph are NEOX RoPE on Q/K
3024            // (`bert.cpp:126-133`) and a gated SiLU FFN (`:195-201`),
3025            // both read from the architecture through
3026            // `bert_gguf_loader::ENCODER_ARCHS` and checked against
3027            // llama.cpp's own pooled embedding
3028            // (`tests/nomic_bert_graphs.rs`). Deferred from the
3029            // DECODER path, as `bert` is: neither has an output head.
3030            (
3031                "nomic-bert",
3032                DeferredEncoderEmbedding,
3033                "encoder; no output head, so never a decoder -- served by \
3034                 frink_models::EmbeddingModel on /v1/embeddings",
3035            ),
3036            (
3037                "nomic-bert-moe",
3038                DeferredEncoderEmbedding,
3039                "encoder/embedding; deferred",
3040            ),
3041            (
3042                "neo-bert",
3043                DeferredEncoderEmbedding,
3044                "encoder/embedding; deferred",
3045            ),
3046            // Served since 2026-09-19 on the same encoder as `bert`
3047            // (ALiBi, GEGLU and two optional norms);
3048            // deferred from the DECODER path, which is where the
3049            // scope column speaks from -- it has no output head.
3050            (
3051                "jina-bert-v2",
3052                DeferredEncoderEmbedding,
3053                "encoder; no output head, so never a decoder -- served by \
3054                 frink_models::EmbeddingModel on /v1/embeddings",
3055            ),
3056            // Served since 2026-09-19 on the same encoder as `bert`
3057            // (its rotation with `bert`'s FFN);
3058            // deferred from the DECODER path, which is where the
3059            // scope column speaks from -- it has no output head.
3060            (
3061                "jina-bert-v3",
3062                DeferredEncoderEmbedding,
3063                "encoder; no output head, so never a decoder -- served by \
3064                 frink_models::EmbeddingModel on /v1/embeddings",
3065            ),
3066            (
3067                "eurobert",
3068                DeferredEncoderEmbedding,
3069                "encoder/embedding; deferred",
3070            ),
3071            (
3072                "llama-embed",
3073                DeferredEncoderEmbedding,
3074                "embedding variant; deferred",
3075            ),
3076            (
3077                "gemma-embedding",
3078                DeferredEncoderEmbedding,
3079                "embedding variant; deferred",
3080            ),
3081            ("yi-vl", DeferredMultimodal, "Yi vision-language; deferred"),
3082            ("qwen2vl", DeferredMultimodal, "vision-language; deferred"),
3083            ("qwen3vl", DeferredMultimodal, "vision-language; deferred"),
3084            ("qwen3vlmoe", DeferredMultimodal, "vision-language; deferred"),
3085            ("cogvlm", DeferredMultimodal, "vision-language; deferred"),
3086            ("chameleon", DeferredMultimodal, "multimodal; deferred"),
3087            ("hunyuan_vl", DeferredMultimodal, "vision-language; deferred"),
3088            ("paddleocr", DeferredMultimodal, "OCR multimodal; deferred"),
3089            ("hy_v3", DeferredMultimodal, "multimodal; deferred"),
3090            ("deepseek2-ocr", DeferredMultimodal, "OCR multimodal; deferred"),
3091            ("dream", DeferredDiffusion, "diffusion LM; deferred"),
3092            ("llada", DeferredDiffusion, "diffusion LM; deferred"),
3093            ("llada-moe", DeferredDiffusion, "diffusion LM; deferred"),
3094            ("rnd1", DeferredDiffusion, "diffusion LM; deferred"),
3095            (
3096                "wavtokenizer-dec",
3097                DeferredAudio,
3098                "audio tokenizer; deferred",
3099            ),
3100            // Landed upstream after the 2026-08-04 pin. Both are
3101            // text-to-speech: `pockettts.cpp` is a small LayerNorm
3102            // decoder that emits audio codes, `qwen3tts.cpp` is a
3103            // three-line shim over it. Deferred with the audio scope
3104            // rather than triaged as text generation, because what
3105            // they need is an audio OUTPUT path, not a decoder arm.
3106            ("pockettts", DeferredAudio, "text-to-speech; deferred"),
3107            ("qwen3tts", DeferredAudio, "text-to-speech; deferred"),
3108            (
3109                "eagle3",
3110                EnumOnly,
3111                "speculative draft head; not a standalone decoder target",
3112            ),
3113            (
3114                "dflash",
3115                EnumOnly,
3116                "speculative draft head; not a standalone decoder target",
3117            ),
3118            ("clip", EnumOnly, "quantize dummy only"),
3119            ("gptj", EnumOnly, "enum-only in llama.cpp factory gap"),
3120            ("(unknown)", EnumOnly, "llama.cpp unknown sentinel"),
3121        ] {
3122            v.push(deferred_scope(n, scope, reason));
3123        }
3124        v.push(prof(
3125            "gemma3n",
3126            TextGeneration,
3127            GemmaFamily,
3128            KvIswa,
3129            Neox,
3130            ArchPath::DedicatedOnly {
3131                reason: "gemma3n AltUp/Laurel tensors not implemented in the generic decoder",
3132            },
3133            PerHead,
3134        ));
3135        for n in ["ferroxtest", "ferroxtestmoe", "ferroxtestmixed"] {
3136            v.push(prof(
3137                n,
3138                TextGeneration,
3139                TestFixture,
3140                KvGqa,
3141                Neox,
3142                ArchPath::TestFixture { rope: Neox },
3143                WholeVector,
3144            ));
3145        }
3146        v
3147    })
3148    .as_slice()
3149}
3150
3151/// Resolve a GGUF `general.architecture` value to its profile.
3152pub fn resolve_profile(arch: &str) -> Option<&'static ArchProfile> {
3153    architecture_catalog().iter().find(|p| p.gguf_name == arch)
3154}
3155
3156/// Resolve a GGUF `general.architecture` value. `None` means the string
3157/// is not in the registry -- callers must fail closed rather than guess.
3158pub fn resolve_architecture(arch: &str) -> Option<ArchPath> {
3159    resolve_profile(arch).map(|p| p.path)
3160}
3161
3162/// llama.cpp's hardcoded alternating sliding-window layout for one
3163/// architecture: the period, *and* which end of each period is the
3164/// full-attention layer.
3165///
3166/// `llama_hparams::set_swa_pattern` (`src/llama-hparams.cpp:8-22`) has
3167/// two phases, and they are not interchangeable:
3168///
3169/// - `dense_first = false`: `is_swa[il] = il % p < (p - 1)` -- the
3170///   **last** layer of every period is full attention.
3171/// - `dense_first = true`:  `is_swa[il] = il % p != 0` -- the **first**
3172///   layer of every period is full attention.
3173///
3174/// For a 32-layer period-4 model the two disagree on 16 of the 32
3175/// layers. Storing only the period would therefore not be a partial
3176/// transcription, it would be a wrong one for the four architectures
3177/// llama.cpp passes `dense_first = true`.
3178#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3179pub struct SwaPattern {
3180    /// llama.cpp's `swa_period` seed literal.
3181    pub period: usize,
3182    /// llama.cpp's `dense_first` argument to `set_swa_pattern`.
3183    pub dense_first: bool,
3184}
3185
3186/// Every architecture for which llama.cpp seeds a sliding-window period
3187/// *before* letting `{arch}.attention.sliding_window_pattern` override
3188/// it, transcribed from `src/models/*.cpp`.
3189///
3190/// The period is not in the file for these families -- llama.cpp
3191/// hardcodes it per architecture and only lets the metadata key override
3192/// it (`ml.get_key_or_arr(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN,
3193/// swa_period, false)` after seeding `swa_period` with the literal
3194/// below). A missing key therefore does **not** mean "every layer is
3195/// windowed", which is what frink assumed: `layer_sliding_window`
3196/// returns the window for all layers when `swa_pattern` is `None`, so a
3197/// gpt-oss or cohere2 checkpoint ran its full-attention layers through a
3198/// 128-token window and answered from a truncated history.
3199///
3200/// Two llama.cpp spellings are deliberately absent, because neither is
3201/// a per-arch *period*:
3202///
3203/// - `set_swa_pattern(0)` (`deepseek4.cpp:68`, `dflash.cpp:54`) makes
3204///   **every** layer sliding, which is what frink already does for a
3205///   declared window with no pattern.
3206/// - `set_swa_pattern(1)` (`phi3.cpp:23`) makes **no** layer sliding,
3207///   and phi3 zeroes `n_swa` and sets `swa_type = NONE` on the same
3208///   branch, so there is no window left to place.
3209///
3210/// Architectures that only ever read a per-layer *array*
3211/// (`get_key_or_arr(..., hparams.is_swa_impl, n_layer)`: `gemma4`,
3212/// `gemma4-assistant`, `step35`, `mimo2`, `dflash`) seed no scalar and
3213/// so have no default to pin.
3214///
3215/// Pinned by `tests/swa_pattern.rs`.
3216/// Architectures where llama.cpp DISABLES sliding-window attention even
3217/// though the checkpoint declares a window.
3218///
3219/// `src/models/phi3.cpp:12-24`: if `attention.sliding_window` is present
3220/// and non-zero, llama.cpp warns, then sets `n_swa = 0`,
3221/// `swa_type = LLAMA_SWA_TYPE_NONE` and `set_swa_pattern(1)` -- i.e. NO
3222/// layer slides. Its own comment says the conversion scripts populate
3223/// the key wrongly and links the PR that turned it off.
3224///
3225/// frink read the key and, having no per-architecture period for
3226/// `phi3`, windowed EVERY layer. So a Phi-3 or Phi-4 model attended over
3227/// a truncated history on every layer where llama.cpp attends over the
3228/// whole context. `phi3` is in [`AUDITED_GENERIC_GQA`], and
3229/// `models/Phi-4-mini-instruct-Q4_K_M.gguf` really does declare
3230/// `phi3.attention.sliding_window = 262144` -- so this was live on a
3231/// model in the benchmark suite, not hypothetical.
3232///
3233/// This is deliberately a REFUSAL TO HONOUR the key rather than a
3234/// transcribed period: llama.cpp is not choosing a different window
3235/// here, it is declining to use the one in the file.
3236///
3237/// # The second cause, and why it shares this predicate
3238///
3239/// `src/models/exaone4.cpp:4-14` wraps the ENTIRE SWA setup --
3240/// `swa_type`, `n_swa`, `set_swa_pattern`, both SWA RoPE fields -- in
3241/// `if (hparams.n_layer() == 64)`, and only then reads
3242/// `LLM_KV_ATTENTION_SLIDING_WINDOW` at :16 into an `hparams.n_swa` no
3243/// layer consults. So EXAONE-4 1.2B (30 layers) attends over the whole
3244/// context on every layer no matter what its file declares, and
3245/// EXAONE-4 32B (64) does not.
3246///
3247/// It is the same QUESTION as `phi3`'s -- "does this file get a window
3248/// at all" -- so it is the same predicate rather than a second one
3249/// beside it. `crate::rope_layers::rope_layers` takes this function's
3250/// answer, not the raw presence of the key, and getting that wrong
3251/// would rope the 1.2B as if it were the 32B: `exaone4.cpp:116` gates
3252/// rotation on `is_swa(il)`, so a spurious window would silently stop
3253/// three layers in four from rotating.
3254pub fn swa_disabled_by_arch(arch: &str, n_layers: usize) -> bool {
3255    matches!(swa_window_override(arch, n_layers), SwaWindowOverride::Drop)
3256}
3257
3258/// What llama.cpp does with a nonzero `attention.sliding_window` the
3259/// file declares, for the architectures whose `load_arch_hparams` does
3260/// not simply honour it.
3261///
3262/// Three answers, one table: HONOUR (every architecture not named),
3263/// DROP (the two [`swa_disabled_by_arch`] rows -- no layer slides), and
3264/// PIN (the window is replaced by a literal, and the layers still
3265/// slide). [`swa_disabled_by_arch`] is DERIVED from this so the two
3266/// cannot disagree about which rows decline the file's value.
3267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3268pub enum SwaWindowOverride {
3269    /// The file's value is the window.
3270    Honour,
3271    /// No window at all, whatever the file says.
3272    Drop,
3273    /// This window, whatever the file says.
3274    Pin(usize),
3275}
3276
3277/// The third case's one row: `src/models/smallthinker.cpp:4-8` reads
3278/// `attention.sliding_window` into `n_swa`, tests it for `> 0`, and
3279/// on that branch assigns `hparams.n_swa = 4096` -- the value it just
3280/// read is used as a flag and then overwritten. So a SmallThinker file
3281/// declaring 3 slides at 4096, and libllama's logits for a fixture
3282/// declaring 3 and the same fixture declaring 4096 are BYTE-IDENTICAL
3283/// (measured, `tests/router_input_graphs.rs`). A file declaring 0 or
3284/// nothing takes the other branch (`:16-18`): no window, every layer
3285/// rotated.
3286///
3287/// This is a PIN rather than a DROP because the layers still slide
3288/// (`:11` calls `set_swa_pattern`) and the SWA RoPE base still applies
3289/// (`:13-15`); only the width is upstream's literal. Honouring the
3290/// file's value here would mask three layers in four over a window
3291/// the graph never uses. `conversion/smallthinker.py:32-38` writes the
3292/// real `sliding_window_size` (4096 on every published SmallThinker),
3293/// so on a real export the pin and the file agree and a reader cannot
3294/// tell them apart; the fixture declares 3 so that they cannot.
3295pub const SMALLTHINKER_PINNED_WINDOW: usize = 4096;
3296
3297/// See [`SwaWindowOverride`].
3298pub fn swa_window_override(arch: &str, n_layers: usize) -> SwaWindowOverride {
3299    match arch {
3300        "phi3" => SwaWindowOverride::Drop,
3301        // phimoe.cpp:3-10 read no window key at all, so `swa_type` stays
3302        // NONE and the key `conversion/phi.py:171` writes for every
3303        // export is dead metadata: libllama reports `n_swa = 0` for a
3304        // file declaring one (measured, tests/phimoe_graphs.rs).
3305        "phimoe" => SwaWindowOverride::Drop,
3306        // exaone4.cpp:4. NOT `>= 64` and not a range: llama.cpp tests
3307        // equality, so a hypothetical 63- or 65-layer EXAONE-4 gets no
3308        // window there either.
3309        "exaone4" if n_layers != 64 => SwaWindowOverride::Drop,
3310        // smallthinker.cpp:8.
3311        "smallthinker" => SwaWindowOverride::Pin(SMALLTHINKER_PINNED_WINDOW),
3312        _ => SwaWindowOverride::Honour,
3313    }
3314}
3315
3316/// Architectures whose FFN gate uses GELU rather than SiLU, i.e. GeGLU
3317/// rather than SwiGLU.
3318///
3319/// llama.cpp picks this PER ARCHITECTURE -- it is the `LLM_FFN_GELU` vs
3320/// `LLM_FFN_SILU` argument each `src/models/*.cpp` passes to `build_ffn`
3321/// / `build_moe_ffn` -- and frink picked it per FAMILY, which is not
3322/// the same partition. `grok` is the case that proves it:
3323/// `src/models/grok.cpp:165` passes `LLM_FFN_GELU` to `build_moe_ffn`,
3324/// but `grok` is `DecoderFamily::StandardGqa`, so frink handed it
3325/// SwiGLU and would have computed a different FFN on every layer.
3326///
3327/// It was latent while `grok` refused as unaudited, and it is LIVE
3328/// now: `tests/grok_graphs.rs` compares the GELU experts against
3329/// libllama, at the GeGLU tolerance that llama.cpp's f16 GELU table
3330/// forces on every GeGLU row.
3331///
3332/// The other `LLM_FFN_GELU` users upstream -- `bert`, `bloom`,
3333/// `codeshell`, `falcon`, `gpt2`, `gptneox`, `mpt`, `phi2`, `starcoder`,
3334/// `starcoder2`, `t5`, `wavtokenizer-dec` -- are all `Deferred` or
3335/// `DedicatedOnly` here, so none reaches the generic path and none is
3336/// listed. The Gemma lineage is GELU too and stays on the family rule,
3337/// because every Gemma row IS `GemmaFamily`.
3338pub fn uses_geglu(arch: &str) -> bool {
3339    // `spark2_5` joined on 2026-09-19 with the pin move:
3340    // `src/models/spark2-5.cpp:124` passes `LLM_FFN_GELU` under
3341    // `LLM_FFN_PAR` to `build_ffn`, i.e. a GATED GELU, and the row is
3342    // `StandardGqa` like `grok` -- so the family rule would have given
3343    // it SwiGLU and a different FFN on every layer. Its golden
3344    // (`tests/gated_attention_graphs.rs`) holds at the same GeGLU
3345    // tolerance llama.cpp's f16 GELU table forces.
3346    matches!(arch, "grok" | "spark2_5")
3347}
3348
3349/// Architectures whose FFN is the UNGATED ReLU-squared MLP:
3350/// `build_ffn(up, NULL gate, down, LLM_FFN_RELU_SQR, LLM_FFN_SEQ)`,
3351/// i.e. `down(relu(up(x))^2)` (`arcee.cpp:123-128`).
3352///
3353/// Five graphs pass `LLM_FFN_RELU_SQR` upstream -- measured, by
3354/// grepping `src/models/*.cpp`: `arcee`, `plm`, `nemotron`, `jais2`,
3355/// `nemotron-h` (the GGUF string is `nemotron_h`; the MoE sibling's
3356/// dense shared expert and its experts pass it too, `:190,227`). All
3357/// five serve it: `plm` on the MLA engine (`crate::mla_arch` reads the
3358/// same fact from its own table, and
3359/// `mla_arch_and_this_table_agree_about_plm` pins that they agree), the
3360/// rest on the generic path.
3361pub fn uses_relu_sqr(arch: &str) -> bool {
3362    matches!(
3363        arch,
3364        "arcee" | "plm" | "nemotron" | "jais2" | "nemotron_h" | "nemotron_h_moe"
3365    )
3366}
3367
3368/// Architectures whose FFN is the UNGATED GELU MLP:
3369/// `build_ffn(up, up_b, NULL gate, down, down_b, LLM_FFN_GELU,
3370/// LLM_FFN_SEQ)`, i.e. `down(gelu(up(x) + up_b)) + down_b`
3371/// (`starcoder2.cpp:125-131`, `codeshell.cpp:120-126`).
3372///
3373/// Eleven graphs pass `LLM_FFN_GELU` under `LLM_FFN_SEQ` upstream --
3374/// measured, `grep -l 'LLM_FFN_GELU, *LLM_FFN_SEQ' src/models/*.cpp`:
3375/// `bert`, `bloom`, `codeshell`, `falcon`, `gptneox`, `gpt2`, `mpt`,
3376/// `phi2`, `starcoder`, `starcoder2`, `wavtokenizer-dec`. The two
3377/// listed reach the generic path with nothing else in the way once the
3378/// projection biases are served (`crate::proj_bias`); `bert` and
3379/// `wavtokenizer-dec` are not decoders, `bloom` / `gpt2` / `mpt` /
3380/// `starcoder` has no RoPE; `gptneox`, `falcon` and `phi2` joined once
3381/// the parallel residual was served (`crate::parallel_residual`). The
3382/// five here map to `FfnActivation::GeluUngated`.
3383pub fn uses_gelu_ungated(arch: &str) -> bool {
3384    matches!(
3385        arch,
3386        "starcoder2"
3387            | "codeshell"
3388            | "gptneox"
3389            | "falcon"
3390            | "phi2"
3391            | "gpt2"
3392            | "starcoder"
3393            | "bloom"
3394            | "mpt"
3395    )
3396}
3397
3398#[cfg(test)]
3399mod relu_sqr_tests {
3400    use super::*;
3401
3402    /// Two tables say what `plm`'s dense FFN is -- this one, read by
3403    /// the generic loader, and `crate::mla_arch`'s row, read by the MLA
3404    /// loader. They must agree, and the MLA table must say ReluSqr for
3405    /// exactly the rows this one names.
3406    #[test]
3407    fn mla_arch_and_this_table_agree_about_plm() {
3408        for row in crate::mla_arch::MLA_ENGINE_ARCHS {
3409            let ungated = row.dense_act.ungated().is_some();
3410            assert_eq!(
3411                ungated,
3412                uses_relu_sqr(row.name),
3413                "`{}`: mla_arch says ungated={ungated}, uses_relu_sqr disagrees",
3414                row.name
3415            );
3416        }
3417        assert!(matches!(
3418            crate::mla_arch::mla_arch("plm").unwrap().dense_act,
3419            frink_moe::GluAct::ReluSqr
3420        ));
3421    }
3422}
3423
3424/// Architectures whose experts are the GATED ReLU MLP:
3425/// `build_moe_ffn(..., LLM_FFN_RELU, ...)` with `gate_exps` present,
3426/// which `llama-graph.cpp:2195-2197` runs as `ggml_reglu_split(gate,
3427/// up)`, i.e. `down(relu(gate(x)) * up(x))` (`smallthinker.cpp:62,158`).
3428///
3429/// ONE graph passes `LLM_FFN_RELU` to `build_moe_ffn` upstream --
3430/// measured, `grep -n 'LLM_FFN_RELU[^_]' src/models/*.cpp` over all
3431/// 140: `smallthinker.cpp:158`. The only other two hits, `t5.cpp:243,
3432/// 345`, are `build_ffn` with a NULL gate (ungated `relu(up)`, a
3433/// different op again) on an encoder-decoder engine, so they are not
3434/// listed. Distinct from [`uses_relu_sqr`] on purpose: that is
3435/// `LLM_FFN_RELU_SQR` with NO gate, served by aliasing gate to up, and
3436/// a loader that aliased this one would compute `relu(up) * up` on a
3437/// file whose gate tensor it had silently dropped.
3438pub fn uses_reglu(arch: &str) -> bool {
3439    matches!(arch, "smallthinker")
3440}
3441
3442pub fn default_swa_layout(arch: &str) -> Option<SwaPattern> {
3443    let last_dense = |period| {
3444        Some(SwaPattern {
3445            period,
3446            dense_first: false,
3447        })
3448    };
3449    let dense_first = |period| {
3450        Some(SwaPattern {
3451            period,
3452            dense_first: true,
3453        })
3454    };
3455    match arch {
3456        // src/models/openai-moe.cpp:9
3457        "gpt-oss" => last_dense(2),
3458        // src/models/gemma2.cpp:6
3459        "gemma2" => last_dense(2),
3460        // src/models/gemma3.cpp:7
3461        "gemma3" => last_dense(6),
3462        // src/models/gemma3n.cpp:4 says 5, NOT 6. This was transcribed
3463        // as 6 alongside gemma3 and is simply wrong. Inert only because
3464        // `gemma3n` refuses for other reasons today.
3465        "gemma3n" => last_dense(5),
3466        // src/models/gemma-embedding.cpp:5. Deferred (embedding scope),
3467        // so latent rather than live.
3468        "gemma-embedding" => last_dense(6),
3469        // src/models/cohere2.cpp:5, exaone4.cpp:7, olmo2.cpp:9
3470        "cohere2" | "exaone4" | "olmo2" => last_dense(4),
3471        // Added after an audit found this table covered 6 architectures
3472        // where llama.cpp hardcodes a period for 17. A MISSING entry is
3473        // not neutral: with no period, every layer gets windowed, so a
3474        // model whose full-attention layers should see the whole context
3475        // sees only a window instead. That is a different model, and it
3476        // fails silently.
3477        //
3478        // src/models/mellum.cpp:11
3479        "mellum" => last_dense(4),
3480        // src/models/exaone-moe.cpp:6. SWA is unconditional there
3481        // with n_swa = 128, so without this every layer ran with a
3482        // 128-token history.
3483        "exaone-moe" => last_dense(4),
3484        // src/models/afmoe.cpp:17. LIVE: `afmoe` is audited, and its
3485        // fixture's window is narrower than the prompt
3486        // (`tests/gated_attention_graphs.rs`).
3487        "afmoe" => last_dense(4),
3488        // src/models/plamo3.cpp:9. LIVE: `plamo3` is audited, and its
3489        // fixture drives a period of 2 from the file with a window
3490        // narrower than the prompt, so both the period override and
3491        // this phase are exercised end to end against libllama.
3492        "plamo3" => last_dense(8),
3493        // src/models/llama4.cpp:19 ("pattern: 3 chunked - 1 full").
3494        // LIVE: the chunked window is `crate::chunked_swa`, and
3495        // tests/llama4_graphs.rs drives a period of 2 from the file.
3496        "llama4" => last_dense(4),
3497        // --- dense_first = true -----------------------------------
3498        //
3499        // These four put the full-attention layer at `il % p == 0`, not
3500        // at `il % p == p - 1`. `ModelConfig::layer_sliding_window`
3501        // implements BOTH phases and carries this flag as
3502        // `swa_dense_first`; it used to implement only the first, which
3503        // is why `smallthinker` and `laguna` windowed every layer.
3504        //
3505        // src/models/smallthinker.cpp:9-11. Latent: `smallthinker` is
3506        // triaged NEW CODE on its raw-input router and ReLU experts, so
3507        // it refuses before this row is consulted. This used to say
3508        // LIVE, and was wrong: the triage row predates the comment.
3509        "smallthinker" => dense_first(4),
3510        // src/models/laguna.cpp:39-41 (its own comment: "XS.2: FULL at
3511        // il%4==0"). LIVE: `laguna` is on the generic GQA path.
3512        "laguna" => dense_first(4),
3513        // src/models/cohere2moe.cpp:31-33. `DedicatedOnly` today
3514        // (parallel attention+FFN residual), so latent.
3515        "cohere2moe" => dense_first(4),
3516        // src/models/modern-bert.cpp:8-10. Deferred (encoder scope), so
3517        // latent.
3518        "modern-bert" => dense_first(3),
3519        _ => None,
3520    }
3521}
3522
3523/// True when this architecture's SWA layers use the model's own RoPE
3524/// base rather than llama.cpp's `rope_freq_base_train_swa` default of
3525/// `10000`.
3526///
3527/// `llama_hparams` defaults that field to `10000.0f`
3528/// (`src/llama-hparams.h:127`) and the Gemma-3 lineage relies on the
3529/// default; the architectures listed here instead open with
3530/// `hparams.rope_freq_base_train_swa = hparams.rope_freq_base_train;`
3531/// before letting `rope.freq_base_swa` override it. frink applied the
3532/// Gemma default to everything, which rotates a gpt-oss SWA layer at
3533/// theta 10000 instead of its real 150000.
3534pub fn swa_rope_base_follows_model(arch: &str) -> bool {
3535    matches!(
3536        arch,
3537        "afmoe"
3538            | "cohere2"
3539            | "cohere2moe"
3540            | "dflash"
3541            | "exaone-moe"
3542            | "exaone4"
3543            | "gemma2"
3544            | "laguna"
3545            | "llama4"
3546            | "mellum"
3547            | "olmo2"
3548            | "gpt-oss"
3549            | "smallthinker"
3550    )
3551}
3552
3553/// True when this architecture's SWA layers inherit the model's TRAINED
3554/// RoPE position scale rather than llama.cpp's
3555/// `rope_freq_scale_train_swa` default of `1.0`.
3556///
3557/// The sibling of [`swa_rope_base_follows_model`], and deliberately NOT
3558/// derived from it: llama.cpp defaults both fields
3559/// (`src/llama-hparams.h:127,129`) and each architecture assigns them
3560/// independently, so the two lists differ. `olmo2.cpp:13-14` and
3561/// `laguna.cpp:47-48` seed the BASE from the model and then pin the
3562/// SCALE to `1.0` -- laguna's own comment is "SWA uses plain RoPE (no
3563/// YaRN scaling); do NOT inherit full layers 1/factor". Collapsing the
3564/// two tables into one would rope those two architectures wrong in
3565/// exactly the way this function exists to stop.
3566///
3567/// The default matters more than the list. `gemma3.cpp:11` reads only
3568/// `LLM_KV_ROPE_FREQ_BASE_SWA` and never touches
3569/// `rope_freq_scale_train_swa`, so Gemma-3's sliding layers rope at
3570/// scale `1.0` while its full-attention layers use the trained scale --
3571/// and the converter agrees, writing `rope.scaling.factor` from
3572/// `rope_parameters["full_attention"]` alone (`conversion/base.py:1222`,
3573/// whose own comment is "TODO: Handle sliding_attention similarly when
3574/// models start implementing it").
3575///
3576/// Every name here is a `hparams.rope_freq_scale_train_swa =
3577/// hparams.rope_freq_scale_train;` in `src/models/`, at the line given.
3578pub fn swa_rope_scale_follows_model(arch: &str) -> bool {
3579    matches!(
3580        arch,
3581        "afmoe"          // afmoe.cpp:22
3582            | "cohere2"     // cohere2.cpp:10
3583            | "cohere2moe"  // cohere2moe.cpp:39
3584            | "dflash"      // dflash.cpp:59, :71
3585            | "exaone-moe"  // exaone-moe.cpp:10
3586            | "exaone4"     // exaone4.cpp:12
3587            | "gemma2"      // gemma2.cpp:11
3588            | "llama4"      // llama4.cpp:24
3589            | "mellum"      // mellum.cpp:20
3590            | "gpt-oss"     // openai-moe.cpp:14
3591            | "smallthinker" // smallthinker.cpp:14
3592    )
3593}
3594
3595/// True when this architecture's graph multiplies every token
3596/// embedding by `sqrt(n_embd)` as ARITHMETIC, reading no key for it.
3597///
3598/// Measured over all 155 `src/models/*.cpp` for
3599/// `ggml_scale(ctx0, inpL, sqrtf(...n_embd...))`: every Gemma graph
3600/// (`gemma.cpp:49`, `gemma2.cpp:70`, `gemma3.cpp:93`, `gemma3n.cpp:104`,
3601/// `gemma4.cpp:155`, `gemma-embedding.cpp:85`) and exactly ONE other,
3602/// `afmoe.cpp:120` ("MuP scaling"). The Gemma side was a `family`
3603/// match in `loader.rs`; `afmoe` is not a Gemma and does the same
3604/// thing, so the fact is a table here rather than a second `if`
3605/// beside the first.
3606///
3607/// This is about the ARITHMETIC, not the key. A file for one of these
3608/// declaring `{arch}.embedding_scale` describes something its graph
3609/// does not do, and `scalar_multipliers::multiplier_support` --
3610/// which lists none of them -- refuses the key before this is asked.
3611pub fn embeddings_scaled_by_sqrt_n_embd(arch: &str, family: DecoderFamily) -> bool {
3612    matches!(family, DecoderFamily::GemmaFamily) || arch == "afmoe"
3613}
3614
3615/// llama.cpp's `hparams.f_attention_scale`, but only when it DIFFERS
3616/// from the `1/sqrt(head_dim)` every frink attention kernel already
3617/// applies. `None` means "the kernels' own scale is already right", so
3618/// a caller stores it straight into `ModelConfig::attention_scale`.
3619///
3620/// Only the Gemma-2 and Gemma-3 27B checkpoints answer `Some`:
3621///
3622/// ```cpp
3623/// // src/models/gemma3.cpp:30-33 (src/models/gemma2.cpp:26-29 identical in shape)
3624/// hparams.f_attention_scale = type == LLM_TYPE_27B
3625///     ? 1.0f / std::sqrt(float(hparams.n_embd / hparams.n_head(0)))
3626///     : 1.0f / std::sqrt(float(hparams.n_embd_head_k()));
3627/// ```
3628///
3629/// and llama.cpp applies it as an explicit `ggml_scale` on Q followed by
3630/// `build_attn(..., 1.0f)` (`gemma3.cpp:154`, `gemma2.cpp:110`), which is
3631/// what [`crate::config::ModelConfig::attention_scale`] means here.
3632///
3633/// **The selector is the LAYER COUNT, not a comparison of the two
3634/// widths.** `LLM_TYPE_27B` comes from `switch (hparams.n_layer())`
3635/// (`gemma3.cpp:20-28` `case 62`, `gemma2.cpp:19-23` `case 46`), and
3636/// deriving it instead from `n_embd / n_head != head_dim` would be
3637/// wrong for EVERY other Gemma size -- all of them have
3638/// `n_embd / n_head != head_dim` too, and all of them take llama.cpp's
3639/// `1/sqrt(n_embd_head_k)` branch. See
3640/// `gemma_27b_is_the_only_size_that_overrides_the_kernel_scale`.
3641///
3642/// `hidden_dim / n_heads` is integer division on purpose: llama.cpp
3643/// divides two `uint32_t` and only then converts to float.
3644///
3645/// `jais` is the one other graph with a literal: `jais.cpp:81-83`
3646/// passes `kq_scale = 1.0f / float(n_embd_head)` -- `1/d`, not
3647/// `1/sqrt(d)` (Jais's muP attention) -- to `build_attn` on every layer.
3648/// Measured: `grep -n "1.0f/float(n_embd_head)" src/models/*.cpp` over
3649/// all 155 graphs is that one file.
3650pub fn attention_scale_override(
3651    arch: &str,
3652    n_layers: usize,
3653    hidden_dim: usize,
3654    n_heads: usize,
3655    head_dim: usize,
3656) -> Option<f32> {
3657    // `case 62` / `case 46` in the `switch (hparams.n_layer())` that
3658    // picks `LLM_TYPE_27B`. Every other Gemma architecture
3659    // (`gemma-embedding`, `gemma3n`, `gemma4`) sets `f_attention_scale`
3660    // unconditionally and has no 27B branch at all.
3661    let is_27b = match arch {
3662        "gemma2" => n_layers == 46,
3663        "gemma3" => n_layers == 62,
3664        _ => false,
3665    };
3666    if head_dim == 0 {
3667        return None;
3668    }
3669    if arch == "jais" {
3670        return Some(1.0 / head_dim as f32);
3671    }
3672    if !is_27b || n_heads == 0 {
3673        return None;
3674    }
3675    let scale = 1.0 / ((hidden_dim / n_heads) as f32).sqrt();
3676    let kernel_scale = 1.0 / (head_dim as f32).sqrt();
3677    (scale != kernel_scale).then_some(scale)
3678}
3679
3680/// Architectures outside the Gemma family whose graph applies the two
3681/// logit softcaps frink implements -- `attn_logit_softcapping` on the
3682/// attention scores and `final_logit_softcapping` after the lm_head.
3683///
3684/// `grok`: `llama-graph.cpp:2572-2582` applies
3685/// `30 * tanh(kq * f_attn_out_scale / 30)` before the softmax, which is
3686/// frink's `attn_logit_softcap` over a Q pre-scaled by
3687/// `ModelConfig::attention_scale`; `grok.cpp:214-218` applies the final
3688/// softcap when the file declares one (default 0, off). The converter
3689/// (`conversion/grok.py:34`) writes `attn_logit_softcapping` for EVERY
3690/// Grok export, so without this list no real Grok file could load.
3691///
3692/// The Gemma family is not here because it is exempted as a family
3693/// below; a name here is one whose graph was read for both softcaps.
3694pub const LOGIT_SOFTCAP_ARCHITECTURES: &[&str] = &["grok", "muse-glimmer"];
3695
3696/// Metadata keys that, when present with a nonzero value, require math
3697/// frink's generic decoder does not implement *unless* the architecture
3698/// profile opts into those features (Gemma family), or the architecture
3699/// is named in [`LOGIT_SOFTCAP_ARCHITECTURES`] for the softcaps.
3700pub fn unsupported_feature_keys(arch: &str) -> Vec<(String, &'static str)> {
3701    let profile = resolve_profile(arch);
3702    // Gemma family implements softcap + SWA pattern; others still refuse.
3703    if matches!(profile.map(|p| p.family), Some(DecoderFamily::GemmaFamily)) {
3704        return Vec::new();
3705    }
3706    let key = |suffix: &str| format!("{arch}.{suffix}");
3707    let mut out = Vec::new();
3708    if !LOGIT_SOFTCAP_ARCHITECTURES.contains(&arch) {
3709        out.push((
3710            key("attention.logit_softcapping"),
3711            "attention logit soft-capping (Gemma 2+); not implemented in the generic decoder",
3712        ));
3713        // The spelling llama.cpp's converters ACTUALLY write
3714        // (`llama-arch.cpp:213` is `%s.attn_logit_softcapping`). The
3715        // line above is a spelling no converter emits, so this gate has
3716        // never fired for any non-Gemma architecture -- while
3717        // `loader.rs` reads BOTH spellings and applies the value.
3718        //
3719        // A checkpoint declaring an attention softcap was therefore not
3720        // refused; it ran with the generic formula. For `grok` that is a
3721        // wrong answer rather than an approximation: `grok.cpp` folds
3722        // the real attention scale INTO the softcap and passes
3723        // `kq_scale = 1.0f`, which the generic path does not do.
3724        //
3725        // A gate that cannot fire is not a gate, and it looked exactly
3726        // like one.
3727        out.push((
3728            key("attn_logit_softcapping"),
3729            "attention logit soft-capping (Gemma 2+); not implemented in the generic decoder",
3730        ));
3731        out.push((
3732            key("final_logit_softcapping"),
3733            "final logit soft-capping (Gemma 2+); not implemented in the generic decoder",
3734        ));
3735    }
3736    // `{arch}.nextn_predict_layers` WAS refused here, for every
3737    // architecture, with the reason that frink's `n_layers` IS
3738    // `block_count` and it would run the MTP head as decoder layers.
3739    // `crate::mtp_blocks::trunk_layers` subtracts the blocks now for
3740    // exactly the seventeen graphs whose `load_arch_hparams` reads the
3741    // key (`NEXTN_READERS`, measured) and still refuses a nonzero value
3742    // on any other -- where llama.cpp itself would run every block and
3743    // then fail on the unread `nextn.*` tensors. One place decides both
3744    // halves, so the reader table and the refusal cannot drift apart.
3745    // `{arch}.attention.sliding_window_pattern` WAS refused here,
3746    // with the reason "not implemented in the generic decoder".
3747    // That reason was false, and had been for some time: the
3748    // alternating pattern lives in `ModelConfig::layer_sliding_window`,
3749    // which implements BOTH phases and which `gpt-oss` -- a
3750    // `StandardGqa` row, not a Gemma one -- has relied on since it
3751    // was audited against libllama.
3752    //
3753    // What the gate really did was make the loader's own read of
3754    // that key (`swa_pattern`) unreachable for every non-Gemma
3755    // architecture: llama.cpp lets the file override the
3756    // architecture's hardcoded period, frink refused any file that
3757    // tried. `plamo3` is the case that proves it -- its converter
3758    // writes the key verbatim (`conversion/plamo.py:178`) -- and
3759    // `tests/fixture_away_graphs.rs` now drives a period of 2 out of
3760    // a plamo3 fixture and compares against llama.cpp's own graph on
3761    // all three forward paths, with the phase and the window
3762    // sabotaged separately.
3763    //
3764    // The real gap the key could hide was NOT the pattern: it was
3765    // that llama.cpp accepts the value as a scalar OR an n_layer-long
3766    // ARRAY (`ml.get_key_or_arr`), and frink carried one scalar
3767    // period. The array is `crate::swa_layers` now, read the way each
3768    // graph reads it -- ignored, honoured, or broadcast -- so neither
3769    // shape is refused here or anywhere else.
3770    //
3771    // `{arch}.moe_latent_size` (`LLM_KV_MOE_LATENT_SIZE`,
3772    // `nemotron-h.cpp:21,36,82-85,206-208`): the routed experts run in a
3773    // LATENT width the layer projects into with `ffn_latent_down` and
3774    // out of with `ffn_latent_up`, while the router and the shared
3775    // expert read the unprojected input. Nemotron-3 Nano writes no such
3776    // key; Nemotron-3 Super does. The generic MoE bodies run their
3777    // experts at `hidden_dim`, so a nonzero value stops here, by name.
3778    out.push((
3779        key("moe_latent_size"),
3780        "a latent MoE (nemotron-h.cpp:206-208: the experts read `ffn_latent_down(x)` and \
3781         their sum is `ffn_latent_up`ed back), which the generic MoE bodies, which run \
3782         the experts at hidden_dim, do not have",
3783    ));
3784    out
3785}
3786
3787/// Scalar multipliers a checkpoint can declare in **metadata** that the
3788/// generic decoder does not apply, with the value that means "no-op".
3789///
3790/// These are the blind spot left by
3791/// [`crate::loader::assert_every_tensor_consumed`]: that gate catches a
3792/// missing *tensor*, but Granite / MiniCPM / Command-R style multipliers
3793/// are hparams, not weights, so a checkpoint carrying them loads
3794/// cleanly, runs at full speed, and computes a graph scaled differently
3795/// from the one the checkpoint was trained as. Nothing says so.
3796///
3797/// llama.cpp key names (`llama-arch.cpp`):
3798/// `%s.logit_scale` (`LLM_KV_LOGIT_SCALE`), `%s.residual_scale`,
3799/// `%s.embedding_scale`, `%s.attention.scale`. Granite reads all four
3800/// (`src/models/granite.cpp::load_arch_hparams`); MiniCPM and
3801/// Command-R/Cohere2 read the subset they use.
3802///
3803/// **This list is DERIVED, never restated.** Which of the four an
3804/// architecture applies lives in
3805/// [`crate::scalar_multipliers::multiplier_support`], and this function
3806/// is exactly its complement: a key appears here if and only if that
3807/// table says the graph does not apply it. Two hand-written lists is the
3808/// shape that once let this repo refuse a key it implemented and
3809/// implement a key it refused, and the Gemma family used to be exempted
3810/// from ALL FOUR of these wholesale on the strength of implementing two,
3811/// so a hand-written `gemma3.residual_scale` would have loaded and been
3812/// ignored.
3813///
3814/// `residual_scale` is the one that reaches furthest: it multiplies the
3815/// attention and FFN branch outputs before every residual add, so on an
3816/// architecture that does not implement it a declared value would have
3817/// to be dropped by every CPU decode/prefill/multi-seq path *and* by the
3818/// fused Metal kernels that fold the residual in.
3819///
3820/// The no-op value differs by key: the three `*_scale` multipliers are
3821/// `1.0`, while llama.cpp's `f_attention_scale` uses `0.0` as its
3822/// "unset, use 1/sqrt(head_dim)" sentinel.
3823pub fn unsupported_scaling_keys(arch: &str) -> Vec<(String, &'static str, f32)> {
3824    use crate::scalar_multipliers::{AttentionScaleKey, LogitScaleUse, ResidualScaleUse};
3825    let support = crate::scalar_multipliers::multiplier_support(arch);
3826    let key = |suffix: &str| format!("{arch}.{suffix}");
3827    let mut out = Vec::new();
3828    if support.logit == LogitScaleUse::NotApplied {
3829        out.push((
3830            key("logit_scale"),
3831            "logit multiplier (Granite / Command-R `logits_scaling`); not applied by the generic decoder",
3832            1.0,
3833        ));
3834    }
3835    if support.residual == ResidualScaleUse::NotRead {
3836        out.push((
3837            key("residual_scale"),
3838            "residual multiplier (Granite `residual_multiplier`); not applied by the generic decoder",
3839            1.0,
3840        ));
3841    }
3842    if !support.embedding {
3843        out.push((
3844            key("embedding_scale"),
3845            "embedding multiplier (Granite / MiniCPM `embedding_multiplier`); the generic decoder only scales embeddings for the Gemma and Granite families",
3846            1.0,
3847        ));
3848    }
3849    // Two spellings of one slot, and an architecture reads at most one
3850    // of them: the OTHER stays refused. `grok` reads `output_scale` and
3851    // never `scale`; Granite the reverse; everyone else neither.
3852    if support.attention != AttentionScaleKey::Scale {
3853        out.push((
3854            key("attention.scale"),
3855            "explicit attention score scale (Granite `attention_multiplier`); the generic decoder always uses 1/sqrt(head_dim)",
3856            0.0,
3857        ));
3858    }
3859    if support.attention != AttentionScaleKey::OutputScale {
3860        // Applied as-is by the one graph that reads it (`grok.cpp`, no
3861        // sentinel), so there is no value that means "off" -- the
3862        // no-op here is the kernels' own scale expressed as a key, which
3863        // no converter writes for a non-Grok architecture. A file
3864        // declaring ANY other value is refused.
3865        out.push((
3866            key("attention.output_scale"),
3867            "attention output scale (Grok `attn_output_multiplier`, applied inside its tanh softcap); the generic decoder always uses 1/sqrt(head_dim)",
3868            0.0,
3869        ));
3870    }
3871    out
3872}
3873
3874/// Markdown coverage table for docs / CI drift checks.
3875pub fn coverage_report_markdown() -> String {
3876    let mut lines = vec![
3877        "# Architecture coverage manifest".to_string(),
3878        String::new(),
3879        "Generated from `frink_models::capability::architecture_catalog`.".to_string(),
3880        "Source of truth for names: pinned llama.cpp `LLM_ARCH_NAMES`.".to_string(),
3881        String::new(),
3882        "| GGUF arch | Scope | Family | Memory | Path |".to_string(),
3883        "|---|---|---|---|---|".to_string(),
3884    ];
3885    for p in architecture_catalog() {
3886        let path = match p.path {
3887            ArchPath::GenericGqa { .. } => "generic-gqa",
3888            ArchPath::TestFixture { .. } => "test-fixture",
3889            ArchPath::DedicatedOnly { .. } => "dedicated",
3890            ArchPath::Deferred { .. } => "deferred",
3891        };
3892        lines.push(format!(
3893            "| `{}` | {:?} | {:?} | {:?} | {} |",
3894            p.gguf_name, p.scope, p.family, p.memory, path
3895        ));
3896    }
3897    lines.push(String::new());
3898    lines.join("\n")
3899}
3900
3901#[cfg(test)]
3902mod audit_tests {
3903    use super::*;
3904
3905    /// Every audited name must actually be on the generic path.
3906    ///
3907    /// A name here that resolves to a dedicated engine, or to nothing,
3908    /// is a stale entry claiming evidence for a path it does not use.
3909    #[test]
3910    fn every_audited_name_is_actually_on_the_generic_path() {
3911        for name in AUDITED_GENERIC_GQA {
3912            let profile = resolve_profile(name)
3913                .unwrap_or_else(|| panic!("audited arch `{name}` is not in the catalog"));
3914            assert!(
3915                matches!(profile.path, ArchPath::GenericGqa { .. }),
3916                "`{name}` is listed as an audited GENERIC-path arch but resolves to {:?}",
3917                profile.path
3918            );
3919        }
3920    }
3921
3922    /// The five architectures that were caught computing the wrong
3923    /// thing must never appear here.
3924    ///
3925    /// They are refused outright now, but this pins the intent: the
3926    /// audited list is evidence of correctness, and these are the
3927    /// counter-examples that motivated it.
3928    #[test]
3929    fn the_architectures_that_were_wrong_are_not_claimed_as_audited() {
3930        // `gpt2` left this list on 2026-09-14: it IS audited now, on a
3931        // rule that rotates nothing (`rope_layers::RopeLayers::Never`)
3932        // with its table added (`crate::position_embd`), which is what
3933        // the finding asked for.
3934        assert!(is_audited_generic("gpt2"));
3935        assert_eq!(
3936            crate::rope_layers::rope_layers("gpt2", 12, false, 0),
3937            crate::rope_layers::RopeLayers::Never
3938        );
3939        // The four ALiBi rows followed `gpt2` the same way
3940        // (`crate::alibi`, tests/alibi_graphs.rs): audited, and under
3941        // `Never`.
3942        for name in ["mpt", "refact", "bloom", "jais"] {
3943            assert!(is_audited_generic(name));
3944            assert_eq!(
3945                crate::rope_layers::rope_layers(name, 24, false, 0),
3946                crate::rope_layers::RopeLayers::Never,
3947                "`{name}` positions by ALiBi and must rotate nothing"
3948            );
3949        }
3950    }
3951
3952    /// Every unaudited generic-path architecture either carries a
3953    /// triage verdict or is named on [`TRIAGE_PENDING`] -- never both,
3954    /// never neither.
3955    ///
3956    /// This is the anti-drift gate. Adding a new architecture to the
3957    /// generic catalog without either reading it against llama.cpp or
3958    /// admitting on the pending list that nobody has, fails here.
3959    #[test]
3960    fn every_unaudited_generic_architecture_is_triaged_or_listed_as_pending() {
3961        for p in architecture_catalog() {
3962            if !matches!(p.path, ArchPath::GenericGqa { .. }) || is_audited_generic(p.gguf_name) {
3963                continue;
3964            }
3965            let pending = TRIAGE_PENDING.contains(&p.gguf_name);
3966            match (p.triage, pending) {
3967                (Some(_), false) | (None, true) => {}
3968                (Some(t), true) => panic!(
3969                    "`{}` carries a {:?} verdict AND is still on TRIAGE_PENDING; remove it \
3970                     from the pending list",
3971                    p.gguf_name, t.class
3972                ),
3973                (None, false) => panic!(
3974                    "`{}` is on the generic path, is not audited, has no triage verdict and \
3975                     is not on TRIAGE_PENDING. Read \
3976                     .scratch/llama.cpp/src/models/ for it, or say so on the pending list",
3977                    p.gguf_name
3978                ),
3979            }
3980        }
3981    }
3982
3983    /// A name on [`TRIAGE_PENDING`] that is not an unaudited generic row
3984    /// is a stale to-do: it would keep claiming work that no longer
3985    /// exists, or point at an architecture the loader never asks about.
3986    #[test]
3987    fn nothing_on_the_pending_list_is_stale() {
3988        for name in TRIAGE_PENDING {
3989            let p = resolve_profile(name)
3990                .unwrap_or_else(|| panic!("TRIAGE_PENDING names `{name}`, not in the catalog"));
3991            assert!(
3992                matches!(p.path, ArchPath::GenericGqa { .. }),
3993                "`{name}` is on TRIAGE_PENDING but resolves to {:?}, which never reaches the \
3994                 unaudited refusal",
3995                p.path
3996            );
3997            assert!(
3998                !is_audited_generic(name),
3999                "`{name}` is audited and runs; it does not need a triage verdict"
4000            );
4001        }
4002        // The list is empty because the triage finished, not because it
4003        // was never populated. If a future architecture lands on the
4004        // generic path with no verdict, it belongs here and
4005        // `every_unaudited_generic_architecture_is_triaged_or_listed_as_pending`
4006        // will say so; until then, empty is the completed state.
4007        assert!(
4008            TRIAGE_PENDING.is_empty(),
4009            "TRIAGE_PENDING regrew to {:?}; that is fine, but say so in docs/MODELS.md too",
4010            TRIAGE_PENDING
4011        );
4012    }
4013
4014    /// An audited architecture runs. A triage verdict on one would be a
4015    /// refusal class attached to something that never refuses.
4016    #[test]
4017    fn an_audited_architecture_carries_no_triage_verdict() {
4018        for name in AUDITED_GENERIC_GQA {
4019            assert!(
4020                unaudited_triage(name).is_none(),
4021                "`{name}` is audited and runs, so it must not carry a triage verdict"
4022            );
4023        }
4024    }
4025
4026    /// A verdict has to say something. An empty blocker, or one that
4027    /// cites no llama.cpp source line, is the failure mode this whole
4028    /// item exists to prevent: a refusal that names a blocker nobody
4029    /// checked.
4030    #[test]
4031    fn every_triage_verdict_cites_the_llama_cpp_line_that_decides_it() {
4032        let mut seen = 0;
4033        for p in architecture_catalog() {
4034            let Some(t) = p.triage else { continue };
4035            seen += 1;
4036            assert!(
4037                t.blocker.len() > 80,
4038                "`{}`'s blocker is too short to name anything: {:?}",
4039                p.gguf_name,
4040                t.blocker
4041            );
4042            let cites_llama_cpp =
4043                t.blocker.contains("src/models/") || t.blocker.contains("src/llama-arch.cpp");
4044            assert!(
4045                cites_llama_cpp,
4046                "`{}`'s blocker cites no llama.cpp source: {}",
4047                p.gguf_name, t.blocker
4048            );
4049            if t.class == TriageClass::Unknown {
4050                assert!(
4051                    t.blocker.contains("WOULD SETTLE IT"),
4052                    "`{}` is UNKNOWN but does not say what would settle it",
4053                    p.gguf_name
4054                );
4055            }
4056        }
4057        assert!(
4058            seen == 4,
4059            "every unaudited generic architecture is triaged; found {seen}. \
4060             It was 47 until the triage found `minicpm3` was an MLA model on the \
4061             generic-GQA row and it moved to DedicatedOnly, 46 until five ONE MATCH ARM \
4062             rows -- deepseek, bailingmoe, seed_oss, maincoder, hunyuan-moe -- were admitted \
4063             with libllama-golden fixtures, 41 until seven FIXTURE-AWAY rows -- \
4064             internlm2, xverse, ernie4_5, baichuan, exaone, bailingmoe2, plamo3 -- got \
4065             theirs (tests/fixture_away_graphs.rs), 34 until `gemma`, `hunyuan-dense` \
4066             and `ernie4_5-moe` got theirs, 31 until `olmo2` and `exaone4` -- the \
4067             POST-NORM-ONLY pair, ONE topology and one implementation \
4068             (`crate::norm`) -- got theirs (tests/post_norm_only_graphs.rs), 29 until \
4069             `chatglm` -- the LAST ONE MATCH ARM row -- got its fused-QKV-bias arm and \
4070             its fixture, 28 until `mistral`, `mixtral` and `yi` turned out not to be \
4071             architectures at all (libllama refuses all three strings) and moved to \
4072             DedicatedOnly, and 25 until the three Granite rows -- granite, granitemoe \
4073             and the granite-moe alias -- closed together on ONE implementation of their \
4074             four scalar multipliers (tests/granite_family_graphs.rs), and 22 until \
4075             `olmo` closed on the non-parametric LayerNorm (`crate::norm`, \
4076             tests/olmo_graphs.rs). `olmo` is the FIRST NEW CODE row to close on its own, \
4077             and it says something the other closures do not: its cause is not shared. \
4078             Every `build_norm` call in llama.cpp's 155 graphs was scanned for a null \
4079             weight and all three hits are `olmo.cpp`, so this variant was never going to \
4080             take a second row with it -- see `NON_PARAMETRIC_LAYER_NORM`. `gemma` was the \
4081             last fixture-away row and `chatglm` the last one-match-arm row, so BOTH \
4082             classes are empty, and 21 until `exaone-moe` closed on the per-layer RoPE \
4083             gate (`crate::rope_layers`, tests/no_rope_layer_graphs.rs) -- which is ONE \
4084             cause behind three refusals, and the count moved by one only because the \
4085             other two were not in it: EXAONE-4 32B was refused BY NAME in loader.rs \
4086             and `smollm3` sat in the \"No RoPE at all\" DedicatedOnly group, so both \
4087             raise the audited number without lowering this one, and 20 until `grok` \
4088             and `dbrx` closed together on seams that had landed the day before -- the \
4089             defaults hook and the norm-site table for `grok`, the LayerNorm variant, \
4090             the QKV clamp and the same table for `dbrx` (tests/grok_graphs.rs, \
4091             tests/dbrx_graphs.rs) -- with the clamp also closing `olmo`'s clip_qkv \
4092             refusal by name, and 18 until `arcee` closed on the ungated ReLU-squared FFN \
4093             (`FfnActivation::ReluSqr`, tests/ungated_ffn_graphs.rs) -- ALONE, because the \
4094             constant it shared with `plm` had named the FFN and missed `plm`'s MLA \
4095             attention -- and `deci` and `openelm` closed together on the per-layer shape \
4096             seam (`crate::layer_shapes`, tests/per_layer_shape_graphs.rs), which the scan \
4097             that sized it says reaches `laguna`, `mimo2` and `step35` too, each of which \
4098             still needed something else, and 15 until `afmoe` and `laguna` closed together \
4099             on the gated attention (`crate::attn_gate`, tests/gated_attention_graphs.rs) \
4100             -- one op with two free parameters behind three verdicts, read side by side \
4101             before being called one cause; `step35` keeps its clamp arrays and window \
4102             array and says the gate is done, and `mimo2`'s sinks moved off the gpt-oss \
4103             name onto the tensor without closing it, and 13 until `mellum` closed on the \
4104             per-layer sliding-window ARRAY (`crate::swa_layers`, \
4105             tests/window_array_graphs.rs) -- the seam three verdicts named, and `mellum` \
4106             is the one generic-path graph that HONOURS the array; the same seam lifted the \
4107             over-refusal of every real EXAONE-4 32B / EXAONE-MoE / Olmo-3 export, whose \
4108             array llama.cpp IGNORES (measured: libllama's logits do not move when it is \
4109             inverted), and `crate::mtp_blocks` landed beside it and skips the NextN \
4110             blocks `mimo2` and `step35` named, so both lead with what is left, and 12 \
4111             until `apertus` and `step35` closed together on the per-layer ACTIVATION \
4112             PARAMETER seam (`crate::act_layers`, tests/per_layer_activation_graphs.rs, \
4113             tests/clamped_swiglu_graphs.rs) -- one plumbing question, `layer il runs its \
4114             FFN activation with these scalars`, and two bodies, xIELU and the clamped \
4115             SwiGLU, read side by side before being called one cause; `step35`'s \
4116             half-width rotary landed on `crate::swa_geometry` as a two-valued width and \
4117             lifted Laguna-XS.2's `rope.dimension_count_swa` refusal by name with it, and \
4118             10 until `mistral3` closed on the per-position attention temperature \
4119             (`crate::attn_temperature`, tests/attn_temperature_graphs.rs) -- the reach \
4120             measured first: three graphs of 155 build the input, `llama4` from literals \
4121             on its own engine and `deepseek2` / `mistral4` on the MLA engine, which \
4122             REFUSES the key by name now where it dropped it; and its `yarn_log_multiplier` \
4123             half found YaRN's magnitude term missing for EVERY architecture \
4124             (`crate::yarn_magnitude`), and 9 until `smallthinker` closed on the router \
4125             operand (`crate::router_input`, tests/router_input_graphs.rs) -- the reach \
4126             measured first over every `build_moe_ffn` call site: four graphs pass a \
4127             precomputed `probs_in`, and it is the only one on the generic path whose \
4128             operand is not the normed FFN input; its gated ReLU experts split \
4129             `GluAct::ReluSqr` from `GluAct::Reglu`, because the one variant that had \
4130             served `arcee` by aliasing would have skipped a real gate, and 8 until \
4131             `bitnet` closed on the two norms INSIDE the blocks (`crate::sub_norms`, \
4132             tests/sub_norm_graphs.rs) -- the reach measured first: one graph of 155 \
4133             creates either tensor, so the seam is a `bool` and it closed alone, and 7 \
4134             until `mimo2` closed on the split K/V head width (`crate::kv_head_dims`, \
4135             tests/split_kv_head_dim_graphs.rs) -- the reach measured over the fourteen \
4136             converters that write `value_length`: three write it apart from \
4137             `key_length`, two on the MLA engine, one here, and 6 until `nanbeige` closed \
4138             on the layer loop (`crate::layer_loops`, tests/layer_loop_graphs.rs) -- one \
4139             graph of 155 reads `num_loops`, and the seam is a mapping from logical to \
4140             physical layer rather than a copy of the weights, and 5 until `talkie` closed \
4141             on four things at once (`crate::skip_stream`, `NormOp::RmsNoParams`, \
4142             `QkNormStyle::PerHeadScalar`, the two served `.scale` companions; \
4143             tests/skip_stream_graphs.rs), each one graph of 155, and 4 until `plm` closed \
4144             on the MLA engine (`crate::mla_arch`, `crate::mla_q_proj`, tests/plm_graphs.rs) \
4145             -- the reach measured first: six graphs of 155 create `attn_kv_a_mqa`, three \
4146             have a direct `attn_q` beside it, and on this engine that is `plm` and every \
4147             lite `deepseek2`, which the loader had refused for a key llama.cpp does not \
4148             read; the fixture is the engine's FIRST libllama golden, and 3 until `arctic` \
4149             closed on the parallel dense + MoE layer (`crate::parallel_dense_ffn`, \
4150             `RouterInput::NormedLayerInput`, tests/parallel_dense_ffn_graphs.rs) -- the reach \
4151             measured first: two graphs of 155 sum a dense FFN with their routed output, and \
4152             the other, Grok-2, had been refused by name from a fixture that now has a golden; \
4153             the branch operand is one graph of 155 and a third variant of the seam \
4154             `smallthinker` opened. \
4155             What is left is 1 NEW CODE (`grovemoe`) and one UNKNOWN (`phi4`). The NEW CODE rows \
4156             that have closed are `olmo2`, `exaone4`, the three Granite rows, `exaone-moe`, \
4157             `grok`, `dbrx`, `arcee`, `deci`, `openelm`, `afmoe`, `laguna`, `mellum`, `apertus`, \
4158             `step35`, `mistral3`, `smallthinker`, `bitnet`, `mimo2`, `nanbeige`, `talkie`, \
4159             `plm` and `arctic`, and each closure but `olmo`'s, `arcee`'s, `mellum`'s, \
4160             `mistral3`'s, `smallthinker`'s, `bitnet`'s, `mimo2`'s, `nanbeige`'s, `talkie`'s \
4161             and `plm`'s took more than one row at a time because each found ONE cause \
4162             behind several refusals; `mellum`'s cause IS shared and moved three verdicts, \
4163             but only one of them was closable by it, `mistral3`'s is shared with two rows \
4164             on other engines, `smallthinker`'s mechanism (a precomputed `probs`) is shared \
4165             with three rows whose CAUSE it is not, `bitnet`'s is shared with nothing, and \
4166             `mimo2`'s is shared with the MLA engine, which has carried the two widths \
4167             since it existed, `nanbeige`'s and `talkie`'s with nothing, `plm`'s with \
4168             the lite DeepSeek-V2 checkpoints on the same engine, and `arctic`'s with \
4169             Grok-2, whose refusal by name lifted with it. \
4170             THEN THE COUNT WENT BACK UP, 2 to 10, and that is the honest shape of \
4171             parity with a moving target: the pinned llama.cpp was six weeks and 792 \
4172             commits old on 2026-09-19, and moving the pin to `5b59b83` added fifteen \
4173             graphs. Eight of them are generic-path candidates and are triaged here \
4174             (`granite_swa`, `graniteswitch`, `muse-glimmer`, `maple`, `spark2_5`, \
4175             `hrm_text`, `minimax-01`, `qwen4exp`); four need an attention this engine \
4176             does not have and are `dedicated` refusals (`bailingmoe3`, `dots3note`, \
4177             `hy_v4`, `kimi-k3`); two are text-to-speech and are deferred with the audio \
4178             scope. TWO of the eight were ONE MATCH ARM -- `maple` needs one \
4179             `crate::rope_layers` row and `spark2_5` needed one `crate::attn_gate` row -- \
4180             and BOTH closed the same day, `spark2_5` on exactly the row its \
4181             verdict named and `maple` on that row PLUS one thing no reading of \
4182             `maple.cpp` alone could have found: `llama-graph.cpp:2228` sends four \
4183             architectures, `maple` among them, to `ggml_swiglu_clamp`, which clamps the \
4184             gate BEFORE the SiLU where every other graph clamps the SiLU's output \
4185             (`frink_moe::ClampForm`). `granite_swa` and `muse-glimmer` closed the same day too, the \
4186             first on \
4187             `RopeLayers::FileMask` -- `attention.rope_pattern`, one line of 155 and the \
4188             FIRST upstream graph that lets the FILE say which layers rotate -- so the \
4189             count is 6, and the second on two norm facts nothing else upstream has (a \
4190             WEIGHTLESS RMS on the embeddings and a post-norm epsilon written as a \
4191             literal in the graph). Four of the eight rows the pin brought in closed the \
4192             day it moved, `hrm_text` made it five the day after, and `minimax-01` six the \
4193             day after that, which is what leaves 4: its lightning-attention block is \
4194             `crate::lightning` on the `AttnShape` seam the Qwen3.5 rows built, its \
4195             recurrent mask is the same two keys `crate::gdn::recurrent_layers` already \
4196             read, and the one thing neither reached is the residual topology \
4197             (`crate::normed_residual`: each sublayer's PRE-NORM output, scaled by a \
4198             REQUIRED `residual_scale`, REPLACES the stream its branch joins), which is ONE \
4199             graph of the 155"
4200        );
4201    }
4202
4203    /// The class reaches the message. Two architectures in different
4204    /// classes must not read the same, which is the defect being fixed.
4205    #[test]
4206    fn the_refusal_detail_distinguishes_the_classes() {
4207        // TWO of the four classes have no rows left. `gemma` was the
4208        // last FIXTURE-AWAY row and `chatglm` the last ONE MATCH ARM
4209        // one, and both are audited now, so neither renders a detail at
4210        // all -- `every_triage_verdict_cites_the_llama_cpp_line...`
4211        // pins the count that says so. The two live classes are sampled
4212        // from the catalog; the two empty ones are sampled from
4213        // `headline()` below, because a class with no rows still has to
4214        // render distinctly the day something lands in it again.
4215        //
4216        // `grovemoe`, which used to be `arctic`, `talkie`, `bitnet`,
4217        // `smallthinker`, `dbrx`, `olmo`: the sample keeps moving because
4218        // the rows keep closing. `olmo`'s non-parametric LayerNorm,
4219        // `dbrx`'s weighted one plus its clamp and its `attn_output_norm`
4220        // slot, `smallthinker`'s router operand and gated ReLU experts,
4221        // `bitnet`'s two inner norms, `talkie`'s weightless norms,
4222        // per-head scalar gain, skip stream and projection gains, and
4223        // `arctic`'s parallel dense + MoE layer are all implemented now.
4224        // `grovemoe`'s second expert bank has no single graph to match
4225        // (its verdict says why).
4226        let new_code = unaudited_refusal_detail("grovemoe");
4227        // `phi4` is the only UNKNOWN row left: `mistral`, `mixtral` and
4228        // `yi` used to be the other three and are refused as strings
4229        // now (see `NO_UPSTREAM_ARCH`).
4230        let unknown = unaudited_refusal_detail("phi4");
4231        // TRIAGE_PENDING is empty now that all 47 are read, so the
4232        // untriaged branch is exercised through a name the catalog does
4233        // not carry. The branch has to keep working: it is what a NEW
4234        // architecture added to the catalog would render until somebody
4235        // reads it.
4236        let untriaged = unaudited_refusal_detail("an-arch-nobody-has-read");
4237        assert!(new_code.contains("NEW CODE"), "{new_code}");
4238        assert!(unknown.contains("UNKNOWN"), "{unknown}");
4239        assert!(
4240            untriaged.contains("not done for `an-arch-nobody-has-read` yet"),
4241            "{untriaged}"
4242        );
4243        for a in [&new_code, &unknown, &untriaged] {
4244            for b in [&new_code, &unknown, &untriaged] {
4245                if !std::ptr::eq(a, b) {
4246                    assert_ne!(a, b, "two refusal details are identical");
4247                }
4248            }
4249        }
4250        // The blocker itself, not only the class label, has to be in the
4251        // message -- a class with no specifics is the old refusal with a
4252        // new adjective.
4253        assert!(new_code.contains("grovemoe.cpp"), "{new_code}");
4254        assert!(unknown.contains("LLM_ARCH_NAMES"), "{unknown}");
4255        // The two empty classes still have to be distinguishable.
4256        let labels = [
4257            TriageClass::FixtureAway,
4258            TriageClass::OneMatchArm,
4259            TriageClass::NewCode,
4260            TriageClass::Unknown,
4261        ];
4262        for (i, a) in labels.iter().enumerate() {
4263            for b in &labels[i + 1..] {
4264                assert_ne!(a.label(), b.label());
4265                assert_ne!(a.headline(), b.headline());
4266            }
4267        }
4268    }
4269
4270    /// An architecture nobody has checked is not audited, which is the
4271    /// whole point of the inversion.
4272    #[test]
4273    fn an_unchecked_architecture_is_not_audited() {
4274        assert!(!is_audited_generic("grovemoe"));
4275        assert!(!is_audited_generic("phi4"));
4276        assert!(!is_audited_generic("an-arch-that-does-not-exist"));
4277    }
4278}
4279
4280#[cfg(test)]
4281mod tests {
4282    use super::*;
4283
4284    #[test]
4285    fn known_mainstream_families_resolve() {
4286        assert_eq!(
4287            resolve_architecture("llama"),
4288            Some(ArchPath::GenericGqa {
4289                rope: RopeLayout::Norm
4290            })
4291        );
4292        assert_eq!(
4293            resolve_architecture("qwen2moe"),
4294            Some(ArchPath::GenericGqa {
4295                rope: RopeLayout::Neox
4296            })
4297        );
4298        // `mistral`, `mixtral` and `yi` are NOT here any more. They are
4299        // resolved, but refused: no converter writes those strings and
4300        // libllama refuses them outright, so they are alias rows that
4301        // exist to say "your file is spelled `llama`", not families
4302        // that load. Pinned by
4303        // `the_alias_rows_are_refused_as_strings_no_converter_writes`.
4304        for alias in ["mistral", "mixtral", "yi"] {
4305            assert!(
4306                matches!(
4307                    resolve_architecture(alias),
4308                    Some(ArchPath::DedicatedOnly { .. })
4309                ),
4310                "`{alias}` must be refused, not routed to the generic decoder"
4311            );
4312        }
4313        assert_eq!(
4314            resolve_architecture("phi3"),
4315            Some(ArchPath::GenericGqa {
4316                rope: RopeLayout::Neox
4317            })
4318        );
4319        assert_eq!(
4320            resolve_architecture("phi4"),
4321            Some(ArchPath::GenericGqa {
4322                rope: RopeLayout::Neox
4323            })
4324        );
4325        assert_eq!(
4326            resolve_profile("phi4").map(|p| p.family),
4327            Some(DecoderFamily::PhiFamily)
4328        );
4329        assert_eq!(
4330            resolve_architecture("gemma3"),
4331            Some(ArchPath::GenericGqa {
4332                rope: RopeLayout::Neox
4333            })
4334        );
4335        for arch in ["gemma4", "gemma4-assistant"] {
4336            assert!(
4337                matches!(
4338                    resolve_architecture(arch),
4339                    Some(ArchPath::DedicatedOnly { .. })
4340                ),
4341                "{arch} uses dedicated Gemma4 engine"
4342            );
4343            assert_eq!(
4344                resolve_profile(arch).map(|p| p.family),
4345                Some(DecoderFamily::GemmaFamily)
4346            );
4347        }
4348        assert!(matches!(
4349            resolve_architecture("gemma3n"),
4350            Some(ArchPath::DedicatedOnly { .. })
4351        ));
4352        assert_eq!(
4353            resolve_architecture("deepseek"),
4354            Some(ArchPath::GenericGqa {
4355                rope: RopeLayout::Norm
4356            })
4357        );
4358        assert_eq!(
4359            resolve_profile("qwen3").map(|p| p.qk_norm),
4360            Some(QkNormStyle::PerHead)
4361        );
4362    }
4363
4364    #[test]
4365    fn deepseek2_is_dedicated_mla_not_generic() {
4366        assert!(matches!(
4367            resolve_architecture("deepseek2"),
4368            Some(ArchPath::DedicatedOnly { .. })
4369        ));
4370    }
4371
4372    #[test]
4373    fn unknown_architecture_is_none() {
4374        assert_eq!(resolve_architecture("totally-unknown-arch"), None);
4375        // t5 is registered as dedicated encoder-decoder stub
4376        assert!(matches!(
4377            resolve_architecture("t5"),
4378            Some(ArchPath::DedicatedOnly { .. })
4379        ));
4380    }
4381
4382    #[test]
4383    fn dedicated_paths_are_not_generic() {
4384        assert!(matches!(
4385            resolve_architecture("glm-dsa"),
4386            Some(ArchPath::DedicatedOnly { .. })
4387        ));
4388        assert!(matches!(
4389            resolve_architecture("deepseek4"),
4390            Some(ArchPath::DedicatedOnly { .. })
4391        ));
4392        assert!(
4393            matches!(
4394                resolve_architecture("minimax-m3"),
4395                Some(ArchPath::DedicatedOnly { .. })
4396            ),
4397            "minimax-m3 must fail closed, not silent generic GQA"
4398        );
4399        // `llama4` was a `DedicatedOnly` refusal here and is an audited
4400        // generic row now (tests/llama4_graphs.rs).
4401        assert!(is_audited_generic("llama4"));
4402        // `glm4` and `glm4moe` were DedicatedOnly refusals here and are
4403        // audited generic rows now (tests/glm4_graphs.rs,
4404        // tests/glm4moe_graphs.rs); `glm-dsa` stays on its engine.
4405        assert!(matches!(
4406            resolve_architecture("glm-dsa"),
4407            Some(ArchPath::DedicatedOnly { .. })
4408        ));
4409        assert!(matches!(
4410            resolve_architecture("glm4"),
4411            Some(ArchPath::GenericGqa {
4412                rope: RopeLayout::Norm
4413            })
4414        ));
4415        assert!(is_audited_generic("glm4"));
4416        assert!(matches!(
4417            resolve_architecture("glm4moe"),
4418            Some(ArchPath::GenericGqa {
4419                rope: RopeLayout::Neox
4420            })
4421        ));
4422        assert!(is_audited_generic("glm4moe"));
4423    }
4424
4425    #[test]
4426    fn test_fixtures_remain_loadable() {
4427        for arch in ["ferroxtest", "ferroxtestmoe", "ferroxtestmixed"] {
4428            assert!(matches!(
4429                resolve_architecture(arch),
4430                Some(ArchPath::TestFixture { .. })
4431            ));
4432        }
4433    }
4434
4435    #[test]
4436    fn catalog_has_unique_names() {
4437        let mut seen = std::collections::HashSet::new();
4438        for p in architecture_catalog() {
4439            assert!(
4440                seen.insert(p.gguf_name),
4441                "duplicate arch name {}",
4442                p.gguf_name
4443            );
4444        }
4445    }
4446
4447    #[test]
4448    fn gemma_family_does_not_fail_closed_on_softcap_keys() {
4449        assert!(unsupported_feature_keys("gemma3").is_empty());
4450        assert!(!unsupported_feature_keys("llama").is_empty());
4451    }
4452
4453    /// `grok` applies both softcaps, so neither key refuses it -- while
4454    /// every OTHER gate in that list still does, and every name on the
4455    /// softcap list is an audited row.
4456    ///
4457    /// The first half is what lets a real Grok file load at all:
4458    /// `conversion/grok.py:34` writes `attn_logit_softcapping` for every
4459    /// export. The second half is what keeps the exemption from
4460    /// widening into "softcaps are fine everywhere": `llama` must still
4461    /// refuse them, and the NextN gate must still reach `grok`.
4462    #[test]
4463    fn grok_is_exempt_from_the_softcap_keys_and_nothing_else() {
4464        let keys: Vec<String> = unsupported_feature_keys("grok")
4465            .into_iter()
4466            .map(|(k, _)| k)
4467            .collect();
4468        for softcap in [
4469            "grok.attention.logit_softcapping",
4470            "grok.attn_logit_softcapping",
4471            "grok.final_logit_softcapping",
4472        ] {
4473            assert!(
4474                !keys.iter().any(|k| k == softcap),
4475                "{softcap} must not refuse grok"
4476            );
4477        }
4478        // `nextn_predict_layers` used to be the "non-softcap gate still
4479        // applies" witness here. It is `crate::mtp_blocks` now, keyed by
4480        // which graphs read it, and `grok` is not one: its refusal
4481        // there is `a_non_reader_with_a_nonzero_count_is_refused_and_zero_is_not`.
4482        assert!(
4483            !keys.iter().any(|k| k.ends_with("nextn_predict_layers")),
4484            "nextn_predict_layers is decided by mtp_blocks::trunk_layers, not here: {keys:?}"
4485        );
4486        let llama: Vec<String> = unsupported_feature_keys("llama")
4487            .into_iter()
4488            .map(|(k, _)| k)
4489            .collect();
4490        assert!(llama.iter().any(|k| k == "llama.attn_logit_softcapping"));
4491        for arch in LOGIT_SOFTCAP_ARCHITECTURES {
4492            assert!(
4493                is_audited_generic(arch),
4494                "`{arch}` is on LOGIT_SOFTCAP_ARCHITECTURES without a fixture proving both \
4495                 softcaps"
4496            );
4497        }
4498    }
4499
4500    /// The derived scaling refusals for `grok`: the two keys its graph
4501    /// does not read stay refused, the three it reads do not, and the
4502    /// OTHER attention spelling is refused for Granite.
4503    ///
4504    /// This is the half of `AttentionScaleKey` that a hand-written list
4505    /// could have got wrong silently: `attention.output_scale` had no
4506    /// refusal at all before `grok`, so a Granite file declaring it
4507    /// would have loaded and been ignored.
4508    #[test]
4509    fn the_scaling_refusals_for_grok_are_derived_from_its_attention_key() {
4510        let refused = |arch: &str| -> Vec<String> {
4511            unsupported_scaling_keys(arch)
4512                .into_iter()
4513                .map(|(k, _, _)| k)
4514                .collect()
4515        };
4516        let grok = refused("grok");
4517        assert_eq!(
4518            grok,
4519            vec![
4520                "grok.residual_scale".to_string(),
4521                "grok.attention.scale".to_string()
4522            ],
4523            "{grok:?}"
4524        );
4525        let granite = refused("granite");
4526        assert_eq!(granite, vec!["granite.attention.output_scale".to_string()]);
4527        let llama = refused("llama");
4528        assert!(llama.contains(&"llama.attention.output_scale".to_string()));
4529        assert!(llama.contains(&"llama.attention.scale".to_string()));
4530        assert_eq!(llama.len(), 5, "{llama:?}");
4531    }
4532
4533    /// The parallel residual is served now (`crate::parallel_residual`),
4534    /// and every row that was refused for it is audited: what this test
4535    /// pins is that no row is refused for the residual any more.
4536    /// `cohere2moe` was the last to leave (2026-09-14) and is checked
4537    /// with the rest.
4538    ///
4539    /// `minicpm` used to be on this list and is NOT a residual-topology
4540    /// row -- it runs Granite's graph verbatim
4541    /// (`models.h:1594-1601`). It was here because its three hardcoded
4542    /// multipliers are invisible to a key-presence gate the same way a
4543    /// parallel residual is, which made the list's name wrong about one
4544    /// of its own members. `scalar_multipliers::MultiplierDefaults`
4545    /// applies them now and `tests/minicpm_graphs.rs` is the evidence.
4546    #[test]
4547    fn architectures_with_a_different_residual_topology_are_refused() {
4548        // The sequential-residual siblings stay on the generic path --
4549        // this is a named list, not a family-wide ban.
4550        //
4551        // `phimoe`, `starcoder2` and `nemotron` used to be checked here
4552        // too. They left the generic path for an unrelated reason (the
4553        // required bias tensors pinned by `tests/attn_bias.rs`); what
4554        // still has to hold is that neither they nor the archs below
4555        // are refused for a *residual* reason they do not have.
4556        // `nemotron` and `starcoder2` are generic again
4557        // (`BIASED_LAYER_NORM`, `crate::proj_bias`); `phimoe` is not.
4558        for arch in [
4559            "phi3",
4560            "plamo3",
4561            "qwen2",
4562            "llama",
4563            "nemotron",
4564            "orion",
4565            "starcoder2",
4566            "codeshell",
4567            "jais2",
4568            "stablelm",
4569            "gptneox",
4570            "plamo",
4571            "command-r",
4572            "falcon",
4573            "phi2",
4574            "cohere2",
4575            "cohere2moe",
4576            "phimoe",
4577            "gpt2",
4578            "starcoder",
4579        ] {
4580            assert!(
4581                matches!(
4582                    resolve_architecture(arch),
4583                    Some(ArchPath::GenericGqa { .. })
4584                ),
4585                "{arch} must stay generic"
4586            );
4587        }
4588    }
4589
4590    /// Every architecture appears exactly once, so a refusal added next
4591    /// to an existing entry cannot be shadowed by whichever the lookup
4592    /// happens to find first.
4593    #[test]
4594    fn no_architecture_is_listed_twice() {
4595        let mut seen = std::collections::HashSet::new();
4596        for p in architecture_catalog() {
4597            assert!(seen.insert(p.gguf_name), "{} listed twice", p.gguf_name);
4598        }
4599    }
4600
4601    /// Every key this gate refuses must be a key a converter actually
4602    /// writes, or the gate cannot fire.
4603    ///
4604    /// `unsupported_feature_keys` listed `{arch}.attention.logit_softcapping`.
4605    /// llama.cpp writes `{arch}.attn_logit_softcapping`
4606    /// (`llama-arch.cpp:213`), and no converter emits the first
4607    /// spelling -- so that arm never matched anything, for any non-Gemma
4608    /// architecture, ever. Meanwhile `loader.rs` reads BOTH spellings,
4609    /// so the value was read and applied with the generic formula
4610    /// instead of being refused. For `grok` that is a wrong answer:
4611    /// `grok.cpp` folds the real attention scale into the softcap and
4612    /// passes `kq_scale = 1.0f`.
4613    ///
4614    /// A gate that cannot fire is worse than a missing gate, because it
4615    /// reads as coverage.
4616    #[test]
4617    fn every_refused_key_is_one_a_converter_actually_writes() {
4618        let keys: Vec<String> = unsupported_feature_keys("llama")
4619            .into_iter()
4620            .map(|(k, _)| k)
4621            .collect();
4622
4623        // Transcribed from `llama-arch.cpp`'s LLM_KV_NAMES.
4624        // `llama.attention.sliding_window_pattern` was on this list and
4625        // is deliberately off it: the alternating pattern IS
4626        // implemented (`ModelConfig::layer_sliding_window`, both
4627        // phases), so refusing it was a gate with a false reason that
4628        // also made the loader's own read of the key unreachable. See
4629        // the comment where it used to be. The array-valued case is
4630        // `crate::swa_layers` (`tests/window_array_graphs.rs`).
4631        for real in [
4632            "llama.attn_logit_softcapping",
4633            "llama.final_logit_softcapping",
4634        ] {
4635            assert!(
4636                keys.iter().any(|k| k == real),
4637                "{real} is a key llama.cpp writes and this gate must refuse it; \
4638                 gate currently holds {keys:?}"
4639            );
4640        }
4641
4642        // Gemma implements all three, so it must still be exempt --
4643        // otherwise "fix the spelling" would have turned into "refuse
4644        // every Gemma checkpoint".
4645        assert!(
4646            unsupported_feature_keys("gemma2").is_empty(),
4647            "the Gemma family implements softcap and the SWA pattern"
4648        );
4649        // And the pattern key must not come back: a file carrying it
4650        // gets its period READ, which is what llama.cpp does.
4651        assert!(
4652            !keys.iter().any(|k| k.ends_with("sliding_window_pattern")),
4653            "the SWA pattern is implemented; refusing it makes the loader's read of the \
4654             key dead code: {keys:?}"
4655        );
4656    }
4657
4658    /// llama.cpp picks Gemma's `f_attention_scale` on the LAYER COUNT
4659    /// (`gemma3.cpp:20-33`, `gemma2.cpp:19-29`), and every published
4660    /// Gemma size -- not just 27B -- has `n_embd / n_head != head_dim`.
4661    /// An override derived from "the two widths disagree" would fire on
4662    /// all eight rows below and mis-scale six of them, which is why this
4663    /// walks the real sizes rather than asserting the 27B number alone.
4664    ///
4665    /// Shipped broken: `loader.rs` hardcoded `attention_scale = None`
4666    /// beside a comment naming the 27B exception, so Gemma-2-27B scored
4667    /// `sqrt(144/128)` and Gemma-3-27B `sqrt(168/128)` too large on
4668    /// every layer -- a sharper softmax than the trained one, with no
4669    /// error.
4670    #[test]
4671    fn gemma_27b_is_the_only_size_that_overrides_the_kernel_scale() {
4672        /// One published Gemma size, as its GGUF header declares it.
4673        struct Size {
4674            arch: &'static str,
4675            n_layers: usize,
4676            n_embd: usize,
4677            n_head: usize,
4678            /// `attention.key_length`, llama.cpp's `n_embd_head_k()`.
4679            head_dim: usize,
4680            /// The denominator llama.cpp's 27B branch produces, or
4681            /// `None` where it takes the `1/sqrt(n_embd_head_k)` branch.
4682            want_denom: Option<f32>,
4683        }
4684        let size = |arch, n_layers, n_embd, n_head, head_dim, want_denom| Size {
4685            arch,
4686            n_layers,
4687            n_embd,
4688            n_head,
4689            head_dim,
4690            want_denom,
4691        };
4692        let sizes = [
4693            size("gemma2", 26, 2304, 8, 256, None),         // Gemma-2-2B
4694            size("gemma2", 42, 3584, 16, 256, None),        // Gemma-2-9B
4695            size("gemma2", 46, 4608, 32, 128, Some(144.0)), // Gemma-2-27B
4696            size("gemma3", 18, 640, 4, 256, None),          // Gemma-3-270M
4697            size("gemma3", 26, 1152, 4, 256, None),         // Gemma-3-1B
4698            size("gemma3", 34, 2560, 8, 256, None),         // Gemma-3-4B
4699            size("gemma3", 48, 3840, 16, 256, None),        // Gemma-3-12B
4700            size("gemma3", 62, 5376, 32, 128, Some(168.0)), // Gemma-3-27B
4701        ];
4702        for &Size {
4703            arch,
4704            n_layers,
4705            n_embd,
4706            n_head,
4707            head_dim,
4708            want_denom,
4709        } in &sizes
4710        {
4711            // The premise of the whole test: no Gemma size has
4712            // `n_embd / n_head == head_dim`, so "the widths disagree"
4713            // cannot be the selector.
4714            assert_ne!(
4715                n_embd / n_head,
4716                head_dim,
4717                "{arch}/{n_layers}L: if this ever holds, re-read the derivation"
4718            );
4719            let got = attention_scale_override(arch, n_layers, n_embd, n_head, head_dim);
4720            match want_denom {
4721                None => assert_eq!(
4722                    got, None,
4723                    "{arch}/{n_layers}L takes llama.cpp's 1/sqrt(n_embd_head_k) branch, \
4724                     which the attention kernels already apply"
4725                ),
4726                Some(denom) => {
4727                    let want = 1.0 / denom.sqrt();
4728                    let got = got.unwrap_or_else(|| {
4729                        panic!("{arch}/{n_layers}L is llama.cpp's LLM_TYPE_27B; scale must be set")
4730                    });
4731                    assert!(
4732                        (got - want).abs() < 1e-7,
4733                        "{arch}/{n_layers}L: want 1/sqrt({denom}) = {want}, got {got}"
4734                    );
4735                    // The direction of the correction: the kernels' own
4736                    // scale is the LARGER one, so the override shrinks
4737                    // the scores rather than growing them.
4738                    let kernel = 1.0f32 / (head_dim as f32).sqrt();
4739                    assert!(
4740                        kernel > got,
4741                        "{arch}/{n_layers}L: kernel scale {kernel} must exceed {got}"
4742                    );
4743                }
4744            }
4745        }
4746        // `gemma-embedding`, `gemma3n` and `gemma4` set
4747        // `f_attention_scale` unconditionally in llama.cpp and have no
4748        // `LLM_TYPE_27B` branch; nothing outside gemma2/gemma3 reaches
4749        // this at all.
4750        for arch in ["gemma-embedding", "gemma3n", "gemma4", "llama", "qwen3"] {
4751            assert_eq!(
4752                attention_scale_override(arch, 62, 5376, 32, 128),
4753                None,
4754                "{arch} has no LLM_TYPE_27B branch in llama.cpp"
4755            );
4756        }
4757    }
4758}