use serde::Deserialize;
use crate::layers::activation::Activation;
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum LayerType {
SlidingAttention,
FullAttention,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Gemma3Config {
pub vocab_size: usize,
pub hidden_size: usize,
pub num_hidden_layers: usize,
pub num_attention_heads: usize,
pub num_key_value_heads: usize,
pub head_dim: usize,
pub intermediate_size: usize,
#[serde(default = "default_act")]
pub hidden_activation: Activation,
pub max_position_embeddings: usize,
#[serde(default = "default_eps")]
pub rms_norm_eps: f64,
#[serde(default = "default_global_theta")]
pub rope_theta: f64,
#[serde(default = "default_local_theta")]
pub rope_local_base_freq: f64,
#[serde(default = "default_window")]
pub sliding_window: usize,
#[serde(default)]
pub layer_types: Option<Vec<LayerType>>,
#[serde(default, rename = "_sliding_window_pattern")]
pub sliding_window_pattern: Option<usize>,
pub query_pre_attn_scalar: f64,
#[serde(default)]
pub use_bidirectional_attention: bool,
#[serde(default)]
pub attention_bias: bool,
#[serde(default)]
pub attn_logit_softcapping: Option<f64>,
#[serde(default)]
pub rope_scaling: Option<serde_json::Value>,
}
fn default_act() -> Activation {
Activation::GeluPytorchTanh
}
fn default_eps() -> f64 {
1e-6
}
fn default_global_theta() -> f64 {
1_000_000.0
}
fn default_local_theta() -> f64 {
10_000.0
}
fn default_window() -> usize {
512
}
impl Gemma3Config {
pub fn layer_types(&self) -> Vec<LayerType> {
if let Some(types) = &self.layer_types {
return types.clone();
}
let pattern = self.sliding_window_pattern.unwrap_or(6).max(1);
(0..self.num_hidden_layers)
.map(|i| {
if (i + 1) % pattern == 0 {
LayerType::FullAttention
} else {
LayerType::SlidingAttention
}
})
.collect()
}
pub fn attention_window(&self) -> usize {
if self.use_bidirectional_attention {
self.sliding_window / 2 + 1
} else {
self.sliding_window
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn config(sliding_window: usize) -> Gemma3Config {
Gemma3Config {
vocab_size: 1,
hidden_size: 1,
num_hidden_layers: 1,
num_attention_heads: 1,
num_key_value_heads: 1,
head_dim: 1,
intermediate_size: 1,
hidden_activation: Activation::GeluPytorchTanh,
max_position_embeddings: 1,
rms_norm_eps: 1e-6,
rope_theta: 1.0,
rope_local_base_freq: 1.0,
sliding_window,
layer_types: None,
sliding_window_pattern: None,
query_pre_attn_scalar: 1.0,
use_bidirectional_attention: true,
attention_bias: false,
attn_logit_softcapping: None,
rope_scaling: None,
}
}
#[test]
fn attention_window_halves_the_stored_span_and_adds_one() {
assert_eq!(config(512).attention_window(), 257);
assert_eq!(config(6).attention_window(), 4);
assert_eq!(config(513).attention_window(), 257);
}
#[test]
fn attention_window_keeps_the_stored_span_without_bidirectional_attention() {
let mut causal = config(512);
causal.use_bidirectional_attention = false;
assert_eq!(causal.attention_window(), 512);
}
}