Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Initial Java Support for GDS to KvikIO #396

Open
wants to merge 32 commits into
base: branch-24.12
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
83044ff
Initial commit
aslobodaNV Jun 25, 2024
62649e6
Update documentation to better flash out how to compile and run the e…
aslobodaNV Jun 25, 2024
161b260
code touchups and Readme update
aslobodaNV Jun 25, 2024
30aa3dc
Update README with markdown formatting. Improve instructions and linkage
aslobodaNV Jun 26, 2024
299bdb6
Add initial maven setup, needs to be debugged
aslobodaNV Jul 12, 2024
1162efc
Update maven build to properly generate shared library
aslobodaNV Jul 12, 2024
3f29546
Merge branch 'branch-24.10' into add_initial_java_support
jakirkham Jul 24, 2024
b3dd38f
Merge branch 'rapidsai:branch-24.10' into add_initial_java_support
aslobodaNV Sep 4, 2024
fe91427
Fix pre-commit issues
aslobodaNV Sep 4, 2024
4ad08c6
move example to be a test, update pom and dependencies to support CI …
aslobodaNV Sep 4, 2024
83875b6
pre-commit fixes
aslobodaNV Sep 4, 2024
4120e4c
add github workflow items
aslobodaNV Sep 4, 2024
888d6a7
Updating workflows
aslobodaNV Sep 18, 2024
d337a16
Merge branch 'branch-24.10' into add_initial_java_support
aslobodaNV Sep 18, 2024
833ad32
Formatting inconsistencies
aslobodaNV Sep 18, 2024
93598a8
Merge remote-tracking branch 'upstream/branch-24.12' into add_initial…
aslobodaNV Oct 1, 2024
661fcce
Update yaml files based on CR feedback, update versions to 24.12 from…
aslobodaNV Oct 1, 2024
7610edb
Fix needs and permissions.
bdice Oct 1, 2024
3eb3daa
Remove test_python_legate.
bdice Oct 1, 2024
13b9a05
Merge branch 'branch-24.12' into add_initial_java_support
bdice Oct 1, 2024
e3f8448
Merge branch 'add_initial_java_support' of github.com:aslobodaNV/kvik…
bdice Oct 1, 2024
fa749a3
Update java/pom.xml
aslobodaNV Oct 2, 2024
222426c
Update package for java bindings.
aslobodaNV Oct 11, 2024
c704108
Use cmake instead of explicit nvcc command
aslobodaNV Oct 11, 2024
69b6d39
Merge remote-tracking branch 'upstream/branch-24.12' into add_initial…
aslobodaNV Oct 11, 2024
40a588f
Merge branch 'branch-24.12' into add_initial_java_support
aslobodaNV Oct 18, 2024
fabd3c1
Fix style issues and missing license
aslobodaNV Oct 18, 2024
c29ac7b
Fix CMakeLists style issues
aslobodaNV Oct 18, 2024
157867f
Update dependencies to try and fix container build
aslobodaNV Oct 18, 2024
acc6777
Just add make not ninja
aslobodaNV Oct 18, 2024
cfa183a
Fix some of the build and container issues, linking issues remain
aslobodaNV Oct 22, 2024
2d41f10
Merge remote-tracking branch 'upstream/branch-24.12' into add_initial…
aslobodaNV Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Java 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 to be installed and for GDS to be properly enabled. Instructions for how to install and enable GDS can be found on NVIDIA's website. 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:
https://repo1.maven.org/maven2/org/jcuda/jcuda/12.0.0/jcuda-12.0.0.jar
https://repo1.maven.org/maven2/org/jcuda/jcuda-natives/12.0.0/jcuda-natives-12.0.0.jar
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Prefer not to link to explicit files since this can easily go out of date without us remembering to update things. For preference, can we link to an appropriate installation document for JCuda?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion, I can link to the installation instructions as well but found that the installation instructions themselves are a bit unclear/potentially out of date. I may keep these links but just add a note to confirm they are still up to date and reference the installation instructions.


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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use markdown formatting here, so that this will look a bit better when rendered.

