use crate::fast;
use g_core::{Dtype, Error, Result, Tensor};
use rayon::prelude::*;
pub struct FusedAux {
pub x: Tensor,
pub a: Tensor,
pub h: Tensor,
pub s2: Tensor,
pub out1: Tensor,
pub xn2: Tensor,
pub f1: Tensor,
pub f2: Tensor,
}
fn check(x: &Tensor, d: usize) -> Result<(usize, usize)> {
if x.dtype() != Dtype::F32 {
return Err(Error::dtype("fused_block", "f32 only"));
}
if x.rank() != 3 || x.shape()[2] != d {
return Err(Error::shape("fused_block", "expected [B, T, D]"));
}
Ok((x.shape()[0], x.shape()[1]))
}
#[allow(clippy::too_many_arguments)]
pub fn fused_block_fwd(
x: &Tensor,
wa: &Tensor,
wb: &Tensor,
wo: &Tensor,
wf1: &Tensor,
wf2: &Tensor,
g1: &Tensor,
g2: &Tensor,
g3: &Tensor,
g4: &Tensor,
eps: f32,
) -> Result<(Tensor, FusedAux)> {
let d = x.shape()[2];
check(x, d)?;
let r1 = crate::rms_norm(x, eps)?;
let xn = fast::binary_f32("fused", &r1, g1, |a, b| a * b)?;
let ones_col = Tensor::from_vec_f32(
vec![1.0; x.shape()[0] * x.shape()[1]],
&[x.shape()[0], x.shape()[1], 1],
)?;
let xna = crate::cat(&[&xn, &ones_col], 2)?;
let ua = crate::matmul(&xna, wa)?;
let a = crate::sigmoid(&ua)?;
let ub = crate::matmul(&xn, wb)?;
let h = crate::gated_scan(&a, &ub)?;
let hg = fast::binary_f32("fused", &h, g2, |a, b| a * b)?;
let s2 = fast::unary_f32(&hg, fast::k_silu)?;
let so = crate::matmul(&s2, wo)?;
let out1 = fast::binary_f32("fused", x, &so, |a, b| a + b)?;
let r2 = crate::rms_norm(&out1, eps)?;
let xn2 = fast::binary_f32("fused", &r2, g3, |a, b| a * b)?;
let f1 = crate::matmul(&xn2, wf1)?;
let sf1 = fast::unary_f32(&f1, fast::k_silu)?;
let f2 = crate::matmul(&sf1, wf2)?;
let f = fast::binary_f32("fused", &f2, g4, |a, b| a * b)?;
let y = fast::binary_f32("fused", &out1, &f, |a, b| a + b)?;
let aux = FusedAux {
x: x.clone(),
a,
h,
s2,
out1,
xn2,
f1,
f2,
};
Ok((y, aux))
}
#[inline]
fn sum01(t: &Tensor) -> Result<Tensor> {
crate::sum(t, Some(&[0, 1]), false)
}
fn bt_b(a: &Tensor, b: &Tensor) -> Result<Tensor> {
let at = a.transpose()?; let prod = crate::matmul(&at, b)?; crate::sum(&prod, Some(&[0]), false)
}
fn silu_local(x: &Tensor, out: &mut [f32]) {
let xv = x.to_vec_f32().expect("f32");
out.par_chunks_mut(1 << 14)
.enumerate()
.for_each(|(i, chunk)| {
let off = i * (1 << 14);
let mut tmp: Vec<f32> = chunk
.iter()
.enumerate()
.map(|(j, _)| -xv[off + j])
.collect();
let p = tmp.as_ptr();
fast::k_exp(
unsafe { std::slice::from_raw_parts(p, tmp.len()) },
&mut tmp,
);
for (j, o) in chunk.iter_mut().enumerate() {
let s = 1.0 / (1.0 + tmp[j]);
*o = s * (1.0 + xv[off + j] - xv[off + j] * s);
}
});
}
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
pub fn fused_block_bwd(
aux: &FusedAux,
wa: &Tensor,
wb: &Tensor,
wo: &Tensor,
wf1: &Tensor,
wf2: &Tensor,
g1: &Tensor,
g2: &Tensor,
g3: &Tensor,
g4: &Tensor,
eps: f32,
gy: &Tensor,
) -> Result<(
Tensor,
Tensor,
Tensor,
Tensor,
Tensor,
Tensor,
Tensor,
Tensor,
Tensor,
Tensor,
)> {
let x = &aux.x;
let _dbg = std::env::var("FUSED_PROF").is_ok();
macro_rules! t0 {
() => {
if _dbg {
std::time::Instant::now()
} else {
std::time::Instant::now()
}
};
}
macro_rules! tlog {
($name:expr, $t:ident) => {
if _dbg {
eprintln!(" {} {:?}", $name, $t.elapsed());
}
};
}
let mut _tt = t0!();
let gg4 = sum01(&fast::binary_f32("fused", gy, &aux.f2, |a, b| a * b)?)?;
let dm = fast::binary_f32("fused", gy, g4, |a, b| a * b)?;
tlog!("tail", _tt);
let mut _tt = t0!();
let sf1 = fast::unary_f32(&aux.f1, fast::k_silu)?;
let gwf2 = bt_b(&sf1, &dm)?; let dsf1 = crate::matmul(&dm, &wf2.transpose()?)?; let mut sl = vec![0f32; aux.f1.numel()];
silu_local(&aux.f1, &mut sl);
let sl_t = Tensor::from_vec_f32(sl, aux.f1.shape())?;
let d_pre = fast::binary_f32("fused", &dsf1, &sl_t, |a, b| a * b)?;
let gwf1 = bt_b(&aux.xn2, &d_pre)?; let dxn2 = crate::matmul(&d_pre, &wf1.transpose()?)?; tlog!("ffn", _tt);
let mut _tt = t0!();
let r2 = crate::rms_norm(&aux.out1, eps)?;
let gg3 = sum01(&fast::binary_f32("fused", &dxn2, &r2, |a, b| a * b)?)?;
let dxn2g = fast::binary_f32("fused", &dxn2, g3, |a, b| a * b)?;
let dout1 = fast::binary_f32(
"fused",
&crate::rms_norm_backward(&aux.out1, &dxn2g, eps)?,
gy,
|a, b| a + b,
)?;
tlog!("rms2", _tt);
let mut _tt = t0!();
let gs2 = crate::matmul(&dout1, &wo.transpose()?)?;
let gwo = bt_b(&aux.s2, &dout1)?;
let mut dx = dout1;
tlog!("wo", _tt);
let mut _tt = t0!();
let hg = fast::binary_f32("fused", &aux.h, g2, |a, b| a * b)?;
let gg2 = sum01(&fast::binary_f32("fused", &gs2, &hg, |a, b| a * b)?)?; let _ = gg2;
let mut sl_h = vec![0f32; aux.h.numel()];
silu_local(&hg, &mut sl_h);
let slh_t = Tensor::from_vec_f32(sl_h, hg.shape())?;
let ghg = fast::binary_f32("fused", &gs2, &slh_t, |a, b| a * b)?;
let dh = fast::binary_f32("fused", &ghg, g2, |a, b| a * b)?;
let gg2 = sum01(&fast::binary_f32("fused", &ghg, &aux.h, |a, b| a * b)?)?;
tlog!("silu2", _tt);
let mut _tt = t0!();
let (ga, gb) = crate::gated_scan_backward(&aux.a, &aux.h, &dh)?;
tlog!("scan", _tt);
let mut _tt = t0!();
let one_minus_a = fast::unary_f32(&aux.a, |s, d| {
for (dd, &v) in d.iter_mut().zip(s) {
*dd = 1.0 - v;
}
})?;
let a_a1 = fast::binary_f32("fused", &aux.a, &one_minus_a, |a, b| a * b)?;
let gua = fast::binary_f32("fused", &ga, &a_a1, |a, b| a * b)?;
let r1 = crate::rms_norm(x, eps)?;
let xn = fast::binary_f32("fused", &r1, g1, |a, b| a * b)?;
tlog!("gate", _tt);
let mut _tt = t0!();
let ones_col = Tensor::from_vec_f32(
vec![1.0; x.shape()[0] * x.shape()[1]],
&[x.shape()[0], x.shape()[1], 1],
)?;
let xna = crate::cat(&[&xn, &ones_col], 2)?;
let gwa = bt_b(&xna, &gua)?; let gwb = bt_b(&xn, &gb)?;
let wa_top = wa.slice(&[
(Some(0), Some((wa.shape()[0] - 1) as isize), None),
(None, None, None),
])?;
let dxn1 = fast::binary_f32(
"fused",
&crate::matmul(&gua, &wa_top.transpose()?)?,
&crate::matmul(&gb, &wb.transpose()?)?,
|a, b| a + b,
)?;
let gg1 = sum01(&fast::binary_f32("fused", &dxn1, &r1, |a, b| a * b)?)?;
let dxn1g = fast::binary_f32("fused", &dxn1, g1, |a, b| a * b)?;
let dx_rms = crate::rms_norm_backward(x, &dxn1g, eps)?;
dx = fast::binary_f32("fused", &dx, &dx_rms, |a, b| a + b)?;
tlog!("rms1+matmuls", _tt);
Ok((dx, gwa, gwb, gwo, gwf1, gwf2, gg1, gg2, gg3, gg4))
}