use crate::config::ModelConfig;
use crate::decoder::AttnWeights;
use crate::loader::{load_weight_matrix, LoadError};
use crate::norm::NormOp;
use crate::norm_sites::NormSites;
use frink_core::cache::{KvCache, PagedKvStore, SharedPagedKv};
use frink_core::{Tensor, WeightMatrix};
use frink_gguf::{GgufValue, TensorSource};
use frink_moe::ExpertWeights;
pub const PER_LAYER_SHAPE_ARCHS: &[(&str, &str)] = &[
(
"deci",
"generic. deci.cpp:30-34 (loader) and :103-105 (graph): all three per layer, with \
n_head == 0 an attention-free layer, n_head_kv == 0 a wo-only layer and n_ff == 0 \
an FFN-free layer",
),
(
"openelm",
"generic. openelm.cpp:26-28 (loader) and :67-69 (graph): all three per layer, sizing \
one fused wqkv per layer",
),
(
"plamo3",
"generic. plamo3.cpp:39-44 (loader) and :110-111 (graph); no published PLaMo-3 \
export writes an array (conversion/plamo.py:27-30 writes scalars), so the seam is \
latent there",
),
(
"laguna",
"generic. laguna.cpp:87-88 (loader) and :176-177 (graph) read n_head(i) per layer; \
KV heads uniform (:86). Closed with the gated attention (`crate::attn_gate`); the \
second rotary width at :50 is `ModelConfig::rope_dim_swa` (`crate::swa_geometry`)",
),
(
"mimo2",
"mimo2.cpp:47-49,111-112 read heads per layer (`swa_num_key_value_heads` on the \
sliding layers, the converter's array). Closed with the split K/V head width \
(`crate::kv_head_dims`, the V width :47-48 sizes apart from K's) and the value \
scale (`crate::attn_value_scale`, :16,181); the sinks at :58 are \
`AttnWeights::sinks`, the is_swa array at :12 is `crate::swa_layers` and the NEXTN \
blocks at :19 are `crate::mtp_blocks`",
),
(
"step35",
"generic. step35.cpp:76-78,208-209 (loader and graph) read heads and KV widths per \
layer. Closed with the per-layer activation seam (`crate::act_layers`, the clamp \
arrays at :28-29) and the two-valued rotary width (`crate::swa_geometry`, :9); the \
gate at :96 is `crate::attn_gate`, the is_swa array at :26 `crate::swa_layers`, \
the NEXTN blocks at :32 `crate::mtp_blocks`",
),
(
"spark2_5",
"generic. spark2-5.cpp:33-37 (loader) and :76-77 (graph) read n_head(i) and \
n_head_kv(i) per layer, sizing the per-head attention gate (:41) by each layer's \
own count. Landed upstream after the 2026-08-04 pin and closed on 2026-09-19 with \
one `crate::attn_gate` row",
),
(
"maple",
"generic. maple.cpp:6 reads `expert_feed_forward_length` as an ARRAY at \
n_layer_all length; the tensors are sized from n_ff_exp() (layer 0) at :27, so \
the array must LOAD even where every entry agrees. Landed upstream after the \
2026-08-04 pin and closed on 2026-09-19 with one `crate::rope_layers` row",
),
(
"nanbeige",
"nanbeige.cpp:24-26 copies each physical layer's arrays to every logical slot; \
`LayerShapes::replicated` does the same and `crate::layer_loops` is the seam the row \
closed on",
),
(
"gemma4",
"dedicated engine. gemma4.cpp:64-67,91 (loader) and :179-184 (graph)",
),
(
"gemma4-assistant",
"dedicated engine. gemma4-assistant.cpp:53-55 (loader) and :134-138 (graph)",
),
(
"jamba",
"generic. jamba.cpp:8-10 (hparams), :37-58 (loader) and :90-92 (graph): n_head_kv(i) \
== 0 marks a Mamba-1 layer (`crate::mamba1`), served since 2026-09-14",
),
(
"lfm2",
"hybrid: n_head_kv(il) == 0 marks a recurrent layer (lfm2.cpp:10,72,130-132)",
),
(
"lfm2moe",
"hybrid: n_head_kv(il) == 0 marks a recurrent layer (lfm2moe.cpp:13,63)",
),
(
"nemotron_h",
"generic. nemotron-h.cpp:9-11 (hparams), :53-98 (loader) and :146-153 (graph): \
n_head_kv(i) == 0 && n_ff(i) == 0 marks a Mamba-2 layer, n_ff(i) == 0 alone an \
attention layer, the rest an FFN-only layer; one block per layer \
(`BLOCK_WITHOUT_FFN_KEEPS_ITS_OUTPUT`), served since 2026-09-14",
),
(
"nemotron_h_moe",
"nemotron-h.cpp:9-11, the same rule; its latent ungated ReLU-squared MoE (:79-90) is \
not served yet",
),
(
"plamo2",
"hybrid: n_head_kv(i) == 0 marks a recurrent layer (plamo2.cpp:19,82-84,218-219)",
),
(
"granitehybrid",
"generic. granite-hybrid.cpp:17-19 (hparams), :58-77 (loader) and :137-140 (graph): \
n_head_kv(i) == 0 marks a Mamba-2 layer (`crate::mamba2`), served since 2026-09-14",
),
(
"granite-hybrid",
"generic. the frink alias of `granitehybrid` (granite-hybrid.cpp:17-19), the same rule",
),
(
"kimi-linear",
"hybrid: n_head_kv(i) == 0 marks a KDA layer (kimi-linear.cpp:18)",
),
];
pub fn per_layer_shapes_read_by_llama_cpp(arch: &str) -> bool {
PER_LAYER_SHAPE_ARCHS.iter().any(|(a, _)| *a == arch)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AttnShape {
Gqa { n_heads: usize, n_kv_heads: usize },
Linear,
Absent,
ShortConv,
Mamba2,
Mamba1,
Plamo2Ssm,
Gdn,
}
pub const BLOCK_WITHOUT_FFN_KEEPS_ITS_OUTPUT: &[&str] =
&["nemotron_h", "nemotron_h_moe", "mamba", "mamba2"];
pub const PURE_RECURRENT: &[(&str, ZeroKvLayer)] = &[
("mamba", ZeroKvLayer::Mamba1),
("mamba2", ZeroKvLayer::Mamba2),
];
pub fn pure_recurrent_block(arch: &str) -> Option<ZeroKvLayer> {
PURE_RECURRENT
.iter()
.find(|(a, _)| *a == arch)
.map(|(_, k)| *k)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ZeroKvLayer {
Linear,
ShortConv,
Mamba2,
Mamba1,
Plamo2,
Mamba2UnlessFfn,
Unserved(&'static str),
}
impl ZeroKvLayer {
pub fn for_arch(arch: &str) -> Self {
if crate::shortconv::is_shortconv_architecture(arch) {
return ZeroKvLayer::ShortConv;
}
match arch {
"granitehybrid" | "granite-hybrid" => ZeroKvLayer::Mamba2,
"jamba" => ZeroKvLayer::Mamba1,
"falcon-h1" => ZeroKvLayer::Unserved(
"no falcon-h1 layer has a zero KV count: falcon-h1.cpp:137-161 runs attention \
AND the Mamba-2 block on every layer (`ModelConfig::parallel_ssm`)",
),
"nemotron_h" | "nemotron_h_moe" => ZeroKvLayer::Mamba2UnlessFfn,
"plamo2" => ZeroKvLayer::Plamo2,
"kimi-linear" => ZeroKvLayer::Unserved(
"a KDA block (kimi-linear.cpp:18), served by `crate::kimi_decoder` and not \
the generic path",
),
_ => ZeroKvLayer::Linear,
}
}
}
impl AttnShape {
pub fn from_counts(
n_heads: usize,
n_kv_heads: usize,
ffn_dim: usize,
zero_kv: ZeroKvLayer,
) -> Result<Self, String> {
match (n_heads, n_kv_heads) {
(0, 0) if zero_kv == ZeroKvLayer::Plamo2 => Ok(AttnShape::Plamo2Ssm),
(0, 0) => Ok(AttnShape::Absent),
(0, kv) => Err(format!(
"head_count 0 with head_count_kv {kv}: deci.cpp:107 would skip attention while \
:44 sizes a zero-wide Q projection"
)),
(_, 0) => match zero_kv {
ZeroKvLayer::Linear => Ok(AttnShape::Linear),
ZeroKvLayer::ShortConv => Ok(AttnShape::ShortConv),
ZeroKvLayer::Mamba2 => Ok(AttnShape::Mamba2),
ZeroKvLayer::Mamba1 => Ok(AttnShape::Mamba1),
ZeroKvLayer::Plamo2 => Ok(AttnShape::Plamo2Ssm),
ZeroKvLayer::Mamba2UnlessFfn if ffn_dim == 0 => Ok(AttnShape::Mamba2),
ZeroKvLayer::Mamba2UnlessFfn => Ok(AttnShape::Absent),
ZeroKvLayer::Unserved(what) => Err(format!(
"head_count_kv 0 marks {what}; `layer_shapes::ZeroKvLayer` is the table"
)),
},
(q, kv) if q % kv != 0 => Err(format!(
"head_count {q} is not a multiple of head_count_kv {kv}"
)),
(n_heads, n_kv_heads) => Ok(AttnShape::Gqa {
n_heads,
n_kv_heads,
}),
}
}
pub fn n_kv_heads(self) -> usize {
match self {
AttnShape::Gqa { n_kv_heads, .. } => n_kv_heads,
AttnShape::Linear
| AttnShape::Absent
| AttnShape::ShortConv
| AttnShape::Mamba2
| AttnShape::Mamba1
| AttnShape::Plamo2Ssm
| AttnShape::Gdn => 0,
}
}
pub fn n_heads(self) -> usize {
match self {
AttnShape::Gqa { n_heads, .. } => n_heads,
AttnShape::Linear
| AttnShape::Absent
| AttnShape::ShortConv
| AttnShape::Mamba2
| AttnShape::Mamba1
| AttnShape::Plamo2Ssm
| AttnShape::Gdn => 0,
}
}
pub fn is_recurrent(self) -> bool {
matches!(
self,
AttnShape::Mamba2 | AttnShape::Mamba1 | AttnShape::Plamo2Ssm | AttnShape::Gdn
)
}
pub fn cache_geometry(
self,
head_dim: usize,
v_head_dim: usize,
hidden_dim: usize,
) -> (usize, usize, usize) {
match self {
AttnShape::Gqa { n_kv_heads, .. } => (n_kv_heads, head_dim, v_head_dim),
AttnShape::Linear
| AttnShape::Absent
| AttnShape::Mamba2
| AttnShape::Mamba1
| AttnShape::Plamo2Ssm
| AttnShape::Gdn => (0, head_dim, v_head_dim),
AttnShape::ShortConv => (1, hidden_dim, 0),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayerShape {
pub attention: AttnShape,
pub ffn_dim: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum LayerShapes {
#[default]
Uniform,
PerLayer(Vec<LayerShape>),
}
impl LayerShapes {
pub fn is_uniform(&self) -> bool {
matches!(self, LayerShapes::Uniform)
}
pub fn replicated(self, n_loops: usize) -> Self {
match self {
LayerShapes::PerLayer(v) if n_loops > 1 => {
LayerShapes::PerLayer(v.iter().copied().cycle().take(v.len() * n_loops).collect())
}
other => other,
}
}
pub fn resolve(
arch: &str,
heads: &[u64],
kv_heads: &[u64],
ffn: Option<&[u64]>,
expert_ffn_dim: usize,
recurrent: Option<&[bool]>,
) -> Result<Self, LoadError> {
let n = heads.len();
assert_eq!(kv_heads.len(), n);
if let Some(recurrent) = recurrent {
assert_eq!(recurrent.len(), n);
let zero_kv = ZeroKvLayer::for_arch(arch);
let mut shapes = Vec::with_capacity(n);
for il in 0..n {
let ffn_dim = ffn.map_or(expert_ffn_dim, |f| f[il] as usize);
let attention = if recurrent[il] {
AttnShape::Gdn
} else {
AttnShape::from_counts(
heads[il] as usize,
kv_heads[il] as usize,
ffn_dim,
zero_kv,
)
.map_err(|why| {
LoadError::UnsupportedFeature(arch.to_string(), format!("blk.{il}: {why}"))
})?
};
shapes.push(LayerShape { attention, ffn_dim });
}
return Ok(LayerShapes::PerLayer(shapes));
}
if let Some(kind) = pure_recurrent_block(arch) {
let shape = AttnShape::from_counts(1, 0, 0, kind)
.map_err(|why| LoadError::UnsupportedFeature(arch.to_string(), why))?;
for il in 0..n {
let ffn_dim = ffn.map_or(0, |f| f[il] as usize);
if heads[il] != 0 || kv_heads[il] != 0 || ffn_dim != 0 {
return Err(LoadError::UnsupportedFeature(
arch.to_string(),
format!(
"blk.{il}: head_count {} / head_count_kv {} / feed_forward_length \
{ffn_dim} on a pure recurrent architecture, whose converter writes \
0 for all three (conversion/mamba.py:155-156) and whose graph has no \
attention and no FFN (mamba.cpp:73-88)",
heads[il], kv_heads[il]
),
));
}
}
return Ok(LayerShapes::PerLayer(vec![
LayerShape {
attention: shape,
ffn_dim: 0
};
n
]));
}
let uniform = heads.windows(2).all(|w| w[0] == w[1])
&& kv_heads.windows(2).all(|w| w[0] == w[1])
&& ffn.is_none_or(|f| f.windows(2).all(|w| w[0] == w[1]));
if uniform {
return Ok(LayerShapes::Uniform);
}
if !per_layer_shapes_read_by_llama_cpp(arch) {
return Err(LoadError::UnsupportedFeature(
arch.to_string(),
format!(
"per-layer head_count / head_count_kv / feed_forward_length arrays whose \
entries differ (heads {heads:?}, kv {kv_heads:?}, ff {ffn:?}). llama.cpp \
reads these arrays for every architecture (llama-model.cpp:1149-1158) but \
this one's graph takes layer 0 through LLAMA_LOAD_LOCALS \
(llama-model.h:760-767), so such a file cannot load there either; \
`layer_shapes::PER_LAYER_SHAPE_ARCHS` lists the ones that index per layer"
),
));
}
let mut shapes = Vec::with_capacity(n);
let zero_kv = ZeroKvLayer::for_arch(arch);
let keeps_output = BLOCK_WITHOUT_FFN_KEEPS_ITS_OUTPUT.contains(&arch);
for il in 0..n {
let ffn_dim = ffn.map_or(expert_ffn_dim, |f| f[il] as usize);
let attention =
AttnShape::from_counts(heads[il] as usize, kv_heads[il] as usize, ffn_dim, zero_kv)
.map_err(|why| {
LoadError::UnsupportedFeature(arch.to_string(), format!("blk.{il}: {why}"))
})?;
if ffn_dim == 0 && attention != AttnShape::Absent && !keeps_output {
return Err(LoadError::UnsupportedFeature(
arch.to_string(),
format!(
"blk.{il}: feed_forward_length 0 on a layer WITH attention \
(head_count {}). deci.cpp:147-149 `continue`s before the residual \
add at :150-153, discarding the attention output that :115-137 \
computed, and frink will not reproduce a dropped branch as the \
reference. An FFN-free layer with head_count 0 is supported",
heads[il]
),
));
}
shapes.push(LayerShape { attention, ffn_dim });
}
Ok(LayerShapes::PerLayer(shapes))
}
}
pub fn read_u64_trunk_layers(
file: &impl TensorSource,
key: &str,
trunk: &crate::mtp_blocks::TrunkLayers,
) -> Result<Option<Vec<u64>>, LoadError> {
Ok(
read_u64_per_layer(file, key, trunk.block_count)?.map(|mut v| {
v.truncate(trunk.n_layers);
v
}),
)
}
pub fn read_u64_per_layer(
file: &impl TensorSource,
key: &str,
n_layers: usize,
) -> Result<Option<Vec<u64>>, LoadError> {
let Some(value) = file.metadata(key) else {
return Ok(None);
};
match value {
GgufValue::Array(items) => {
if items.len() != n_layers {
return Err(LoadError::UnsupportedFeature(
key.to_string(),
format!(
"array of {} entries for {n_layers} layers; llama.cpp refuses this too \
(`key has wrong array length`, llama-model-loader.cpp:464-465)",
items.len()
),
));
}
let mut out = Vec::with_capacity(n_layers);
for (il, item) in items.iter().enumerate() {
out.push(item.as_u64().ok_or_else(|| {
LoadError::UnsupportedFeature(
key.to_string(),
format!("entry {il} is not an unsigned integer: {item:?}"),
)
})?);
}
Ok(Some(out))
}
scalar => scalar
.as_u64()
.map(|v| Some(vec![v; n_layers]))
.ok_or_else(|| LoadError::MissingHparam(key.to_string())),
}
}
fn no_rows(cols: usize) -> WeightMatrix {
WeightMatrix::F32(Tensor::new(Vec::new(), vec![0, cols]))
}
pub(crate) fn load_non_gqa_attention(
shape: AttnShape,
file: &impl TensorSource,
arch: &str,
layer: usize,
norm_sites: &NormSites,
hidden_dim: usize,
) -> Result<AttnWeights, LoadError> {
let mut shortconv = None;
let mut ssm = None;
let (norm_weight, o_proj) = match shape {
AttnShape::Linear => (
norm_sites.load_pre_norm(norm_sites.attn, file, Some(layer))?,
load_weight_matrix(file, &format!("blk.{layer}.attn_output.weight"))?,
),
AttnShape::Absent => (NormOp::None, no_rows(0)),
AttnShape::ShortConv => {
shortconv = Some(crate::shortconv::ShortConv::load(
file, arch, layer, hidden_dim,
)?);
(
norm_sites.load_pre_norm(norm_sites.attn, file, Some(layer))?,
no_rows(0),
)
}
AttnShape::Mamba2 => {
ssm = Some(crate::ssm_block::SsmBlock::Mamba2(
crate::mamba2::Mamba2::load(file, arch, layer, hidden_dim)?,
));
(
norm_sites.load_pre_norm(norm_sites.attn, file, Some(layer))?,
no_rows(0),
)
}
AttnShape::Mamba1 => {
ssm = Some(crate::ssm_block::SsmBlock::Mamba1(
crate::mamba1::Mamba1::load(file, arch, layer, hidden_dim)?,
));
(
norm_sites.load_pre_norm(norm_sites.attn, file, Some(layer))?,
no_rows(0),
)
}
AttnShape::Plamo2Ssm => {
ssm = Some(crate::ssm_block::SsmBlock::Plamo2(
crate::plamo2_ssm::Plamo2Ssm::load(file, arch, layer, hidden_dim)?,
));
(
norm_sites.load_pre_norm(norm_sites.attn, file, Some(layer))?,
no_rows(0),
)
}
AttnShape::Gdn => {
ssm = Some(crate::ssm_block::SsmBlock::Gdn(crate::gdn::Gdn::load(
file, arch, layer, hidden_dim,
)?));
(
norm_sites.load_pre_norm(norm_sites.attn, file, Some(layer))?,
no_rows(0),
)
}
AttnShape::Gqa { .. } => unreachable!("a GQA layer loads its projections"),
};
if let AttnShape::Linear = shape {
if o_proj.rows() != hidden_dim || o_proj.cols() != hidden_dim {
return Err(LoadError::UnsupportedFeature(
format!("blk.{layer}.attn_output.weight"),
format!(
"a wo-only layer's projection is {{n_embd, n_embd}} (deci.cpp:39); this one \
is {}x{} for hidden_dim {hidden_dim}",
o_proj.rows(),
o_proj.cols()
),
));
}
}
Ok(AttnWeights {
q_proj: no_rows(hidden_dim),
k_proj: no_rows(hidden_dim),
v_proj: no_rows(hidden_dim),
o_proj,
norm_weight,
q_norm: None,
k_norm: None,
q_bias: None,
k_bias: None,
v_bias: None,
post_attn_norm: match shape {
AttnShape::Plamo2Ssm => NormSites::load_post_norm(norm_sites.post_attn, file, layer)?,
_ => None,
},
post_ffn_norm: NormSites::load_post_norm(norm_sites.post_ffn, file, layer)?,
output_gate: None,
sinks: None,
attn_sub_norm: None,
o_scale: None,
o_bias: None,
shortconv,
ssm,
q_gate_interleaved: false,
})
}
pub(crate) fn absent_ffn(hidden_dim: usize) -> ExpertWeights {
ExpertWeights {
gate: no_rows(hidden_dim),
up: no_rows(hidden_dim),
down: no_rows(0),
}
}
pub(crate) fn check_gqa_projection_widths(
layer: usize,
shape: AttnShape,
head_dim: usize,
v_head_dim: usize,
hidden_dim: usize,
attn: &AttnWeights,
) -> Result<(), LoadError> {
let AttnShape::Gqa {
n_heads,
n_kv_heads,
} = shape
else {
unreachable!("only GQA layers have Q/K/V to check")
};
let q_rows = if attn.q_gate_interleaved { 2 } else { 1 } * n_heads * head_dim;
let want = [
("attn_q", attn.q_proj.rows(), q_rows),
("attn_k", attn.k_proj.rows(), n_kv_heads * head_dim),
("attn_v", attn.v_proj.rows(), n_kv_heads * v_head_dim),
("attn_output (rows)", attn.o_proj.rows(), hidden_dim),
(
"attn_output (cols)",
attn.o_proj.cols(),
n_heads * v_head_dim,
),
];
for (name, got, expected) in want {
if got != expected {
return Err(LoadError::UnsupportedFeature(
format!("blk.{layer}.{name}.weight"),
format!(
"{got} does not match this layer's head_count {n_heads} / head_count_kv \
{n_kv_heads} x head_dim {head_dim} / v_head_dim {v_head_dim} (expected \
{expected}); llama.cpp's check_tensor_dims refuses the same file"
),
));
}
}
Ok(())
}
impl ModelConfig {
pub fn has_recurrent_layers(&self) -> bool {
self.parallel_ssm
|| (0..self.n_layers).any(|il| self.layer_shape(il).attention.is_recurrent())
}
pub fn layer_cache_geometry(&self, il: usize) -> (usize, usize, usize) {
self.layer_shape(il).attention.cache_geometry(
self.head_dim,
self.v_head_dim(),
self.hidden_dim,
)
}
pub fn layer_shape(&self, il: usize) -> LayerShape {
match &self.layer_shapes {
LayerShapes::Uniform => LayerShape {
attention: AttnShape::Gqa {
n_heads: self.n_heads,
n_kv_heads: self.n_kv_heads,
},
ffn_dim: self.moe.expert_ffn_dim,
},
LayerShapes::PerLayer(v) => v[il],
}
}
pub fn new_kv_caches(&self) -> Vec<KvCache> {
(0..self.n_layers)
.map(|il| {
let (n_kv_heads, head_dim, v_head_dim) = self.layer_cache_geometry(il);
KvCache::new_split(n_kv_heads, head_dim, v_head_dim)
})
.collect()
}
pub fn new_kv_caches_with_capacity(&self, max_seq_len: usize) -> Vec<KvCache> {
(0..self.n_layers)
.map(|il| {
let (n_kv_heads, head_dim, v_head_dim) = self.layer_cache_geometry(il);
KvCache::with_capacity_split(n_kv_heads, head_dim, v_head_dim, max_seq_len)
})
.collect()
}
pub fn new_kv_caches_with_pool(
&self,
pool: &std::sync::Arc<std::sync::Mutex<frink_core::cache::KvBlockPool>>,
max_seq_len: usize,
) -> Result<Vec<KvCache>, frink_core::cache::KvPoolExhausted> {
(0..self.n_layers)
.map(|il| {
let (n_kv_heads, head_dim, v_head_dim) = self.layer_cache_geometry(il);
KvCache::with_pool_split(
n_kv_heads,
head_dim,
v_head_dim,
std::sync::Arc::clone(pool),
max_seq_len,
)
})
.collect()
}
pub fn new_paged_kv(&self, block_size: usize, blocks_per_layer: usize) -> SharedPagedKv {
SharedPagedKv::from_stores(
(0..self.n_layers)
.map(|il| {
let (n_kv_heads, head_dim, v_head_dim) = self.layer_cache_geometry(il);
PagedKvStore::new_split(
block_size,
blocks_per_layer,
n_kv_heads,
head_dim,
v_head_dim,
)
})
.collect(),
)
}
pub fn kv_heads_all_layers(&self) -> usize {
(0..self.n_layers)
.map(|il| self.layer_shape(il).attention.n_kv_heads())
.sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn deci_like() -> Vec<LayerShape> {
vec![
LayerShape {
attention: AttnShape::Gqa {
n_heads: 4,
n_kv_heads: 2,
},
ffn_dim: 16,
},
LayerShape {
attention: AttnShape::Linear,
ffn_dim: 8,
},
LayerShape {
attention: AttnShape::Absent,
ffn_dim: 16,
},
LayerShape {
attention: AttnShape::Absent,
ffn_dim: 0,
},
]
}
#[test]
fn the_two_zero_counts_are_two_different_layer_kinds() {
let deci = ZeroKvLayer::for_arch("deci");
assert_eq!(
AttnShape::from_counts(0, 0, 16, deci),
Ok(AttnShape::Absent)
);
assert_eq!(
AttnShape::from_counts(4, 0, 16, deci),
Ok(AttnShape::Linear)
);
assert_eq!(
AttnShape::from_counts(4, 2, 16, deci),
Ok(AttnShape::Gqa {
n_heads: 4,
n_kv_heads: 2
})
);
assert!(AttnShape::from_counts(0, 2, 16, deci).is_err());
assert!(AttnShape::from_counts(3, 2, 16, deci).is_err());
assert_eq!(AttnShape::Linear.n_kv_heads(), 0);
assert_eq!(AttnShape::Absent.n_heads(), 0);
}
#[test]
fn a_zero_kv_layer_means_what_the_architecture_says() {
let lfm2 = ZeroKvLayer::for_arch("lfm2");
assert_eq!(
AttnShape::from_counts(4, 0, 16, lfm2),
Ok(AttnShape::ShortConv)
);
assert!(matches!(
AttnShape::from_counts(4, 2, 16, lfm2),
Ok(AttnShape::Gqa { .. })
));
assert_eq!(
AttnShape::from_counts(4, 0, 16, ZeroKvLayer::for_arch("jamba")),
Ok(AttnShape::Mamba1)
);
assert_eq!(
AttnShape::from_counts(4, 0, 16, ZeroKvLayer::for_arch("plamo2")),
Ok(AttnShape::Plamo2Ssm)
);
assert_eq!(
AttnShape::from_counts(0, 0, 16, ZeroKvLayer::for_arch("plamo2")),
Ok(AttnShape::Plamo2Ssm)
);
assert_eq!(
AttnShape::from_counts(0, 0, 16, ZeroKvLayer::for_arch("jamba")),
Ok(AttnShape::Absent)
);
let s = LayerShapes::resolve("mamba", &[0, 0], &[0, 0], Some(&[0, 0]), 0, None).unwrap();
let LayerShapes::PerLayer(v) = s else {
panic!("per layer");
};
assert!(v
.iter()
.all(|l| l.attention == AttnShape::Mamba1 && l.ffn_dim == 0));
assert!(
LayerShapes::resolve("mamba2", &[0, 0], &[0, 0], None, 0, None)
.is_ok_and(|s| matches!(s, LayerShapes::PerLayer(_)))
);
assert!(LayerShapes::resolve("mamba", &[4, 4], &[0, 0], None, 0, None).is_err());
assert_eq!(AttnShape::ShortConv.cache_geometry(6, 6, 24), (1, 24, 0));
assert_eq!(
AttnShape::from_counts(4, 0, 16, ZeroKvLayer::for_arch("granitehybrid")),
Ok(AttnShape::Mamba2)
);
let nh = ZeroKvLayer::for_arch("nemotron_h");
assert_eq!(AttnShape::from_counts(4, 0, 0, nh), Ok(AttnShape::Mamba2));
assert_eq!(AttnShape::from_counts(4, 0, 40, nh), Ok(AttnShape::Absent));
assert!(LayerShapes::resolve("deci", &[4, 4], &[2, 2], Some(&[16, 0]), 16, None).is_err());
let s = LayerShapes::resolve(
"nemotron_h",
&[4, 4, 4],
&[0, 2, 0],
Some(&[0, 0, 40]),
16,
None,
)
.unwrap();
let LayerShapes::PerLayer(v) = s else {
panic!("per layer");
};
assert_eq!(
v.iter().map(|l| l.attention).collect::<Vec<_>>(),
[
AttnShape::Mamba2,
AttnShape::Gqa {
n_heads: 4,
n_kv_heads: 2
},
AttnShape::Absent
]
);
assert_eq!(v.iter().map(|l| l.ffn_dim).collect::<Vec<_>>(), [0, 0, 40]);
assert_eq!(AttnShape::Mamba2.cache_geometry(6, 6, 24), (0, 6, 6));
assert!(AttnShape::Mamba2.is_recurrent() && !AttnShape::ShortConv.is_recurrent());
assert_eq!(AttnShape::Linear.cache_geometry(6, 6, 24), (0, 6, 6));
assert_eq!(AttnShape::ShortConv.n_kv_heads(), 0);
let s = LayerShapes::resolve("plamo2", &[4, 4], &[2, 0], None, 16, None).unwrap();
let LayerShapes::PerLayer(v) = s else {
panic!("per layer");
};
assert_eq!(v[1].attention, AttnShape::Plamo2Ssm);
assert!(AttnShape::Plamo2Ssm.is_recurrent());
assert_eq!(AttnShape::Plamo2Ssm.cache_geometry(8, 8, 32), (0, 8, 8));
let s = LayerShapes::resolve("lfm2", &[4, 4], &[0, 2], None, 16, None).unwrap();
let LayerShapes::PerLayer(v) = s else {
panic!("per layer");
};
assert_eq!(v[0].attention, AttnShape::ShortConv);
}
#[test]
fn equal_arrays_collapse_to_uniform_even_for_a_layer_zero_architecture() {
let s = LayerShapes::resolve("llama", &[4, 4], &[2, 2], Some(&[16, 16]), 16, None).unwrap();
assert!(s.is_uniform());
}
#[test]
fn a_varying_array_is_refused_unless_llama_cpp_indexes_it_per_layer() {
let err = LayerShapes::resolve("llama", &[4, 4], &[2, 1], None, 16, None).unwrap_err();
assert!(format!("{err}").contains("PER_LAYER_SHAPE_ARCHS"), "{err}");
let s = LayerShapes::resolve(
"deci",
&[4, 4, 0, 0],
&[2, 0, 0, 0],
Some(&[16, 8, 16, 0]),
16,
None,
)
.unwrap();
assert_eq!(s, LayerShapes::PerLayer(deci_like()));
}
#[test]
fn an_ffn_free_layer_with_attention_is_refused_and_one_without_is_not() {
let err =
LayerShapes::resolve("deci", &[4, 4], &[2, 2], Some(&[16, 0]), 16, None).unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("deci.cpp:147-149"), "{msg}");
assert!(msg.contains("blk.1"), "{msg}");
assert!(LayerShapes::resolve("deci", &[4, 0], &[2, 0], Some(&[16, 0]), 16, None).is_ok());
}
#[test]
fn caches_are_sized_per_layer_and_the_scalar_is_never_consulted() {
let mut cfg = crate::config::glm_5_2();
cfg.n_layers = 4;
cfg.n_heads = 4;
cfg.n_kv_heads = 2;
cfg.head_dim = 8;
cfg.layer_shapes = LayerShapes::PerLayer(deci_like());
let caches = cfg.new_kv_caches();
assert_eq!(
caches.iter().map(|c| c.n_kv_heads).collect::<Vec<_>>(),
vec![2, 0, 0, 0]
);
assert_eq!(cfg.kv_heads_all_layers(), 2);
assert_eq!(cfg.layer_shape(1).attention, AttnShape::Linear);
assert_eq!(cfg.layer_shape(3).ffn_dim, 0);
let mut wrong = KvCache::new(cfg.n_kv_heads, cfg.head_dim);
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
wrong.push(&[], &[]).unwrap();
}));
assert!(res.is_err(), "push must assert the row width");
cfg.layer_shapes = LayerShapes::Uniform;
assert!(cfg
.new_kv_caches()
.iter()
.all(|c| c.n_kv_heads == 2 && c.head_dim == 8));
assert_eq!(cfg.kv_heads_all_layers(), 8);
}
#[test]
fn a_projection_sized_for_another_layer_s_counts_is_refused_naming_the_tensor() {
let m = |rows: usize, cols: usize| {
WeightMatrix::F32(Tensor::new(vec![0.0; rows * cols], vec![rows, cols]))
};
let shape = AttnShape::Gqa {
n_heads: 4,
n_kv_heads: 2,
};
let (head_dim, hidden) = (6, 24);
let build = |q_rows: usize, k_rows: usize| AttnWeights {
q_proj: m(q_rows, hidden),
k_proj: m(k_rows, hidden),
v_proj: m(k_rows, hidden),
o_proj: m(hidden, q_rows),
norm_weight: NormOp::None,
q_norm: None,
k_norm: None,
q_bias: None,
k_bias: None,
v_bias: None,
post_attn_norm: None,
post_ffn_norm: None,
output_gate: None,
sinks: None,
attn_sub_norm: None,
o_scale: None,
o_bias: None,
shortconv: None,
ssm: None,
q_gate_interleaved: false,
};
assert!(
check_gqa_projection_widths(0, shape, head_dim, head_dim, hidden, &build(24, 12))
.is_ok()
);
let err = check_gqa_projection_widths(1, shape, head_dim, head_dim, hidden, &build(24, 18))
.unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("blk.1.attn_k.weight"), "{msg}");
assert!(msg.contains("head_count_kv 2"), "{msg}");
let err = check_gqa_projection_widths(2, shape, head_dim, head_dim, hidden, &build(18, 12))
.unwrap_err();
assert!(format!("{err}").contains("blk.2.attn_q.weight"), "{err}");
}
#[test]
fn the_reach_table_cites_its_lines_and_names_what_each_row_still_needs() {
for (arch, note) in PER_LAYER_SHAPE_ARCHS {
assert!(note.contains(".cpp:"), "`{arch}` cites no line: {note}");
}
let generic: Vec<&str> = PER_LAYER_SHAPE_ARCHS
.iter()
.filter(|(_, n)| n.starts_with("generic"))
.map(|(a, _)| *a)
.collect();
assert_eq!(
generic,
[
"deci",
"openelm",
"plamo3",
"laguna",
"step35",
"spark2_5",
"maple",
"jamba",
"nemotron_h",
"granitehybrid",
"granite-hybrid"
]
);
assert!(per_layer_shapes_read_by_llama_cpp("deci"));
assert!(!per_layer_shapes_read_by_llama_cpp("granite"));
}
}