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}