use crate::error::InferenceError;
use crate::grammar::GrammarEngine;
use crate::stop_reason::StopReason;
use std::path::Path;
use std::sync::Arc;
pub const QWEN_CHAT_IM_END_TOKEN_ID: u32 = 248_046;
pub const QWEN3_THINK_OPEN_TOKEN_ID: u32 = 248_068;
pub const QWEN3_THINK_CLOSE_TOKEN_ID: u32 = 248_069;
pub const QWEN3_NEWLINE_TOKEN_ID: u32 = 198;
pub const MAX_HIDDEN_LAYERS: usize = 4096;
pub(crate) const MAX_VOCAB_SIZE: usize = 4_000_000;
pub(crate) const MAX_EMBEDDING_BYTES: u128 = 34_359_738_368;
pub(crate) const MAX_HEAD_DIM: usize = 2048;
pub(crate) const MAX_NUM_EXPERTS: usize = 4096;
pub(crate) const MAX_FULL_ATTENTION_DIM: usize = 1_048_576;
pub(crate) const MAX_INTERMEDIATE_SIZE: usize = 1_048_576;
pub(crate) const MAX_DENSE_FFN_TENSOR_BYTES: u128 = 4_294_967_296;
pub(crate) const MAX_VISION_DEPTH: usize = 1024;
pub(crate) const MAX_CONFIG_VECTOR_LEN: usize = 1024;
pub(crate) const MAX_LINEAR_OUTPUT_DIM: usize = 1_048_576;
pub(crate) const MAX_GDN_STATE_SIZE: usize = 16_777_216;
pub(crate) const MAX_POSITION_EMBEDDINGS: usize = 4_194_304;
pub(crate) const MAX_ATTENTION_HEADS: usize = 8_192;
pub(crate) const MAX_HIDDEN_SIZE: usize = 1_048_576;
pub(crate) const MAX_LINEAR_NUM_KEY_HEADS: usize = 8_192;
pub(crate) const MAX_LINEAR_NUM_VALUE_HEADS: usize = 8_192;
const GDN_CHUNK_SIZE_MIRROR: usize = 32;
const GDN_MAX_PREFILL_MIRROR: usize = 512;
pub(crate) const MAX_GDN_CHUNK_SCRATCH_BYTES: usize = 1_073_741_824;
pub(crate) const MAX_GDN_SESSION_BYTES: usize = 2_147_483_648;
pub(crate) const MAX_CONV_KERNEL_DIM: usize = 512;
pub(crate) const MAX_GDN_CONV_BUFFER_SIZE: usize = 16_777_216;
pub(crate) const MAX_GDN_CROSS_LAYER_STATE_BYTES: u128 = 4_294_967_296;
pub(crate) const MAX_ROPE_TABLE_BYTES: usize = 1_073_741_824;
pub(crate) const MAX_VISION_HIDDEN_SIZE: usize = 1_048_576;
pub(crate) const MAX_VISION_NUM_HEADS: usize = 8_192;
pub(crate) const MAX_VISION_PATCH_SIZE: usize = 4_096;
pub(crate) const MAX_VISION_SPATIAL_MERGE_SIZE: usize = 64;
pub(crate) const MAX_VISION_OUT_HIDDEN_SIZE: usize = 1_048_576;
pub(crate) const MAX_VISION_TEMPORAL_PATCH_SIZE: usize = 256;
pub(crate) const MAX_VISION_NUM_POSITION_EMBEDDINGS: usize = 16_777_216;
pub(crate) const MAX_VISION_IN_CHANNELS: usize = 256;
pub(crate) const MAX_VISION_TENSOR_BYTES: u128 = 536_870_912;
pub(crate) const MAX_CONFIG_JSON_BYTES: u64 = 8_388_608;
fn read_config_json_bounded(path: &Path) -> Result<String, InferenceError> {
let file_len = std::fs::metadata(path).map_err(InferenceError::Io)?.len();
if file_len > MAX_CONFIG_JSON_BYTES {
return Err(InferenceError::Inference(format!(
"config.json at {} is {file_len} bytes, exceeding MAX_CONFIG_JSON_BYTES \
({MAX_CONFIG_JSON_BYTES})",
path.display()
)));
}
std::fs::read_to_string(path).map_err(InferenceError::Io)
}
pub const QWEN3_NO_THINK_PREFIX: [u32; 6] = [
QWEN3_THINK_OPEN_TOKEN_ID,
QWEN3_NEWLINE_TOKEN_ID,
QWEN3_NEWLINE_TOKEN_ID,
QWEN3_THINK_CLOSE_TOKEN_ID,
QWEN3_NEWLINE_TOKEN_ID,
QWEN3_NEWLINE_TOKEN_ID,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum LayerType {
LinearAttention,
FullAttention,
}
#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
pub struct VisionModelConfig {
pub depth: usize,
pub hidden_size: usize,
pub num_heads: usize,
pub patch_size: usize,
pub spatial_merge_size: usize,
pub out_hidden_size: usize,
pub temporal_patch_size: usize,
pub num_position_embeddings: usize,
pub in_channels: usize,
#[serde(default, deserialize_with = "deserialize_deepstack_visual_indexes")]
pub deepstack_visual_indexes: Vec<usize>,
#[serde(default)]
pub intermediate_size: Option<usize>,
}
impl VisionModelConfig {
pub fn validate(&self) -> Result<(), InferenceError> {
if self.depth == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: depth must be > 0".to_string(),
));
}
if self.depth > MAX_VISION_DEPTH {
return Err(InferenceError::Inference(format!(
"invalid vision_config: depth ({}) exceeds MAX_VISION_DEPTH ({MAX_VISION_DEPTH})",
self.depth
)));
}
if self.hidden_size == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: hidden_size must be > 0".to_string(),
));
}
if self.hidden_size > MAX_VISION_HIDDEN_SIZE {
return Err(InferenceError::Inference(format!(
"invalid vision_config: hidden_size ({}) exceeds MAX_VISION_HIDDEN_SIZE \
({MAX_VISION_HIDDEN_SIZE})",
self.hidden_size
)));
}
if self.num_heads == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: num_heads must be > 0".to_string(),
));
}
if self.num_heads > MAX_VISION_NUM_HEADS {
return Err(InferenceError::Inference(format!(
"invalid vision_config: num_heads ({}) exceeds MAX_VISION_NUM_HEADS \
({MAX_VISION_NUM_HEADS})",
self.num_heads
)));
}
if self.patch_size == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: patch_size must be > 0".to_string(),
));
}
if self.patch_size > MAX_VISION_PATCH_SIZE {
return Err(InferenceError::Inference(format!(
"invalid vision_config: patch_size ({}) exceeds MAX_VISION_PATCH_SIZE \
({MAX_VISION_PATCH_SIZE})",
self.patch_size
)));
}
if self.spatial_merge_size == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: spatial_merge_size must be > 0".to_string(),
));
}
if self.spatial_merge_size > MAX_VISION_SPATIAL_MERGE_SIZE {
return Err(InferenceError::Inference(format!(
"invalid vision_config: spatial_merge_size ({}) exceeds \
MAX_VISION_SPATIAL_MERGE_SIZE ({MAX_VISION_SPATIAL_MERGE_SIZE})",
self.spatial_merge_size
)));
}
if self.out_hidden_size == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: out_hidden_size must be > 0".to_string(),
));
}
if self.out_hidden_size > MAX_VISION_OUT_HIDDEN_SIZE {
return Err(InferenceError::Inference(format!(
"invalid vision_config: out_hidden_size ({}) exceeds \
MAX_VISION_OUT_HIDDEN_SIZE ({MAX_VISION_OUT_HIDDEN_SIZE})",
self.out_hidden_size
)));
}
if self.temporal_patch_size == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: temporal_patch_size must be > 0".to_string(),
));
}
if self.temporal_patch_size > MAX_VISION_TEMPORAL_PATCH_SIZE {
return Err(InferenceError::Inference(format!(
"invalid vision_config: temporal_patch_size ({}) exceeds \
MAX_VISION_TEMPORAL_PATCH_SIZE ({MAX_VISION_TEMPORAL_PATCH_SIZE})",
self.temporal_patch_size
)));
}
if self.num_position_embeddings == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: num_position_embeddings must be > 0".to_string(),
));
}
if self.num_position_embeddings > MAX_VISION_NUM_POSITION_EMBEDDINGS {
return Err(InferenceError::Inference(format!(
"invalid vision_config: num_position_embeddings ({}) exceeds \
MAX_VISION_NUM_POSITION_EMBEDDINGS ({MAX_VISION_NUM_POSITION_EMBEDDINGS})",
self.num_position_embeddings
)));
}
if self.in_channels != 3 {
return Err(InferenceError::Inference(format!(
"invalid vision_config: in_channels must be 3 (RGB); got {} -- the image \
preprocessor is RGB-only and cannot serve any other channel count",
self.in_channels
)));
}
if self.in_channels > MAX_VISION_IN_CHANNELS {
return Err(InferenceError::Inference(format!(
"invalid vision_config: in_channels ({}) exceeds MAX_VISION_IN_CHANNELS \
({MAX_VISION_IN_CHANNELS})",
self.in_channels
)));
}
let side = (self.num_position_embeddings as f64).sqrt().round() as usize;
if side * side != self.num_position_embeddings {
return Err(InferenceError::Inference(format!(
"invalid vision_config: num_position_embeddings ({}) must be a perfect square \
(nearest side {side} squares to {})",
self.num_position_embeddings,
side * side
)));
}
if !self.hidden_size.is_multiple_of(self.num_heads) {
return Err(InferenceError::Inference(format!(
"invalid vision_config: hidden_size ({}) must be divisible by num_heads ({})",
self.hidden_size, self.num_heads
)));
}
for &idx in &self.deepstack_visual_indexes {
if idx >= self.depth {
return Err(InferenceError::Inference(format!(
"invalid vision_config: deepstack_visual_indexes entry {idx} is out of \
range for depth {}",
self.depth
)));
}
}
if let Some(vision_intermediate_size) = self.intermediate_size {
if vision_intermediate_size == 0 {
return Err(InferenceError::Inference(
"invalid vision_config: intermediate_size must be > 0 when present".to_string(),
));
}
if vision_intermediate_size > MAX_INTERMEDIATE_SIZE {
return Err(InferenceError::Inference(format!(
"invalid vision_config: intermediate_size ({vision_intermediate_size}) \
exceeds MAX_INTERMEDIATE_SIZE ({MAX_INTERMEDIATE_SIZE})"
)));
}
}
self.checked_derived_sizes()?;
Ok(())
}
fn checked_derived_sizes(&self) -> Result<(), InferenceError> {
let overflow = || {
InferenceError::Inference(
"invalid vision_config: a derived tensor size overflows usize".to_string(),
)
};
let budget = |elems: usize, what: &str| -> Result<(), InferenceError> {
let bytes = elems as u128 * 4;
if bytes > MAX_VISION_TENSOR_BYTES {
return Err(InferenceError::Inference(format!(
"invalid vision_config: derived tensor {what} ({bytes} bytes) exceeds \
MAX_VISION_TENSOR_BYTES ({MAX_VISION_TENSOR_BYTES})"
)));
}
Ok(())
};
let qkv_out = self.hidden_size.checked_mul(3).ok_or_else(overflow)?;
budget(qkv_out, "qkv_out")?;
let mlp_intermediate = match self.intermediate_size {
Some(v) => v,
None => self.hidden_size.checked_mul(4).ok_or_else(overflow)?,
};
budget(mlp_intermediate, "mlp_intermediate")?;
let merge_in = self
.spatial_merge_size
.checked_mul(self.spatial_merge_size)
.and_then(|sq| sq.checked_mul(self.hidden_size))
.ok_or_else(overflow)?;
let merger_fc1 = merge_in.checked_mul(merge_in).ok_or_else(overflow)?;
budget(merger_fc1, "merger_fc1")?;
let merger_fc2 = self
.out_hidden_size
.checked_mul(merge_in)
.ok_or_else(overflow)?;
budget(merger_fc2, "merger_fc2")?;
let patch_embed_weight = self
.hidden_size
.checked_mul(self.in_channels)
.and_then(|v| v.checked_mul(self.temporal_patch_size))
.and_then(|v| v.checked_mul(self.patch_size))
.and_then(|v| v.checked_mul(self.patch_size))
.ok_or_else(overflow)?;
budget(patch_embed_weight, "patch_embed_weight")?;
let pos_embed = self
.num_position_embeddings
.checked_mul(self.hidden_size)
.ok_or_else(overflow)?;
budget(pos_embed, "pos_embed")?;
Ok(())
}
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(default)]
pub struct Qwen35Config {
pub hidden_size: usize,
pub num_hidden_layers: usize,
pub vocab_size: usize,
pub intermediate_size: usize,
pub rms_norm_eps: f32,
pub num_attention_heads: usize,
pub num_key_value_heads: usize,
pub head_dim: usize,
pub rope_theta: f64,
pub partial_rotary_factor: f32,
#[serde(default)]
pub rope_parameters: Option<RopeParams>,
pub linear_num_key_heads: usize,
#[serde(default)]
pub linear_num_value_heads: Option<usize>,
pub linear_key_head_dim: usize,
pub linear_value_head_dim: usize,
pub linear_conv_kernel_dim: usize,
#[serde(default)]
pub num_experts: Option<usize>,
#[serde(default)]
pub num_experts_per_tok: Option<usize>,
#[serde(default)]
pub moe_intermediate_size: Option<usize>,
#[serde(default)]
pub shared_expert_intermediate_size: Option<usize>,
#[serde(default)]
pub output_router_logits: bool,
#[serde(default)]
pub router_aux_loss_coef: Option<f32>,
#[serde(default = "default_tie_word_embeddings")]
pub tie_word_embeddings: bool,
#[serde(default)]
pub mtp_num_hidden_layers: usize,
#[serde(default)]
pub mtp_use_dedicated_embeddings: bool,
#[serde(default)]
pub quarot_rotation_seed: Option<u64>,
pub full_attention_interval: usize,
#[serde(deserialize_with = "deserialize_bounded_vec")]
pub layer_types: Vec<LayerType>,
#[serde(default, deserialize_with = "deserialize_bounded_vec")]
pub layer_mask: Vec<bool>,
pub eos_token_id: u32,
#[serde(default = "default_max_position_embeddings")]
pub max_position_embeddings: usize,
#[serde(default)]
pub vision_config: Option<VisionModelConfig>,
#[serde(default)]
pub image_token_id: Option<u32>,
#[serde(default)]
pub video_token_id: Option<u32>,
#[serde(default)]
pub vision_start_token_id: Option<u32>,
#[serde(default)]
pub vision_end_token_id: Option<u32>,
}
fn deserialize_bounded_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de>,
{
struct BoundedVecVisitor<T>(std::marker::PhantomData<T>);
impl<'de, T> serde::de::Visitor<'de> for BoundedVecVisitor<T>
where
T: serde::Deserialize<'de>,
{
type Value = Vec<T>;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "a sequence of at most {MAX_HIDDEN_LAYERS} elements")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut vec = Vec::new();
while vec.len() < MAX_HIDDEN_LAYERS {
match seq.next_element()? {
Some(elem) => vec.push(elem),
None => return Ok(vec),
}
}
if seq.next_element::<T>()?.is_some() {
return Err(serde::de::Error::custom(format!(
"sequence exceeds MAX_HIDDEN_LAYERS ({MAX_HIDDEN_LAYERS}) elements"
)));
}
Ok(vec)
}
}
deserializer.deserialize_seq(BoundedVecVisitor(std::marker::PhantomData))
}
fn deserialize_config_vector<'de, D, T>(
deserializer: D,
field_name: &'static str,
) -> Result<Vec<T>, D::Error>
where
D: serde::Deserializer<'de>,
T: serde::Deserialize<'de>,
{
struct BoundedVecVisitor<T> {
field_name: &'static str,
marker: std::marker::PhantomData<T>,
}
impl<'de, T> serde::de::Visitor<'de> for BoundedVecVisitor<T>
where
T: serde::Deserialize<'de>,
{
type Value = Vec<T>;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"a sequence of at most {MAX_CONFIG_VECTOR_LEN} elements for {}",
self.field_name
)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut vec = Vec::new();
while vec.len() < MAX_CONFIG_VECTOR_LEN {
match seq.next_element()? {
Some(elem) => vec.push(elem),
None => return Ok(vec),
}
}
if seq.next_element::<T>()?.is_some() {
return Err(serde::de::Error::custom(format!(
"{} exceeds MAX_CONFIG_VECTOR_LEN ({MAX_CONFIG_VECTOR_LEN}) elements",
self.field_name
)));
}
Ok(vec)
}
}
deserializer.deserialize_seq(BoundedVecVisitor {
field_name,
marker: std::marker::PhantomData,
})
}
fn deserialize_deepstack_visual_indexes<'de, D>(deserializer: D) -> Result<Vec<usize>, D::Error>
where
D: serde::Deserializer<'de>,
{
deserialize_config_vector(deserializer, "deepstack_visual_indexes")
}
fn deserialize_mrope_section<'de, D>(deserializer: D) -> Result<Option<Vec<usize>>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct OptionVisitor;
impl<'de> serde::de::Visitor<'de> for OptionVisitor {
type Value = Option<Vec<usize>>;
fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"an optional sequence of at most {MAX_CONFIG_VECTOR_LEN} elements for \
mrope_section"
)
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
fn visit_unit<E>(self) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(None)
}
fn visit_some<D2>(self, deserializer: D2) -> Result<Self::Value, D2::Error>
where
D2: serde::Deserializer<'de>,
{
deserialize_config_vector(deserializer, "mrope_section").map(Some)
}
}
deserializer.deserialize_option(OptionVisitor)
}
fn default_tie_word_embeddings() -> bool {
true
}
fn default_max_position_embeddings() -> usize {
4096
}
#[derive(Debug, Clone, Default, serde::Deserialize)]
pub struct RopeParams {
#[serde(default)]
pub rope_theta: f64,
#[serde(default)]
pub partial_rotary_factor: Option<f32>,
#[serde(default, deserialize_with = "deserialize_mrope_section")]
pub mrope_section: Option<Vec<usize>>,
#[serde(default)]
pub mrope_interleaved: Option<bool>,
}
#[derive(Debug, serde::Deserialize)]
struct HfQwenConfigFile {
#[serde(default)]
text_config: Option<Qwen35Config>,
#[serde(default)]
tie_word_embeddings: Option<bool>,
#[serde(default)]
vision_config: Option<VisionModelConfig>,
#[serde(default)]
image_token_id: Option<u32>,
#[serde(default)]
video_token_id: Option<u32>,
#[serde(default)]
vision_start_token_id: Option<u32>,
#[serde(default)]
vision_end_token_id: Option<u32>,
}
impl Default for Qwen35Config {
fn default() -> Self {
Self::qwen36_35b_a3b()
}
}
#[derive(Debug, Clone)]
pub struct ValidatedQwen35Config(Qwen35Config);
impl std::ops::Deref for ValidatedQwen35Config {
type Target = Qwen35Config;
fn deref(&self) -> &Qwen35Config {
&self.0
}
}
impl ValidatedQwen35Config {
pub fn into_inner(self) -> Qwen35Config {
self.0
}
}
impl TryFrom<Qwen35Config> for ValidatedQwen35Config {
type Error = InferenceError;
fn try_from(cfg: Qwen35Config) -> Result<Self, InferenceError> {
cfg.validate()
}
}
impl Qwen35Config {
pub fn qwen35_2b() -> Self {
let num_hidden_layers = 24;
let full_attention_interval = 4;
let layer_types = compute_layer_types(num_hidden_layers, full_attention_interval);
Self {
hidden_size: 2048,
num_hidden_layers,
vocab_size: 248_320,
intermediate_size: 6144,
rms_norm_eps: 1e-6,
num_attention_heads: 8,
num_key_value_heads: 2,
head_dim: 256,
rope_theta: 10_000_000.0,
partial_rotary_factor: 0.25,
rope_parameters: None,
linear_num_key_heads: 16,
linear_num_value_heads: Some(16),
linear_key_head_dim: 128,
linear_value_head_dim: 128,
linear_conv_kernel_dim: 4,
num_experts: None,
num_experts_per_tok: None,
moe_intermediate_size: None,
shared_expert_intermediate_size: None,
output_router_logits: false,
router_aux_loss_coef: None,
tie_word_embeddings: true,
full_attention_interval,
layer_types,
layer_mask: vec![true; num_hidden_layers],
eos_token_id: 248_044,
max_position_embeddings: 262_144,
mtp_num_hidden_layers: 0,
mtp_use_dedicated_embeddings: false,
quarot_rotation_seed: None,
vision_config: None,
image_token_id: None,
video_token_id: None,
vision_start_token_id: None,
vision_end_token_id: None,
}
}
pub fn qwen35_0_8b() -> Self {
let num_hidden_layers = 24;
let full_attention_interval = 4;
let layer_types = compute_layer_types(num_hidden_layers, full_attention_interval);
Self {
hidden_size: 1024,
num_hidden_layers,
vocab_size: 248_320,
intermediate_size: 3584,
rms_norm_eps: 1e-6,
num_attention_heads: 8,
num_key_value_heads: 2,
head_dim: 256,
rope_theta: 10_000_000.0,
partial_rotary_factor: 0.25,
rope_parameters: None,
linear_num_key_heads: 16,
linear_num_value_heads: Some(16),
linear_key_head_dim: 128,
linear_value_head_dim: 128,
linear_conv_kernel_dim: 4,
num_experts: None,
num_experts_per_tok: None,
moe_intermediate_size: None,
shared_expert_intermediate_size: None,
output_router_logits: false,
router_aux_loss_coef: None,
tie_word_embeddings: true,
full_attention_interval,
layer_types,
layer_mask: vec![true; num_hidden_layers],
eos_token_id: 248_044,
max_position_embeddings: 262_144,
mtp_num_hidden_layers: 1,
mtp_use_dedicated_embeddings: false,
quarot_rotation_seed: None,
vision_config: None,
image_token_id: None,
video_token_id: None,
vision_start_token_id: None,
vision_end_token_id: None,
}
}
pub fn qwen36_35b_a3b() -> Self {
let num_hidden_layers = 40;
let full_attention_interval = 4;
let layer_types = compute_layer_types(num_hidden_layers, full_attention_interval);
Self {
hidden_size: 2048,
num_hidden_layers,
vocab_size: 248_320,
intermediate_size: 6144,
rms_norm_eps: 1e-6,
num_attention_heads: 16,
num_key_value_heads: 2,
head_dim: 256,
rope_theta: 10_000_000.0,
partial_rotary_factor: 0.25,
rope_parameters: None,
linear_num_key_heads: 16,
linear_num_value_heads: Some(32),
linear_key_head_dim: 128,
linear_value_head_dim: 128,
linear_conv_kernel_dim: 4,
num_experts: Some(256),
num_experts_per_tok: Some(8),
moe_intermediate_size: Some(512),
shared_expert_intermediate_size: Some(512),
output_router_logits: false,
router_aux_loss_coef: Some(0.001),
tie_word_embeddings: false,
full_attention_interval,
layer_types,
layer_mask: vec![true; num_hidden_layers],
eos_token_id: 248_044,
max_position_embeddings: 262_144,
mtp_num_hidden_layers: 1,
mtp_use_dedicated_embeddings: false,
quarot_rotation_seed: None,
vision_config: None,
image_token_id: None,
video_token_id: None,
vision_start_token_id: None,
vision_end_token_id: None,
}
}
pub fn qwen36_27b() -> Self {
let num_hidden_layers = 64;
let full_attention_interval = 4;
let layer_types = compute_layer_types(num_hidden_layers, full_attention_interval);
Self {
hidden_size: 5120,
num_hidden_layers,
vocab_size: 248_320,
intermediate_size: 17408,
rms_norm_eps: 1e-6,
num_attention_heads: 24,
num_key_value_heads: 4,
head_dim: 256,
rope_theta: 10_000_000.0,
partial_rotary_factor: 0.25,
rope_parameters: None,
linear_num_key_heads: 16,
linear_num_value_heads: Some(48),
linear_key_head_dim: 128,
linear_value_head_dim: 128,
linear_conv_kernel_dim: 4,
num_experts: None,
num_experts_per_tok: None,
moe_intermediate_size: None,
shared_expert_intermediate_size: None,
output_router_logits: false,
router_aux_loss_coef: None,
tie_word_embeddings: false,
full_attention_interval,
layer_types,
layer_mask: vec![true; num_hidden_layers],
eos_token_id: 248_044,
max_position_embeddings: 262_144,
mtp_num_hidden_layers: 1,
mtp_use_dedicated_embeddings: false,
quarot_rotation_seed: None,
vision_config: None,
image_token_id: None,
video_token_id: None,
vision_start_token_id: None,
vision_end_token_id: None,
}
}
pub fn from_config_json(path: &Path) -> Result<Self, InferenceError> {
let json = read_config_json_bounded(path)?;
Self::from_config_json_str(&json)
}
pub fn from_config_json_validated(
path: &Path,
) -> Result<ValidatedQwen35Config, InferenceError> {
let json = read_config_json_bounded(path)?;
Self::from_config_json_str_validated(&json)
}
pub fn from_model_dir(dir: &Path) -> Result<Self, InferenceError> {
let config_path = dir.join("config.json");
if !config_path.exists() {
return Err(InferenceError::ModelNotFound(format!(
"missing config.json in {} -- every supported Qwen checkpoint ships one; \
no architecture preset is inferred from a config-less directory",
dir.display()
)));
}
Self::from_config_json(&config_path)
}
pub fn from_model_dir_validated(dir: &Path) -> Result<ValidatedQwen35Config, InferenceError> {
let config_path = dir.join("config.json");
if !config_path.exists() {
return Err(InferenceError::ModelNotFound(format!(
"missing config.json in {} -- every supported Qwen checkpoint ships one; \
no architecture preset is inferred from a config-less directory",
dir.display()
)));
}
Self::from_config_json_validated(&config_path)
}
pub fn from_config_json_str_validated(
json: &str,
) -> Result<ValidatedQwen35Config, InferenceError> {
let parsed: HfQwenConfigFile = serde_json::from_str(json)
.map_err(|e| InferenceError::Inference(format!("invalid Qwen config.json: {e}")))?;
let mut cfg = parsed
.text_config
.unwrap_or_else(Qwen35Config::qwen36_35b_a3b);
if let Some(tie) = parsed.tie_word_embeddings {
cfg.tie_word_embeddings = tie;
}
cfg.vision_config = parsed.vision_config;
cfg.image_token_id = parsed.image_token_id;
cfg.video_token_id = parsed.video_token_id;
cfg.vision_start_token_id = parsed.vision_start_token_id;
cfg.vision_end_token_id = parsed.vision_end_token_id;
if let Some(rp) = &cfg.rope_parameters {
if cfg.rope_theta == 0.0 && rp.rope_theta > 0.0 {
cfg.rope_theta = rp.rope_theta;
}
if let Some(prf) = rp.partial_rotary_factor {
cfg.partial_rotary_factor = prf;
}
}
cfg.validate()
}
pub fn from_config_json_str(json: &str) -> Result<Self, InferenceError> {
Self::from_config_json_str_validated(json).map(ValidatedQwen35Config::into_inner)
}
pub fn validate(self) -> Result<ValidatedQwen35Config, InferenceError> {
let mut cfg = self;
if cfg.num_hidden_layers > MAX_HIDDEN_LAYERS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: num_hidden_layers ({}) exceeds MAX_HIDDEN_LAYERS \
({MAX_HIDDEN_LAYERS})",
cfg.num_hidden_layers
)));
}
if cfg.layer_types.len() != cfg.num_hidden_layers {
if cfg.full_attention_interval == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: full_attention_interval must be > 0 when \
layer_types is absent or its length differs from num_hidden_layers"
.to_string(),
));
}
cfg.layer_types =
compute_layer_types(cfg.num_hidden_layers, cfg.full_attention_interval);
}
cfg.normalize_layer_mask();
if cfg.hidden_size == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: hidden_size must be > 0".to_string(),
));
}
if cfg.hidden_size > MAX_HIDDEN_SIZE {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: hidden_size ({}) exceeds MAX_HIDDEN_SIZE \
({MAX_HIDDEN_SIZE})",
cfg.hidden_size
)));
}
if cfg.vocab_size == 0 || cfg.vocab_size > MAX_VOCAB_SIZE {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: vocab_size ({}) must be > 0 and <= MAX_VOCAB_SIZE \
({MAX_VOCAB_SIZE})",
cfg.vocab_size
)));
}
let embedding_bytes = cfg.vocab_size as u128 * cfg.hidden_size as u128 * 4;
if embedding_bytes > MAX_EMBEDDING_BYTES {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: embedding tensor size ({embedding_bytes} bytes) \
exceeds MAX_EMBEDDING_BYTES ({MAX_EMBEDDING_BYTES}): vocab_size \
({}) * hidden_size ({})",
cfg.vocab_size, cfg.hidden_size
)));
}
if cfg.intermediate_size == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: intermediate_size must be > 0".to_string(),
));
}
if cfg.intermediate_size > MAX_INTERMEDIATE_SIZE {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: intermediate_size ({}) exceeds \
MAX_INTERMEDIATE_SIZE ({MAX_INTERMEDIATE_SIZE})",
cfg.intermediate_size
)));
}
if !cfg.is_moe() {
let dense_ffn_tensor_bytes =
cfg.hidden_size as u128 * cfg.intermediate_size as u128 * 4;
if dense_ffn_tensor_bytes > MAX_DENSE_FFN_TENSOR_BYTES {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: dense FFN tensor size \
({dense_ffn_tensor_bytes} bytes) exceeds MAX_DENSE_FFN_TENSOR_BYTES \
({MAX_DENSE_FFN_TENSOR_BYTES}): hidden_size ({}) * intermediate_size ({})",
cfg.hidden_size, cfg.intermediate_size
)));
}
}
if cfg.num_attention_heads == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: num_attention_heads must be > 0".to_string(),
));
}
if cfg.num_attention_heads > MAX_ATTENTION_HEADS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: num_attention_heads ({}) exceeds \
MAX_ATTENTION_HEADS ({MAX_ATTENTION_HEADS})",
cfg.num_attention_heads
)));
}
if cfg.num_key_value_heads == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: num_key_value_heads must be > 0".to_string(),
));
}
if cfg.num_key_value_heads > MAX_ATTENTION_HEADS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: num_key_value_heads ({}) exceeds \
MAX_ATTENTION_HEADS ({MAX_ATTENTION_HEADS})",
cfg.num_key_value_heads
)));
}
if !cfg
.num_attention_heads
.is_multiple_of(cfg.num_key_value_heads)
{
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: num_attention_heads ({}) must be divisible by \
num_key_value_heads ({})",
cfg.num_attention_heads, cfg.num_key_value_heads
)));
}
if cfg.head_dim == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: head_dim must be > 0".to_string(),
));
}
if cfg.head_dim > MAX_HEAD_DIM {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: head_dim ({}) exceeds MAX_HEAD_DIM ({MAX_HEAD_DIM})",
cfg.head_dim
)));
}
let full_q_dim = cfg
.num_attention_heads
.checked_mul(cfg.head_dim)
.ok_or_else(|| {
InferenceError::Inference(format!(
"invalid Qwen config.json: full-attention q_dim overflows usize: \
num_attention_heads ({}) * head_dim ({})",
cfg.num_attention_heads, cfg.head_dim
))
})?;
full_q_dim.checked_mul(2).ok_or_else(|| {
InferenceError::Inference(format!(
"invalid Qwen config.json: full-attention 2*q_dim scratch multiplication \
overflows usize: q_dim ({full_q_dim})"
))
})?;
let full_kv_dim = cfg
.num_key_value_heads
.checked_mul(cfg.head_dim)
.ok_or_else(|| {
InferenceError::Inference(format!(
"invalid Qwen config.json: full-attention kv_dim overflows usize: \
num_key_value_heads ({}) * head_dim ({})",
cfg.num_key_value_heads, cfg.head_dim
))
})?;
if full_q_dim > MAX_FULL_ATTENTION_DIM {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: full-attention q_dim ({full_q_dim}) exceeds \
MAX_FULL_ATTENTION_DIM ({MAX_FULL_ATTENTION_DIM})"
)));
}
if full_kv_dim > MAX_FULL_ATTENTION_DIM {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: full-attention kv_dim ({full_kv_dim}) exceeds \
MAX_FULL_ATTENTION_DIM ({MAX_FULL_ATTENTION_DIM})"
)));
}
if cfg.num_hidden_layers == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: num_hidden_layers must be > 0".to_string(),
));
}
if cfg.linear_conv_kernel_dim == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: linear_conv_kernel_dim must be > 0".to_string(),
));
}
if cfg.linear_conv_kernel_dim > MAX_CONV_KERNEL_DIM {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: linear_conv_kernel_dim ({}) exceeds \
MAX_CONV_KERNEL_DIM ({MAX_CONV_KERNEL_DIM})",
cfg.linear_conv_kernel_dim
)));
}
if !(cfg.rms_norm_eps.is_finite() && cfg.rms_norm_eps > 0.0) {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: rms_norm_eps ({}) must be finite and > 0.0",
cfg.rms_norm_eps
)));
}
if !(cfg.rope_theta.is_finite() && cfg.rope_theta > 0.0) {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: rope_theta ({}) must be finite and > 0.0",
cfg.rope_theta
)));
}
if !(cfg.partial_rotary_factor.is_finite()
&& cfg.partial_rotary_factor >= 0.0
&& cfg.partial_rotary_factor <= 1.0)
{
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: partial_rotary_factor ({}) must be in [0.0, 1.0]",
cfg.partial_rotary_factor
)));
}
let rope_dim = cfg.rope_dim();
if rope_dim < 2 || !rope_dim.is_multiple_of(2) || rope_dim > cfg.head_dim {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: derived rope_dim ({rope_dim}) must be even, >= 2, \
and <= head_dim ({hd}) (partial_rotary_factor={prf})",
hd = cfg.head_dim,
prf = cfg.partial_rotary_factor,
)));
}
let rope_table_bytes = 4u128 * cfg.max_position_embeddings as u128 * rope_dim as u128;
if rope_table_bytes > MAX_ROPE_TABLE_BYTES as u128 {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: RoPE table size ({rope_table_bytes} bytes) exceeds \
MAX_ROPE_TABLE_BYTES ({MAX_ROPE_TABLE_BYTES}): max_position_embeddings \
({}) * rope_dim ({rope_dim})",
cfg.max_position_embeddings
)));
}
let key_heads = cfg.linear_num_key_heads;
let value_heads = cfg.linear_num_value_heads();
if key_heads == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: linear_num_key_heads must be > 0".to_string(),
));
}
if key_heads > MAX_LINEAR_NUM_KEY_HEADS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: linear_num_key_heads ({key_heads}) exceeds \
MAX_LINEAR_NUM_KEY_HEADS ({MAX_LINEAR_NUM_KEY_HEADS})"
)));
}
if value_heads == 0 || !value_heads.is_multiple_of(key_heads) {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: linear_num_value_heads ({value_heads}) must be a \
positive multiple of linear_num_key_heads ({key_heads})"
)));
}
if value_heads > MAX_LINEAR_NUM_VALUE_HEADS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: linear_num_value_heads ({value_heads}) exceeds \
MAX_LINEAR_NUM_VALUE_HEADS ({MAX_LINEAR_NUM_VALUE_HEADS})"
)));
}
if cfg.linear_key_head_dim == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: linear_key_head_dim must be > 0".to_string(),
));
}
if cfg.linear_value_head_dim == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: linear_value_head_dim must be > 0".to_string(),
));
}
let linear_output_dim = value_heads
.checked_mul(cfg.linear_value_head_dim)
.ok_or_else(|| {
InferenceError::Inference(format!(
"invalid Qwen config.json: linear attention output_dim overflows usize: \
linear_num_value_heads ({value_heads}) * linear_value_head_dim ({})",
cfg.linear_value_head_dim
))
})?;
if linear_output_dim > MAX_LINEAR_OUTPUT_DIM {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: linear attention output_dim ({linear_output_dim}) \
exceeds MAX_LINEAR_OUTPUT_DIM ({MAX_LINEAR_OUTPUT_DIM})"
)));
}
let gdn_state_size = value_heads
.checked_mul(cfg.linear_key_head_dim)
.and_then(|v| v.checked_mul(cfg.linear_value_head_dim))
.ok_or_else(|| {
InferenceError::Inference(format!(
"invalid Qwen config.json: GatedDeltaNet state size overflows usize: \
linear_num_value_heads ({value_heads}) * linear_key_head_dim ({}) * \
linear_value_head_dim ({})",
cfg.linear_key_head_dim, cfg.linear_value_head_dim
))
})?;
if gdn_state_size > MAX_GDN_STATE_SIZE {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: GatedDeltaNet state size ({gdn_state_size}) exceeds \
MAX_GDN_STATE_SIZE ({MAX_GDN_STATE_SIZE})"
)));
}
let num_chunks_upper =
(GDN_MAX_PREFILL_MIRROR as u128).div_ceil(GDN_CHUNK_SIZE_MIRROR as u128);
let chunk_rows_upper =
num_chunks_upper * value_heads as u128 * GDN_CHUNK_SIZE_MIRROR as u128;
let max_head_dim = cfg.linear_key_head_dim.max(cfg.linear_value_head_dim) as u128;
let c2_upper = chunk_rows_upper * GDN_CHUNK_SIZE_MIRROR as u128;
let worst_chunk_scratch_elems = (chunk_rows_upper * max_head_dim).max(c2_upper);
let worst_chunk_scratch_bytes = worst_chunk_scratch_elems * 4;
if worst_chunk_scratch_bytes > MAX_GDN_CHUNK_SCRATCH_BYTES as u128 {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: GatedDeltaNet chunk-scratch size \
({worst_chunk_scratch_bytes} bytes) exceeds MAX_GDN_CHUNK_SCRATCH_BYTES \
({MAX_GDN_CHUNK_SCRATCH_BYTES}): linear_num_value_heads ({value_heads}) * \
max(linear_key_head_dim, linear_value_head_dim) ({max_head_dim})"
)));
}
let bp_upper = GDN_MAX_PREFILL_MIRROR as u128;
let output_dim_u = linear_output_dim as u128;
let kd_u = cfg.linear_key_head_dim as u128;
let vd_u = cfg.linear_value_head_dim as u128;
let key_heads_u = key_heads as u128;
let value_heads_u = value_heads as u128;
let qkv_dim_u = 2 * key_heads_u * kd_u + output_dim_u;
let gdn_qkv_elems = bp_upper * qkv_dim_u;
let gdn_z_elems = bp_upper * output_dim_u;
let gdn_qkvz_elems = qkv_dim_u + output_dim_u;
let gdn_key_scratch_elems = key_heads_u * (2 * kd_u + 1) + value_heads_u * 2;
let gdn_raw_out_elems = output_dim_u;
let chunk_raw_out_elems = bp_upper * output_dim_u;
let qkw_kright_elems = 4 * chunk_rows_upper * kd_u;
let vur_elems = 3 * chunk_rows_upper * vd_u;
let bla_elems = chunk_rows_upper * 2;
let gamma_elems = chunk_rows_upper;
let gamma_end_elems = num_chunks_upper * value_heads_u;
let kkt_qkl_elems = 2 * c2_upper;
let total_session_elems = gdn_qkv_elems
+ gdn_z_elems
+ gdn_qkvz_elems
+ gdn_key_scratch_elems
+ gdn_raw_out_elems
+ chunk_raw_out_elems
+ qkw_kright_elems
+ vur_elems
+ bla_elems
+ gamma_elems
+ gamma_end_elems
+ kkt_qkl_elems;
let total_session_bytes = total_session_elems * 4;
if total_session_bytes > MAX_GDN_SESSION_BYTES as u128 {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: aggregate GatedDeltaNet per-session buffer size \
({total_session_bytes} bytes) exceeds MAX_GDN_SESSION_BYTES \
({MAX_GDN_SESSION_BYTES})"
)));
}
let conv_dim = key_heads
.checked_mul(cfg.linear_key_head_dim)
.and_then(|q| q.checked_mul(2))
.and_then(|q| q.checked_add(linear_output_dim))
.ok_or_else(|| {
InferenceError::Inference(format!(
"invalid Qwen config.json: GatedDeltaNet qkv_dim overflows usize: \
2 * linear_num_key_heads ({key_heads}) * linear_key_head_dim ({}) + \
linear_output_dim ({linear_output_dim})",
cfg.linear_key_head_dim
))
})?;
let conv_buf_len = cfg.linear_conv_kernel_dim - 1;
let gdn_conv_buffer_size = conv_dim.checked_mul(conv_buf_len).ok_or_else(|| {
InferenceError::Inference(format!(
"invalid Qwen config.json: GatedDeltaNet conv buffer size overflows usize: \
linear_qkv_dim ({conv_dim}) * (linear_conv_kernel_dim - 1) ({conv_buf_len})"
))
})?;
if gdn_conv_buffer_size > MAX_GDN_CONV_BUFFER_SIZE {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: GatedDeltaNet conv buffer size \
({gdn_conv_buffer_size}) exceeds MAX_GDN_CONV_BUFFER_SIZE \
({MAX_GDN_CONV_BUFFER_SIZE})"
)));
}
let num_linear_layers = cfg.num_linear_attention_layers() as u128;
let per_layer_state_bytes = (gdn_state_size as u128 + gdn_conv_buffer_size as u128) * 4;
let cross_layer_state_bytes = per_layer_state_bytes * num_linear_layers;
if cross_layer_state_bytes > MAX_GDN_CROSS_LAYER_STATE_BYTES {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: aggregate GatedDeltaNet cross-layer state size \
({cross_layer_state_bytes} bytes) exceeds MAX_GDN_CROSS_LAYER_STATE_BYTES \
({MAX_GDN_CROSS_LAYER_STATE_BYTES}): per-layer state ({per_layer_state_bytes} \
bytes) * num_linear_attention_layers ({num_linear_layers})"
)));
}
if let Some(vision_cfg) = &cfg.vision_config {
vision_cfg.validate()?;
}
if let Some(num_experts_per_tok) = cfg.num_experts_per_tok {
if num_experts_per_tok == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: num_experts_per_tok must be > 0".to_string(),
));
}
if num_experts_per_tok > MAX_NUM_EXPERTS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: num_experts_per_tok ({num_experts_per_tok}) \
exceeds MAX_NUM_EXPERTS ({MAX_NUM_EXPERTS})"
)));
}
}
if let Some(num_experts) = cfg.num_experts {
if num_experts == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: num_experts must be > 0".to_string(),
));
}
if num_experts > MAX_NUM_EXPERTS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: num_experts ({num_experts}) exceeds \
MAX_NUM_EXPERTS ({MAX_NUM_EXPERTS})"
)));
}
if let Some(num_experts_per_tok) = cfg.num_experts_per_tok
&& num_experts_per_tok > num_experts
{
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: num_experts_per_tok \
({num_experts_per_tok}) must not exceed num_experts ({num_experts})"
)));
}
}
if let Some(moe_intermediate_size) = cfg.moe_intermediate_size {
if moe_intermediate_size == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: moe_intermediate_size must be > 0".to_string(),
));
}
if moe_intermediate_size > MAX_INTERMEDIATE_SIZE {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: moe_intermediate_size \
({moe_intermediate_size}) exceeds MAX_INTERMEDIATE_SIZE \
({MAX_INTERMEDIATE_SIZE})"
)));
}
}
if let Some(shared_expert_intermediate_size) = cfg.shared_expert_intermediate_size {
if shared_expert_intermediate_size == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: shared_expert_intermediate_size must be > 0"
.to_string(),
));
}
if shared_expert_intermediate_size > MAX_INTERMEDIATE_SIZE {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: shared_expert_intermediate_size \
({shared_expert_intermediate_size}) exceeds MAX_INTERMEDIATE_SIZE \
({MAX_INTERMEDIATE_SIZE})"
)));
}
}
if cfg.max_position_embeddings == 0 {
return Err(InferenceError::Inference(
"invalid Qwen config.json: max_position_embeddings must be > 0".to_string(),
));
}
if cfg.max_position_embeddings > MAX_POSITION_EMBEDDINGS {
return Err(InferenceError::Inference(format!(
"invalid Qwen config.json: max_position_embeddings ({}) exceeds \
MAX_POSITION_EMBEDDINGS ({MAX_POSITION_EMBEDDINGS})",
cfg.max_position_embeddings
)));
}
Ok(ValidatedQwen35Config(cfg))
}
pub fn linear_num_value_heads(&self) -> usize {
self.linear_num_value_heads.unwrap_or(32)
}
pub fn is_moe(&self) -> bool {
self.num_experts.is_some()
|| self.num_experts_per_tok.is_some()
|| self.moe_intermediate_size.is_some()
|| self.shared_expert_intermediate_size.is_some()
}
pub fn moe_intermediate_size(&self) -> usize {
self.moe_intermediate_size.unwrap_or(self.intermediate_size)
}
pub fn shared_expert_intermediate_size(&self) -> usize {
self.shared_expert_intermediate_size
.unwrap_or_else(|| self.moe_intermediate_size())
}
pub fn num_full_attention_layers(&self) -> usize {
self.layer_types
.iter()
.filter(|t| **t == LayerType::FullAttention)
.count()
}
pub fn num_linear_attention_layers(&self) -> usize {
self.layer_types
.iter()
.filter(|t| **t == LayerType::LinearAttention)
.count()
}
pub fn full_q_dim(&self) -> usize {
self.num_attention_heads * self.head_dim
}
pub fn full_kv_dim(&self) -> usize {
self.num_key_value_heads * self.head_dim
}
pub fn kv_cache_layer_count(&self) -> usize {
self.num_full_attention_layers()
}
pub fn kv_bytes_per_token(&self, dtype_bytes: usize) -> usize {
self.num_full_attention_layers() * 2 * self.full_kv_dim() * dtype_bytes
}
pub fn rope_dim(&self) -> usize {
(self.head_dim as f32 * self.partial_rotary_factor) as usize
}
pub fn linear_qkv_dim(&self) -> usize {
self.linear_num_key_heads * self.linear_key_head_dim + self.linear_num_key_heads * self.linear_key_head_dim + self.linear_num_value_heads() * self.linear_value_head_dim }
pub fn linear_output_dim(&self) -> usize {
self.linear_num_value_heads() * self.linear_value_head_dim
}
pub fn checked_full_q_dim(&self) -> Result<usize, InferenceError> {
self.num_attention_heads
.checked_mul(self.head_dim)
.ok_or_else(|| {
InferenceError::InvalidInput(format!(
"invalid Qwen config: num_attention_heads ({}) * head_dim ({}) overflows usize",
self.num_attention_heads, self.head_dim
))
})
}
pub fn checked_full_kv_dim(&self) -> Result<usize, InferenceError> {
self.num_key_value_heads
.checked_mul(self.head_dim)
.ok_or_else(|| {
InferenceError::InvalidInput(format!(
"invalid Qwen config: num_key_value_heads ({}) * head_dim ({}) overflows usize",
self.num_key_value_heads, self.head_dim
))
})
}
pub fn checked_linear_qkv_dim(&self) -> Result<usize, InferenceError> {
let overflow = || {
InferenceError::InvalidInput(format!(
"invalid Qwen config: linear_num_key_heads ({}) * linear_key_head_dim ({}) + \
linear_num_value_heads ({}) * linear_value_head_dim ({}) overflows usize",
self.linear_num_key_heads,
self.linear_key_head_dim,
self.linear_num_value_heads(),
self.linear_value_head_dim,
))
};
let q = self
.linear_num_key_heads
.checked_mul(self.linear_key_head_dim)
.ok_or_else(overflow)?;
let k = self
.linear_num_key_heads
.checked_mul(self.linear_key_head_dim)
.ok_or_else(overflow)?;
let v = self
.linear_num_value_heads()
.checked_mul(self.linear_value_head_dim)
.ok_or_else(overflow)?;
q.checked_add(k)
.and_then(|qk| qk.checked_add(v))
.ok_or_else(overflow)
}
pub fn checked_linear_output_dim(&self) -> Result<usize, InferenceError> {
self.linear_num_value_heads()
.checked_mul(self.linear_value_head_dim)
.ok_or_else(|| {
InferenceError::InvalidInput(format!(
"invalid Qwen config: linear_num_value_heads ({}) * linear_value_head_dim ({}) \
overflows usize",
self.linear_num_value_heads(),
self.linear_value_head_dim
))
})
}
pub fn checked_linear_conv_len(&self) -> Result<usize, InferenceError> {
let qkv_dim = self.checked_linear_qkv_dim()?;
qkv_dim
.checked_mul(self.linear_conv_kernel_dim)
.ok_or_else(|| {
InferenceError::InvalidInput(format!(
"invalid Qwen config: linear_qkv_dim ({qkv_dim}) * linear_conv_kernel_dim ({}) \
overflows usize",
self.linear_conv_kernel_dim
))
})
}
pub fn is_full_attention(&self, layer_idx: usize) -> bool {
self.layer_types
.get(layer_idx)
.copied()
.unwrap_or(LayerType::LinearAttention)
== LayerType::FullAttention
}
fn normalize_layer_mask(&mut self) {
if self.layer_mask.len() != self.num_hidden_layers {
self.layer_mask = vec![true; self.num_hidden_layers];
}
}
pub fn is_layer_active(&self, layer_idx: usize) -> bool {
self.layer_mask.get(layer_idx).copied().unwrap_or(true)
}
pub fn num_active_layers(&self) -> usize {
(0..self.num_hidden_layers)
.filter(|&i| self.is_layer_active(i))
.count()
}
pub fn num_active_linear_attention_layers(&self) -> usize {
(0..self.num_hidden_layers)
.filter(|&i| self.is_layer_active(i) && !self.is_full_attention(i))
.count()
}
pub fn num_active_full_attention_layers(&self) -> usize {
(0..self.num_hidden_layers)
.filter(|&i| self.is_layer_active(i) && self.is_full_attention(i))
.count()
}
pub fn apply_layer_mask(&mut self, mask: Vec<bool>) {
assert_eq!(
mask.len(),
self.num_hidden_layers,
"layer_mask length {} does not match num_hidden_layers {}",
mask.len(),
self.num_hidden_layers
);
assert!(
mask.iter().any(|&active| active),
"layer_mask must keep at least one active layer"
);
self.layer_mask = mask;
}
pub fn pruned_config(&self, mask: Vec<bool>) -> Self {
let mut cfg = self.clone();
cfg.apply_layer_mask(mask);
cfg
}
}
pub(crate) fn checked_double(value: usize, what: &str) -> Result<usize, InferenceError> {
value.checked_mul(2).ok_or_else(|| {
InferenceError::InvalidInput(format!(
"invalid Qwen config: 2 * {what} ({value}) overflows usize"
))
})
}
#[derive(Clone)]
pub struct GenerateConfig {
pub max_new_tokens: usize,
pub temperature: f32,
pub top_k: usize,
pub top_p: f32,
pub repetition_penalty: f32,
pub seed: Option<u64>,
pub stop_token_ids: Vec<u32>,
pub enable_thinking: bool,
pub enable_mtp: Option<bool>,
pub grammar: Option<Arc<GrammarEngine>>,
pub stop_strings: Vec<String>,
pub reasoning_budget: Option<usize>,
pub logprobs: Option<usize>,
}
impl std::fmt::Debug for GenerateConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GenerateConfig")
.field("max_new_tokens", &self.max_new_tokens)
.field("temperature", &self.temperature)
.field("top_k", &self.top_k)
.field("top_p", &self.top_p)
.field("repetition_penalty", &self.repetition_penalty)
.field("seed", &self.seed)
.field("stop_token_ids", &self.stop_token_ids)
.field("enable_thinking", &self.enable_thinking)
.field("enable_mtp", &self.enable_mtp)
.field("grammar", &self.grammar.as_ref().map(|_| "<GrammarEngine>"))
.field("stop_strings", &self.stop_strings)
.field("reasoning_budget", &self.reasoning_budget)
.field("logprobs", &self.logprobs)
.finish()
}
}
impl Default for GenerateConfig {
fn default() -> Self {
Self {
max_new_tokens: 256,
temperature: 0.7,
top_k: 50,
top_p: 0.9,
repetition_penalty: 1.1,
seed: None,
stop_token_ids: vec![QWEN_CHAT_IM_END_TOKEN_ID],
enable_thinking: true,
enable_mtp: None,
grammar: None,
stop_strings: vec![],
reasoning_budget: None,
logprobs: None,
}
}
}
#[inline]
pub(crate) fn force_close_think(
reasoning_budget: Option<usize>,
enable_thinking: bool,
thinking_closed: bool,
generated_so_far: usize,
close_id: Option<u32>,
) -> Option<u32> {
let budget = reasoning_budget?;
let close = close_id?;
if enable_thinking && !thinking_closed && budget > 0 && generated_so_far >= budget {
Some(close)
} else {
None
}
}
#[inline]
pub(crate) fn decode_cap(reasoning_budget: Option<usize>, max_new_tokens: usize) -> usize {
match reasoning_budget {
Some(rb) if rb > 0 => rb.saturating_add(max_new_tokens).saturating_add(1),
_ => max_new_tokens,
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TopLogprob {
pub token_id: u32,
pub logprob: f32,
}
#[derive(Debug, Clone)]
pub struct TokenLogprob {
pub token_id: u32,
pub logprob: f32,
pub top: Vec<TopLogprob>,
}
#[derive(Debug, Clone)]
pub struct GenerateOutput {
pub text: String,
pub token_ids: Vec<u32>,
pub prompt_tokens: usize,
pub generated_tokens: usize,
pub stopped: bool,
pub stop_reason: Option<StopReason>,
pub token_logprobs: Vec<TokenLogprob>,
}
pub(crate) fn compute_layer_types(num_layers: usize, interval: usize) -> Vec<LayerType> {
(0..num_layers)
.map(|i| {
if (i + 1) % interval == 0 {
LayerType::FullAttention
} else {
LayerType::LinearAttention
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_construction_and_layer_types() {
let cfg = Qwen35Config::qwen35_2b();
assert_eq!(cfg.num_hidden_layers, 24);
assert_eq!(cfg.hidden_size, 2048);
assert_eq!(cfg.vocab_size, 248_320);
assert_eq!(cfg.layer_types.len(), 24);
assert_eq!(cfg.num_full_attention_layers(), 6);
assert_eq!(cfg.num_linear_attention_layers(), 18);
let full_indices: Vec<usize> = cfg
.layer_types
.iter()
.enumerate()
.filter(|(_, t)| **t == LayerType::FullAttention)
.map(|(i, _)| i)
.collect();
assert_eq!(full_indices, vec![3, 7, 11, 15, 19, 23]);
for i in [
0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, 16, 17, 18, 20, 21, 22,
] {
assert_eq!(cfg.layer_types[i], LayerType::LinearAttention);
}
}
#[test]
fn test_generate_config_defaults() {
let gen_cfg = GenerateConfig::default();
assert_eq!(gen_cfg.max_new_tokens, 256);
assert!((gen_cfg.temperature - 0.7).abs() < 1e-6);
assert_eq!(gen_cfg.top_k, 50);
assert!((gen_cfg.top_p - 0.9).abs() < 1e-6);
assert!((gen_cfg.repetition_penalty - 1.1).abs() < 1e-6);
assert!(
gen_cfg.stop_token_ids.contains(&QWEN_CHAT_IM_END_TOKEN_ID),
"default stop tokens must include im_end"
);
}
#[test]
fn test_dimension_helpers() {
let cfg = Qwen35Config::qwen35_2b();
assert_eq!(cfg.full_q_dim(), 8 * 256); assert_eq!(cfg.full_kv_dim(), 2 * 256); assert_eq!(cfg.rope_dim(), 64);
assert_eq!(cfg.linear_qkv_dim(), 6144);
assert_eq!(cfg.linear_output_dim(), 2048); }
#[test]
fn test_is_full_attention() {
let cfg = Qwen35Config::qwen35_2b();
assert!(!cfg.is_full_attention(0));
assert!(!cfg.is_full_attention(1));
assert!(!cfg.is_full_attention(2));
assert!(cfg.is_full_attention(3));
assert!(!cfg.is_full_attention(4));
assert!(cfg.is_full_attention(7));
assert!(cfg.is_full_attention(23));
assert!(!cfg.is_full_attention(100));
}
#[test]
fn test_qwen36_hf_config_fixture_parse_fields() {
let json = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/qwen36_config.json"
));
let cfg = Qwen35Config::from_config_json_str(json).expect("Qwen3.6 HF config parses");
assert_eq!(cfg.hidden_size, 2048);
assert_eq!(cfg.num_attention_heads, 16);
assert_eq!(cfg.num_key_value_heads, 2);
assert_eq!(cfg.linear_num_value_heads(), 32);
assert_eq!(cfg.num_hidden_layers, 40);
assert_eq!(cfg.num_experts, Some(256));
assert_eq!(cfg.num_experts_per_tok, Some(8));
assert_eq!(cfg.moe_intermediate_size, Some(512));
assert_eq!(cfg.shared_expert_intermediate_size, Some(512));
assert!(!cfg.tie_word_embeddings);
assert!(cfg.is_moe());
assert_eq!(cfg.vocab_size, 248320);
assert_eq!(cfg.head_dim, 256);
assert_eq!(cfg.intermediate_size, 6144);
assert_eq!(cfg.linear_num_key_heads, 16);
assert_eq!(cfg.linear_key_head_dim, 128);
assert_eq!(cfg.linear_value_head_dim, 128);
assert_eq!(cfg.linear_conv_kernel_dim, 4);
assert_eq!(cfg.full_attention_interval, 4);
assert_eq!(cfg.eos_token_id, 248044_u32);
assert_eq!(cfg.max_position_embeddings, 262144);
assert_eq!(cfg.rope_theta, 10_000_000.0_f64);
assert_eq!(cfg.partial_rotary_factor, 0.25_f32);
let full_indices: Vec<usize> = cfg
.layer_types
.iter()
.enumerate()
.filter(|(_, t)| **t == LayerType::FullAttention)
.map(|(i, _)| i)
.collect();
assert_eq!(full_indices, vec![3, 7, 11, 15, 19, 23, 27, 31, 35, 39]);
}
#[test]
fn test_qwen36_27b_preset_dimensions() {
let cfg = Qwen35Config::qwen36_27b();
assert_eq!(cfg.hidden_size, 5120);
assert_eq!(cfg.num_hidden_layers, 64);
assert_eq!(cfg.vocab_size, 248_320);
assert_eq!(cfg.intermediate_size, 17408);
assert_eq!(cfg.num_attention_heads, 24);
assert_eq!(cfg.num_key_value_heads, 4);
assert_eq!(cfg.head_dim, 256);
assert!((cfg.rope_theta - 10_000_000.0_f64).abs() < 1.0);
assert!((cfg.partial_rotary_factor - 0.25_f32).abs() < 1e-6);
assert_eq!(cfg.eos_token_id, 248_044_u32);
assert_eq!(cfg.max_position_embeddings, 262_144);
}
#[test]
fn test_qwen36_27b_preset_layer_types() {
let cfg = Qwen35Config::qwen36_27b();
assert_eq!(cfg.layer_types.len(), 64);
assert_eq!(cfg.full_attention_interval, 4);
assert_eq!(
cfg.layer_types
.iter()
.filter(|t| **t == LayerType::LinearAttention)
.count(),
48
);
assert_eq!(
cfg.layer_types
.iter()
.filter(|t| **t == LayerType::FullAttention)
.count(),
16
);
for i in 0..64_usize {
let expected = (i + 1) % 4 == 0;
assert_eq!(
cfg.layer_types[i] == LayerType::FullAttention,
expected,
"layer {i} type mismatch"
);
}
}
#[test]
fn test_qwen36_27b_preset_not_moe() {
let cfg = Qwen35Config::qwen36_27b();
assert!(!cfg.is_moe());
assert!(cfg.num_experts.is_none());
assert!(cfg.num_experts_per_tok.is_none());
assert!(cfg.moe_intermediate_size.is_none());
assert!(cfg.shared_expert_intermediate_size.is_none());
assert!(cfg.router_aux_loss_coef.is_none());
assert!(!cfg.output_router_logits);
}
#[test]
fn test_qwen36_27b_preset_gdn_fields() {
let cfg = Qwen35Config::qwen36_27b();
assert_eq!(cfg.linear_num_key_heads, 16);
assert_eq!(cfg.linear_num_value_heads, Some(48));
assert_eq!(cfg.linear_num_value_heads(), 48);
assert_eq!(cfg.linear_key_head_dim, 128);
assert_eq!(cfg.linear_value_head_dim, 128);
assert_eq!(cfg.linear_conv_kernel_dim, 4);
assert!(!cfg.tie_word_embeddings);
assert_eq!(cfg.mtp_num_hidden_layers, 1);
assert!(!cfg.mtp_use_dedicated_embeddings);
}
#[test]
fn test_qwen36_27b_from_config_json() {
let home = std::env::var("HOME").unwrap_or_else(|_| "/root".to_string());
let path =
std::path::PathBuf::from(format!("{home}/.lattice/models/qwen3.6-27b/config.json"));
if !path.exists() {
return; }
let cfg = Qwen35Config::from_config_json(&path).expect("27B config.json parses");
assert_eq!(cfg.hidden_size, 5120);
assert_eq!(cfg.num_hidden_layers, 64);
assert_eq!(cfg.vocab_size, 248_320);
assert_eq!(cfg.intermediate_size, 17408);
assert_eq!(cfg.num_attention_heads, 24);
assert_eq!(cfg.num_key_value_heads, 4);
assert_eq!(cfg.head_dim, 256);
assert!(!cfg.tie_word_embeddings);
assert_eq!(cfg.layer_types.len(), 64);
assert!(!cfg.is_moe());
assert_eq!(cfg.mtp_num_hidden_layers, 1);
assert!(
(cfg.rope_theta - 10_000_000.0_f64).abs() < 1.0,
"rope_theta should be extracted from nested rope_parameters"
);
}
#[test]
fn test_layer_mask_default_all_true_27b() {
let cfg = Qwen35Config::qwen36_27b();
assert_eq!(cfg.layer_mask.len(), 64);
assert!(cfg.layer_mask.iter().all(|&active| active));
assert_eq!(cfg.num_active_layers(), 64);
assert_eq!(cfg.num_active_linear_attention_layers(), 48);
assert_eq!(cfg.num_active_full_attention_layers(), 16);
}
#[test]
fn test_num_active_layers_partial_mask() {
let mut cfg = Qwen35Config::qwen36_27b();
let mut mask = vec![true; 64];
mask[0] = false;
mask[3] = false;
mask[4] = false;
cfg.apply_layer_mask(mask);
assert_eq!(cfg.num_active_layers(), 61);
assert_eq!(cfg.num_active_linear_attention_layers(), 46);
assert_eq!(cfg.num_active_full_attention_layers(), 15);
}
#[test]
#[should_panic(expected = "layer_mask length")]
fn test_apply_layer_mask_wrong_length_panics() {
let mut cfg = Qwen35Config::qwen36_27b();
cfg.apply_layer_mask(vec![true; 32]);
}
#[test]
fn test_pruned_config_preserves_fields() {
let cfg = Qwen35Config::qwen36_27b();
let mut mask = vec![true; 64];
mask[5] = false;
mask[10] = false;
let pruned = cfg.pruned_config(mask.clone());
assert_eq!(pruned.hidden_size, cfg.hidden_size);
assert_eq!(pruned.num_hidden_layers, cfg.num_hidden_layers);
assert_eq!(pruned.vocab_size, cfg.vocab_size);
assert_eq!(pruned.layer_types, cfg.layer_types);
assert_eq!(pruned.layer_mask, mask);
assert_eq!(pruned.num_active_layers(), 62);
}
#[test]
#[should_panic(expected = "at least one active layer")]
fn test_apply_layer_mask_all_false_panics() {
let mut cfg = Qwen35Config::qwen36_27b();
cfg.apply_layer_mask(vec![false; 64]);
}
#[test]
fn test_layer_mask_normalizes_on_parse() {
let json = r#"{
"text_config": {
"hidden_size": 2048,
"num_hidden_layers": 4,
"full_attention_interval": 4,
"eos_token_id": 1
}
}"#;
let cfg = Qwen35Config::from_config_json_str(json).unwrap();
assert_eq!(
cfg.layer_mask.len(),
4,
"normalize_layer_mask must fill mask to num_hidden_layers"
);
assert!(
cfg.layer_mask.iter().all(|&v| v),
"normalized mask must be all-true"
);
}
#[test]
fn test_zero_full_attention_interval_errors_not_panics() {
let json = r#"{
"text_config": {
"hidden_size": 2048,
"num_hidden_layers": 4,
"full_attention_interval": 0,
"eos_token_id": 1
}
}"#;
let result = Qwen35Config::from_config_json_str(json);
assert!(
result.is_err(),
"full_attention_interval: 0 must yield an InferenceError, not panic"
);
}
#[test]
fn test_zero_num_key_value_heads_errors_not_panics() {
let json = r#"{"text_config": {"num_key_value_heads": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("num_key_value_heads: 0 must yield an InferenceError, not a panic")
.to_string();
assert!(
err.contains("num_key_value_heads"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_indivisible_head_counts_error_not_panics() {
let json = r#"{"text_config": {"num_attention_heads": 3, "num_key_value_heads": 2}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("indivisible head counts must yield an InferenceError, not OOB/panic")
.to_string();
assert!(err.contains("divisible"), "wrong guard fired: {err}");
}
#[test]
fn test_zero_head_dim_errors() {
let json = r#"{"text_config": {"head_dim": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("head_dim: 0 must yield an InferenceError")
.to_string();
assert!(err.contains("head_dim"), "wrong guard fired: {err}");
}
#[test]
fn test_zero_num_hidden_layers_errors() {
let json = r#"{"text_config": {"num_hidden_layers": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("num_hidden_layers: 0 must yield an InferenceError")
.to_string();
assert!(
err.contains("num_hidden_layers"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_extreme_num_hidden_layers_rejected_before_allocation() {
let json = format!(
r#"{{"text_config": {{"num_hidden_layers": {}}}}}"#,
usize::MAX
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"num_hidden_layers: usize::MAX must yield an InferenceError, not a panic/OOM",
)
.to_string();
assert!(
err.contains("num_hidden_layers") && err.contains("MAX_HIDDEN_LAYERS"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_ten_million_hidden_layers_rejected() {
let json = r#"{"text_config": {"num_hidden_layers": 10000000}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("num_hidden_layers: 10_000_000 must yield an InferenceError")
.to_string();
assert!(
err.contains("num_hidden_layers") && err.contains("MAX_HIDDEN_LAYERS"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_num_hidden_layers_above_ceiling_errors_before_allocation() {
let json = r#"{"text_config": {"num_hidden_layers": 10000000}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("num_hidden_layers above MAX_HIDDEN_LAYERS must yield an InferenceError")
.to_string();
assert!(
err.contains("num_hidden_layers"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_layer_types_array_over_max_hidden_layers_rejected_at_deserialize() {
let elems = vec!["\"linear_attention\""; MAX_HIDDEN_LAYERS + 1].join(",");
let json = format!(r#"{{"text_config": {{"layer_types": [{elems}]}}}}"#);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"a layer_types array of MAX_HIDDEN_LAYERS+1 elements must be rejected \
during deserialization, not allocated in full",
)
.to_string();
assert!(
err.contains("MAX_HIDDEN_LAYERS"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_layer_types_in_bounds_array_accepted() {
let json = r#"{"text_config": {"num_hidden_layers": 3, "full_attention_interval": 4,
"layer_types": ["linear_attention", "linear_attention", "linear_attention"]}}"#;
let cfg = Qwen35Config::from_config_json_str(json)
.expect("an in-bounds layer_types array must be accepted");
assert_eq!(cfg.layer_types.len(), 3);
}
#[test]
fn test_layer_mask_array_over_max_hidden_layers_rejected_at_deserialize() {
let elems = vec!["true"; MAX_HIDDEN_LAYERS + 1].join(",");
let json = format!(r#"{{"text_config": {{"layer_mask": [{elems}]}}}}"#);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"a layer_mask array of MAX_HIDDEN_LAYERS+1 elements must be rejected \
during deserialization, not allocated in full",
)
.to_string();
assert!(
err.contains("MAX_HIDDEN_LAYERS"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_layer_mask_in_bounds_array_accepted() {
let json = r#"{"text_config": {"num_hidden_layers": 3, "full_attention_interval": 4,
"layer_mask": [true, false, true]}}"#;
let cfg = Qwen35Config::from_config_json_str(json)
.expect("an in-bounds layer_mask array must be accepted");
assert_eq!(cfg.layer_mask, vec![true, false, true]);
}
#[test]
fn test_hidden_size_zero_errors() {
let json = r#"{"text_config": {"hidden_size": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("hidden_size: 0 must yield an InferenceError")
.to_string();
assert!(err.contains("hidden_size"), "wrong guard fired: {err}");
}
#[test]
fn test_vocab_size_zero_errors() {
let json = r#"{"text_config": {"vocab_size": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("vocab_size: 0 must yield an InferenceError")
.to_string();
assert!(err.contains("vocab_size"), "wrong guard fired: {err}");
}
#[test]
fn test_vocab_size_over_max_errors() {
let json = format!(
r#"{{"text_config": {{"vocab_size": {}}}}}"#,
MAX_VOCAB_SIZE + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("vocab_size above MAX_VOCAB_SIZE must yield an InferenceError")
.to_string();
assert!(
err.contains("vocab_size") && err.contains("MAX_VOCAB_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_vocab_size_at_max_accepted() {
let json = format!(r#"{{"text_config": {{"vocab_size": {MAX_VOCAB_SIZE}}}}}"#);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"vocab_size == MAX_VOCAB_SIZE must be accepted"
);
}
#[test]
fn test_head_dim_over_max_rejected_all_linear_config() {
let json = format!(
r#"{{"text_config": {{"head_dim": {}, "num_hidden_layers": 2,
"layer_types": ["linear_attention", "linear_attention"]}}}}"#,
MAX_HEAD_DIM + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"head_dim above MAX_HEAD_DIM must yield an InferenceError even for an \
all-linear-attention config",
)
.to_string();
assert!(
err.contains("head_dim") && err.contains("MAX_HEAD_DIM"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_head_dim_at_max_accepted_all_linear_config() {
let json = format!(
r#"{{"text_config": {{"head_dim": {MAX_HEAD_DIM}, "num_hidden_layers": 2,
"partial_rotary_factor": 0.25,
"layer_types": ["linear_attention", "linear_attention"]}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"head_dim == MAX_HEAD_DIM must be accepted for an all-linear-attention config"
);
}
#[test]
fn test_full_q_dim_overflow_rejected_all_linear_config() {
let json = format!(
r#"{{"text_config": {{"head_dim": 2, "num_attention_heads": {},
"num_key_value_heads": 1, "num_hidden_layers": 2,
"layer_types": ["linear_attention", "linear_attention"]}}}}"#,
usize::MAX
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"an extreme num_attention_heads must yield an InferenceError even for an \
all-linear-attention config",
)
.to_string();
assert!(
err.contains("num_attention_heads") && err.contains("MAX_ATTENTION_HEADS"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_full_kv_dim_overflow_rejected_all_linear_config() {
let json = format!(
r#"{{"text_config": {{"head_dim": 2, "num_attention_heads": {max},
"num_key_value_heads": {max}, "num_hidden_layers": 2,
"layer_types": ["linear_attention", "linear_attention"]}}}}"#,
max = usize::MAX
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"an extreme num_attention_heads / num_key_value_heads must yield an \
InferenceError even for an all-linear-attention config",
)
.to_string();
assert!(
err.contains("MAX_ATTENTION_HEADS"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_full_attention_geometry_accepted_full_attention_config() {
assert!(
Qwen35Config::from_config_json_str("{}").is_ok(),
"a valid full-attention-carrying config must be accepted"
);
}
#[test]
fn test_full_attention_geometry_accepted_all_linear_config() {
let json = r#"{"text_config": {"num_hidden_layers": 2,
"layer_types": ["linear_attention", "linear_attention"]}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"a valid all-linear-attention config must be accepted"
);
}
#[test]
fn test_full_q_dim_over_budget_rejected_all_linear_config() {
let json = format!(
r#"{{"text_config": {{"head_dim": {MAX_HEAD_DIM}, "num_attention_heads": 513,
"num_key_value_heads": 1, "num_hidden_layers": 2,
"partial_rotary_factor": 0.25,
"layer_types": ["linear_attention", "linear_attention"]}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"a non-overflowing but budget-exceeding full_q_dim must yield an \
InferenceError even for an all-linear-attention config",
)
.to_string();
assert!(
err.contains("q_dim") && err.contains("MAX_FULL_ATTENTION_DIM"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_full_kv_dim_over_budget_rejected_all_linear_config() {
let json = format!(
r#"{{"text_config": {{"head_dim": {MAX_HEAD_DIM}, "num_attention_heads": 513,
"num_key_value_heads": 513, "num_hidden_layers": 2,
"partial_rotary_factor": 0.25,
"layer_types": ["linear_attention", "linear_attention"]}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"a non-overflowing but budget-exceeding full_kv_dim must yield an \
InferenceError even for an all-linear-attention config",
)
.to_string();
assert!(
err.contains("MAX_FULL_ATTENTION_DIM"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_full_attention_dims_at_budget_accepted_all_linear_config() {
let json = format!(
r#"{{"text_config": {{"head_dim": {MAX_HEAD_DIM}, "num_attention_heads": 512,
"num_key_value_heads": 1, "num_hidden_layers": 2,
"partial_rotary_factor": 0.25,
"layer_types": ["linear_attention", "linear_attention"]}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"full_q_dim == MAX_FULL_ATTENTION_DIM must be accepted"
);
}
#[test]
fn test_moe_num_experts_zero_errors() {
let json = r#"{"text_config": {"num_experts": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("num_experts: 0 must yield an InferenceError")
.to_string();
assert!(err.contains("num_experts"), "wrong guard fired: {err}");
}
#[test]
fn test_moe_num_experts_per_tok_zero_errors() {
let json = r#"{"text_config": {"num_experts": 4, "num_experts_per_tok": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("num_experts_per_tok: 0 must yield an InferenceError")
.to_string();
assert!(
err.contains("num_experts_per_tok"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_moe_num_experts_per_tok_over_num_experts_errors() {
let json = r#"{"text_config": {"num_experts": 8, "num_experts_per_tok": 9}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("num_experts_per_tok > num_experts must yield an InferenceError")
.to_string();
assert!(
err.contains("num_experts_per_tok") && err.contains("num_experts"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_moe_num_experts_over_max_errors() {
let json = format!(
r#"{{"text_config": {{"num_experts": {}, "num_experts_per_tok": 1}}}}"#,
MAX_NUM_EXPERTS + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("num_experts above MAX_NUM_EXPERTS must yield an InferenceError")
.to_string();
assert!(
err.contains("num_experts") && err.contains("MAX_NUM_EXPERTS"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_moe_valid_config_accepted() {
let json = r#"{"text_config": {"num_experts": 256, "num_experts_per_tok": 8}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"a valid MoE config must be accepted"
);
}
#[test]
fn test_moe_dense_config_unaffected() {
let json = r#"{"text_config": {"num_experts": null, "num_experts_per_tok": null,
"num_hidden_layers": 2,
"layer_types": ["linear_attention", "linear_attention"]}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"a dense config with no MoE fields must be unaffected by the MoE dimension checks"
);
}
#[test]
fn test_intermediate_size_over_max_errors() {
let json = format!(
r#"{{"text_config": {{"intermediate_size": {}}}}}"#,
MAX_INTERMEDIATE_SIZE + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"intermediate_size above MAX_INTERMEDIATE_SIZE must yield an InferenceError",
)
.to_string();
assert!(
err.contains("intermediate_size") && err.contains("MAX_INTERMEDIATE_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_intermediate_size_at_max_accepted() {
let json = format!(
r#"{{"text_config": {{"intermediate_size": {MAX_INTERMEDIATE_SIZE}, "hidden_size": 1}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"intermediate_size == MAX_INTERMEDIATE_SIZE must be accepted"
);
}
#[test]
fn test_moe_intermediate_size_over_max_errors() {
let json = format!(
r#"{{"text_config": {{"moe_intermediate_size": {}}}}}"#,
MAX_INTERMEDIATE_SIZE + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"moe_intermediate_size above MAX_INTERMEDIATE_SIZE must yield an InferenceError",
)
.to_string();
assert!(
err.contains("moe_intermediate_size") && err.contains("MAX_INTERMEDIATE_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_shared_expert_intermediate_size_over_max_errors() {
let json = format!(
r#"{{"text_config": {{"shared_expert_intermediate_size": {}}}}}"#,
MAX_INTERMEDIATE_SIZE + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"shared_expert_intermediate_size above MAX_INTERMEDIATE_SIZE must yield an \
InferenceError",
)
.to_string();
assert!(
err.contains("shared_expert_intermediate_size")
&& err.contains("MAX_INTERMEDIATE_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_moe_and_shared_intermediate_size_at_max_accepted() {
let json = format!(
r#"{{"text_config": {{"moe_intermediate_size": {MAX_INTERMEDIATE_SIZE},
"shared_expert_intermediate_size": {MAX_INTERMEDIATE_SIZE}}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"moe_intermediate_size / shared_expert_intermediate_size == \
MAX_INTERMEDIATE_SIZE must be accepted"
);
}
#[test]
fn test_dense_config_no_moe_intermediate_fields_unaffected() {
let json = r#"{"text_config": {"moe_intermediate_size": null,
"shared_expert_intermediate_size": null,
"num_hidden_layers": 2,
"layer_types": ["linear_attention", "linear_attention"]}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"a dense config with no MoE intermediate fields must be unaffected by the \
MAX_INTERMEDIATE_SIZE gated checks"
);
}
#[test]
fn test_zero_linear_conv_kernel_dim_errors() {
let json = r#"{"text_config": {"linear_conv_kernel_dim": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("linear_conv_kernel_dim: 0 must yield an InferenceError, not underflow")
.to_string();
assert!(
err.contains("linear_conv_kernel_dim"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_zero_linear_num_key_heads_errors() {
let json = r#"{"text_config": {"linear_num_key_heads": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("linear_num_key_heads: 0 must yield an InferenceError, not divide-by-zero")
.to_string();
assert!(
err.contains("linear_num_key_heads"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_value_heads_below_key_heads_errors() {
let json = r#"{"text_config": {"linear_num_value_heads": 1}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("value_heads < key_heads must yield an InferenceError, not divide-by-zero")
.to_string();
assert!(
err.contains("linear_num_value_heads"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_value_heads_multiple_of_key_heads_accepted() {
let json = r#"{"text_config": {"linear_num_key_heads": 16, "linear_num_value_heads": 32}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"key 16 / value 32 (ratio 2) is a real GDN config and must be accepted"
);
}
#[test]
fn test_partial_rotary_factor_above_one_errors() {
let json = r#"{"text_config": {"partial_rotary_factor": 3.0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("partial_rotary_factor > 1.0 must yield an InferenceError, not OOB")
.to_string();
assert!(
err.contains("partial_rotary_factor"),
"wrong guard fired: {err}"
);
}
#[test]
fn test_partial_rotary_factor_one_accepted() {
let json = r#"{"text_config": {"partial_rotary_factor": 1.0}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"partial_rotary_factor == 1.0 (full rotary) must be accepted"
);
}
#[test]
fn test_odd_rope_dim_errors_not_panics() {
let json = r#"{"text_config": {"head_dim": 10, "partial_rotary_factor": 0.3}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("odd rope_dim must yield an InferenceError, not silent wrong output")
.to_string();
assert!(err.contains("rope_dim"), "wrong guard fired: {err}");
}
#[test]
fn test_zero_rope_dim_errors_not_panics() {
let json = r#"{"text_config": {"partial_rotary_factor": 0.0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("zero rope_dim must yield an InferenceError, not capacity-zero surprise")
.to_string();
assert!(err.contains("rope_dim"), "wrong guard fired: {err}");
}
#[test]
fn test_generate_config_enable_thinking_default_and_toggle() {
let default_cfg = GenerateConfig::default();
assert!(
default_cfg.enable_thinking,
"default must have thinking enabled"
);
let no_think = GenerateConfig {
enable_thinking: false,
..GenerateConfig::default()
};
assert!(!no_think.enable_thinking);
assert_eq!(no_think.max_new_tokens, 256);
assert!((no_think.temperature - 0.7).abs() < 1e-6);
}
#[test]
fn test_qwen36_27b_layer_distribution_matches_config_json() {
let types = compute_layer_types(64, 4);
for chunk_start in (0..64).step_by(4) {
assert_eq!(types[chunk_start], LayerType::LinearAttention);
assert_eq!(types[chunk_start + 1], LayerType::LinearAttention);
assert_eq!(types[chunk_start + 2], LayerType::LinearAttention);
assert_eq!(types[chunk_start + 3], LayerType::FullAttention);
}
}
#[test]
fn test_qwen35_config_backward_compat() {
let json = r#"{
"text_config": {
"hidden_size": 2048,
"num_hidden_layers": 24,
"vocab_size": 248320,
"num_attention_heads": 8,
"num_key_value_heads": 2,
"head_dim": 256,
"rms_norm_eps": 0.000001,
"intermediate_size": 6144,
"linear_num_key_heads": 16,
"linear_num_value_heads": 16,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128,
"linear_conv_kernel_dim": 4,
"full_attention_interval": 4,
"eos_token_id": 248044,
"max_position_embeddings": 262144,
"rope_theta": 10000000.0,
"partial_rotary_factor": 0.25
}
}"#;
let cfg = Qwen35Config::from_config_json_str(json).expect("backward-compat config parses");
assert_eq!(cfg.num_hidden_layers, 24);
assert_eq!(cfg.linear_num_value_heads(), 16);
assert!(!cfg.is_moe(), "Qwen3.5 must not be detected as MoE");
assert!(
cfg.tie_word_embeddings,
"default tie_word_embeddings is true"
);
assert_eq!(cfg.num_experts, None);
assert_eq!(cfg.num_experts_per_tok, None);
assert_eq!(
cfg.mtp_num_hidden_layers, 0,
"Qwen3.5 mtp_num_hidden_layers must default to 0"
);
assert!(!cfg.mtp_use_dedicated_embeddings);
}
#[test]
fn qwen35_config_missing_max_position_embeddings_defaults_4096() {
let json = r#"{
"text_config": {
"hidden_size": 2048,
"num_hidden_layers": 24,
"vocab_size": 248320,
"num_attention_heads": 8,
"num_key_value_heads": 2,
"head_dim": 256,
"rms_norm_eps": 0.000001,
"intermediate_size": 6144,
"linear_num_key_heads": 16,
"linear_num_value_heads": 16,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128,
"linear_conv_kernel_dim": 4,
"full_attention_interval": 4,
"eos_token_id": 248044,
"rope_theta": 10000000.0,
"partial_rotary_factor": 0.25
}
}"#;
let cfg = Qwen35Config::from_config_json_str(json)
.expect("config without max_position_embeddings still parses");
assert_eq!(cfg.max_position_embeddings, 4096);
}
#[test]
fn test_qwen35_0_8b_preset_dimensions() {
let cfg = Qwen35Config::qwen35_0_8b();
assert_eq!(cfg.hidden_size, 1024);
assert_eq!(cfg.num_hidden_layers, 24);
assert_eq!(cfg.vocab_size, 248_320);
assert_eq!(cfg.intermediate_size, 3584);
assert_eq!(cfg.num_attention_heads, 8);
assert_eq!(cfg.num_key_value_heads, 2);
assert_eq!(cfg.head_dim, 256);
assert_eq!(cfg.rope_dim(), 64); assert_eq!(cfg.linear_num_key_heads, 16);
assert_eq!(cfg.linear_num_value_heads(), 16);
assert_eq!(cfg.linear_key_head_dim, 128);
assert_eq!(cfg.linear_value_head_dim, 128);
assert_eq!(cfg.eos_token_id, 248_044);
assert_eq!(cfg.max_position_embeddings, 262_144);
assert_eq!(cfg.mtp_num_hidden_layers, 1);
assert!(cfg.tie_word_embeddings);
assert!(!cfg.is_moe(), "Qwen3.5-0.8B is dense, not MoE");
assert_eq!(cfg.layer_types.len(), 24);
assert_eq!(cfg.num_full_attention_layers(), 6);
assert_eq!(cfg.num_linear_attention_layers(), 18);
}
#[test]
fn test_qwen35_0_8b_config_json_fixture_parses() {
let json = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/qwen35_0_8b_config.json"
));
let cfg =
Qwen35Config::from_config_json_str(json).expect("Qwen3.5-0.8B config.json parses");
assert_eq!(cfg.hidden_size, 1024);
assert_eq!(cfg.num_hidden_layers, 24);
assert_eq!(cfg.vocab_size, 248_320);
assert_eq!(cfg.intermediate_size, 3584);
assert_eq!(cfg.num_attention_heads, 8);
assert_eq!(cfg.num_key_value_heads, 2);
assert_eq!(cfg.head_dim, 256);
assert_eq!(cfg.linear_num_key_heads, 16);
assert_eq!(cfg.linear_num_value_heads(), 16);
assert_eq!(cfg.linear_key_head_dim, 128);
assert_eq!(cfg.linear_value_head_dim, 128);
assert_eq!(cfg.linear_conv_kernel_dim, 4);
assert_eq!(cfg.full_attention_interval, 4);
assert_eq!(cfg.eos_token_id, 248_044);
assert_eq!(cfg.max_position_embeddings, 262_144);
assert_eq!(cfg.mtp_num_hidden_layers, 1);
assert!(!cfg.is_moe(), "Qwen3.5-0.8B is dense, not MoE");
assert_eq!(cfg.rope_theta, 10_000_000.0);
assert!((cfg.partial_rotary_factor - 0.25).abs() < 1e-6);
assert_eq!(cfg.rope_dim(), 64);
assert_eq!(cfg.layer_types.len(), 24);
assert_eq!(cfg.num_full_attention_layers(), 6);
assert_eq!(cfg.num_linear_attention_layers(), 18);
let full_indices: Vec<usize> = cfg
.layer_types
.iter()
.enumerate()
.filter(|(_, t)| **t == LayerType::FullAttention)
.map(|(i, _)| i)
.collect();
assert_eq!(full_indices, vec![3, 7, 11, 15, 19, 23]);
assert!(cfg.tie_word_embeddings);
let vision = cfg
.vision_config
.as_ref()
.expect("released checkpoint has a vision_config");
assert_eq!(vision.depth, 12);
assert_eq!(vision.hidden_size, 768);
assert_eq!(vision.num_heads, 12);
assert_eq!(vision.patch_size, 16);
assert_eq!(vision.spatial_merge_size, 2);
assert_eq!(vision.out_hidden_size, 1024);
assert_eq!(vision.temporal_patch_size, 2);
assert_eq!(vision.num_position_embeddings, 2304);
assert_eq!(vision.in_channels, 3);
assert!(vision.deepstack_visual_indexes.is_empty());
assert_eq!(cfg.image_token_id, Some(248_056));
assert_eq!(cfg.video_token_id, Some(248_057));
assert_eq!(cfg.vision_start_token_id, Some(248_053));
assert_eq!(cfg.vision_end_token_id, Some(248_054));
let rope_params = cfg
.rope_parameters
.as_ref()
.expect("released checkpoint nests rope_parameters under text_config");
assert_eq!(rope_params.mrope_section, Some(vec![11, 11, 10]));
assert_eq!(rope_params.mrope_interleaved, Some(true));
}
#[test]
fn test_text_only_config_has_no_vision_fields() {
let json = r#"{
"text_config": {
"hidden_size": 1024,
"num_hidden_layers": 4,
"vocab_size": 1000,
"intermediate_size": 2048,
"rms_norm_eps": 1e-6,
"num_attention_heads": 8,
"num_key_value_heads": 2,
"head_dim": 256,
"rope_theta": 10000000.0,
"partial_rotary_factor": 0.25,
"linear_num_key_heads": 16,
"linear_num_value_heads": 16,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128,
"linear_conv_kernel_dim": 4,
"full_attention_interval": 4,
"eos_token_id": 999,
"max_position_embeddings": 4096
}
}"#;
let cfg =
Qwen35Config::from_config_json_str(json).expect("text-only config.json still parses");
assert_eq!(cfg.hidden_size, 1024, "non-vision field must still parse");
assert!(cfg.vision_config.is_none());
assert!(cfg.image_token_id.is_none());
assert!(cfg.video_token_id.is_none());
assert!(cfg.vision_start_token_id.is_none());
assert!(cfg.vision_end_token_id.is_none());
let rope_params = cfg.rope_parameters.clone().unwrap_or_default();
assert!(rope_params.mrope_section.is_none());
assert!(rope_params.mrope_interleaved.is_none());
}
fn config_json_with_vision(vision_config_body: &str) -> String {
format!(
r#"{{
"text_config": {{
"hidden_size": 1024,
"num_hidden_layers": 4,
"vocab_size": 1000,
"intermediate_size": 2048,
"rms_norm_eps": 1e-6,
"num_attention_heads": 8,
"num_key_value_heads": 2,
"head_dim": 256,
"rope_theta": 10000000.0,
"partial_rotary_factor": 0.25,
"linear_num_key_heads": 16,
"linear_num_value_heads": 16,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128,
"linear_conv_kernel_dim": 4,
"full_attention_interval": 4,
"eos_token_id": 999,
"max_position_embeddings": 4096
}},
"vision_config": {vision_config_body}
}}"#
)
}
#[test]
fn parser_rejects_present_vision_config_with_depth_zero() {
let json = config_json_with_vision(
r#"{
"depth": 0,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}"#,
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("depth: 0 vision_config must be rejected at parse time");
assert!(
err.to_string().contains("depth"),
"error must name depth: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_num_heads_zero() {
let json = config_json_with_vision(
r#"{
"depth": 12,
"hidden_size": 768,
"num_heads": 0,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}"#,
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("num_heads: 0 vision_config must be rejected at parse time");
assert!(
err.to_string().contains("num_heads"),
"error must name num_heads: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_depth_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": {},
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#,
MAX_VISION_DEPTH + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("depth above MAX_VISION_DEPTH must be rejected at parse time")
.to_string();
assert!(
err.contains("depth") && err.contains("MAX_VISION_DEPTH"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_present_vision_config_with_depth_at_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": {MAX_VISION_DEPTH},
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"depth == MAX_VISION_DEPTH must be accepted"
);
}
#[test]
fn parser_rejects_present_mrope_section_over_max() {
let huge: Vec<String> = (0..MAX_CONFIG_VECTOR_LEN + 1)
.map(|_| "0".to_string())
.collect();
let json = format!(
r#"{{"text_config": {{"rope_parameters": {{"mrope_section": [{}]}}}}}}"#,
huge.join(",")
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("mrope_section length above MAX_CONFIG_VECTOR_LEN must be rejected")
.to_string();
assert!(
err.contains("mrope_section") && err.contains("MAX_CONFIG_VECTOR_LEN"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_present_mrope_section_realistic_size() {
let json = r#"{"text_config": {"rope_parameters": {"mrope_section": [11, 11, 10]}}}"#;
let cfg = Qwen35Config::from_config_json_str(json)
.expect("realistic mrope_section must be accepted");
assert_eq!(
cfg.rope_parameters
.expect("rope_parameters present")
.mrope_section,
Some(vec![11, 11, 10])
);
}
#[test]
fn parser_rejects_present_deepstack_visual_indexes_over_max() {
let huge: Vec<String> = (0..MAX_CONFIG_VECTOR_LEN + 1)
.map(|_| "0".to_string())
.collect();
let json = config_json_with_vision(&format!(
r#"{{
"depth": 12,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3,
"deepstack_visual_indexes": [{}]
}}"#,
huge.join(",")
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"deepstack_visual_indexes length above MAX_CONFIG_VECTOR_LEN must be rejected",
)
.to_string();
assert!(
err.contains("deepstack_visual_indexes") && err.contains("MAX_CONFIG_VECTOR_LEN"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_present_deepstack_visual_indexes_realistic_size() {
let json = config_json_with_vision(
r#"{
"depth": 12,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3,
"deepstack_visual_indexes": [2, 5, 8]
}"#,
);
let cfg = Qwen35Config::from_config_json_str(&json)
.expect("realistic deepstack_visual_indexes must be accepted");
assert_eq!(
cfg.vision_config
.expect("vision_config present")
.deepstack_visual_indexes,
vec![2, 5, 8]
);
}
#[test]
fn parser_rejects_all_full_attention_config_with_overflowing_linear_output_dim() {
let json = format!(
r#"{{"text_config": {{
"num_hidden_layers": 2,
"layer_types": ["full_attention", "full_attention"],
"linear_num_key_heads": 1,
"linear_num_value_heads": 2,
"linear_key_head_dim": 1,
"linear_value_head_dim": {}
}}}}"#,
usize::MAX
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"overflowing linear_value_head_dim on an all-full-attention config must be \
rejected before any allocation",
)
.to_string();
assert!(
err.contains("output_dim") && err.contains("overflows"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_all_full_attention_config_with_linear_output_dim_over_max() {
let value_heads = MAX_LINEAR_NUM_VALUE_HEADS;
let value_dim = (MAX_LINEAR_OUTPUT_DIM / value_heads) + 1;
let json = format!(
r#"{{"text_config": {{
"num_hidden_layers": 2,
"layer_types": ["full_attention", "full_attention"],
"linear_num_key_heads": 1,
"linear_num_value_heads": {value_heads},
"linear_key_head_dim": 1,
"linear_value_head_dim": {value_dim}
}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"linear_output_dim above MAX_LINEAR_OUTPUT_DIM on an all-full-attention config \
must be rejected",
)
.to_string();
assert!(
err.contains("output_dim") && err.contains("MAX_LINEAR_OUTPUT_DIM"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_all_full_attention_config_with_realistic_linear_dims() {
let json = r#"{"text_config": {
"num_hidden_layers": 2,
"layer_types": ["full_attention", "full_attention"],
"linear_num_key_heads": 16,
"linear_num_value_heads": 32,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128
}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"realistic linear-attention dims on an all-full-attention config must be accepted"
);
}
#[test]
fn kv_layer_count_excludes_linear_layers() {
let cfg = Qwen35Config::qwen35_0_8b();
assert_eq!(
cfg.kv_cache_layer_count(),
6,
"must be full-attention count"
);
assert_ne!(
cfg.kv_cache_layer_count(),
cfg.num_hidden_layers,
"kv_cache_layer_count must not equal num_hidden_layers (would 4× decode memory)"
);
}
#[test]
fn full_plus_linear_equals_total() {
for (name, cfg) in [
("qwen35_0_8b", Qwen35Config::qwen35_0_8b()),
("qwen35_2b", Qwen35Config::qwen35_2b()),
("qwen36_35b_a3b", Qwen35Config::qwen36_35b_a3b()),
("qwen36_27b", Qwen35Config::qwen36_27b()),
] {
assert_eq!(
cfg.num_full_attention_layers() + cfg.num_linear_attention_layers(),
cfg.num_hidden_layers,
"{name}: full + linear must equal num_hidden_layers"
);
}
}
#[test]
fn kv_bytes_per_token_f16() {
let cfg = Qwen35Config::qwen35_0_8b();
assert_eq!(cfg.kv_bytes_per_token(2), 12_288);
}
#[test]
fn kv_bytes_per_token_identity() {
let cfg = Qwen35Config::qwen35_0_8b();
assert_eq!(cfg.kv_bytes_per_token(1), 6_144);
assert_eq!(
cfg.kv_bytes_per_token(1),
cfg.num_hidden_layers * cfg.head_dim
);
}
#[test]
fn decode_cap_none_budget_returns_max() {
assert_eq!(decode_cap(None, 512), 512);
assert_eq!(decode_cap(None, 0), 0);
}
#[test]
fn decode_cap_zero_budget_returns_max() {
assert_eq!(decode_cap(Some(0), 512), 512);
assert_eq!(decode_cap(Some(0), 1), 1);
}
#[test]
fn decode_cap_nonzero_budget_adds_budgets() {
assert_eq!(decode_cap(Some(2048), 512), 2561);
assert_eq!(decode_cap(Some(1), 1), 3);
assert_eq!(decode_cap(Some(100), 200), 301);
}
#[test]
fn decode_cap_saturates_on_overflow() {
assert_eq!(decode_cap(Some(usize::MAX), 1), usize::MAX);
assert_eq!(decode_cap(Some(1), usize::MAX), usize::MAX);
}
#[test]
fn force_close_think_disabled_when_budget_none() {
assert_eq!(
force_close_think(None, true, false, 100, Some(99)),
None,
"None budget must disable forcing"
);
}
#[test]
fn force_close_think_disabled_when_budget_zero() {
assert_eq!(
force_close_think(Some(0), true, false, 100, Some(99)),
None,
"budget=0 must disable forcing"
);
}
#[test]
fn force_close_think_disabled_when_enable_thinking_false() {
assert_eq!(
force_close_think(Some(10), false, false, 20, Some(99)),
None,
"enable_thinking=false must disable forcing"
);
}
#[test]
fn force_close_think_disabled_when_already_closed() {
assert_eq!(
force_close_think(Some(10), true, true, 20, Some(99)),
None,
"already-closed thinking block must not force again"
);
}
#[test]
fn force_close_think_disabled_when_close_id_none() {
assert_eq!(
force_close_think(Some(10), true, false, 20, None),
None,
"close_id=None must disable forcing"
);
}
#[test]
fn force_close_think_fires_at_budget_boundary() {
let close_id = 248_069_u32;
assert_eq!(
force_close_think(Some(10), true, false, 10, Some(close_id)),
Some(close_id),
"must force when generated_so_far equals budget"
);
assert_eq!(
force_close_think(Some(10), true, false, 11, Some(close_id)),
Some(close_id),
"must force when generated_so_far exceeds budget"
);
}
#[test]
fn force_close_think_does_not_fire_before_budget() {
let close_id = 248_069_u32;
assert_eq!(
force_close_think(Some(10), true, false, 9, Some(close_id)),
None,
"must not force when generated_so_far is one below budget"
);
assert_eq!(
force_close_think(Some(10), true, false, 0, Some(close_id)),
None,
"must not force when zero tokens generated"
);
}
#[test]
fn from_model_dir_errors_on_missing_config_json() {
let tmp = tempfile::tempdir().unwrap();
let err = Qwen35Config::from_model_dir(tmp.path())
.expect_err("a directory with no config.json must be a hard error");
let msg = err.to_string();
assert!(
msg.contains("config.json"),
"error must name the missing file: {msg}"
);
assert!(
msg.contains(&tmp.path().display().to_string()),
"error must name the offending directory: {msg}"
);
}
#[test]
fn from_model_dir_loads_a_real_config_json() {
let tmp = tempfile::tempdir().unwrap();
let json = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/qwen35_0_8b_config.json"
));
std::fs::write(tmp.path().join("config.json"), json).unwrap();
let cfg = Qwen35Config::from_model_dir(tmp.path())
.expect("a directory with a valid config.json must load");
assert_eq!(
cfg.hidden_size, 1024,
"must parse the real 0.8B config, not a preset"
);
}
#[test]
fn from_model_dir_propagates_a_malformed_config_json_parse_error() {
let tmp = tempfile::tempdir().unwrap();
std::fs::write(tmp.path().join("config.json"), "not valid json {{{").unwrap();
let err = Qwen35Config::from_model_dir(tmp.path())
.expect_err("malformed config.json must still be a parse error, not a preset");
assert!(
err.to_string().contains("config.json") || err.to_string().contains("invalid Qwen"),
"error must reflect the parse failure: {err}"
);
}
#[test]
fn from_model_dir_rejects_oversized_config_json_before_read_to_string() {
let tmp = tempfile::tempdir().unwrap();
let oversized = "x".repeat(MAX_CONFIG_JSON_BYTES as usize + 1);
std::fs::write(tmp.path().join("config.json"), oversized).unwrap();
let err = Qwen35Config::from_model_dir(tmp.path())
.expect_err("an oversized config.json must be rejected");
assert!(
err.to_string().contains("MAX_CONFIG_JSON_BYTES"),
"wrong guard fired: {err}"
);
let err_validated = Qwen35Config::from_model_dir_validated(tmp.path())
.expect_err("an oversized config.json must be rejected via the validated sibling");
assert!(
err_validated.to_string().contains("MAX_CONFIG_JSON_BYTES"),
"wrong guard fired: {err_validated}"
);
}
#[test]
fn parser_rejects_gdn_state_size_over_max_with_small_output_dim() {
let json = r#"{"text_config": {
"linear_num_key_heads": 1,
"linear_num_value_heads": 1,
"linear_key_head_dim": 1000000,
"linear_value_head_dim": 128
}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err(
"a huge linear_key_head_dim must be rejected by MAX_GDN_STATE_SIZE even though \
linear_output_dim (value_heads * linear_value_head_dim) is tiny",
)
.to_string();
assert!(
err.contains("GatedDeltaNet state size") && err.contains("MAX_GDN_STATE_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_gdn_state_size() {
let json = r#"{"text_config": {
"linear_num_key_heads": 16,
"linear_num_value_heads": 32,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128
}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"realistic GDN geometry must be accepted"
);
}
#[test]
fn parser_rejects_zero_linear_key_head_dim() {
let json = r#"{"text_config": {"linear_key_head_dim": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("linear_key_head_dim: 0 must be rejected")
.to_string();
assert!(
err.contains("linear_key_head_dim"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_zero_linear_value_head_dim() {
let json = r#"{"text_config": {"linear_value_head_dim": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("linear_value_head_dim: 0 must be rejected")
.to_string();
assert!(
err.contains("linear_value_head_dim"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_hidden_size_over_max() {
let json = format!(
r#"{{"text_config": {{"hidden_size": {}}}}}"#,
MAX_HIDDEN_SIZE + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("hidden_size above MAX_HIDDEN_SIZE must be rejected")
.to_string();
assert!(
err.contains("hidden_size") && err.contains("MAX_HIDDEN_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_hidden_size_at_max() {
let json = format!(
r#"{{"text_config": {{"hidden_size": {MAX_HIDDEN_SIZE}, "vocab_size": 1, "intermediate_size": 1}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"hidden_size == MAX_HIDDEN_SIZE must be accepted when paired with a small vocab_size"
);
}
#[test]
fn parser_rejects_num_attention_heads_over_max() {
let json = format!(
r#"{{"text_config": {{"head_dim": 1, "num_attention_heads": {}, "num_key_value_heads": 1}}}}"#,
MAX_ATTENTION_HEADS + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("num_attention_heads above MAX_ATTENTION_HEADS must be rejected")
.to_string();
assert!(
err.contains("num_attention_heads") && err.contains("MAX_ATTENTION_HEADS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_num_attention_heads_at_max() {
let json = format!(
r#"{{"text_config": {{"head_dim": 8, "num_attention_heads": {MAX_ATTENTION_HEADS}, "num_key_value_heads": 1}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"num_attention_heads == MAX_ATTENTION_HEADS must be accepted"
);
}
#[test]
fn parser_rejects_num_key_value_heads_over_max() {
let json = format!(
r#"{{"text_config": {{"head_dim": 1, "num_attention_heads": {MAX_ATTENTION_HEADS}, "num_key_value_heads": {}}}}}"#,
MAX_ATTENTION_HEADS + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("num_key_value_heads above MAX_ATTENTION_HEADS must be rejected")
.to_string();
assert!(
err.contains("num_key_value_heads") && err.contains("MAX_ATTENTION_HEADS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_num_key_value_heads_at_max() {
let json = format!(
r#"{{"text_config": {{"head_dim": 8, "num_attention_heads": {MAX_ATTENTION_HEADS}, "num_key_value_heads": {MAX_ATTENTION_HEADS}}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"num_key_value_heads == MAX_ATTENTION_HEADS must be accepted"
);
}
#[test]
fn parser_rejects_num_experts_per_tok_over_max_without_num_experts() {
let json = format!(
r#"{{"text_config": {{"num_experts": null, "num_experts_per_tok": {}}}}}"#,
MAX_NUM_EXPERTS + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"num_experts_per_tok above MAX_NUM_EXPERTS must be rejected even when \
num_experts is absent",
)
.to_string();
assert!(
err.contains("num_experts_per_tok") && err.contains("MAX_NUM_EXPERTS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_num_experts_per_tok_at_max_without_num_experts() {
let json = format!(
r#"{{"text_config": {{"num_experts": null, "num_experts_per_tok": {MAX_NUM_EXPERTS}}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"num_experts_per_tok == MAX_NUM_EXPERTS must be accepted when num_experts is absent"
);
}
#[test]
fn parser_rejects_zero_intermediate_size() {
let json = r#"{"text_config": {"intermediate_size": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("intermediate_size: 0 must be rejected")
.to_string();
assert!(
err.contains("intermediate_size"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_zero_moe_intermediate_size() {
let json = r#"{"text_config": {"moe_intermediate_size": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("moe_intermediate_size: 0 must be rejected")
.to_string();
assert!(
err.contains("moe_intermediate_size"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_zero_shared_expert_intermediate_size() {
let json = r#"{"text_config": {"shared_expert_intermediate_size": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("shared_expert_intermediate_size: 0 must be rejected")
.to_string();
assert!(
err.contains("shared_expert_intermediate_size"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_max_position_embeddings_over_max() {
let json = format!(
r#"{{"text_config": {{"max_position_embeddings": {}, "head_dim": 2, "partial_rotary_factor": 1.0}}}}"#,
MAX_POSITION_EMBEDDINGS + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("max_position_embeddings above MAX_POSITION_EMBEDDINGS must be rejected")
.to_string();
assert!(
err.contains("max_position_embeddings") && err.contains("MAX_POSITION_EMBEDDINGS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_max_position_embeddings_at_max() {
let json = format!(
r#"{{"text_config": {{"max_position_embeddings": {MAX_POSITION_EMBEDDINGS}}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"max_position_embeddings == MAX_POSITION_EMBEDDINGS must be accepted"
);
}
#[test]
fn parser_rejects_linear_num_key_heads_over_max() {
let json = format!(
r#"{{"text_config": {{
"linear_num_key_heads": {over},
"linear_num_value_heads": {over},
"linear_key_head_dim": 1,
"linear_value_head_dim": 1
}}}}"#,
over = MAX_LINEAR_NUM_KEY_HEADS + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("linear_num_key_heads above MAX_LINEAR_NUM_KEY_HEADS must be rejected")
.to_string();
assert!(
err.contains("linear_num_key_heads") && err.contains("MAX_LINEAR_NUM_KEY_HEADS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_linear_num_key_heads_at_max() {
let json = format!(
r#"{{"text_config": {{
"linear_num_key_heads": {MAX_LINEAR_NUM_KEY_HEADS},
"linear_num_value_heads": {MAX_LINEAR_NUM_KEY_HEADS},
"linear_key_head_dim": 1,
"linear_value_head_dim": 1
}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"linear_num_key_heads == MAX_LINEAR_NUM_KEY_HEADS must be accepted"
);
}
#[test]
fn parser_rejects_linear_conv_kernel_dim_over_max() {
let json = format!(
r#"{{"text_config": {{"linear_conv_kernel_dim": {}}}}}"#,
MAX_CONV_KERNEL_DIM + 1
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("linear_conv_kernel_dim above MAX_CONV_KERNEL_DIM must be rejected")
.to_string();
assert!(
err.contains("linear_conv_kernel_dim") && err.contains("MAX_CONV_KERNEL_DIM"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_linear_conv_kernel_dim_at_max() {
let json =
format!(r#"{{"text_config": {{"linear_conv_kernel_dim": {MAX_CONV_KERNEL_DIM}}}}}"#);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"linear_conv_kernel_dim == MAX_CONV_KERNEL_DIM must be accepted"
);
}
#[test]
fn parser_rejects_gdn_conv_buffer_size_over_max_with_bounded_factors() {
let json = format!(
r#"{{"text_config": {{
"linear_num_key_heads": {MAX_LINEAR_NUM_KEY_HEADS},
"linear_num_value_heads": {MAX_LINEAR_NUM_VALUE_HEADS},
"linear_key_head_dim": 32,
"linear_value_head_dim": 32,
"linear_conv_kernel_dim": {MAX_CONV_KERNEL_DIM}
}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"a conv buffer product over MAX_GDN_CONV_BUFFER_SIZE must be rejected even \
though every individual factor is at or under its own cap",
)
.to_string();
assert!(
(err.contains("GatedDeltaNet conv buffer size")
&& err.contains("MAX_GDN_CONV_BUFFER_SIZE"))
|| err.contains("MAX_GDN_SESSION_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_gdn_conv_buffer_size() {
let json = r#"{"text_config": {
"linear_num_key_heads": 16,
"linear_num_value_heads": 32,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128,
"linear_conv_kernel_dim": 4
}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"realistic GDN conv buffer geometry must be accepted"
);
}
#[test]
fn parser_rejects_rope_table_bytes_over_max_with_bounded_factors() {
let json = format!(
r#"{{"text_config": {{
"max_position_embeddings": {MAX_POSITION_EMBEDDINGS},
"head_dim": {MAX_HEAD_DIM},
"partial_rotary_factor": 1.0
}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"a RoPE table byte count over MAX_ROPE_TABLE_BYTES must be rejected even \
though max_position_embeddings and head_dim are each at or under their own cap",
)
.to_string();
assert!(
err.contains("RoPE table size") && err.contains("MAX_ROPE_TABLE_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_rope_table_bytes() {
let json = r#"{"text_config": {
"max_position_embeddings": 262144,
"head_dim": 256,
"partial_rotary_factor": 0.25
}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"realistic RoPE table geometry must be accepted"
);
}
#[test]
fn parser_rejects_present_vision_config_with_hidden_size_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": {},
"num_heads": 1,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#,
MAX_VISION_HIDDEN_SIZE + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("hidden_size above MAX_VISION_HIDDEN_SIZE must be rejected")
.to_string();
assert!(
err.contains("hidden_size") && err.contains("MAX_VISION_HIDDEN_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_hidden_size_at_max_over_byte_budget() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": {MAX_VISION_HIDDEN_SIZE},
"num_heads": 1,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("hidden_size == MAX_VISION_HIDDEN_SIZE must be rejected by the byte budget")
.to_string();
assert!(
err.contains("MAX_VISION_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_num_heads_over_max() {
let over = MAX_VISION_NUM_HEADS + 1;
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": {over},
"num_heads": {over},
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("num_heads above MAX_VISION_NUM_HEADS must be rejected")
.to_string();
assert!(
err.contains("num_heads") && err.contains("MAX_VISION_NUM_HEADS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_present_vision_config_with_num_heads_at_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": {MAX_VISION_NUM_HEADS},
"num_heads": {MAX_VISION_NUM_HEADS},
"patch_size": 4,
"spatial_merge_size": 1,
"out_hidden_size": 1024,
"temporal_patch_size": 1,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"num_heads == MAX_VISION_NUM_HEADS must be accepted"
);
}
#[test]
fn parser_rejects_present_vision_config_with_patch_size_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": {},
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#,
MAX_VISION_PATCH_SIZE + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("patch_size above MAX_VISION_PATCH_SIZE must be rejected")
.to_string();
assert!(
err.contains("patch_size") && err.contains("MAX_VISION_PATCH_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_patch_size_at_max_over_byte_budget() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": {MAX_VISION_PATCH_SIZE},
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("patch_size == MAX_VISION_PATCH_SIZE must be rejected by the byte budget")
.to_string();
assert!(
err.contains("MAX_VISION_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_spatial_merge_size_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": {},
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#,
MAX_VISION_SPATIAL_MERGE_SIZE + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("spatial_merge_size above MAX_VISION_SPATIAL_MERGE_SIZE must be rejected")
.to_string();
assert!(
err.contains("spatial_merge_size") && err.contains("MAX_VISION_SPATIAL_MERGE_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_spatial_merge_size_at_max_over_byte_budget() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": {MAX_VISION_SPATIAL_MERGE_SIZE},
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"spatial_merge_size == MAX_VISION_SPATIAL_MERGE_SIZE must be rejected by the \
byte budget",
)
.to_string();
assert!(
err.contains("MAX_VISION_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_out_hidden_size_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": {},
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#,
MAX_VISION_OUT_HIDDEN_SIZE + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("out_hidden_size above MAX_VISION_OUT_HIDDEN_SIZE must be rejected")
.to_string();
assert!(
err.contains("out_hidden_size") && err.contains("MAX_VISION_OUT_HIDDEN_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_out_hidden_size_at_max_over_byte_budget() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": {MAX_VISION_OUT_HIDDEN_SIZE},
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"out_hidden_size == MAX_VISION_OUT_HIDDEN_SIZE must be rejected by the byte \
budget",
)
.to_string();
assert!(
err.contains("MAX_VISION_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_temporal_patch_size_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": {},
"num_position_embeddings": 2304,
"in_channels": 3
}}"#,
MAX_VISION_TEMPORAL_PATCH_SIZE + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("temporal_patch_size above MAX_VISION_TEMPORAL_PATCH_SIZE must be rejected")
.to_string();
assert!(
err.contains("temporal_patch_size") && err.contains("MAX_VISION_TEMPORAL_PATCH_SIZE"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_temporal_patch_size_at_max_over_byte_budget() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": {MAX_VISION_TEMPORAL_PATCH_SIZE},
"num_position_embeddings": 2304,
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"temporal_patch_size == MAX_VISION_TEMPORAL_PATCH_SIZE must be rejected by the \
byte budget",
)
.to_string();
assert!(
err.contains("MAX_VISION_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_num_position_embeddings_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": {},
"in_channels": 3
}}"#,
MAX_VISION_NUM_POSITION_EMBEDDINGS + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"num_position_embeddings above MAX_VISION_NUM_POSITION_EMBEDDINGS must be \
rejected",
)
.to_string();
assert!(
err.contains("num_position_embeddings")
&& err.contains("MAX_VISION_NUM_POSITION_EMBEDDINGS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_num_position_embeddings_at_max_over_byte_budget() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": {MAX_VISION_NUM_POSITION_EMBEDDINGS},
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"num_position_embeddings == MAX_VISION_NUM_POSITION_EMBEDDINGS must be \
rejected by the byte budget",
)
.to_string();
assert!(
err.contains("MAX_VISION_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_non_square_num_position_embeddings() {
let json = config_json_with_vision(
r#"{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2305,
"in_channels": 3
}"#,
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("non-square num_position_embeddings must be rejected")
.to_string();
assert!(
err.contains("num_position_embeddings") && err.contains("perfect square"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_present_vision_config_with_square_num_position_embeddings() {
let json = config_json_with_vision(
r#"{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}"#,
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"square num_position_embeddings (48^2 = 2304) must be accepted"
);
}
#[test]
fn parser_rejects_zero_max_position_embeddings() {
let json = r#"{"text_config": {"max_position_embeddings": 0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("max_position_embeddings: 0 must yield an InferenceError, not panic")
.to_string();
assert!(
err.contains("max_position_embeddings"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_max_position_embeddings_at_one() {
let json = r#"{"text_config": {"max_position_embeddings": 1}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"max_position_embeddings: 1 must be accepted (minimal nonzero RoPE table)"
);
}
#[test]
fn parser_rejects_zero_rope_theta() {
let json = r#"{"text_config": {"rope_theta": 0.0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("rope_theta: 0.0 must yield an InferenceError, not NaN/inf propagation")
.to_string();
assert!(err.contains("rope_theta"), "wrong guard fired: {err}");
}
#[test]
fn parser_rejects_negative_rope_theta() {
let json = r#"{"text_config": {"rope_theta": -1.0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("negative rope_theta must yield an InferenceError")
.to_string();
assert!(err.contains("rope_theta"), "wrong guard fired: {err}");
}
#[test]
fn parser_accepts_realistic_rope_theta() {
let json = r#"{"text_config": {"rope_theta": 10000000.0}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"realistic rope_theta must be accepted"
);
}
#[test]
fn parser_rejects_zero_rms_norm_eps() {
let json = r#"{"text_config": {"rms_norm_eps": 0.0}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("rms_norm_eps: 0.0 must yield an InferenceError, not NaN propagation")
.to_string();
assert!(err.contains("rms_norm_eps"), "wrong guard fired: {err}");
}
#[test]
fn parser_rejects_negative_rms_norm_eps() {
let json = r#"{"text_config": {"rms_norm_eps": -1e-6}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("negative rms_norm_eps must yield an InferenceError")
.to_string();
assert!(err.contains("rms_norm_eps"), "wrong guard fired: {err}");
}
#[test]
fn parser_rejects_nan_rms_norm_eps() {
let mut cfg = Qwen35Config::qwen35_2b();
cfg.rms_norm_eps = f32::NAN;
let err = cfg
.validate()
.expect_err("NaN rms_norm_eps must yield an InferenceError")
.to_string();
assert!(err.contains("rms_norm_eps"), "wrong guard fired: {err}");
}
#[test]
fn parser_rejects_infinite_rms_norm_eps() {
let mut cfg = Qwen35Config::qwen35_2b();
cfg.rms_norm_eps = f32::INFINITY;
let err = cfg
.validate()
.expect_err("infinite rms_norm_eps must yield an InferenceError")
.to_string();
assert!(err.contains("rms_norm_eps"), "wrong guard fired: {err}");
}
#[test]
fn parser_accepts_realistic_rms_norm_eps() {
let json = r#"{"text_config": {"rms_norm_eps": 0.000001}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"realistic rms_norm_eps (1e-6) must be accepted"
);
}
#[test]
fn parser_rejects_linear_num_value_heads_over_max() {
let value_heads = MAX_LINEAR_NUM_VALUE_HEADS + 1;
let json = format!(
r#"{{"text_config": {{
"linear_num_key_heads": 1,
"linear_num_value_heads": {value_heads},
"linear_key_head_dim": 1,
"linear_value_head_dim": 1
}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("linear_num_value_heads above MAX_LINEAR_NUM_VALUE_HEADS must be rejected")
.to_string();
assert!(
err.contains("linear_num_value_heads") && err.contains("MAX_LINEAR_NUM_VALUE_HEADS"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_linear_num_value_heads_at_max() {
let json = format!(
r#"{{"text_config": {{
"linear_num_key_heads": 1,
"linear_num_value_heads": {MAX_LINEAR_NUM_VALUE_HEADS},
"linear_key_head_dim": 1,
"linear_value_head_dim": 1
}}}}"#
);
assert!(
Qwen35Config::from_config_json_str(&json).is_ok(),
"linear_num_value_heads == MAX_LINEAR_NUM_VALUE_HEADS must be accepted"
);
}
#[test]
fn parser_rejects_gdn_chunk_scratch_over_max_with_value_heads_and_state_size_in_budget() {
let huge_key_dim = 600_000;
let json = format!(
r#"{{"text_config": {{
"linear_num_key_heads": 1,
"linear_num_value_heads": 1,
"linear_key_head_dim": {huge_key_dim},
"linear_value_head_dim": 1
}}}}"#
);
assert!(
huge_key_dim <= MAX_GDN_STATE_SIZE,
"test fixture assumption: gdn_state_size must be within MAX_GDN_STATE_SIZE"
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err(
"a GDN chunk-scratch product over MAX_GDN_CHUNK_SCRATCH_BYTES must be rejected \
even though linear_num_value_heads, MAX_LINEAR_OUTPUT_DIM, and \
MAX_GDN_STATE_SIZE all pass",
)
.to_string();
assert!(
err.contains("chunk-scratch") && err.contains("MAX_GDN_CHUNK_SCRATCH_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_gdn_chunk_scratch_geometry() {
let json = r#"{"text_config": {
"linear_num_key_heads": 16,
"linear_num_value_heads": 32,
"linear_key_head_dim": 128,
"linear_value_head_dim": 128
}}"#;
assert!(
Qwen35Config::from_config_json_str(json).is_ok(),
"realistic GDN chunk-scratch geometry must be accepted"
);
}
#[test]
fn parser_rejects_present_vision_config_with_in_channels_over_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": {}
}}"#,
MAX_VISION_IN_CHANNELS + 1
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("in_channels above MAX_VISION_IN_CHANNELS must be rejected")
.to_string();
assert!(
err.contains("in_channels must be 3"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_present_vision_config_with_in_channels_at_max() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": {MAX_VISION_IN_CHANNELS}
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("in_channels == MAX_VISION_IN_CHANNELS is not RGB and must be rejected")
.to_string();
assert!(
err.contains("in_channels must be 3"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_present_vision_config_with_in_channels_three() {
let json = config_json_with_vision(
r#"{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": 3
}"#,
);
let cfg = Qwen35Config::from_config_json_str(&json)
.expect("in_channels: 3 vision_config must parse");
assert_eq!(
cfg.vision_config
.expect("vision_config present")
.in_channels,
3
);
}
#[test]
fn parser_rejects_present_vision_config_with_any_in_channels_but_three() {
for in_channels in [0_usize, 1, 2, 4] {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": 768,
"num_heads": 12,
"patch_size": 16,
"spatial_merge_size": 2,
"out_hidden_size": 1024,
"temporal_patch_size": 2,
"num_position_embeddings": 2304,
"in_channels": {in_channels}
}}"#
));
let err = match Qwen35Config::from_config_json_str(&json) {
Ok(_) => {
panic!(
"in_channels: {in_channels} vision_config must be rejected at parse time"
)
}
Err(e) => e.to_string(),
};
assert!(
err.contains("in_channels must be 3"),
"in_channels: {in_channels} -- wrong guard fired: {err}"
);
}
}
#[test]
fn parser_rejects_hostile_gdn_aggregate_value_heads_geometry() {
let json = r#"{"text_config": {
"linear_num_key_heads": 16,
"linear_num_value_heads": 4096,
"linear_key_head_dim": 128,
"linear_value_head_dim": 32
}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("hostile aggregate GDN geometry (value_heads=4096) must be rejected")
.to_string();
assert!(
err.contains("MAX_GDN_SESSION_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_rejects_hostile_gdn_aggregate_key_head_dim_geometry() {
let json = r#"{"text_config": {
"linear_num_key_heads": 1,
"linear_num_value_heads": 1,
"linear_key_head_dim": 524288,
"linear_value_head_dim": 32,
"linear_conv_kernel_dim": 16
}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("hostile aggregate GDN geometry (key_head_dim=524288) must be rejected")
.to_string();
assert!(
err.contains("MAX_GDN_SESSION_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_gdn_aggregate_geometry() {
let cfg = Qwen35Config::qwen36_27b();
assert!(
cfg.validate().is_ok(),
"realistic qwen36_27b GDN aggregate geometry must be accepted"
);
}
#[test]
fn parser_rejects_hostile_gdn_cross_layer_state_aggregate() {
let json = r#"{"text_config": {
"num_hidden_layers": 512,
"full_attention_interval": 999999,
"linear_num_key_heads": 16,
"linear_num_value_heads": 48,
"linear_key_head_dim": 128,
"linear_value_head_dim": 700,
"linear_conv_kernel_dim": 4
}}"#;
let err = Qwen35Config::from_config_json_str(json)
.expect_err("hostile 512-linear-layer GDN cross-layer aggregate must be rejected")
.to_string();
assert!(
err.contains("MAX_GDN_CROSS_LAYER_STATE_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_gdn_cross_layer_state_aggregate() {
let cfg = Qwen35Config::qwen36_27b();
assert!(
cfg.validate().is_ok(),
"realistic qwen36_27b GDN cross-layer aggregate must be accepted"
);
}
#[test]
fn parser_rejects_hostile_embedding_product() {
let json = format!(
r#"{{"text_config": {{"vocab_size": 4000000, "hidden_size": {MAX_HIDDEN_SIZE}}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("hostile embedding product must be rejected before materialization")
.to_string();
assert!(
err.contains("MAX_EMBEDDING_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_embedding_product() {
let cfg = Qwen35Config::qwen36_35b_a3b();
assert!(
cfg.validate().is_ok(),
"realistic embedding product must be accepted"
);
}
#[test]
fn parser_rejects_hostile_dense_ffn_product() {
let json = format!(
r#"{{"text_config": {{"vocab_size": 1, "hidden_size": {MAX_HIDDEN_SIZE}, "intermediate_size": {MAX_INTERMEDIATE_SIZE}, "num_experts": null, "num_experts_per_tok": null, "moe_intermediate_size": null, "shared_expert_intermediate_size": null}}}}"#
);
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("hostile dense FFN product must be rejected before materialization")
.to_string();
assert!(
err.contains("MAX_DENSE_FFN_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn parser_accepts_realistic_dense_ffn_product() {
let cfg = Qwen35Config::qwen35_2b();
assert!(
cfg.validate().is_ok(),
"realistic dense FFN product must be accepted"
);
}
#[test]
fn parser_rejects_hostile_vision_derived_tensor_product() {
let json = config_json_with_vision(&format!(
r#"{{
"depth": 4,
"hidden_size": {MAX_VISION_HIDDEN_SIZE},
"num_heads": 1,
"patch_size": 14,
"spatial_merge_size": 1,
"out_hidden_size": {MAX_VISION_OUT_HIDDEN_SIZE},
"temporal_patch_size": 2,
"num_position_embeddings": 16777216,
"in_channels": 3
}}"#
));
let err = Qwen35Config::from_config_json_str(&json)
.expect_err("hostile vision derived-tensor product must be rejected")
.to_string();
assert!(
err.contains("MAX_VISION_TENSOR_BYTES"),
"wrong guard fired: {err}"
);
}
#[test]
fn validated_config_rejects_hostile_directly_constructed_config() {
let mut cfg = Qwen35Config::qwen35_2b();
cfg.linear_num_key_heads = 16;
cfg.linear_num_value_heads = Some(4096);
cfg.linear_key_head_dim = 128;
cfg.linear_value_head_dim = 32;
let result: Result<ValidatedQwen35Config, InferenceError> = cfg.try_into();
assert!(
result.is_err(),
"hostile directly-constructed config must fail ValidatedQwen35Config::try_from"
);
}
#[test]
fn validated_config_accepts_and_derefs_realistic_config() {
let validated = Qwen35Config::qwen35_2b()
.validate()
.expect("realistic preset must validate");
assert_eq!(validated.hidden_size, 2048);
let raw = validated.into_inner();
assert_eq!(raw.hidden_size, 2048);
}
#[test]
fn from_config_json_str_validated_matches_from_config_json_str() {
let json = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/qwen36_config.json"
));
let raw = Qwen35Config::from_config_json_str(json).expect("raw parse succeeds");
let validated =
Qwen35Config::from_config_json_str_validated(json).expect("validated parse succeeds");
assert_eq!(raw.hidden_size, validated.hidden_size);
assert_eq!(raw.num_hidden_layers, validated.num_hidden_layers);
}
}