1use pagesift_infer::{Activation, Tensor};
9
10use crate::Gpu;
11use crate::buffer::{Args, DeviceBuffer};
12use crate::driver::DevPtr;
13
14pub(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)]
25pub struct ConvShape {
26 pub c: usize,
27 pub h: usize,
28 pub w: usize,
29 pub o: usize,
30 pub kh: usize,
31 pub kw: usize,
32 pub stride: (usize, usize),
33 pub pad: (usize, usize),
34 pub dilation: (usize, usize),
35 pub groups: usize,
36}
37
38impl ConvShape {
39 pub fn out_hw(&self) -> (usize, usize) {
41 let oh =
42 (self.h + 2 * self.pad.0 - self.dilation.0 * (self.kh - 1) - 1) / self.stride.0 + 1;
43 let ow =
44 (self.w + 2 * self.pad.1 - self.dilation.1 * (self.kw - 1) - 1) / self.stride.1 + 1;
45 (oh, ow)
46 }
47
48 pub(crate) fn needs_col(&self) -> bool {
50 let depthwise = self.c == self.groups;
51 let direct = self.kh == 1 && self.kw == 1 && self.stride == (1, 1) && self.pad == (0, 0);
52 !depthwise && !direct
53 }
54
55 pub(crate) fn implicit(&self) -> bool {
58 self.groups == 1
59 && self.stride == (1, 1)
60 && self.dilation == (1, 1)
61 && self.c > 1
62 && self.kh * self.kw > 1
63 }
64
65 pub(crate) fn col_len(&self) -> usize {
67 let (oh, ow) = self.out_hw();
68 self.c * self.kh * self.kw * oh * ow
69 }
70
71 fn check(&self) {
73 assert!(
74 self.groups > 0 && self.c.is_multiple_of(self.groups),
75 "卷积输入通道 {} 不能被 groups {} 整除",
76 self.c,
77 self.groups
78 );
79 assert_eq!(
80 self.o % self.groups,
81 0,
82 "输出通道 {} 不能被 groups {} 整除",
83 self.o,
84 self.groups
85 );
86 assert!(
87 self.stride.0 > 0 && self.stride.1 > 0 && self.dilation.0 > 0 && self.dilation.1 > 0
88 );
89 assert!(self.kh > 0 && self.kw > 0, "卷积核为空");
90 assert!(
91 self.h + 2 * self.pad.0 > self.dilation.0 * (self.kh - 1)
92 && self.w + 2 * self.pad.1 > self.dilation.1 * (self.kw - 1),
93 "卷积核比补边后的输入还大"
94 );
95 }
96}
97
98fn bytes_of(n: usize) -> u64 {
100 crate::pool::bytes(n)
101}
102
103pub(crate) fn blocks_1d(n: usize) -> u32 {
104 u32::try_from(n.div_ceil(BLOCK_1D as usize)).expect("一维网格超出 u32")
105}
106
107impl Gpu {
108 #[allow(clippy::too_many_arguments)]
110 pub(crate) fn sgemm_raw(
111 &self,
112 a: DevPtr,
113 b: DevPtr,
114 c: DevPtr,
115 (m, n, k): (usize, usize, usize),
116 batch: usize,
117 strides: (usize, usize, usize),
118 ) -> Result<(), String> {
119 self.sgemm_ext(a, b, c, (m, n, k), n, false, batch, strides)
120 }
121
122 #[allow(clippy::too_many_arguments)]
124 pub(crate) fn sgemm_ext(
125 &self,
126 a: DevPtr,
127 b: DevPtr,
128 c: DevPtr,
129 (m, n, k): (usize, usize, usize),
130 ldb: usize,
131 accumulate: bool,
132 batch: usize,
133 (sa, sb, sc): (usize, usize, usize),
134 ) -> Result<(), String> {
135 let gy = m.div_ceil(GEMM_TILE);
136 if gy > GRID_YZ_MAX || batch > GRID_YZ_MAX {
137 return Err(format!("SGEMM grid too large: M = {m}, batch {batch}"));
138 }
139 let grid = (
140 u32::try_from(n.div_ceil(GEMM_TILE)).map_err(|e| e.to_string())?,
141 gy as u32,
142 batch as u32,
143 );
144 let args = Args::default()
145 .raw_ptr(a)
146 .raw_ptr(b)
147 .raw_ptr(c)
148 .u32(m)
149 .u32(n)
150 .u32(k)
151 .u32(ldb)
152 .u32(accumulate as usize)
153 .u64(sa)
154 .u64(sb)
155 .u64(sc);
156 self.launch(self.k.sgemm, grid, (GEMM_SIDE, GEMM_SIDE, 1), args)
157 }
158
159 pub fn gemm_device(
161 &self,
162 a: &DeviceBuffer<'_>,
163 b: &DeviceBuffer<'_>,
164 c: &mut DeviceBuffer<'_>,
165 m: usize,
166 n: usize,
167 k: usize,
168 ) -> Result<(), String> {
169 assert!(
170 a.len() == m * k && b.len() == k * n && c.len() == m * n,
171 "矩阵乘的显存长度与 m、n、k 不符"
172 );
173 self.sgemm_raw(a.ptr(), b.ptr(), c.ptr(), (m, n, k), 1, (0, 0, 0))
174 }
175
176 pub fn conv2d_device(
178 &self,
179 x: &DeviceBuffer<'_>,
180 w: &DeviceBuffer<'_>,
181 bias: Option<&DeviceBuffer<'_>>,
182 s: &ConvShape,
183 ) -> Result<DeviceBuffer<'_>, String> {
184 let (oh, ow) = s.out_hw();
185 let mut out = self.alloc(s.o * oh * ow)?;
186 self.conv2d_device_into(x, w, bias, s, &mut out)?;
187 Ok(out)
188 }
189
190 pub fn conv2d_device_into(
193 &self,
194 x: &DeviceBuffer<'_>,
195 w: &DeviceBuffer<'_>,
196 bias: Option<&DeviceBuffer<'_>>,
197 s: &ConvShape,
198 out: &mut DeviceBuffer<'_>,
199 ) -> Result<(), String> {
200 s.check();
201 let cg = s.c / s.groups;
202 assert_eq!(x.len(), s.c * s.h * s.w, "输入长度与形状不符");
203 assert_eq!(w.len(), s.o * cg * s.kh * s.kw, "权重长度与形状不符");
204 assert!(
205 bias.is_none_or(|b| b.len() == s.o),
206 "偏置长度与输出通道数不符"
207 );
208 let (oh, ow) = s.out_hw();
209 let ohw = oh * ow;
210 assert_eq!(out.len(), s.o * ohw, "输出显存长度与形状不符");
211 let col_len = s.c * s.kh * s.kw * ohw;
213 if out.len() > u32::MAX as usize || col_len > u32::MAX as usize {
214 return Err(format!(
215 "convolution too large: {} output and {col_len} unfolded elements exceed 2^32",
216 out.len()
217 ));
218 }
219 let col = if s.needs_col() {
220 Some(self.alloc(col_len)?)
221 } else {
222 None
223 };
224 self.conv_raw(
225 x.ptr(),
226 w.ptr(),
227 bias.map(|b| b.ptr()),
228 s,
229 1,
230 out.ptr(),
231 col.as_ref().map(|c| c.ptr()),
232 )?;
233 drop(col);
235 Ok(())
236 }
237
238 #[allow(clippy::too_many_arguments)]
241 pub(crate) fn conv_raw(
242 &self,
243 x: DevPtr,
244 w: DevPtr,
245 bias: Option<DevPtr>,
246 s: &ConvShape,
247 n: usize,
248 out: DevPtr,
249 col: Option<DevPtr>,
250 ) -> Result<(), String> {
251 s.check();
252 let cg = s.c / s.groups;
253 let og = s.o / s.groups;
254 let (oh, ow) = s.out_hw();
255 let ohw = oh * ow;
256 let total = n * s.o * ohw;
257 let geom = |a: Args| {
258 a.u32(s.h)
259 .u32(s.w)
260 .u32(s.kh)
261 .u32(s.kw)
262 .u32(s.stride.0)
263 .u32(s.stride.1)
264 .u32(s.pad.0)
265 .u32(s.pad.1)
266 .u32(s.dilation.0)
267 .u32(s.dilation.1)
268 .u32(oh)
269 .u32(ow)
270 };
271 if cg == 1 {
272 let args = Args::default()
274 .raw_ptr(x)
275 .raw_ptr(w)
276 .raw_ptr(bias.unwrap_or(0))
277 .raw_ptr(out)
278 .u32(n * s.o)
279 .u32(og)
280 .u32(s.o)
281 .u32(bias.is_some() as usize);
282 return self.launch(
283 self.k.depthwise,
284 (blocks_1d(total), 1, 1),
285 (BLOCK_1D, 1, 1),
286 geom(args),
287 );
288 }
289 let kk = cg * s.kh * s.kw; let b = match col {
291 None => x, Some(col) => {
293 let len = n * s.col_len();
294 let args = geom(Args::default().raw_ptr(x).raw_ptr(col).u32(n * s.c));
295 self.launch(
296 self.k.im2col,
297 (blocks_1d(len), 1, 1),
298 (BLOCK_1D, 1, 1),
299 args,
300 )?;
301 col
302 }
303 };
304 let (sb, sc) = (kk * ohw, og * ohw);
306 if s.groups == 1 {
307 self.sgemm_raw(w, b, out, (og, ohw, kk), n, (0, sb, sc))?;
309 } else {
310 for z in 0..n {
312 let off = |per: usize| bytes_of(z * s.groups * per);
313 self.sgemm_raw(
314 w,
315 b + off(sb),
316 out + off(sc),
317 (og, ohw, kk),
318 s.groups,
319 (og * kk, sb, sc),
320 )?;
321 }
322 }
323 if let Some(bias) = bias {
324 let args = Args::default()
325 .raw_ptr(out)
326 .raw_ptr(bias)
327 .u32(ohw)
328 .u32(s.o)
329 .u32(total);
330 self.launch(
331 self.k.add_bias,
332 (blocks_1d(total), 1, 1),
333 (BLOCK_1D, 1, 1),
334 args,
335 )?;
336 }
337 Ok(())
338 }
339
340 pub fn activate_device(&self, x: &mut DeviceBuffer<'_>, act: Activation) -> Result<(), String> {
342 let f = match act {
343 Activation::Identity => return Ok(()),
344 Activation::Relu => self.k.relu,
345 Activation::Silu => self.k.silu,
346 Activation::Gelu => self.k.gelu,
347 Activation::HardSigmoid => self.k.hardsigmoid,
348 Activation::Sigmoid => self.k.sigmoid,
349 };
350 let args = Args::default().ptr(x).u32(x.len());
351 self.launch(f, (blocks_1d(x.len()), 1, 1), (BLOCK_1D, 1, 1), args)
352 }
353
354 pub fn add_bias_device(
356 &self,
357 x: &mut DeviceBuffer<'_>,
358 bias: &DeviceBuffer<'_>,
359 hw: usize,
360 ) -> Result<(), String> {
361 assert_eq!(x.len(), bias.len() * hw, "偏置长度与张量形状不符");
362 let args = Args::default()
363 .ptr(x)
364 .ptr(bias)
365 .u32(hw)
366 .u32(bias.len())
367 .u32(x.len());
368 self.launch(
369 self.k.add_bias,
370 (blocks_1d(x.len()), 1, 1),
371 (BLOCK_1D, 1, 1),
372 args,
373 )
374 }
375
376 pub fn matmul(&self, a: &Tensor, b: &Tensor) -> Result<Vec<f32>, String> {
378 let (m, k) = a.tc();
379 let (kb, n) = b.tc();
380 assert_eq!(k, kb, "矩阵乘的内维不同");
381 let (da, db) = (self.upload(&a.data)?, self.upload(&b.data)?);
382 let mut dc = self.alloc(m * n)?;
383 self.gemm_device(&da, &db, &mut dc, m, n, k)?;
384 dc.to_vec()
385 }
386
387 #[allow(clippy::too_many_arguments)]
390 pub fn conv2d(
391 &self,
392 x: &Tensor,
393 w: &Tensor,
394 bias: Option<&[f32]>,
395 stride: (usize, usize),
396 pad: (usize, usize),
397 dilation: (usize, usize),
398 groups: usize,
399 ) -> Result<Vec<f32>, String> {
400 let (c, h, wd) = x.chw();
401 assert_eq!(w.shape.len(), 4, "卷积权重要 [O, C/groups, kh, kw]");
403 let s = ConvShape {
404 c,
405 h,
406 w: wd,
407 o: w.shape[0],
408 kh: w.shape[2],
409 kw: w.shape[3],
410 stride,
411 pad,
412 dilation,
413 groups,
414 };
415 assert_eq!(
416 c,
417 w.shape[1] * groups,
418 "卷积输入通道 {c} 与权重 {}×{groups} 不符",
419 w.shape[1]
420 );
421 let dx = self.upload(&x.data)?;
422 let dw = self.upload(&w.data)?;
423 let db = bias.map(|b| self.upload(b)).transpose()?;
424 self.conv2d_device(&dx, &dw, db.as_ref(), &s)?.to_vec()
425 }
426
427 pub fn activate(&self, x: &mut [f32], act: Activation) -> Result<(), String> {
429 if act == Activation::Identity {
430 return Ok(());
431 }
432 let mut d = self.upload(x)?;
433 self.activate_device(&mut d, act)?;
434 d.copy_to_host(x)
435 }
436}