Suggested change
Java 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 to be installed and for GDS to be properly enabled. Instructions for how to install and enable GDS can be found on NVIDIA's website. 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:
https://repo1.maven.org/maven2/org/jcuda/jcuda/12.0.0/jcuda-12.0.0.jar
https://repo1.maven.org/maven2/org/jcuda/jcuda-natives/12.0.0/jcuda-natives-12.0.0.jar
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.
# Java 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 to be installed and for GDS to be properly enabled. Instructions for how to install and enable GDS can be found on NVIDIA's website. 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:
https://repo1.maven.org/maven2/org/jcuda/jcuda/12.0.0/jcuda-12.0.0.jar
https://repo1.maven.org/maven2/org/jcuda/jcuda-natives/12.0.0/jcuda-natives-12.0.0.jar
## 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.

(See https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax for further syntax).

/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. If it is not already placed on your path in can be included when compiling and running your Java code 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 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

29 changes: 29 additions & 0 deletions java/src/main/java/bindings/kvikio/cufile/CuFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package bindings.kvikio.cufile;
aslobodaNV marked this conversation as resolved.
Show resolved Hide resolved

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) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: catching Throwable is not ideal. It can include all kinds of unrecoverable errors. At a minimum you probably want to print out why this could not be initialized in the error message. That way you can debug that a dependency was missing/etc.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to point me to an example of best practices in this area? Happy to change it further from my latest update.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I marked it as a nit because it is probably fine in this case. In fact when I went back to look at the CUDF code, I did the exact same thing 5 years ago.

https://github.com/rapidsai/cudf/blob/4dbb8a354a9d4f0b4d82a5bf9747409c6304358f/java/src/main/java/ai/rapids/cudf/NativeDepsLoader.java#L74-L83

It is just generally not good practice to catch a Throwable, because Throwable includes all Errors and Errors are generally considered to not be recoverable. But really the main thing here is giving the user enough information that they can debug why it is failing. You are printing the error message, but the full stack trace would be better. That is the main thing that I would suggest you do.

System.out.println("could not load cufile jni library");
}
}
}

public static boolean libraryLoaded() {
return initialized;
}
}
19 changes: 19 additions & 0 deletions java/src/main/java/bindings/kvikio/cufile/CuFileDriver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package bindings.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);
}
23 changes: 23 additions & 0 deletions java/src/main/java/bindings/kvikio/cufile/CuFileHandle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package bindings.kvikio.cufile;

abstract class CuFileHandle implements AutoCloseable {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be nice to have some javadocs here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will be sure to get these javadocs that you have requested in before we merge, just wanted to tackle the build issues we were seeing first. Thank you for all your feedback.

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);
}
17 changes: 17 additions & 0 deletions java/src/main/java/bindings/kvikio/cufile/CuFileReadHandle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package bindings.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);

}
16 changes: 16 additions & 0 deletions java/src/main/java/bindings/kvikio/cufile/CuFileWriteHandle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package bindings.kvikio.cufile;

public final class CuFileWriteHandle extends CuFileHandle {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here javadocs would be very nice for the developer experience.


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);
}
73 changes: 73 additions & 0 deletions java/src/main/java/bindings/kvikio/example/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package bindings.kvikio.example;

import bindings.kvikio.cufile.CuFileReadHandle;
import bindings.kvikio.cufile.CuFileWriteHandle;

import static jcuda.runtime.cudaMemcpyKind.cudaMemcpyDeviceToHost;
import static jcuda.runtime.cudaMemcpyKind.cudaMemcpyHostToDevice;

import java.util.Arrays;

import jcuda.NativePointerObject;
import jcuda.Pointer;
import jcuda.Sizeof;
import jcuda.runtime.JCuda;

class main {
public static void main(String []args)
{
// Allocate CUDA device memory
int numInts = 4;
Pointer pointer = new Pointer();
JCuda.cudaMalloc(pointer, numInts*Sizeof.INT);

// Build host arrays, print them out
int hostData[] = new int[numInts];
int hostDataFilled[] = new int[numInts];
for (int i = 0; i < numInts; ++i) {
hostDataFilled[i]=i;
}
System.out.println(Arrays.toString(hostData));
System.out.println(Arrays.toString(hostDataFilled));

// 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
JCuda.cudaMemcpy(Pointer.to(hostData), pointer, numInts*Sizeof.INT, cudaMemcpyDeviceToHost);
System.out.println(Arrays.toString(hostDataFilled));
System.out.println(Arrays.toString(hostData));
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();
}
};
Loading
Loading