use serde::Deserialize;
use crate::layers::activation::Activation;
#[derive(Clone, Debug, Deserialize)]
pub struct Qwen3Config {
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_act: Activation,
pub max_position_embeddings: usize,
#[serde(default = "default_eps")]
pub rms_norm_eps: f64,
#[serde(default = "default_theta")]
pub rope_theta: f64,
#[serde(default)]
pub attention_bias: bool,
#[serde(default)]
pub use_sliding_window: bool,
#[serde(default = "default_is_causal")]
pub is_causal: bool,
#[serde(default)]
pub tie_word_embeddings: bool,
}
fn default_act() -> Activation {
Activation::Silu
}
fn default_eps() -> f64 {
1e-6
}
fn default_theta() -> f64 {
1_000_000.0
}
fn default_is_causal() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL: &str = r#"{
"vocab_size": 32,
"hidden_size": 16,
"num_hidden_layers": 2,
"num_attention_heads": 4,
"num_key_value_heads": 2,
"head_dim": 4,
"intermediate_size": 32,
"max_position_embeddings": 16
}"#;
#[test]
fn is_causal_defaults_to_true_when_the_key_is_absent() {
let config: Qwen3Config = serde_json::from_str(MINIMAL).unwrap();
assert!(config.is_causal);
}
#[test]
fn is_causal_parses_false_when_present() {
let json = MINIMAL.replacen('}', r#", "is_causal": false}"#, 1);
let config: Qwen3Config = serde_json::from_str(&json).unwrap();
assert!(!config.is_causal);
}
#[test]
fn tie_word_embeddings_defaults_to_false_and_is_read_when_present() {
let config: Qwen3Config = serde_json::from_str(MINIMAL).unwrap();
assert!(!config.tie_word_embeddings);
let json = MINIMAL.replacen('}', r#", "tie_word_embeddings": true}"#, 1);
let config: Qwen3Config = serde_json::from_str(&json).unwrap();
assert!(config.tie_word_embeddings);
}
}