Skip to content

[POC] Add Vortex as an option to libcudf NDS-H benchmarks - #24289

Closed
0ax1 wants to merge 41 commits into
NVIDIA:mainfrom
spiraldb:ad/ndsh-vortex-benchmarks
Closed

0ax1 wants to merge 41 commits into
NVIDIA:mainfrom
spiraldb:ad/ndsh-vortex-benchmarks

Conversation

@0ax1

@0ax1 0ax1 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds benchmark-private write_vortex / read_vortex helpers and Parquet/Vortex comparisons for Q1, Q5, Q6, Q9, and Q10, enabled by CUDF_WITH_VORTEX=ON (default OFF). No installed libcudf reader/writer API is introduced.

  • Write: GPU-resident cuDF tables → row-bounded host Arrow chunks → CPU-encoded local Vortex files. Fixture generation and writes are outside benchmark timing.
  • Read: Vortex GPU decoding → Arrow Device import → owning cuDF tables, using shared query implementations.
  • Adds round-trip tests, independent CPU references, and query smoke tests.

I/O and HtoD approach

This POC uses Vortex’s own I/O, not cudf::io::datasource. Local data reads use pooled pinned host buffers and asynchronous HtoD transfers before GPU decoding; metadata uses buffered host reads.

The adapter currently accepts local paths only. It does not expose file-like objects, host/device-buffer sources, or remote objects through cuDF. Vortex has Rust-side host-buffer and object-store readers, but connecting those—or cuDF datasources—to this adapter is follow-up work. Direct I/O bypasses the OS data-page cache; it is not GPUDirect Storage.

Comparison and validation

Measures read-only and read-plus-query CPU wall time, including materialization, cleanup, and synchronization. Both formats use matched data and projections. Q5/Q9/Q10 overlap Vortex table reads; Parquet reads remain sequential.

Validated relevant builds/tests with Vortex OFF and ON, including tests without benchmarks. All five queries passed SF0.01 warm/cold buffered comparisons and separate warm direct-I/O Vortex runs; warm smoke tests cover every Q9 engine. Builds used the existing cache with the documented NVCC workaround.

SF100 Nsight Systems profiles are still outstanding; these runs establish correctness, not performance conclusions.

Current limitations include Linux/device 0, flat columns, an experimental file layout, build-tree runtime dependencies, and the documented host Arrow export failure-path lifetime issue.

0ax1 added 30 commits September 24, 2026 10:45
Protect host Arrow owners during exception unwinding without adding synchronization to successful ownership transfers. Cover allocation failure after an earlier asynchronous transfer, stream completion, and recovery.

Signed-off-by: Alexander Droste <[email protected]>
Move Parquet reading, writing, and source generation out of shared utilities. Update query includes, writer calls, and benchmark build sources while preserving the existing Parquet execution paths and managed-memory generation.

Signed-off-by: Alexander Droste <[email protected]>
Extract reader-injected query execution while retaining the legacy Parquet wrappers and pushdown behavior. Add named-table generation for shared-format fixtures, with a scoped managed pool for callback-based generation and an explicit resource for returned lineitem tables.

Signed-off-by: Alexander Droste <[email protected]>
Add vortex_writer_options and write_vortex(options, stream, mr), an optional pinned Vortex dependency, and writer tests independent of benchmark builds. Reuse bounded Arrow host staging with CPU compression and synchronous file finalization.

Document local-file, flat-type, device-0, shared-library and experimental-layout limitations, allocation semantics, and the disabled-feature error path. Keep embedded Vortex symbols private.

Signed-off-by: Alexander Droste <[email protected]>
Add projected local GPU reads, Arrow Device import, owning cuDF materialization, and stream/owner cleanup. Delegate fixture writes to the public writer.

Cover slices, nulls, bitmap boundaries, projections, failure recovery and concurrent reads. Build the adapter with cuDF warning flags and register its build-tree-only test with RAPIDS GPU resource accounting. Document runtime-library restrictions.

Signed-off-by: Alexander Droste <[email protected]>
Add paired fixtures and independent CPU references for Q1, Q5, Q6, Q9 and Q10. Validate each selected format, workload and I/O path before timing, preserve Parquet pushdown coverage, and overlap independent Vortex table reads.

Separate buffered/direct I/O from page-cache state, retain only one fixture scale per query, and measure wall time and RMM peaks after setup cleanup. Add cache and memory-counter regression tests and GPU-accounted query smoke tests. Keep Vortex-enabled query binaries build-tree-only and document comparison asymmetries and validation limitations.

