Skip to content

Add streaming_groupby for stateful streaming aggregation - #21924

Merged
rapids-bot[bot] merged 83 commits into
NVIDIA:release/26.06from
PointKernel:streaming_groupby
May 20, 2026
Merged

Add streaming_groupby for stateful streaming aggregation#21924
rapids-bot[bot] merged 83 commits into
NVIDIA:release/26.06from
PointKernel:streaming_groupby

Conversation

@PointKernel

@PointKernel PointKernel commented Mar 25, 2026

Copy link
Copy Markdown
Member

Description

Closes #18182

This PR adds streaming_groupby, a stateful groupby that accumulates partial aggregates across batches using a single persistent hash table.

Users specify max_groups, the maximum number of distinct groups expected, and all main data structures are allocated once and reused without resizing. The hash table stores a size_type group ID per slot.
The ID is global across the stream: each distinct group, in the order it is first seen, is assigned a stable ID in [0, distinct_count) that is shared by the result table (used as the row index) and by the companion array (used as the lookup index). The actual keys live in a list of per-batch compacted key tables. The companion array, of length max_groups, holds a {batch_id, row_id} pair for each group ID, pointing back to where that group's representative key is stored. Equality probes resolve a slot ID through the companion array into the correct preprocessed batch table and compare via an n-table row comparator.

Each batch is processed in two steps. The first step calls insert_and_find against the hash set. Winners write a transient value max_groups + batch_idx into their slot, which is distinguishable from any real group ID since real IDs live in [0, max_groups). Existing slots already hold a final ID and are returned as-is. A side flag array marks which rows won their slot, and a slot-offset array records where each row landed for cheap revisits. The newly inserted rows are stream-compacted and gathered into a fresh compacted key table that is appended to the per-batch list. The second step walks only the new keys, atomically rewrites their transient slot values to stable global IDs starting at the current distinct count, and writes the matching {batch_id, row_id} entries into the companion array. A final reread converts any remaining transient slot reads into global IDs, so every row in the batch ends up mapped to its stable group ID. Aggregations are updated atomically into a single result table indexed directly by these IDs.

Merging reprobes the other object's compacted keys against this hash table to recover their target group IDs in this object's ID space, then atomically combines the matching result rows. Finalization concatenates the per-batch compacted key tables to produce the distinct-keys output, slices the result table to [0, distinct_count) — no gather is needed, since the global IDs are already the row indices — and runs the compound-aggregation finalizers to produce user-facing columns. The internal state is left intact, so further aggregate calls remain valid.

Certain trade-offs are intentional. For example, the streaming groupby is designed to deep-copy all distinct keys locally. This enables batch-based processing: once a batch has completed the add step, its input data can be released, which helps reduce memory usage.

Additionally, the current code path does not support shared memory. As a result, inputs with very low cardinality can suffer from poor runtime performance due to high atomic contention, since many updates target the same key or memory location. This is an accepted trade-off. In practice, downstream users can run a standard groupby to estimate cardinality; if it is low, they can concatenate all input data and use a regular groupby instead, which typically yields better performance.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@PointKernel PointKernel added feature request New feature or request libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change labels Mar 25, 2026
@github-actions github-actions Bot added the CMake CMake build issue label Mar 25, 2026
Comment thread cpp/examples/billion_rows/brc_chunks.cpp
Comment thread cpp/src/groupby/streaming_groupby.cu Outdated
Comment thread cpp/include/cudf/groupby.hpp
Comment thread cpp/examples/billion_rows/brc_chunks.cpp
Comment thread cpp/tests/groupby/streaming_groupby_test.cpp Outdated
@PointKernel

PointKernel commented Apr 1, 2026

Copy link
Copy Markdown
Member Author

Results of the groupby cardinality benchmark, the input is fixed at 20M elements:
num_aggregations = 1

## groupby_max_cardinality

### [0] NVIDIA RTX PRO 6000 Blackwell Workstation Edition

