use super::config::LlamaConfig;
use super::weights::{LayerWeights, QuantWeight, Weights};
use crate::backend::{Backend, BackendError};
use crate::format::{FormatError, LoadedModel};
use crate::core::op::Op;
use crate::core::tensor::Tensor;
use std::path::Path;
use std::sync::Arc;
fn qw_matmul(x: &Tensor, w: &QuantWeight, backend: &dyn Backend) -> Result<Tensor, BackendError> {
backend.quant_matmul(x, &w.tensor)
}
pub struct LlamaModel {
pub config: LlamaConfig,
pub weights: Weights,
pub past_seq_len: usize,
pub kv_cache: Vec<(Vec<f32>, Vec<f32>)>,
pub prof: ForwardProf,
}
#[derive(Default, Clone, Debug)]
pub struct ForwardProf {
pub enabled: bool,
pub embed_ms: f64,
pub input_norm_ms: f64,
pub qkv_proj_ms: f64,
pub qk_norm_ms: f64,
pub rope_ms: f64,
pub kv_append_ms: f64,
pub attention_ms: f64,
pub o_proj_ms: f64,
pub post_norm_ms: f64,
pub ffn_ms: f64,
pub residual_ms: f64,
pub final_norm_ms: f64,
pub lm_head_ms: f64,
pub forwards: usize,
}
impl ForwardProf {
pub fn total_ms(&self) -> f64 {
self.embed_ms
+ self.input_norm_ms
+ self.qkv_proj_ms
+ self.qk_norm_ms
+ self.rope_ms
+ self.kv_append_ms
+ self.attention_ms
+ self.o_proj_ms
+ self.post_norm_ms
+ self.ffn_ms
+ self.residual_ms
+ self.final_norm_ms
+ self.lm_head_ms
}
pub fn summary(&self) -> String {
let total = self.total_ms().max(0.001);
let pct = |ms: f64| (ms / total) * 100.0;
format!(
" embed {:>7.1} ms ({:>5.1}%)\n\
\x20 input_norm {:>7.1} ms ({:>5.1}%)\n\
\x20 qkv_proj {:>7.1} ms ({:>5.1}%)\n\
\x20 qk_norm {:>7.1} ms ({:>5.1}%)\n\
\x20 rope {:>7.1} ms ({:>5.1}%)\n\
\x20 kv_append {:>7.1} ms ({:>5.1}%)\n\
\x20 attention {:>7.1} ms ({:>5.1}%)\n\
\x20 o_proj {:>7.1} ms ({:>5.1}%)\n\
\x20 post_norm {:>7.1} ms ({:>5.1}%)\n\
\x20 ffn {:>7.1} ms ({:>5.1}%)\n\
\x20 residual {:>7.1} ms ({:>5.1}%)\n\
\x20 final_norm {:>7.1} ms ({:>5.1}%)\n\
\x20 lm_head {:>7.1} ms ({:>5.1}%)\n\
\x20 โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n\
\x20 TOTAL {:>7.1} ms ({} forwards)",
self.embed_ms, pct(self.embed_ms),
self.input_norm_ms, pct(self.input_norm_ms),
self.qkv_proj_ms, pct(self.qkv_proj_ms),
self.qk_norm_ms, pct(self.qk_norm_ms),
self.rope_ms, pct(self.rope_ms),
self.kv_append_ms, pct(self.kv_append_ms),
self.attention_ms, pct(self.attention_ms),
self.o_proj_ms, pct(self.o_proj_ms),
self.post_norm_ms, pct(self.post_norm_ms),
self.ffn_ms, pct(self.ffn_ms),
self.residual_ms, pct(self.residual_ms),
self.final_norm_ms, pct(self.final_norm_ms),
self.lm_head_ms, pct(self.lm_head_ms),
total,
self.forwards,
)
}
}
impl LlamaModel {
pub fn load(path: &Path) -> Result<Self, FormatError> {
let lm = LoadedModel::load(path)?;
Self::from_loaded(&lm)
}
pub fn from_loaded(lm: &LoadedModel) -> Result<Self, FormatError> {
let config = LlamaConfig::parse(&lm.file.config, &lm.tensors)?;
let weights = Weights::load(lm, &config)?;
let max_seq = config.max_position_embeddings.min(8192);
let kv_cache = (0..config.num_hidden_layers)
.map(|i| {
let cache_seq = config.layer_kv_cache_seq(i, max_seq);
let sz = config.layer_kv_heads(i) * cache_seq * config.layer_head_dim(i);
(vec![0f32; sz], vec![0f32; sz])
})
.collect();
Ok(Self {
config,
weights,
past_seq_len: 0,
kv_cache,
prof: ForwardProf::default(),
})
}
pub fn enable_prof(&mut self) {
self.prof = ForwardProf {
enabled: true,
..Default::default()
};
}
pub fn to_backend(&mut self, backend: &dyn Backend) -> Result<(), BackendError> {
self.weights.final_norm = backend.to_backend(&self.weights.final_norm)?;
let upload_quant = backend.uploads_quant_weights();
if upload_quant {
self.weights.embed_tokens_quant.tensor =
backend.to_backend(&self.weights.embed_tokens_quant.tensor)?;
self.weights.embed_tokens_quant.bytes = Arc::new(Vec::new());
if let Some(ref mut lm) = self.weights.lm_head {
lm.tensor = backend.to_backend(&lm.tensor)?;
lm.bytes = Arc::new(Vec::new());
}
}
for layer in &mut self.weights.layers {
layer.input_norm = backend.to_backend(&layer.input_norm)?;
layer.post_norm = backend.to_backend(&layer.post_norm)?;
if let Some(ref b) = layer.q_proj_bias {
layer.q_proj_bias = Some(backend.to_backend(b)?);
}
if let Some(ref b) = layer.k_proj_bias {
layer.k_proj_bias = Some(backend.to_backend(b)?);
}
if let Some(ref b) = layer.v_proj_bias {
layer.v_proj_bias = Some(backend.to_backend(b)?);
}
if let Some(ref n) = layer.q_norm {
layer.q_norm = Some(backend.to_backend(n)?);
}
if let Some(ref n) = layer.k_norm {
layer.k_norm = Some(backend.to_backend(n)?);
}
if let Some(ref n) = layer.post_attn_norm {
layer.post_attn_norm = Some(backend.to_backend(n)?);
}
if let Some(ref n) = layer.post_ffw_norm {
layer.post_ffw_norm = Some(backend.to_backend(n)?);
}
if upload_quant {
layer.q_proj.tensor = backend.to_backend(&layer.q_proj.tensor)?;
layer.q_proj.bytes = Arc::new(Vec::new());
layer.k_proj.tensor = backend.to_backend(&layer.k_proj.tensor)?;
layer.k_proj.bytes = Arc::new(Vec::new());
layer.v_proj.tensor = backend.to_backend(&layer.v_proj.tensor)?;
layer.v_proj.bytes = Arc::new(Vec::new());
layer.o_proj.tensor = backend.to_backend(&layer.o_proj.tensor)?;
layer.o_proj.bytes = Arc::new(Vec::new());
layer.gate_proj.tensor = backend.to_backend(&layer.gate_proj.tensor)?;
layer.gate_proj.bytes = Arc::new(Vec::new());
layer.up_proj.tensor = backend.to_backend(&layer.up_proj.tensor)?;
layer.up_proj.bytes = Arc::new(Vec::new());
layer.down_proj.tensor = backend.to_backend(&layer.down_proj.tensor)?;
layer.down_proj.bytes = Arc::new(Vec::new());
}
}
Ok(())
}
pub fn reset_kv_cache(&mut self) {
self.past_seq_len = 0;
}
pub fn forward(
&mut self,
token_id: u32,
backend: &dyn Backend,
) -> Result<Vec<f32>, BackendError> {
let c = &self.config;
let max_seq = c.max_position_embeddings.min(8192);
if self.past_seq_len >= max_seq {
return Err(BackendError::ContextOverflow {
pos: self.past_seq_len,
max: max_seq,
});
}
if (token_id as usize) >= c.vocab_size {
return Err(BackendError::InvalidInput {
op: "TokenEmbed",
reason: format!(
"token_id {token_id} out of vocab range {}",
c.vocab_size
),
});
}
use std::time::Instant;
let prof_enabled = self.prof.enabled;
let t_embed = Instant::now();
let embed_table = &self.weights.embed_tokens;
let hidden_size = c.hidden_size;
if std::env::var("RUN_DEBUG_EMBED_ROWS").is_ok() && self.past_seq_len == 0 {
let table = embed_table.try_as_f32()?;
let rows_to_check: Vec<usize> = std::env::var("RUN_DEBUG_EMBED_ROWS")
.ok()
.map(|s| {
s.split(',')
.filter_map(|x| x.trim().parse::<usize>().ok())
.collect()
})
.unwrap_or_default();
for r in &rows_to_check {
let s = r * hidden_size;
let row = &table[s..s + hidden_size];
let m = row.iter().map(|v| v.abs()).fold(0f32, f32::max);
let rms = (row.iter().map(|v| v * v).sum::<f32>() / hidden_size as f32).sqrt();
let mean = row.iter().sum::<f32>() / hidden_size as f32;
eprintln!("embed row {r:>6}: abs_max={m:>8.4} rms={rms:>7.4} mean={mean:>9.5}");
}
}
let row_start = (token_id as usize) * hidden_size;
let mut embed_row: Vec<f32> = embed_table.try_as_f32()?[row_start..row_start + hidden_size].to_vec();
if c.family.scaled_embeddings {
let scale = (hidden_size as f32).sqrt();
for v in embed_row.iter_mut() {
*v *= scale;
}
}
let mut hidden = Tensor::try_from_f32(vec![1, hidden_size], embed_row)?;
if prof_enabled {
self.prof.embed_ms += t_embed.elapsed().as_secs_f64() * 1000.0;
}
let pos = self.past_seq_len as f32;
let pos_tensor = Tensor::from_f32(vec![1], vec![pos]);
let debug_layers = std::env::var("RUN_DEBUG_LAYERS").is_ok();
if debug_layers {
let h = hidden.try_as_f32()?;
let m = h.iter().map(|v| v.abs()).fold(0f32, f32::max);
let s = (h.iter().map(|v| v * v).sum::<f32>() / h.len() as f32).sqrt();
eprintln!("post-embed abs_max={m:.4} rms={s:.4}");
}
use crate::backend::LayerFusedInput;
use crate::arch::decoder::config::HiddenActivation;
let fused_inputs: Vec<LayerFusedInput<'_>> = self.weights.layers.iter().enumerate()
.map(|(i, l)| LayerFusedInput {
input_norm: &l.input_norm,
q_proj: &l.q_proj.tensor,
k_proj: &l.k_proj.tensor,
v_proj: &l.v_proj.tensor,
q_bias: l.q_proj_bias.as_ref(),
k_bias: l.k_proj_bias.as_ref(),
v_bias: l.v_proj_bias.as_ref(),
q_norm: l.q_norm.as_ref(),
k_norm: l.k_norm.as_ref(),
o_proj: &l.o_proj.tensor,
post_norm: &l.post_norm,
gate_proj: &l.gate_proj.tensor,
up_proj: &l.up_proj.tensor,
down_proj: &l.down_proj.tensor,
num_heads: c.num_attention_heads as u32,
kv_heads: c.layer_kv_heads(i) as u32,
head_dim: c.layer_head_dim(i) as u32,
rope_dim: c.layer_rope_dim(i) as u32,
rope_theta: c.layer_rope_theta(i),
attn_scale: c.layer_attn_scale(i),
window: c.layer_window(i).map(|w| w as u32).unwrap_or(0),
layer_idx: i,
post_attn_norm: l.post_attn_norm.as_ref(),
post_ffw_norm: l.post_ffw_norm.as_ref(),
use_gelu_tanh: c.hidden_activation == HiddenActivation::GeluTanh,
layer_output_scale: l.layer_output_scale.as_ref()
.and_then(|t| t.try_as_f32().ok().and_then(|s| s.first().copied()))
.unwrap_or(1.0_f32),
})
.collect();
let hc_timing = std::env::var("HC_TIMING").is_ok();
let t_fused = Instant::now();
let fused_max_n: usize = std::env::var("FUSED_MAX_N")
.ok().and_then(|s| s.parse().ok()).unwrap_or(usize::MAX);
let fused_debug = std::env::var("FUSED_DEBUG").is_ok();
let used_fused;
if !debug_layers && !prof_enabled {
let mut li = 0; let mut any_fused = false;
loop {
if li >= c.num_hidden_layers { break; }
let l0 = &fused_inputs[li];
let mut gi = li + 1;
while gi < c.num_hidden_layers {
let lj = &fused_inputs[gi];
if lj.head_dim != l0.head_dim || lj.kv_heads != l0.kv_heads
|| lj.num_heads != l0.num_heads || lj.window != l0.window
{ break; }
if gi - li >= fused_max_n { break; }
gi += 1;
}
let group_max_seq = if l0.window > 0 { l0.window } else { max_seq as u32 };
match backend.forward_decode_fused_layers(
&hidden, &fused_inputs[li..gi],
self.past_seq_len, group_max_seq, c.rms_norm_eps,
)? {
Some(h) => {
if fused_debug {
let vals = backend.download_f32(&h)?;
let nan_c = vals.iter().filter(|v| !v.is_finite()).count();
eprintln!("[FUSED_DEBUG] group li={li}..{gi} n={} nan={nan_c}/{}", gi-li, vals.len());
if nan_c > 0 {
let first8: Vec<_> = vals.iter().take(8).collect();
eprintln!(" first8: {first8:.4?}");
}
}
hidden = h; li = gi; any_fused = true;
}
None => {
if fused_debug {
eprintln!("[FUSED_DEBUG] group li={li}..{gi} n={} โ per-layer fallback", gi-li);
}
for i in li..gi {
hidden = forward_layer(
&hidden, i, &self.weights.layers[i], c, &pos_tensor, backend,
&mut self.kv_cache[i], self.past_seq_len, None,
)?;
}
li = gi;
}
}
}
used_fused = any_fused;
} else {
used_fused = false;
for i in 0..c.num_hidden_layers {
hidden = forward_layer(
&hidden,
i,
&self.weights.layers[i],
c,
&pos_tensor,
backend,
&mut self.kv_cache[i],
self.past_seq_len,
if prof_enabled { Some(&mut self.prof) } else { None },
)?;
if debug_layers {
let h = hidden.try_as_f32()?;
let m = h.iter().map(|v| v.abs()).fold(0f32, f32::max);
let s = (h.iter().map(|v| v * v).sum::<f32>() / h.len() as f32).sqrt();
let kind = match c.layer_types.get(i).copied() {
Some(crate::arch::decoder::config::LayerKind::Full) => "full",
_ => "slid",
};
eprintln!("layer {i:>3} {kind} abs_max={m:>9.4} rms={s:>8.4}");
}
}
}
let fused_ms = t_fused.elapsed().as_secs_f64() * 1000.0;
let t_final = Instant::now();
let final_normed = backend
.execute(
&Op::RmsNorm {
eps: c.rms_norm_eps,
},
&[&hidden, &self.weights.final_norm],
)?
.remove(0);
let final_ms = t_final.elapsed().as_secs_f64() * 1000.0;
if prof_enabled {
self.prof.final_norm_ms += final_ms;
}
let t_lm = Instant::now();
let lm_head_qw = self
.weights
.lm_head
.as_ref()
.unwrap_or(&self.weights.embed_tokens_quant);
if debug_layers {
let h = final_normed.try_as_f32()?;
let m = h.iter().map(|v| v.abs()).fold(0f32, f32::max);
let s = (h.iter().map(|v| v * v).sum::<f32>() / h.len() as f32).sqrt();
eprintln!("post-final-norm abs_max={m:.4} rms={s:.4}");
}
let logits = qw_matmul(&final_normed, lm_head_qw, backend)?;
if debug_layers {
let h = backend.download_f32(&logits)?;
let m = h.iter().map(|v| v.abs()).fold(0f32, f32::max);
let s = (h.iter().map(|v| v * v).sum::<f32>() / h.len() as f32).sqrt();
eprintln!("post-lm-head abs_max={m:.4} rms={s:.4}");
}
let lm_ms = t_lm.elapsed().as_secs_f64() * 1000.0;
if prof_enabled {
self.prof.lm_head_ms += lm_ms;
}
if prof_enabled {
self.prof.forwards += 1;
}
self.past_seq_len += 1;
let t_dl = Instant::now();
let mut logits_vec = backend.download_f32(&logits)?;
let dl_ms = t_dl.elapsed().as_secs_f64() * 1000.0;
if hc_timing {
eprintln!(
" fused={fused_ms:>6.2}ms({}) fn={final_ms:>5.2}ms lm={lm_ms:>6.2}ms dl={dl_ms:>5.2}ms tot={:.2}ms",
if used_fused { "Y" } else { "N" },
fused_ms + final_ms + lm_ms + dl_ms
);
}
if let Some(cap) = c.final_logit_softcapping {
for v in logits_vec.iter_mut() {
*v = (*v / cap).tanh() * cap;
}
}
if logits_vec.iter().any(|v| !v.is_finite()) {
return Err(BackendError::NonFiniteOutput {
op: "forward",
layer: c.num_hidden_layers,
pos: self.past_seq_len - 1,
});
}
if std::env::var("RUN_DEBUG_LOGITS").is_ok() {
let mut idx: Vec<usize> = (0..logits_vec.len()).collect();
idx.sort_unstable_by(|&a, &b| {
logits_vec[b].partial_cmp(&logits_vec[a]).unwrap_or(std::cmp::Ordering::Equal)
});
eprintln!("top-10 logits:");
for &i in idx.iter().take(10) {
eprintln!(" id={i:>6} logit={:>8.3}", logits_vec[i]);
}
}
Ok(logits_vec)
}
}
fn dbg_stats_l(layer: usize, label: &str, v: &[f32]) {
let m = v.iter().map(|x| x.abs()).fold(0f32, f32::max);
let rms = (v.iter().map(|x| x * x).sum::<f32>() / v.len() as f32).sqrt();
eprintln!(" L{layer} {:20} abs_max={:>12.4} rms={:>10.4} len={}", label, m, rms, v.len());
}
fn dbg_stats(label: &str, v: &[f32]) { dbg_stats_l(0, label, v); }
fn forward_layer(
hidden: &Tensor,
layer_idx: usize,
layer: &LayerWeights,
config: &LlamaConfig,
pos: &Tensor,
backend: &dyn Backend,
kv: &mut (Vec<f32>, Vec<f32>),
past_seq_len: usize,
prof: Option<&mut ForwardProf>,
) -> Result<Tensor, BackendError> {
use std::time::Instant;
let debug_layer = std::env::var("RUN_DEBUG_LAYER_IDX")
.ok()
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(0);
let debug_l0 = std::env::var("RUN_DEBUG_LAYERS").is_ok() && layer_idx == debug_layer && past_seq_len == 0;
let eps = config.rms_norm_eps;
if debug_l0 {
dbg_stats_l(layer_idx, "hidden_in (embed)", &backend.download_f32(hidden)?);
}
let hidden_size = config.hidden_size;
let head_dim = config.layer_head_dim(layer_idx);
let num_heads = config.num_attention_heads;
let kv_heads = config.layer_kv_heads(layer_idx);
let sliding_window = config.layer_window(layer_idx);
let mut acc_input_norm = 0f64;
let mut acc_qkv_proj = 0f64;
let mut acc_qk_norm = 0f64;
let mut acc_rope = 0f64;
let mut acc_kv_append = 0f64;
let mut acc_attention = 0f64;
let mut acc_o_proj = 0f64;
let mut acc_post_norm = 0f64;
let mut acc_ffn = 0f64;
let mut acc_residual = 0f64;
let t = Instant::now();
let no_bias = layer.q_proj_bias.is_none()
&& layer.k_proj_bias.is_none()
&& layer.v_proj_bias.is_none();
let (mut q, mut k, mut v, qk_norm_done) = if no_bias {
if let (Some(qn), Some(kn)) = (&layer.q_norm, &layer.k_norm) {
let (qq, kk, vv) = backend.fused_norm_qkv_qknorm(
hidden,
&layer.input_norm,
&layer.q_proj.tensor,
&layer.k_proj.tensor,
&layer.v_proj.tensor,
qn, kn,
eps,
num_heads, kv_heads, head_dim,
)?;
(qq, kk, vv, true)
} else {
let qkv_outs = backend.fused_norm_quant_matmul_multi(
hidden, &layer.input_norm, eps,
&[&layer.q_proj.tensor, &layer.k_proj.tensor, &layer.v_proj.tensor],
)?;
let mut it = qkv_outs.into_iter();
(it.next().unwrap(), it.next().unwrap(), it.next().unwrap(), false)
}
} else {
let qkv_outs = backend.fused_norm_quant_matmul_multi(
hidden, &layer.input_norm, eps,
&[&layer.q_proj.tensor, &layer.k_proj.tensor, &layer.v_proj.tensor],
)?;
let mut it = qkv_outs.into_iter();
(it.next().unwrap(), it.next().unwrap(), it.next().unwrap(), false)
};
acc_input_norm += t.elapsed().as_secs_f64() * 1000.0;
if debug_l0 || (std::env::var("RUN_DEBUG_LAYERS").is_ok() && layer_idx == 1 && past_seq_len == 0) {
dbg_stats_l(layer_idx, "q (post-qkv)", &backend.download_f32(&q)?);
dbg_stats_l(layer_idx, "k (post-qkv)", &backend.download_f32(&k)?);
dbg_stats_l(layer_idx, "v (post-qkv)", &backend.download_f32(&v)?);
}
if let Some(bias) = &layer.q_proj_bias {
q = backend.execute(&Op::Add, &[&q, bias])?.remove(0);
}
if let Some(bias) = &layer.k_proj_bias {
k = backend.execute(&Op::Add, &[&k, bias])?.remove(0);
}
if let Some(bias) = &layer.v_proj_bias {
v = backend.execute(&Op::Add, &[&v, bias])?.remove(0);
}
acc_qkv_proj += t.elapsed().as_secs_f64() * 1000.0;
let t = Instant::now();
if !qk_norm_done {
if let (Some(qn), Some(kn)) = (&layer.q_norm, &layer.k_norm) {
let q_reshaped = Tensor::from_f32(vec![num_heads, head_dim], q.to_f32_vec());
let k_reshaped = Tensor::from_f32(vec![kv_heads, head_dim], k.to_f32_vec());
let normed = backend.rms_norm_multi(&[(&q_reshaped, qn), (&k_reshaped, kn)], eps)?;
let mut it = normed.into_iter();
let q_n = it.next().unwrap();
let k_n = it.next().unwrap();
q = Tensor::from_f32(vec![1, num_heads * head_dim], q_n.to_f32_vec());
k = Tensor::from_f32(vec![1, kv_heads * head_dim], k_n.to_f32_vec());
}
}
acc_qk_norm += t.elapsed().as_secs_f64() * 1000.0;
let t = Instant::now();
let layer_rope_base = config.layer_rope_theta(layer_idx);
let layer_rope_dim = config.layer_rope_dim(layer_idx);
let q_shape = vec![num_heads, head_dim];
let k_shape = vec![kv_heads, head_dim];
let q_reshaped = Tensor::from_f32(q_shape.clone(), q.to_f32_vec());
let k_reshaped = Tensor::from_f32(k_shape.clone(), k.to_f32_vec());
let q_roped = backend
.execute(
&Op::Rope {
head_dim: head_dim as u32,
rope_dim: layer_rope_dim as u32,
base: layer_rope_base,
},
&[&q_reshaped, pos],
)?
.remove(0);
let k_roped = backend
.execute(
&Op::Rope {
head_dim: head_dim as u32,
rope_dim: layer_rope_dim as u32,
base: layer_rope_base,
},
&[&k_reshaped, pos],
)?
.remove(0);
acc_rope += t.elapsed().as_secs_f64() * 1000.0;
if debug_l0 {
dbg_stats_l(layer_idx, "q_roped", &backend.download_f32(&q_roped)?);
dbg_stats_l(layer_idx, "k_roped", &backend.download_f32(&k_roped)?);
}
let max_seq = config.max_position_embeddings.min(8192);
let cache_seq = config.layer_kv_cache_seq(layer_idx, max_seq);
let scale = config.layer_attn_scale(layer_idx);
let window: u32 = sliding_window.map(|w| w as u32).unwrap_or(0);
let total_seq = past_seq_len + 1;
let _ = (total_seq, max_seq);
let use_gpu_attn = backend.supports_gpu_attention()
&& !config.family.v_norm_per_head
&& layer.post_attn_norm.is_none()
&& std::env::var("MR_GPU_ATTN").is_ok();
let mut hidden1_gpu: Option<Tensor> = None;
let attn_tensor = if use_gpu_attn {
let t = Instant::now();
let q_for_attn = Tensor { shape: vec![num_heads, head_dim], dtype: q_roped.dtype, data: q_roped.data.clone() };
let k_for_attn = Tensor { shape: vec![kv_heads, head_dim], dtype: k_roped.dtype, data: k_roped.data.clone() };
let v_for_attn = Tensor { shape: vec![kv_heads, head_dim], dtype: v.dtype, data: v.data.clone() };
let h1 = backend.fused_attn_oproj_residual(
&q_for_attn, &k_for_attn, &v_for_attn,
hidden, &layer.o_proj.tensor,
layer_idx, past_seq_len,
num_heads as u32, kv_heads as u32, head_dim as u32, cache_seq as u32,
scale, window,
)?;
acc_kv_append += t.elapsed().as_secs_f64() * 1000.0;
acc_attention += 0.0;
hidden1_gpu = Some(h1);
Tensor::from_f32(vec![1, num_heads * head_dim], vec![0.0; num_heads * head_dim])
} else {
let t = Instant::now();
let v_flat = if config.family.v_norm_per_head {
let mut v_data = v.to_f32_vec();
let inv_d = 1.0 / head_dim as f32;
for h in 0..kv_heads {
let off = h * head_dim;
let mut sumsq = 0f32;
for j in 0..head_dim {
let val = v_data[off + j];
sumsq += val * val;
}
let rms = (sumsq * inv_d + eps).sqrt();
let scale_v = 1.0 / rms;
for j in 0..head_dim {
v_data[off + j] *= scale_v;
}
}
v_data
} else {
v.to_f32_vec()
};
let k_flat = k_roped.to_f32_vec();
let kv_slot = past_seq_len % cache_seq;
for h in 0..kv_heads {
let src_base = h * head_dim;
let dst_base = h * cache_seq * head_dim + kv_slot * head_dim;
for d in 0..head_dim {
kv.0[dst_base + d] = k_flat[src_base + d];
kv.1[dst_base + d] = v_flat[src_base + d];
}
}
acc_kv_append += t.elapsed().as_secs_f64() * 1000.0;
let t = Instant::now();
let repeat = num_heads / kv_heads;
let read_seq = total_seq.min(cache_seq);
let read_start = if total_seq > cache_seq { total_seq - cache_seq } else { 0 };
let mut k_full = vec![0f32; num_heads * read_seq * head_dim];
let mut v_full = vec![0f32; num_heads * read_seq * head_dim];
for h in 0..num_heads {
let kv_h = h / repeat;
for si in 0..read_seq {
let s = read_start + si;
let slot = s % cache_seq;
for d in 0..head_dim {
let src = kv_h * cache_seq * head_dim + slot * head_dim + d;
let dst = h * read_seq * head_dim + si * head_dim + d;
k_full[dst] = kv.0[src];
v_full[dst] = kv.1[src];
}
}
}
let q_heads = q_roped.to_f32_vec();
let mut attn_out = vec![0f32; num_heads * head_dim];
for h in 0..num_heads {
let mut scores = vec![0f32; read_seq];
let q_off = h * head_dim;
let kv_off = h * read_seq * head_dim;
for s in 0..read_seq {
let mut acc = 0f32;
for d in 0..head_dim {
acc += q_heads[q_off + d] * k_full[kv_off + s * head_dim + d];
}
scores[s] = acc * scale;
}
let max_s = scores.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let mut sum = 0f32;
for s in scores.iter_mut() { *s = (*s - max_s).exp(); sum += *s; }
for s in scores.iter_mut() { *s /= sum; }
let out_off = h * head_dim;
for s in 0..read_seq {
let v_row = &v_full[kv_off + s * head_dim..kv_off + (s + 1) * head_dim];
for d in 0..head_dim {
attn_out[out_off + d] += scores[s] * v_row[d];
}
}
}
acc_attention += t.elapsed().as_secs_f64() * 1000.0;
Tensor::from_f32(vec![1, num_heads * head_dim], attn_out)
};
let debug_l1 = std::env::var("RUN_DEBUG_LAYERS").is_ok() && layer_idx == 1 && past_seq_len == 0;
if debug_l0 || debug_l1 {
dbg_stats_l(layer_idx, "attn_out", &backend.download_f32(&attn_tensor)?);
}
let hidden1 = if let Some(h1) = hidden1_gpu {
h1
} else {
let t = Instant::now();
let mut attn_proj = qw_matmul(&attn_tensor, &layer.o_proj, backend)?;
if debug_l0 || debug_l1 {
dbg_stats_l(layer_idx, "o_proj_out", &backend.download_f32(&attn_proj)?);
}
if let Some(ref n) = layer.post_attn_norm {
attn_proj = backend
.execute(&Op::RmsNorm { eps }, &[&attn_proj, n])?
.remove(0);
}
acc_o_proj += t.elapsed().as_secs_f64() * 1000.0;
let t = Instant::now();
let h1 = backend.execute(&Op::Add, &[hidden, &attn_proj])?.remove(0);
if debug_l0 || debug_l1 {
dbg_stats_l(layer_idx, "hidden1 (attn+res)", &backend.download_f32(&h1)?);
}
acc_residual += t.elapsed().as_secs_f64() * 1000.0;
h1
};
use crate::arch::decoder::config::HiddenActivation;
let mut out_gpu: Option<Tensor> = None;
let mut ffn_out = match config.hidden_activation {
HiddenActivation::Silu if layer.post_ffw_norm.is_none() && layer.layer_output_scale.is_none() && !debug_l0 => {
if std::env::var("RUN_DEBUG_LAYERS").is_ok() && past_seq_len == 0 && layer_idx <= 2 {
dbg_stats_l(layer_idx, "hidden1 (fused input)", &backend.download_f32(&hidden1)?);
}
let t = Instant::now();
let out = backend.fused_ffn_residual(
&hidden1,
&layer.post_norm,
&layer.gate_proj.tensor,
&layer.up_proj.tensor,
&layer.down_proj.tensor,
eps,
)?;
acc_post_norm += t.elapsed().as_secs_f64() * 1000.0;
out_gpu = Some(out);
if std::env::var("RUN_DEBUG_LAYERS").is_ok() && past_seq_len == 0 && layer_idx <= 2 {
let fused = out_gpu.as_ref().unwrap();
dbg_stats_l(layer_idx, "ffn+res (fused)", &backend.download_f32(fused)?);
}
Tensor::from_f32(hidden1.shape.clone(), vec![0.0; hidden_size])
}
_ => {
let t = Instant::now();
let gate_up = backend.fused_norm_quant_matmul_multi(
&hidden1,
&layer.post_norm,
eps,
&[&layer.gate_proj.tensor, &layer.up_proj.tensor],
)?;
acc_post_norm += t.elapsed().as_secs_f64() * 1000.0;
let t = Instant::now();
let mut gate_up_iter = gate_up.into_iter();
let gate = gate_up_iter.next().unwrap();
let up = gate_up_iter.next().unwrap();
if debug_l0 {
dbg_stats_l(layer_idx, "gate (post_norm+proj)", &backend.download_f32(&gate)?);
dbg_stats_l(layer_idx, "up (post_norm+proj)", &backend.download_f32(&up)?);
}
let act: fn(f32) -> f32 = match config.hidden_activation {
HiddenActivation::Silu => |x| x / (1.0 + (-x).exp()),
HiddenActivation::GeluTanh => |x| {
let c = (2.0_f32 / std::f32::consts::PI).sqrt();
0.5 * x * (1.0 + (c * (x + 0.044715 * x * x * x)).tanh())
},
HiddenActivation::GeluErf => |x| {
let inv_sqrt2 = 1.0_f32 / std::f32::consts::SQRT_2;
let z = x * inv_sqrt2;
let sign = if z < 0.0 { -1.0 } else { 1.0 };
let zabs = z.abs();
let p = 0.327_591_1_f32;
let a1 = 0.254_829_592_f32;
let a2 = -0.284_496_736_f32;
let a3 = 1.421_413_741_f32;
let a4 = -1.453_152_027_f32;
let a5 = 1.061_405_429_f32;
let t_ = 1.0 / (1.0 + p * zabs);
let y = 1.0
- ((((a5 * t_ + a4) * t_ + a3) * t_ + a2) * t_ + a1)
* t_
* (-zabs * zabs).exp();
0.5 * x * (1.0 + sign * y)
},
};
let mut mid = gate.to_f32_vec();
let up_vec = up.to_f32_vec();
for (m, u) in mid.iter_mut().zip(up_vec.iter()) { *m = act(*m) * *u; }
let mid_t = Tensor::from_f32(gate.shape.clone(), mid);
if debug_l0 {
dbg_stats_l(layer_idx, "silu(gate)*up (mid)", &backend.download_f32(&mid_t)?);
}
let _ = t;
let down_out = qw_matmul(&mid_t, &layer.down_proj, backend)?;
if debug_l0 {
dbg_stats_l(layer_idx, "down_proj_out", &backend.download_f32(&down_out)?);
}
down_out
}
};
if let Some(ref n) = layer.post_ffw_norm {
ffn_out = backend
.execute(&Op::RmsNorm { eps }, &[&ffn_out, n])?
.remove(0);
}
acc_ffn += t.elapsed().as_secs_f64() * 1000.0;
if debug_l0 {
if let Some(ref o) = out_gpu {
dbg_stats_l(layer_idx, "ffn+res (fused)", &backend.download_f32(o)?);
} else {
dbg_stats_l(layer_idx, "ffn_out", &backend.download_f32(&ffn_out)?);
}
}
let _ = hidden_size;
let t = Instant::now();
let mut out = if let Some(o) = out_gpu {
o
} else {
backend.execute(&Op::Add, &[&hidden1, &ffn_out])?.remove(0)
};
if let Some(ref s) = layer.layer_output_scale {
let scalar = s.as_f32()[0];
if std::env::var("RUN_DEBUG_LAYERS").is_ok() {
eprintln!(" layer {layer_idx:>3} layer_output_scale = {scalar:.6}");
}
let mut out_v = out.to_f32_vec();
for v in out_v.iter_mut() {
*v *= scalar;
}
out = Tensor::from_f32(out.shape.clone(), out_v);
}
acc_residual += t.elapsed().as_secs_f64() * 1000.0;
if let Some(p) = prof {
p.input_norm_ms += acc_input_norm;
p.qkv_proj_ms += acc_qkv_proj;
p.qk_norm_ms += acc_qk_norm;
p.rope_ms += acc_rope;
p.kv_append_ms += acc_kv_append;
p.attention_ms += acc_attention;
p.o_proj_ms += acc_o_proj;
p.post_norm_ms += acc_post_norm;
p.ffn_ms += acc_ffn;
p.residual_ms += acc_residual;
}
Ok(out)
}