1use crate::{LaunchAsync, LaunchConfig};
4use xlog_core::{AggOp, Result, ScalarType, Schema, XlogError};
5
6use super::{
7 arith_kernels, groupby_kernels, pack_kernels, scan_kernels, ARITH_MODULE, GROUPBY_MODULE,
8 PACK_MODULE, SCAN_MODULE,
9};
10use crate::memory::{CudaColumn, TrackedCudaSlice};
11use crate::CudaBuffer;
12
13impl super::CudaKernelProvider {
14 pub fn groupby_agg(
30 &self,
31 input: &CudaBuffer,
32 key_cols: &[usize],
33 agg: AggOp,
34 value_col: usize,
35 ) -> Result<CudaBuffer> {
36 self.groupby_multi_agg(input, key_cols, &[(value_col, agg)])
37 }
38
39 pub fn groupby_multi_agg(
67 &self,
68 buffer: &CudaBuffer,
69 key_cols: &[usize],
70 aggs: &[(usize, AggOp)],
71 ) -> Result<CudaBuffer> {
72 if Self::use_recorded_groupby_env()?
78 && !key_cols.is_empty()
79 && !aggs.is_empty()
80 && key_cols.len() <= 4
81 {
82 if let Some(launch_stream) = self.recorded_op_stream_or_init() {
83 let keys_compatible = key_cols.iter().all(|&k| {
84 matches!(
85 buffer.schema.column_type(k),
86 Some(ScalarType::U32) | Some(ScalarType::Symbol)
87 )
88 });
89 let aggs_compatible = aggs.iter().all(|&(_, op)| {
90 matches!(op, AggOp::Count | AggOp::Sum | AggOp::Min | AggOp::Max)
91 });
92 if keys_compatible && aggs_compatible {
93 return self.groupby_multi_agg_recorded(buffer, key_cols, aggs, launch_stream);
94 }
95 }
96 }
97 let num_rows = self.device_row_count(buffer)?;
98 if num_rows == 0 {
99 let result_schema =
100 self.groupby_multi_agg_result_schema(buffer.schema(), key_cols, aggs);
101 return self.create_empty_buffer(result_schema);
102 }
103 if num_rows > u32::MAX as usize {
104 return Err(XlogError::Kernel(format!(
105 "GroupBy supports at most {} rows, got {}",
106 u32::MAX,
107 num_rows
108 )));
109 }
110
111 if key_cols.is_empty() {
113 return Err(XlogError::Kernel(
114 "GroupBy requires at least one key column".to_string(),
115 ));
116 }
117 if aggs.is_empty() {
118 return Err(XlogError::Kernel(
119 "GroupBy requires at least one aggregation".to_string(),
120 ));
121 }
122
123 for &key_col in key_cols {
125 if key_col >= buffer.arity() {
126 return Err(XlogError::Kernel(format!(
127 "Key column {} out of bounds (arity {})",
128 key_col,
129 buffer.arity()
130 )));
131 }
132 }
133
134 for &(value_col, agg_op) in aggs {
136 if value_col >= buffer.arity() {
137 return Err(XlogError::Kernel(format!(
138 "Value column {} out of bounds (arity {})",
139 value_col,
140 buffer.arity()
141 )));
142 }
143
144 let value_ty = buffer
145 .schema()
146 .column_type(value_col)
147 .ok_or_else(|| XlogError::Kernel("Value column has no type".to_string()))?;
148 match agg_op {
149 AggOp::Count => {}
150 AggOp::Sum | AggOp::Min | AggOp::Max => {
154 if !matches!(value_ty, ScalarType::U32 | ScalarType::U64) {
155 return Err(XlogError::Kernel(format!(
156 "{:?} currently requires U32 or U64 values, got {:?}",
157 agg_op, value_ty
158 )));
159 }
160 }
161 AggOp::LogSumExp => {
162 if value_ty != ScalarType::F64 {
163 return Err(XlogError::Kernel(format!(
164 "LogSumExp requires F64 values, got {:?}",
165 value_ty
166 )));
167 }
168 }
169 }
170 }
171
172 let sorted = self.sort(buffer, key_cols)?;
174 let num_rows = self.device_row_count(&sorted)?;
175 if num_rows > u32::MAX as usize {
176 return Err(XlogError::Kernel(format!(
177 "GroupBy supports at most {} rows, got {}",
178 u32::MAX,
179 num_rows
180 )));
181 }
182 let num_rows = num_rows as u32;
183
184 let boundary_func = self
186 .device
187 .inner()
188 .get_func(GROUPBY_MODULE, groupby_kernels::DETECT_GROUP_BOUNDARIES)
189 .ok_or_else(|| {
190 XlogError::Kernel("detect_group_boundaries kernel not found".to_string())
191 })?;
192
193 let boundaries = self.memory.alloc::<u8>(num_rows as usize)?;
195
196 let packed = self.compute_hashes_and_pack_keys(&sorted, key_cols)?;
197 if packed.key_bytes == 0 || packed.key_bytes % 4 != 0 {
198 return Err(XlogError::Kernel(format!(
199 "GroupBy key packing produced {} bytes per row (expected multiple of 4); Bool keys are not supported",
200 packed.key_bytes
201 )));
202 }
203
204 let segments_per_row = (packed.key_bytes / 4) as usize;
205 let total_segments = (num_rows as usize) * segments_per_row;
206 let packed_u32 = self.bytes_as_u32_view(&packed.packed_keys, total_segments)?;
207
208 let block_size = 256u32;
210 let grid_size = num_rows.div_ceil(block_size);
211 let config = LaunchConfig {
212 grid_dim: (grid_size, 1, 1),
213 block_dim: (block_size, 1, 1),
214 shared_mem_bytes: 0,
215 };
216
217 unsafe {
219 boundary_func.clone().launch(
220 config,
221 (
222 &packed_u32,
223 num_rows,
224 segments_per_row as u32,
225 segments_per_row as u32,
226 &boundaries,
227 ),
228 )
229 }
230 .map_err(|e| XlogError::Kernel(format!("detect_group_boundaries failed: {}", e)))?;
231
232 self.device.synchronize()?;
233
234 let device = self.device.inner();
236 let num_blocks = grid_size;
237 let d_boundary_pos = self.memory.alloc::<u32>(num_rows as usize)?;
238 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
239
240 let phase1_fn = device
241 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE1)
242 .ok_or_else(|| {
243 XlogError::Kernel("Failed to get multiblock_scan_phase1 kernel".to_string())
244 })?;
245
246 unsafe {
248 phase1_fn.clone().launch(
249 LaunchConfig {
250 grid_dim: (num_blocks, 1, 1),
251 block_dim: (block_size, 1, 1),
252 shared_mem_bytes: 0,
253 },
254 (&boundaries, &d_boundary_pos, &d_block_sums, num_rows),
255 )
256 }
257 .map_err(|e| XlogError::Kernel(format!("multiblock_scan_phase1 failed: {}", e)))?;
258
259 if num_blocks > 1 {
260 self.multiblock_scan_u32_inplace(&mut d_block_sums, num_blocks)?;
261
262 let phase3_fn = device
263 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
264 .ok_or_else(|| {
265 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
266 })?;
267
268 unsafe {
270 phase3_fn.clone().launch(
271 LaunchConfig {
272 grid_dim: (num_blocks, 1, 1),
273 block_dim: (block_size, 1, 1),
274 shared_mem_bytes: 0,
275 },
276 (&d_boundary_pos, &d_block_sums, num_rows),
277 )
278 }
279 .map_err(|e| XlogError::Kernel(format!("multiblock_scan_phase3 failed: {}", e)))?;
280 }
281
282 self.device.synchronize()?;
283 let d_num_groups = self.capture_num_groups(&d_boundary_pos, &boundaries, num_rows)?;
284 let row_cap = num_rows as u64;
285 let row_cap_usize = num_rows as usize;
286 let row_cap_u32 = num_rows;
287
288 let mut group_ids = self.memory.alloc::<u32>(num_rows as usize)?;
289 let mut group_first_idx = self.memory.alloc::<u32>(row_cap_usize)?;
290
291 let group_ids_fn = device
292 .get_func(GROUPBY_MODULE, groupby_kernels::GROUP_IDS_FROM_BOUNDARIES)
293 .ok_or_else(|| {
294 XlogError::Kernel("group_ids_from_boundaries kernel not found".to_string())
295 })?;
296 let group_start_fn = device
297 .get_func(GROUPBY_MODULE, groupby_kernels::GROUP_START_INDICES)
298 .ok_or_else(|| XlogError::Kernel("group_start_indices kernel not found".to_string()))?;
299
300 unsafe {
302 group_ids_fn.clone().launch(
303 config,
304 (&boundaries, &d_boundary_pos, num_rows, &mut group_ids),
305 )
306 }
307 .map_err(|e| XlogError::Kernel(format!("group_ids_from_boundaries failed: {}", e)))?;
308
309 unsafe {
311 group_start_fn.clone().launch(
312 config,
313 (&boundaries, &d_boundary_pos, num_rows, &mut group_first_idx),
314 )
315 }
316 .map_err(|e| XlogError::Kernel(format!("group_start_indices failed: {}", e)))?;
317
318 self.device.synchronize()?;
319
320 let mut agg_columns: Vec<CudaColumn> = Vec::with_capacity(aggs.len());
322
323 for &(value_col, agg_op) in aggs {
324 let values = sorted
325 .column(value_col)
326 .ok_or_else(|| XlogError::Kernel("Value column not found".to_string()))?;
327
328 match agg_op {
329 AggOp::Count => {
330 let output_bytes = row_cap_usize
331 .checked_mul(std::mem::size_of::<u64>())
332 .ok_or_else(|| {
333 XlogError::Kernel("Count output size overflow".to_string())
334 })?;
335 let mut output = self.memory.alloc::<u8>(output_bytes)?;
336 device.memset_zeros(&mut output).map_err(|e| {
337 XlogError::Kernel(format!("Failed to zero count output: {}", e))
338 })?;
339
340 let count_func = device
341 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_COUNT)
342 .ok_or_else(|| {
343 XlogError::Kernel("groupby_count kernel not found".to_string())
344 })?;
345
346 unsafe {
348 count_func
349 .clone()
350 .launch(config, (&boundaries, &group_ids, num_rows, &output))
351 }
352 .map_err(|e| XlogError::Kernel(format!("groupby_count failed: {}", e)))?;
353
354 self.device.synchronize()?;
355 agg_columns.push(output.into());
356 }
357 AggOp::Sum => {
358 let value_ty = sorted
359 .schema()
360 .column_type(value_col)
361 .ok_or_else(|| XlogError::Kernel("Value column has no type".to_string()))?;
362 let output_bytes = row_cap_usize
363 .checked_mul(std::mem::size_of::<u64>())
364 .ok_or_else(|| XlogError::Kernel("Sum output size overflow".to_string()))?;
365 let mut output = self.memory.alloc::<u8>(output_bytes)?;
366 device.memset_zeros(&mut output).map_err(|e| {
367 XlogError::Kernel(format!("Failed to zero sum output: {}", e))
368 })?;
369
370 if value_ty == ScalarType::U64 {
373 let values_view = self.column_as_u64_view(values, num_rows as usize)?;
374 let sum_func = device
375 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_SUM_U64)
376 .ok_or_else(|| {
377 XlogError::Kernel("groupby_sum_u64 kernel not found".to_string())
378 })?;
379 unsafe {
381 sum_func
382 .clone()
383 .launch(config, (&values_view, &group_ids, num_rows, &output))
384 }
385 .map_err(|e| XlogError::Kernel(format!("groupby_sum_u64 failed: {}", e)))?;
386 } else {
387 let values_view = self.column_as_u32_view(values, num_rows as usize)?;
388 let sum_func = device
389 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_SUM)
390 .ok_or_else(|| {
391 XlogError::Kernel("groupby_sum kernel not found".to_string())
392 })?;
393 unsafe {
395 sum_func
396 .clone()
397 .launch(config, (&values_view, &group_ids, num_rows, &output))
398 }
399 .map_err(|e| XlogError::Kernel(format!("groupby_sum failed: {}", e)))?;
400 }
401
402 self.device.synchronize()?;
403 agg_columns.push(output.into());
404 }
405 AggOp::Min => {
406 let value_ty = sorted
407 .schema()
408 .column_type(value_col)
409 .ok_or_else(|| XlogError::Kernel("Value column has no type".to_string()))?;
410 if value_ty == ScalarType::U64 {
411 let values_view = self.column_as_u64_view(values, num_rows as usize)?;
414 let output_bytes = row_cap_usize
415 .checked_mul(std::mem::size_of::<u64>())
416 .ok_or_else(|| {
417 XlogError::Kernel("Min output size overflow".to_string())
418 })?;
419 let mut output = self.memory.alloc::<u8>(output_bytes)?;
420 let fill_fn = device
421 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U64)
422 .ok_or_else(|| {
423 XlogError::Kernel("arith_fill_const_u64 not found".to_string())
424 })?;
425 let fill_config = LaunchConfig::for_num_elems(row_cap_u32);
426 unsafe {
428 fill_fn
429 .clone()
430 .launch(fill_config, (u64::MAX, row_cap_u32, &mut output))
431 }
432 .map_err(|e| {
433 XlogError::Kernel(format!("Failed to init min output: {}", e))
434 })?;
435
436 let min_func = device
437 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MIN_U64)
438 .ok_or_else(|| {
439 XlogError::Kernel("groupby_min_u64 kernel not found".to_string())
440 })?;
441 unsafe {
443 min_func
444 .clone()
445 .launch(config, (&values_view, &group_ids, num_rows, &output))
446 }
447 .map_err(|e| XlogError::Kernel(format!("groupby_min_u64 failed: {}", e)))?;
448
449 self.device.synchronize()?;
450 agg_columns.push(output.into());
451 } else {
452 let values_view = self.column_as_u32_view(values, num_rows as usize)?;
453 let output_bytes = row_cap_usize
454 .checked_mul(std::mem::size_of::<u32>())
455 .ok_or_else(|| {
456 XlogError::Kernel("Min output size overflow".to_string())
457 })?;
458 let mut output = self.memory.alloc::<u8>(output_bytes)?;
459 let fill_fn = device
460 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U32)
461 .ok_or_else(|| {
462 XlogError::Kernel("arith_fill_const_u32 not found".to_string())
463 })?;
464 let fill_config = LaunchConfig::for_num_elems(row_cap_u32);
465 unsafe {
467 fill_fn
468 .clone()
469 .launch(fill_config, (u32::MAX, row_cap_u32, &mut output))
470 }
471 .map_err(|e| {
472 XlogError::Kernel(format!("Failed to init min output: {}", e))
473 })?;
474
475 let min_func = device
476 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MIN)
477 .ok_or_else(|| {
478 XlogError::Kernel("groupby_min kernel not found".to_string())
479 })?;
480
481 unsafe {
483 min_func
484 .clone()
485 .launch(config, (&values_view, &group_ids, num_rows, &output))
486 }
487 .map_err(|e| XlogError::Kernel(format!("groupby_min failed: {}", e)))?;
488
489 self.device.synchronize()?;
490 agg_columns.push(output.into());
491 }
492 }
493 AggOp::Max => {
494 let value_ty = sorted
495 .schema()
496 .column_type(value_col)
497 .ok_or_else(|| XlogError::Kernel("Value column has no type".to_string()))?;
498 if value_ty == ScalarType::U64 {
499 let values_view = self.column_as_u64_view(values, num_rows as usize)?;
502 let output_bytes = row_cap_usize
503 .checked_mul(std::mem::size_of::<u64>())
504 .ok_or_else(|| {
505 XlogError::Kernel("Max output size overflow".to_string())
506 })?;
507 let mut output = self.memory.alloc::<u8>(output_bytes)?;
508 device.memset_zeros(&mut output).map_err(|e| {
509 XlogError::Kernel(format!("Failed to zero max output: {}", e))
510 })?;
511
512 let max_func = device
513 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MAX_U64)
514 .ok_or_else(|| {
515 XlogError::Kernel("groupby_max_u64 kernel not found".to_string())
516 })?;
517 unsafe {
519 max_func
520 .clone()
521 .launch(config, (&values_view, &group_ids, num_rows, &output))
522 }
523 .map_err(|e| XlogError::Kernel(format!("groupby_max_u64 failed: {}", e)))?;
524
525 self.device.synchronize()?;
526 agg_columns.push(output.into());
527 } else {
528 let values_view = self.column_as_u32_view(values, num_rows as usize)?;
529 let output_bytes = row_cap_usize
530 .checked_mul(std::mem::size_of::<u32>())
531 .ok_or_else(|| {
532 XlogError::Kernel("Max output size overflow".to_string())
533 })?;
534 let mut output = self.memory.alloc::<u8>(output_bytes)?;
535 device.memset_zeros(&mut output).map_err(|e| {
536 XlogError::Kernel(format!("Failed to zero max output: {}", e))
537 })?;
538
539 let max_func = device
540 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MAX)
541 .ok_or_else(|| {
542 XlogError::Kernel("groupby_max kernel not found".to_string())
543 })?;
544
545 unsafe {
547 max_func
548 .clone()
549 .launch(config, (&values_view, &group_ids, num_rows, &output))
550 }
551 .map_err(|e| XlogError::Kernel(format!("groupby_max failed: {}", e)))?;
552
553 self.device.synchronize()?;
554 agg_columns.push(output.into());
555 }
556 }
557 AggOp::LogSumExp => {
558 let values_f64 = self.column_as_f64_view(values, num_rows as usize)?;
559 let output_bytes = row_cap_usize
560 .checked_mul(std::mem::size_of::<f64>())
561 .ok_or_else(|| {
562 XlogError::Kernel("LogSumExp output size overflow".to_string())
563 })?;
564 let mut maxs = self.memory.alloc::<u8>(output_bytes)?;
565 let mut sumexps = self.memory.alloc::<u8>(output_bytes)?;
566 let results = self.memory.alloc::<u8>(output_bytes)?;
567
568 let fill_f64 = device
569 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_F64)
570 .ok_or_else(|| {
571 XlogError::Kernel("arith_fill_const_f64 not found".to_string())
572 })?;
573 let fill_config = LaunchConfig::for_num_elems(row_cap_u32);
574 unsafe {
576 fill_f64
577 .clone()
578 .launch(fill_config, (f64::NEG_INFINITY, row_cap_u32, &mut maxs))
579 }
580 .map_err(|e| XlogError::Kernel(format!("Failed to init maxs: {}", e)))?;
581 device
582 .memset_zeros(&mut sumexps)
583 .map_err(|e| XlogError::Kernel(format!("Failed to init sumexps: {}", e)))?;
584
585 let max_func = device
586 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_LOGSUMEXP_MAX)
587 .ok_or_else(|| {
588 XlogError::Kernel("groupby_logsumexp_max kernel not found".to_string())
589 })?;
590
591 unsafe {
593 max_func
594 .clone()
595 .launch(config, (&values_f64, &group_ids, num_rows, &maxs))
596 }
597 .map_err(|e| {
598 XlogError::Kernel(format!("groupby_logsumexp_max failed: {}", e))
599 })?;
600
601 self.device.synchronize()?;
602
603 let sumexp_func = device
604 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_LOGSUMEXP_SUMEXP)
605 .ok_or_else(|| {
606 XlogError::Kernel(
607 "groupby_logsumexp_sumexp kernel not found".to_string(),
608 )
609 })?;
610
611 unsafe {
613 sumexp_func
614 .clone()
615 .launch(config, (&values_f64, &group_ids, &maxs, num_rows, &sumexps))
616 }
617 .map_err(|e| {
618 XlogError::Kernel(format!("groupby_logsumexp_sumexp failed: {}", e))
619 })?;
620
621 self.device.synchronize()?;
622
623 let final_config = LaunchConfig::for_num_elems(row_cap_u32);
624 let final_func = device
625 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_LOGSUMEXP_FINAL)
626 .ok_or_else(|| {
627 XlogError::Kernel(
628 "groupby_logsumexp_final kernel not found".to_string(),
629 )
630 })?;
631
632 unsafe {
634 final_func.clone().launch(
635 final_config,
636 (&maxs, &sumexps, &d_num_groups, row_cap_u32, &results),
637 )
638 }
639 .map_err(|e| {
640 XlogError::Kernel(format!("groupby_logsumexp_final failed: {}", e))
641 })?;
642
643 self.device.synchronize()?;
644 agg_columns.push(results.into());
645 }
646 }
647 }
648
649 let mut result_columns: Vec<CudaColumn> = Vec::with_capacity(key_cols.len() + aggs.len());
651
652 let group_packed_bytes = row_cap_usize
653 .checked_mul(packed.key_bytes as usize)
654 .ok_or_else(|| XlogError::Kernel("GroupBy packed size overflow".to_string()))?;
655 let mut group_packed = self.memory.alloc::<u8>(group_packed_bytes)?;
656
657 let gather_fn = device
658 .get_func(PACK_MODULE, pack_kernels::GATHER_PACKED_ROWS_COUNTED)
659 .ok_or_else(|| {
660 XlogError::Kernel("gather_packed_rows_counted kernel not found".to_string())
661 })?;
662 let gather_config = LaunchConfig::for_num_elems(row_cap_u32);
663
664 unsafe {
666 gather_fn.clone().launch(
667 gather_config,
668 (
669 &packed.packed_keys,
670 packed.key_bytes,
671 &group_first_idx,
672 &d_num_groups,
673 row_cap_u32,
674 &mut group_packed,
675 ),
676 )
677 }
678 .map_err(|e| XlogError::Kernel(format!("gather_packed_rows failed: {}", e)))?;
679
680 let mut col_offsets: Vec<u32> = Vec::with_capacity(key_cols.len());
681 let mut col_sizes: Vec<u32> = Vec::with_capacity(key_cols.len());
682 let mut offset = 0u32;
683 for &key_col in key_cols {
684 let size = buffer
685 .schema()
686 .column_type(key_col)
687 .map(|t| t.size_bytes() as u32)
688 .unwrap_or(4);
689 col_offsets.push(offset);
690 col_sizes.push(size);
691 offset = offset
692 .checked_add(size)
693 .ok_or_else(|| XlogError::Kernel("GroupBy key size overflow".to_string()))?;
694 }
695
696 let unpack_fn = device
697 .get_func(PACK_MODULE, pack_kernels::UNPACK_COLUMN_COUNTED)
698 .ok_or_else(|| {
699 XlogError::Kernel("unpack_column_counted kernel not found".to_string())
700 })?;
701 let unpack_config = LaunchConfig::for_num_elems(row_cap_u32);
702
703 for idx in 0..key_cols.len() {
704 let col_size = col_sizes[idx];
705 let col_offset = col_offsets[idx];
706 let out_bytes = row_cap_usize
707 .checked_mul(col_size as usize)
708 .ok_or_else(|| XlogError::Kernel("GroupBy key column overflow".to_string()))?;
709 let mut out_col = self.memory.alloc::<u8>(out_bytes)?;
710
711 unsafe {
713 unpack_fn.clone().launch(
714 unpack_config,
715 (
716 &group_packed,
717 packed.key_bytes,
718 col_offset,
719 col_size,
720 &d_num_groups,
721 row_cap_u32,
722 &mut out_col,
723 ),
724 )
725 }
726 .map_err(|e| XlogError::Kernel(format!("unpack_column failed: {}", e)))?;
727
728 result_columns.push(out_col.into());
729 }
730
731 result_columns.extend(agg_columns);
732
733 let result_schema = self.groupby_multi_agg_result_schema(buffer.schema(), key_cols, aggs);
734
735 Ok(CudaBuffer::from_columns(
736 result_columns,
737 row_cap,
738 d_num_groups,
739 result_schema,
740 ))
741 }
742
743 fn capture_num_groups(
744 &self,
745 boundary_pos: &TrackedCudaSlice<u32>,
746 boundaries: &TrackedCudaSlice<u8>,
747 num_rows: u32,
748 ) -> Result<TrackedCudaSlice<u32>> {
749 let mut d_num_groups = self.memory.alloc::<u32>(1)?;
750 let capture_fn = self
751 .device
752 .inner()
753 .get_func(GROUPBY_MODULE, groupby_kernels::CAPTURE_NUM_GROUPS)
754 .ok_or_else(|| XlogError::Kernel("capture_num_groups kernel not found".to_string()))?;
755 unsafe {
757 capture_fn.clone().launch(
758 LaunchConfig {
759 grid_dim: (1, 1, 1),
760 block_dim: (1, 1, 1),
761 shared_mem_bytes: 0,
762 },
763 (boundary_pos, boundaries, num_rows, &mut d_num_groups),
764 )
765 }
766 .map_err(|e| XlogError::Kernel(format!("capture_num_groups failed: {}", e)))?;
767 Ok(d_num_groups)
768 }
769
770 pub(crate) fn groupby_multi_agg_result_schema(
772 &self,
773 input: &Schema,
774 key_cols: &[usize],
775 aggs: &[(usize, AggOp)],
776 ) -> Schema {
777 let mut columns: Vec<(String, ScalarType)> = key_cols
778 .iter()
779 .filter_map(|&i| input.columns.get(i).cloned())
780 .collect();
781 let mut sort_labels: Vec<String> = key_cols
782 .iter()
783 .filter_map(|&i| {
784 input
785 .column_sort_label(i)
786 .map(ToString::to_string)
787 .or_else(|| input.columns.get(i).map(|(name, _)| name.clone()))
788 })
789 .collect();
790
791 for (i, &(value_col, agg_op)) in aggs.iter().enumerate() {
792 let agg_name = match agg_op {
793 AggOp::Count => format!("count_{}", i),
794 AggOp::Sum => format!("sum_{}", i),
795 AggOp::Min => format!("min_{}", i),
796 AggOp::Max => format!("max_{}", i),
797 AggOp::LogSumExp => format!("logsumexp_{}", i),
798 };
799 let agg_type = match agg_op {
802 AggOp::Count => ScalarType::U64,
803 AggOp::Sum => ScalarType::U64,
804 AggOp::Min | AggOp::Max => match input.columns.get(value_col).map(|(_, ty)| *ty) {
807 Some(ScalarType::U64) => ScalarType::U64,
808 _ => ScalarType::U32,
809 },
810 AggOp::LogSumExp => ScalarType::F64,
811 };
812 columns.push((agg_name, agg_type));
813 sort_labels.push(format!("aggregate_{}", i));
814 }
815
816 Schema::new(columns)
817 .with_sort_labels(sort_labels)
818 .expect("groupby result sort labels match column arity")
819 }
820
821 pub(super) fn pack_keys_gpu_on_stream(
842 &self,
843 buffer: &CudaBuffer,
844 key_cols: &[usize],
845 cu_stream: &cudarc::driver::CudaStream,
846 launch_stream: crate::device_runtime::StreamId,
847 runtime: &std::sync::Arc<crate::device_runtime::XlogDeviceRuntime>,
848 ) -> Result<crate::provider::PackedKeyData> {
849 use crate::launch::LaunchRecorder;
850
851 if key_cols.is_empty() {
852 return Err(XlogError::Kernel(
853 "pack_keys_gpu_on_stream: no key columns specified".to_string(),
854 ));
855 }
856 if key_cols.len() > 4 {
857 return Err(XlogError::Kernel(
858 "pack_keys_gpu_on_stream: max 4 key columns supported".to_string(),
859 ));
860 }
861 let num_rows = self.device_row_count(buffer)?;
862 if num_rows > u32::MAX as usize {
863 return Err(XlogError::Kernel(format!(
864 "pack_keys_gpu_on_stream supports at most {} rows, got {}",
865 u32::MAX,
866 num_rows
867 )));
868 }
869 let num_rows = num_rows as u32;
870
871 let mut col_sizes_host: Vec<u32> = Vec::with_capacity(key_cols.len());
872 let mut row_size: u32 = 0;
873 for &col_idx in key_cols {
874 let ty = buffer
875 .schema()
876 .column_type(col_idx)
877 .ok_or_else(|| XlogError::Kernel(format!("Invalid column index: {}", col_idx)))?;
878 let s = ty.size_bytes() as u32;
879 col_sizes_host.push(s);
880 row_size += s;
881 }
882
883 if num_rows == 0 {
884 return Ok(crate::provider::PackedKeyData {
885 hashes: self.memory.alloc::<u64>(0)?,
886 packed_keys: self.memory.alloc::<u8>(0)?,
887 key_bytes: row_size,
888 });
889 }
890
891 let packed_bytes = (num_rows as u64) * (row_size as u64);
892 let packed_slice = self.memory.alloc::<u8>(packed_bytes as usize)?;
893 let hash_slice = self.memory.alloc::<u64>(num_rows as usize)?;
894
895 let mut col_ptrs: [u64; 4] = [0; 4];
896 for (i, &col_idx) in key_cols.iter().enumerate() {
897 let col = buffer
898 .column(col_idx)
899 .ok_or_else(|| XlogError::Kernel(format!("Key column {} not found", col_idx)))?;
900 col_ptrs[i] = *col.device_ptr();
901 }
902 let mut packed_col_sizes = 0u64;
903 for (i, size) in col_sizes_host.iter().copied().enumerate() {
904 if size > u16::MAX as u32 {
905 return Err(XlogError::Kernel(format!(
906 "pack_keys_gpu_on_stream: column element size {} exceeds 16-bit kernel argument",
907 size
908 )));
909 }
910 packed_col_sizes |= (size as u64) << (i * 16);
911 }
912
913 let mut rec = LaunchRecorder::new_strict(launch_stream);
921 for &col_idx in key_cols {
922 let col = buffer
923 .column(col_idx)
924 .ok_or_else(|| XlogError::Kernel(format!("Key column {} not found", col_idx)))?;
925 rec.read_column(col);
926 }
927 rec.write(&packed_slice);
928 rec.write(&hash_slice);
929 rec.preflight(runtime).map_err(|e| {
930 XlogError::Kernel(format!(
931 "pack_keys_gpu_on_stream: launch recorder preflight failed: {}",
932 e
933 ))
934 })?;
935
936 let func = self
937 .device
938 .inner()
939 .get_func(PACK_MODULE, pack_kernels::PACK_AND_HASH_KEYS)
940 .ok_or_else(|| XlogError::Kernel("pack_and_hash_keys kernel not found".to_string()))?;
941 let block_size = 256u32;
942 let grid_size = num_rows.div_ceil(block_size);
943 let cfg = LaunchConfig {
944 grid_dim: (grid_size, 1, 1),
945 block_dim: (block_size, 1, 1),
946 shared_mem_bytes: 0,
947 };
948 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
950 unsafe {
951 func.clone().launch_in(
952 stream,
953 cfg,
954 (
955 col_ptrs[0],
956 col_ptrs[1],
957 col_ptrs[2],
958 col_ptrs[3],
959 packed_col_sizes,
960 key_cols.len() as u32,
961 num_rows,
962 row_size,
963 &packed_slice,
964 &hash_slice,
965 ),
966 )
967 }
968 .map_err(|e| {
969 XlogError::Kernel(format!("pack_and_hash_keys (on_stream) failed: {}", e))
970 })?;
971 Ok(())
972 };
973 let rec = unsafe { rec.enqueue_prepared_with(cu_stream, enqueue) }
975 .map_err(|error| error.into_xlog_error())?;
976
977 rec.commit().map_err(|e| {
983 XlogError::Kernel(format!(
984 "pack_keys_gpu_on_stream: launch recorder commit failed: {}",
985 e
986 ))
987 })?;
988
989 Ok(crate::provider::PackedKeyData {
990 hashes: hash_slice,
991 packed_keys: packed_slice,
992 key_bytes: row_size,
993 })
994 }
995
996 fn memset_zeros_u8_on_stream(
1000 &self,
1001 buf: &mut TrackedCudaSlice<u8>,
1002 enqueue: &crate::launch::CudaEnqueue<'_>,
1003 ) -> Result<()> {
1004 if buf.is_empty() {
1005 return Ok(());
1006 }
1007 let ptr = *buf.device_ptr();
1008 let len = <TrackedCudaSlice<u8> as crate::DeviceSlice<u8>>::len(buf);
1009 unsafe {
1014 let res =
1015 cudarc::driver::sys::cuMemsetD8Async(ptr, 0, len, enqueue.stream().cu_stream());
1016 if res != cudarc::driver::sys::cudaError_enum::CUDA_SUCCESS {
1017 return Err(XlogError::Kernel(format!(
1018 "cuMemsetD8Async (groupby init) failed: {:?}",
1019 res
1020 )));
1021 }
1022 }
1023 Ok(())
1024 }
1025
1026 pub fn groupby_multi_agg_recorded(
1059 &self,
1060 buffer: &CudaBuffer,
1061 key_cols: &[usize],
1062 aggs: &[(usize, AggOp)],
1063 launch_stream: crate::device_runtime::StreamId,
1064 ) -> Result<CudaBuffer> {
1065 use crate::launch::LaunchRecorder;
1066
1067 let runtime = self.memory.runtime().ok_or_else(|| {
1068 XlogError::Kernel(
1069 "groupby_multi_agg_recorded requires a runtime-backed GpuMemoryManager".to_string(),
1070 )
1071 })?;
1072 let cu_stream = runtime
1073 .stream_pool()
1074 .resolve(launch_stream)
1075 .ok_or_else(|| {
1076 XlogError::Kernel(format!(
1077 "groupby_multi_agg_recorded: launch_stream StreamId({}) does not resolve",
1078 launch_stream.0
1079 ))
1080 })?;
1081
1082 let num_rows = self.device_row_count(buffer)?;
1083 if num_rows == 0 {
1084 let result_schema =
1085 self.groupby_multi_agg_result_schema(buffer.schema(), key_cols, aggs);
1086 return self.create_empty_buffer(result_schema);
1087 }
1088 if num_rows > u32::MAX as usize {
1089 return Err(XlogError::Kernel(format!(
1090 "GroupBy supports at most {} rows, got {}",
1091 u32::MAX,
1092 num_rows
1093 )));
1094 }
1095 if key_cols.is_empty() {
1096 return Err(XlogError::Kernel(
1097 "GroupBy requires at least one key column".to_string(),
1098 ));
1099 }
1100 if aggs.is_empty() {
1101 return Err(XlogError::Kernel(
1102 "GroupBy requires at least one aggregation".to_string(),
1103 ));
1104 }
1105 if key_cols.len() > 4 {
1106 return Err(XlogError::Kernel(
1107 "groupby_multi_agg_recorded: max 4 key columns supported (pack_keys constraint)"
1108 .to_string(),
1109 ));
1110 }
1111 for &k in key_cols {
1112 if k >= buffer.arity() {
1113 return Err(XlogError::Kernel(format!(
1114 "Key column {} out of bounds (arity {})",
1115 k,
1116 buffer.arity()
1117 )));
1118 }
1119 let ty = buffer
1120 .schema()
1121 .column_type(k)
1122 .ok_or_else(|| XlogError::Kernel("Key column has no type".to_string()))?;
1123 if !matches!(ty, ScalarType::U32 | ScalarType::Symbol) {
1124 return Err(XlogError::Kernel(format!(
1125 "groupby_multi_agg_recorded: key column type {:?} unsupported (U32 / Symbol \
1126 only); multi-type sort_recorded is deferred",
1127 ty
1128 )));
1129 }
1130 }
1131 for &(value_col, agg_op) in aggs {
1132 if value_col >= buffer.arity() {
1133 return Err(XlogError::Kernel(format!(
1134 "Value column {} out of bounds (arity {})",
1135 value_col,
1136 buffer.arity()
1137 )));
1138 }
1139 let value_ty = buffer
1140 .schema()
1141 .column_type(value_col)
1142 .ok_or_else(|| XlogError::Kernel("Value column has no type".to_string()))?;
1143 match agg_op {
1144 AggOp::Count => {}
1145 AggOp::Sum => {
1146 if !matches!(value_ty, ScalarType::U32 | ScalarType::U64) {
1149 return Err(XlogError::Kernel(format!(
1150 "Sum currently requires U32 or U64 values, got {:?}",
1151 value_ty
1152 )));
1153 }
1154 }
1155 AggOp::Min | AggOp::Max => {
1156 if !matches!(value_ty, ScalarType::U32 | ScalarType::U64) {
1160 return Err(XlogError::Kernel(format!(
1161 "{:?} currently requires U32 or U64 values, got {:?}",
1162 agg_op, value_ty
1163 )));
1164 }
1165 }
1166 AggOp::LogSumExp => {
1167 return Err(XlogError::Kernel(
1168 "groupby_multi_agg_recorded: LogSumExp not yet supported in the \
1169 recorded path (multi-kernel chain deferred to a future implementation)"
1170 .to_string(),
1171 ));
1172 }
1173 }
1174 }
1175
1176 let sorted = self.sort_recorded(buffer, key_cols, launch_stream)?;
1178 let num_rows = self.device_row_count(&sorted)?;
1179 if num_rows > u32::MAX as usize {
1180 return Err(XlogError::Kernel(format!(
1181 "GroupBy supports at most {} rows, got {}",
1182 u32::MAX,
1183 num_rows
1184 )));
1185 }
1186 let num_rows = num_rows as u32;
1187 let row_cap_usize = num_rows as usize;
1188 let row_cap_u32 = num_rows;
1189 let row_cap_u64 = num_rows as u64;
1190
1191 let packed =
1193 self.pack_keys_gpu_on_stream(&sorted, key_cols, &cu_stream, launch_stream, runtime)?;
1194 if packed.key_bytes == 0 || packed.key_bytes % 4 != 0 {
1195 return Err(XlogError::Kernel(format!(
1196 "GroupBy key packing produced {} bytes per row (expected multiple of 4); \
1197 Bool keys are not supported",
1198 packed.key_bytes
1199 )));
1200 }
1201 let segments_per_row = (packed.key_bytes / 4) as usize;
1202 let total_segments = row_cap_usize * segments_per_row;
1203 let packed_u32 = self.bytes_as_u32_view(&packed.packed_keys, total_segments)?;
1204
1205 let boundaries = self.memory.alloc::<u8>(row_cap_usize)?;
1210 let block_size = 256u32;
1211 let num_blocks = num_rows.div_ceil(block_size);
1212 let cfg = LaunchConfig {
1213 grid_dim: (num_blocks, 1, 1),
1214 block_dim: (block_size, 1, 1),
1215 shared_mem_bytes: 0,
1216 };
1217 let d_boundary_pos = self.memory.alloc::<u32>(row_cap_usize)?;
1218 let mut d_block_sums = self.memory.alloc::<u32>(num_blocks as usize)?;
1219 let mut d_num_groups = self.memory.alloc::<u32>(1)?;
1220 let mut group_ids = self.memory.alloc::<u32>(row_cap_usize)?;
1221 let mut group_first_idx = self.memory.alloc::<u32>(row_cap_usize)?;
1222
1223 let mut agg_outputs: Vec<TrackedCudaSlice<u8>> = Vec::with_capacity(aggs.len());
1225 for &(value_col, agg_op) in aggs {
1226 let elem_size = match agg_op {
1227 AggOp::Count | AggOp::Sum => std::mem::size_of::<u64>(),
1228 AggOp::Min | AggOp::Max => match sorted.schema().column_type(value_col) {
1230 Some(ScalarType::U64) => std::mem::size_of::<u64>(),
1231 _ => std::mem::size_of::<u32>(),
1232 },
1233 AggOp::LogSumExp => unreachable!("rejected above"),
1234 };
1235 let bytes = row_cap_usize
1236 .checked_mul(elem_size)
1237 .ok_or_else(|| XlogError::Kernel("groupby agg output size overflow".to_string()))?;
1238 agg_outputs.push(self.memory.alloc::<u8>(bytes)?);
1239 }
1240
1241 let group_packed_bytes = row_cap_usize
1243 .checked_mul(packed.key_bytes as usize)
1244 .ok_or_else(|| XlogError::Kernel("GroupBy packed size overflow".to_string()))?;
1245 let mut group_packed = self.memory.alloc::<u8>(group_packed_bytes)?;
1246
1247 let mut col_offsets: Vec<u32> = Vec::with_capacity(key_cols.len());
1248 let mut col_sizes: Vec<u32> = Vec::with_capacity(key_cols.len());
1249 let mut offset = 0u32;
1250 for &key_col in key_cols {
1251 let s = buffer
1252 .schema()
1253 .column_type(key_col)
1254 .map(|t| t.size_bytes() as u32)
1255 .unwrap_or(4);
1256 col_offsets.push(offset);
1257 col_sizes.push(s);
1258 offset = offset
1259 .checked_add(s)
1260 .ok_or_else(|| XlogError::Kernel("GroupBy key size overflow".to_string()))?;
1261 }
1262 let mut key_unpacked: Vec<TrackedCudaSlice<u8>> = Vec::with_capacity(key_cols.len());
1263 for &col_size in &col_sizes {
1264 let bytes = row_cap_usize
1265 .checked_mul(col_size as usize)
1266 .ok_or_else(|| XlogError::Kernel("GroupBy key column overflow".to_string()))?;
1267 key_unpacked.push(self.memory.alloc::<u8>(bytes)?);
1268 }
1269
1270 let mut scan_scratch = self.multiblock_scan_u32_scratch_for_len(num_blocks)?;
1277 let mut rec = LaunchRecorder::new_strict(launch_stream);
1278 rec.read(sorted.num_rows_device());
1279 rec.read(&packed.packed_keys);
1283 for &(value_col, _) in aggs {
1284 let c = sorted.column(value_col).ok_or_else(|| {
1285 XlogError::Kernel(format!("Value column {} not found", value_col))
1286 })?;
1287 rec.read_column(c);
1288 }
1289 rec.write(&boundaries);
1290 rec.write(&d_boundary_pos);
1291 rec.write(&d_block_sums);
1292 rec.write(&d_num_groups);
1293 rec.write(&group_ids);
1294 rec.write(&group_first_idx);
1295 rec.write(&group_packed);
1296 for o in &agg_outputs {
1297 rec.write(o);
1298 }
1299 for k in &key_unpacked {
1300 rec.write(k);
1301 }
1302 for level in scan_scratch.levels() {
1303 rec.read_write(level);
1304 }
1305 rec.preflight(runtime).map_err(|e| {
1306 XlogError::Kernel(format!(
1307 "groupby_multi_agg_recorded: preflight failed: {}",
1308 e
1309 ))
1310 })?;
1311
1312 let device = self.device.inner();
1313
1314 let boundary_func = device
1316 .get_func(GROUPBY_MODULE, groupby_kernels::DETECT_GROUP_BOUNDARIES)
1317 .ok_or_else(|| {
1318 XlogError::Kernel("detect_group_boundaries kernel not found".to_string())
1319 })?;
1320 let enqueue = |stream: &crate::launch::CudaEnqueue<'_>| -> Result<()> {
1322 unsafe {
1323 boundary_func.clone().launch_in(
1324 stream,
1325 cfg,
1326 (
1327 &packed_u32,
1328 num_rows,
1329 segments_per_row as u32,
1330 segments_per_row as u32,
1331 &boundaries,
1332 ),
1333 )
1334 }
1335 .map_err(|e| {
1336 XlogError::Kernel(format!("detect_group_boundaries (on_stream) failed: {}", e))
1337 })?;
1338
1339 let phase1_fn = device
1341 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE1)
1342 .ok_or_else(|| {
1343 XlogError::Kernel("Failed to get multiblock_scan_phase1 kernel".to_string())
1344 })?;
1345 unsafe {
1347 phase1_fn.clone().launch_in(
1348 stream,
1349 LaunchConfig {
1350 grid_dim: (num_blocks, 1, 1),
1351 block_dim: (block_size, 1, 1),
1352 shared_mem_bytes: 0,
1353 },
1354 (&boundaries, &d_boundary_pos, &d_block_sums, num_rows),
1355 )
1356 }
1357 .map_err(|e| {
1358 XlogError::Kernel(format!("multiblock_scan_phase1 (on_stream) failed: {}", e))
1359 })?;
1360
1361 if num_blocks > 1 {
1362 self.multiblock_scan_u32_inplace_on_stream(
1363 &mut d_block_sums,
1364 num_blocks,
1365 stream,
1366 &mut scan_scratch,
1367 )?;
1368 let phase3_fn = device
1369 .get_func(SCAN_MODULE, scan_kernels::MULTIBLOCK_SCAN_PHASE3)
1370 .ok_or_else(|| {
1371 XlogError::Kernel("Failed to get multiblock_scan_phase3 kernel".to_string())
1372 })?;
1373 unsafe {
1375 phase3_fn.clone().launch_in(
1376 stream,
1377 LaunchConfig {
1378 grid_dim: (num_blocks, 1, 1),
1379 block_dim: (block_size, 1, 1),
1380 shared_mem_bytes: 0,
1381 },
1382 (&d_boundary_pos, &d_block_sums, num_rows),
1383 )
1384 }
1385 .map_err(|e| {
1386 XlogError::Kernel(format!("multiblock_scan_phase3 (on_stream) failed: {}", e))
1387 })?;
1388 }
1389
1390 let capture_fn = device
1392 .get_func(GROUPBY_MODULE, groupby_kernels::CAPTURE_NUM_GROUPS)
1393 .ok_or_else(|| {
1394 XlogError::Kernel("capture_num_groups kernel not found".to_string())
1395 })?;
1396 unsafe {
1398 capture_fn.clone().launch_in(
1399 stream,
1400 LaunchConfig {
1401 grid_dim: (1, 1, 1),
1402 block_dim: (1, 1, 1),
1403 shared_mem_bytes: 0,
1404 },
1405 (&d_boundary_pos, &boundaries, num_rows, &mut d_num_groups),
1406 )
1407 }
1408 .map_err(|e| {
1409 XlogError::Kernel(format!("capture_num_groups (on_stream) failed: {}", e))
1410 })?;
1411
1412 let group_ids_fn = device
1414 .get_func(GROUPBY_MODULE, groupby_kernels::GROUP_IDS_FROM_BOUNDARIES)
1415 .ok_or_else(|| {
1416 XlogError::Kernel("group_ids_from_boundaries kernel not found".to_string())
1417 })?;
1418 let group_start_fn = device
1419 .get_func(GROUPBY_MODULE, groupby_kernels::GROUP_START_INDICES)
1420 .ok_or_else(|| {
1421 XlogError::Kernel("group_start_indices kernel not found".to_string())
1422 })?;
1423 unsafe {
1425 group_ids_fn.clone().launch_in(
1426 stream,
1427 cfg,
1428 (&boundaries, &d_boundary_pos, num_rows, &mut group_ids),
1429 )
1430 }
1431 .map_err(|e| {
1432 XlogError::Kernel(format!(
1433 "group_ids_from_boundaries (on_stream) failed: {}",
1434 e
1435 ))
1436 })?;
1437 unsafe {
1438 group_start_fn.clone().launch_in(
1439 stream,
1440 cfg,
1441 (&boundaries, &d_boundary_pos, num_rows, &mut group_first_idx),
1442 )
1443 }
1444 .map_err(|e| {
1445 XlogError::Kernel(format!("group_start_indices (on_stream) failed: {}", e))
1446 })?;
1447
1448 for ((value_col, agg_op), output) in aggs.iter().zip(agg_outputs.iter_mut()) {
1450 let values = sorted.column(*value_col).ok_or_else(|| {
1451 XlogError::Kernel(format!("Value column {} not found", value_col))
1452 })?;
1453 match agg_op {
1454 AggOp::Count => {
1455 self.memset_zeros_u8_on_stream(output, stream)?;
1456 let count_func = device
1457 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_COUNT)
1458 .ok_or_else(|| {
1459 XlogError::Kernel("groupby_count kernel not found".to_string())
1460 })?;
1461 unsafe {
1463 count_func.clone().launch_in(
1464 stream,
1465 cfg,
1466 (&boundaries, &group_ids, num_rows, &*output),
1467 )
1468 }
1469 .map_err(|e| {
1470 XlogError::Kernel(format!("groupby_count (on_stream) failed: {}", e))
1471 })?;
1472 }
1473 AggOp::Sum => {
1474 self.memset_zeros_u8_on_stream(output, stream)?;
1475 let value_ty =
1476 sorted.schema().column_type(*value_col).ok_or_else(|| {
1477 XlogError::Kernel("Value column has no type".to_string())
1478 })?;
1479 if value_ty == ScalarType::U64 {
1480 let values_view = self.column_as_u64_view(values, row_cap_usize)?;
1481 let sum_func = device
1482 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_SUM_U64)
1483 .ok_or_else(|| {
1484 XlogError::Kernel(
1485 "groupby_sum_u64 kernel not found".to_string(),
1486 )
1487 })?;
1488 unsafe {
1490 sum_func.clone().launch_in(
1491 stream,
1492 cfg,
1493 (&values_view, &group_ids, num_rows, &*output),
1494 )
1495 }
1496 .map_err(|e| {
1497 XlogError::Kernel(format!(
1498 "groupby_sum_u64 (on_stream) failed: {}",
1499 e
1500 ))
1501 })?;
1502 } else {
1503 let values_view = self.column_as_u32_view(values, row_cap_usize)?;
1504 let sum_func = device
1505 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_SUM)
1506 .ok_or_else(|| {
1507 XlogError::Kernel("groupby_sum kernel not found".to_string())
1508 })?;
1509 unsafe {
1511 sum_func.clone().launch_in(
1512 stream,
1513 cfg,
1514 (&values_view, &group_ids, num_rows, &*output),
1515 )
1516 }
1517 .map_err(|e| {
1518 XlogError::Kernel(format!("groupby_sum (on_stream) failed: {}", e))
1519 })?;
1520 }
1521 }
1522 AggOp::Min => {
1523 let value_ty =
1524 sorted.schema().column_type(*value_col).ok_or_else(|| {
1525 XlogError::Kernel("Value column has no type".to_string())
1526 })?;
1527 let fill_config = LaunchConfig::for_num_elems(row_cap_u32);
1528 if value_ty == ScalarType::U64 {
1529 let fill_fn = device
1532 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U64)
1533 .ok_or_else(|| {
1534 XlogError::Kernel("arith_fill_const_u64 not found".to_string())
1535 })?;
1536 unsafe {
1538 fill_fn.clone().launch_in(
1539 stream,
1540 fill_config,
1541 (u64::MAX, row_cap_u32, &mut *output),
1542 )
1543 }
1544 .map_err(|e| {
1545 XlogError::Kernel(format!(
1546 "arith_fill_const_u64 (on_stream) failed: {}",
1547 e
1548 ))
1549 })?;
1550 let values_view = self.column_as_u64_view(values, row_cap_usize)?;
1551 let min_func = device
1552 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MIN_U64)
1553 .ok_or_else(|| {
1554 XlogError::Kernel(
1555 "groupby_min_u64 kernel not found".to_string(),
1556 )
1557 })?;
1558 unsafe {
1560 min_func.clone().launch_in(
1561 stream,
1562 cfg,
1563 (&values_view, &group_ids, num_rows, &*output),
1564 )
1565 }
1566 .map_err(|e| {
1567 XlogError::Kernel(format!(
1568 "groupby_min_u64 (on_stream) failed: {}",
1569 e
1570 ))
1571 })?;
1572 } else {
1573 let fill_fn = device
1574 .get_func(ARITH_MODULE, arith_kernels::ARITH_FILL_CONST_U32)
1575 .ok_or_else(|| {
1576 XlogError::Kernel("arith_fill_const_u32 not found".to_string())
1577 })?;
1578 unsafe {
1580 fill_fn.clone().launch_in(
1581 stream,
1582 fill_config,
1583 (u32::MAX, row_cap_u32, &mut *output),
1584 )
1585 }
1586 .map_err(|e| {
1587 XlogError::Kernel(format!(
1588 "arith_fill_const_u32 (on_stream) failed: {}",
1589 e
1590 ))
1591 })?;
1592 let values_view = self.column_as_u32_view(values, row_cap_usize)?;
1593 let min_func = device
1594 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MIN)
1595 .ok_or_else(|| {
1596 XlogError::Kernel("groupby_min kernel not found".to_string())
1597 })?;
1598 unsafe {
1600 min_func.clone().launch_in(
1601 stream,
1602 cfg,
1603 (&values_view, &group_ids, num_rows, &*output),
1604 )
1605 }
1606 .map_err(|e| {
1607 XlogError::Kernel(format!("groupby_min (on_stream) failed: {}", e))
1608 })?;
1609 }
1610 }
1611 AggOp::Max => {
1612 self.memset_zeros_u8_on_stream(output, stream)?;
1613 let value_ty =
1614 sorted.schema().column_type(*value_col).ok_or_else(|| {
1615 XlogError::Kernel("Value column has no type".to_string())
1616 })?;
1617 if value_ty == ScalarType::U64 {
1618 let values_view = self.column_as_u64_view(values, row_cap_usize)?;
1621 let max_func = device
1622 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MAX_U64)
1623 .ok_or_else(|| {
1624 XlogError::Kernel(
1625 "groupby_max_u64 kernel not found".to_string(),
1626 )
1627 })?;
1628 unsafe {
1630 max_func.clone().launch_in(
1631 stream,
1632 cfg,
1633 (&values_view, &group_ids, num_rows, &*output),
1634 )
1635 }
1636 .map_err(|e| {
1637 XlogError::Kernel(format!(
1638 "groupby_max_u64 (on_stream) failed: {}",
1639 e
1640 ))
1641 })?;
1642 } else {
1643 let values_view = self.column_as_u32_view(values, row_cap_usize)?;
1644 let max_func = device
1645 .get_func(GROUPBY_MODULE, groupby_kernels::GROUPBY_MAX)
1646 .ok_or_else(|| {
1647 XlogError::Kernel("groupby_max kernel not found".to_string())
1648 })?;
1649 unsafe {
1651 max_func.clone().launch_in(
1652 stream,
1653 cfg,
1654 (&values_view, &group_ids, num_rows, &*output),
1655 )
1656 }
1657 .map_err(|e| {
1658 XlogError::Kernel(format!("groupby_max (on_stream) failed: {}", e))
1659 })?;
1660 }
1661 }
1662 AggOp::LogSumExp => unreachable!("rejected above"),
1663 }
1664 }
1665
1666 let gather_fn = device
1668 .get_func(PACK_MODULE, pack_kernels::GATHER_PACKED_ROWS_COUNTED)
1669 .ok_or_else(|| {
1670 XlogError::Kernel("gather_packed_rows_counted kernel not found".to_string())
1671 })?;
1672 let gather_config = LaunchConfig::for_num_elems(row_cap_u32);
1673 unsafe {
1675 gather_fn.clone().launch_in(
1676 stream,
1677 gather_config,
1678 (
1679 &packed.packed_keys,
1680 packed.key_bytes,
1681 &group_first_idx,
1682 &d_num_groups,
1683 row_cap_u32,
1684 &mut group_packed,
1685 ),
1686 )
1687 }
1688 .map_err(|e| {
1689 XlogError::Kernel(format!(
1690 "gather_packed_rows_counted (on_stream) failed: {}",
1691 e
1692 ))
1693 })?;
1694
1695 let unpack_fn = device
1697 .get_func(PACK_MODULE, pack_kernels::UNPACK_COLUMN_COUNTED)
1698 .ok_or_else(|| {
1699 XlogError::Kernel("unpack_column_counted kernel not found".to_string())
1700 })?;
1701 let unpack_config = LaunchConfig::for_num_elems(row_cap_u32);
1702 for idx in 0..key_cols.len() {
1703 let col_size = col_sizes[idx];
1704 let col_offset = col_offsets[idx];
1705 unsafe {
1708 unpack_fn.clone().launch_in(
1709 stream,
1710 unpack_config,
1711 (
1712 &group_packed,
1713 packed.key_bytes,
1714 col_offset,
1715 col_size,
1716 &d_num_groups,
1717 row_cap_u32,
1718 &mut key_unpacked[idx],
1719 ),
1720 )
1721 }
1722 .map_err(|e| {
1723 XlogError::Kernel(format!("unpack_column_counted (on_stream) failed: {}", e))
1724 })?;
1725 }
1726
1727 Ok(())
1728 };
1729 let rec = unsafe { rec.enqueue_prepared_with(&cu_stream, enqueue) }
1731 .map_err(|error| error.into_xlog_error())?;
1732
1733 rec.commit().map_err(|e| {
1737 XlogError::Kernel(format!("groupby_multi_agg_recorded: commit failed: {}", e))
1738 })?;
1739
1740 let mut result_columns: Vec<CudaColumn> = Vec::with_capacity(key_cols.len() + aggs.len());
1742 for k in key_unpacked {
1743 result_columns.push(k.into());
1744 }
1745 for o in agg_outputs {
1746 result_columns.push(o.into());
1747 }
1748 let result_schema = self.groupby_multi_agg_result_schema(buffer.schema(), key_cols, aggs);
1749 Ok(CudaBuffer::from_columns(
1750 result_columns,
1751 row_cap_u64,
1752 d_num_groups,
1753 result_schema,
1754 ))
1755 }
1756
1757 pub fn groupby_agg_recorded(
1761 &self,
1762 input: &CudaBuffer,
1763 key_cols: &[usize],
1764 agg: AggOp,
1765 value_col: usize,
1766 launch_stream: crate::device_runtime::StreamId,
1767 ) -> Result<CudaBuffer> {
1768 self.groupby_multi_agg_recorded(input, key_cols, &[(value_col, agg)], launch_stream)
1769 }
1770}