Skip to content

Commit ad262a3

Browse files
committed
Expose pooled CUDA file scans through FFI
Signed-off-by: Onur Satici <onur@spiraldb.com>
1 parent e727c46 commit ad262a3

7 files changed

Lines changed: 101 additions & 9 deletions

File tree

vortex-cuda/ffi/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ Use `vx_cuda_session_new` to initialize CUDA once and reuse it across exports.
1919

2020
Use `vx_cuda_array_sink_open_file` to open a standard Vortex file sink configured to produce CUDA-readable files.
2121
Push host-resident arrays and close the sink using the standard `vx_array_sink_*` APIs.
22+
23+
Use `vx_cuda_scan_path_arrow_device_stream` to read such a local file through pinned host buffers
24+
and receive an Arrow C Device stream. Reuse the same CUDA session across scans so the pinned buffer
25+
pool and CUDA state are reused as well.

vortex-cuda/ffi/cinclude/vortex_cuda.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,21 @@ vx_array_sink *vx_cuda_array_sink_open_file(const vx_session *session,
7575
const vx_dtype *dtype,
7676
vx_error **error_out);
7777

78+
/**
79+
* Scan a local CUDA-compatible Vortex file as an Arrow C Device stream.
80+
*
81+
* Files written by `vx_cuda_array_sink_open_file` are compatible with this path. Reusing the same
82+
* CUDA session across calls also reuses the pinned host buffers used to stage file reads.
83+
*
84+
* On success returns 0 and writes an owned `ArrowDeviceArrayStream` to `out_stream`. The caller
85+
* must release the stream and each produced `ArrowDeviceArray` through their embedded Arrow
86+
* release callbacks. On error returns 1 and writes a `vx_error` to `error_out` when non-NULL.
87+
*/
88+
int vx_cuda_scan_path_arrow_device_stream(const vx_session *session,
89+
vx_view path,
90+
struct ArrowDeviceArrayStream *out_stream,
91+
vx_error **error_out);
92+
7893
/**
7994
* Export a borrowed Vortex array for cuDF's Arrow Device import path.
8095
*

vortex-cuda/ffi/src/lib.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ use std::ptr;
1313
use std::sync::Arc;
1414

1515
use arrow_schema::ffi::FFI_ArrowSchema;
16+
use vortex::array::stream::ArrayStreamExt;
1617
use vortex::compressor::BtrBlocksCompressorBuilder;
1718
use vortex::error::VortexResult;
1819
use vortex::error::vortex_ensure;
20+
use vortex::file::OpenOptionsSessionExt;
1921
use vortex::file::WriteStrategyBuilder;
22+
use vortex::io::VortexReadAt;
23+
use vortex::io::runtime::BlockingRuntime;
24+
use vortex::io::session::RuntimeSessionExt;
2025
use vortex::session::SessionExt;
2126
use vortex::session::VortexSession;
2227
use vortex_cuda::CudaSession;
28+
use vortex_cuda::PooledFileReadAt;
2329
use vortex_cuda::arrow::ArrowDeviceArray;
2430
use vortex_cuda::arrow::ArrowDeviceArrayStream;
2531
use vortex_cuda::arrow::DeviceArrayExt;
@@ -104,6 +110,58 @@ pub unsafe extern "C-unwind" fn vx_cuda_array_sink_open_file(
104110
})
105111
}
106112

113+
/// Scan a local Vortex file through pinned host buffers and export an Arrow C Device stream.
114+
///
115+
/// The file must use encodings and layouts supported by the CUDA execution path, such as files
116+
/// written by [`vx_cuda_array_sink_open_file`]. Pinned staging buffers are reused across scans made
117+
/// with the same CUDA session.
118+
///
119+
/// On success returns `0` and writes an owned [`ArrowDeviceArrayStream`] to `out_stream`. The
120+
/// caller must release the stream and each array produced by it through their embedded Arrow
121+
/// release callbacks.
122+
///
123+
/// On error returns `1` and, when `error_out` is non-null, writes a `vx_error` (free with
124+
/// `vx_error_free`).
125+
///
126+
/// # Safety
127+
///
128+
/// `session` must be a valid borrowed handle created by `vortex-ffi`. `path` must be valid for the
129+
/// duration of this call and contain UTF-8. `out_stream` must be a valid writable pointer. If
130+
/// `error_out` is non-null, it must be valid for writing one error pointer.
131+
#[unsafe(no_mangle)]
132+
pub unsafe extern "C-unwind" fn vx_cuda_scan_path_arrow_device_stream(
133+
session: *const vx_session,
134+
path: vx_view,
135+
out_stream: *mut ArrowDeviceArrayStream,
136+
error_out: *mut *mut vx_error,
137+
) -> c_int {
138+
try_or(error_out, VX_CUDA_ERR, || {
139+
vortex_ensure!(!out_stream.is_null(), "null ArrowDeviceArrayStream output");
140+
141+
let path = unsafe { path.as_str() }?.to_owned();
142+
let session = session_with_cuda(unsafe { vx_session_ref(session) }?)?;
143+
let cuda_session = session.get::<CudaSession>();
144+
let stream = cuda_session.stream()?;
145+
let pool = Arc::clone(cuda_session.pinned_buffer_pool());
146+
drop(cuda_session);
147+
148+
let reader: Arc<dyn VortexReadAt> = Arc::new(PooledFileReadAt::open(
149+
path,
150+
session.handle(),
151+
pool,
152+
stream,
153+
)?);
154+
let array_stream = ffi_runtime().block_on(async {
155+
let file = session.open_options().open(reader).await?;
156+
Ok::<_, vortex::error::VortexError>(file.scan()?.into_array_stream()?.boxed())
157+
})?;
158+
let device_stream = array_stream.export_device_array_stream(&session, ffi_runtime())?;
159+
160+
unsafe { ptr::write(out_stream, device_stream) };
161+
Ok(VX_CUDA_OK)
162+
})
163+
}
164+
107165
/// Export a borrowed Vortex array for cuDF's Arrow Device import path.
108166
///
109167
/// On success returns `0` and writes independently releasable `out_schema` and `out_array`; the

vortex-cuda/gpu-scan-cli/src/main.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,9 @@ use vortex::file::WriteStrategyBuilder;
3131
use vortex::io::session::RuntimeSessionExt;
3232
use vortex::session::VortexSession;
3333
use vortex_cuda::CudaSession;
34-
use vortex_cuda::PinnedByteBufferPool;
3534
use vortex_cuda::PooledByteBufferReadAt;
3635
use vortex_cuda::PooledFileReadAt;
3736
use vortex_cuda::TracingLaunchStrategy;
38-
use vortex_cuda::VortexCudaStreamPool;
3937
use vortex_cuda::executor::CudaArrayExt;
4038
use vortex_cuda::layout::CudaFlatLayoutStrategy;
4139
use vortex_cuda::layout::register_cuda_layout;
@@ -155,11 +153,10 @@ async fn cmd_scan(path: PathBuf, gpu_file: bool, json_output: bool) -> VortexRes
155153
let mut cuda_ctx = CudaSession::create_execution_ctx(&session)?
156154
.with_launch_strategy(Arc::new(TracingLaunchStrategy));
157155

158-
let pool = Arc::new(PinnedByteBufferPool::new(Arc::clone(
159-
cuda_ctx.stream().context(),
160-
)));
161-
let cuda_stream =
162-
VortexCudaStreamPool::new(Arc::clone(cuda_ctx.stream().context()), 1).stream()?;
156+
let cuda_session = session.get::<CudaSession>();
157+
let pool = Arc::clone(cuda_session.pinned_buffer_pool());
158+
let cuda_stream = cuda_session.stream()?;
159+
drop(cuda_session);
163160
let handle = session.handle();
164161

165162
let gpu_file_handle = if gpu_file {

vortex-cuda/src/pinned.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ pub struct PinnedByteBufferPool {
113113
puts: AtomicU64,
114114
}
115115

116+
impl std::fmt::Debug for PinnedByteBufferPool {
117+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118+
f.debug_struct("PinnedByteBufferPool")
119+
.field("max_keep_per_size", &self.max_keep_per_size)
120+
.field("stats", &self.stats())
121+
.finish_non_exhaustive()
122+
}
123+
}
124+
116125
struct InflightPinnedBuffer {
117126
event: Arc<CudaEvent>,
118127
buffer: PinnedByteBuffer,

vortex-cuda/src/session.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::executor::CudaExecute;
2323
pub use crate::executor::CudaExecutionCtx;
2424
use crate::initialize_cuda;
2525
use crate::kernel::KernelLoader;
26+
use crate::pinned::PinnedByteBufferPool;
2627
use crate::stream::VortexCudaStream;
2728
use crate::stream_pool::VortexCudaStreamPool;
2829

@@ -40,6 +41,7 @@ pub struct CudaSession {
4041
export_device_array: Arc<dyn ExportDeviceArray>,
4142
kernel_loader: Arc<KernelLoader>,
4243
stream_pool: Arc<VortexCudaStreamPool>,
44+
pinned_buffer_pool: Arc<PinnedByteBufferPool>,
4345
}
4446

4547
impl CudaSession {
@@ -57,12 +59,14 @@ impl CudaSession {
5759
Arc::clone(&context),
5860
stream_pool_capacity,
5961
));
62+
let pinned_buffer_pool = Arc::new(PinnedByteBufferPool::new(Arc::clone(&context)));
6063
Self {
6164
context,
6265
kernels: Arc::new(DashMap::default()),
6366
kernel_loader: Arc::new(KernelLoader::new()),
6467
export_device_array: Arc::new(CanonicalDeviceArrayExport),
6568
stream_pool,
69+
pinned_buffer_pool,
6670
}
6771
}
6872

@@ -105,6 +109,11 @@ impl CudaSession {
105109
self.stream_pool.stream()
106110
}
107111

112+
/// Returns the session-scoped pool used for staging file reads in pinned host memory.
113+
pub fn pinned_buffer_pool(&self) -> &Arc<PinnedByteBufferPool> {
114+
&self.pinned_buffer_pool
115+
}
116+
108117
/// Registers CUDA support for an array encoding.
109118
///
110119
/// # Arguments

vortex-ffi/src/string.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ impl vx_view {
6161
///
6262
/// # Safety
6363
///
64-
/// Same requirements as in as_bytes
65-
pub(crate) unsafe fn as_str<'a>(&self) -> VortexResult<&'a str> {
64+
/// `self.ptr` must be valid for `self.len` reads, or null when `self.len` is zero.
65+
pub unsafe fn as_str<'a>(&self) -> VortexResult<&'a str> {
6666
str::from_utf8(unsafe { self.as_bytes() }?).map_err(|e| vortex_err!("invalid utf-8: {e}"))
6767
}
6868
}

0 commit comments

Comments
 (0)