use ferrox_core::attention::{causal_gqa_attention_softcap, causal_gqa_attention_windowed_softcap};
use ferrox_core::cache::{KvCache, PagedKvCache, SharedPagedKv};
use ferrox_core::matmul::rms_norm;
use super::{Decoder, GptOssLayer, LayerWeights};
pub(crate) enum KvStep<'a> {
Decode(&'a mut KvCache),
Batched(&'a mut KvCache),
Paged {
cache: &'a mut PagedKvCache,
stores: &'a SharedPagedKv,
},
}
impl Decoder {
pub(crate) fn attn_block(
&self,
layer_idx: usize,
layer: &LayerWeights,
normed: &[f32],
pos: usize,
kv: KvStep<'_>,
) -> Vec<f32> {
let head_dim = self.config.head_dim;
let n_heads = self.config.n_heads;
let n_kv_heads = self.config.n_kv_heads;
let (mut q, mut k, mut v) = {
#[cfg(any(feature = "cuda", feature = "metal"))]
{
if let Some(mut outs) = ferrox_core::WeightMatrix::apply_gpu_multi(
&[&layer.attn.q_proj, &layer.attn.k_proj, &layer.attn.v_proj],
normed,
) {
let v = outs.pop().unwrap();
let k = outs.pop().unwrap();
let q = outs.pop().unwrap();
(q, k, v)
} else {
ferrox_core::weight_matrix::WeightMatrix::apply_three(
&layer.attn.q_proj,
&layer.attn.k_proj,
&layer.attn.v_proj,
normed,
)
}
}
#[cfg(not(any(feature = "cuda", feature = "metal")))]
{
ferrox_core::weight_matrix::WeightMatrix::apply_three(
&layer.attn.q_proj,
&layer.attn.k_proj,
&layer.attn.v_proj,
normed,
)
}
};
if let Some(bias) = &layer.attn.q_bias {
for (x, b) in q.iter_mut().zip(bias.iter()) {
*x += b;
}
}
if let Some(bias) = &layer.attn.k_bias {
for (x, b) in k.iter_mut().zip(bias.iter()) {
*x += b;
}
}
if let Some(bias) = &layer.attn.v_bias {
for (x, b) in v.iter_mut().zip(bias.iter()) {
*x += b;
}
}
if let Some(q_norm) = &layer.attn.q_norm {
q = self.apply_qk_norm(&q, q_norm);
}
if let Some(k_norm) = &layer.attn.k_norm {
k = self.apply_qk_norm(&k, k_norm);
}
self.apply_rope_attn_factor(&mut q, &mut k);
for h in 0..n_heads {
self.apply_rope_head_layer(&mut q[h * head_dim..(h + 1) * head_dim], pos, layer_idx);
}
for h in 0..n_kv_heads {
self.apply_rope_head_layer(&mut k[h * head_dim..(h + 1) * head_dim], pos, layer_idx);
}
self.apply_attention_scale(&mut q);
let oai = self.gpt_oss.as_ref().map(|g| &g.layers[layer_idx]);
let attn_out = self.push_and_attend_row(kv, layer_idx, &k, &v, &q, oai);
let mut projected = layer.attn.o_proj.apply(&attn_out);
if let Some(oai) = oai {
for (x, b) in projected.iter_mut().zip(oai.o_bias.iter()) {
*x += b;
}
}
if let Some(post) = &layer.attn.post_attn_norm {
projected = rms_norm(&projected, post, self.config.rms_norm_eps);
}
projected
}
pub(crate) fn push_and_attend_row(
&self,
kv: KvStep<'_>,
layer_idx: usize,
k: &[f32],
v: &[f32],
q: &[f32],
oai: Option<&GptOssLayer>,
) -> Vec<f32> {
let n_heads = self.config.n_heads;
let n_kv_heads = self.config.n_kv_heads;
let head_dim = self.config.head_dim;
let window = self.config.layer_sliding_window(layer_idx);
let cuda_resident_layer = match &kv {
KvStep::Decode(_) => Some(layer_idx),
KvStep::Batched(_) | KvStep::Paged { .. } => None,
};
match kv {
KvStep::Decode(cache) | KvStep::Batched(cache) => {
cache
.push(k, v)
.expect("unbounded/planned KvCache growth is infallible");
if let Some(oai) = oai {
return ferrox_core::causal_gqa_attention_sinks(
q,
&cache.k,
&cache.v,
n_heads,
n_kv_heads,
head_dim,
cache.seq_len,
window,
&oai.attn_sinks,
);
}
match (window, cuda_resident_layer) {
(Some(window), _) => causal_gqa_attention_windowed_softcap(
q,
&cache.k,
&cache.v,
n_heads,
n_kv_heads,
head_dim,
cache.seq_len,
window,
self.config.attn_logit_softcap,
),
(None, Some(l)) => self.gqa_attention(
l,
q,
&cache.k,
&cache.v,
n_heads,
n_kv_heads,
head_dim,
cache.seq_len,
),
(None, None) => causal_gqa_attention_softcap(
q,
&cache.k,
&cache.v,
n_heads,
n_kv_heads,
head_dim,
cache.seq_len,
self.config.attn_logit_softcap,
),
}
}
KvStep::Paged { cache, stores } => {
{
let mut store = stores.write(layer_idx);
cache
.push(&mut store, k, v)
.expect("every caller reserves this row's pages before the stack runs");
}
let store = stores.read(layer_idx);
ferrox_core::causal_gqa_attention_paged_sinks(
q,
&store,
cache.block_table(),
n_heads,
n_kv_heads,
head_dim,
cache.seq_len(),
window,
oai.map(|o| o.attn_sinks.as_slice()),
if oai.is_some() {
None
} else {
self.config.attn_logit_softcap
},
)
}
}
}
}