Skip to main content

frink_models/
parallel_residual.rs

1//! **THE PARALLEL RESIDUAL** -- `x + attn(norm(x)) + ffn(norm(x))`, the
2//! layer shape the generic decoder does not have, and which of llama.cpp's
3//! graphs build it.
4//!
5//! # What it is
6//!
7//! The generic layer is sequential: `h = x + attn(norm1(x))`, then
8//! `h + ffn(norm2(h))`. The parallel shape feeds the FFN the LAYER INPUT
9//! -- normed -- rather than the attention output, and sums the three
10//! terms once. Two spellings exist upstream:
11//!
12//! - **One shared norm** (`SharedNorm`): the FFN reads the SAME normed
13//!   tensor attention read. `stablelm.cpp:135-137` (`cur = inpSA` when
14//!   `ffn_norm` is ABSENT), `phi2.cpp:67,108,116-117`,
15//!   `falcon.cpp:124-135` (Falcon-7B, no `attn_norm_2`),
16//!   `command-r.cpp:68,106-119`, `cohere2.cpp:120-134`,
17//!   `cohere2moe.cpp:222-266`, `plamo.cpp:59-64,97-98,111-112` (`cur =
18//!   sa_inp`, over an RMSNorm).
19//! - **Two norms** (`TwoNorms`): `x + attn(ln1(x)) + ffn(ln2(x))`,
20//!   `gptneox.cpp:143-166` (`use_parallel_residual`, read at `:5`) and
21//!   `falcon.cpp:79-85` (Falcon-40B: `attn_norm_2` present, and it is
22//!   ATTENTION's norm, `attn_norm` staying the FFN's; every Falcon layer
23//!   is parallel, the optional tensor only picks the arm).
24//!
25//! # Reach -- MEASURED
26//!
27//! Over all 155 `src/models/*.cpp` (2026-09-12): `grep -l "par_res\|
28//! parallel residual"` is `gptneox.cpp` and `stablelm.cpp`; a scan for
29//! TWO consecutive `cur = ggml_add(ctx0, cur, ...)` lines -- the
30//! three-term sum spelled out -- is `cohere2`, `cohere2moe` (twice, the
31//! trunk and its MTP block), `command-r`, `falcon`, `phi2` and `plamo`,
32//! with `gptneox` and `stablelm` separating their two adds by a `cb`
33//! line. `gemma4.cpp:260` names an `attn_out` that is ALREADY `cur +
34//! inpL`, so it is sequential and not in the table. Eight graphs, two
35//! spellings, and `stablelm` is the one where BOTH shapes sit behind one
36//! architecture string, decided by tensor presence. (`plamo` was missed
37//! by a first grep that looked for `attn_out` by name; its attention
38//! output is `sa_out`. The two-adds scan is the measurement.)
39//!
40//! # How it is served
41//!
42//! The sequential bodies already compute `h = x + attn(attn_norm(x))`
43//! and then `h + ffn(normed2)`; `(x + attn) + ffn` IS the three-term
44//! sum, so the parallel shape differs from the sequential one in
45//! exactly one thing: WHAT `normed2` is. Sequential: `ffn_norm(h)`.
46//! Parallel: a norm of `x`, the LAYER INPUT, which attention has
47//! already been added on top of by the time the FFN body runs. So the
48//! FFN input is captured BEFORE attention, at the same point
49//! `crate::router_input` captures the router's operand, and the two
50//! travel together as `decoder::ffn_block::BranchInputs` -- one
51//! constructor, `Decoder::branch_inputs`, called at the top of every
52//! layer of every host body, so a body cannot take one and forget the
53//! other. `MoeWeights::parallel` is the per-layer fact (the `stablelm`
54//! row is decided per layer, as `stablelm.cpp:129` decides it), and
55//! for a `SharedNorm` layer the pre-FFN slot is `NormOp::None`,
56//! because there is no tensor and the norm was applied when attention
57//! took its input. `ModelConfig::parallel_residual` is the model-level
58//! fact `Decoder::metal_can_serve_model` reads: every fused Metal
59//! launch bakes `ffn_norm` over the post-attention residual into its
60//! kernel, so a model with a parallel layer stays on the host bodies.
61//!
62//! Evidence (`tests/parallel_residual_graphs.rs`): `gptneox` under the
63//! key, both values, and `plamo`, against libllama; the `stablelm`
64//! shape that was refused by this module for one PR
65//! (`tests/stablelm_graphs.rs`) matches now, and `command-r` followed
66//! on the shared-norm arm over a weighted LayerNorm
67//! (`tests/command_r_graphs.rs`). `use_parallel_residual`
68//! is read by `gptneox.cpp:5` and by NOTHING in `stablelm.cpp`
69//! (libllama's logits with and without it are byte-identical there,
70//! measured), which is why `stablelm`'s rule is the tensor and
71//! `gptneox`'s is the key.
72
73use frink_gguf::TensorSource;
74
75/// Which normed input the FFN reads in a parallel layer.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum ParallelNorm {
78    /// The FFN reads the tensor attention read (`attn_norm(x)`).
79    SharedNorm,
80    /// The FFN reads its own norm of the layer input (`ffn_norm(x)`).
81    TwoNorms,
82}
83
84/// How a graph decides that a layer is parallel.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum ParallelWhen {
87    /// Every layer, unconditionally.
88    Always,
89    /// `blk.N.ffn_norm.weight` is absent (`stablelm.cpp:129`).
90    FfnNormAbsent,
91    /// `{arch}.use_parallel_residual` is true (`gptneox.cpp:5,143`).
92    ParallelResidualKey,
93}
94
95/// One graph that builds the parallel residual.
96#[derive(Debug, Clone, Copy)]
97pub struct ParallelResidual {
98    pub arch: &'static str,
99    /// The arm a parallel layer takes when `second_norm` is absent or
100    /// `None`.
101    pub norm: ParallelNorm,
102    pub when: ParallelWhen,
103    /// A per-layer OPTIONAL tensor whose presence turns the layer into
104    /// the two-norm arm: `falcon.cpp:35-36,79-85`'s `attn_norm_2`, which
105    /// norms the layer input FOR ATTENTION while `attn_norm` keeps
106    /// feeding the FFN (`crate::norm_sites::ATTN_NORM_2_FEEDS_ATTENTION`
107    /// crosses the two slots on such a layer).
108    pub second_norm: Option<&'static str>,
109    pub lines: &'static str,
110}
111
112/// The eight graphs, with the lines. Only the `stablelm` row is on the
113/// generic path today; the others are recorded so the seam that serves
114/// the shape is sized from the table and not from one graph.
115pub const PARALLEL_RESIDUAL_GRAPHS: &[ParallelResidual] = &[
116    ParallelResidual {
117        arch: "stablelm",
118        norm: ParallelNorm::SharedNorm,
119        when: ParallelWhen::FfnNormAbsent,
120        second_norm: None,
121        lines: "src/models/stablelm.cpp:38-39,129-138,147",
122    },
123    ParallelResidual {
124        arch: "gptneox",
125        norm: ParallelNorm::TwoNorms,
126        when: ParallelWhen::ParallelResidualKey,
127        second_norm: None,
128        lines: "src/models/gptneox.cpp:5,143-166",
129    },
130    ParallelResidual {
131        arch: "phi2",
132        norm: ParallelNorm::SharedNorm,
133        when: ParallelWhen::Always,
134        second_norm: None,
135        lines: "src/models/phi2.cpp:67,108,116-117",
136    },
137    ParallelResidual {
138        arch: "falcon",
139        norm: ParallelNorm::SharedNorm,
140        when: ParallelWhen::Always,
141        second_norm: Some("attn_norm_2"),
142        lines: "src/models/falcon.cpp:35-36,79-85,124-135",
143    },
144    ParallelResidual {
145        arch: "command-r",
146        norm: ParallelNorm::SharedNorm,
147        when: ParallelWhen::Always,
148        second_norm: None,
149        lines: "src/models/command-r.cpp:68,106-119",
150    },
151    ParallelResidual {
152        arch: "cohere2",
153        norm: ParallelNorm::SharedNorm,
154        when: ParallelWhen::Always,
155        second_norm: None,
156        lines: "src/models/cohere2.cpp:120-134",
157    },
158    ParallelResidual {
159        arch: "cohere2moe",
160        norm: ParallelNorm::SharedNorm,
161        when: ParallelWhen::Always,
162        second_norm: None,
163        lines: "src/models/cohere2moe.cpp:222-266",
164    },
165    ParallelResidual {
166        arch: "plamo",
167        norm: ParallelNorm::SharedNorm,
168        when: ParallelWhen::Always,
169        second_norm: None,
170        lines: "src/models/plamo.cpp:59-64,97-98,111-112",
171    },
172];
173
174/// The row for an architecture, or `None` for a sequential graph.
175pub fn parallel_residual(arch: &str) -> Option<&'static ParallelResidual> {
176    PARALLEL_RESIDUAL_GRAPHS.iter().find(|row| row.arch == arch)
177}
178
179/// Whether layer `l` of `arch` in `file` is a parallel layer, by the
180/// row's rule. `false` for a graph not in the table.
181pub fn layer_is_parallel(file: &impl TensorSource, arch: &str, l: usize) -> bool {
182    let Some(row) = parallel_residual(arch) else {
183        return false;
184    };
185    match row.when {
186        ParallelWhen::Always => true,
187        ParallelWhen::FfnNormAbsent => file
188            .find_tensor(&format!("blk.{l}.ffn_norm.weight"))
189            .is_none(),
190        ParallelWhen::ParallelResidualKey => file
191            .metadata_bool(&format!("{arch}.use_parallel_residual"))
192            .unwrap_or(false),
193    }
194}
195
196/// The FFN input rule for layer `l` of `arch` in `file`: `Some(norm)`
197/// for a parallel layer, `None` for a sequential one (every graph not in
198/// the table, and a table row whose rule does not fire on this layer).
199pub fn layer_parallel_norm(file: &impl TensorSource, arch: &str, l: usize) -> Option<ParallelNorm> {
200    let row = parallel_residual(arch)?;
201    if !layer_is_parallel(file, arch, l) {
202        return None;
203    }
204    let has_second = row.second_norm.is_some_and(|name| {
205        file.find_tensor(&format!("blk.{l}.{name}.weight"))
206            .is_some()
207    });
208    Some(if has_second {
209        ParallelNorm::TwoNorms
210    } else {
211        row.norm
212    })
213}
214
215/// Whether any trunk layer of `arch` in `file` is parallel: the
216/// model-level fact the fused Metal launches refuse on.
217pub fn model_has_parallel_layer(file: &impl TensorSource, arch: &str, n_layers: usize) -> bool {
218    (0..n_layers).any(|l| layer_is_parallel(file, arch, l))
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224
225    /// Every row names a real architecture string llama.cpp has, and the
226    /// rows the loader can reach are exactly the audited ones; the rest
227    /// are refused or deferred before any tensor is read, which is what
228    /// makes their `when` a recorded fact rather than a live rule.
229    #[test]
230    fn every_row_is_a_registered_architecture_and_the_generic_ones_are_audited() {
231        for row in PARALLEL_RESIDUAL_GRAPHS {
232            assert!(
233                crate::capability::resolve_profile(row.arch).is_some(),
234                "`{}` ({}) is not a registered architecture",
235                row.arch,
236                row.lines
237            );
238            let generic = matches!(
239                crate::capability::resolve_architecture(row.arch),
240                Some(crate::capability::ArchPath::GenericGqa { .. })
241            );
242            assert_eq!(
243                generic,
244                matches!(
245                    row.arch,
246                    "stablelm"
247                        | "gptneox"
248                        | "plamo"
249                        | "command-r"
250                        | "falcon"
251                        | "phi2"
252                        | "cohere2"
253                        | "cohere2moe"
254                ),
255                "`{}`: a generic-path row here must have a golden in \
256                 tests/parallel_residual_graphs.rs or its own graph test",
257                row.arch
258            );
259        }
260    }
261
262    /// The rows are distinct, so a graph cannot be given two rules.
263    #[test]
264    fn no_architecture_has_two_rows() {
265        let mut names: Vec<&str> = PARALLEL_RESIDUAL_GRAPHS.iter().map(|r| r.arch).collect();
266        names.sort_unstable();
267        names.dedup();
268        assert_eq!(names.len(), PARALLEL_RESIDUAL_GRAPHS.len());
269        assert_eq!(PARALLEL_RESIDUAL_GRAPHS.len(), 8, "the measured reach");
270    }
271
272    /// A graph not in the table is sequential on every layer, whatever
273    /// its tensors say.
274    #[test]
275    fn a_sequential_graph_is_never_parallel() {
276        let file = crate::test_source::StubSource::with_tensors(&[]);
277        assert!(!layer_is_parallel(&file, "llama", 0));
278        assert_eq!(layer_parallel_norm(&file, "llama", 0), None);
279        assert!(!model_has_parallel_layer(&file, "llama", 4));
280    }
281
282    /// The `stablelm` rule: parallel exactly when the layer's
283    /// `ffn_norm.weight` is missing, per layer, with the shared norm.
284    #[test]
285    fn stablelm_is_decided_by_ffn_norm_presence_per_layer() {
286        use crate::test_source::StubSource;
287        let sequential =
288            StubSource::with_tensors(&["blk.0.ffn_norm.weight", "blk.1.ffn_norm.weight"]);
289        assert_eq!(layer_parallel_norm(&sequential, "stablelm", 0), None);
290        assert_eq!(layer_parallel_norm(&sequential, "stablelm", 1), None);
291        assert!(!model_has_parallel_layer(&sequential, "stablelm", 2));
292
293        let mixed = StubSource::with_tensors(&["blk.0.ffn_norm.weight"]);
294        assert_eq!(layer_parallel_norm(&mixed, "stablelm", 0), None);
295        assert_eq!(
296            layer_parallel_norm(&mixed, "stablelm", 1),
297            Some(ParallelNorm::SharedNorm)
298        );
299        assert!(model_has_parallel_layer(&mixed, "stablelm", 2));
300        // The trunk length bounds the scan: a parallel block past it is
301        // not this loader's layer.
302        assert!(!model_has_parallel_layer(&mixed, "stablelm", 1));
303    }
304
305    /// The other three rules, on the rows that carry them, so the
306    /// table's `when` column is exercised and not only recorded.
307    #[test]
308    fn the_key_the_second_norm_and_the_unconditional_rules() {
309        use crate::test_source::StubSource;
310        use frink_gguf::GgufValue;
311        let neox_seq = StubSource::with_tensors(&["blk.0.ffn_norm.weight"])
312            .with_key("gptneox.use_parallel_residual", GgufValue::Bool(false));
313        assert!(!layer_is_parallel(&neox_seq, "gptneox", 0));
314        let neox_par = StubSource::with_tensors(&["blk.0.ffn_norm.weight"])
315            .with_key("gptneox.use_parallel_residual", GgufValue::Bool(true));
316        assert_eq!(
317            layer_parallel_norm(&neox_par, "gptneox", 0),
318            Some(ParallelNorm::TwoNorms)
319        );
320
321        // Every Falcon layer is parallel; `attn_norm_2` picks the arm.
322        let falcon_7b = StubSource::with_tensors(&["blk.0.attn_norm.weight"]);
323        assert_eq!(
324            layer_parallel_norm(&falcon_7b, "falcon", 0),
325            Some(ParallelNorm::SharedNorm)
326        );
327        let falcon_40b = StubSource::with_tensors(&["blk.0.attn_norm_2.weight"]);
328
329        let phi2 = StubSource::with_tensors(&["blk.0.ffn_norm.weight"]);
330        assert_eq!(
331            layer_parallel_norm(&phi2, "phi2", 0),
332            Some(ParallelNorm::SharedNorm)
333        );
334        assert_eq!(
335            layer_parallel_norm(&falcon_40b, "falcon", 0),
336            Some(ParallelNorm::TwoNorms)
337        );
338    }
339}