1#![allow(clippy::missing_safety_doc)]
20use std::ffi::c_void;
21
22use cudarc::runtime::sys::{cudaError_t, cudaStream_t};
23
24#[cfg(feature = "permute_kernels")]
26#[repr(i32)]
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum TensorDataType {
29 F16 = 0,
30 BF16 = 1,
31 F32 = 2,
32 F64 = 3,
33}
34
35#[cfg(feature = "permute_kernels")]
37#[repr(i32)]
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum BlockLayout {
40 NHD = 0,
41 HND = 1,
42}
43
44#[cfg(feature = "permute_kernels")]
45#[allow(dead_code)]
46unsafe extern "C" {
47 fn kvbm_kernels_launch_universal_from_block(
48 universal_ptrs: *const *mut c_void,
49 block_ptrs: *const *const c_void,
50 num_blocks: usize,
51 nh: usize,
52 nl: usize,
53 no: usize,
54 nt: usize,
55 hd: usize,
56 dtype: i32,
57 layout: i32,
58 stream: cudaStream_t,
59 ) -> cudaError_t;
60
61 fn kvbm_kernels_launch_block_from_universal(
62 universal_ptrs: *const *const c_void,
63 block_ptrs: *const *mut c_void,
64 num_blocks: usize,
65 nh: usize,
66 nl: usize,
67 no: usize,
68 nt: usize,
69 hd: usize,
70 dtype: i32,
71 layout: i32,
72 stream: cudaStream_t,
73 ) -> cudaError_t;
74}
75
76#[repr(i32)]
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum MemcpyBatchMode {
80 BatchedWithFallback = 0,
82 FallbackOnly = 1,
84 BatchWithoutFallback = 2,
86}
87
88#[allow(dead_code)]
89unsafe extern "C" {
90 fn kvbm_kernels_launch_vectorized_copy(
91 src_ptrs: *mut *mut c_void,
92 dst_ptrs: *mut *mut c_void,
93 copy_size_bytes: usize,
94 num_pairs: i32,
95 stream: cudaStream_t,
96 ) -> cudaError_t;
97
98 fn kvbm_kernels_memcpy_batch(
99 src_ptrs: *const *const c_void,
100 dst_ptrs: *const *mut c_void,
101 size_per_copy: usize,
102 num_copies: usize,
103 mode: i32,
104 stream: cudaStream_t,
105 ) -> cudaError_t;
106
107 fn kvbm_kernels_has_memcpy_batch_async() -> bool;
108 fn kvbm_kernels_is_stub_build() -> bool;
109}
110
111pub fn is_memcpy_batch_available() -> bool {
116 unsafe { kvbm_kernels_has_memcpy_batch_async() }
117}
118
119pub fn is_using_stubs() -> bool {
136 unsafe { kvbm_kernels_is_stub_build() }
137}
138
139pub unsafe fn memcpy_batch(
155 src_ptrs: *const *const c_void,
156 dst_ptrs: *const *mut c_void,
157 size_per_copy: usize,
158 num_copies: usize,
159 mode: MemcpyBatchMode,
160 stream: cudaStream_t,
161) -> cudaError_t {
162 unsafe {
163 kvbm_kernels_memcpy_batch(
164 src_ptrs,
165 dst_ptrs,
166 size_per_copy,
167 num_copies,
168 mode as i32,
169 stream,
170 )
171 }
172}
173
174#[cfg(feature = "permute_kernels")]
182#[allow(clippy::too_many_arguments)]
183pub unsafe fn universal_from_block(
184 universal_ptrs: *const *mut c_void,
185 block_ptrs: *const *const c_void,
186 num_blocks: usize,
187 nh: usize,
188 nl: usize,
189 no: usize,
190 nt: usize,
191 hd: usize,
192 dtype: TensorDataType,
193 layout: BlockLayout,
194 stream: cudaStream_t,
195) -> cudaError_t {
196 unsafe {
197 kvbm_kernels_launch_universal_from_block(
198 universal_ptrs,
199 block_ptrs,
200 num_blocks,
201 nh,
202 nl,
203 no,
204 nt,
205 hd,
206 dtype as i32,
207 layout as i32,
208 stream,
209 )
210 }
211}
212
213#[cfg(feature = "permute_kernels")]
215#[allow(clippy::too_many_arguments)]
216pub unsafe fn block_from_universal(
217 universal_ptrs: *const *const c_void,
218 block_ptrs: *const *mut c_void,
219 num_blocks: usize,
220 nh: usize,
221 nl: usize,
222 no: usize,
223 nt: usize,
224 hd: usize,
225 dtype: TensorDataType,
226 layout: BlockLayout,
227 stream: cudaStream_t,
228) -> cudaError_t {
229 unsafe {
230 kvbm_kernels_launch_block_from_universal(
231 universal_ptrs,
232 block_ptrs,
233 num_blocks,
234 nh,
235 nl,
236 no,
237 nt,
238 hd,
239 dtype as i32,
240 layout as i32,
241 stream,
242 )
243 }
244}
245
246pub unsafe fn vectorized_copy(
271 src_ptrs: *mut *mut c_void,
272 dst_ptrs: *mut *mut c_void,
273 copy_size_bytes: usize,
274 num_pairs: i32,
275 stream: cudaStream_t,
276) -> cudaError_t {
277 unsafe {
278 kvbm_kernels_launch_vectorized_copy(src_ptrs, dst_ptrs, copy_size_bytes, num_pairs, stream)
279 }
280}
281
282#[cfg(all(
287 test,
288 feature = "testing-cuda",
289 feature = "permute_kernels",
290 not(stub_kernels)
291))]
292mod tests {
293 use super::*;
294 use cudarc::driver::result::memset_d8_async;
295 use cudarc::driver::{CudaContext, CudaSlice, DevicePtr, DevicePtrMut, DriverError};
296 use cudarc::runtime::sys as cuda_runtime;
297
298 #[test]
299 fn universal_roundtrip() -> Result<(), DriverError> {
300 let device_count = match CudaContext::device_count() {
301 Ok(count) => count,
302 Err(_) => return Ok(()),
303 };
304 if device_count <= 0 {
305 return Ok(());
306 }
307
308 let ctx = CudaContext::new(0)?;
309 let stream = ctx.default_stream();
310 let stream_raw = stream.cu_stream() as cuda_runtime::cudaStream_t;
311
312 let nh = 2usize;
313 let nl = 2usize;
314 let no = 2usize;
315 let nt = 3usize;
316 let hd = 4usize;
317 let inner = nt * nh * hd;
318 let chunk_count = nl * no;
319 let block_volume = nh * nl * no * nt * hd;
320 let num_blocks = 2usize;
321
322 let dtype = TensorDataType::F32;
323 let layout = BlockLayout::NHD;
324
325 let mut host_block_chunks: Vec<Vec<Vec<f32>>> = Vec::with_capacity(num_blocks);
326 let mut block_slices: Vec<Vec<CudaSlice<f32>>> = Vec::with_capacity(num_blocks);
327 let mut block_ptr_values: Vec<usize> = Vec::with_capacity(num_blocks * chunk_count);
328
329 for block_idx in 0..num_blocks {
330 let mut host_chunks_for_block = Vec::with_capacity(chunk_count);
331 let mut slices_for_block = Vec::with_capacity(chunk_count);
332 for chunk_idx in 0..chunk_count {
333 let global_idx = block_idx * chunk_count + chunk_idx;
334 let mut host_chunk = Vec::with_capacity(inner);
335 for offset in 0..inner {
336 host_chunk.push((global_idx * inner + offset) as f32 + 0.25f32);
337 }
338 let slice = stream.clone_htod(&host_chunk)?;
339 {
340 let (ptr_raw, _guard) = slice.device_ptr(&stream);
341 block_ptr_values.push(ptr_raw as usize);
342 }
343 slices_for_block.push(slice);
344 host_chunks_for_block.push(host_chunk);
345 }
346 block_slices.push(slices_for_block);
347 host_block_chunks.push(host_chunks_for_block);
348 }
349
350 let block_ptrs = stream.clone_htod(block_ptr_values.as_slice())?;
351
352 let mut universal_slices = Vec::with_capacity(num_blocks);
353 let mut universal_ptr_values = Vec::with_capacity(num_blocks);
354 for _ in 0..num_blocks {
355 let mut slice = unsafe { stream.alloc::<f32>(block_volume)? };
356 {
357 let (ptr_raw, _guard) = slice.device_ptr_mut(&stream);
358 universal_ptr_values.push(ptr_raw as usize);
359 unsafe {
360 memset_d8_async(
361 ptr_raw,
362 0xDE,
363 block_volume * std::mem::size_of::<f32>(),
364 stream.cu_stream(),
365 )?;
366 }
367 }
368 universal_slices.push(slice);
369 }
370 let universal_ptrs = stream.clone_htod(universal_ptr_values.as_slice())?;
371
372 {
374 let (block_ptrs_raw, _block_guard) = block_ptrs.device_ptr(&stream);
375 let block_ptrs_ptr = block_ptrs_raw as usize as *const *const c_void;
376 let (universal_ptrs_raw, _univ_guard) = universal_ptrs.device_ptr(&stream);
377 let universal_ptrs_ptr = universal_ptrs_raw as usize as *const *mut c_void;
378
379 let status = unsafe {
380 super::universal_from_block(
381 universal_ptrs_ptr,
382 block_ptrs_ptr,
383 num_blocks,
384 nh,
385 nl,
386 no,
387 nt,
388 hd,
389 dtype,
390 layout,
391 stream_raw,
392 )
393 };
394 assert_eq!(status, cuda_runtime::cudaError::cudaSuccess);
395 }
396 stream.synchronize()?;
397
398 let inner_offset = |nt_idx: usize, nh_idx: usize, hd_idx: usize| match layout {
399 BlockLayout::NHD => ((nt_idx * nh) + nh_idx) * hd + hd_idx,
400 BlockLayout::HND => ((nh_idx * nt) + nt_idx) * hd + hd_idx,
401 };
402
403 for (block_idx, universal_slice) in universal_slices.iter().enumerate().take(num_blocks) {
404 let host_universal = stream.clone_dtoh(universal_slice)?;
405 for nh_idx in 0..nh {
406 for nl_idx in 0..nl {
407 for no_idx in 0..no {
408 for nt_idx in 0..nt {
409 for hd_idx in 0..hd {
410 let universal_index =
411 ((((nh_idx * nl + nl_idx) * no + no_idx) * nt + nt_idx) * hd)
412 + hd_idx;
413 let chunk_idx = nl_idx * no + no_idx;
414 let offset = inner_offset(nt_idx, nh_idx, hd_idx);
415 let expected = ((block_idx * chunk_count + chunk_idx) * inner
416 + offset) as f32
417 + 0.25f32;
418 let value = host_universal[universal_index];
419 assert!(
420 (value - expected).abs() < 1e-5,
421 "universal mismatch block {} [{} {} {} {} {}]: {} vs {}",
422 block_idx,
423 nh_idx,
424 nl_idx,
425 no_idx,
426 nt_idx,
427 hd_idx,
428 value,
429 expected
430 );
431 }
432 }
433 }
434 }
435 }
436 }
437
438 for block in &mut block_slices {
440 for slice in block {
441 let (dptr, _guard) = slice.device_ptr_mut(&stream);
442 unsafe {
443 memset_d8_async(
444 dptr,
445 0xDE,
446 inner * std::mem::size_of::<f32>(),
447 stream.cu_stream(),
448 )?;
449 }
450 }
451 }
452 stream.synchronize()?;
453
454 {
455 let (block_ptrs_raw, _block_guard) = block_ptrs.device_ptr(&stream);
456 let block_ptrs_mut = block_ptrs_raw as usize as *const *mut c_void;
457 let (universal_ptrs_raw, _univ_guard) = universal_ptrs.device_ptr(&stream);
458 let universal_ptrs_const = universal_ptrs_raw as usize as *const *const c_void;
459 let status = unsafe {
460 super::block_from_universal(
461 universal_ptrs_const,
462 block_ptrs_mut,
463 num_blocks,
464 nh,
465 nl,
466 no,
467 nt,
468 hd,
469 dtype,
470 layout,
471 stream_raw,
472 )
473 };
474 assert_eq!(status, cuda_runtime::cudaError::cudaSuccess);
475 }
476 stream.synchronize()?;
477
478 for block_idx in 0..num_blocks {
479 for chunk_idx in 0..chunk_count {
480 let host_chunk = stream.clone_dtoh(&block_slices[block_idx][chunk_idx])?;
481 for (inner_idx, value) in host_chunk.iter().enumerate() {
482 let expected = host_block_chunks[block_idx][chunk_idx][inner_idx];
483 assert!(
484 (value - expected).abs() < 1e-5,
485 "block mismatch block {} chunk {} offset {}: {} vs {}",
486 block_idx,
487 chunk_idx,
488 inner_idx,
489 value,
490 expected
491 );
492 }
493 }
494 }
495
496 Ok(())
497 }
498
499 #[test]
501 fn test_vectorized_copy_aligned() -> Result<(), DriverError> {
502 let device_count = match CudaContext::device_count() {
503 Ok(count) => count,
504 Err(_) => return Ok(()),
505 };
506 if device_count <= 0 {
507 return Ok(());
508 }
509
510 let ctx = CudaContext::new(0)?;
511 let stream = ctx.default_stream();
512 let stream_raw = stream.cu_stream() as cuda_runtime::cudaStream_t;
513
514 let num_pairs = 4;
516 let copy_size = 256usize; let mut src_slices = Vec::with_capacity(num_pairs);
520 let mut src_ptr_values = Vec::with_capacity(num_pairs);
521 let mut expected_data = Vec::with_capacity(num_pairs);
522
523 for i in 0..num_pairs {
524 let data: Vec<u8> = (0..copy_size)
525 .map(|j| ((i * copy_size + j) % 256) as u8)
526 .collect();
527 expected_data.push(data.clone());
528 let slice = stream.clone_htod(&data)?;
529 {
530 let (ptr, _guard) = slice.device_ptr(&stream);
531 src_ptr_values.push(ptr as usize);
532 }
533 src_slices.push(slice);
534 }
535
536 let mut dst_slices = Vec::with_capacity(num_pairs);
538 let mut dst_ptr_values = Vec::with_capacity(num_pairs);
539
540 for _ in 0..num_pairs {
541 let mut slice = unsafe { stream.alloc::<u8>(copy_size)? };
542 {
543 let (ptr, _guard) = slice.device_ptr_mut(&stream);
544 dst_ptr_values.push(ptr as usize);
545 }
546 dst_slices.push(slice);
547 }
548
549 let src_ptrs = stream.clone_htod(&src_ptr_values)?;
551 let dst_ptrs = stream.clone_htod(&dst_ptr_values)?;
552
553 {
555 let (src_ptrs_raw, _src_guard) = src_ptrs.device_ptr(&stream);
556 let (dst_ptrs_raw, _dst_guard) = dst_ptrs.device_ptr(&stream);
557
558 let status = unsafe {
559 super::vectorized_copy(
560 src_ptrs_raw as usize as *mut *mut c_void,
561 dst_ptrs_raw as usize as *mut *mut c_void,
562 copy_size,
563 num_pairs as i32,
564 stream_raw,
565 )
566 };
567 assert_eq!(status, cuda_runtime::cudaError::cudaSuccess);
568 }
569 stream.synchronize()?;
570
571 for i in 0..num_pairs {
573 let result = stream.clone_dtoh(&dst_slices[i])?;
574 assert_eq!(result, expected_data[i], "Mismatch at pair {}", i);
575 }
576
577 Ok(())
578 }
579}