[Preview][Data] Avoid concatenating multi-chunk tensor columns during row takes#41
Closed
OneSizeFitsQuorum wants to merge 4 commits into
Closed
[Preview][Data] Avoid concatenating multi-chunk tensor columns during row takes#41OneSizeFitsQuorum wants to merge 4 commits into
OneSizeFitsQuorum wants to merge 4 commits into
Conversation
…uffle Signed-off-by: OneSizeFitsQuorum <tanxinyu@apache.org>
There was a problem hiding this comment.
Pull request overview
This PR introduces a faster row-take path for eligible multi-chunk Ray tensor extension columns by gathering selected rows directly from the original chunks instead of first concatenating the entire column. This targets Ray Data’s Arrow-backed take paths (notably local shuffle) to avoid an avoidable full-source O(N × D) concatenation cost when only K rows are needed.
Changes:
- Add a prepared/validated “chunked tensor take” implementation that can gather rows directly from multi-chunk Ray tensor columns with bounded scratch memory.
- Integrate the one-shot fast path into
take_table()for general Arrow row-take operations when indices can be normalized to nativeint64. - Teach
ShufflingBatcherto build and reuse prepared tensor take plans per shuffle-buffer generation, while continuing to combine/take non-tensor columns via existing mechanisms.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| python/ray/data/tests/unit/test_chunked_tensor_take.py | Adds focused unit/regression coverage for prepared/unprepared chunked tensor takes, fallbacks, error propagation, and local-shuffle reuse behavior. |
| python/ray/data/_internal/tensor_extensions/chunked_tensor_take.py | Implements prepared-plan validation, zero-copy chunk view checks, and bounded-scratch gathering for multi-chunk Ray tensor extension columns. |
| python/ray/data/_internal/batcher.py | Adds local-shuffle preparation/take helpers to reuse prepared tensor plans across batches within a shuffle-buffer generation. |
| python/ray/data/_internal/arrow_ops/transform_pyarrow.py | Adds conservative index normalization for enabling the tensor fast path inside take_table() while preserving fallback behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: OneSizeFitsQuorum <tanxinyu@apache.org>
Signed-off-by: OneSizeFitsQuorum <tanxinyu@apache.org>
Signed-off-by: OneSizeFitsQuorum <tanxinyu@apache.org>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This change avoids concatenating eligible multi-chunk Ray tensor extension
columns before row takes. Local shuffle is the primary use case, but the
one-shot path is shared by other PyArrow-backed Ray Data operations.
For a shuffle buffer containing
Ntensor rows, each with widthD, producinga
K-row batch currently pays for a fullO(N × D)column concatenation beforethe required
O(K × D)output copy. The proposed path gathers selected rowsdirectly from the source chunks, bringing the primary copy cost closer to the
unavoidable
O(K × D).There are two integration levels:
take_table()prepares and executes a one-shot direct gather for generalArrow row-take operations.
ShufflingBatcherprepares validated, generation-scoped chunk metadata onceper compaction and reuses it for each output batch. The plan is discarded and
rebuilt after the next compaction.
Only local shuffle currently reuses a prepared plan across multiple takes.
Implementation
PreparedChunkedTensorTakePlanfor validated Ray tensor V1/V2 chunkmetadata.
boundaries.
contiguous tensor output.
take_table().ShufflingBatcher.next_batch().RAY_DATA_ENABLE_CHUNKED_TENSOR_TAKEas a default-on operationalfallback switch for both integration levels.
Ray Data paths
Because
ArrowBlockAccessor.take()delegates totake_table(), the one-shotfast path is already reachable from:
concat_and_sort();Local shuffle has the largest expected gain because the same prepared plan is
reused to emit multiple batches and typically selects
K < Nrows at a time.General takes and join projections can have the same favorable
K < Nrelationship.
Sort, random shuffle, and hash partition usually perform a full permutation
with
K = N. They still avoid the extra full-source concatenation and itstemporary allocation, but must copy the full output tensor once. The performance
results below measure the local-shuffle use case; they do not claim measured
speedups for every reachable operator.
Pandas blocks, Polars sort paths, and direct
pyarrow.Table.take()calls do notuse this fast path. Contiguous Arrow slicing remains on its existing zero-copy
path.
Safety and fallback behavior
The fast path is limited to non-null numeric fixed-shape
ArrowTensorTypeandArrowTensorTypeV2columns with multiple chunks and validated contiguous childbuffers.
The existing implementation remains in use for:
multidimensional indices.
Recognized unsupported layouts return to the existing concatenate/take path.
Setting
RAY_DATA_ENABLE_CHUNKED_TENSOR_TAKE=0disables the fast pathentirely. Unexpected allocation and internal errors are propagated.
Tests
The focused and existing regression suites cover:
zero-sized-shape, and zero-row fallbacks;
Commands:
Result:
The complete repository hooks were run against all four changed files:
All applicable hooks passed, including Ruff, Black, pydoclint, Semgrep,
docstyle, AST, and Ray import-order checks.
The correctness probe produced byte-identical baseline, patched, and
packaged-runtime outputs.
Synthetic performance
The primary local-shuffle scenario uses 100,000 rows of
float32[3000],10,000-row batches, a 100,000-row shuffle buffer, and four independently paired
baseline/patch processes.
9.80x[9.68x, 9.89x]0.1394.92xspeedup8.97xspeedup0.9640.972The summary was regenerated from 22 raw result files and matched the recorded
output byte-for-byte. All predefined performance, variance, and fallback
regression gates passed.
End-to-end Ray Train + Ray Data validation
In the original Ray Train + Ray Data use case, workers consume batches
containing wide multi-chunk tensors from a locally shuffled dataset. The
workload configuration was:
60,074,8726,444,594float32[30, 100]tensor plus onefloat32label12,0045,373(approximately11,181rows per block)577721 GB(672 GiB)77 GB(72 GiB)8, using8GPUs10,000rows100,000rows5shuffle=True,shuffle_ratio=1.0,block_random_shuffle=True2.56.1, Python3.11.9, PyArrow17.0.0, NumPy1.26.4The workload did not include an application-level filter or sort. The baseline
and patch runs used hardware-equivalent nodes. The end-to-end comparison
produced:
0.2214.52xfaster0.2084.80xfaster0.2334.30xfaster0.2593.85xfaster0.9188.2%lower0.87013.0%lowerThe validation completed without object spilling, object-store fallback, data
task failures, retries, worker startup failures, node failures, or container
restarts.
Duplicate-work check
On 2026-07-23, I searched open Ray Issues and PRs for
chunked tensor,multi-chunk tensor,tensor take,local shuffle tensor, andShufflingBatcher. I found no open work addressing this optimization.PR ray-project#64576 touches
transform_pyarrow.py, but only changes an assertion errormessage and unrelated operator bounds checks. It does not overlap this design or
implementation.
AI assistance was used in preparing this change.