diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index fb439d8751..e696a7e1de 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -16,6 +16,7 @@ jobs:
- checks
- conda-cpp-build
- conda-cpp-tests
+ - conda-java-tests
- conda-python-build
- conda-python-tests
- docs-build
@@ -72,6 +73,16 @@ jobs:
if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp
with:
build_type: pull-request
+ conda-java-tests:
+ needs: conda-cpp-build
+ secrets: inherit
+ uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.12
+ with:
+ build_type: pull-request
+ node_type: "gpu-v100-latest-1"
+ arch: "amd64"
+ container_image: "rapidsai/ci-conda:latest"
+ run_script: "ci/test_java.sh"
conda-python-build:
needs: conda-cpp-build
secrets: inherit
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index edec9999fd..787a2afd5a 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -30,3 +30,15 @@ jobs:
branch: ${{ inputs.branch }}
date: ${{ inputs.date }}
sha: ${{ inputs.sha }}
+ conda-java-tests:
+ secrets: inherit
+ uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@branch-24.12
+ with:
+ build_type: nightly
+ branch: ${{ inputs.branch }}
+ date: ${{ inputs.date }}
+ sha: ${{ inputs.sha }}
+ node_type: "gpu-v100-latest-1"
+ arch: "amd64"
+ container_image: "rapidsai/ci-conda:latest"
+ run_script: "ci/test_java.sh"
diff --git a/ci/test_java.sh b/ci/test_java.sh
new file mode 100755
index 0000000000..b1a6ec5f58
--- /dev/null
+++ b/ci/test_java.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# Copyright (c) 2024, NVIDIA CORPORATION.
+
+set -euo pipefail
+
+. /opt/conda/etc/profile.d/conda.sh
+
+rapids-logger "Generate java testing dependencies"
+rapids-dependency-file-generator \
+ --output conda \
+ --file-key test_java \
+ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch)" | tee env.yaml
+
+rapids-mamba-retry env create --yes -f env.yaml -n test
+
+# Temporarily allow unbound variables for conda activation.
+set +u
+conda activate test
+set -u
+
+rapids-logger "Downloading artifacts from previous jobs"
+CPP_CHANNEL=$(rapids-download-conda-from-s3 cpp)
+
+rapids-print-env
+
+rapids-mamba-retry install \
+ --channel "${CPP_CHANNEL}" \
+ libkvikio libkvikio-tests
+
+rapids-logger "Check GPU usage"
+nvidia-smi
+
+EXITCODE=0
+trap "EXITCODE=1" ERR
+set +e
+
+rapids-logger "Run Java tests"
+mkdir /mnt/nvme
+touch /mnt/nvme/java_test
+pushd java
+mvn test -B
+popd
+
+rapids-logger "Test script exiting with value: $EXITCODE"
+exit ${EXITCODE}
diff --git a/dependencies.yaml b/dependencies.yaml
index 39ba3aaa17..c011485e37 100644
--- a/dependencies.yaml
+++ b/dependencies.yaml
@@ -92,6 +92,14 @@ files:
key: test
includes:
- test_python
+ test_java:
+ output: none
+ includes:
+ - build-universal
+ - build-cpp
+ - cuda_version
+ - cuda
+ - test_java
channels:
- rapidsai
- rapidsai-nightly
@@ -355,3 +363,12 @@ dependencies:
- matrix: # All CUDA 11 versions
packages:
- cuda-python>=11.7.1,<12.0a0
+ test_java:
+ common:
+ - output_types: conda
+ packages:
+ - cxx-compiler
+ - *cmake_ver
+ - maven
+ - openjdk=11.*
+ - make
diff --git a/java/README.md b/java/README.md
new file mode 100644
index 0000000000..5ea69b0928
--- /dev/null
+++ b/java/README.md
@@ -0,0 +1,74 @@
+# Java KvikIO Bindings
+
+## Summary
+These Java KvikIO bindings for GDS currently support only synchronous read and write IO operations using the underlying CuFile API. Support for batch IO and asynchronous operations are not yet supported.
+
+## Dependencies
+The Java KvikIO bindings have been developed to work on Linux based systems and require [CUDA](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html) to be installed and for [GDS](https://docs.nvidia.com/gpudirect-storage/troubleshooting-guide/index.html) to be properly enabled. To compile the shared library it is also necessary to have a JDK installed. To run the included example, it is also necessary to install JCuda as it is used to handle memory allocations and the transfer of data between host and GPU memory. JCuda jar files supporting CUDA 12.x can be found here:
+[jcuda-12.0.0.jar](https://repo1.maven.org/maven2/org/jcuda/jcuda/12.0.0/jcuda-12.0.0.jar),
+[jcuda-natives-12.0.0.jar](https://repo1.maven.org/maven2/org/jcuda/jcuda-natives/12.0.0/jcuda-natives-12.0.0.jar)
+
+For more information on JCuda and potentially more up to date installation instructions or jar files, see here:
+[JCuda](http://javagl.de/jcuda.org/), [JCuda Usage](https://github.com/jcuda/jcuda-main/blob/master/USAGE.md), [JCuda Maven Repo](https://mvnrepository.com/artifact/org.jcuda)
+
+## Compilation
+To recompile the .so file for your local system run the following command. Note: Update the command to reflect the directory where you have installed CUDA and your JDK.
+
+ /usr/local/cuda/bin/nvcc -shared -o libCuFileJNI.so -I/usr/local/cuda/include/ -I/usr/lib/jvm/java-21-openjdk-amd64/include/ -I/usr/lib/jvm/java-21-openjdk-amd64/include/linux src/main/native/src/CuFileJni.cpp --compiler-options "-fPIC" -lcufile
+
+The resulting .so file must be in your JVM library path when running upstream Java programs. If it is not already placed on your path in can be included by including an argument like the following:
+
+ -Djava.library.path={path/to/your/so/file/}
+
+## Examples
+An example for how to use the Java KvikIO bindings can be found in src/main/java/bindings/kvikio/example . Note: This example has a dependency on JCuda so ensure that when running the example the JCuda shared library files are on the JVM library path along with the libCuFileJNI.so file.
+
+### Specific instructions to run the example using Maven
+
+#### Compile the shared library and Java files with Maven
+
+ cd kvikio/java/
+ mvn clean install
+
+#### Setup a test file target NOTE: your mount directory may differ from /mnt/nvme, so update this command appropriately as well as example/Main.java to point to the correct file path.
+
+ touch /mnt/nvme/java_test
+
+#### Run example
+
+ cd kvikio/java/
+ java -cp target/cufile-24.12.0-SNAPSHOT.jar:$HOME/.m2/repository/org/jcuda/jcuda/12.0.0/jcuda-12.0.0.jar:$HOME/.m2/repository/org/jcuda/jcuda-natives/12.0.0/jcuda-natives-12.0.0.jar -Djava.library.path=./target bindings.kvikio.example.Main
+
+### Specific instructions to run the example from a terminal
+
+#### Compile class files
+
+ cd kvikio/java/src/main/java/bindings/kvikio/cufile
+ javac *.java
+
+#### Retrieve Jcuda jar files
+
+ cd kvikio/java/
+ mkdir lib
+ cd lib
+ wget https://repo1.maven.org/maven2/org/jcuda/jcuda/12.0.0/jcuda-12.0.0.jar
+ wget https://repo1.maven.org/maven2/org/jcuda/jcuda-natives/12.0.0/jcuda-natives-12.0.0.jar
+
+#### Compile shared library
+
+ cd kvikio/java/lib
+ /usr/local/cuda/bin/nvcc -shared -o libCuFileJNI.so -I/usr/local/cuda/include/ -I/usr/lib/jvm/java-21-openjdk-amd64/include/ -I/usr/lib/jvm/java-21-openjdk-amd64/include/linux ../src/main/native/src/CuFileJni.cpp --compiler-options "-fPIC" -lcufile
+
+#### Setup a test file target NOTE: your mount directory may differ from /mnt/nvme, so update this command appropriately as well as example/Main.java to point to the correct file path.
+
+ touch /mnt/nvme/java_test
+
+#### Compile example file
+
+ cd kvikio/java/src/main/java
+ javac -cp .:../../../lib/jcuda-12.0.0.jar:../../../lib/jcuda-natives-12.0.0.jar bindings/kvikio/example/Main.java
+
+#### Run example
+
+ cd kvikio/java/src/main/java
+ java -cp .:../../../lib/jcuda-12.0.0.jar:../../../lib/jcuda-natives-12.0.0.jar -Djava.library.path=../../../lib/ bindings.kvikio.example.main
diff --git a/java/pom.xml b/java/pom.xml
new file mode 100644
index 0000000000..1abfa2ca14
--- /dev/null
+++ b/java/pom.xml
@@ -0,0 +1,146 @@
+
+
+
+ 4.0.0
+
+ ai.rapids.kvikio
+ cufile
+ 24.12.0-SNAPSHOT
+
+ cufile
+
+ This project provides Java bindings for the GPUDirect Storage cufile library, enabling the GPU to load and
+ save large amounts of data to and from persistent storage. This is still a work in progress so some APIs may change.
+
+ http://ai.rapids
+
+
+ UTF-8
+ 11
+ 11
+ 5.4.2
+ 12.0.0
+ 3.23.2-b1
+
+
+
+
+ org.jcuda
+ jcuda
+ ${jcuda.version}
+ test
+
+
+ org.jcuda
+ jcuda-natives
+ ${jcuda.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ ${junit.version}
+ test
+
+
+
+
+
+
+
+ maven-exec-plugin
+ 1.6.0
+
+
+ maven-clean-plugin
+ 3.1.0
+
+ true
+
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+ -Djava.library.path=${project.build.directory}:${java.library.path}
+
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ 1.2.0
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.4.2
+
+
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ com.googlecode.cmake-maven-project
+ cmake-maven-plugin
+ ${cmake.version}
+
+
+ cmake-generate
+
+ generate
+
+
+ ${project.basedir}/src/main/native
+ ${project.build.directory}/native-build
+
+
+
+
+
+
+ cmake-compile
+
+ compile
+
+
+ ${project.build.directory}/native-build
+
+
+
+
+
+
+
diff --git a/java/src/main/java/ai/rapids/kvikio/cufile/CuFile.java b/java/src/main/java/ai/rapids/kvikio/cufile/CuFile.java
new file mode 100644
index 0000000000..498c18fffa
--- /dev/null
+++ b/java/src/main/java/ai/rapids/kvikio/cufile/CuFile.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ai.rapids.kvikio.cufile;
+
+public class CuFile {
+ private static boolean initialized = false;
+ private static CuFileDriver driver;
+
+ static {
+ initialize();
+ }
+
+ static synchronized void initialize() {
+ if (!initialized) {
+ try {
+ System.loadLibrary("CuFileJNI");
+ driver = new CuFileDriver();
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ driver.close();
+ }));
+ initialized = true;
+ } catch (Throwable t) {
+ System.out.println("could not load cufile jni library:"+ t.getMessage());
+ }
+ }
+ }
+
+ public static boolean libraryLoaded() {
+ return initialized;
+ }
+}
diff --git a/java/src/main/java/ai/rapids/kvikio/cufile/CuFileDriver.java b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileDriver.java
new file mode 100644
index 0000000000..35b81624d3
--- /dev/null
+++ b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileDriver.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ai.rapids.kvikio.cufile;
+
+final class CuFileDriver implements AutoCloseable {
+ private final long pointer;
+
+ CuFileDriver() {
+ pointer = create();
+ }
+
+ public void close() {
+ destroy(pointer);
+ }
+
+ private static native long create();
+
+ private static native void destroy(long pointer);
+}
diff --git a/java/src/main/java/ai/rapids/kvikio/cufile/CuFileHandle.java b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileHandle.java
new file mode 100644
index 0000000000..025ef5cb40
--- /dev/null
+++ b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileHandle.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ai.rapids.kvikio.cufile;
+
+abstract class CuFileHandle implements AutoCloseable {
+ private final long pointer;
+
+ static {
+ CuFile.initialize();
+ }
+
+ protected CuFileHandle(long pointer) {
+ this.pointer = pointer;
+ }
+
+ public void close() {
+ destroy(pointer);
+ }
+
+ protected long getPointer() {
+ return this.pointer;
+ }
+
+ private static native void destroy(long pointer);
+}
diff --git a/java/src/main/java/ai/rapids/kvikio/cufile/CuFileReadHandle.java b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileReadHandle.java
new file mode 100644
index 0000000000..44bf498837
--- /dev/null
+++ b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileReadHandle.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ai.rapids.kvikio.cufile;
+
+public final class CuFileReadHandle extends CuFileHandle {
+
+ public CuFileReadHandle(String path) {
+ super(create(path));
+ }
+
+ public void read(long device_pointer, long size, long file_offset, long device_offset) {
+ readFile(getPointer(), device_pointer, size, file_offset, device_offset);
+ }
+
+ private static native long create(String path);
+
+ private static native void readFile(long file_pointer, long device_pointer, long size, long file_offset,
+ long device_offset);
+
+}
diff --git a/java/src/main/java/ai/rapids/kvikio/cufile/CuFileWriteHandle.java b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileWriteHandle.java
new file mode 100644
index 0000000000..17c802d937
--- /dev/null
+++ b/java/src/main/java/ai/rapids/kvikio/cufile/CuFileWriteHandle.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ai.rapids.kvikio.cufile;
+
+public final class CuFileWriteHandle extends CuFileHandle {
+
+ public CuFileWriteHandle(String path) {
+ super(create(path));
+ }
+
+ public void write(long device_pointer, long size, long file_offset, long buffer_offset) {
+ writeFile(getPointer(), device_pointer, size, file_offset, buffer_offset);
+ }
+
+ private static native long create(String path);
+
+ private static native void writeFile(long file_pointer, long device_pointer, long size, long file_offset,
+ long buffer_offset);
+}
diff --git a/java/src/main/native/CMakeLists.txt b/java/src/main/native/CMakeLists.txt
new file mode 100644
index 0000000000..d826253e19
--- /dev/null
+++ b/java/src/main/native/CMakeLists.txt
@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2024, NVIDIA CORPORATION.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+# in compliance with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License
+# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+# or implied. See the License for the specific language governing permissions and limitations under
+# the License.
+#
+
+cmake_minimum_required(VERSION 3.23)
+
+project(CuFileJNI LANGUAGES CXX CUDA)
+
+find_package(CUDA REQUIRED)
+
+find_package(JNI REQUIRED)
+
+add_library(CuFileJNI SHARED src/CuFileJni.cpp)
+
+set_source_files_properties(src/CuFileJni.cpp PROPERTIES LANGUAGE CUDA)
+
+target_include_directories(
+ CuFileJNI PRIVATE ${CUDA_INCLUDE_DIRS} ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2}
+)
+
+target_link_libraries(CuFileJNI cufile cuda)
diff --git a/java/src/main/native/src/CuFileJni.cpp b/java/src/main/native/src/CuFileJni.cpp
new file mode 100644
index 0000000000..d59394161b
--- /dev/null
+++ b/java/src/main/native/src/CuFileJni.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+char const* GetCuErrorString(CUresult cu_result)
+{
+ char const* description;
+ if (cuGetErrorName(cu_result, &description) != CUDA_SUCCESS) description = "unknown cuda error";
+ return description;
+}
+
+std::string cuFileGetErrorString(int error_code)
+{
+ return IS_CUFILE_ERR(error_code) ? std::string(CUFILE_ERRSTR(error_code))
+ : std::string(std::strerror(error_code));
+}
+
+std::string cuFileGetErrorString(CUfileError_t status)
+{
+ std::string error = cuFileGetErrorString(status.err);
+ if (IS_CUDA_ERR(status)) { error.append(".").append(GetCuErrorString(status.cu_err)); }
+ return error;
+}
+
+/** @brief RAII wrapper for a file descriptor and the corresponding cuFile handle. */
+class cufile_file {
+ public:
+ /**
+ * @brief Construct a file wrapper.
+ *
+ * Should not be called directly; use the following factory methods instead.
+ *
+ * @param file_descriptor A valid file descriptor.
+ */
+ explicit cufile_file(int file_descriptor) : file_descriptor_{file_descriptor}
+ {
+ CUfileDescr_t cufile_descriptor{CU_FILE_HANDLE_TYPE_OPAQUE_FD, file_descriptor_};
+ auto const status = cuFileHandleRegister(&cufile_handle_, &cufile_descriptor);
+ if (status.err != CU_FILE_SUCCESS) {
+ close(file_descriptor_);
+ throw std::logic_error("Failed to register cuFile handle: " + cuFileGetErrorString(status));
+ }
+ }
+
+ /**
+ * @brief Factory method to create a file wrapper for reading.
+ *
+ * @param path Absolute path of the file to read from. This file must exist.
+ * @return std::unique_ptr for reading.
+ */
+ static auto make_reader(char const* path)
+ {
+ auto const file_descriptor = open(path, O_RDONLY | O_DIRECT);
+ if (file_descriptor < 0) {
+ throw std::logic_error("Failed to open file to read: " + cuFileGetErrorString(errno));
+ }
+ return std::make_unique(file_descriptor);
+ }
+
+ /**
+ * @brief Factory method to create a file wrapper for writing.
+ *
+ * @param path Absolute path of the file to write to. This creates the file if it does not already
+ * exist..
+ * @return std::unique_ptr for writing.
+ */
+ static auto make_writer(char const* path)
+ {
+ auto const file_descriptor = open(path, O_CREAT | O_WRONLY | O_DIRECT, S_IRUSR | S_IWUSR);
+ if (file_descriptor < 0) {
+ throw std::logic_error("Failed to open file to write: " + cuFileGetErrorString(errno));
+ }
+ return std::make_unique(file_descriptor);
+ }
+
+ // Disable copy (and move) semantics.
+ cufile_file(cufile_file const&) = delete;
+ cufile_file& operator=(cufile_file const&) = delete;
+
+ /** @brief Destroy the file wrapper by de-registering the cuFile handle and closing the file. */
+ ~cufile_file()
+ {
+ cuFileHandleDeregister(cufile_handle_);
+ close(file_descriptor_);
+ }
+
+ /**
+ * @brief Read the file into a device buffer.
+ *
+ * @param buffer Device buffer to read the file content into.
+ * @param file_offset Starting offset from which to read the file.
+ */
+ void read(void* buffer,
+ std::size_t size,
+ std::size_t file_offset,
+ std::size_t device_offset) const
+ {
+ auto const status = cuFileRead(cufile_handle_, buffer, size, file_offset, device_offset);
+
+ if (status < 0) {
+ if (IS_CUFILE_ERR(status)) {
+ throw std::logic_error("Failed to read file into buffer: " + cuFileGetErrorString(status));
+ } else {
+ throw std::logic_error("Failed to read file into buffer: " + cuFileGetErrorString(errno));
+ }
+ }
+ }
+
+ void write(void* buffer, std::size_t size, std::size_t file_offset, std::size_t buffer_offset)
+ {
+ auto const status = cuFileWrite(cufile_handle_, buffer, size, file_offset, buffer_offset);
+ if (status < 0) {
+ if (IS_CUFILE_ERR(status)) {
+ throw std::logic_error("Failed to write file from buffer: " + cuFileGetErrorString(status));
+ } else {
+ throw std::logic_error("Failed to write file from buffer: " + cuFileGetErrorString(errno));
+ }
+ }
+ }
+
+ private:
+ /// The underlying file descriptor.
+ int file_descriptor_;
+ /// The registered cuFile handle.
+ CUfileHandle_t cufile_handle_{};
+};
+
+class cufile_driver {
+ public:
+ cufile_driver()
+ {
+ auto const status = cuFileDriverOpen();
+ if (status.err != CU_FILE_SUCCESS) {
+ throw std::logic_error("Failed to initialize cuFile driver: " + cuFileGetErrorString(status));
+ }
+ }
+
+ cufile_driver(cufile_driver const&) = delete;
+ cufile_driver& operator=(cufile_driver const&) = delete;
+
+ ~cufile_driver() { cuFileDriverClose(); }
+};
+
+extern "C" {
+#include
+
+JNIEXPORT jlong JNICALL Java_ai_rapids_kvikio_cufile_CuFileDriver_create(JNIEnv* env, jclass)
+{
+ try {
+ return reinterpret_cast(new cufile_driver());
+ } catch (const std::exception& e) {
+ jlong default_ret_val = 0;
+ if (env->ExceptionOccurred()) { return default_ret_val; }
+
+ jclass exceptionClass = env->FindClass("java/lang/Throwable");
+ if (exceptionClass != NULL) { env->ThrowNew(exceptionClass, e.what()); }
+ return default_ret_val;
+ }
+}
+
+JNIEXPORT void JNICALL Java_ai_rapids_kvikio_cufile_CuFileDriver_destroy(JNIEnv* env,
+ jclass,
+ jlong pointer)
+{
+ delete reinterpret_cast(pointer);
+}
+
+JNIEXPORT void JNICALL Java_ai_rapids_kvikio_cufile_CuFileHandle_destroy(JNIEnv* env,
+ jclass,
+ jlong pointer)
+{
+ delete reinterpret_cast(pointer);
+}
+
+JNIEXPORT jlong JNICALL Java_ai_rapids_kvikio_cufile_CuFileReadHandle_create(JNIEnv* env,
+ jclass,
+ jstring path)
+{
+ auto file = cufile_file::make_reader(env->GetStringUTFChars(path, nullptr));
+ return reinterpret_cast(file.release());
+}
+
+JNIEXPORT void JNICALL Java_ai_rapids_kvikio_cufile_CuFileReadHandle_readFile(JNIEnv* env,
+ jclass,
+ jlong file_pointer,
+ jlong device_pointer,
+ jlong size,
+ jlong file_offset,
+ jlong device_offset)
+{
+ auto* file_ptr = reinterpret_cast(file_pointer);
+ auto* dev_ptr = reinterpret_cast(device_pointer);
+ file_ptr->read(dev_ptr, size, file_offset, device_offset);
+}
+
+JNIEXPORT jlong JNICALL Java_ai_rapids_kvikio_cufile_CuFileWriteHandle_create(JNIEnv* env,
+ jclass,
+ jstring path)
+{
+ auto file = cufile_file::make_writer(env->GetStringUTFChars(path, nullptr));
+ return reinterpret_cast(file.release());
+}
+
+JNIEXPORT void JNICALL
+Java_ai_rapids_kvikio_cufile_CuFileWriteHandle_writeFile(JNIEnv* env,
+ jclass,
+ jlong file_pointer,
+ jlong device_pointer,
+ jlong size,
+ jlong file_offset,
+ jlong buffer_offset)
+{
+ auto* file_ptr = reinterpret_cast(file_pointer);
+ auto* dev_ptr = reinterpret_cast(device_pointer);
+ file_ptr->write(dev_ptr, size, file_offset, buffer_offset);
+}
+}
diff --git a/java/src/test/java/ai/rapids/kvikio/cufile/BasicReadWriteTest.java b/java/src/test/java/ai/rapids/kvikio/cufile/BasicReadWriteTest.java
new file mode 100644
index 0000000000..1ed6aa570c
--- /dev/null
+++ b/java/src/test/java/ai/rapids/kvikio/cufile/BasicReadWriteTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2024, NVIDIA CORPORATION.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ai.rapids.kvikio.cufile;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import jcuda.Pointer;
+import jcuda.Sizeof;
+import jcuda.runtime.JCuda;
+
+import static jcuda.runtime.cudaMemcpyKind.cudaMemcpyDeviceToHost;
+import static jcuda.runtime.cudaMemcpyKind.cudaMemcpyHostToDevice;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class BasicReadWriteTest {
+
+ @Test
+ public void testReadBackWrite() {
+ // Allocate CUDA device memory
+ int numInts = 4;
+ Pointer pointer = new Pointer();
+ JCuda.cudaMalloc(pointer, numInts * Sizeof.INT);
+
+ // Build host arrays
+ int[] hostData = new int[numInts];
+ int[] hostDataFilled = new int[numInts];
+ for (int i = 0; i < numInts; ++i) {
+ hostDataFilled[i] = i;
+ }
+
+ // Obtain pointer value for allocated CUDA device memory
+ long pointerAddress = getPointerAddress(pointer);
+
+ // Copy filled data array to GPU and write to file
+ JCuda.cudaMemcpy(pointer, Pointer.to(hostDataFilled), numInts * Sizeof.INT, cudaMemcpyHostToDevice);
+ CuFileWriteHandle fw = new CuFileWriteHandle("/mnt/nvme/java_test");
+ fw.write(pointerAddress, numInts * Sizeof.INT, 0, 0);
+ fw.close();
+
+ // Clear data stored in GPU
+ JCuda.cudaMemcpy(pointer, Pointer.to(hostData), numInts * Sizeof.INT, cudaMemcpyHostToDevice);
+
+ // Read data back into GPU
+ CuFileReadHandle f = new CuFileReadHandle("/mnt/nvme/java_test");
+ f.read(pointerAddress, numInts * Sizeof.INT, 0, 0);
+ f.close();
+
+ // Copy data back to host and confirm what was written was read back
+ JCuda.cudaMemcpy(Pointer.to(hostData), pointer, numInts * Sizeof.INT, cudaMemcpyDeviceToHost);
+ assertArrayEquals(hostData, hostDataFilled);
+ JCuda.cudaFree(pointer);
+ }
+
+ private static long getPointerAddress(Pointer p) {
+ // WORKAROUND until a method like CUdeviceptr#getAddress exists
+ class PointerWithAddress extends Pointer {
+ PointerWithAddress(Pointer other) {
+ super(other);
+ }
+
+ long getAddress() {
+ return getNativePointer() + getByteOffset();
+ }
+ }
+ return new PointerWithAddress(p).getAddress();
+ }
+}