Chunk large native libraries for parallel extraction - #15356
Chunk large native libraries for parallel extraction#15356gerashegalov wants to merge 4 commits into
Conversation
Signed-off-by: Gera Shegalov <gshegalov@nvidia.com>
Greptile SummaryThis PR splits large native libraries (≥ 256 MiB) into 32 MiB chunks during
Confidence Score: 5/5Safe to merge. The chunking and verification logic is correct, error paths are clean, and the atomic rename/remove commit sequence is well-guarded by both the exception-handling structure and the initialize-phase parallel-world cleanup on the next invocation. The chunking arithmetic is provably correct (chunk sizes sum exactly to library_size), CRC32 verification closes the integrity loop, the exception paths consistently use error-ignoring cleanup helpers so the original exception is never masked, and the enabled-marker guard ensures the verifier is a no-op when no chunking occurred. No logic defects were found in any of the four changed files. Files Needing Attention: No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant Maven
participant Init as initialize phase
participant GenRes as generate-resources
participant ProcRes as process-resources
participant Jar as package phase
participant Verify as verify phase
Maven->>Init: clean-any-prior-output
Init->>Init: delete parallel-world, jars, deps, native-chunks
Maven->>GenRes: create-parallel-world
GenRes->>GenRes: populate parallel-world from aggregator JARs
Maven->>ProcRes: copy-jni-and-ucx-classes
ProcRes->>ProcRes: copy jni-deps into parallel-world
Maven->>ProcRes: chunk-native-libraries (Jython, guarded by enabled+compress)
ProcRes->>ProcRes: walk parallel-world arch/os files
ProcRes->>ProcRes: "split each library >=256MiB into 32MiB chunks"
ProcRes->>ProcRes: write manifest with library size and per-chunk CRC32
ProcRes->>ProcRes: atomic rename tmp dirs, remove original library
ProcRes->>ProcRes: write deflated-chunks.list and chunk-manifests.list
Maven->>Jar: create-parallel-worlds-jar
Jar->>Jar: DEFLATE all parallel-world entries including chunks
Maven->>Verify: verify-native-library-chunks (Jython, guarded by enabled marker)
Verify->>Verify: open dist JAR and check all chunks are DEFLATED
Verify->>Verify: parse each manifest and verify chunk count
Verify->>Verify: verify no conventional resource beside manifest
Verify->>Verify: read each chunk and verify size and CRC32
Verify->>Verify: compare verified-chunk set against expected list
Reviews (4): Last reviewed commit: "Support byte-size suffixes for native ch..." | Re-trigger Greptile |
Signed-off-by: Gera Shegalov <gshegalov@nvidia.com>
Signed-off-by: Gera Shegalov <gshegalov@nvidia.com>
|
NOTE: release/26.08 has been created from main. Please retarget your PR to release/26.08 if it should be included in the release. |
Signed-off-by: Gera Shegalov <gshegalov@nvidia.com>
Add an internal chunk-manifest resource format for large native libraries and extract those chunks concurrently. The loader: - preserves conventional single-resource loading and `ai.rapids.cudf.lib-native-dir` - reads up to 12 JAR entries concurrently - writes directly to the pre-sized output with positional `FileChannel` writes - validates manifest structure, exact chunk sizes, and per-chunk CRC32 values - removes partial output after extraction failures Related issue: [NVIDIA/cudf-spark#15145](NVIDIA/cudf-spark#15145) identifies synchronous, single-threaded native JAR extraction as a major executor startup cost. This allows distribution JARs containing 900+ MiB CUDA native libraries to trade a measured 0.0087% increase in archive size for substantially faster startup and JAR creation. NVIDIA/cudf-spark#15356 is the companion producer PR. ### Performance #### Native extraction Measured the exact pre-change and PR paths on the same host with JDK 17 and the same 1,504,051,352-byte CUDA12 `libcudf.so`. Each case ran six extractions in one JVM; the first invocation was treated as warm-up and the remaining five were used for the median. | Path | First invocation | Post-warm-up range | Median | | --- | ---: | ---: | ---: | | Pre-change conventional DEFLATED resource, 16 KiB sequential copy | 6.411 s | 6.370-6.426 s | 6.371 s | | 45 DEFLATED chunks, 12-worker positional extraction | 1.164 s | 1.078-1.124 s | 1.113 s | The post-warm-up median improves by **5.72x**, reducing extraction latency by **82.5%**. The first measured invocation improves by 5.51x. Both paths produced exactly 1,504,051,352 bytes. #### Archive size and creation time Using matched resource trees and forcing the existing `jar:jar@create-parallel-worlds-jar` execution: | Input representation | Median | Output JAR size | | --- | ---: | ---: | | One conventional DEFLATED `libcudf.so` entry | 55.07 s | 952,504,790 bytes | | 45 DEFLATED chunks plus manifest | 5.47 s | 952,587,551 bytes | Chunking increased the archive by 82,761 bytes (**0.0087%**) while making focused JAR creation **10.07x faster**, a **90.1%** wall-time reduction. The full benchmark methodology and measured ranges are in NVIDIA/cudf-spark#15356. ### Validation - `mvn compiler:compile compiler:testCompile` - `mvn surefire:test -Dtest=NativeDepsLoaderExtractionTest` (10 tests) - `mvn surefire:test@native-deps-loader-test` (5 tests) - Cross-repository production JAR benchmark described above The full native Maven lifecycle was not run locally because the installed CMake is 3.28 and this branch requires CMake 4.0; the Java sources and focused tests were compiled and run directly. Authors: - Gera Shegalov (https://github.com/gerashegalov) Approvers: - Zach Puller (https://github.com/zpuller) - Nghia Truong (https://github.com/ttnghia) URL: #23409
Description
Depends on rapidsai/cudf#23409, which teaches
NativeDepsLoaderto consume the generated format.Related issue: #15145. This PR addresses its native JAR extraction bottleneck; background preloading and concurrent memory-pool initialization remain separate work.
Large CUDA native libraries are expensive to inflate through one sequential JAR stream. CUDA 13 also uses zstd-compressed fatbins, so additional whole-library DEFLATE compression is less valuable while startup latency remains significant.
This change makes chunking a cudf-spark distribution packaging concern:
process-resourcescreate-parallel-worlds-jarexecution picks up the generated resources and DEFLATEs them in its normal archive passverify-phase task checks entry methods, resource presence, sizes, and reconstructed checksumsThere is no new user-facing configuration. The POM properties are internal build controls so chunking remains limited to the cudf-spark distribution JAR.
Performance
Native extraction
Measured the exact pre-change and PR paths on the same host with JDK 17 and the same 1,504,051,352-byte CUDA12
libcudf.so. Each case ran six extractions in one JVM; the first invocation was treated as warm-up and the remaining five were used for the median.The post-warm-up median improves by 5.72x, reducing extraction latency by 82.5%. The first measured invocation improves by 5.51x. Both paths produced exactly 1,504,051,352 bytes.
JAR creation
Isolated the existing
create-parallel-worlds-jarMaven execution using two resource trees on the same ext4 filesystem. All non-native files were hard-linked between the trees; only the native representation differed. Every invocation forced a new archive:Each case had one warm-up followed by five measured runs. Times below are direct-goal wall time, including identical Maven/JVM startup overhead.
libcudf.soentryChunking makes focused JAR creation 10.07x faster, reducing wall time by 90.1%. The chunked archive is only 82,761 bytes larger, a 0.0087% increase. Plexus Archiver can compress independent chunk entries concurrently instead of processing one 1.5 GB entry on a single worker.
Validation
./build/make-scala-version-build-files.sh 2.13mvn package -pl dist -DskipTestsmvn antrun:run@verify-native-library-chunks -pl dist -DskipTests -Dspark.rapids.source.basedir=$PWDChecklists
Documentation
Testing
Performance