Build the Java bindings dynamically against the pip-installed libcuopt in the official Docker images - #1959
Conversation
…images Neither ci/docker/Dockerfile nor Dockerfile.ubi installed any Java support at all. The existing dynamic-link Java build (ci/build_java.sh) can't help here either: it needs a conda environment with libcuopt's headers, and these images are pip-based (no headers, just the compiled libcuopt.so). The self-contained classifier JAR from java-static-build (#1817) is the right fit instead: it embeds libcuopt statically, so nothing has to link against this image's own pip-installed libcuopt at all. build.yaml already builds one per ARCH x CUDA-major combination on every branch/nightly run, in the same overall workflow run that (via build_test_publish_images.yaml) builds these images, so build_images.yaml downloads it as a same-run artifact rather than needing any new publish channel. Verified against a real build: built the actual Dockerfile with a real classifier JAR pulled from a recent successful main run, then compiled and ran a small Java program against the resulting image's cuopt.jar. Found and fixed a real gap doing that -- the JNI library failed with UnsatisfiedLinkError: libcublas.so.13, because this image's CUDA runtime comes from pip-installed nvidia-cublas-cu13 (under .../dist-packages/nvidia/cu13/lib), not /usr/local/cuda/targets/.../lib the way the RAPIDS CI image the JAR was built and tested against has it. libcuopt.so itself doesn't hit this (it carries its own $ORIGIN-relative RPATH into that same pip directory), but the JNI library has no such RPATH, so LD_LIBRARY_PATH now includes it explicitly. Confirmed the fix: the program got past native library loading and all the way to an actual cuOptSolve call (which then hit CUOPT_VALIDATION_ERROR only because this local test had no GPU passed through -- a test-environment limitation, not a packaging bug).
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: NVIDIA/cuopt/.coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (4)
💤 Files with no reviewable changes (1)
Included review availability: Your plan provides up to 12 included reviews per hour; 8 remain after this review. 📝 WalkthroughWalkthroughThe workflow stages Java and native sources for Docker builds. Standard and UBI images compile Java bindings against the installed ChangesDynamic Java image builds
Priority: ⬇️ Low Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Refactor Suggested reviewers: Merge Risk: ⚪ Minimal · up to The images now build Java bindings against the installed native package and include the required runtime artifacts for both supported image variants. No actionable production or deployment risk is evidenced. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
CI Test Summary⏭️ All 5 test job(s) skipped. |
… Docker images" This reverts commit b1d3ced.
The self-contained classifier JAR approach (reverted in the prior commit) statically links libcuopt and its dependencies (TBB, NCCL, cuDSS, rmm, rapids_logger) into the JNI library, duplicating ~600MB of libraries the image already carries for the Python side via pip. Instead, compile libcuopt_jni.so inside each image against the libcuopt.so already pip-installed there: CUOPT_RUNTIME_LIBRARY_DIR bakes libcuopt's location into cuopt_jni's own RPATH, so no LD_LIBRARY_PATH changes are needed -- libcuopt.so resolves the rest of its dependency chain itself via its existing $ORIGIN-relative RPATH. The result is a 55KB jar + ~250KB .so per image instead of a ~600MB embedded copy. Verified locally end-to-end on both Dockerfile (Ubuntu) and Dockerfile.ubi (RHEL/UBI10): built each image, compiled and ran a small Java program against cuopt.jar with -Dcuopt.native.dir, and confirmed native library loading succeeds fully (no UnsatisfiedLinkError) and the call chain reaches cuOptSolve, which reports CUOPT_VALIDATION_ERROR only because this host's driver doesn't support the images' CUDA 13.3 requirement -- the same signature seen when verifying the (now-abandoned) static approach. UBI10's AppStream repo has no java-17-openjdk package; use java-21-openjdk (an LTS release satisfying build_native.sh's JDK 17+ requirement) instead.
|
@coderabbitai review |
|
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Keep only the load-bearing why, not the full rationale already covered in the PR description.
|
@coderabbitai review |
✅ Action performedReview finished.
|
gforsyth
left a comment
There was a problem hiding this comment.
Looks reasonable.
My only non-blocking comment is that the two new build_*.sh scripts are effectively identical except for the CUOPT_SITE_PACKAGES line. It might be worth only maintaining one script with a switch instead of having to make changes to both.
| REPO_ROOT="${1:?missing repo root}" | ||
| OUT_DIR="${2:?missing output directory}" | ||
|
|
||
| CUOPT_SITE_PACKAGES="/usr/local/lib64/python3.14/site-packages/libcuopt" |
There was a problem hiding this comment.
How are we certain that this is always in python3.14 here but we require the Python short-version in the debian version?
There was a problem hiding this comment.
Dockerfile.ubi hardcodes python3.14 everywhere already (no ARG PYTHON_SHORT_VER — it doesn't matrix Python versions like Dockerfile does, just dnf install python3.14 directly). This script matches that existing convention rather than introducing a new one.
There was a problem hiding this comment.
Confirmed: UBI10's AppStream repo only offers python3.14 as an installable alternate version (plus the un-removable system python3.12 dnf itself needs) — no deadsnakes-style range like the Debian image has. build_images.yaml also never passes PYTHON_SHORT_VER to the UBI build (only CUDA_VER/CUOPT_VER), and the image tag has no -py suffix. So it's a support gap, not a preference for 3.14.
There was a problem hiding this comment.
Thanks, I don't need the massive LLM overexplanation
There was a problem hiding this comment.
Apologies, just wanted to make sure I figured out main root requirement for this. I will try to keep it short next time.
build_java_dynamic.sh and build_java_dynamic_ubi.sh differed only in how they located libcuopt's pip site-packages dir. Take that path as a direct argument instead, and have each Dockerfile pass its own layout, so there's one script to maintain.
|
Merged the two scripts into one (build_java_dynamic.sh now takes the libcuopt site-packages path as an argument instead of deriving it from a Python version), pushed in a92e14c. Verified both Dockerfile and Dockerfile.ubi still build and run correctly with the consolidated script. |
gforsyth
left a comment
There was a problem hiding this comment.
Looks good, thanks for consolidating!
|
/merge |
Summary
Builds
libcuopt_jni.soin-image against the pip-installedlibcuopt.so, instead of embedding a statically-linked classifier JAR (~600MB duplicated).CUOPT_RUNTIME_LIBRARY_DIRgivescuopt_jni.sothe same RPATH-based resolutionlibcuopt.soalready uses, so noLD_LIBRARY_PATHchanges are needed.java-buildstage inDockerfile/Dockerfile.ubi: JDK + build toolchain, reusescuda-headersfornvcc, runsci/docker/build_java_dynamic*.sh(builds the.so,javac-compiles the classes, no Maven).cuopt-finalinstalls a headless JRE and copies incuopt.jar+libcuopt_jni.so.java-17-openjdk; usesjava-21-openjdk.~300KB per image instead of ~600MB.
Verification
Built both Dockerfiles locally, ran a Java program against
cuopt.jarin each. Native loading succeeded fully (RPATH-only, confirmed vialdd); reachedcuOptSolve, which failed only withCUOPT_VALIDATION_ERROR(no GPU on this host).Test plan
build_images.yamlmatrix