Signed-off-by: Alexander Droste <[email protected]>
Move the writer and its enabled-path tests out of libcudf, and configure Vortex only for enabled benchmark builds. Reader and writer now share one FFI archive per executable rather than embedding another copy in libcudf.

Preserve the public API proposal on ad/vortex-public-writer and the independent Arrow cleanup on ad/arrow-host-export-cleanup. Retain the Arrow safety prerequisite in this branch.

Signed-off-by: Alexander Droste <[email protected]>
Use aggregate settings instead of a public-style builder and accessors. Generate default names during metadata construction and compact string views in place. Keep writer behavior tests while dropping assertions about plain field assignment, and let async futures join on test error paths.

Signed-off-by: Alexander Droste <[email protected]>
Share Q5/Q10 discounted revenue, inline Q6's single binary operation, and generate Q1/Q6 fixtures through the existing managed-memory callback. Preserve native query types and boundary checks, and drain generated-table use before the callback returns.

Signed-off-by: Alexander Droste <[email protected]>
Keep query-specific read order and validation explicit while sharing the resource logger, I/O context, warmup, timed owner scopes, and common summaries. Preserve consumer synchronization before owner destruction and device synchronization before counter reset and timer stop.

Signed-off-by: Alexander Droste <[email protected]>
Keep table_with_names::to_parquet and its original implementation, located in the explicit Parquet source directory. Avoid renaming native benchmark write calls merely to add Vortex support.

Signed-off-by: Alexander Droste <[email protected]>
Undo unrelated deduplication of the native revenue helpers and fixture table lists. Retain shared execution needed by Vortex comparisons and simplifications confined to the new fixture and local benchmark paths.

Signed-off-by: Alexander Droste <[email protected]>
Keep both export entry points and a copy above the CUDA 13 policy threshold, but use smaller inputs and remove unrelated successful-export checks. State the timing-based sentinel and nested-drain limits explicitly instead of claiming independent release-order coverage.

Signed-off-by: Alexander Droste <[email protected]>
Pair each input, CPU input where needed, and independent expectation directly rather than selecting by row count or parallel-vector index. Preserve all 19 cases and 44 reader-policy/engine executions, and include the scenario name in validation errors.

Signed-off-by: Alexander Droste <[email protected]>
Retain prefix, offset and empty slices, logical-content checks, actual character-buffer sizes and consumer-stream completion. Avoid building and comparing unrelated numeric columns in this focused test.

Signed-off-by: Alexander Droste <[email protected]>
Replace validator self-tests and broad predicate/oracle boundary scenarios with a four-row known-result check through both Parquet and Vortex files. Keep generated-data projection/reference validation, selected-path validation and empty-SUM handling outside timing. Leave the native query and license header unchanged.

Signed-off-by: Alexander Droste <[email protected]>
Move adapter, writer, and utility tests into the existing cpp/tests directories and centralize their CTest registrations. Build unit tests independently of BUILD_BENCHMARKS; only query smoke tests require benchmark executables. Share private Vortex support between tests and benchmarks and update build instructions.

Signed-off-by: Alexander Droste <[email protected]>
Restore core Arrow host export and its tests to upstream. Document the unresolved export failure-path safety prerequisite separately from the Vortex integration.

Signed-off-by: Alexander Droste <[email protected]>
Link to the Vortex README for detailed comparison settings and limitations. Describe current behavior without PR history or before-and-after commentary.

Signed-off-by: Alexander Droste <[email protected]>
Share query execution between benchmarks and ordinary GTests, keeping synthetic boundary and join cases outside benchmark setup. Configure shared data-generation support for tests independently of benchmarks and simplify local validation arguments.

Signed-off-by: Alexander Droste <[email protected]>
0ax1 added 11 commits September 24, 2026 13:31
Replace the custom adapter test runner and Arrow comparator with GTest and cuDF column checks. Preserve exact values while allowing redundant all-valid masks, retain stream-completion checks, and use existing CUDA error and benchmark NVTX helpers.

Signed-off-by: Alexander Droste <[email protected]>
Use ConfigureTest for memory statistics and the cuDF GTest wrapper for CPU-only fixture tests. Reset cached test state before counting operations so repeated or shuffled fixture tests remain independent.

Signed-off-by: Alexander Droste <[email protected]>
Keep build and run commands, test entry points, comparison semantics, and essential limitations. Remove repeated implementation and build-wiring detail.

Signed-off-by: Alexander Droste <[email protected]>
Add concise file-level descriptions of fixture caching, query execution and references, private Vortex I/O, and regression-test coverage. Preserve code and existing license headers.

