1use std::ffi::c_void;
9use std::sync::OnceLock;
10
11use anyhow::{Result, bail};
12use atlas_core::registry::{AtlasRegistry, RawCudaFunc};
13use cudarc::driver::LaunchConfig;
14
15unsafe extern "C" {
17 fn cuMemAlloc_v2(dptr: *mut u64, bytesize: usize) -> i32;
18 fn cuMemFree_v2(dptr: u64) -> i32;
19 fn cuMemsetD8Async(dst: u64, value: u8, n: usize, stream: u64) -> i32;
20 fn cuStreamSynchronize(stream: u64) -> i32;
21 fn cuEventCreate(event: *mut u64, flags: u32) -> i32;
22 fn cuEventRecord(event: u64, stream: u64) -> i32;
23 fn cuEventSynchronize(event: u64) -> i32;
24 fn cuEventElapsedTime(ms: *mut f32, start: u64, end: u64) -> i32;
25 fn cuEventDestroy_v2(event: u64) -> i32;
26}
27
28static INIT: OnceLock<&'static AtlasRegistry> = OnceLock::new();
33
34pub fn ensure_registry() -> &'static AtlasRegistry {
36 INIT.get_or_init(|| {
37 let ptx = atlas_kernels::ptx_modules();
38 let registry =
39 AtlasRegistry::load(0, &ptx).expect("AtlasRegistry load failed — is GPU available?");
40 Box::leak(Box::new(registry)) as &'static AtlasRegistry
41 })
42}
43
44pub fn gpu_alloc_zeroed(stream: u64, bytes: usize) -> Result<u64> {
46 let mut dptr: u64 = 0;
47 let status = unsafe { cuMemAlloc_v2(&mut dptr, bytes) };
48 if status != 0 {
49 bail!("cuMemAlloc_v2 failed: status {status}, {bytes} bytes");
50 }
51 let status = unsafe { cuMemsetD8Async(dptr, 0, bytes, stream) };
52 if status != 0 {
53 bail!("cuMemsetD8Async failed: status {status}");
54 }
55 Ok(dptr)
56}
57
58pub fn gpu_free(dptr: u64) {
60 if dptr != 0 {
61 unsafe { cuMemFree_v2(dptr) };
62 }
63}
64
65pub fn gpu_sync(stream: u64) -> Result<()> {
67 let status = unsafe { cuStreamSynchronize(stream) };
68 if status != 0 {
69 bail!("cuStreamSynchronize failed: status {status}");
70 }
71 Ok(())
72}
73
74pub fn get_kernel(registry: &'static AtlasRegistry, module: &str, func: &str) -> RawCudaFunc {
82 let cache = OnceLock::new();
83 registry
84 .raw_function_cached(&cache, module, func)
85 .unwrap_or_else(|e| panic!("Kernel {module}::{func} not found: {e}"))
86}
87
88pub unsafe fn launch(
93 registry: &AtlasRegistry,
94 func: RawCudaFunc,
95 grid: (u32, u32, u32),
96 block: (u32, u32, u32),
97 shared_mem: u32,
98 stream: u64,
99 params: &mut [*mut c_void],
100) -> Result<()> {
101 let cfg = LaunchConfig {
102 grid_dim: grid,
103 block_dim: block,
104 shared_mem_bytes: shared_mem,
105 };
106 unsafe { registry.launch_on_stream(func, cfg, stream, params) }
107 .map_err(|e| anyhow::anyhow!("Kernel launch failed: {e}"))
108}
109
110pub fn bench_kernel_ms(
114 stream: u64,
115 warmup: usize,
116 iters: usize,
117 mut kernel_fn: impl FnMut(),
118) -> f32 {
119 let mut start: u64 = 0;
121 let mut end: u64 = 0;
122 unsafe {
123 cuEventCreate(&mut start, 0);
124 cuEventCreate(&mut end, 0);
125 }
126
127 let mut best_ms = f32::MAX;
128
129 for _ in 0..3 {
130 for _ in 0..warmup {
132 kernel_fn();
133 }
134 gpu_sync(stream).unwrap();
135
136 unsafe { cuEventRecord(start, stream) };
138 for _ in 0..iters {
139 kernel_fn();
140 }
141 unsafe { cuEventRecord(end, stream) };
142 unsafe { cuEventSynchronize(end) };
143
144 let mut elapsed_ms: f32 = 0.0;
145 unsafe { cuEventElapsedTime(&mut elapsed_ms, start, end) };
146 let per_iter = elapsed_ms / iters as f32;
147 if per_iter < best_ms {
148 best_ms = per_iter;
149 }
150 }
151
152 unsafe {
153 cuEventDestroy_v2(start);
154 cuEventDestroy_v2(end);
155 }
156
157 best_ms
158}