Skip to content

Commit 2c8f160

Browse files
authored
feat(cuda): support column projection and dictionary decoding in file scans (#9934)
## Summary - Add column projection to CUDA scans, avoiding reads and decoding for unselected columns. - Decode dictionaries for consistent Arrow stream schemas, preserving defaults for other exports. - Cache schemas without reading a batch. - Preserve row-block boundaries, register the CUDA layout edition, and generate the C header. --------- Signed-off-by: Alexander Droste <alexander.droste@protonmail.com>
1 parent 5c1671e commit 2c8f160

19 files changed

Lines changed: 2064 additions & 504 deletions

File tree

‎.github/workflows/cuda.yaml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ jobs:
4545
- "vortex-test/**"
4646
- "pyproject.toml"
4747
- "uv.lock"
48+
- "Cargo.toml"
49+
- "Cargo.lock"
4850
- ".github/workflows/**"
4951
5052
cuda-build-lint:
@@ -71,6 +73,8 @@ jobs:
7173
cargo build --profile ci --locked --all-features --all-targets
7274
-p vortex-cuda -p vortex-cuda-ffi -p vortex-cub -p vortex-nvcomp
7375
-p gpu-scan-cli -p vortex-test-e2e-cuda -p vortex-python-cuda
76+
- name: Verify generated CUDA FFI header is up to date
77+
run: git --no-pager diff --exit-code -- vortex-cuda/ffi/cinclude/vortex_cuda.h
7478
- name: Clippy CUDA crates
7579
run: |
7680
cargo clippy --profile ci --locked --all-features --all-targets \

‎Cargo.lock‎

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vortex-cuda/ffi/Cargo.toml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ vortex-cuda = { path = ".." }
2222
vortex-ffi = { path = "../../vortex-ffi" }
2323

2424
[dev-dependencies]
25+
cudarc = { workspace = true }
26+
tempfile = { workspace = true }
2527
vortex-cuda-macros = { workspace = true }
2628

29+
[build-dependencies]
30+
cbindgen = { workspace = true }
31+
2732
[lib]
2833
name = "vortex_cuda_ffi"
2934
crate-type = ["rlib", "staticlib", "cdylib"]

‎vortex-cuda/ffi/README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,8 @@ pool and CUDA state are reused as well.
2727
On Linux, use `vx_cuda_scan_path_arrow_device_stream_with_options` with
2828
`vx_cuda_scan_options.flags = VX_CUDA_SCAN_FLAG_DIRECT_IO` to bypass the operating system page
2929
cache for pooled data-plane reads. Footer and zone-map reads remain buffered on the host.
30+
31+
## Generated header
32+
33+
`build.rs` generates `cinclude/vortex_cuda.h` with cbindgen. Edit `src/lib.rs` or `cbindgen.toml`
34+
and commit the regenerated header. It skips clang-format; CUDA CI checks for header drift.

‎vortex-cuda/ffi/build.rs‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use std::error::Error;
5+
use std::fs;
6+
use std::fs::OpenOptions;
7+
use std::io;
8+
use std::io::Write;
9+
use std::process;
10+
11+
fn main() -> Result<(), Box<dyn Error>> {
12+
println!("cargo:rerun-if-changed=src");
13+
println!("cargo:rerun-if-changed=cbindgen.toml");
14+
println!("cargo:rerun-if-changed=build.rs");
15+
16+
let header = "cinclude/vortex_cuda.h";
17+
let mut generated = Vec::new();
18+
// Parse only the FFI source to avoid macro expansion and recursive CUDA builds.
19+
cbindgen::Builder::new()
20+
.with_src("src/lib.rs")
21+
.with_config(cbindgen::Config::from_file("cbindgen.toml")?)
22+
.generate()?
23+
.write(&mut generated);
24+
match fs::read(header) {
25+
Ok(existing) if existing == generated => return Ok(()),
26+
Ok(_) => {}
27+
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
28+
Err(error) => return Err(format!("failed to read {header}: {error}").into()),
29+
}
30+
publish_header(header, &generated)
31+
.map_err(|error| format!("failed to publish {header}: {error}"))?;
32+
Ok(())
33+
}
34+
35+
fn publish_header(header: &str, generated: &[u8]) -> io::Result<()> {
36+
let mut attempt = 0_u64;
37+
let (temporary, mut file) = loop {
38+
let temporary = format!("{header}.{}.{attempt}.tmp", process::id());
39+
match OpenOptions::new()
40+
.write(true)
41+
.create_new(true)
42+
.open(&temporary)
43+
{
44+
Ok(file) => break (temporary, file),
45+
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => attempt += 1,
46+
Err(error) => return Err(error),
47+
}
48+
};
49+
50+
// Publish atomically so concurrent builds never see a truncated header.
51+
let written = file.write_all(generated);
52+
drop(file);
53+
let result = written.and_then(|()| fs::rename(&temporary, header));
54+
if result.is_err()
55+
&& let Err(error) = fs::remove_file(&temporary)
56+
{
57+
eprintln!("failed to remove temporary header {temporary}: {error}");
58+
}
59+
result
60+
}

‎vortex-cuda/ffi/cbindgen.toml‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
language = "C"
5+
braces = "SameLine"
6+
cpp_compat = true
7+
usize_is_size_t = true
8+
style = "both"
9+
no_includes = true
10+
11+
header = """
12+
// SPDX-License-Identifier: Apache-2.0
13+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
14+
15+
// clang-format off
16+
17+
#pragma once
18+
19+
// THIS FILE IS AUTO-GENERATED, DO NOT MAKE EDITS DIRECTLY
20+
21+
#include <stddef.h>
22+
#include <stdint.h>
23+
24+
#include "vortex.h"
25+
26+
/* Link against the CUDA-enabled FFI library that provides both the base Vortex FFI and these CUDA
27+
* entry points. Do not pass Vortex handles between independently linked Rust FFI libraries. */
28+
29+
/* Definitions from the Arrow C Device data interface. Define USE_OWN_ARROW_DEVICE to skip them.
30+
* https://arrow.apache.org/docs/format/CDeviceDataInterface.html */
31+
#if !defined(ARROW_C_DEVICE_DATA_INTERFACE) && !defined(USE_OWN_ARROW_DEVICE)
32+
#define ARROW_C_DEVICE_DATA_INTERFACE
33+
34+
typedef int32_t ArrowDeviceType;
35+
#define ARROW_DEVICE_CPU 1
36+
#define ARROW_DEVICE_CUDA 2
37+
#define ARROW_DEVICE_CUDA_HOST 3
38+
#define ARROW_DEVICE_OPENCL 4
39+
#define ARROW_DEVICE_VULKAN 7
40+
#define ARROW_DEVICE_METAL 8
41+
#define ARROW_DEVICE_VPI 9
42+
#define ARROW_DEVICE_ROCM 10
43+
#define ARROW_DEVICE_ROCM_HOST 11
44+
#define ARROW_DEVICE_EXT_DEV 12
45+
#define ARROW_DEVICE_CUDA_MANAGED 13
46+
#define ARROW_DEVICE_ONEAPI 14
47+
#define ARROW_DEVICE_WEBGPU 15
48+
#define ARROW_DEVICE_HEXAGON 16
49+
50+
struct ArrowDeviceArray {
51+
struct ArrowArray array;
52+
int64_t device_id;
53+
ArrowDeviceType device_type;
54+
void *sync_event;
55+
int64_t reserved[3];
56+
};
57+
#endif
58+
59+
#if !defined(ARROW_C_DEVICE_STREAM_INTERFACE) && !defined(USE_OWN_ARROW_DEVICE)
60+
#define ARROW_C_DEVICE_STREAM_INTERFACE
61+
struct ArrowDeviceArrayStream {
62+
ArrowDeviceType device_type;
63+
int (*get_schema)(struct ArrowDeviceArrayStream *, struct ArrowSchema *out);
64+
int (*get_next)(struct ArrowDeviceArrayStream *, struct ArrowDeviceArray *out);
65+
const char *(*get_last_error)(struct ArrowDeviceArrayStream *);
66+
void (*release)(struct ArrowDeviceArrayStream *);
67+
void *private_data;
68+
};
69+
#endif
70+
"""
71+
72+
trailer = "// clang-format on"
73+
74+
# These externally defined Arrow ABI types use struct tags, not typedef names.
75+
[export.rename]
76+
"ArrowDeviceArray" = "struct ArrowDeviceArray"
77+
"ArrowDeviceArrayStream" = "struct ArrowDeviceArrayStream"

0 commit comments

Comments
 (0)