use crate::error::Result;
use crate::gguf::quantized::{OwnedQuantizedTensor, QuantizedTensorRef};
use crate::gguf::{GGUFConfig, GGUFModel, OwnedQuantizedModel};
use std::f32::consts::E;
#[path = "qwen35_known_issue.rs"]
pub mod qwen35_known_issue;
pub fn silu(x: f32) -> f32 {
x / (1.0 + (-x as f32).exp())
}
pub fn softplus(x: f32) -> f32 {
if x > 20.0 {
x
} else {
(1.0 + x.exp()).ln()
}
}
pub fn l2_norm(x: &mut [f32], eps: f32) {
let mut sq_sum = 0.0;
for &v in x.iter() {
sq_sum += v * v;
}
let scale = 1.0 / (sq_sum + eps).sqrt();
for v in x.iter_mut() {
*v *= scale;
}
}
pub fn l2_norm_per_head(x: &mut [f32], head_dim: usize, eps: f32) {
for head in x.chunks_exact_mut(head_dim) {
l2_norm(head, eps);
}
}
pub fn apply_sigmoid_gate(x: &mut [f32], gate: &[f32]) {
for (o, g) in x.iter_mut().zip(gate) {
*o *= 1.0 / (1.0 + (-*g).exp());
}
}
pub fn gated_rmsnorm(
input: &[f32],
gate: &[f32],
weight: &[f32],
eps: f32,
head_v_dim: usize,
output: &mut [f32],
) {
assert_eq!(input.len(), gate.len());
assert_eq!(input.len(), output.len());
assert_eq!(weight.len(), head_v_dim);
assert_eq!(input.len() % head_v_dim, 0);
for (chunk_in, (chunk_gate, chunk_out)) in input.chunks_exact(head_v_dim).zip(
gate.chunks_exact(head_v_dim)
.zip(output.chunks_exact_mut(head_v_dim)),
) {
let mut sq_sum = 0.0;
for &v in chunk_in {
sq_sum += v * v;
}
let rms_scale = 1.0 / ((sq_sum / head_v_dim as f32) + eps).sqrt();
for i in 0..head_v_dim {
let norm_v = chunk_in[i] * rms_scale * weight[i];
chunk_out[i] = norm_v * silu(chunk_gate[i]);
}
}
}
pub fn causal_conv1d(
input: &[f32],
state: &mut [f32],
weight: &[f32],
kernel_size: usize,
channels: usize,
output: &mut [f32],
) {
assert_eq!(input.len(), channels);
assert_eq!(state.len(), (kernel_size - 1) * channels);
assert_eq!(weight.len(), kernel_size * channels);
assert_eq!(output.len(), channels);
for c in 0..channels {
let mut sum = 0.0;
let s_offset = c * (kernel_size - 1);
let w_offset = c * kernel_size;
for k in 0..(kernel_size - 1) {
sum += state[s_offset + k] * weight[w_offset + k];
}
sum += input[c] * weight[w_offset + kernel_size - 1];
for k in 0..(kernel_size - 2) {
state[s_offset + k] = state[s_offset + k + 1];
}
if kernel_size > 1 {
state[s_offset + kernel_size - 2] = input[c];
}
output[c] = sum;
}
}
pub fn delta_rule_recurrence(
q: &[f32],
k: &[f32],
v: &[f32],
beta: &[f32],
gate: &[f32],
state: &mut [f32],
output: &mut [f32],
num_v_heads: usize,
head_v_dim: usize,
) {
delta_rule_recurrence_gqa(
q,
k,
v,
beta,
gate,
state,
output,
num_v_heads,
head_v_dim,
num_v_heads,
head_v_dim,
);
}
pub fn delta_rule_recurrence_gqa(
q: &[f32],
k: &[f32],
v: &[f32],
beta: &[f32],
gate: &[f32],
state: &mut [f32],
output: &mut [f32],
num_k_heads: usize,
head_k_dim: usize,
num_v_heads: usize,
head_v_dim: usize,
) {
assert!(
num_k_heads > 0 && num_v_heads % num_k_heads == 0,
"Gated DeltaNet: num_v_heads ({num_v_heads}) must be a positive multiple of num_k_heads \
({num_k_heads})"
);
assert_eq!(q.len(), num_k_heads * head_k_dim);
assert_eq!(k.len(), num_k_heads * head_k_dim);
assert_eq!(v.len(), num_v_heads * head_v_dim);
assert_eq!(beta.len(), num_v_heads);
assert_eq!(gate.len(), num_v_heads);
assert_eq!(state.len(), num_v_heads * head_v_dim * head_k_dim);
assert_eq!(output.len(), num_v_heads * head_v_dim);
let scale = 1.0 / (head_k_dim as f32).sqrt();
for h in 0..num_v_heads {
let kh = h % num_k_heads;
let q_h = &q[kh * head_k_dim..(kh + 1) * head_k_dim];
let k_h = &k[kh * head_k_dim..(kh + 1) * head_k_dim];
let v_h = &v[h * head_v_dim..(h + 1) * head_v_dim];
let beta_val = beta[h];
let gate_val = gate[h];
let state_stride = head_v_dim * head_k_dim;
let state_offset = h * state_stride;
delta_rule_head(
q_h,
k_h,
v_h,
beta_val,
gate_val,
&mut state[state_offset..state_offset + state_stride],
&mut output[h * head_v_dim..(h + 1) * head_v_dim],
head_k_dim,
scale,
);
}
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub(crate) fn delta_rule_head(
q_h: &[f32],
k_h: &[f32],
v_h: &[f32],
beta_val: f32,
gate_val: f32,
s_h: &mut [f32],
out_h: &mut [f32],
head_k_dim: usize,
scale: f32,
) {
let head_v_dim = out_h.len();
let exp_gate = gate_val.exp();
for s in s_h.iter_mut() {
*s *= exp_gate;
}
let mut delta = vec![0.0; head_v_dim];
for j in 0..head_v_dim {
let row_j = &s_h[j * head_k_dim..(j + 1) * head_k_dim];
let mut sum = 0.0;
for i in 0..head_k_dim {
sum += row_j[i] * k_h[i];
}
delta[j] = (v_h[j] - sum) * beta_val;
}
for j in 0..head_v_dim {
let row_j = &mut s_h[j * head_k_dim..(j + 1) * head_k_dim];
let d_j = delta[j];
for i in 0..head_k_dim {
row_j[i] += k_h[i] * d_j;
}
}
for j in 0..head_v_dim {
let row_j = &s_h[j * head_k_dim..(j + 1) * head_k_dim];
let mut sum = 0.0;
for i in 0..head_k_dim {
sum += row_j[i] * q_h[i];
}
out_h[j] = sum * scale;
}
}
pub fn apply_partial_neox_rope(
x: &mut [f32],
num_heads: usize,
head_dim: usize,
n_rot: usize,
pos: usize,
freq_base: f32,
) {
let half = n_rot / 2;
let theta_scale = freq_base.powf(-2.0 / n_rot as f32);
for h in 0..num_heads {
let base = h * head_dim;
let mut theta = pos as f32;
for j in 0..half {
let (sin, cos) = theta.sin_cos();
let (a, b) = (x[base + j], x[base + j + half]);
x[base + j] = a * cos - b * sin;
x[base + j + half] = a * sin + b * cos;
theta *= theta_scale;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_silu() {
assert!((silu(0.0) - 0.0).abs() < 1e-6);
assert!((silu(1.0) - (1.0 / (1.0 + (-1.0_f32).exp()))).abs() < 1e-6);
}
#[test]
fn test_l2_norm() {
let mut x = [1.0, 2.0, 2.0];
l2_norm(&mut x, 0.0);
assert!((x[0] - 1.0 / 3.0).abs() < 1e-6);
assert!((x[1] - 2.0 / 3.0).abs() < 1e-6);
assert!((x[2] - 2.0 / 3.0).abs() < 1e-6);
}
#[test]
fn test_causal_conv1d() {
let input = [1.0, 2.0];
let mut state = [0.1, 0.2, 0.3, 0.4]; let weight = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
let mut output = [0.0, 0.0];
causal_conv1d(&input, &mut state, &weight, 3, 2, &mut output);
assert!((output[0] - 3.5).abs() < 1e-5);
assert!((output[1] - 15.2).abs() < 1e-5);
assert!((state[0] - 0.2).abs() < 1e-5);
assert!((state[1] - 1.0).abs() < 1e-5);
assert!((state[2] - 0.4).abs() < 1e-5);
assert!((state[3] - 2.0).abs() < 1e-5);
}
#[test]
fn test_delta_rule_recurrence() {
let num_v_heads = 1;
let head_v_dim = 2;
let q = [1.0, 2.0];
let k = [0.5, 0.5];
let v = [1.0, -1.0];
let beta = [0.5];
let gate = [0.0];
let mut state = [
1.0, 0.0, 0.0, 1.0, ];
let mut output = [0.0, 0.0];
delta_rule_recurrence(
&q,
&k,
&v,
&beta,
&gate,
&mut state,
&mut output,
num_v_heads,
head_v_dim,
);
let s2 = 2.0_f32.sqrt();
assert!((output[0] - 1.375 / s2).abs() < 1e-5);
assert!((output[1] - 0.875 / s2).abs() < 1e-5);
assert!((state[0] - 1.125).abs() < 1e-5); assert!((state[1] - 0.125).abs() < 1e-5); assert!((state[2] - -0.375).abs() < 1e-5); assert!((state[3] - 0.625).abs() < 1e-5); }
}
pub struct Qwen35State {
pub(crate) conv_states: Vec<Vec<f32>>,
pub(crate) ssm_states: Vec<Vec<f32>>,
pub(crate) kv_cache: crate::gguf::OwnedQuantizedKVCache,
}
impl Qwen35State {
pub(crate) fn new(
num_layers: usize,
max_seq_len: usize,
head_dim: usize,
num_kv_heads: usize,
num_k_heads: usize,
head_k_dim: usize,
num_v_heads: usize,
head_value_dim: usize,
) -> Self {
let convalue_dim = head_k_dim * num_k_heads * 2 + head_value_dim * num_v_heads;
Self {
conv_states: vec![vec![0.0; convalue_dim * 3]; num_layers],
ssm_states: vec![vec![0.0; num_v_heads * head_value_dim * head_k_dim]; num_layers],
kv_cache: crate::gguf::OwnedQuantizedKVCache::new(
num_layers,
num_kv_heads * head_dim,
max_seq_len,
),
}
}
pub fn reset(&mut self) {
for v in self
.conv_states
.iter_mut()
.chain(self.ssm_states.iter_mut())
{
v.fill(0.0);
}
self.kv_cache.reset();
}
}
#[derive(Debug, Clone)]
pub struct Qwen35Checkpoint {
conv_states: Vec<Vec<f32>>,
ssm_states: Vec<Vec<f32>>,
kv_len: usize,
}
impl Qwen35State {
#[must_use]
pub fn checkpoint(&self) -> Qwen35Checkpoint {
Qwen35Checkpoint {
conv_states: self.conv_states.clone(),
ssm_states: self.ssm_states.clone(),
kv_len: self.kv_cache.len(),
}
}
pub fn restore(&mut self, checkpoint: &Qwen35Checkpoint) -> bool {
if self.kv_cache.len() < checkpoint.kv_len
|| self.conv_states.len() != checkpoint.conv_states.len()
|| self.ssm_states.len() != checkpoint.ssm_states.len()
{
return false;
}
for (dst, src) in self
.conv_states
.iter_mut()
.zip(&checkpoint.conv_states)
.chain(self.ssm_states.iter_mut().zip(&checkpoint.ssm_states))
{
dst.copy_from_slice(src);
}
self.kv_cache.truncate(checkpoint.kv_len);
true
}
}
pub(crate) struct Qwen35OwnedDeltaNetLayer {
pub(crate) attn_norm: Vec<f32>,
pub(crate) attn_qkv: OwnedQuantizedTensor,
pub(crate) attn_gate: OwnedQuantizedTensor,
pub(crate) ssm_alpha: OwnedQuantizedTensor,
pub(crate) ssm_beta: OwnedQuantizedTensor,
pub(crate) ssm_a: Vec<f32>,
pub(crate) ssm_dt_bias: Vec<f32>,
pub ssm_conv1d_weight: Vec<f32>,
pub(crate) ssm_norm_weight: Vec<f32>,
pub(crate) ssm_out: OwnedQuantizedTensor,
pub(crate) post_attention_norm: Vec<f32>,
pub(crate) ffn_gate: OwnedQuantizedTensor,
pub(crate) ffn_up: OwnedQuantizedTensor,
pub(crate) ffn_down: OwnedQuantizedTensor,
}
pub(crate) struct Qwen35OwnedAttentionLayer {
pub(crate) attn_norm: Vec<f32>,
pub(crate) attn_q: OwnedQuantizedTensor,
pub(crate) attn_k: OwnedQuantizedTensor,
pub(crate) attn_v: OwnedQuantizedTensor,
pub(crate) attn_q_norm: Vec<f32>,
pub(crate) attn_k_norm: Vec<f32>,
pub(crate) attn_output: OwnedQuantizedTensor,
pub(crate) post_attention_norm: Vec<f32>,
pub(crate) ffn_gate: OwnedQuantizedTensor,
pub(crate) ffn_up: OwnedQuantizedTensor,
pub(crate) ffn_down: OwnedQuantizedTensor,
}
pub(crate) enum Qwen35OwnedLayer {
DeltaNet(Qwen35OwnedDeltaNetLayer),
Attention(Qwen35OwnedAttentionLayer),
}
pub struct Qwen35Model<'a> {
pub(crate) base: &'a OwnedQuantizedModel,
pub(crate) layers: Vec<Qwen35OwnedLayer>,
pub(crate) head_dim: usize,
pub(crate) num_kv_heads: usize,
pub(crate) num_v_heads: usize,
pub(crate) head_v_dim: usize,
pub(crate) num_k_heads: usize,
pub(crate) head_k_dim: usize,
pub(crate) conv_kernel: usize,
pub(crate) rope_sections: [usize; 4],
}
fn load_f32_vec(tensor_ref: &QuantizedTensorRef, data: &[u8]) -> Result<Vec<f32>> {
if tensor_ref.qtype != crate::gguf::types::GGUF_TYPE_F32 {
return Err(crate::error::RealizarError::FormatError {
reason: format!(
"qwen35: expected an F32 tensor, found GGUF type {}",
tensor_ref.qtype
),
});
}
let bytes = data
.get(tensor_ref.offset..tensor_ref.offset + tensor_ref.byte_size)
.ok_or_else(|| crate::error::RealizarError::FormatError {
reason: format!(
"qwen35: F32 tensor at byte {} (+{}) lies outside the file",
tensor_ref.offset, tensor_ref.byte_size
),
})?;
Ok(bytes
.as_chunks::<4>()
.0
.iter()
.map(|c| f32::from_le_bytes(*c))
.collect())
}
type GGUFMetadata = std::collections::HashMap<String, crate::gguf::types::GGUFValue>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Qwen35SsmMeta {
pub(crate) head_k_dim: usize,
pub(crate) num_k_heads: usize,
pub(crate) num_v_heads: usize,
pub(crate) conv_kernel: usize,
pub(crate) rope_sections: [usize; 4],
}
fn metadata_usize(metadata: &GGUFMetadata, key: &str) -> Option<usize> {
match metadata.get(key) {
Some(crate::gguf::types::GGUFValue::UInt32(v)) => Some(*v as usize),
Some(crate::gguf::types::GGUFValue::Int32(v)) => Some(*v as usize),
_ => None,
}
}
fn metadata_usize_or(metadata: &GGUFMetadata, key: &str, fallback: &str, default: usize) -> usize {
metadata_usize(metadata, key)
.or_else(|| metadata_usize(metadata, fallback))
.unwrap_or(default)
}
fn rope_sections_from_metadata(metadata: &GGUFMetadata) -> [usize; 4] {
let mut rope_sections = [16, 24, 24, 0];
if let Some(crate::gguf::types::GGUFValue::Array(arr)) = metadata
.get("qwen2.rope.dimension_sections")
.or_else(|| metadata.get("qwen35.rope.dimension_sections"))
{
for (i, val) in arr.iter().take(4).enumerate() {
if let crate::gguf::types::GGUFValue::UInt32(v) = val {
rope_sections[i] = *v as usize;
} else if let crate::gguf::types::GGUFValue::Int32(v) = val {
rope_sections[i] = *v as usize;
}
}
}
rope_sections
}
pub(crate) fn qwen35_ssm_meta(metadata: &GGUFMetadata) -> Qwen35SsmMeta {
Qwen35SsmMeta {
head_k_dim: metadata_usize_or(
metadata,
"qwen2.ssm.state_size",
"qwen35.ssm.state_size",
16,
),
num_k_heads: metadata_usize_or(
metadata,
"qwen2.ssm.group_count",
"qwen35.ssm.group_count",
1,
),
num_v_heads: metadata_usize_or(
metadata,
"qwen2.ssm.time_step_rank",
"qwen35.ssm.time_step_rank",
16,
),
conv_kernel: metadata_usize_or(
metadata,
"qwen2.ssm.conv_kernel",
"qwen35.ssm.conv_kernel",
4,
),
rope_sections: rope_sections_from_metadata(metadata),
}
}
struct Qwen35LayerDims {
hidden_dim: usize,
intermediate_dim: usize,
num_heads: usize,
num_kv_heads: usize,
num_v_heads: usize,
conv_dim: usize,
value_dim: usize,
}
fn own_deltanet_layer(
d: &crate::gguf::qwen35_load::Qwen35DeltaNetLayer,
data: &[u8],
dims: &Qwen35LayerDims,
) -> Result<Qwen35OwnedDeltaNetLayer> {
Ok(Qwen35OwnedDeltaNetLayer {
attn_norm: load_f32_vec(&d.attn_norm, data)?,
attn_qkv: OwnedQuantizedTensor::from_ref_with_dims(
&d.attn_qkv,
data,
dims.hidden_dim,
dims.conv_dim,
),
attn_gate: OwnedQuantizedTensor::from_ref_with_dims(
&d.attn_gate,
data,
dims.hidden_dim,
dims.value_dim,
),
ssm_alpha: OwnedQuantizedTensor::from_ref_with_dims(
&d.ssm_alpha,
data,
dims.hidden_dim,
dims.num_v_heads,
),
ssm_beta: OwnedQuantizedTensor::from_ref_with_dims(
&d.ssm_beta,
data,
dims.hidden_dim,
dims.num_v_heads,
),
ssm_a: load_f32_vec(&d.ssm_a, data)?,
ssm_dt_bias: load_f32_vec(&d.ssm_dt_bias, data)?,
ssm_conv1d_weight: load_f32_vec(&d.ssm_conv1d_weight, data)?,
ssm_norm_weight: load_f32_vec(&d.ssm_norm_weight, data)?,
ssm_out: OwnedQuantizedTensor::from_ref_with_dims(
&d.ssm_out,
data,
dims.value_dim,
dims.hidden_dim,
),
post_attention_norm: load_f32_vec(&d.post_attention_norm, data)?,
ffn_gate: OwnedQuantizedTensor::from_ref_with_dims(
&d.ffn_gate,
data,
dims.hidden_dim,
dims.intermediate_dim,
),
ffn_up: OwnedQuantizedTensor::from_ref_with_dims(
&d.ffn_up,
data,
dims.hidden_dim,
dims.intermediate_dim,
),
ffn_down: OwnedQuantizedTensor::from_ref_with_dims(
&d.ffn_down,
data,
dims.intermediate_dim,
dims.hidden_dim,
),
})
}
fn own_attention_layer(
a: &crate::gguf::qwen35_load::Qwen35AttentionLayer,
data: &[u8],
dims: &Qwen35LayerDims,
) -> Result<Qwen35OwnedAttentionLayer> {
let attn_q_norm = load_f32_vec(&a.attn_q_norm, data)?;
let true_head_dim = attn_q_norm.len();
Ok(Qwen35OwnedAttentionLayer {
attn_norm: load_f32_vec(&a.attn_norm, data)?,
attn_q: OwnedQuantizedTensor::from_ref_with_dims(
&a.attn_q,
data,
dims.hidden_dim,
dims.num_heads * true_head_dim * 2,
),
attn_k: OwnedQuantizedTensor::from_ref_with_dims(
&a.attn_k,
data,
dims.hidden_dim,
dims.num_kv_heads * true_head_dim,
),
attn_v: OwnedQuantizedTensor::from_ref_with_dims(
&a.attn_v,
data,
dims.hidden_dim,
dims.num_kv_heads * true_head_dim,
),
attn_q_norm,
attn_k_norm: load_f32_vec(&a.attn_k_norm, data)?,
attn_output: OwnedQuantizedTensor::from_ref_with_dims(
&a.attn_output,
data,
dims.num_heads * true_head_dim,
dims.hidden_dim,
),
post_attention_norm: load_f32_vec(&a.post_attention_norm, data)?,
ffn_gate: OwnedQuantizedTensor::from_ref_with_dims(
&a.ffn_gate,
data,
dims.hidden_dim,
dims.intermediate_dim,
),
ffn_up: OwnedQuantizedTensor::from_ref_with_dims(
&a.ffn_up,
data,
dims.hidden_dim,
dims.intermediate_dim,
),
ffn_down: OwnedQuantizedTensor::from_ref_with_dims(
&a.ffn_down,
data,
dims.intermediate_dim,
dims.hidden_dim,
),
})
}
fn attention_head_dim(layers: &[Qwen35OwnedLayer], fallback: usize) -> usize {
layers
.iter()
.find_map(|l| match l {
Qwen35OwnedLayer::Attention(a) => Some(a.attn_q_norm.len()),
Qwen35OwnedLayer::DeltaNet(_) => None,
})
.unwrap_or(fallback)
}
impl<'a> Qwen35Model<'a> {
pub fn create_base_model(
model: &crate::gguf::GGUFModel,
data: &[u8],
) -> crate::error::Result<crate::gguf::OwnedQuantizedModel> {
qwen35_known_issue::warn_if_known_issue(model);
let config = crate::gguf::config::ValidatedModelConfig::from_gguf(model)?.into_inner();
let token_embedding = model.get_tensor_f32("token_embd.weight", data)?;
let output_norm_weight = model.get_tensor_f32("output_norm.weight", data)?;
let lm_head_ref =
crate::gguf::QuantizedGGUFTransformer::get_tensor_ref(model, data, "output.weight")
.or_else(|_| {
crate::gguf::QuantizedGGUFTransformer::get_tensor_ref(
model,
data,
"token_embd.weight",
)
})?;
let lm_head_weight = crate::gguf::OwnedQuantizedTensor::from_ref_with_dims(
&lm_head_ref,
data,
config.hidden_dim,
config.vocab_size,
);
Ok(crate::gguf::OwnedQuantizedModel {
config,
token_embedding,
position_embedding: None,
layers: vec![],
encoder_layers: vec![],
encoder_output_norm_weight: None,
encoder_output_norm_bias: None,
output_norm_weight,
output_norm_bias: None,
lm_head_weight,
lm_head_bias: None,
#[cfg(feature = "cuda")]
cuda_executor: None,
#[cfg(feature = "cuda")]
cuda_kernel_count: std::sync::atomic::AtomicU64::new(0),
#[cfg(feature = "cuda")]
cached_weight_names: std::sync::Mutex::new(std::collections::HashSet::new()),
})
}
pub fn from_model_and_layers(
base: &'a OwnedQuantizedModel,
model: &GGUFModel,
data: &[u8],
) -> Result<Self> {
let refs = crate::gguf::qwen35_load::load_qwen35_layers(model, data)?;
let meta = qwen35_ssm_meta(&model.metadata);
let head_v_dim = meta.head_k_dim;
let num_heads = base.config.num_heads;
let num_kv_heads = base.config.num_kv_heads;
let head_dim = base.config.hidden_dim / num_heads;
let key_dim = meta.head_k_dim * meta.num_k_heads;
let value_dim = head_v_dim * meta.num_v_heads;
let dims = Qwen35LayerDims {
hidden_dim: base.config.hidden_dim,
intermediate_dim: base.config.intermediate_dim,
num_heads,
num_kv_heads,
num_v_heads: meta.num_v_heads,
conv_dim: key_dim * 2 + value_dim,
value_dim,
};
let mut owned = Vec::with_capacity(refs.len());
for layer_ref in refs {
owned.push(match layer_ref {
crate::gguf::qwen35_load::Qwen35Layer::DeltaNet(d) => {
Qwen35OwnedLayer::DeltaNet(own_deltanet_layer(&d, data, &dims)?)
},
crate::gguf::qwen35_load::Qwen35Layer::Attention(a) => {
Qwen35OwnedLayer::Attention(own_attention_layer(&a, data, &dims)?)
},
});
}
let attn_head_dim = attention_head_dim(&owned, head_dim);
Ok(Self {
base,
layers: owned,
head_dim: attn_head_dim,
num_kv_heads,
num_v_heads: meta.num_v_heads,
head_v_dim,
num_k_heads: meta.num_k_heads,
head_k_dim: meta.head_k_dim,
conv_kernel: meta.conv_kernel,
rope_sections: meta.rope_sections,
})
}
#[must_use]
pub fn new_state(&self, max_seq_len: usize) -> Qwen35State {
Qwen35State::new(
self.layers.len(),
max_seq_len,
self.head_dim,
self.num_kv_heads,
self.num_k_heads,
self.head_k_dim,
self.num_v_heads,
self.head_v_dim,
)
}
pub fn forward_single_qwen35(
&self,
token_id: u32,
cache: &mut Qwen35State,
position: usize,
) -> Result<Vec<f32>> {
let mut hidden = self.base.token_embedding()[(token_id as usize)
* self.base.config.hidden_dim
..(token_id as usize + 1) * self.base.config.hidden_dim]
.to_vec();
let mut normed = vec![0.0; self.base.config.hidden_dim];
let mut post_attn_normed = vec![0.0; self.base.config.hidden_dim];
let fn_softplus = |x: f32| -> f32 {
if x > 20.0 {
x
} else {
(1.0 + x.exp()).ln()
}
};
for (il, layer) in self.layers.iter().enumerate() {
match layer {
Qwen35OwnedLayer::DeltaNet(d) => {
self.forward_deltanet(
d,
&mut hidden,
cache,
il,
position,
&mut normed,
&mut post_attn_normed,
)?;
},
Qwen35OwnedLayer::Attention(a) => {
self.forward_attention(
a,
&mut hidden,
cache,
il,
position,
&mut normed,
&mut post_attn_normed,
)?;
},
}
}
let mut out_normed = vec![0.0; self.base.config.hidden_dim];
crate::gguf::ops::rms_norm_into(
&hidden,
self.base.output_norm_weight(),
self.base.config.eps,
&mut out_normed,
);
let mut logits = vec![0.0; self.base.config.vocab_size];
self.base
.fused_matmul_into(&out_normed, self.base.lm_head_weight(), &mut logits)?;
cache.kv_cache.advance();
Ok(logits)
}
pub(crate) fn forward_deltanet(
&self,
d: &Qwen35OwnedDeltaNetLayer,
hidden: &mut [f32],
cache: &mut Qwen35State,
il: usize,
position: usize,
normed: &mut [f32],
post_attn_normed: &mut [f32],
) -> Result<()> {
crate::gguf::ops::rms_norm_into(hidden, &d.attn_norm, self.base.config.eps, normed);
let conv_dim = self.head_k_dim * self.num_k_heads * 2 + self.head_v_dim * self.num_v_heads;
let v_dim = self.head_v_dim * self.num_v_heads;
let mut conv_in = vec![0.0; conv_dim];
self.base
.fused_matmul_into(normed, &d.attn_qkv, &mut conv_in)?;
let mut dt_raw = vec![0.0; self.num_v_heads];
self.base
.fused_matmul_into(normed, &d.ssm_alpha, &mut dt_raw)?;
let mut beta = vec![0.0; self.num_v_heads];
self.base
.fused_matmul_into(normed, &d.ssm_beta, &mut beta)?;
let mut gate = vec![0.0; v_dim];
self.base
.fused_matmul_into(normed, &d.attn_gate, &mut gate)?;
let ssm_out_in = self.deltanet_mix_token(d, cache, il, &conv_in, &dt_raw, &mut beta, &gate);
let mut ssm_out = vec![0.0; self.base.config.hidden_dim];
self.base
.fused_matmul_into(&ssm_out_in, &d.ssm_out, &mut ssm_out)?;
for i in 0..self.base.config.hidden_dim {
hidden[i] += ssm_out[i];
}
crate::gguf::ops::rms_norm_into(
hidden,
&d.post_attention_norm,
self.base.config.eps,
post_attn_normed,
);
let mut ffn_gate = vec![0.0; d.ffn_gate.out_dim];
self.base
.fused_matmul_into(post_attn_normed, &d.ffn_gate, &mut ffn_gate)?;
let mut ffn_up = vec![0.0; d.ffn_up.out_dim];
self.base
.fused_matmul_into(post_attn_normed, &d.ffn_up, &mut ffn_up)?;
for i in 0..ffn_gate.len() {
let x = ffn_gate[i];
let silu = x / (1.0 + (-x as f32).exp());
ffn_up[i] *= silu;
}
let mut ffn_down = vec![0.0; d.ffn_down.out_dim];
self.base
.fused_matmul_into(&ffn_up, &d.ffn_down, &mut ffn_down)?;
for i in 0..self.base.config.hidden_dim {
hidden[i] += ffn_down[i];
}
Ok(())
}
pub(crate) fn forward_attention(
&self,
a: &Qwen35OwnedAttentionLayer,
hidden: &mut [f32],
cache: &mut Qwen35State,
il: usize,
position: usize,
normed: &mut [f32],
post_attn_normed: &mut [f32],
) -> Result<()> {
crate::gguf::ops::rms_norm_into(hidden, &a.attn_norm, self.base.config.eps, normed);
let mut q_full = vec![0.0; a.attn_q.out_dim];
let mut k = vec![0.0; a.attn_k.out_dim];
let mut v = vec![0.0; a.attn_v.out_dim];
self.base
.fused_matmul_into(normed, &a.attn_q, &mut q_full)?;
self.base.fused_matmul_into(normed, &a.attn_k, &mut k)?;
self.base.fused_matmul_into(normed, &a.attn_v, &mut v)?;
let attn_out_in = self.attention_mix_token(a, cache, il, position, &q_full, &mut k, &v);
let mut attn_out = vec![0.0; self.base.config.hidden_dim];
self.base
.fused_matmul_into(&attn_out_in, &a.attn_output, &mut attn_out)?;
for i in 0..self.base.config.hidden_dim {
hidden[i] += attn_out[i];
}
crate::gguf::ops::rms_norm_into(
hidden,
&a.post_attention_norm,
self.base.config.eps,
post_attn_normed,
);
let mut ffn_gate = vec![0.0; a.ffn_gate.out_dim];
self.base
.fused_matmul_into(post_attn_normed, &a.ffn_gate, &mut ffn_gate)?;
let mut ffn_up = vec![0.0; a.ffn_up.out_dim];
self.base
.fused_matmul_into(post_attn_normed, &a.ffn_up, &mut ffn_up)?;
for i in 0..ffn_gate.len() {
let x = ffn_gate[i];
let silu = x / (1.0 + (-x as f32).exp());
ffn_up[i] *= silu;
}
let mut ffn_down = vec![0.0; a.ffn_down.out_dim];
self.base
.fused_matmul_into(&ffn_up, &a.ffn_down, &mut ffn_down)?;
for i in 0..self.base.config.hidden_dim {
hidden[i] += ffn_down[i];
}
Ok(())
}
pub fn forward_prefill_qwen35(
&self,
tokens: &[u32],
cache: &mut Qwen35State,
start_pos: usize,
) -> Result<Vec<f32>> {
if tokens.is_empty() {
return Err(crate::error::RealizarError::InvalidShape {
reason: "forward_prefill_qwen35: no tokens".to_string(),
});
}
let (used, cap) = (cache.kv_cache.len(), cache.kv_cache.max_len());
if used + tokens.len() > cap {
return Err(crate::error::RealizarError::InvalidShape {
reason: format!(
"forward_prefill_qwen35: {} tokens at cache length {used} overflow max_seq_len {cap}",
tokens.len()
),
});
}
let mut logits = Vec::new();
for (ci, chunk) in tokens.chunks(QWEN35_PREFILL_CHUNK).enumerate() {
logits =
self.prefill_chunk_qwen35(chunk, cache, start_pos + ci * QWEN35_PREFILL_CHUNK)?;
}
Ok(logits)
}
fn prefill_chunk_qwen35(
&self,
chunk: &[u32],
cache: &mut Qwen35State,
pos0: usize,
) -> Result<Vec<f32>> {
let hd = self.base.config.hidden_dim;
let eps = self.base.config.eps;
let b = chunk.len();
let emb = self.base.token_embedding();
let mut hidden = vec![0.0; b * hd];
for (t, &tok) in chunk.iter().enumerate() {
let at = tok as usize * hd;
hidden[t * hd..(t + 1) * hd].copy_from_slice(&emb[at..at + hd]);
}
let mut normed = vec![0.0; b * hd];
for (il, layer) in self.layers.iter().enumerate() {
match layer {
Qwen35OwnedLayer::DeltaNet(d) => {
rms_norm_rows(&hidden, &d.attn_norm, eps, hd, &mut normed);
let conv_in = self.matmul_rows(&normed, b, &d.attn_qkv)?;
let dt_raw = self.matmul_rows(&normed, b, &d.ssm_alpha)?;
let mut beta = self.matmul_rows(&normed, b, &d.ssm_beta)?;
let gate = self.matmul_rows(&normed, b, &d.attn_gate)?;
let mix = self
.deltanet_mix_rows(d, cache, il, b, &conv_in, &dt_raw, &mut beta, &gate);
let out = self.matmul_rows(&mix, b, &d.ssm_out)?;
add_into(&mut hidden, &out);
self.ffn_rows(
&mut hidden,
b,
&d.post_attention_norm,
[&d.ffn_gate, &d.ffn_up, &d.ffn_down],
)?;
},
Qwen35OwnedLayer::Attention(a) => {
rms_norm_rows(&hidden, &a.attn_norm, eps, hd, &mut normed);
let q_full = self.matmul_rows(&normed, b, &a.attn_q)?;
let mut k = self.matmul_rows(&normed, b, &a.attn_k)?;
let v = self.matmul_rows(&normed, b, &a.attn_v)?;
let mix = self.attention_mix_rows(a, cache, il, pos0, b, &q_full, &mut k, &v);
let out = self.matmul_rows(&mix, b, &a.attn_output)?;
add_into(&mut hidden, &out);
self.ffn_rows(
&mut hidden,
b,
&a.post_attention_norm,
[&a.ffn_gate, &a.ffn_up, &a.ffn_down],
)?;
},
}
}
cache.kv_cache.advance_by(b);
let mut out_normed = vec![0.0; hd];
crate::gguf::ops::rms_norm_into(
&hidden[(b - 1) * hd..],
self.base.output_norm_weight(),
eps,
&mut out_normed,
);
let mut logits = vec![0.0; self.base.config.vocab_size];
self.base
.fused_matmul_into(&out_normed, self.base.lm_head_weight(), &mut logits)?;
Ok(logits)
}
#[allow(clippy::too_many_arguments)]
fn deltanet_mix_rows(
&self,
d: &Qwen35OwnedDeltaNetLayer,
cache: &mut Qwen35State,
il: usize,
b: usize,
conv_in: &[f32],
dt_raw: &[f32],
beta: &mut [f32],
gate: &[f32],
) -> Vec<f32> {
use rayon::prelude::*;
let (cd, nv) = (d.attn_qkv.out_dim, d.ssm_alpha.out_dim);
let (hk, hv, nk) = (self.head_k_dim, self.head_v_dim, self.num_k_heads);
let vd = hv * nv;
let preps: Vec<[Vec<f32>; 4]> = (0..b)
.map(|t| {
self.deltanet_prep_token(
d,
cache,
il,
&conv_in[t * cd..(t + 1) * cd],
&dt_raw[t * nv..(t + 1) * nv],
&mut beta[t * nv..(t + 1) * nv],
)
})
.collect();
let scale = 1.0 / (hk as f32).sqrt();
let beta: &[f32] = beta;
let mut by_head = vec![0.0f32; nv * b * hv];
cache.ssm_states[il][..]
.par_chunks_mut(hv * hk)
.zip(by_head.par_chunks_mut(b * hv))
.enumerate()
.for_each(|(h, (s_h, out))| {
let kh = h % nk;
for (t, [q, k, v, dt]) in preps.iter().enumerate() {
delta_rule_head(
&q[kh * hk..(kh + 1) * hk],
&k[kh * hk..(kh + 1) * hk],
&v[h * hv..(h + 1) * hv],
beta[t * nv + h],
dt[h],
s_h,
&mut out[t * hv..(t + 1) * hv],
hk,
scale,
);
}
});
let mut mix = vec![0.0; b * vd];
let mut out_h = vec![0.0; vd];
for t in 0..b {
for h in 0..nv {
let at = h * b * hv + t * hv;
out_h[h * hv..(h + 1) * hv].copy_from_slice(&by_head[at..at + hv]);
}
gated_rmsnorm(
&out_h,
&gate[t * vd..(t + 1) * vd],
&d.ssm_norm_weight,
self.base.config.eps,
hv,
&mut mix[t * vd..(t + 1) * vd],
);
}
mix
}
#[allow(clippy::too_many_arguments)]
fn attention_mix_rows(
&self,
a: &Qwen35OwnedAttentionLayer,
cache: &mut Qwen35State,
il: usize,
pos0: usize,
b: usize,
q_full: &[f32],
k: &mut [f32],
v: &[f32],
) -> Vec<f32> {
use rayon::prelude::*;
let (qd, kd) = (a.attn_q.out_dim, a.attn_k.out_dim);
let od = a.attn_output.in_dim;
let qg: Vec<(Vec<f32>, Vec<f32>)> = (0..b)
.map(|t| {
self.attention_append_token(
a,
cache,
il,
pos0 + t,
&q_full[t * qd..(t + 1) * qd],
&mut k[t * kd..(t + 1) * kd],
&v[t * kd..(t + 1) * kd],
)
})
.collect();
let cache: &Qwen35State = cache;
let mut mix = vec![0.0; b * od];
mix.par_chunks_mut(od)
.zip(qg.par_iter())
.enumerate()
.for_each(|(t, (out, (q, gate)))| {
out.copy_from_slice(&self.attention_attend_token(cache, il, pos0 + t, q, gate));
});
mix
}
fn matmul_rows(
&self,
input: &[f32],
b: usize,
weight: &OwnedQuantizedTensor,
) -> Result<Vec<f32>> {
let (in_dim, out_dim) = (weight.in_dim, weight.out_dim);
let mut out = vec![0.0; b * out_dim];
if weight.qtype == crate::gguf::GGUF_TYPE_Q4_K && b > 1 {
crate::quantize::fused_q4k_multirow_matmul_f32_into(
&weight.data,
&input[..b * in_dim],
b,
in_dim,
out_dim,
&mut out,
)?;
} else if weight.qtype == crate::gguf::GGUF_TYPE_Q5_K && b > 1 {
crate::quantize::fused_q5k_multirow_matmul_into(
&weight.data,
&input[..b * in_dim],
b,
in_dim,
out_dim,
&mut out,
)?;
} else if weight.qtype == crate::gguf::GGUF_TYPE_Q6_K && b > 1 {
crate::quantize::fused_q6k_multirow_matmul_into(
&weight.data,
&input[..b * in_dim],
b,
in_dim,
out_dim,
&mut out,
)?;
} else {
for t in 0..b {
self.base.fused_matmul_into(
&input[t * in_dim..(t + 1) * in_dim],
weight,
&mut out[t * out_dim..(t + 1) * out_dim],
)?;
}
}
Ok(out)
}
fn ffn_rows(
&self,
hidden: &mut [f32],
b: usize,
post_norm: &[f32],
[ffn_gate, ffn_up, ffn_down]: [&OwnedQuantizedTensor; 3],
) -> Result<()> {
let hd = self.base.config.hidden_dim;
let mut normed = vec![0.0; b * hd];
rms_norm_rows(hidden, post_norm, self.base.config.eps, hd, &mut normed);
let gate = self.matmul_rows(&normed, b, ffn_gate)?;
let mut up = self.matmul_rows(&normed, b, ffn_up)?;
for (u, &x) in up.iter_mut().zip(&gate) {
let silu = x / (1.0 + (-x as f32).exp());
*u *= silu;
}
let down = self.matmul_rows(&up, b, ffn_down)?;
add_into(hidden, &down);
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn deltanet_prep_token(
&self,
d: &Qwen35OwnedDeltaNetLayer,
cache: &mut Qwen35State,
il: usize,
conv_in: &[f32],
dt_raw: &[f32],
beta: &mut [f32],
) -> [Vec<f32>; 4] {
let fn_softplus = |x: f32| -> f32 {
if x > 20.0 {
x
} else {
(1.0 + x.exp()).ln()
}
};
let conv_dim = self.head_k_dim * self.num_k_heads * 2 + self.head_v_dim * self.num_v_heads;
let mut conv_out = vec![0.0; conv_dim];
causal_conv1d(
conv_in,
&mut cache.conv_states[il][..],
&d.ssm_conv1d_weight,
self.conv_kernel,
conv_dim,
&mut conv_out,
);
for x in conv_out.iter_mut() {
*x = *x / (1.0 + (-*x).exp()); }
let k_dim = self.head_k_dim * self.num_k_heads;
let v_dim = self.head_v_dim * self.num_v_heads;
let mut q = conv_out[0..k_dim].to_vec();
let mut k = conv_out[k_dim..k_dim * 2].to_vec();
let v = conv_out[k_dim * 2..conv_dim].to_vec();
l2_norm_per_head(&mut q, self.head_k_dim, self.base.config.eps);
l2_norm_per_head(&mut k, self.head_k_dim, self.base.config.eps);
let mut dt = vec![0.0; self.num_v_heads];
for (i, val) in dt_raw.iter().enumerate() {
dt[i] = fn_softplus(val + d.ssm_dt_bias[i]) * d.ssm_a[i];
}
for x in beta.iter_mut() {
*x = 1.0 / (1.0 + (-*x).exp());
}
[q, k, v, dt]
}
#[allow(clippy::too_many_arguments)]
fn deltanet_mix_token(
&self,
d: &Qwen35OwnedDeltaNetLayer,
cache: &mut Qwen35State,
il: usize,
conv_in: &[f32],
dt_raw: &[f32],
beta: &mut [f32],
gate: &[f32],
) -> Vec<f32> {
let [q, k, v, dt] = self.deltanet_prep_token(d, cache, il, conv_in, dt_raw, beta);
let v_dim = self.head_v_dim * self.num_v_heads;
let mut out_h = vec![0.0; v_dim];
delta_rule_recurrence_gqa(
&q,
&k,
&v,
beta,
&dt,
&mut cache.ssm_states[il][..],
&mut out_h,
self.num_k_heads,
self.head_k_dim,
self.num_v_heads,
self.head_v_dim,
);
let mut ssm_out_in = vec![0.0; v_dim];
gated_rmsnorm(
&out_h,
gate,
&d.ssm_norm_weight,
self.base.config.eps,
self.head_v_dim,
&mut ssm_out_in,
);
ssm_out_in
}
#[allow(clippy::too_many_arguments)]
fn attention_mix_token(
&self,
a: &Qwen35OwnedAttentionLayer,
cache: &mut Qwen35State,
il: usize,
position: usize,
q_full: &[f32],
k: &mut [f32],
v: &[f32],
) -> Vec<f32> {
let (q, gate) = self.attention_append_token(a, cache, il, position, q_full, k, v);
self.attention_attend_token(cache, il, position, &q, &gate)
}
#[allow(clippy::too_many_arguments)]
fn attention_append_token(
&self,
a: &Qwen35OwnedAttentionLayer,
cache: &mut Qwen35State,
il: usize,
position: usize,
q_full: &[f32],
k: &mut [f32],
v: &[f32],
) -> (Vec<f32>, Vec<f32>) {
let num_heads = self.base.config.num_heads;
let head_dim = a.attn_q_norm.len(); let mut q = vec![0.0; num_heads * head_dim];
let mut gate = vec![0.0; num_heads * head_dim];
for h in 0..num_heads {
let offset_q_full = h * head_dim * 2;
let offset_q = h * head_dim;
q[offset_q..offset_q + head_dim]
.copy_from_slice(&q_full[offset_q_full..offset_q_full + head_dim]);
gate[offset_q..offset_q + head_dim]
.copy_from_slice(&q_full[offset_q_full + head_dim..offset_q_full + head_dim * 2]);
}
crate::gguf::ops::apply_per_head_rms_norm(
&mut q,
&a.attn_q_norm,
num_heads,
self.base.config.eps,
);
crate::gguf::ops::apply_per_head_rms_norm(
k,
&a.attn_k_norm,
self.base.config.num_kv_heads,
self.base.config.eps,
);
let n_rot = 2 * self.rope_sections.iter().sum::<usize>();
let freq_base = self.base.config.rope_theta;
apply_partial_neox_rope(&mut q, num_heads, head_dim, n_rot, position, freq_base);
apply_partial_neox_rope(
k,
self.base.config.num_kv_heads,
head_dim,
n_rot,
position,
freq_base,
);
cache.kv_cache.append(il, k, v);
(q, gate)
}
fn attention_attend_token(
&self,
cache: &Qwen35State,
il: usize,
position: usize,
q: &[f32],
gate: &[f32],
) -> Vec<f32> {
let k_cache = cache.kv_cache.get_k(il);
let v_cache = cache.kv_cache.get_v(il);
let mut attn_out_in = vec![0.0; q.len()];
let num_kv_heads = self.base.config.num_kv_heads;
let num_heads = self.base.config.num_heads;
let group_size = num_heads / num_kv_heads;
let head_dim = self.head_dim;
for h in 0..num_heads {
let kv_h = h / group_size;
let q_h = &q[h * head_dim..(h + 1) * head_dim];
let mut scores = vec![0.0; position + 1];
for p in 0..=position {
let mut dot = 0.0;
let k_p = &k_cache[p * (num_kv_heads * head_dim) + kv_h * head_dim
..p * (num_kv_heads * head_dim) + (kv_h + 1) * head_dim];
for i in 0..head_dim {
dot += q_h[i] * k_p[i];
}
scores[p] = dot / (head_dim as f32).sqrt();
}
crate::gguf::ops::softmax(&mut scores);
let out_h = &mut attn_out_in[h * head_dim..(h + 1) * head_dim];
for p in 0..=position {
let w = scores[p];
let v_p = &v_cache[p * (num_kv_heads * head_dim) + kv_h * head_dim
..p * (num_kv_heads * head_dim) + (kv_h + 1) * head_dim];
for i in 0..head_dim {
out_h[i] += w * v_p[i];
}
}
}
apply_sigmoid_gate(&mut attn_out_in, gate);
attn_out_in
}
}
pub const QWEN35_PREFILL_CHUNK: usize = 64;
fn rms_norm_rows(x: &[f32], weight: &[f32], eps: f32, width: usize, out: &mut [f32]) {
for (row, o) in x.chunks_exact(width).zip(out.chunks_exact_mut(width)) {
crate::gguf::ops::rms_norm_into(row, weight, eps, o);
}
}
fn add_into(acc: &mut [f32], x: &[f32]) {
for (a, &v) in acc.iter_mut().zip(x) {
*a += v;
}
}
#[cfg(test)]
pub(crate) fn qwen35_reference_generate(
mapped: &crate::gguf::MappedGGUFModel,
base: &OwnedQuantizedModel,
input_tokens: &[u32],
gen_config: &crate::gguf::QuantizedGenerateConfig,
) -> Result<Vec<u32>> {
use rand::SeedableRng;
if input_tokens.is_empty() {
return Err(crate::error::RealizarError::InvalidShape {
reason: "qwen35_reference_generate: prompt cannot be empty".to_string(),
});
}
qwen35_check_context(input_tokens.len(), base.config.context_length)?;
let qwen = Qwen35Model::from_model_and_layers(base, &mapped.model, mapped.data())?;
let max_seq_len = input_tokens.len() + gen_config.max_tokens + 1;
let mut state = qwen.new_state(max_seq_len);
let mut rng = rand::rngs::StdRng::seed_from_u64(gen_config.seed);
let mut logits = Vec::new();
for (pos, &token) in input_tokens.iter().enumerate() {
logits = qwen.forward_single_qwen35(token, &mut state, pos)?;
}
let mut tokens = input_tokens.to_vec();
for _ in 0..gen_config.max_tokens {
let next = if gen_config.temperature == 0.0 || gen_config.top_k == 1 {
crate::gguf::ops::argmax(&logits)
} else {
OwnedQuantizedModel::sample_topk_seeded(
&logits,
gen_config.temperature,
gen_config.top_k,
gen_config.top_p,
&mut rng,
)
};
tokens.push(next);
if gen_config.stop_tokens.contains(&next) || tokens.len() >= max_seq_len {
break;
}
logits = qwen.forward_single_qwen35(next, &mut state, tokens.len() - 1)?;
}
Ok(tokens)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Qwen35CpuReason {
Requested,
NoCudaBackend,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Qwen35Route {
Gpu,
Cpu(Qwen35CpuReason),
}
#[must_use]
pub fn qwen35_route(no_gpu: bool, cuda_backend: bool) -> Qwen35Route {
if no_gpu {
Qwen35Route::Cpu(Qwen35CpuReason::Requested)
} else if cuda_backend {
Qwen35Route::Gpu
} else {
Qwen35Route::Cpu(Qwen35CpuReason::NoCudaBackend)
}
}
pub fn qwen35_check_context(prompt_len: usize, context_length: usize) -> Result<()> {
if prompt_len > context_length {
return Err(crate::error::RealizarError::ContextLimitExceeded {
provided: prompt_len,
maximum: context_length,
});
}
Ok(())
}
#[must_use]
pub fn qwen35_route_notice(route: Qwen35Route) -> Option<&'static str> {
match route {
Qwen35Route::Cpu(Qwen35CpuReason::NoCudaBackend) => Some(
"[qwen35: this binary has no CUDA backend (built without --features cuda); \
the Gated DeltaNet forward runs on the CPU (#3091)]",
),
Qwen35Route::Gpu | Qwen35Route::Cpu(Qwen35CpuReason::Requested) => None,
}
}
pub const QWEN35_GPU_FALLBACK_PREFIX: &str = "warning: GPU (CUDA) qwen35 path rejected";
#[cfg(feature = "cuda")]
pub(crate) const QWEN35_F2_PROBE_MAX: usize = 64;
#[cfg(feature = "cuda")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum F2Verdict {
Accepted {
positions_judged: usize,
},
Rejected,
NotJudged,
}
#[cfg(feature = "cuda")]
impl F2Verdict {
const fn lets_the_gpu_serve(self) -> bool {
!matches!(self, Self::Rejected)
}
}
#[cfg(feature = "cuda")]
fn f2_validate_qwen35(
gpu: &mut crate::gguf::cuda::Qwen35CudaModel<'_>,
cpu: &Qwen35Model<'_>,
probe_context: &[u32],
) -> F2Verdict {
if std::env::var("SKIP_PARITY_GATE").is_ok_and(|v| v == "1") {
return F2Verdict::NotJudged;
}
let probe = &probe_context[probe_context.len().saturating_sub(QWEN35_F2_PROBE_MAX)..];
if probe.len() < 2 {
return F2Verdict::NotJudged;
}
let Some(cpu_per_pos) = f2_qwen35_cpu_reference(cpu, probe) else {
return F2Verdict::NotJudged; };
let decode_token = cpu_per_pos
.get(probe.len().saturating_sub(1))
.map_or(0, |l| crate::infer::argmax_u32(l));
let gpu_per_pos = match f2_qwen35_gpu_logits(gpu, probe, decode_token) {
Ok(v) => v,
Err(msg) => {
eprintln!("{msg}");
return F2Verdict::Rejected; },
};
let report = crate::infer::f2_multi_position_report(&cpu_per_pos, &gpu_per_pos);
if report.accepted {
F2Verdict::Accepted {
positions_judged: cpu_per_pos.len(),
}
} else {
eprintln!(
"{}",
crate::infer::f2_divergence_msg(&report, crate::infer::F2ProbePath::Batched)
);
F2Verdict::Rejected
}
}
#[cfg(feature = "cuda")]
#[derive(Debug, Clone, PartialEq)]
pub struct F2Outcome {
pub accepted: bool,
pub source: &'static str,
pub validate_ms: f64,
pub sha256_ms: f64,
pub receipt_path: Option<std::path::PathBuf>,
}
#[cfg(feature = "cuda")]
fn f2_validate_qwen35_receipted(
gpu: &mut crate::gguf::cuda::Qwen35CudaModel<'_>,
cpu: &Qwen35Model<'_>,
probe_context: &[u32],
model_bytes: &[u8],
device_name: &str,
) -> F2Outcome {
let hash = Qwen35ModelHash::of(model_bytes);
f2_validate_qwen35_receipted_hashed(gpu, cpu, probe_context, &hash, device_name)
}
#[cfg(feature = "cuda")]
#[derive(Debug, Clone)]
pub(crate) struct Qwen35ModelHash {
pub(crate) sha256: String,
pub(crate) sha256_ms: f64,
}
#[cfg(feature = "cuda")]
impl Qwen35ModelHash {
pub(crate) fn of(model_bytes: &[u8]) -> Self {
let start = std::time::Instant::now();
let sha256 = crate::gguf::f2_receipt::model_sha256(model_bytes);
Self {
sha256,
sha256_ms: start.elapsed().as_secs_f64() * 1000.0,
}
}
}
#[cfg(feature = "cuda")]
pub(crate) fn f2_validate_qwen35_receipted_hashed(
gpu: &mut crate::gguf::cuda::Qwen35CudaModel<'_>,
cpu: &Qwen35Model<'_>,
probe_context: &[u32],
hash: &Qwen35ModelHash,
device_name: &str,
) -> F2Outcome {
use crate::gguf::f2_receipt::{
apr_version, decide, read_receipt, receipt_dir, receipt_path, revalidate_requested,
unix_now, write_receipt, F2Decision, F2Receipt, F2ReceiptKey, F2_RECEIPT_SCHEMA,
};
let key = F2ReceiptKey {
model_sha256: hash.sha256.clone(),
apr_version: apr_version(),
device: device_name.to_string(),
};
let sha256_ms = hash.sha256_ms;
let path = receipt_dir().map(|d| receipt_path(&d, &key.model_sha256));
let found = match path.as_deref() {
Some(p) => read_receipt(p),
None => Ok(None),
};
match decide(found, &key, revalidate_requested()) {
F2Decision::Skip { receipt } => {
let age_s = unix_now().saturating_sub(receipt.validated_at);
eprintln!(
"F2 guard: receipt matches (model sha256 {}…, apr {}, {}) — validated {}s ago on {} positions; CPU reference forward skipped [source=receipt, sha256 {:.0} ms]. `apr run --revalidate` forces a fresh run.",
&key.model_sha256[..12],
key.apr_version,
key.device,
age_s,
receipt.positions_judged,
sha256_ms
);
return F2Outcome {
accepted: true,
source: "receipt",
validate_ms: 0.0,
sha256_ms,
receipt_path: path,
};
},
F2Decision::Validate(reason) => {
eprintln!("F2 guard: validating on this run ({reason}) [source=fresh]");
},
}
let start = std::time::Instant::now();
let verdict = f2_validate_qwen35(gpu, cpu, probe_context);
let validate_ms = start.elapsed().as_secs_f64() * 1000.0;
let source = match verdict {
F2Verdict::Accepted { positions_judged } => {
match path.as_deref() {
Some(p) => {
let receipt = F2Receipt {
schema: F2_RECEIPT_SCHEMA,
key,
validated_at: unix_now(),
positions_judged,
};
match write_receipt(p, &receipt) {
Ok(()) => eprintln!(
"F2 guard: passed in {validate_ms:.0} ms on {positions_judged} positions; receipt written to {} — the next run of this (model, apr, device) skips it.",
p.display()
),
Err(e) => eprintln!(
"F2 guard: passed in {validate_ms:.0} ms, but the receipt could not be written ({e}); the next run validates again. Set APR_F2_RECEIPT_DIR to a writable directory."
),
}
}
None => eprintln!(
"F2 guard: passed in {validate_ms:.0} ms; no cache directory (no HOME, XDG_CACHE_HOME or APR_F2_RECEIPT_DIR), so no receipt — every run validates."
),
}
"fresh"
},
F2Verdict::NotJudged => {
eprintln!(
"F2 guard: nothing was compared on this run (probe too short, SKIP_PARITY_GATE, or the CPU reference did not run); the GPU serves, and NO receipt is written."
);
"not-judged"
},
F2Verdict::Rejected => "fresh",
};
F2Outcome {
accepted: verdict.lets_the_gpu_serve(),
source,
validate_ms,
sha256_ms,
receipt_path: path,
}
}
#[cfg(feature = "cuda")]
fn f2_qwen35_cpu_reference(cpu: &Qwen35Model<'_>, probe: &[u32]) -> Option<Vec<Vec<f32>>> {
let mut state = cpu.new_state(probe.len() + 2);
let mut per_pos: Vec<Vec<f32>> = Vec::with_capacity(probe.len() + 1);
for (pos, &tok) in probe.iter().enumerate() {
per_pos.push(cpu.forward_single_qwen35(tok, &mut state, pos).ok()?);
}
let last = per_pos.last()?;
let next = crate::infer::argmax_u32(last);
per_pos.push(
cpu.forward_single_qwen35(next, &mut state, probe.len())
.ok()?,
);
Some(per_pos)
}
#[cfg(feature = "cuda")]
fn f2_qwen35_gpu_logits(
gpu: &mut crate::gguf::cuda::Qwen35CudaModel<'_>,
probe: &[u32],
decode_token: u32,
) -> std::result::Result<Vec<Vec<f32>>, String> {
let steps = probe.len() + 1;
let mut state = gpu
.new_state_with_len(steps)
.map_err(|e| format!("F2 qwen35 probe: the device state would not allocate: {e}"))?;
let every_position: Vec<usize> = (0..probe.len()).collect();
let mut per_pos = gpu
.prefill_logits_at(probe, &mut state, 0, &every_position)
.map_err(|e| crate::infer::gpu_forward_failure_msg(0, steps, &e))?;
match gpu.forward_single(decode_token, &mut state, probe.len()) {
Ok(logits) => per_pos.push(logits),
Err(e) => {
return Err(crate::infer::gpu_forward_failure_msg(
probe.len(),
steps,
&e,
))
},
}
Ok(per_pos)
}
#[cfg(test)]
mod qwen35_route_tests {
use super::{
qwen35_check_context, qwen35_route, qwen35_route_notice, Qwen35CpuReason, Qwen35Route,
};
#[test]
fn a_prompt_past_the_declared_context_is_refused_with_both_numbers() {
const CTX: usize = 262_144;
for (len, ok) in [
(1, true),
(CTX - 1, true),
(CTX, true),
(CTX + 1, false),
(263_089, false),
] {
let got = qwen35_check_context(len, CTX);
assert_eq!(got.is_ok(), ok, "prompt of {len} against {CTX}: {got:?}");
if let Err(e) = got {
let msg = e.to_string();
assert!(
msg.contains(&len.to_string()) && msg.contains(&CTX.to_string()),
"{msg}"
);
}
}
}
#[test]
fn a_cuda_build_that_was_not_told_otherwise_routes_to_the_gpu() {
assert_eq!(qwen35_route(false, true), Qwen35Route::Gpu);
assert_eq!(qwen35_route_notice(Qwen35Route::Gpu), None);
}
#[test]
fn no_gpu_wins_over_a_present_cuda_backend() {
assert_eq!(
qwen35_route(true, true),
Qwen35Route::Cpu(Qwen35CpuReason::Requested)
);
assert_eq!(
qwen35_route_notice(Qwen35Route::Cpu(Qwen35CpuReason::Requested)),
None
);
}
#[test]
fn a_build_without_cuda_says_so() {
let route = qwen35_route(false, false);
assert_eq!(route, Qwen35Route::Cpu(Qwen35CpuReason::NoCudaBackend));
let notice = qwen35_route_notice(route).expect("the no-backend case owes a notice");
assert!(
notice.contains("no CUDA backend"),
"the notice must name the missing backend, not the architecture: {notice}"
);
assert!(
!notice.contains("#3090"),
"#3090 is the GPU forward, which now exists — citing it here is the withdrawn 'the GPU does not implement it' notice: {notice}"
);
}
#[test]
fn every_cpu_reason_is_produced_by_the_router() {
for (no_gpu, cuda, want) in [
(true, true, Qwen35CpuReason::Requested),
(true, false, Qwen35CpuReason::Requested),
(false, false, Qwen35CpuReason::NoCudaBackend),
] {
assert_eq!(qwen35_route(no_gpu, cuda), Qwen35Route::Cpu(want));
}
}
}
#[cfg(test)]
mod qwen35_ssm_meta_tests {
use super::{qwen35_ssm_meta, Qwen35SsmMeta};
use crate::gguf::types::GGUFValue;
use std::collections::HashMap;
fn md(pairs: &[(&str, GGUFValue)]) -> HashMap<String, GGUFValue> {
pairs
.iter()
.map(|(k, v)| ((*k).to_string(), v.clone()))
.collect()
}
#[test]
fn test_empty_metadata_gives_the_qwen35_0_8b_defaults() {
assert_eq!(
qwen35_ssm_meta(&HashMap::new()),
Qwen35SsmMeta {
head_k_dim: 16,
num_k_heads: 1,
num_v_heads: 16,
conv_kernel: 4,
rope_sections: [16, 24, 24, 0],
}
);
}
#[test]
fn test_qwen2_keys_win_and_qwen35_keys_are_the_fallback() {
let meta = qwen35_ssm_meta(&md(&[
("qwen2.ssm.state_size", GGUFValue::UInt32(128)),
("qwen35.ssm.state_size", GGUFValue::UInt32(999)),
("qwen35.ssm.group_count", GGUFValue::UInt32(2)),
("qwen35.ssm.time_step_rank", GGUFValue::Int32(32)),
("qwen35.ssm.conv_kernel", GGUFValue::UInt32(4)),
]));
assert_eq!(meta.head_k_dim, 128, "the qwen2 key is preferred");
assert_eq!(meta.num_k_heads, 2, "no qwen2 key: the qwen35 one is read");
assert_eq!(meta.num_v_heads, 32, "Int32 is accepted like UInt32");
assert_eq!(meta.conv_kernel, 4);
}
#[test]
fn test_a_non_integer_value_falls_through_to_the_default() {
let meta = qwen35_ssm_meta(&md(&[(
"qwen2.ssm.conv_kernel",
GGUFValue::String("four".to_string()),
)]));
assert_eq!(meta.conv_kernel, 4);
}
#[test]
fn test_rope_sections_are_defaulted_per_section() {
let meta = qwen35_ssm_meta(&md(&[(
"qwen2.rope.dimension_sections",
GGUFValue::Array(vec![
GGUFValue::UInt32(8),
GGUFValue::Float32(1.0),
GGUFValue::Int32(12),
]),
)]));
assert_eq!(meta.rope_sections, [8, 24, 12, 0]);
}
}
#[cfg(test)]
mod qwen35_math_tests {
use super::*;
#[test]
fn test_l2_norm_per_head_normalises_each_head_on_its_own() {
let mut x = [3.0, 4.0, 30.0, 40.0];
l2_norm_per_head(&mut x, 2, 1e-12);
for (got, want) in x.iter().zip([0.6, 0.8, 0.6, 0.8]) {
assert!((got - want).abs() < 1e-6, "{x:?}");
}
}
#[test]
fn test_apply_sigmoid_gate_scales_by_sigmoid() {
let mut x = [2.0, 2.0, 2.0];
apply_sigmoid_gate(&mut x, &[0.0, 40.0, -40.0]);
assert!((x[0] - 1.0).abs() < 1e-6, "sigmoid(0) = 0.5: {x:?}");
assert!((x[1] - 2.0).abs() < 1e-6, "sigmoid(40) ~ 1: {x:?}");
assert!(x[2].abs() < 1e-6, "sigmoid(-40) ~ 0: {x:?}");
}
#[test]
fn test_partial_neox_rope_is_identity_at_position_zero() {
let orig: Vec<f32> = (0..8u8).map(|i| f32::from(i) + 1.0).collect();
let mut x = orig.clone();
apply_partial_neox_rope(&mut x, 1, 8, 4, 0, 10_000.0);
assert_eq!(x, orig);
}
#[test]
fn test_partial_neox_rope_rotates_half_pairs_and_leaves_the_tail() {
let base = 10_000.0f32;
let mut x = [0.0, 1.0, 0.0, 0.0, 5.0, 6.0, 7.0, 8.0];
apply_partial_neox_rope(&mut x, 1, 8, 4, 3, base);
let theta1 = 3.0 * base.powf(-2.0 / 4.0); assert!(
(x[1] - theta1.cos()).abs() < 1e-6 && (x[3] - theta1.sin()).abs() < 1e-6,
"{x:?}"
);
assert_eq!(
(x[0], x[2]),
(0.0, 0.0),
"the (0,2) pair was all zeros: {x:?}"
);
assert_eq!(&x[4..], &[5.0, 6.0, 7.0, 8.0]);
}
}
#[cfg(test)]
#[path = "forward_qwen35_contract_tests.rs"]
mod qhf_contract_tests;
#[cfg(test)]
#[path = "forward_qwen35_gqa_tests.rs"]
mod qwen35_gqa_tests;
#[cfg(test)]
#[path = "forward_qwen35_prefill_tests.rs"]
mod qwen35_prefill_tests;