|  T  | num_rows | num_aggregations | cardinality |    api    | Samples |  CPU Time  | Noise  |  GPU Time  | Noise  | Mrows/s | peak_memory_usage |
|-----|----------|------------------|-------------|-----------|---------|------------|--------|------------|--------|---------|-------------------|
| I32 | 20000000 |                1 |          20 |    normal |   1344x | 673.537 us |  1.64% | 669.366 us |  1.65% |   29879 |       229.769 MiB |
| I32 | 20000000 |                1 |          50 |    normal |    656x | 772.041 us |  0.68% | 767.872 us |  0.68% |   26046 |       229.769 MiB |
| I32 | 20000000 |                1 |         100 |    normal |   1376x | 868.518 us |  1.22% | 864.319 us |  1.22% |   23139 |       229.769 MiB |
| I32 | 20000000 |                1 |        1000 |    normal |   1168x | 877.624 us |  5.05% | 873.417 us |  5.08% |   22898 |       229.769 MiB |
| I32 | 20000000 |                1 |       10000 |    normal |    816x | 769.437 us |  1.38% | 765.224 us |  1.39% |   26136 |       229.769 MiB |
| I32 | 20000000 |                1 |      100000 |    normal |    784x | 777.015 us |  0.60% | 772.874 us |  0.60% |   25877 |       229.769 MiB |
| I32 | 20000000 |                1 |     1000000 |    normal |    545x |   1.029 ms |  0.50% |   1.025 ms |  0.50% |   19520 |       229.769 MiB |
| I32 | 20000000 |                1 |          20 | streaming |    544x |   3.807 ms | 18.69% |   3.803 ms | 18.71% |    5259 |       228.882 MiB |
| I32 | 20000000 |                1 |          50 | streaming |    576x |   2.614 ms | 16.55% |   2.610 ms | 16.57% |    7661 |       228.882 MiB |
| I32 | 20000000 |                1 |         100 | streaming |    544x |   2.105 ms | 12.73% |   2.101 ms | 12.76% |    9521 |       228.882 MiB |
| I32 | 20000000 |                1 |        1000 | streaming |    720x |   1.405 ms |  4.23% |   1.401 ms |  4.23% |   14274 |       228.882 MiB |
| I32 | 20000000 |                1 |       10000 | streaming |    704x |   1.302 ms |  1.90% |   1.297 ms |  1.91% |   15414 |       228.882 MiB |
| I32 | 20000000 |                1 |      100000 | streaming |    624x |   1.235 ms |  0.57% |   1.231 ms |  0.57% |   16249 |       228.882 MiB |
| I32 | 20000000 |                1 |     1000000 | streaming |    289x |   1.738 ms |  0.32% |   1.734 ms |  0.32% |   11534 |       228.882 MiB |

num_aggregations = 8

## groupby_max_cardinality

### [0] NVIDIA RTX PRO 6000 Blackwell Workstation Edition

|  T  | num_rows | num_aggregations | cardinality |    api    | Samples | CPU Time  | Noise  | GPU Time  | Noise  | Mrows/s | peak_memory_usage |
|-----|----------|------------------|-------------|-----------|---------|-----------|--------|-----------|--------|---------|-------------------|
| I32 | 20000000 |                8 |          20 |    normal |   1840x |  1.131 ms |  0.79% |  1.127 ms |  0.79% |  142030 |       229.769 MiB |
| I32 | 20000000 |                8 |          50 |    normal |   1456x |  1.246 ms |  1.58% |  1.242 ms |  1.59% |  128842 |       229.769 MiB |
| I32 | 20000000 |                8 |         100 |    normal |   1728x |  1.374 ms |  0.65% |  1.369 ms |  0.65% |  116846 |       229.769 MiB |
| I32 | 20000000 |                8 |        1000 |    normal |     44x | 11.437 ms |  0.28% | 11.432 ms |  0.28% |   13995 |       306.063 MiB |
| I32 | 20000000 |                8 |       10000 |    normal |   1456x |  2.912 ms |  0.59% |  2.907 ms |  0.59% |   55032 |       306.063 MiB |
| I32 | 20000000 |                8 |      100000 |    normal |    756x |  2.125 ms |  0.50% |  2.120 ms |  0.50% |   75456 |       306.063 MiB |
| I32 | 20000000 |                8 |     1000000 |    normal |    213x |  2.356 ms |  0.32% |  2.352 ms |  0.32% |   68039 |       306.063 MiB |
| I32 | 20000000 |                8 |          20 | streaming |    576x | 15.836 ms | 16.82% | 15.832 ms | 16.82% |   10106 |       228.883 MiB |
| I32 | 20000000 |                8 |          50 | streaming |    544x | 10.132 ms | 10.61% | 10.128 ms | 10.62% |   15797 |       228.883 MiB |
| I32 | 20000000 |                8 |         100 | streaming |    656x |  7.641 ms |  7.21% |  7.637 ms |  7.21% |   20950 |       228.883 MiB |
| I32 | 20000000 |                8 |        1000 | streaming |    160x |  4.301 ms |  3.20% |  4.297 ms |  3.20% |   37238 |       228.883 MiB |
| I32 | 20000000 |                8 |       10000 | streaming |    544x |  3.301 ms |  1.67% |  3.297 ms |  1.67% |   48524 |       228.883 MiB |
| I32 | 20000000 |                8 |      100000 | streaming |    745x |  2.943 ms |  0.50% |  2.939 ms |  0.50% |   54442 |       228.883 MiB |
| I32 | 20000000 |                8 |     1000000 | streaming |    122x |  4.103 ms |  0.19% |  4.099 ms |  0.19% |   39031 |       228.883 MiB |

