Skip to content

[FEA] RTCX: Replace Software SHA256 with SIMD-accelerated xxHash128 for JIT cache - #22920

Closed
lamarrr wants to merge 8 commits into
NVIDIA:mainfrom
lamarrr:fast-jit-kernel-hash
Closed

[FEA] RTCX: Replace Software SHA256 with SIMD-accelerated xxHash128 for JIT cache#22920
lamarrr wants to merge 8 commits into
NVIDIA:mainfrom
lamarrr:fast-jit-kernel-hash

Conversation

@lamarrr

@lamarrr lamarrr commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Description

This pull request replaces the software SHA256 implementation with a SIMD-accelerated xxHash implementation (XXH3-128). SHA256 was initially chosen as the obvious choice for a collision-free hash and it was implemented to keep dependencies minimal.

For a hot JIT cache the previous cryptographic sha256 hash took ~88.1% (~4727ns) of the get_kernel function execution time.
With the new non-cryptographic SIMD-accelerated hash the hash time is reduced by 16.33x to 288ns ~31.2% of the get_kernel function execution time.

Changeset Summary

  • Introduced a new hash128 structure to replace the previous sha256 implementation.
  • Added hash128_hex_string for hexadecimal representation of hash128.
  • Updated cache management functions to utilize hash128 instead of sha256.
  • Removed sha256.hpp and its associated logic, consolidating hashing functionality.
  • Adjusted file handling and caching mechanisms to accommodate the new hash type.
  • Ensured compatibility with existing code by updating relevant function signatures and implementations.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

- Introduced a new hash128 structure to replace the previous sha256 implementation.
- Added hash128_hex_string for hexadecimal representation of hash128.
- Updated cache management functions to utilize hash128 instead of sha256.
- Removed sha256.hpp and its associated logic, consolidating hashing functionality.
- Adjusted file handling and caching mechanisms to accommodate the new hash type.
- Ensured compatibility with existing code by updating relevant function signatures and implementations.
@lamarrr
lamarrr requested a review from a team as a code owner June 17, 2026 16:09
@lamarrr
lamarrr requested review from GregoryKimball and vuule June 17, 2026 16:09
@github-actions github-actions Bot added the libcudf Affects libcudf (C++/CUDA) code. label Jun 17, 2026
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the custom in-header SHA-256 implementation (sha256.hpp) with XXH3 128-bit hashing across the librtcx cache subsystem and JIT caching. A new hash.hpp introduces rtcx::hash128 and rtcx::hash128_hex_string types. All cache key APIs, disk filename derivation, embed hash computation, and JIT cache keys are updated to use hash128.

Changes

SHA-256 → XXH3 128-bit hash migration

Layer / File(s) Summary
New hash128 and hash128_hex_string types
cpp/librtcx/hash.hpp
Introduces rtcx::hash128_hex_string (32-hex-digit storage with make() overloads converting from 16-byte spans or __uint128_t values) and rtcx::hash128 (128-bit wrapper with constructors from whole or (high, low) 64-bit parts, equality, byte access, to_hex_string(), and parse() that validates and parses hex strings).
embed.hpp: compute_embed_hash switched to XXH3
cpp/librtcx/embed.hpp
Replaces sha256.hpp include with hash.hpp and xxhash.h (with XXH_INLINE_ALL). Rewrites compute_embed_hash to use XXH3_createState/reset/update/digest/free, returning rtcx::hash128{high64, low64} instead of rtcx::sha256.
rtcx.hpp: hash128_hasher and LRU cache key type update
cpp/librtcx/rtcx.hpp
Replaces sha256_hasher with hash128_hasher for unordered_map key hashing operating on raw 128-bit values. Switches LRU cache entries_ map, eviction ranking vector, and insert() parameter from sha256 to hash128. Updates public get_or_add_blob/get_or_add_library signatures to accept hash128 const&.
rtcx.cpp: disk and memory cache operations updated to hash128
cpp/librtcx/rtcx.cpp
Adds xxhash include with XXH_INLINE_ALL. Updates get_disk_blob/get_disk_library helpers to derive filenames from hash128::to_hex_string(). Updates cache_blob_to_disk parameter and get_or_add_blob/get_or_add_library (hit and miss paths) to use hash128 keys for memory/disk lookups and storage. Updates preload-from-disk parsing to use hash128::parse and insertion to cache under hash128 keys.
cpp/src/jit/cache.cpp: JIT caching updated to hash128 and XXH3
cpp/src/jit/cache.cpp
Replaces local SHA-256 hashing helpers with XXH3 incremental hash functions. Updates jit_bundle_t::get_hash() to use rtcx::hash128_hex_string::make() instead of sha256_hex_string. Reworkes get_kernel() to hash a cuLibrary spec string (excluding pre-hashed header fields) plus raw header inputs via XXH3_128bits_digest, producing rtcx::hash128 cache keys.
README: xxHash added to build-time requirements
cpp/librtcx/README.md
Lists LibZSTD and xxHash as separate standalone build-time dependencies.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • rapidsai/cudf#21625: Establishes the librtcx embedding/caching pipeline with sha256-based keys in embed.hpp, rtcx.hpp, and rtcx.cpp — the exact same code paths this PR migrates to XXH3/hash128.