Signed-off-by: Alexander Droste <[email protected]>
Condense reader and writer contracts and remove repeated helper descriptions while preserving file-level purpose, ownership, synchronization, I/O, and format constraints. Documentation only; no code or license changes.

Signed-off-by: Alexander Droste <[email protected]>
Keep projection checks at their callers while preserving sequential/direct comparisons and temporary owner lifetimes.

Signed-off-by: Alexander Droste <[email protected]>
Compose load_data and compute_profit directly in fixture validation and regression tests without changing native benchmark execution.

Signed-off-by: Alexander Droste <[email protected]>
Remove Q5 support for silently ignoring unrelated tables and use checked map access for Q10 customers already guaranteed by order filtering.

Signed-off-by: Alexander Droste <[email protected]>
Use cuDF representation sizes for decimal fixtures and combine per-column metadata and exact-value checks without removing assertions or stream-completion coverage.

Signed-off-by: Alexander Droste <[email protected]>
Use plain GoogleTest and drop the unused GoogleMock and cuDF test-header dependencies while retaining all fixture lifetime and retry checks.

Signed-off-by: Alexander Droste <[email protected]>
Signed-off-by: Alexander Droste <[email protected]>
@copy-pr-bot

copy-pr-bot Bot commented Sep 24, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added libcudf Affects libcudf (C++/CUDA) code. CMake CMake build issue labels Sep 24, 2026
@0ax1

0ax1 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

Parquet vs. Vortex: hot and cold cache, SF1

Measured on 2026-09-24 at cuDF commit 4f26ad1f9d.

Hardware and storage

  • GPU: NVIDIA GH200 480GB, device 0; 132 SMs, 96,768 MiB CUDA-visible GPU memory.
  • CPU: 64 ARM Neoverse-V2 cores.
  • Software: Release build, CUDA toolkit 13.1.115, NVIDIA driver 595.71.05.
  • Fixture location: /tmp, on ext4 partition /dev/vda1.
  • Disk: /dev/vda, a 4 TiB virtio block device (4,398,046,511,104 bytes), with 512-byte logical/physical sectors.
  • Mount options: rw,relatime,discard,errors=remount-ro,commit=30.
  • The physical backing drive model and storage tier are not exposed to the guest; these results should not be described as local NVMe or SSD measurements.

Method

Mean CPU wall time in milliseconds over 30 timed samples per state. Both formats use buffered I/O (io=buffered, KVIKIO_COMPAT_MODE=ON). Speedup is Parquet time divided by Vortex time.

Hot means a read-only warmup of the OS page cache. Cold means input-file pages are evicted and zero resident pages verified before each timed iteration. Hardware and host-storage caches are not flushed.

Timing includes reads, optional query execution, owning cuDF materialization, cleanup, and synchronization. Fixture generation, writes, validation, and eviction are excluded.

Hot cache

Query Parquet read (ms) Vortex read (ms) Read speedup Parquet read + query (ms) Vortex read + query (ms) Read + query speedup
Q1 14.293 4.104 3.48x 19.118 8.798 2.17x
Q5 17.550 3.658 4.80x 20.542 6.501 3.16x
Q6† 7.958 1.887 4.22x 8.487 2.422 3.50x
Q9 (binaryop) 27.108 4.356 6.22x 30.453 7.335 4.15x
Q9 (ast) 27.222 4.465 6.10x 30.302 7.342 4.13x
Q9 (transform) 27.212 4.396 6.19x 30.222 7.452 4.06x
Q10† 26.160 6.425 4.07x 27.685 8.077 3.43x

Cold cache

Query Parquet read (ms) Vortex read (ms) Read speedup Parquet read + query (ms) Vortex read + query (ms) Read + query speedup
Q1 19.119 6.457 2.96x 23.889 10.978 2.18x
Q5 22.230 4.260 5.22x 25.066 7.255 3.46x
Q6† 11.131 3.115 3.57x 11.674 3.703 3.15x
Q9 (binaryop) 32.836 5.047 6.51x 36.275 8.248 4.40x
Q9 (ast) 32.849 5.048 6.51x 35.952 8.335 4.31x
Q9 (transform) 32.786 5.108 6.42x 35.955 8.260 4.35x
Q10† 30.911 7.574 4.08x 32.353 9.083 3.56x

@0ax1

0ax1 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor Author

cc: @GregoryKimball

@GregoryKimball

GregoryKimball commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Thanks @0ax1 for sharing this POC. I've been studying it to generate some Nsight Systems profiles.