cc @devavret

@devavret

devavret commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

I have an idea for how you can work with non-fixed width keys. I'll explain here with just keys and not values
Here are our requirements:

  1. Per-batch work should be proportional to batch size and not the size of allocated hash map
  2. Store all unique keys seen so far in a memory efficient way

For each batch, while inserting, we mark the new keys we saw in this batch. Then we'll compact and store it in a way that can be referred to while inserting subsequent batches. We'll end up with several set of key tables held inside the groupby object which should hopefully be less memory than if we had sparsely populated a key table of the same size as the single large hash set.

So we start with a pre-allocated large hash set and similarly sized arrays for values. Along with that, we allocate two more vectors: key_table, key_idx. These refer to the batch we first saw the key in and the index of the key row in the compacted batch. So suppose we allocate an empty hash set with 8 empty slots like so:

idx slot batchIdx keyIdx
0
1
2
3
4
5
6
7

and we receive our first batch of keys

idx key
0 a
1 b
2 c
3 b
4 d
5 c

and they end up in the hash set like so:

idx slot batchIdx keyIdx
0 2
1
2 0
3 4
4
5 3
6
7

while inserting, we remember where each inserted key ended up so we store the slot index in a key batch sized vector

idx key slotIdx
0 a 2
1 b
2 c 0
3 b 5
4 d 3
5 c

Normally we use this to gather and return the unique keys

new idx key slotIdx
0 a 2
1 c 0
2 b 5
3 d 3

but here we'll also use it to mark the location of these keys in the map

idx slot batchIdx keyIdx
0 2 0 1
1
2 0 0 0
3 4 0 3
4
5 3 0 2
6
7

Now the next batch of keys come in

idx key
0 a
1 c
2 e
3 a
4 e

While inserting in the hash set, we have enough information to either refer to this batch itself or to keys seen in batches before. The row comparator would have the following logic

if (slot filled) {
    if (batchIdx filled) { // key seen previous batches
        compare current row with batch[batchIdx].row[rowIdx];
    } else { // key seen in this batch
        compare current row with this_batch.row[slot];
    }
} else {
    insert current index in slot;
    store slotIdx in slotIdx[];
}

After insertion the new batch looks like this

idx key
2 e

And the hash set and associated vectors look like this

idx slot batchIdx keyIdx
0 2 0 1
1
2 0 0 0
3 4 0 3
4
5 3 0 2
6
7 2 1 0

@PointKernel

Copy link
Copy Markdown
Member Author
    compare current row with batch[batchIdx].row[rowIdx];

@devavret Thanks for sharing your proposal. My main question is about how the comparison would actually be performed. cudf row operators are designed for either self-comparison or comparing two tables. How would we compare a row in the new batch against a row from a previous batch? Are you suggesting storing all preprocessed keys from previous batches so that we can then use a two-table comparator between the new batch and a specific previous batch?

There’s also a subtle issue around atomically updating the associated vectors. For example, if thread A wins the hash table CAS and inserts into slot S, there’s a window before it writes key_table[S] and key_idx[S]. During that time, thread B may probe slot S, see it as occupied, and read (partial) uninitialized data from those arrays.

The root cause is that the hash set slot and the associated metadata arrays live in separate memory locations where cuco’s atomic CAS only protects the slot itself, not the side arrays. There’s no straightforward way to atomically update all three together without either packing them into a single value (e.g., a struct/tuple stored as the slot) or introducing fences/spin-waits, both of which would hurt performance.

