Skip to content

Commit eaf5812

Browse files
committed
align with main
1 parent 99ec94c commit eaf5812

2 files changed

Lines changed: 85 additions & 63 deletions

File tree

python/cudf_polars/cudf_polars/streaming/actor_graph/collectives/orderscheme.py

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
1+
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33
"""OrderScheme adjustment utilities for the RapidsMPF streaming runtime."""
44

@@ -10,12 +10,12 @@
1010
import polars as pl
1111

1212
import pylibcudf as plc
13+
from cudf_streaming.partition_utils import unpack_and_concat
14+
from cudf_streaming.table_chunk import TableChunk
1315
from pylibcudf.contiguous_split import pack
14-
from rapidsmpf.integrations.cudf.partition import unpack_and_concat
1516
from rapidsmpf.memory.packed_data import PackedData
1617
from rapidsmpf.streaming.coll.sparse_alltoall import SparseAlltoall
1718
from rapidsmpf.streaming.core.message import Message
18-
from rapidsmpf.streaming.cudf.table_chunk import TableChunk
1919

2020
from cudf_polars.containers import DataFrame, DataType
2121
from cudf_polars.streaming.actor_graph.utils import (
@@ -27,11 +27,11 @@
2727
from cudf_polars.utils.cuda_stream import stream_ordered_after
2828

2929
if TYPE_CHECKING:
30+
from cudf_streaming.channel_metadata import OrderScheme, Ordering
3031
from rapidsmpf.communicator.communicator import Communicator
3132
from rapidsmpf.memory.buffer_resource import BufferResource
3233
from rapidsmpf.streaming.core.channel import Channel
3334
from rapidsmpf.streaming.core.context import Context
34-
from rapidsmpf.streaming.cudf.channel_metadata import OrderScheme
3535
from rmm.pylibrmm.stream import Stream
3636

3737
from cudf_polars.dsl.ir import IR, IRExecutionContext
@@ -41,6 +41,12 @@
4141
_PID_PLC_DTYPE = plc.DataType(plc.TypeId.INT32)
4242

4343

44+
def _primary_ordering(scheme: OrderScheme) -> Ordering:
45+
"""Return the single ordering supported by adjust_orderscheme for now."""
46+
(ordering,) = scheme.orderings
47+
return ordering
48+
49+
4450
def _contiguous_owner(pid: int, nranks: int, npartitions: int) -> int:
4551
"""Return the rank owning *pid* under contiguous partition assignment."""
4652
return pid * nranks // npartitions
@@ -81,10 +87,12 @@ def _contiguous_owners(
8187

8288
def _validate_schemes(input_scheme: OrderScheme, output_scheme: OrderScheme) -> None:
8389
"""Validate the first-pass flat OrderScheme adjustment contract."""
84-
if not output_scheme.strict_boundaries:
90+
input_ordering = _primary_ordering(input_scheme)
91+
output_ordering = _primary_ordering(output_scheme)
92+
if not output_ordering.strict_boundaries:
8593
raise ValueError("adjust_orderscheme requires a strict output OrderScheme.")
86-
prefix_len = len(output_scheme.keys)
87-
if input_scheme.keys[:prefix_len] != output_scheme.keys:
94+
prefix_len = len(output_ordering.keys)
95+
if input_ordering.keys[:prefix_len] != output_ordering.keys:
8896
raise NotImplementedError(
8997
"adjust_orderscheme currently requires the output OrderScheme keys "
9098
"to be a prefix of the input OrderScheme keys."
@@ -94,18 +102,18 @@ def _validate_schemes(input_scheme: OrderScheme, output_scheme: OrderScheme) ->
94102
def _split_points(
95103
table: plc.Table,
96104
boundary_table: plc.Table,
97-
scheme: OrderScheme,
105+
ordering: Ordering,
98106
stream: Stream,
99107
) -> list[int]:
100108
"""Return row split points that partition *table* by *scheme* boundaries."""
101109
if boundary_table.num_rows() == 0:
102110
return []
103-
key_table = plc.Table([table.columns()[key.column_index] for key in scheme.keys])
111+
key_table = plc.Table([table.columns()[key.column_index] for key in ordering.keys])
104112
split_col = plc.search.lower_bound(
105113
key_table,
106114
boundary_table,
107-
[key.order for key in scheme.keys],
108-
[key.null_order for key in scheme.keys],
115+
[key.order for key in ordering.keys],
116+
[key.null_order for key in ordering.keys],
109117
stream=stream,
110118
)
111119
return (
@@ -133,16 +141,16 @@ def _append_partition_id(table: plc.Table, pid: int, stream: Stream) -> plc.Tabl
133141
def _boundary_search_positions(
134142
input_boundary_table: plc.Table,
135143
output_boundary_table: plc.Table,
136-
output_scheme: OrderScheme,
144+
output_ordering: Ordering,
137145
stream: Stream,
138146
) -> tuple[list[int], list[int]]:
139147
"""Search output boundary positions for projected input boundary rows."""
140148
if input_boundary_table.num_rows() == 0:
141149
return [], []
142-
prefix_len = len(output_scheme.keys)
150+
prefix_len = len(output_ordering.keys)
143151
input_prefix_boundaries = plc.Table(input_boundary_table.columns()[:prefix_len])
144-
orders = [key.order for key in output_scheme.keys]
145-
null_orders = [key.null_order for key in output_scheme.keys]
152+
orders = [key.order for key in output_ordering.keys]
153+
null_orders = [key.null_order for key in output_ordering.keys]
146154
lower_col = plc.search.lower_bound(
147155
output_boundary_table,
148156
input_prefix_boundaries,
@@ -169,16 +177,16 @@ def _boundary_search_positions(
169177
def _peer_ranks(
170178
rank: int,
171179
nranks: int,
172-
input_scheme: OrderScheme,
173-
output_scheme: OrderScheme,
180+
input_ordering: Ordering,
181+
output_ordering: Ordering,
174182
lower_positions: list[int],
175183
upper_positions: list[int],
176184
) -> tuple[list[int], list[int]]:
177185
"""Return source and destination ranks needed for OrderScheme adjustment."""
178-
input_npartitions = input_scheme.num_boundaries + 1
179-
output_npartitions = output_scheme.num_boundaries + 1
180-
output_prefix_only = len(output_scheme.keys) < len(input_scheme.keys)
181-
include_upper_boundary = output_prefix_only or not input_scheme.strict_boundaries
186+
input_npartitions = input_ordering.num_boundaries + 1
187+
output_npartitions = output_ordering.num_boundaries + 1
188+
output_prefix_only = len(output_ordering.keys) < len(input_ordering.keys)
189+
include_upper_boundary = output_prefix_only or not input_ordering.strict_boundaries
182190

183191
def dsts_for_source(source_rank: int) -> list[int]:
184192
input_start, input_stop = _partition_range(
@@ -265,10 +273,10 @@ async def _adjust_orderscheme_local(
265273
ir_context: IRExecutionContext,
266274
ch_out: Channel[TableChunk],
267275
ch_in: Channel[TableChunk],
268-
output_scheme: OrderScheme,
276+
output_ordering: Ordering,
269277
) -> None:
270-
npartitions = output_scheme.num_boundaries + 1
271-
boundary_chunk = output_scheme.get_boundaries(context.br())
278+
npartitions = output_ordering.num_boundaries + 1
279+
boundary_chunk = output_ordering.get_boundaries(context.br())
272280
boundary_table = boundary_chunk.table_view()
273281
pending_pid: int | None = None
274282
pending_chunks: ChunkStore | None = None
@@ -294,11 +302,11 @@ async def emit_pending(pid: int) -> None:
294302
if chunk.table_view().num_rows() == 0:
295303
continue
296304
with stream_ordered_after(
297-
context.get_stream_from_pool,
305+
context.br().stream_pool.get_stream,
298306
upstreams=(chunk.stream, boundary_chunk.stream),
299307
) as stream:
300308
table = chunk.table_view()
301-
splits = _split_points(table, boundary_table, output_scheme, stream)
309+
splits = _split_points(table, boundary_table, output_ordering, stream)
302310
for pid, piece in enumerate(
303311
plc.copying.split(table, splits, stream=stream)
304312
):
@@ -372,7 +380,9 @@ async def adjust_orderscheme(
372380
sortedness is not checked here.
373381
"""
374382
_validate_schemes(input_scheme, output_scheme)
375-
npartitions = output_scheme.num_boundaries + 1
383+
input_ordering = _primary_ordering(input_scheme)
384+
output_ordering = _primary_ordering(output_scheme)
385+
npartitions = output_ordering.num_boundaries + 1
376386
local_pids = _local_partitions(comm.rank, comm.nranks, npartitions)
377387

378388
if comm.nranks > 1 and collective_id is None:
@@ -386,31 +396,31 @@ async def adjust_orderscheme(
386396
ir_context,
387397
ch_out,
388398
ch_in,
389-
output_scheme,
399+
output_ordering,
390400
)
391401
return
392402

393-
input_boundary_chunk = input_scheme.get_boundaries(context.br())
394-
boundary_chunk = output_scheme.get_boundaries(context.br())
403+
input_boundary_chunk = input_ordering.get_boundaries(context.br())
404+
boundary_chunk = output_ordering.get_boundaries(context.br())
395405
boundary_table = boundary_chunk.table_view()
396406
srcs: list[int] = []
397407
dsts: list[int] = []
398408
if comm.nranks > 1:
399409
with stream_ordered_after(
400-
context.get_stream_from_pool,
410+
context.br().stream_pool.get_stream,
401411
upstreams=(input_boundary_chunk.stream, boundary_chunk.stream),
402412
) as stream:
403413
lower_positions, upper_positions = _boundary_search_positions(
404414
input_boundary_chunk.table_view(),
405415
boundary_table,
406-
output_scheme,
416+
output_ordering,
407417
stream,
408418
)
409419
srcs, dsts = _peer_ranks(
410420
comm.rank,
411421
comm.nranks,
412-
input_scheme,
413-
output_scheme,
422+
input_ordering,
423+
output_ordering,
414424
lower_positions,
415425
upper_positions,
416426
)
@@ -429,11 +439,13 @@ async def adjust_orderscheme(
429439
if chunk.table_view().num_rows() == 0:
430440
continue
431441
with stream_ordered_after(
432-
context.get_stream_from_pool,
442+
context.br().stream_pool.get_stream,
433443
upstreams=(chunk.stream, boundary_chunk.stream),
434444
) as stream:
435445
table = chunk.table_view()
436-
splits = _split_points(table, boundary_table, output_scheme, stream)
446+
splits = _split_points(
447+
table, boundary_table, output_ordering, stream
448+
)
437449
for pid, piece in enumerate(
438450
plc.copying.split(table, splits, stream=stream)
439451
):
@@ -473,7 +485,7 @@ async def adjust_orderscheme(
473485
output_chunks[pid].extend(chunks)
474486
else:
475487
assert exchange is not None
476-
stream = context.get_stream_from_pool()
488+
stream = context.br().stream_pool.get_stream()
477489
for packed in exchange.extract(source_rank):
478490
remote_piece = _unpack_remote_piece(packed, stream, context.br())
479491
if remote_piece is None:

python/cudf_polars/tests/streaming/test_adjust_orderscheme.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.
1+
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

44
from __future__ import annotations
@@ -12,12 +12,13 @@
1212
import polars as pl
1313

1414
import pylibcudf as plc
15-
from rapidsmpf.streaming.core.message import Message
16-
from rapidsmpf.streaming.cudf.channel_metadata import (
15+
from cudf_streaming.channel_metadata import (
1716
OrderKey,
1817
OrderScheme,
18+
Ordering,
1919
)
20-
from rapidsmpf.streaming.cudf.table_chunk import TableChunk
20+
from cudf_streaming.table_chunk import TableChunk
21+
from rapidsmpf.streaming.core.message import Message
2122

2223
from cudf_polars.containers import DataFrame, DataType
2324
from cudf_polars.dsl.ir import Empty, IRExecutionContext
@@ -71,16 +72,24 @@ def _make_scheme(
7172
)
7273
return OrderScheme(
7374
[
74-
OrderKey(index, plc.types.Order.ASCENDING, plc.types.NullOrder.BEFORE)
75-
for index in key_indices
76-
],
77-
TableChunk.from_pylibcudf_table(
78-
boundary_df.table,
79-
stream,
80-
exclusive_view=True,
81-
br=context.br(),
82-
),
83-
strict_boundaries=strict,
75+
Ordering(
76+
[
77+
OrderKey(
78+
index,
79+
plc.types.Order.ASCENDING,
80+
plc.types.NullOrder.BEFORE,
81+
)
82+
for index in key_indices
83+
],
84+
TableChunk.from_pylibcudf_table(
85+
boundary_df.table,
86+
stream,
87+
exclusive_view=True,
88+
br=context.br(),
89+
),
90+
strict_boundaries=strict,
91+
)
92+
]
8493
)
8594

8695

@@ -118,7 +127,7 @@ async def _adjust_and_collect(
118127
"""Run adjustment and collect output chunks by partition ID."""
119128
ch_in = context.create_channel()
120129
ch_out = context.create_channel()
121-
stream = context.get_stream_from_pool()
130+
stream = context.br().stream_pool.get_stream()
122131
output: dict[int, pl.DataFrame] = {}
123132

124133
async def _produce() -> None:
@@ -147,7 +156,7 @@ async def _consume() -> None:
147156

148157
with ThreadPoolExecutor(max_workers=1) as executor:
149158
ir_context = IRExecutionContext(
150-
executor, get_cuda_stream=context.get_stream_from_pool
159+
executor, get_cuda_stream=context.br().stream_pool.get_stream
151160
)
152161
await gather_in_task_group(
153162
_produce(),
@@ -190,7 +199,7 @@ async def _adjust_direct(
190199
ch_out = context.create_channel()
191200
with ThreadPoolExecutor(max_workers=1) as executor:
192201
ir_context = IRExecutionContext(
193-
executor, get_cuda_stream=context.get_stream_from_pool
202+
executor, get_cuda_stream=context.br().stream_pool.get_stream
194203
)
195204
await adjust_orderscheme(
196205
context,
@@ -222,7 +231,7 @@ def test_adjust_orderscheme_rejects_invalid_schemes(
222231
match: str,
223232
) -> None:
224233
context = spmd_engine.context
225-
stream = context.get_stream_from_pool()
234+
stream = context.br().stream_pool.get_stream()
226235
input_scheme = _make_scheme(context, 4, key_indices=input_keys, stream=stream)
227236
output_scheme = _make_scheme(
228237
context,
@@ -247,7 +256,7 @@ def test_adjust_orderscheme_requires_collective_id(
247256
if comm.nranks == 1:
248257
pytest.skip("collective_id is only required for multi-rank runs.")
249258

250-
stream = context.get_stream_from_pool()
259+
stream = context.br().stream_pool.get_stream()
251260
input_scheme = _make_scheme(context, 4, stream=stream)
252261
output_scheme = _make_scheme(context, 4, stream=stream)
253262

@@ -274,7 +283,7 @@ def test_adjust_orderscheme_sparse_boundary_shift(
274283
pytest.skip("This test expects exactly two ranks.")
275284

276285
keys = list(range(4)) if comm.rank == 0 else list(range(4, 8))
277-
stream = context.get_stream_from_pool()
286+
stream = context.br().stream_pool.get_stream()
278287
# Input sorted on (key, val) is also sorted on the target key prefix.
279288
input_scheme = _make_scheme(context, (4, 4), key_indices=(0, 1), stream=stream)
280289
output_scheme = _make_scheme(context, target_boundary, stream=stream)
@@ -304,7 +313,7 @@ def test_adjust_orderscheme_emits_empty_owned_partitions(
304313
pytest.skip("This test expects exactly two ranks.")
305314

306315
keys = [0, 1, 2] if comm.rank == 0 else [5, 8]
307-
stream = context.get_stream_from_pool()
316+
stream = context.br().stream_pool.get_stream()
308317
input_scheme = _make_scheme(context, 5, stream=stream)
309318
output_scheme = _make_scheme(context, [3, 5, 7], stream=stream)
310319

@@ -331,13 +340,14 @@ def test_adjust_orderscheme_emits_empty_owned_partitions(
331340
def test_adjust_orderscheme_all_empty_input(spmd_engine: SPMDEngine) -> None:
332341
context = spmd_engine.context
333342
comm = spmd_engine.comm
334-
stream = context.get_stream_from_pool()
343+
stream = context.br().stream_pool.get_stream()
335344
input_scheme = _make_scheme(context, 5, stream=stream)
336345
output_scheme = _make_scheme(context, [3, 5, 7], stream=stream)
346+
output_npartitions = output_scheme.orderings[0].num_boundaries + 1
337347
expected: _ExpectedPartitions = {
338348
pid: []
339-
for pid in range(output_scheme.num_boundaries + 1)
340-
if pid * comm.nranks // (output_scheme.num_boundaries + 1) == comm.rank
349+
for pid in range(output_npartitions)
350+
if pid * comm.nranks // output_npartitions == comm.rank
341351
}
342352

343353
if comm.nranks == 1:
@@ -384,7 +394,7 @@ def test_adjust_orderscheme_single_rank_no_collective(
384394
if comm.nranks != 1:
385395
pytest.skip("This test covers the single-rank path.")
386396

387-
stream = context.get_stream_from_pool()
397+
stream = context.br().stream_pool.get_stream()
388398
input_scheme = _make_scheme(context, 4, stream=stream)
389399
output_scheme = _make_scheme(context, target_boundary, stream=stream)
390400
output_by_pid = asyncio.run(
@@ -407,7 +417,7 @@ def test_adjust_orderscheme_multi_chunk_input(spmd_engine: SPMDEngine) -> None:
407417
if comm.nranks != 1:
408418
pytest.skip("This test covers local chunk accumulation.")
409419

410-
stream = context.get_stream_from_pool()
420+
stream = context.br().stream_pool.get_stream()
411421
input_scheme = _make_scheme(context, 4, stream=stream)
412422
output_scheme = _make_scheme(context, 4, stream=stream)
413423
output = asyncio.run(

0 commit comments

Comments
 (0)