1use crate::memory::{DeviceMemoryView, DeviceRead};
4
5use crate::{DeviceSlice, LaunchAsync, LaunchConfig};
6use xlog_core::{Result, ScalarType, Schema, XlogError};
7
8use super::{ilp_credit_kernels, ilp_kernels, ILP_CREDIT_MODULE, ILP_MODULE};
9use crate::memory::{CudaBuffer, CudaColumn, TrackedCudaSlice};
10
11impl super::CudaKernelProvider {
12 fn ilp_i32_view(&self, col: &CudaColumn, num_elements: usize) -> Result<DeviceMemoryView<i32>> {
13 let required_bytes = num_elements * std::mem::size_of::<i32>();
14 if col.num_bytes() < required_bytes {
15 return Err(XlogError::Kernel(format!(
16 "Column has {} bytes but {} required for {} i32 elements",
17 col.num_bytes(),
18 required_bytes,
19 num_elements
20 )));
21 }
22 let ptr = *col.device_ptr();
23 if !(ptr as usize).is_multiple_of(std::mem::align_of::<i32>()) {
24 return Err(XlogError::Kernel(
25 "Column device pointer is not i32-aligned".to_string(),
26 ));
27 }
28 unsafe { col.device_view().slice(..required_bytes).cast() }
30 .ok_or_else(|| XlogError::Kernel("device column cast is invalid".into()))
31 }
32
33 fn ilp_i64_view(&self, col: &CudaColumn, num_elements: usize) -> Result<DeviceMemoryView<i64>> {
34 let required_bytes = num_elements * std::mem::size_of::<i64>();
35 if col.num_bytes() < required_bytes {
36 return Err(XlogError::Kernel(format!(
37 "Column has {} bytes but {} required for {} i64 elements",
38 col.num_bytes(),
39 required_bytes,
40 num_elements
41 )));
42 }
43 let ptr = *col.device_ptr();
44 if !(ptr as usize).is_multiple_of(std::mem::align_of::<i64>()) {
45 return Err(XlogError::Kernel(
46 "Column device pointer is not i64-aligned".to_string(),
47 ));
48 }
49 unsafe { col.device_view().slice(..required_bytes).cast() }
51 .ok_or_else(|| XlogError::Kernel("device column cast is invalid".into()))
52 }
53
54 pub fn build_selected_id_mask(
55 &self,
56 ids_buf: &CudaBuffer,
57 candidate_count: usize,
58 ) -> Result<CudaBuffer> {
59 let selected_len = usize::try_from(ids_buf.num_rows())
60 .map_err(|_| XlogError::Kernel("selected id row count overflow".to_string()))?;
61 let candidate_count_u32 = u32::try_from(candidate_count).map_err(|_| {
62 XlogError::Kernel(format!(
63 "candidate count {} exceeds u32::MAX for strict sparse mask",
64 candidate_count
65 ))
66 })?;
67
68 let mut active_flags = self.memory.alloc::<u32>(candidate_count)?;
69 if candidate_count > 0 {
70 self.device
71 .inner()
72 .memset_zeros(&mut active_flags)
73 .map_err(|e| XlogError::Kernel(format!("zero strict sparse mask: {}", e)))?;
74 }
75
76 if selected_len > 0 {
77 let selected_len_u32 = u32::try_from(selected_len).map_err(|_| {
78 XlogError::Kernel(format!(
79 "selected id count {} exceeds u32::MAX for strict sparse mask",
80 selected_len
81 ))
82 })?;
83 let block_size = 256u32;
84 let grid_size = selected_len_u32.div_ceil(block_size);
85 let ids_col = ids_buf
86 .column(0)
87 .ok_or_else(|| XlogError::Kernel("selected id buffer has no column".to_string()))?;
88 match ids_buf.schema().column_type(0).ok_or_else(|| {
89 XlogError::Kernel("selected id buffer has no schema type".to_string())
90 })? {
91 ScalarType::U32 | ScalarType::Symbol => {
92 let ids_view = self.column_as_u32_view(ids_col, selected_len)?;
93 let func = self
94 .device
95 .inner()
96 .get_func(ILP_MODULE, ilp_kernels::ILP_MARK_SELECTED_IDS_U32)
97 .ok_or_else(|| {
98 XlogError::Kernel(
99 "ilp_mark_selected_ids_u32 kernel not found".to_string(),
100 )
101 })?;
102 unsafe {
104 func.clone().launch(
105 LaunchConfig {
106 grid_dim: (grid_size, 1, 1),
107 block_dim: (block_size, 1, 1),
108 shared_mem_bytes: 0,
109 },
110 (
111 &ids_view,
112 selected_len_u32,
113 candidate_count_u32,
114 &mut active_flags,
115 ),
116 )
117 }
118 .map_err(|e| {
119 XlogError::Kernel(format!(
120 "strict sparse selected-id scatter failed: {}",
121 e
122 ))
123 })?;
124 }
125 ScalarType::I32 => {
126 let ids_view = self.ilp_i32_view(ids_col, selected_len)?;
127 let func = self
128 .device
129 .inner()
130 .get_func(ILP_MODULE, ilp_kernels::ILP_MARK_SELECTED_IDS_I32)
131 .ok_or_else(|| {
132 XlogError::Kernel(
133 "ilp_mark_selected_ids_i32 kernel not found".to_string(),
134 )
135 })?;
136 unsafe {
138 func.clone().launch(
139 LaunchConfig {
140 grid_dim: (grid_size, 1, 1),
141 block_dim: (block_size, 1, 1),
142 shared_mem_bytes: 0,
143 },
144 (
145 &ids_view,
146 selected_len_u32,
147 candidate_count_u32,
148 &mut active_flags,
149 ),
150 )
151 }
152 .map_err(|e| {
153 XlogError::Kernel(format!(
154 "strict sparse selected-id scatter failed: {}",
155 e
156 ))
157 })?;
158 }
159 ScalarType::I64 => {
160 let ids_view = self.ilp_i64_view(ids_col, selected_len)?;
161 let func = self
162 .device
163 .inner()
164 .get_func(ILP_MODULE, ilp_kernels::ILP_MARK_SELECTED_IDS_I64)
165 .ok_or_else(|| {
166 XlogError::Kernel(
167 "ilp_mark_selected_ids_i64 kernel not found".to_string(),
168 )
169 })?;
170 unsafe {
172 func.clone().launch(
173 LaunchConfig {
174 grid_dim: (grid_size, 1, 1),
175 block_dim: (block_size, 1, 1),
176 shared_mem_bytes: 0,
177 },
178 (
179 &ids_view,
180 selected_len_u32,
181 candidate_count_u32,
182 &mut active_flags,
183 ),
184 )
185 }
186 .map_err(|e| {
187 XlogError::Kernel(format!(
188 "strict sparse selected-id scatter failed: {}",
189 e
190 ))
191 })?;
192 }
193 ScalarType::U64 => {
194 let ids_view = self.column_as_u64_view(ids_col, selected_len)?;
195 let func = self
196 .device
197 .inner()
198 .get_func(ILP_MODULE, ilp_kernels::ILP_MARK_SELECTED_IDS_U64)
199 .ok_or_else(|| {
200 XlogError::Kernel(
201 "ilp_mark_selected_ids_u64 kernel not found".to_string(),
202 )
203 })?;
204 unsafe {
206 func.clone().launch(
207 LaunchConfig {
208 grid_dim: (grid_size, 1, 1),
209 block_dim: (block_size, 1, 1),
210 shared_mem_bytes: 0,
211 },
212 (
213 &ids_view,
214 selected_len_u32,
215 candidate_count_u32,
216 &mut active_flags,
217 ),
218 )
219 }
220 .map_err(|e| {
221 XlogError::Kernel(format!(
222 "strict sparse selected-id scatter failed: {}",
223 e
224 ))
225 })?;
226 }
227 other => {
228 return Err(XlogError::Kernel(format!(
229 "selected candidate ids must be I32/I64/U32/U64, got {:?}",
230 other
231 )));
232 }
233 }
234
235 self.device
236 .synchronize()
237 .map_err(|e| XlogError::Kernel(format!("strict sparse scatter sync: {}", e)))?;
238 }
239
240 let d_num_rows = self.upload_device_row_count(candidate_count_u32)?;
241 Ok(CudaBuffer::from_columns_with_host_count(
242 vec![active_flags.into_bytes().into()],
243 candidate_count as u64,
244 d_num_rows,
245 Schema::new(vec![("active".to_string(), ScalarType::U32)]),
246 candidate_count_u32,
247 ))
248 }
249
250 pub fn validate_selected_ids(
251 &self,
252 ids_buf: &CudaBuffer,
253 candidate_count: usize,
254 ) -> Result<()> {
255 let selected_len = usize::try_from(ids_buf.num_rows())
256 .map_err(|_| XlogError::Kernel("selected id row count overflow".to_string()))?;
257 let candidate_count_u32 = u32::try_from(candidate_count).map_err(|_| {
258 XlogError::Kernel(format!(
259 "candidate count {} exceeds u32::MAX for strict sparse mask",
260 candidate_count
261 ))
262 })?;
263
264 if selected_len == 0 {
265 return Ok(());
266 }
267
268 let selected_len_u32 = u32::try_from(selected_len).map_err(|_| {
269 XlogError::Kernel(format!(
270 "selected id count {} exceeds u32::MAX for strict sparse mask",
271 selected_len
272 ))
273 })?;
274 let block_size = 256u32;
275 let grid_size = selected_len_u32.div_ceil(block_size);
276 let ids_col = ids_buf
277 .column(0)
278 .ok_or_else(|| XlogError::Kernel("selected id buffer has no column".to_string()))?;
279
280 let mut seen_flags = self.memory.alloc::<u32>(candidate_count)?;
281 if candidate_count > 0 {
282 self.device
283 .inner()
284 .memset_zeros(&mut seen_flags)
285 .map_err(|e| {
286 XlogError::Kernel(format!("zero strict sparse validation flags: {}", e))
287 })?;
288 }
289
290 let mut error_code = self.memory.alloc::<u32>(1)?;
291 let mut error_pos = self.memory.alloc::<u32>(1)?;
292 self.device
293 .inner()
294 .memset_zeros(&mut error_code)
295 .map_err(|e| XlogError::Kernel(format!("zero strict sparse error code: {}", e)))?;
296 self.device
297 .inner()
298 .memset_zeros(&mut error_pos)
299 .map_err(|e| XlogError::Kernel(format!("zero strict sparse error pos: {}", e)))?;
300
301 match ids_buf
302 .schema()
303 .column_type(0)
304 .ok_or_else(|| XlogError::Kernel("selected id buffer has no schema type".to_string()))?
305 {
306 ScalarType::U32 | ScalarType::Symbol => {
307 let ids_view = self.column_as_u32_view(ids_col, selected_len)?;
308 let func = self
309 .device
310 .inner()
311 .get_func(ILP_MODULE, ilp_kernels::ILP_VALIDATE_SELECTED_IDS_U32)
312 .ok_or_else(|| {
313 XlogError::Kernel(
314 "ilp_validate_selected_ids_u32 kernel not found".to_string(),
315 )
316 })?;
317 unsafe {
319 func.clone().launch(
320 LaunchConfig {
321 grid_dim: (grid_size, 1, 1),
322 block_dim: (block_size, 1, 1),
323 shared_mem_bytes: 0,
324 },
325 (
326 &ids_view,
327 selected_len_u32,
328 candidate_count_u32,
329 &mut seen_flags,
330 &mut error_code,
331 &mut error_pos,
332 ),
333 )
334 }
335 .map_err(|e| {
336 XlogError::Kernel(format!(
337 "strict sparse selected-id validation failed: {}",
338 e
339 ))
340 })?;
341 }
342 ScalarType::I32 => {
343 let ids_view = self.ilp_i32_view(ids_col, selected_len)?;
344 let func = self
345 .device
346 .inner()
347 .get_func(ILP_MODULE, ilp_kernels::ILP_VALIDATE_SELECTED_IDS_I32)
348 .ok_or_else(|| {
349 XlogError::Kernel(
350 "ilp_validate_selected_ids_i32 kernel not found".to_string(),
351 )
352 })?;
353 unsafe {
355 func.clone().launch(
356 LaunchConfig {
357 grid_dim: (grid_size, 1, 1),
358 block_dim: (block_size, 1, 1),
359 shared_mem_bytes: 0,
360 },
361 (
362 &ids_view,
363 selected_len_u32,
364 candidate_count_u32,
365 &mut seen_flags,
366 &mut error_code,
367 &mut error_pos,
368 ),
369 )
370 }
371 .map_err(|e| {
372 XlogError::Kernel(format!(
373 "strict sparse selected-id validation failed: {}",
374 e
375 ))
376 })?;
377 }
378 ScalarType::I64 => {
379 let ids_view = self.ilp_i64_view(ids_col, selected_len)?;
380 let func = self
381 .device
382 .inner()
383 .get_func(ILP_MODULE, ilp_kernels::ILP_VALIDATE_SELECTED_IDS_I64)
384 .ok_or_else(|| {
385 XlogError::Kernel(
386 "ilp_validate_selected_ids_i64 kernel not found".to_string(),
387 )
388 })?;
389 unsafe {
391 func.clone().launch(
392 LaunchConfig {
393 grid_dim: (grid_size, 1, 1),
394 block_dim: (block_size, 1, 1),
395 shared_mem_bytes: 0,
396 },
397 (
398 &ids_view,
399 selected_len_u32,
400 candidate_count_u32,
401 &mut seen_flags,
402 &mut error_code,
403 &mut error_pos,
404 ),
405 )
406 }
407 .map_err(|e| {
408 XlogError::Kernel(format!(
409 "strict sparse selected-id validation failed: {}",
410 e
411 ))
412 })?;
413 }
414 ScalarType::U64 => {
415 let ids_view = self.column_as_u64_view(ids_col, selected_len)?;
416 let func = self
417 .device
418 .inner()
419 .get_func(ILP_MODULE, ilp_kernels::ILP_VALIDATE_SELECTED_IDS_U64)
420 .ok_or_else(|| {
421 XlogError::Kernel(
422 "ilp_validate_selected_ids_u64 kernel not found".to_string(),
423 )
424 })?;
425 unsafe {
427 func.clone().launch(
428 LaunchConfig {
429 grid_dim: (grid_size, 1, 1),
430 block_dim: (block_size, 1, 1),
431 shared_mem_bytes: 0,
432 },
433 (
434 &ids_view,
435 selected_len_u32,
436 candidate_count_u32,
437 &mut seen_flags,
438 &mut error_code,
439 &mut error_pos,
440 ),
441 )
442 }
443 .map_err(|e| {
444 XlogError::Kernel(format!(
445 "strict sparse selected-id validation failed: {}",
446 e
447 ))
448 })?;
449 }
450 other => {
451 return Err(XlogError::Kernel(format!(
452 "selected candidate ids must be I32/I64/U32/U64, got {:?}",
453 other
454 )));
455 }
456 }
457
458 self.device
459 .synchronize()
460 .map_err(|e| XlogError::Kernel(format!("strict sparse validation sync: {}", e)))?;
461
462 let error_code_host = self.dtoh_scalar_untracked(&error_code, 0)?;
463 if error_code_host == 0 {
464 return Ok(());
465 }
466 let error_pos_host = self.dtoh_scalar_untracked(&error_pos, 0)?;
467 match error_code_host {
468 1 => Err(XlogError::Kernel(format!(
469 "selected candidate id out of range at position {}",
470 error_pos_host
471 ))),
472 2 => Err(XlogError::Kernel(format!(
473 "duplicate selected candidate id at position {}",
474 error_pos_host
475 ))),
476 code => Err(XlogError::Kernel(format!(
477 "strict sparse selected-id validation failed with error code {}",
478 code
479 ))),
480 }
481 }
482
483 pub fn filter_buffer_by_candidate_flag(
484 &self,
485 input: &CudaBuffer,
486 candidate_flags: &CudaBuffer,
487 candidate_idx: usize,
488 ) -> Result<CudaBuffer> {
489 if input.is_empty() {
490 return self.create_empty_buffer(input.schema().clone());
491 }
492 if candidate_idx >= candidate_flags.num_rows() as usize {
493 return Err(XlogError::Kernel(format!(
494 "candidate flag index {} out of range [0, {})",
495 candidate_idx,
496 candidate_flags.num_rows()
497 )));
498 }
499
500 let flag_col = candidate_flags
501 .column(0)
502 .ok_or_else(|| XlogError::Kernel("candidate flag buffer has no column".to_string()))?;
503 let flag_view = self.column_as_u32_view(flag_col, candidate_flags.num_rows() as usize)?;
504 let row_count = u32::try_from(input.num_rows()).map_err(|_| {
505 XlogError::Kernel(format!(
506 "strict sparse row count {} exceeds u32::MAX",
507 input.num_rows()
508 ))
509 })?;
510 let candidate_idx_u32 = u32::try_from(candidate_idx).map_err(|_| {
511 XlogError::Kernel(format!(
512 "candidate flag index {} exceeds u32::MAX",
513 candidate_idx
514 ))
515 })?;
516
517 let mut row_mask = self.memory.alloc::<u8>(row_count as usize)?;
518 let func = self
519 .device
520 .inner()
521 .get_func(ILP_MODULE, ilp_kernels::ILP_BROADCAST_CANDIDATE_FLAG)
522 .ok_or_else(|| {
523 XlogError::Kernel("ilp_broadcast_candidate_flag kernel not found".to_string())
524 })?;
525 let block_size = 256u32;
526 let grid_size = row_count.div_ceil(block_size);
527 unsafe {
529 func.clone().launch(
530 LaunchConfig {
531 grid_dim: (grid_size, 1, 1),
532 block_dim: (block_size, 1, 1),
533 shared_mem_bytes: 0,
534 },
535 (&flag_view, candidate_idx_u32, row_count, &mut row_mask),
536 )
537 }
538 .map_err(|e| XlogError::Kernel(format!("strict sparse flag broadcast failed: {}", e)))?;
539
540 self.filter_by_device_mask(input, &row_mask)
541 }
542
543 pub fn ilp_coo_fill_launch(
548 &self,
549 compacted_fact_indices: &TrackedCudaSlice<u32>,
550 cidx: u32,
551 count: u32,
552 offset: u32,
553 coo_fact: &mut TrackedCudaSlice<u32>,
554 coo_cand: &mut TrackedCudaSlice<u32>,
555 ) -> Result<()> {
556 if count == 0 {
557 return Ok(());
558 }
559 let func = self
560 .device
561 .inner()
562 .get_func(ILP_CREDIT_MODULE, ilp_credit_kernels::ILP_COO_FILL)
563 .ok_or_else(|| XlogError::Kernel("ilp_coo_fill kernel not found".to_string()))?;
564 let block_size = 256u32;
565 let grid_size = count.div_ceil(block_size);
566 unsafe {
568 func.clone().launch(
569 LaunchConfig {
570 grid_dim: (grid_size, 1, 1),
571 block_dim: (block_size, 1, 1),
572 shared_mem_bytes: 0,
573 },
574 (
575 compacted_fact_indices,
576 cidx,
577 count,
578 offset,
579 coo_fact,
580 coo_cand,
581 ),
582 )
583 }
584 .map_err(|e| XlogError::Kernel(format!("ilp_coo_fill failed: {}", e)))?;
585 self.device.synchronize()?;
586 Ok(())
587 }
588
589 pub fn ilp_credit_forward_f32_launch(
592 &self,
593 row_offsets: &TrackedCudaSlice<u32>,
594 col_indices: &TrackedCudaSlice<u32>,
595 cand_probs: &CudaColumn, is_positive: &TrackedCudaSlice<u8>,
597 num_facts: u32,
598 eps: f32,
599 ) -> Result<(TrackedCudaSlice<f32>, TrackedCudaSlice<f32>)> {
600 let mut credit_out = self.memory.alloc::<f32>(num_facts as usize)?;
601 let mut loss_contrib = self.memory.alloc::<f32>(num_facts as usize)?;
602 if num_facts == 0 {
603 return Ok((credit_out, loss_contrib));
604 }
605 let func = self
606 .device
607 .inner()
608 .get_func(
609 ILP_CREDIT_MODULE,
610 ilp_credit_kernels::ILP_CREDIT_FORWARD_F32,
611 )
612 .ok_or_else(|| {
613 XlogError::Kernel("ilp_credit_forward_f32 kernel not found".to_string())
614 })?;
615 let block_size = 256u32;
616 let grid_size = num_facts.div_ceil(block_size);
617 let cand_view = unsafe { cand_probs.device_view().cast::<f32>() }
619 .and_then(|view| view.try_slice(..cudarc::driver::DeviceSlice::len(cand_probs) / 4))
620 .ok_or_else(|| XlogError::Kernel("device scalar view is invalid".into()))?;
621 unsafe {
623 func.clone().launch(
624 LaunchConfig {
625 grid_dim: (grid_size, 1, 1),
626 block_dim: (block_size, 1, 1),
627 shared_mem_bytes: 0,
628 },
629 (
630 row_offsets,
631 col_indices,
632 &cand_view,
633 is_positive,
634 num_facts,
635 eps,
636 &mut credit_out,
637 &mut loss_contrib,
638 ),
639 )
640 }
641 .map_err(|e| XlogError::Kernel(format!("ilp_credit_forward_f32 failed: {}", e)))?;
642 self.device.synchronize()?;
643 Ok((credit_out, loss_contrib))
644 }
645
646 pub fn ilp_credit_forward_f64_launch(
649 &self,
650 row_offsets: &TrackedCudaSlice<u32>,
651 col_indices: &TrackedCudaSlice<u32>,
652 cand_probs: &CudaColumn, is_positive: &TrackedCudaSlice<u8>,
654 num_facts: u32,
655 eps: f64,
656 ) -> Result<(TrackedCudaSlice<f64>, TrackedCudaSlice<f64>)> {
657 let mut credit_out = self.memory.alloc::<f64>(num_facts as usize)?;
658 let mut loss_contrib = self.memory.alloc::<f64>(num_facts as usize)?;
659 if num_facts == 0 {
660 return Ok((credit_out, loss_contrib));
661 }
662 let func = self
663 .device
664 .inner()
665 .get_func(
666 ILP_CREDIT_MODULE,
667 ilp_credit_kernels::ILP_CREDIT_FORWARD_F64,
668 )
669 .ok_or_else(|| {
670 XlogError::Kernel("ilp_credit_forward_f64 kernel not found".to_string())
671 })?;
672 let block_size = 256u32;
673 let grid_size = num_facts.div_ceil(block_size);
674 let cand_view = unsafe { cand_probs.device_view().cast::<f64>() }
675 .and_then(|view| view.try_slice(..cudarc::driver::DeviceSlice::len(cand_probs) / 8))
676 .ok_or_else(|| XlogError::Kernel("device scalar view is invalid".into()))?;
677 unsafe {
679 func.clone().launch(
680 LaunchConfig {
681 grid_dim: (grid_size, 1, 1),
682 block_dim: (block_size, 1, 1),
683 shared_mem_bytes: 0,
684 },
685 (
686 row_offsets,
687 col_indices,
688 &cand_view,
689 is_positive,
690 num_facts,
691 eps,
692 &mut credit_out,
693 &mut loss_contrib,
694 ),
695 )
696 }
697 .map_err(|e| XlogError::Kernel(format!("ilp_credit_forward_f64 failed: {}", e)))?;
698 self.device.synchronize()?;
699 Ok((credit_out, loss_contrib))
700 }
701
702 pub fn ilp_credit_backward_f32_launch(
705 &self,
706 row_offsets: &TrackedCudaSlice<u32>,
707 col_indices: &TrackedCudaSlice<u32>,
708 credit_out: &TrackedCudaSlice<f32>,
709 is_positive: &TrackedCudaSlice<u8>,
710 num_facts: u32,
711 num_cands: u32,
712 ) -> Result<TrackedCudaSlice<f32>> {
713 let mut d_grad = self.memory.alloc::<f32>(num_cands as usize)?;
714 self.device
715 .inner()
716 .memset_zeros(&mut d_grad)
717 .map_err(|e| XlogError::Kernel(format!("Failed to zero grad: {}", e)))?;
718 if num_facts == 0 {
719 return Ok(d_grad);
720 }
721 let func = self
722 .device
723 .inner()
724 .get_func(
725 ILP_CREDIT_MODULE,
726 ilp_credit_kernels::ILP_CREDIT_BACKWARD_F32,
727 )
728 .ok_or_else(|| {
729 XlogError::Kernel("ilp_credit_backward_f32 kernel not found".to_string())
730 })?;
731 let block_size = 256u32;
732 let grid_size = num_facts.div_ceil(block_size);
733 unsafe {
735 func.clone().launch(
736 LaunchConfig {
737 grid_dim: (grid_size, 1, 1),
738 block_dim: (block_size, 1, 1),
739 shared_mem_bytes: 0,
740 },
741 (
742 row_offsets,
743 col_indices,
744 credit_out,
745 is_positive,
746 num_facts,
747 &mut d_grad,
748 ),
749 )
750 }
751 .map_err(|e| XlogError::Kernel(format!("ilp_credit_backward_f32 failed: {}", e)))?;
752 self.device.synchronize()?;
753 Ok(d_grad)
754 }
755
756 pub fn ilp_credit_backward_f64_launch(
759 &self,
760 row_offsets: &TrackedCudaSlice<u32>,
761 col_indices: &TrackedCudaSlice<u32>,
762 credit_out: &TrackedCudaSlice<f64>,
763 is_positive: &TrackedCudaSlice<u8>,
764 num_facts: u32,
765 num_cands: u32,
766 ) -> Result<TrackedCudaSlice<f64>> {
767 let mut d_grad = self.memory.alloc::<f64>(num_cands as usize)?;
768 self.device
769 .inner()
770 .memset_zeros(&mut d_grad)
771 .map_err(|e| XlogError::Kernel(format!("Failed to zero grad: {}", e)))?;
772 if num_facts == 0 {
773 return Ok(d_grad);
774 }
775 let func = self
776 .device
777 .inner()
778 .get_func(
779 ILP_CREDIT_MODULE,
780 ilp_credit_kernels::ILP_CREDIT_BACKWARD_F64,
781 )
782 .ok_or_else(|| {
783 XlogError::Kernel("ilp_credit_backward_f64 kernel not found".to_string())
784 })?;
785 let block_size = 256u32;
786 let grid_size = num_facts.div_ceil(block_size);
787 unsafe {
789 func.clone().launch(
790 LaunchConfig {
791 grid_dim: (grid_size, 1, 1),
792 block_dim: (block_size, 1, 1),
793 shared_mem_bytes: 0,
794 },
795 (
796 row_offsets,
797 col_indices,
798 credit_out,
799 is_positive,
800 num_facts,
801 &mut d_grad,
802 ),
803 )
804 }
805 .map_err(|e| XlogError::Kernel(format!("ilp_credit_backward_f64 failed: {}", e)))?;
806 self.device.synchronize()?;
807 Ok(d_grad)
808 }
809
810 pub fn ilp_reduce_sum_f32_launch(
816 &self,
817 input: &TrackedCudaSlice<f32>,
818 n: u32,
819 ) -> Result<TrackedCudaSlice<f32>> {
820 let mut d_result = self.memory.alloc::<f32>(1)?;
821 self.device
822 .inner()
823 .memset_zeros(&mut d_result)
824 .map_err(|e| XlogError::Kernel(format!("ilp_reduce_sum_f32 zero result: {}", e)))?;
825
826 if n == 0 {
827 return Ok(d_result);
828 }
829
830 let func = self
831 .device
832 .inner()
833 .get_func(ILP_MODULE, ilp_kernels::ILP_REDUCE_SUM_F32)
834 .ok_or_else(|| XlogError::Kernel("ilp_reduce_sum_f32 not found".to_string()))?;
835 let block_size = 256u32;
836 let grid_size = n.div_ceil(block_size);
837 unsafe {
839 func.clone().launch(
840 LaunchConfig {
841 grid_dim: (grid_size, 1, 1),
842 block_dim: (block_size, 1, 1),
843 shared_mem_bytes: 0,
844 },
845 (input, n, &mut d_result),
846 )
847 }
848 .map_err(|e| XlogError::Kernel(format!("ilp_reduce_sum_f32: {}", e)))?;
849 self.device.synchronize()?;
850 Ok(d_result)
851 }
852
853 pub fn ilp_reduce_sum_f64_launch(
859 &self,
860 input: &TrackedCudaSlice<f64>,
861 n: u32,
862 ) -> Result<TrackedCudaSlice<f64>> {
863 let mut d_result = self.memory.alloc::<f64>(1)?;
864 self.device
865 .inner()
866 .memset_zeros(&mut d_result)
867 .map_err(|e| XlogError::Kernel(format!("ilp_reduce_sum_f64 zero result: {}", e)))?;
868
869 if n == 0 {
870 return Ok(d_result);
871 }
872
873 let func = self
874 .device
875 .inner()
876 .get_func(ILP_MODULE, ilp_kernels::ILP_REDUCE_SUM_F64)
877 .ok_or_else(|| XlogError::Kernel("ilp_reduce_sum_f64 not found".to_string()))?;
878 let block_size = 256u32;
879 let grid_size = n.div_ceil(block_size);
880 unsafe {
882 func.clone().launch(
883 LaunchConfig {
884 grid_dim: (grid_size, 1, 1),
885 block_dim: (block_size, 1, 1),
886 shared_mem_bytes: 0,
887 },
888 (input, n, &mut d_result),
889 )
890 }
891 .map_err(|e| XlogError::Kernel(format!("ilp_reduce_sum_f64: {}", e)))?;
892 self.device.synchronize()?;
893 Ok(d_result)
894 }
895
896 #[expect(
908 clippy::too_many_arguments,
909 reason = "the launch surface mirrors the fixed COO-fill kernel ABI and keeps every device buffer explicit"
910 )]
911 pub fn ilp_coo_fill_from_mask_launch(
912 &self,
913 mask: &TrackedCudaSlice<u8>,
914 prefix_sum: &TrackedCudaSlice<u32>,
915 fact_indices: &TrackedCudaSlice<u32>,
916 offset_idx: u32,
917 cand_value: u32,
918 num_query: u32,
919 d_offsets: &TrackedCudaSlice<u32>,
920 coo_fact: &mut TrackedCudaSlice<u32>,
921 coo_cand: &mut TrackedCudaSlice<u32>,
922 ) -> Result<()> {
923 if num_query == 0 {
924 return Ok(());
925 }
926 let func = self
927 .device()
928 .inner()
929 .get_func(ILP_MODULE, ilp_kernels::ILP_COO_FILL_FROM_MASK)
930 .ok_or_else(|| XlogError::Kernel("ilp_coo_fill_from_mask not found".to_string()))?;
931 let block_size = 256u32;
932 let grid_size = num_query.div_ceil(block_size);
933 unsafe {
935 func.clone().launch(
936 LaunchConfig {
937 grid_dim: (grid_size, 1, 1),
938 block_dim: (block_size, 1, 1),
939 shared_mem_bytes: 0,
940 },
941 (
942 mask,
943 prefix_sum,
944 fact_indices,
945 offset_idx,
946 cand_value,
947 num_query,
948 d_offsets,
949 coo_fact,
950 coo_cand,
951 ),
952 )
953 }
954 .map_err(|e| XlogError::Kernel(format!("ilp_coo_fill_from_mask: {}", e)))?;
955 self.device()
956 .inner()
957 .synchronize()
958 .map_err(|e| XlogError::Kernel(format!("ilp_coo_fill_from_mask sync: {}", e)))?;
959 Ok(())
960 }
961
962 pub fn ilp_csr_histogram_launch(
972 &self,
973 sorted_facts: &TrackedCudaSlice<u32>,
974 nnz: u32,
975 num_facts: u32,
976 ) -> Result<TrackedCudaSlice<u32>> {
977 let mut d_hist = self.memory().alloc::<u32>(num_facts as usize)?;
978 self.device()
979 .inner()
980 .memset_zeros(&mut d_hist)
981 .map_err(|e| XlogError::Kernel(format!("ilp_csr_histogram zero hist: {}", e)))?;
982
983 if nnz == 0 {
984 return Ok(d_hist);
985 }
986
987 let func = self
988 .device()
989 .inner()
990 .get_func(ILP_MODULE, ilp_kernels::ILP_CSR_HISTOGRAM)
991 .ok_or_else(|| XlogError::Kernel("ilp_csr_histogram kernel not found".to_string()))?;
992
993 let block_size = 256u32;
994 let grid_size = nnz.div_ceil(block_size);
995
996 unsafe {
998 func.clone()
999 .launch(
1000 cudarc::driver::LaunchConfig {
1001 grid_dim: (grid_size, 1, 1),
1002 block_dim: (block_size, 1, 1),
1003 shared_mem_bytes: 0,
1004 },
1005 (sorted_facts, nnz, num_facts, &mut d_hist),
1006 )
1007 .map_err(|e| XlogError::Kernel(format!("ilp_csr_histogram launch: {}", e)))?;
1008 }
1009
1010 self.device()
1011 .inner()
1012 .synchronize()
1013 .map_err(|e| XlogError::Kernel(format!("ilp_csr_histogram sync: {}", e)))?;
1014
1015 Ok(d_hist)
1016 }
1017}