@devavret

devavret commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

cudf row operators are designed for either self-comparison or comparing two tables.

Are you suggesting storing all preprocessed keys from previous batches so that we can then use a two-table comparator between the new batch and a specific previous batch?

Yes, we'd need to store preprocessed keys but only the unique ones. So each batch gets preprocessed once before inserting, then after it has been inserted and compacted, preprocess again and store.

I'm suggesting we generalize this so the comparison can happen across arbitrary number of tables. I feel like there must be a way to wrap the current two table row operator to achieve this. The first row is from the table being inserted. The batch idx helps us point to the other table and the row idx tells us which row in the other batch to compare to. So the row comparator has access to current table and a store containing deduplicated previously seen batches.

There’s also a subtle issue around atomically updating the associated vectors. For example, if thread A wins the hash table CAS and inserts into slot S, there’s a window before it writes key_table[S] and key_idx[S]. During that time, thread B may probe slot S, see it as occupied, and read (partial) uninitialized data from those arrays.

The root cause is that the hash set slot and the associated metadata arrays live in separate memory locations where cuco’s atomic CAS only protects the slot itself, not the side arrays. There’s no straightforward way to atomically update all three together without either packing them into a single value (e.g., a struct/tuple stored as the slot) or introducing fences/spin-waits, both of which would hurt performance.

Right, I'm not suggesting the batchIdx and kyIdx be updated immediately upon batch insertion. The steps I described are like so:

  1. Try inserting a batch. mark which rows are new (never seen before)
  2. Extract these new rows from this batch along with associated slot index
  3. Now update (in-place scatter) the batchIdx and keyIdx in companion vectors without using any hash set API

@PointKernel

Copy link
Copy Markdown
Member Author

/ok to test fc91494

@PointKernel
PointKernel requested a review from devavret April 9, 2026 02:31
@PointKernel

PointKernel commented Apr 9, 2026

Copy link
Copy Markdown
Member Author

Thanks @devavret. The implementation has been updated to support variable-width keys following your design described in #21924 (comment).

The main difference is in how we identify keys in the hash set. Instead of inserting batch-local indices and using slot positions for companion vector lookups, we maintain a num_stored counter that tracks the cumulative size of all previous batches. Each row i in the current batch is inserted as num_stored + i, ensuring all values in the hash set are globally distinct. We call these "encoded indices." When a new batch arrives and probes an existing entry, the comparator retrieves the stored encoded index and looks up key_batch[encoded_idx] and key_row[encoded_idx] to locate the original key in its compacted batch for comparison.

We use this approach because cuco's comparator receives stored values, not slot positions, so there is no way to index companion vectors by slot. The tradeoff is that the encoded index space is consumed by the full batch size (not just distinct keys), so cumulative batch rows must stay within max_groups. On the other hand, we avoid the post-batch scatter to update key_batch and key_row, since companion vectors are written once at compaction time and never modified.

For your reference, I collected the latest results of the same benchmark launched in #21924 (comment):

## groupby_max_cardinality

### [0] NVIDIA RTX PRO 6000 Blackwell Workstation Edition