Suggested labels

improvement

Suggested reviewers

  • mhaseeb123
  • vyasr
  • bdice
  • wence-
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: replacing SHA256 with xxHash128 for JIT cache performance optimization.
Description check ✅ Passed The description clearly explains the motivation (performance), quantifies the improvement (16.33x speedup), and provides a comprehensive changeset summary aligned with the actual modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
cpp/librtcx/rtcx.hpp (1)

478-518: 💤 Low value

Rename parameter sha to hash for consistency.

The parameter at line 511 is still named sha even though the type has been changed to hash128. This naming inconsistency could cause confusion during future maintenance.

-  void insert(hash128 const& sha, T&& value, std::uint64_t tick)
+  void insert(hash128 const& hash, T&& value, std::uint64_t tick)
   {
     if (limit_ == 0) { return; }

     if ((entries_.size() + 1) > limit_) { purge(); }

-    entries_.emplace(sha, entry{tick, std::move(value)});
+    entries_.emplace(hash, entry{tick, std::move(value)});
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/librtcx/rtcx.hpp` around lines 478 - 518, The `insert` method in the
`lru_memory_cache` class has a parameter named `sha` with type `hash128`, which
is inconsistent. Rename the parameter from `sha` to `hash` in the method
signature, and update all references to this parameter within the method body,
including the call to `entries_.emplace()` where it is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/librtcx/embed.hpp`:
- Around line 110-117: The XXH3_createState() function call can return NULL on
memory allocation failure, but the code does not check for this condition before
using the state pointer in subsequent XXH3_128bits_update and
XXH3_128bits_digest calls. Add a NULL check immediately after the
XXH3_createState() assignment using the RTCX_EMBED_EXPECTS macro to validate
that the state pointer is not NULL before proceeding with the hash operations.
This will prevent potential null pointer dereferences if memory allocation
fails.

In `@cpp/librtcx/hash.hpp`:
- Around line 70-73: The operator[] method in the hash class has a critical
out-of-bounds memory access bug. When index=0, the calculation 16 - index
results in accessing byte[16], which is invalid for a 16-byte buffer (valid
indices are 0-15). Change the indexing calculation from 16 - index to 15 - index
to properly access the valid byte range while maintaining big-endian byte order
where index=0 returns the most significant byte (at position 15) and index=15
returns the least significant byte (at position 0).

---

Nitpick comments:
In `@cpp/librtcx/rtcx.hpp`:
- Around line 478-518: The `insert` method in the `lru_memory_cache` class has a
parameter named `sha` with type `hash128`, which is inconsistent. Rename the
parameter from `sha` to `hash` in the method signature, and update all
references to this parameter within the method body, including the call to
`entries_.emplace()` where it is used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 24df94fa-a489-4583-9332-4d68f5750363

📥 Commits

Reviewing files that changed from the base of the PR and between 96896b1 and 69690ee.

📒 Files selected for processing (7)
  • cpp/librtcx/README.md
  • cpp/librtcx/embed.hpp
  • cpp/librtcx/hash.hpp
  • cpp/librtcx/rtcx.cpp
  • cpp/librtcx/rtcx.hpp
  • cpp/librtcx/sha256.hpp
  • cpp/src/jit/cache.cpp
💤 Files with no reviewable changes (1)
  • cpp/librtcx/sha256.hpp

Comment thread cpp/librtcx/embed.hpp Outdated
Comment thread cpp/librtcx/hash.hpp
@lamarrr lamarrr added feature request New feature or request non-breaking Non-breaking change labels Jun 17, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cpp/src/jit/cache.cpp (1)

32-36: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

CRITICAL: Encode header boundaries in the cache key.

Issue: hash(state, header_include_names) and hash(state, headers) concatenate C strings without counts or per-entry lengths, so inputs like {"a","bc"} and {"ab","c"} hash identically.
Why: That cache_key drives memory/disk cuLibrary reuse, so a collision here can return a library compiled with different headers.

Suggested fix
 void hash(XXH3_state_t* ctx, std::span<char const> input)
 {
   XXH3_128bits_update(ctx, input.data(), input.size());
 }
 
+void hash(XXH3_state_t* ctx, std::uint64_t input)
+{
+  hash(ctx, std::span<char const>{reinterpret_cast<char const*>(&input), sizeof(input)});
+}
+
 void hash(XXH3_state_t* ctx, std::span<char const* const> inputs)
 {
+  hash(ctx, static_cast<std::uint64_t>(inputs.size()));
   for (auto const* input : inputs) {
-    XXH3_128bits_update(ctx, input, std::strlen(input));
+    auto const size = std::strlen(input);
+    hash(ctx, static_cast<std::uint64_t>(size));
+    hash(ctx, std::span<char const>{input, size});
   }
 }

Also applies to: 321-323

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/jit/cache.cpp` around lines 32 - 36, The hash function `hash` is
concatenating C string contents without encoding boundary information, causing
different input combinations like `{"a","bc"}` and `{"ab","c"}` to produce
identical hashes. This creates cache collisions that could lead to incorrect
library reuse. Fix this by encoding the length of each string before hashing its
content in the loop that iterates through inputs, ensuring that each string's
length is hashed as a distinct value using XXH3_128bits_update before hashing
the string content itself. Apply this same fix to the other hash calls at lines
321-323 that also pass header data without boundary encoding.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cpp/src/jit/cache.cpp`:
- Around line 32-36: The hash function `hash` is concatenating C string contents
without encoding boundary information, causing different input combinations like
`{"a","bc"}` and `{"ab","c"}` to produce identical hashes. This creates cache
collisions that could lead to incorrect library reuse. Fix this by encoding the
length of each string before hashing its content in the loop that iterates
through inputs, ensuring that each string's length is hashed as a distinct value
using XXH3_128bits_update before hashing the string content itself. Apply this
same fix to the other hash calls at lines 321-323 that also pass header data
without boundary encoding.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: aa6d7399-5c21-4102-bb50-c39c2110b21d

📥 Commits

Reviewing files that changed from the base of the PR and between c479bad and 454a036.

📒 Files selected for processing (2)
  • cpp/librtcx/embed.hpp
  • cpp/src/jit/cache.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/librtcx/embed.hpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
cpp/src/jit/cache.cpp (1)

300-303: ⚡ Quick win

Consider stack-allocating XXH3_state_t to avoid heap allocation in hot path.

XXH3_state_t can be declared directly on the stack, eliminating the malloc/free overhead from XXH3_createState()/XXH3_freeState().

♻️ Proposed change
-  XXH3_state_t* state = XXH3_createState();
-  CUDF_EXPECTS(state != nullptr, "Failed to create XXH3 state", std::runtime_error);
-  XXH3_128bits_reset(state);
-  RTCX_DEFER([state] { XXH3_freeState(state); });
+  XXH3_state_t state;
+  XXH3_128bits_reset(&state);

Then update usages to pass &state instead of state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cpp/src/jit/cache.cpp` around lines 300 - 303, The XXH3_state_t object is
being heap-allocated using XXH3_createState() in this hot path, which incurs
malloc/free overhead. Instead, declare XXH3_state_t directly as a
stack-allocated variable, remove the XXH3_createState() call and the null check,
update the XXH3_128bits_reset() call to pass the address of the stack-allocated
state using the address-of operator, and remove the RTCX_DEFER cleanup block
that calls XXH3_freeState() since the stack allocation will be automatically
cleaned up when it goes out of scope.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cpp/src/jit/cache.cpp`:
- Around line 300-303: The XXH3_state_t object is being heap-allocated using
XXH3_createState() in this hot path, which incurs malloc/free overhead. Instead,
declare XXH3_state_t directly as a stack-allocated variable, remove the
XXH3_createState() call and the null check, update the XXH3_128bits_reset() call
to pass the address of the stack-allocated state using the address-of operator,
and remove the RTCX_DEFER cleanup block that calls XXH3_freeState() since the
stack allocation will be automatically cleaned up when it goes out of scope.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d2b51fab-fcbd-4d24-bde9-1687b915be0b

📥 Commits

Reviewing files that changed from the base of the PR and between 454a036 and b8bf0f2.

📒 Files selected for processing (1)
  • cpp/src/jit/cache.cpp

@lamarrr
lamarrr requested a review from a team as a code owner June 18, 2026 01:42
@github-actions github-actions Bot added the CMake CMake build issue label Jun 18, 2026
@ttnghia

ttnghia commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

How worse it is if we use std::hash?

@lamarrr

lamarrr commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

closed and merged into #22680

@lamarrr lamarrr closed this Jun 22, 2026
@lamarrr

lamarrr commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

std::hash is not collision-resistant and has a high chance of collisions, 64-bits is too small for a content-addressable hash

rapids-bot Bot pushed a commit that referenced this pull request Jul 3, 2026
This PR adds LTO-based transforms to libcudf by introducing a new `cudf::transform_lto` API that accepts  LTO-IR or FATBIN UDF binaries and executes them through the existing transform pipeline. The API supports typed transform inputs/outputs, null-awareness, and optional user data.

The change enables libcudf transforms to execute precompiled device UDF fragments instead of relying only on source/PTX-style runtime compilation. This creates a path for lower-overhead, link-time-optimized transform kernels while preserving the existing transform abstraction around input columns, scalar inputs, output specifications, and null policy.

<img width="1693" height="929" alt="image" src="https://github.com/user-attachments/assets/36e4850b-3176-4297-942f-e532e93cb378" />


The AOT-compiled transform UDF has a similar ABI signature as NUMBA-CUDA UDFs (https://nvidia.github.io/numba-cuda/user/cuda_ffi.html):

```cpp
extern "C" __device__ int transform(Output * ... outputs, Inputs... inputs);
```

The integer return is used for signaling errors (non-zero values) and may be discarded or propagated by the implementation.


This pull request also replaces the software SHA256 implementation with a SIMD-accelerated xxHash implementation (XXH3-128, ported from #22920). SHA256 was initially chosen as the obvious choice for a collision-free hash, and it was implemented to keep dependencies minimal.

For a hot JIT cache, the previous cryptographic SHA256 hash took ~88.1% (~4727ns) of the `get_kernel` function execution time.
With the new non-cryptographic SIMD-accelerated hash, the hash time is reduced by 16.33x to 288ns, ~31.2% of the `get_kernel` function execution time.

This pull request also:

-  Hoists the `cudaGetDeviceProperties` values into the cudf context object; each call takes ~1.8ms per-call (see: #23074)
- Refactors librtcx's CMake functions to comply with RAPIDS' naming standards
- Makes the `nvrtc`-related flags be dispatched by the `nvrtcVersion` and not `cudaGetRuntimeVersion`. They can be different
- Removes `discard_errors` specialization of the transform kernel; this helps reduce the number of kernel instantiations that need to be pre-compiled, and also reduces kernel variance

Closes #19578 & #23074

#### Benchmarks
Throughput benchmarks are provided in #22680 (comment), and compilation-time benchmarks are provided in #22680 (comment)

Authors:
  - Basit Ayantunde (https://github.com/lamarrr)
  - Bradley Dice (https://github.com/bdice)
  - Kyle Edwards (https://github.com/KyleFromNVIDIA)

Approvers:
  - Bradley Dice (https://github.com/bdice)
  - Nghia Truong (https://github.com/ttnghia)

URL: #22680
@coderabbitai coderabbitai Bot mentioned this pull request Jul 7, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CMake CMake build issue feature request New feature or request libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants