use super::{Decoder, LayerWeights};
use frink_core::KvCache;
impl Decoder {
#[cfg(feature = "metal")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn device_attention_layer(
&self,
l: usize,
layer: &LayerWeights,
cache: &mut KvCache,
q: &[f32],
k: &[f32],
v: &[f32],
gate: Option<&[f32]>,
residual: &[f32],
) -> Option<Vec<f32>> {
let (out_proj, fold_branch, _ffn, launches) = self.attn_tail_launch(l, layer, q)?;
let shape = self.config.layer_shape(l);
let crate::layer_shapes::AttnShape::Gqa {
n_heads,
n_kv_heads,
} = shape.attention
else {
return None;
};
if !self.device_attention_shape_ok(l, layer) {
return None;
}
let head_dim = self.config.head_dim;
let rows = cache.rows();
let width = rows * n_kv_heads * head_dim;
Self::ensure_attn_mirror(cache, n_kv_heads, head_dim, rows, width)?;
let mirror = cache.metal_attn.as_mut()?;
let out = frink_metal::gdn_branch::launch_attn_layer(
mirror,
q,
k,
v,
gate,
n_heads,
self.config.attn_logit_softcap,
&out_proj,
fold_branch.as_ref(),
&launches.as_metal(),
residual,
)
.ok()?;
layer.moe.record_activations_dense();
let _ = n_kv_heads;
Some(out)
}
#[cfg(feature = "metal")]
#[allow(clippy::too_many_arguments)]
pub(crate) fn device_attention_layer_then_run(
&self,
l: usize,
layer: &LayerWeights,
q: &[f32],
k: &[f32],
v: &[f32],
gate: Option<&[f32]>,
hidden: &mut Vec<f32>,
kv_caches: &mut [frink_core::KvCache],
pending: &mut crate::decoder::fused_recurrent::PendingQkv,
) -> Option<usize> {
let mut end = l + 1;
while end < kv_caches.len() && self.fused_layer_parts(end).is_some() {
end += 1;
}
if end == l + 1 {
return None;
}
let (out_proj, fold_branch, _ffn, launches) = self.attn_tail_launch(l, layer, q)?;
let shape = self.config.layer_shape(l);
let crate::layer_shapes::AttnShape::Gqa {
n_heads,
n_kv_heads,
} = shape.attention
else {
return None;
};
if !self.device_attention_shape_ok(l, layer) {
return None;
}
let head_dim = self.config.head_dim;
let mut run = frink_metal::gdn_branch::GdnRun::start(hidden).ok()?;
{
let cache = &mut kv_caches[l];
let rows = cache.rows();
Self::ensure_attn_mirror(
cache,
n_kv_heads,
head_dim,
rows,
rows * n_kv_heads * head_dim,
)?;
let mirror = cache.metal_attn.as_mut()?;
unsafe {
run.attn_layer(
mirror,
q,
k,
v,
gate,
n_heads,
self.config.attn_logit_softcap,
&out_proj,
fold_branch.as_ref(),
&launches.as_metal(),
)
}
.ok()?;
cache
.push(k, v)
.expect("unbounded/planned KvCache growth is infallible");
}
layer.moe.record_activations_dense();
self.run_layers(&mut run, l + 1, end, kv_caches)?;
let head = self.encode_next_attn_head(&mut run, end, kv_caches.len());
let (out, qkv) = run.finish_with_head().ok()?;
*hidden = out;
*pending = head.then_some(qkv).flatten().map(|q| (end, q));
Some(end)
}
#[cfg(feature = "metal")]
pub(crate) fn device_attention_shape_ok(&self, l: usize, layer: &LayerWeights) -> bool {
self.config.layer_sliding_window(l).is_none()
&& layer.attn.sinks.is_none()
&& self.alibi_slopes.is_none()
&& self.config.v_head_dim() == self.config.head_dim
}
#[cfg(feature = "metal")]
fn ensure_attn_mirror(
cache: &mut KvCache,
n_kv_heads: usize,
head_dim: usize,
rows: usize,
width: usize,
) -> Option<()> {
let want = rows + 1;
let too_small = cache
.metal_attn
.as_ref()
.is_some_and(|m| m.capacity() < want);
if cache.metal_attn.is_none() || too_small {
let capacity = match cache.capacity_positions() {
Some(planned) if planned >= want => planned,
_ => want.next_power_of_two().max(512),
};
cache.metal_attn =
frink_metal::attn::MetalKvBuffers::with_capacity(n_kv_heads, head_dim, capacity)
.ok();
}
let stale = cache.metal_attn.as_ref()?.seq_len != rows;
if stale {
let (k, v) = (&cache.k[..width], &cache.v[..width]);
let mirror = cache.metal_attn.as_mut()?;
let k: &[f32] = unsafe { std::slice::from_raw_parts(k.as_ptr(), k.len()) };
let v: &[f32] = unsafe { std::slice::from_raw_parts(v.as_ptr(), v.len()) };
mirror.upload_from_host(k, v, rows).ok()?;
mirror.seq_len = rows;
}
Some(())
}
}