use pagesift_infer::{
Activation, BN_EPS, BatchNorm, Conv2d, LayerNorm, Linear, Tensor, Weights, ops,
};
use crate::lcnet::ConvBn;
pub(crate) fn conv_bn(
w: &Weights,
conv: &str,
norm: &str,
stride: usize,
pad: usize,
depthwise: bool,
act: Activation,
) -> Result<ConvBn, String> {
let mut c = Conv2d::load(w, conv, (stride, stride), (pad, pad), (1, 1), 1)?;
if depthwise {
c.groups = c.weight.shape[0];
}
c.fold(&BatchNorm::load(w, norm, BN_EPS)?);
Ok(ConvBn { conv: c, act })
}
pub(crate) fn conv_layer(
w: &Weights,
p: &str,
stride: usize,
depthwise: bool,
act: Activation,
) -> Result<ConvBn, String> {
let k = w.get(&format!("{p}.convolution.weight"))?.shape[2];
conv_bn(
w,
&format!("{p}.convolution"),
&format!("{p}.normalization"),
stride,
(k - 1) / 2,
depthwise,
act,
)
}
pub(crate) fn conv_norm(
w: &Weights,
p: &str,
stride: usize,
act: Activation,
) -> Result<ConvBn, String> {
let k = w.get(&format!("{p}.conv.weight"))?.shape[2];
conv_bn(
w,
&format!("{p}.conv"),
&format!("{p}.norm"),
stride,
(k - 1) / 2,
false,
act,
)
}
pub(crate) fn count(w: &Weights, name: impl Fn(usize) -> String) -> usize {
(0..).take_while(|&i| w.opt(&name(i)).is_some()).count()
}
#[derive(Debug, Clone)]
pub(crate) struct Mlp {
layers: Vec<Linear>,
}
impl Mlp {
pub(crate) fn load(w: &Weights, p: &str) -> Result<Mlp, String> {
let n = count(w, |i| format!("{p}.layers.{i}.weight"));
if n == 0 {
return Err(format!("no {p}.layers in the weights"));
}
let layers = (0..n)
.map(|i| Linear::load(w, &format!("{p}.layers.{i}")))
.collect::<Result<_, _>>()?;
Ok(Mlp { layers })
}
pub(crate) fn forward(&self, x: &Tensor) -> Tensor {
let mut h = self.layers[0].forward(x);
for l in &self.layers[1..] {
Activation::Relu.apply_all(&mut h);
h = l.forward(&h);
}
h
}
}
#[derive(Debug, Clone)]
pub(crate) struct Ffn {
fc1: Linear,
fc2: Linear,
act: Activation,
}
impl Ffn {
pub(crate) fn load(w: &Weights, p: &str, act: Activation) -> Result<Ffn, String> {
Ok(Ffn {
fc1: Linear::load(w, &format!("{p}.fc1"))?,
fc2: Linear::load(w, &format!("{p}.fc2"))?,
act,
})
}
pub(crate) fn forward(&self, x: &Tensor) -> Tensor {
let mut h = self.fc1.forward(x);
self.act.apply_all(&mut h);
self.fc2.forward(&h)
}
}
pub(crate) fn add_norm(x: &Tensor, y: &Tensor, norm: &LayerNorm) -> Tensor {
norm.forward(&ops::add(x, y))
}
#[derive(Debug, Clone)]
pub(crate) struct SelfAttention {
q: Linear,
k: Linear,
v: Linear,
o: Linear,
heads: usize,
}
impl SelfAttention {
pub(crate) fn load(w: &Weights, p: &str, heads: usize) -> Result<SelfAttention, String> {
Ok(SelfAttention {
q: Linear::load(w, &format!("{p}.q_proj"))?,
k: Linear::load(w, &format!("{p}.k_proj"))?,
v: Linear::load(w, &format!("{p}.v_proj"))?,
o: Linear::load(w, &format!("{p}.out_proj"))?,
heads,
})
}
pub(crate) fn forward(&self, x: &Tensor, pos: &Tensor) -> Tensor {
let qk = ops::add(x, pos);
let out = attention(
&self.q.forward(&qk),
&self.k.forward(&qk),
&self.v.forward(x),
self.heads,
);
self.o.forward(&out)
}
}
fn columns(x: &Tensor, at: usize, n: usize) -> Tensor {
let (t, c) = x.tc();
let mut data = Vec::with_capacity(t * n);
for row in x.data.chunks_exact(c) {
data.extend_from_slice(&row[at..at + n]);
}
Tensor::new(vec![t, n], data)
}
pub(crate) fn attention(q: &Tensor, k: &Tensor, v: &Tensor, heads: usize) -> Tensor {
let (t, c) = q.tc();
let d = c / heads;
let scale = (d as f32).sqrt().recip();
let mut out = vec![0f32; t * c];
for h in 0..heads {
let at = h * d;
let kt = ops::transpose(&columns(k, at, d));
let mut s = ops::matmul(&columns(q, at, d), &kt);
for x in &mut s.data {
*x *= scale;
}
ops::softmax_rows(&mut s);
let o = ops::matmul(&s, &columns(v, at, d));
for (dst, src) in out.chunks_exact_mut(c).zip(o.data.chunks_exact(d)) {
dst[at..at + d].copy_from_slice(src);
}
}
Tensor::new(vec![t, c], out)
}
pub(crate) fn sine_pos_2d(h: usize, w: usize, dim: usize, temperature: f64) -> Tensor {
let pd = dim / 4;
let omega: Vec<f64> = (0..pd)
.map(|i| temperature.powf(i as f64 / pd as f64).recip())
.collect();
let mut data = Vec::with_capacity(h * w * dim);
for y in 0..h {
for x in 0..w {
for grid in [y as f64, x as f64] {
data.extend(omega.iter().map(|o| (grid * o).sin() as f32));
data.extend(omega.iter().map(|o| (grid * o).cos() as f32));
}
}
}
Tensor::new(vec![h * w, dim], data)
}
fn bilinear_taps(input: usize, output: usize) -> Vec<(usize, usize, f32)> {
let scale = input as f32 / output as f32;
(0..output)
.map(|o| {
let src = ((o as f32 + 0.5) * scale - 0.5).max(0.0);
let i0 = (src as usize).min(input - 1);
(i0, (i0 + 1).min(input - 1), src - i0 as f32)
})
.collect()
}
pub(crate) fn resize_bilinear(x: &Tensor, oh: usize, ow: usize) -> Tensor {
let (c, h, w) = x.chw();
if (h, w) == (oh, ow) {
return x.clone();
}
let tx = bilinear_taps(w, ow);
let ty = bilinear_taps(h, oh);
let mut out = Vec::with_capacity(c * oh * ow);
for plane in x.data.chunks_exact(h * w) {
for &(y0, y1, ly) in &ty {
let (r0, r1) = (&plane[y0 * w..(y0 + 1) * w], &plane[y1 * w..(y1 + 1) * w]);
out.extend(tx.iter().map(|&(x0, x1, lx)| {
let top = r0[x0] * (1.0 - lx) + r0[x1] * lx;
let bottom = r1[x0] * (1.0 - lx) + r1[x1] * lx;
top * (1.0 - ly) + bottom * ly
}));
}
}
Tensor::new(vec![c, oh, ow], out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn 双线性放大两倍() {
let x = Tensor::new(vec![1, 1, 2], vec![0.0, 1.0]);
let y = resize_bilinear(&x, 2, 4);
assert_eq!(y.shape, [1, 2, 4]);
for (a, b) in y
.data
.iter()
.zip([0.0, 0.25, 0.75, 1.0, 0.0, 0.25, 0.75, 1.0])
{
assert!((a - b).abs() < 1e-6, "{:?}", y.data);
}
}
#[test]
fn 位置编码的布局() {
let p = sine_pos_2d(2, 3, 8, 10000.0);
assert_eq!(p.shape, [6, 8]);
let row = &p.data[5 * 8..6 * 8];
let want = [
1f64.sin(),
0.01f64.sin(),
1f64.cos(),
0.01f64.cos(),
2f64.sin(),
0.02f64.sin(),
2f64.cos(),
0.02f64.cos(),
];
for (a, b) in row.iter().zip(want) {
assert!((*a as f64 - b).abs() < 1e-6, "{row:?}");
}
}
#[test]
fn 注意力按头拼回() {
let q = Tensor::new(vec![2, 4], vec![50., 0., 0., 50., 0., 50., 50., 0.]);
let v = Tensor::new(vec![2, 4], vec![1., 2., 3., 4., 5., 6., 7., 8.]);
let y = attention(&q, &q, &v, 2);
for (a, b) in y.data.iter().zip(&v.data) {
assert!((a - b).abs() < 1e-3, "{:?}", y.data);
}
}
}