1mod backend;
20mod buffer;
21mod driver;
22mod exec;
23mod exec_ops;
24mod ops;
25mod pool;
26mod worker;
27
28use std::ffi::{CStr, c_char, c_int, c_void};
29use std::ptr::null_mut;
30
31pub use backend::{Costs, GpuBackend};
32pub use buffer::{DeviceBuffer, Timer};
33pub use exec::{BatchExec, Ctx, DevTensor, Owner};
34pub use ops::ConvShape;
35pub use pool::{OOM_TAG, is_oom};
36pub use worker::{SharedWorker, WaitError, Worker};
37
38use driver::{
39 ATTR_CC_MAJOR, ATTR_CC_MINOR, Driver, Handle, JIT_ERROR_LOG_BUFFER,
40 JIT_ERROR_LOG_BUFFER_SIZE_BYTES,
41};
42
43const PTX_SM70: &str = concat!(include_str!("../ptx/kernels_sm70.ptx"), "\0");
45const PTX_SM61: &str = concat!(include_str!("../ptx/kernels_sm61.ptx"), "\0");
47
48pub(crate) struct Kernels {
50 pub sgemm: Handle,
51 pub im2col: Handle,
52 pub depthwise: Handle,
53 pub add_bias: Handle,
54 pub relu: Handle,
55 pub silu: Handle,
56 pub gelu: Handle,
57 pub hardsigmoid: Handle,
58 pub sigmoid: Handle,
59 pub se_gate: Handle,
60 pub zero_cols: Handle,
61 pub pad_rb: Handle,
62 pub pool_max: Handle,
63 pub pool_avg: Handle,
64 pub global_avg: Handle,
65 pub scale_ch: Handle,
66 pub add2: Handle,
67 pub copy_part: Handle,
68 pub interp_nearest: Handle,
69 pub chw_to_seq: Handle,
70 pub conv_transpose: Handle,
71 pub layer_norm: Handle,
72 pub attn_split: Handle,
73 pub attn_softmax: Handle,
74 pub attn_merge: Handle,
75 pub ctc_digest: Handle,
76 pub pad_all: Handle,
77 pub crop_cols: Handle,
78}
79
80pub struct Gpu {
82 drv: Driver,
83 dev: c_int,
84 index: usize,
85 ctx: Handle,
86 module: Handle,
87 k: Kernels,
88 name: String,
89 cc: (i32, i32),
90 ptx: &'static Ptx,
91}
92
93unsafe impl Send for Gpu {}
96unsafe impl Sync for Gpu {}
97
98pub fn device_count() -> usize {
101 let Ok(drv) = Driver::load() else { return 0 };
102 let mut count = 0;
103 if unsafe { (drv.init)(0) } != 0 || unsafe { (drv.device_get_count)(&mut count) } != 0 {
104 return 0;
105 }
106 count.max(0) as usize
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
111pub struct GpuInfo {
112 pub index: usize,
114 pub name: String,
116 pub compute_capability: (i32, i32),
118 pub memory_total: u64,
120 pub memory_free: u64,
122 pub driver_version: Option<String>,
124 pub cuda_version: (i32, i32),
126 pub ptx: &'static str,
128}
129
130pub fn disabled_by_env() -> Option<String> {
133 let v = std::env::var("PAGESIFT_GPU").ok()?;
134 v.eq_ignore_ascii_case("off")
135 .then(|| "GPU disabled by PAGESIFT_GPU=off".to_owned())
136}
137
138struct Picked {
140 drv: Driver,
141 dev: c_int,
142 cc: (i32, i32),
143 ptx: &'static Ptx,
144 name: String,
145}
146
147fn pick(index: usize) -> Result<Picked, String> {
149 if let Some(why) = disabled_by_env() {
150 return Err(why);
151 }
152 let drv = Driver::load()?;
153 drv.check(unsafe { (drv.init)(0) }, "cuInit")?;
154 let mut count = 0;
155 drv.check(
156 unsafe { (drv.device_get_count)(&mut count) },
157 "cuDeviceGetCount",
158 )?;
159 if count == 0 {
160 return Err("no NVIDIA GPU found".into());
161 }
162 if index >= count as usize {
163 return Err(format!("no GPU {index}: this machine has {count}"));
164 }
165 let mut dev = 0;
166 drv.check(
167 unsafe { (drv.device_get)(&mut dev, index as c_int) },
168 "cuDeviceGet",
169 )?;
170 let attr = |a| -> Result<i32, String> {
171 let mut v = 0;
172 drv.check(
173 unsafe { (drv.device_get_attribute)(&mut v, a, dev) },
174 "cuDeviceGetAttribute",
175 )?;
176 Ok(v)
177 };
178 let cc = (attr(ATTR_CC_MAJOR)?, attr(ATTR_CC_MINOR)?);
179 let ptx = select_ptx(cc)?;
180 let name = device_name(&drv, dev);
181 Ok(Picked {
182 drv,
183 dev,
184 cc,
185 ptx,
186 name,
187 })
188}
189
190fn memory_info(drv: &Driver) -> Result<(u64, u64), String> {
192 let (mut free, mut total) = (0usize, 0usize);
193 drv.check(
194 unsafe { (drv.mem_get_info)(&mut free, &mut total) },
195 "cuMemGetInfo",
196 )?;
197 Ok((free as u64, total as u64))
198}
199
200fn build_info(
202 drv: &Driver,
203 index: usize,
204 name: &str,
205 cc: (i32, i32),
206 ptx: &Ptx,
207) -> Result<GpuInfo, String> {
208 let (memory_free, memory_total) = memory_info(drv)?;
209 let mut v = 0;
210 drv.check(
211 unsafe { (drv.driver_get_version)(&mut v) },
212 "cuDriverGetVersion",
213 )?;
214 let cuda_version = (v / 1000, (v % 1000) / 10);
216 Ok(GpuInfo {
217 index,
218 name: name.to_owned(),
219 compute_capability: cc,
220 memory_total,
221 memory_free,
222 driver_version: driver::nvml_driver_version(),
223 cuda_version,
224 ptx: ptx.name,
225 })
226}
227
228pub fn probe(index: usize) -> Result<GpuInfo, String> {
231 let p = pick(index)?;
232 let mut ctx = null_mut();
233 p.drv.check(
234 unsafe { (p.drv.primary_ctx_retain)(&mut ctx, p.dev) },
235 "cuDevicePrimaryCtxRetain",
236 )?;
237 let r = p
238 .drv
239 .check(unsafe { (p.drv.ctx_set_current)(ctx) }, "cuCtxSetCurrent")
240 .and_then(|()| build_info(&p.drv, index, &p.name, p.cc, p.ptx));
241 unsafe { (p.drv.primary_ctx_release)(p.dev) };
242 r
243}
244
245impl Gpu {
246 pub fn open() -> Result<Gpu, String> {
248 Gpu::open_index(0)
249 }
250
251 pub fn open_index(index: usize) -> Result<Gpu, String> {
254 let Picked {
255 drv,
256 dev,
257 cc,
258 ptx,
259 name,
260 } = pick(index)?;
261 let mut ctx = null_mut();
262 drv.check(
263 unsafe { (drv.primary_ctx_retain)(&mut ctx, dev) },
264 "cuDevicePrimaryCtxRetain",
265 )?;
266 let mut gpu = Gpu {
268 drv,
269 dev,
270 index,
271 ctx,
272 module: null_mut(),
273 k: Kernels::null(),
274 name,
275 cc,
276 ptx,
277 };
278 gpu.bind()?;
279 gpu.module = load_module(&gpu.drv, ptx.source)?;
280 gpu.k = Kernels::get(&gpu.drv, gpu.module)?;
281 Ok(gpu)
282 }
283
284 pub fn name(&self) -> &str {
286 &self.name
287 }
288
289 pub fn compute_capability(&self) -> (i32, i32) {
291 self.cc
292 }
293
294 pub fn memory_free(&self) -> Result<u64, String> {
296 self.bind()?;
297 Ok(memory_info(&self.drv)?.0)
298 }
299
300 pub fn info(&self) -> Result<GpuInfo, String> {
302 self.bind()?;
303 build_info(&self.drv, self.index, &self.name, self.cc, self.ptx)
304 }
305
306 pub fn synchronize(&self) -> Result<(), String> {
308 self.bind()?;
309 self.drv
310 .check(unsafe { (self.drv.ctx_synchronize)() }, "cuCtxSynchronize")
311 }
312
313 pub(crate) fn bind(&self) -> Result<(), String> {
315 self.drv.check(
316 unsafe { (self.drv.ctx_set_current)(self.ctx) },
317 "cuCtxSetCurrent",
318 )
319 }
320}
321
322impl Drop for Gpu {
323 fn drop(&mut self) {
324 let _ = self.bind();
326 if !self.module.is_null() {
327 unsafe { (self.drv.module_unload)(self.module) };
328 }
329 unsafe { (self.drv.primary_ctx_release)(self.dev) };
330 }
331}
332
333struct Ptx {
335 name: &'static str,
336 source: &'static str,
337}
338
339static SM70: Ptx = Ptx {
340 name: "sm_70",
341 source: PTX_SM70,
342};
343static SM61: Ptx = Ptx {
344 name: "sm_61",
345 source: PTX_SM61,
346};
347
348fn select_ptx(cc: (i32, i32)) -> Result<&'static Ptx, String> {
351 match cc {
352 (major, _) if major >= 7 => Ok(&SM70),
354 (6, minor) if minor >= 1 => Ok(&SM61),
355 (major, minor) => Err(format!(
356 "GPU compute capability {major}.{minor} is too old, need at least 6.1 (Pascal)"
357 )),
358 }
359}
360
361fn device_name(drv: &Driver, dev: c_int) -> String {
362 let mut buf = [0 as c_char; 256]; let r = unsafe { (drv.device_get_name)(buf.as_mut_ptr(), buf.len() as c_int, dev) };
364 if r != 0 {
365 return "unknown NVIDIA GPU".into();
366 }
367 unsafe { CStr::from_ptr(buf.as_ptr()) }
368 .to_string_lossy()
369 .into_owned()
370}
371
372fn load_module(drv: &Driver, ptx: &str) -> Result<Handle, String> {
374 let mut log = vec![0u8; 16 * 1024]; let mut opts = [JIT_ERROR_LOG_BUFFER, JIT_ERROR_LOG_BUFFER_SIZE_BYTES];
376 let mut vals = [log.as_mut_ptr() as *mut c_void, log.len() as *mut c_void];
378 let mut module = null_mut();
379 let r = unsafe {
380 (drv.module_load_data_ex)(
381 &mut module,
382 ptx.as_ptr() as *const c_void,
383 opts.len() as u32,
384 opts.as_mut_ptr(),
385 vals.as_mut_ptr(),
386 )
387 };
388 if let Err(e) = drv.check(r, "cuModuleLoadDataEx") {
389 let text = CStr::from_bytes_until_nul(&log)
390 .map(|c| c.to_string_lossy().into_owned())
391 .unwrap_or_default();
392 return Err(format!("{e}; JIT log: {}", text.trim()));
393 }
394 Ok(module)
395}
396
397impl Kernels {
398 fn null() -> Kernels {
399 let n = null_mut();
400 Kernels {
401 sgemm: n,
402 im2col: n,
403 depthwise: n,
404 add_bias: n,
405 relu: n,
406 silu: n,
407 gelu: n,
408 hardsigmoid: n,
409 sigmoid: n,
410 se_gate: n,
411 zero_cols: n,
412 pad_rb: n,
413 pool_max: n,
414 pool_avg: n,
415 global_avg: n,
416 scale_ch: n,
417 add2: n,
418 copy_part: n,
419 interp_nearest: n,
420 chw_to_seq: n,
421 conv_transpose: n,
422 layer_norm: n,
423 attn_split: n,
424 attn_softmax: n,
425 attn_merge: n,
426 ctc_digest: n,
427 pad_all: n,
428 crop_cols: n,
429 }
430 }
431
432 fn get(drv: &Driver, module: Handle) -> Result<Kernels, String> {
433 let f = |name: &CStr| -> Result<Handle, String> {
434 let mut h = null_mut();
435 let what = format!("cuModuleGetFunction({})", name.to_string_lossy());
436 drv.check(
437 unsafe { (drv.module_get_function)(&mut h, module, name.as_ptr()) },
438 &what,
439 )?;
440 Ok(h)
441 };
442 Ok(Kernels {
443 sgemm: f(c"sgemm")?,
444 im2col: f(c"im2col")?,
445 depthwise: f(c"depthwise")?,
446 add_bias: f(c"add_bias")?,
447 relu: f(c"relu")?,
448 silu: f(c"silu")?,
449 gelu: f(c"gelu")?,
450 hardsigmoid: f(c"hardsigmoid")?,
451 sigmoid: f(c"sigmoid")?,
452 se_gate: f(c"se_gate")?,
453 zero_cols: f(c"zero_cols")?,
454 pad_rb: f(c"pad_rb")?,
455 pool_max: f(c"pool_max")?,
456 pool_avg: f(c"pool_avg")?,
457 global_avg: f(c"global_avg")?,
458 scale_ch: f(c"scale_ch")?,
459 add2: f(c"add2")?,
460 copy_part: f(c"copy_part")?,
461 interp_nearest: f(c"interp_nearest")?,
462 chw_to_seq: f(c"chw_to_seq")?,
463 conv_transpose: f(c"conv_transpose")?,
464 layer_norm: f(c"layer_norm")?,
465 attn_split: f(c"attn_split")?,
466 attn_softmax: f(c"attn_softmax")?,
467 attn_merge: f(c"attn_merge")?,
468 ctc_digest: f(c"ctc_digest")?,
469 pad_all: f(c"pad_all")?,
470 crop_cols: f(c"crop_cols")?,
471 })
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
481 fn bad_ptx_is_an_error() {
482 let Ok(g) = Gpu::open() else { return };
483 let e = load_module(&g.drv, "this is not ptx\0").err().unwrap();
484 assert!(e.contains("cuModuleLoadDataEx"), "{e}");
485 }
486
487 #[test]
488 fn ptx_by_compute_capability() {
489 let src = |cc| select_ptx(cc).map(|p| p.source);
490 assert!(src((8, 6)).unwrap().contains("\n.target sm_70\n"));
491 assert!(src((7, 0)).unwrap().contains("\n.target sm_70\n"));
492 assert!(src((6, 1)).unwrap().contains("\n.target sm_61\n"));
493 assert!(src((6, 2)).unwrap().contains("\n.target sm_61\n"));
494 assert_eq!(select_ptx((6, 1)).unwrap().name, "sm_61");
495 assert_eq!(select_ptx((8, 6)).unwrap().name, "sm_70");
496 assert!(src((6, 0)).is_err());
497 assert!(src((5, 2)).is_err());
498 assert!(
499 PTX_SM61.is_ascii() && PTX_SM70.is_ascii(),
500 "ptxas 只收 ASCII"
501 );
502 assert!(PTX_SM61.ends_with('\0') && PTX_SM70.ends_with('\0'));
503 }
504}