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