|  T  | num_rows | num_aggregations | cardinality |    api    | Samples |  CPU Time  | Noise  |  GPU Time  | Noise  | Mrows/s | peak_memory_usage |
|-----|----------|------------------|-------------|-----------|---------|------------|--------|------------|--------|---------|-------------------|
| I32 | 20000000 |                1 |          20 |    normal |   1920x | 673.555 us |  5.60% | 668.869 us |  5.17% |   29901 |       229.769 MiB |
| I32 | 20000000 |                1 |          50 |    normal |   1776x | 771.450 us |  4.50% | 766.449 us |  3.83% |   26094 |       229.769 MiB |
| I32 | 20000000 |                1 |         100 |    normal |   1888x | 872.996 us |  3.49% | 868.784 us |  3.50% |   23020 |       229.769 MiB |
| I32 | 20000000 |                1 |        1000 |    normal |    928x | 874.794 us |  5.68% | 870.618 us |  5.71% |   22972 |       229.769 MiB |
| I32 | 20000000 |                1 |       10000 |    normal |   1696x | 769.951 us |  3.90% | 765.765 us |  3.92% |   26117 |       229.769 MiB |
| I32 | 20000000 |                1 |      100000 |    normal |   1760x | 778.944 us |  2.49% | 774.862 us |  2.50% |   25811 |       229.769 MiB |
| I32 | 20000000 |                1 |     1000000 |    normal |   1040x |   1.031 ms |  0.86% |   1.027 ms |  0.87% |   19474 |       229.769 MiB |
| I32 | 20000000 |                1 |          20 | streaming |    656x |   2.188 ms | 17.62% |   2.183 ms | 17.65% |    9160 |       152.611 MiB |
| I32 | 20000000 |                1 |          50 | streaming |    576x |   1.696 ms | 15.13% |   1.687 ms | 14.81% |   11854 |       152.611 MiB |
| I32 | 20000000 |                1 |         100 | streaming |    400x |   1.432 ms | 12.08% |   1.426 ms | 12.08% |   14021 |       152.611 MiB |
| I32 | 20000000 |                1 |        1000 | streaming |    720x | 989.396 us |  5.11% | 985.218 us |  5.13% |   20300 |       152.611 MiB |
| I32 | 20000000 |                1 |       10000 | streaming |   1776x | 904.237 us |  2.83% | 899.495 us |  2.31% |   22234 |       152.611 MiB |
| I32 | 20000000 |                1 |      100000 | streaming |   2496x | 880.704 us |  3.50% | 876.172 us |  2.88% |   22826 |       152.611 MiB |
| I32 | 20000000 |                1 |     1000000 | streaming |   2048x |   1.299 ms |  2.38% |   1.294 ms |  1.96% |   15453 |       152.611 MiB |

and

## groupby_max_cardinality

### [0] NVIDIA RTX PRO 6000 Blackwell Workstation Edition

|  T  | num_rows | num_aggregations | cardinality |    api    | Samples | CPU Time  | Noise  | GPU Time  | Noise  | Mrows/s | peak_memory_usage |
|-----|----------|------------------|-------------|-----------|---------|-----------|--------|-----------|--------|---------|-------------------|
| I32 | 20000000 |                8 |          20 |    normal |   1088x |  1.138 ms |  2.81% |  1.134 ms |  2.82% |  141071 |       229.769 MiB |
| I32 | 20000000 |                8 |          50 |    normal |   1984x |  1.236 ms |  2.16% |  1.231 ms |  1.92% |  129972 |       229.769 MiB |
| I32 | 20000000 |                8 |         100 |    normal |   1344x |  1.376 ms |  2.22% |  1.372 ms |  2.10% |  116651 |       229.769 MiB |
| I32 | 20000000 |                8 |        1000 |    normal |     60x | 11.391 ms |  0.50% | 11.387 ms |  0.50% |   14051 |       306.063 MiB |
| I32 | 20000000 |                8 |       10000 |    normal |   1632x |  2.898 ms |  0.62% |  2.893 ms |  0.57% |   55300 |       306.063 MiB |
| I32 | 20000000 |                8 |      100000 |    normal |   1632x |  2.095 ms |  1.23% |  2.090 ms |  1.07% |   76553 |       306.063 MiB |
| I32 | 20000000 |                8 |     1000000 |    normal |   2224x |  2.331 ms |  1.20% |  2.326 ms |  0.98% |   68794 |       306.063 MiB |
| I32 | 20000000 |                8 |          20 | streaming |    288x | 14.083 ms | 16.90% | 14.078 ms | 16.91% |   11365 |       152.611 MiB |
| I32 | 20000000 |                8 |          50 | streaming |    544x |  9.575 ms |  9.86% |  9.570 ms |  9.86% |   16719 |       152.611 MiB |
| I32 | 20000000 |                8 |         100 | streaming |    464x |  7.368 ms |  7.79% |  7.363 ms |  7.79% |   21729 |       152.611 MiB |
| I32 | 20000000 |                8 |        1000 | streaming |    528x |  3.809 ms |  3.55% |  3.804 ms |  3.46% |   42064 |       152.611 MiB |
| I32 | 20000000 |                8 |       10000 | streaming |    640x |  2.920 ms |  2.19% |  2.913 ms |  1.88% |   54921 |       152.611 MiB |
| I32 | 20000000 |                8 |      100000 | streaming |    197x |  2.554 ms |  0.44% |  2.550 ms |  0.44% |   62754 |       152.611 MiB |
| I32 | 20000000 |                8 |     1000000 | streaming |    142x |  3.540 ms |  0.27% |  3.536 ms |  0.27% |   45250 |       152.611 MiB |

