use pagesift_infer::{Activation, Tensor};
use crate::Gpu;
use crate::buffer::{Args, DeviceBuffer};
use crate::driver::DevPtr;
pub(crate) const BLOCK_1D: u32 = 256; const GEMM_TILE: usize = 64; const GEMM_SIDE: u32 = 16; const GRID_YZ_MAX: usize = 65535;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConvShape {
pub c: usize,
pub h: usize,
pub w: usize,
pub o: usize,
pub kh: usize,
pub kw: usize,
pub stride: (usize, usize),
pub pad: (usize, usize),
pub dilation: (usize, usize),
pub groups: usize,
}
impl ConvShape {
pub fn out_hw(&self) -> (usize, usize) {
let oh =
(self.h + 2 * self.pad.0 - self.dilation.0 * (self.kh - 1) - 1) / self.stride.0 + 1;
let ow =
(self.w + 2 * self.pad.1 - self.dilation.1 * (self.kw - 1) - 1) / self.stride.1 + 1;
(oh, ow)
}
pub(crate) fn needs_col(&self) -> bool {
let depthwise = self.c == self.groups;
let direct = self.kh == 1 && self.kw == 1 && self.stride == (1, 1) && self.pad == (0, 0);
!depthwise && !direct
}
pub(crate) fn implicit(&self) -> bool {
self.groups == 1
&& self.stride == (1, 1)
&& self.dilation == (1, 1)
&& self.c > 1
&& self.kh * self.kw > 1
}
pub(crate) fn col_len(&self) -> usize {
let (oh, ow) = self.out_hw();
self.c * self.kh * self.kw * oh * ow
}
fn check(&self) {
assert!(
self.groups > 0 && self.c.is_multiple_of(self.groups),
"卷积输入通道 {} 不能被 groups {} 整除",
self.c,
self.groups
);
assert_eq!(
self.o % self.groups,
0,
"输出通道 {} 不能被 groups {} 整除",
self.o,
self.groups
);
assert!(
self.stride.0 > 0 && self.stride.1 > 0 && self.dilation.0 > 0 && self.dilation.1 > 0
);
assert!(self.kh > 0 && self.kw > 0, "卷积核为空");
assert!(
self.h + 2 * self.pad.0 > self.dilation.0 * (self.kh - 1)
&& self.w + 2 * self.pad.1 > self.dilation.1 * (self.kw - 1),
"卷积核比补边后的输入还大"
);
}
}
fn bytes_of(n: usize) -> u64 {
crate::pool::bytes(n)
}
pub(crate) fn blocks_1d(n: usize) -> u32 {
u32::try_from(n.div_ceil(BLOCK_1D as usize)).expect("一维网格超出 u32")
}
impl Gpu {
#[allow(clippy::too_many_arguments)]
pub(crate) fn sgemm_raw(
&self,
a: DevPtr,
b: DevPtr,
c: DevPtr,
(m, n, k): (usize, usize, usize),
batch: usize,
strides: (usize, usize, usize),
) -> Result<(), String> {
self.sgemm_ext(a, b, c, (m, n, k), n, false, batch, strides)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn sgemm_ext(
&self,
a: DevPtr,
b: DevPtr,
c: DevPtr,
(m, n, k): (usize, usize, usize),
ldb: usize,
accumulate: bool,
batch: usize,
(sa, sb, sc): (usize, usize, usize),
) -> Result<(), String> {
let gy = m.div_ceil(GEMM_TILE);
if gy > GRID_YZ_MAX || batch > GRID_YZ_MAX {
return Err(format!("SGEMM grid too large: M = {m}, batch {batch}"));
}
let grid = (
u32::try_from(n.div_ceil(GEMM_TILE)).map_err(|e| e.to_string())?,
gy as u32,
batch as u32,
);
let args = Args::default()
.raw_ptr(a)
.raw_ptr(b)
.raw_ptr(c)
.u32(m)
.u32(n)
.u32(k)
.u32(ldb)
.u32(accumulate as usize)
.u64(sa)
.u64(sb)
.u64(sc);
self.launch(self.k.sgemm, grid, (GEMM_SIDE, GEMM_SIDE, 1), args)
}
pub fn gemm_device(
&self,
a: &DeviceBuffer<'_>,
b: &DeviceBuffer<'_>,
c: &mut DeviceBuffer<'_>,
m: usize,
n: usize,
k: usize,
) -> Result<(), String> {
assert!(
a.len() == m * k && b.len() == k * n && c.len() == m * n,
"矩阵乘的显存长度与 m、n、k 不符"
);
self.sgemm_raw(a.ptr(), b.ptr(), c.ptr(), (m, n, k), 1, (0, 0, 0))
}
pub fn conv2d_device(
&self,
x: &DeviceBuffer<'_>,
w: &DeviceBuffer<'_>,
bias: Option<&DeviceBuffer<'_>>,
s: &ConvShape,
) -> Result<DeviceBuffer<'_>, String> {
let (oh, ow) = s.out_hw();
let mut out = self.alloc(s.o * oh * ow)?;
self.conv2d_device_into(x, w, bias, s, &mut out)?;
Ok(out)
}
pub fn conv2d_device_into(
&self,
x: &DeviceBuffer<'_>,
w: &DeviceBuffer<'_>,
bias: Option<&DeviceBuffer<'_>>,
s: &ConvShape,
out: &mut DeviceBuffer<'_>,
) -> Result<(), String> {
s.check();
let cg = s.c / s.groups;
assert_eq!(x.len(), s.c * s.h * s.w, "输入长度与形状不符");
assert_eq!(w.len(), s.o * cg * s.kh * s.kw, "权重长度与形状不符");
assert!(
bias.is_none_or(|b| b.len() == s.o),
"偏置长度与输出通道数不符"
);
let (oh, ow) = s.out_hw();
let ohw = oh * ow;
assert_eq!(out.len(), s.o * ohw, "输出显存长度与形状不符");
let col_len = s.c * s.kh * s.kw * ohw;
if out.len() > u32::MAX as usize || col_len > u32::MAX as usize {
return Err(format!(
"convolution too large: {} output and {col_len} unfolded elements exceed 2^32",
out.len()
));
}
let col = if s.needs_col() {
Some(self.alloc(col_len)?)
} else {
None
};
self.conv_raw(
x.ptr(),
w.ptr(),
bias.map(|b| b.ptr()),
s,
1,
out.ptr(),
col.as_ref().map(|c| c.ptr()),
)?;
drop(col);
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn conv_raw(
&self,
x: DevPtr,
w: DevPtr,
bias: Option<DevPtr>,
s: &ConvShape,
n: usize,
out: DevPtr,
col: Option<DevPtr>,
) -> Result<(), String> {
s.check();
let cg = s.c / s.groups;
let og = s.o / s.groups;
let (oh, ow) = s.out_hw();
let ohw = oh * ow;
let total = n * s.o * ohw;
let geom = |a: Args| {
a.u32(s.h)
.u32(s.w)
.u32(s.kh)
.u32(s.kw)
.u32(s.stride.0)
.u32(s.stride.1)
.u32(s.pad.0)
.u32(s.pad.1)
.u32(s.dilation.0)
.u32(s.dilation.1)
.u32(oh)
.u32(ow)
};
if cg == 1 {
let args = Args::default()
.raw_ptr(x)
.raw_ptr(w)
.raw_ptr(bias.unwrap_or(0))
.raw_ptr(out)
.u32(n * s.o)
.u32(og)
.u32(s.o)
.u32(bias.is_some() as usize);
return self.launch(
self.k.depthwise,
(blocks_1d(total), 1, 1),
(BLOCK_1D, 1, 1),
geom(args),
);
}
let kk = cg * s.kh * s.kw; let b = match col {
None => x, Some(col) => {
let len = n * s.col_len();
let args = geom(Args::default().raw_ptr(x).raw_ptr(col).u32(n * s.c));
self.launch(
self.k.im2col,
(blocks_1d(len), 1, 1),
(BLOCK_1D, 1, 1),
args,
)?;
col
}
};
let (sb, sc) = (kk * ohw, og * ohw);
if s.groups == 1 {
self.sgemm_raw(w, b, out, (og, ohw, kk), n, (0, sb, sc))?;
} else {
for z in 0..n {
let off = |per: usize| bytes_of(z * s.groups * per);
self.sgemm_raw(
w,
b + off(sb),
out + off(sc),
(og, ohw, kk),
s.groups,
(og * kk, sb, sc),
)?;
}
}
if let Some(bias) = bias {
let args = Args::default()
.raw_ptr(out)
.raw_ptr(bias)
.u32(ohw)
.u32(s.o)
.u32(total);
self.launch(
self.k.add_bias,
(blocks_1d(total), 1, 1),
(BLOCK_1D, 1, 1),
args,
)?;
}
Ok(())
}
pub fn activate_device(&self, x: &mut DeviceBuffer<'_>, act: Activation) -> Result<(), String> {
let f = match act {
Activation::Identity => return Ok(()),
Activation::Relu => self.k.relu,
Activation::Silu => self.k.silu,
Activation::Gelu => self.k.gelu,
Activation::HardSigmoid => self.k.hardsigmoid,
Activation::Sigmoid => self.k.sigmoid,
};
let args = Args::default().ptr(x).u32(x.len());
self.launch(f, (blocks_1d(x.len()), 1, 1), (BLOCK_1D, 1, 1), args)
}
pub fn add_bias_device(
&self,
x: &mut DeviceBuffer<'_>,
bias: &DeviceBuffer<'_>,
hw: usize,
) -> Result<(), String> {
assert_eq!(x.len(), bias.len() * hw, "偏置长度与张量形状不符");
let args = Args::default()
.ptr(x)
.ptr(bias)
.u32(hw)
.u32(bias.len())
.u32(x.len());
self.launch(
self.k.add_bias,
(blocks_1d(x.len()), 1, 1),
(BLOCK_1D, 1, 1),
args,
)
}
pub fn matmul(&self, a: &Tensor, b: &Tensor) -> Result<Vec<f32>, String> {
let (m, k) = a.tc();
let (kb, n) = b.tc();
assert_eq!(k, kb, "矩阵乘的内维不同");
let (da, db) = (self.upload(&a.data)?, self.upload(&b.data)?);
let mut dc = self.alloc(m * n)?;
self.gemm_device(&da, &db, &mut dc, m, n, k)?;
dc.to_vec()
}
#[allow(clippy::too_many_arguments)]
pub fn conv2d(
&self,
x: &Tensor,
w: &Tensor,
bias: Option<&[f32]>,
stride: (usize, usize),
pad: (usize, usize),
dilation: (usize, usize),
groups: usize,
) -> Result<Vec<f32>, String> {
let (c, h, wd) = x.chw();
assert_eq!(w.shape.len(), 4, "卷积权重要 [O, C/groups, kh, kw]");
let s = ConvShape {
c,
h,
w: wd,
o: w.shape[0],
kh: w.shape[2],
kw: w.shape[3],
stride,
pad,
dilation,
groups,
};
assert_eq!(
c,
w.shape[1] * groups,
"卷积输入通道 {c} 与权重 {}×{groups} 不符",
w.shape[1]
);
let dx = self.upload(&x.data)?;
let dw = self.upload(&w.data)?;
let db = bias.map(|b| self.upload(b)).transpose()?;
self.conv2d_device(&dx, &dw, db.as_ref(), &s)?.to_vec()
}
pub fn activate(&self, x: &mut [f32], act: Activation) -> Result<(), String> {
if act == Activation::Identity {
return Ok(());
}
let mut d = self.upload(x)?;
self.activate_device(&mut d, act)?;
d.copy_to_host(x)
}
}