1use anyhow::Result;
27
28use crate::gpu::{DevicePtr, GpuBackend, KernelArg, KernelHandle};
29
30struct ArgKind {
33 is_buffer: bool,
34 byte_len: u16,
37 slot: u32,
40}
41
42pub struct KernelLaunch<'a> {
47 gpu: &'a dyn GpuBackend,
48 kernel: KernelHandle,
49 grid: [u32; 3],
50 block: [u32; 3],
51 shared_mem: u32,
52 storage: Vec<u64>,
55 kinds: Vec<ArgKind>,
57}
58
59impl<'a> KernelLaunch<'a> {
60 pub fn new(gpu: &'a dyn GpuBackend, kernel: KernelHandle) -> Self {
61 Self {
62 gpu,
63 kernel,
64 grid: [1, 1, 1],
65 block: [1, 1, 1],
66 shared_mem: 0,
67 storage: Vec::with_capacity(16),
68 kinds: Vec::with_capacity(16),
69 }
70 }
71
72 pub fn grid(mut self, grid: [u32; 3]) -> Self {
73 self.grid = grid;
74 self
75 }
76
77 pub fn block(mut self, block: [u32; 3]) -> Self {
78 self.block = block;
79 self
80 }
81
82 pub fn shared_mem(mut self, bytes: u32) -> Self {
83 self.shared_mem = bytes;
84 self
85 }
86
87 pub fn arg_ptr(mut self, p: DevicePtr) -> Self {
89 let slot = self.storage.len() as u32;
90 self.storage.push(p.0);
91 self.kinds.push(ArgKind {
92 is_buffer: true,
93 byte_len: 0,
94 slot,
95 });
96 self
97 }
98
99 pub fn arg_tensormap(mut self, map: &[u8; 128]) -> Self {
103 let slot = self.storage.len() as u32;
104 for c in map.chunks(8) {
105 let mut w = [0u8; 8];
106 w.copy_from_slice(c);
107 self.storage.push(u64::from_le_bytes(w));
108 }
109 self.kinds.push(ArgKind {
110 is_buffer: false,
111 byte_len: 128,
112 slot,
113 });
114 self
115 }
116
117 pub fn arg_u32(mut self, v: u32) -> Self {
118 let slot = self.storage.len() as u32;
119 self.storage.push(v as u64);
120 self.kinds.push(ArgKind {
121 is_buffer: false,
122 byte_len: 4,
123 slot,
124 });
125 self
126 }
127
128 pub fn arg_u64(mut self, v: u64) -> Self {
129 let slot = self.storage.len() as u32;
130 self.storage.push(v);
131 self.kinds.push(ArgKind {
132 is_buffer: false,
133 byte_len: 8,
134 slot,
135 });
136 self
137 }
138
139 pub fn arg_i32(mut self, v: i32) -> Self {
140 let slot = self.storage.len() as u32;
141 self.storage.push(v as u32 as u64);
142 self.kinds.push(ArgKind {
143 is_buffer: false,
144 byte_len: 4,
145 slot,
146 });
147 self
148 }
149
150 pub fn arg_f32(mut self, v: f32) -> Self {
151 let slot = self.storage.len() as u32;
152 self.storage.push(f32::to_bits(v) as u64);
153 self.kinds.push(ArgKind {
154 is_buffer: false,
155 byte_len: 4,
156 slot,
157 });
158 self
159 }
160
161 pub fn launch(self, stream: u64) -> Result<()> {
167 let mut args: Vec<KernelArg<'_>> = Vec::with_capacity(self.kinds.len());
168 for kind in self.kinds.iter() {
169 let slot = &self.storage[kind.slot as usize];
170 if kind.is_buffer {
171 args.push(KernelArg::Buffer(DevicePtr(*slot)));
172 } else {
173 let bytes = unsafe {
177 std::slice::from_raw_parts(
178 slot as *const u64 as *const u8,
179 kind.byte_len as usize,
180 )
181 };
182 args.push(KernelArg::Bytes(bytes));
183 }
184 }
185 let r = self.gpu.launch_typed(
186 self.kernel,
187 self.grid,
188 self.block,
189 self.shared_mem,
190 stream,
191 &args,
192 );
193 if r.is_ok() && self.gpu.debug_sync_kernels() {
196 self.gpu.synchronize(stream).map_err(|e| {
197 let bt = std::backtrace::Backtrace::force_capture();
198 anyhow::anyhow!(
199 "METRALE_DEBUG_SYNC_KERNELS: async GPU fault immediately after kernel launch \
200 grid={:?} block={:?} shared_mem={}: {e}\nLAUNCH BACKTRACE:\n{bt}",
201 self.grid,
202 self.block,
203 self.shared_mem
204 )
205 })?;
206 }
207 r
208 }
209}
210
211pub fn div_ceil(a: u32, b: u32) -> u32 {
213 a.div_ceil(b)
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::gpu::mock::MockGpuBackend;
220
221 #[test]
222 fn test_kernel_launch_builder() {
223 let gpu = MockGpuBackend::new();
224 let kernel = gpu.kernel("test", "test_kernel").unwrap();
225
226 let result = KernelLaunch::new(&gpu, kernel)
227 .grid([4, 1, 1])
228 .block([256, 1, 1])
229 .arg_ptr(DevicePtr(0x1000))
230 .arg_u32(42)
231 .arg_f32(1.5)
232 .launch(0);
233
234 assert!(result.is_ok());
235 assert_eq!(gpu.launch_count(), 1);
236 }
237
238 #[test]
241 fn a_128_byte_arg_is_not_truncated() {
242 use crate::gpu::{KernelArg, pack_kernel_args};
243 let map: Vec<u8> = (0..128u16).map(|i| (i * 7 % 251) as u8).collect();
244 let args = [
245 KernelArg::Buffer(DevicePtr(0xDEAD_BEEF)),
246 KernelArg::Bytes(&map),
247 KernelArg::Bytes(&42u32.to_le_bytes()),
248 ];
249 let (storage, starts) = pack_kernel_args(&args);
250
251 assert_eq!(
252 starts.len(),
253 3,
254 "one param entry per argument, not per slot"
255 );
256 assert_eq!(starts, vec![0, 1, 17], "the map occupies slots 1..=16");
257 assert_eq!(storage.len(), 18);
258 assert_eq!(storage[0], 0xDEAD_BEEF);
259
260 let round_trip: Vec<u8> = storage[starts[1]..starts[1] + 16]
261 .iter()
262 .flat_map(|w| w.to_le_bytes())
263 .collect();
264 assert_eq!(
265 round_trip, map,
266 "128-byte struct arg was corrupted or truncated"
267 );
268 assert_eq!(
269 storage[starts[2]] as u32, 42,
270 "the arg after it is still intact"
271 );
272 }
273
274 #[test]
277 fn a_tensormap_arg_does_not_shift_the_args_around_it() {
278 let gpu = MockGpuBackend::new();
279 let kernel = gpu.kernel("test", "tma_kernel").unwrap();
280 let map = [0xABu8; 128];
281
282 let b = KernelLaunch::new(&gpu, kernel)
283 .arg_ptr(DevicePtr(0x1000))
284 .arg_tensormap(&map)
285 .arg_u32(7);
286
287 assert_eq!(b.kinds.len(), 3, "one param entry per arg, not per slot");
288 assert_eq!(b.storage.len(), 1 + 16 + 1);
289 assert_eq!(b.kinds[0].slot, 0);
290 assert_eq!(b.kinds[1].slot, 1);
291 assert_eq!(b.kinds[1].byte_len, 128);
292 assert_eq!(
293 b.kinds[2].slot, 17,
294 "the arg after the map must not be shifted"
295 );
296 assert_eq!(b.storage[b.kinds[2].slot as usize] as u32, 7);
297 assert!(b.launch(0).is_ok());
298 }
299
300 #[test]
302 fn an_empty_byte_arg_still_gets_one_slot() {
303 use crate::gpu::{KernelArg, pack_kernel_args};
304 let (storage, starts) =
305 pack_kernel_args(&[KernelArg::Bytes(&[]), KernelArg::Bytes(&[9u8])]);
306 assert_eq!(starts, vec![0, 1]);
307 assert_eq!(storage.len(), 2);
308 assert_eq!(storage[1], 9);
309 }
310
311 #[test]
312 fn test_div_ceil() {
313 assert_eq!(div_ceil(10, 3), 4);
314 assert_eq!(div_ceil(9, 3), 3);
315 assert_eq!(div_ceil(1, 256), 1);
316 assert_eq!(div_ceil(256, 256), 1);
317 assert_eq!(div_ceil(257, 256), 2);
318 }
319}