The performance gap between normal groupby and streaming groupby has been significantly reduced, with both approaches now demonstrating comparable performance in high-cardinality scenarios for both small and large numbers of aggregations. The remaining gap exists only in low-cardinality cases, where streaming groupby has not yet incorporated shared memory optimizations. Additionally, peak memory usage has decreased from 229 MB to 152 MB across all cases, meaning streaming groupby now consumes less peak memory than normal groupby.

@PointKernel
PointKernel marked this pull request as ready for review April 13, 2026 20:59
@PointKernel
PointKernel requested review from a team as code owners April 13, 2026 20:59
@PointKernel PointKernel added the 3 - Ready for Review Ready for review by team label Apr 13, 2026
@PointKernel
PointKernel requested a review from a team as a code owner April 17, 2026 17:29
@PointKernel
PointKernel requested a review from bdice April 17, 2026 17:29
@GregoryKimball

Copy link
Copy Markdown
Contributor

@devavret would you please complete your review?

@GregoryKimball
GregoryKimball requested a review from ttnghia April 21, 2026 17:40
@GregoryKimball

Copy link
Copy Markdown
Contributor

@ttnghia would you please share your review?

@ttnghia

ttnghia commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Does streaming aggregate with 1 iteration produce exactly the same output as the normal aggregation? If so, should we considering deprecate the normal aggreation pipeline, and adjust the implementation of streaming aggregation accordingly? I.e., instead of calling streaming_groupby, just let is be the new experimental::groupby?

Comment thread cpp/include/cudf/groupby.hpp Outdated
* @throws std::overflow_error if accumulated rows plus batch size exceeds `max_groups`
* @throws cudf::logic_error if distinct keys exceed `max_groups`
*/
void aggregate(table_view const& data, rmm::cuda_stream_view stream = cudf::get_default_stream());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request: can we have an API tell us the unique number of keys so far. seems like it's stored anyway and would be trivial to surface.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bump ^. Realistically, it's not possible to know the exact cardinailty beforehand and engines are supposed to make a guess. So when it does grow beyond initial mad_distinct_keys, we want to create a new streaming_groupby with more capacity, merge the current one into it and destroy. Currently, this means this aggregate() call lives in a try{} with the resizing done in catch{} block. It would be nicer to not have to rely on exceptions for this runtime behaviour.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I'm wondering whether this should store a stream instead of taking a new one each time. Do we need to wait on the previous stream before calling aggregate with a new batch and new stream?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I see that you only get that information AFTER checking for new distinct keys in the current batch. Which means you need to do some work before you can say the current batch will overflow or not.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already exposed as distinct_keys.

@GregoryKimball

Copy link
Copy Markdown
Contributor

Does streaming aggregate with 1 iteration produce exactly the same output as the normal aggregation? If so, should we considering deprecate the normal aggreation pipeline, and adjust the implementation of streaming aggregation accordingly?

Thank you @ttnghia, this is a great question. Currently the streaming aggregate with 1 iteration produces the same result, yes. But the big difference is that streaming groupby can't use the shared memory optimization, and there isn't a clear way to extend streaming aggregations to support that. So I expect that both streaming and non-streaming will exist for the foreseeable future.

Would you please share another pass of review feedback?

@PointKernel
PointKernel removed request for a team and galipremsagar May 19, 2026 21:43
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot May 19, 2026
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot May 19, 2026

@bdice bdice left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving CI + CMake, I did not read the C++.

@PointKernel

Copy link
Copy Markdown
Member Author

/merge

@rapids-bot
rapids-bot Bot merged commit 962d15b into NVIDIA:release/26.06 May 20, 2026
114 of 115 checks passed
@vuule vuule moved this from Burndown to Landed in libcudf May 20, 2026
@PointKernel
PointKernel deleted the streaming_groupby branch May 20, 2026 17:14
@GregoryKimball GregoryKimball removed this from libcudf Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3 - Ready for Review Ready for review by team CMake CMake build issue feature request New feature or request libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA] Add streaming aggregation API to libcudf

8 participants