The biggest signal I see is that the Vortex reader manages a higher HtoD throughput than the cuDF Parquet reader. The file sizes between Parquet and Vortex are similar, but the HtoD time is about cut in half.

Here is what I'm seeing at SF10 for Q5 with sequential reads (DGX B200 single GPU).

cudf::read_parquet
image

vortex.read
image

I'm not sure the root cause, but it could be less synchronization in the HtoD pipelining from Rust compared to KvikIO with default settings. ( @kingcrimsontianyu , @vyasr perhaps there is something we can learn from https://github.com/vortex-data/vortex/blob/develop/vortex-cuda/src/pooled_read_at/file.rs and https://github.com/vortex-data/vortex/blob/develop/vortex-cuda/src/pinned.rs )

reuse the remote-read BounceBufferCache::recycle_after() design in posix_device_io() and remove its per-chunk StreamSynchronize()

🤷

@kingcrimsontianyu

Copy link
Copy Markdown
Contributor

reuse the remote-read BounceBufferCache::recycle_after() design in posix_device_io() and remove its per-chunk StreamSynchronize()

The idea is to avoid the stream sync immediately after each I/O, so that I/O from task k+1 on a worker overlaps H2D from task k. Before the remote multi-poll backend and the recycle_after approach exist, I experimented with this idea some time last year rapidsai/kvikio#921: While it makes the profile prettier (good I/O-H2D overlap without gap), the I/O time of each segment read becomes longer for unknown reason. But I think this unsolved issue deserves a revisit.

KvikIO still uses default streams for the H2D copy (https://github.com/rapidsai/kvikio/blob/main/cpp/src/detail/stream.cpp#L37) to maintain certain contract with the cuFile API and I advocate removal of this contract to be able to use non-blocking streams. Not sure if H2D on default streams would throttle the bandwidth.

Perhaps we may tune up the KVIKIO_TASK_SIZE and see if this helps with the copy bandwidth.

@0ax1

0ax1 commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

@GregoryKimball & @kingcrimsontianyu Much appreciate both of you having a look at the POC!

@GregoryKimball

Copy link
Copy Markdown
Contributor

@kingcrimsontianyu thank you for the discussion of these IO signals today. I did a follow-up study with 32 threads and 16 MiB tasks. This brings execution for parquet down from 119 to 73 ms.

image

Seems like this closes the IO gap. So maybe the decode-decompress difference is more like 73ms for parquet and 65ms for vortex.

@kingcrimsontianyu

Copy link
Copy Markdown
Contributor

@GregoryKimball Thanks for the test.

This might be another reason for us to add an io_uring backend for local I/O so as to maintain decent I/O performance at low thread count rapidsai/kvikio#828, in a similar way multi-poll does to remote I/O.

@GregoryKimball

GregoryKimball commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Holding IO aside, the decompress/decode phase for Vortex is indeed faster for the projected lineitem file. About 10ms faster at SF10 on DGX B200.

parquet (empty space is the decompression engine)
image

vortex
image

@vyasr

vyasr commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

@0ax1 thanks for this PR, and apologies for the slow response here! These numbers certainly look promising. Regarding #23877, my read of this PR is that we can achieve almost all of the benefits that we want by using the Vortex C++ or Python libraries directly and just feeding the data into cuDF via the Arrow data interface (which seems to be what you're already doing in this PR). I think the main thing we have to be careful of is ensuring that we actually take zero-copy views of the Vortex data. Aside from that, I don't see any major challenges.

Should we turn this pull request into an official cuDF example of using Vortex? We don't need to introduce Vortex as a dependency or introduce a Vortex reader in cuDF, we just need an example to point to showing how you can use the Vortex library itself to read Vortex files and then pass them zero-copy to cuDF.

@0ax1

0ax1 commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

@vyasr @GregoryKimball Thanks again for having a look! We would love to keep the possibility open to upstream Vortex into cuDF but starting off as an official cuDF example sounds great actually. Shall I just open a new PR for that or how do you picture the flow here?

@0ax1

0ax1 commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

Closing, as we decided to add Vortex as an example for now.

@0ax1 0ax1 closed this Oct 23, 2026
@0ax1
0ax1 deleted the ad/ndsh-vortex-benchmarks branch November 2, 2026 11:23
@vyasr

vyasr commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

@0ax1 sorry for the delay, I was out of town for the past two weeks. Since you already closed this PR, let's use a new one for the example. I'll comment further on #23877.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CMake CMake build issue libcudf Affects libcudf (C++/CUDA) code.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants