use combs_formats::{Activation, ModelMetadata, RopeScaling};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum NormFlavor {
#[default]
RmsNorm,
GemmaRmsNorm,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LayerKind {
Global,
Sliding(usize),
}
#[derive(Debug, Clone)]
pub struct ArchSpec {
pub activation: Activation,
pub norm_flavor: NormFlavor,
pub qk_norm: bool,
pub sandwich_norms: bool,
pub embed_scale_sqrt_hidden: bool,
pub attn_logit_softcap: Option<f64>,
pub final_logit_softcap: Option<f64>,
pub query_pre_attn_scalar: Option<f64>,
pub rope_theta: f64,
pub rope_scaling: RopeScaling,
pub rope_local_theta: Option<f64>,
pub layers: Vec<LayerKind>,
}
impl ArchSpec {
pub fn resolve(meta: &ModelMetadata) -> Self {
let pattern = &meta.attention_pattern;
let n = meta.num_hidden_layers;
let arch = meta.architecture.as_str();
let layers: Vec<LayerKind> = match (arch, pattern.sliding_window) {
(_, None) => vec![LayerKind::Global; n],
("gemma3" | "gemma3_text", Some(w)) => (0..n)
.map(|i| {
if pattern.is_global_layer(i) {
LayerKind::Global
} else {
LayerKind::Sliding(w)
}
})
.collect(),
("qwen2" | "qwen3", Some(w)) => {
let full = pattern.max_window_layers.unwrap_or(n);
(0..n)
.map(|i| if i < full { LayerKind::Global } else { LayerKind::Sliding(w) })
.collect()
}
("mistral" | "phi3", Some(w)) => vec![LayerKind::Sliding(w); n],
(_, Some(_)) => vec![LayerKind::Global; n],
};
let gemma = matches!(arch, "gemma3" | "gemma3_text");
ArchSpec {
activation: if gemma { Activation::GeluTanh } else { meta.activation },
norm_flavor: if gemma { NormFlavor::GemmaRmsNorm } else { NormFlavor::RmsNorm },
qk_norm: matches!(arch, "gemma3" | "gemma3_text" | "qwen3"),
sandwich_norms: gemma,
embed_scale_sqrt_hidden: gemma,
attn_logit_softcap: None,
final_logit_softcap: None,
query_pre_attn_scalar: pattern.query_pre_attn_scalar,
rope_theta: meta.rope_theta,
rope_scaling: meta.rope_scaling.clone(),
rope_local_theta: (gemma && pattern.sliding_window.is_some())
.then_some(pattern.rope_local_theta),
layers,
}
}
pub fn windows(&self) -> Vec<Option<usize>> {
self.layers
.iter()
.map(|k| match k {
LayerKind::Global => None,
LayerKind::Sliding(w) => Some(*w),
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use combs_formats::AttentionPattern;
fn meta(arch: &str, layers: usize, pattern: AttentionPattern) -> ModelMetadata {
let mut m = ModelMetadata::diffusion_placeholder(arch);
m.num_hidden_layers = layers;
m.attention_pattern = pattern;
m
}
#[test]
fn gemma_every_nth_layer_is_global() {
let p = AttentionPattern {
sliding_window: Some(512),
pattern: 6,
..AttentionPattern::default()
};
let spec = ArchSpec::resolve(&meta("gemma3_text", 12, p));
for (i, k) in spec.layers.iter().enumerate() {
let expect = if (i + 1) % 6 == 0 { LayerKind::Global } else { LayerKind::Sliding(512) };
assert_eq!(*k, expect, "layer {i}");
}
assert!(spec.qk_norm && spec.sandwich_norms && spec.embed_scale_sqrt_hidden);
assert_eq!(spec.norm_flavor, NormFlavor::GemmaRmsNorm);
assert_eq!(spec.rope_local_theta, Some(10_000.0));
}
#[test]
fn qwen_first_n_layers_are_global() {
let p = AttentionPattern {
sliding_window: Some(4096),
max_window_layers: Some(2),
..AttentionPattern::default()
};
let spec = ArchSpec::resolve(&meta("qwen2", 4, p));
assert_eq!(
spec.layers,
vec![
LayerKind::Global,
LayerKind::Global,
LayerKind::Sliding(4096),
LayerKind::Sliding(4096)
]
);
let p = AttentionPattern {
sliding_window: Some(4096),
..AttentionPattern::default()
};
let spec = ArchSpec::resolve(&meta("qwen2", 4, p));
assert_eq!(spec.layers, vec![LayerKind::Global; 4]);
}
#[test]
fn mistral_slides_everywhere_and_llama_stays_global() {
let p = AttentionPattern {
sliding_window: Some(4096),
..AttentionPattern::default()
};
let spec = ArchSpec::resolve(&meta("mistral", 3, p));
assert_eq!(spec.layers, vec![LayerKind::Sliding(4096); 3]);
assert_eq!(spec.windows(), vec![Some(4096); 3]);
let spec = ArchSpec::resolve(&meta("llama", 3, AttentionPattern::default()));
assert_eq!(spec.layers, vec![LayerKind::Global; 3]);
assert!(!spec.qk_norm && !spec.sandwich_norms);
assert_eq!(spec.norm_flavor, NormFlavor::RmsNorm);
}
}