Skip to content

Introduce fused GEMM + 1-NN primitive using cuTile - #2249

Open
divyegala wants to merge 75 commits into
NVIDIA:mainfrom
divyegala:cutile-python-to-cpp
Open

Introduce fused GEMM + 1-NN primitive using cuTile#2249
divyegala wants to merge 75 commits into
NVIDIA:mainfrom
divyegala:cutile-python-to-cpp

Conversation

@divyegala

@divyegala divyegala commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

This PR adds infrastructure built on top of existing JIT LTO architecture to generate kernels using cutile-python at build time, and embed them in the C++ library to make them callable from C++.

@copy-pr-bot

copy-pr-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

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.

@divyegala divyegala changed the title cuTile Python to CPP embedding example Introduce fused GEMM + 1-NN primitive using cuTile Jun 24, 2026
Comment thread cpp/tests/CMakeLists.txt Outdated
Comment thread cpp/src/cluster/kmeans.cuh

@huuanhhuyn huuanhhuyn left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Many thanks @divyegala for the comprehensive PR!

I double-verified the functional correctness of the kernel from this PR. The kernel also matches one-to-one the performance with the one from gitlab.

Comment thread conda/environments/all_cuda-133_arch-aarch64.yaml
Comment thread cpp/src/distance/detail/fused_distance_nn/cutile/fused_1nn_cutile_matrix.json Outdated
Comment thread cpp/src/distance/detail/fused_distance_nn/cutile/fused_1nn_cutile_matrix.json Outdated
Comment thread cpp/src/distance/detail/fused_distance_nn/cutile/export_fused_1nn.py Outdated

@dantegd dantegd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we make the path selection and workspace allocation agree on whether the cuTile launch is guaranteed to succeed? When minClusterDistanceCompute.cu selects FusedCutile, it doesn’t allocate the CUTLASS KVP scratch or mutex workspace. But if try_fused_1nn_tile() returns false, cpp/src/distance/detail/fused_distance_nn.cuh falls through to CUTLASS and expects those buffers to exist. That leaves us without a valid fallback if launcher construction fails, even though the try_ API suggests falling back is supported. Would it make sense to build or probe the launcher before committing to FusedCutile, or keep the fallback workspace allocated for as long as this path can still return false?

Comment thread cpp/src/distance/detail/fused_distance_nn/cutile/fused_1nn_kernel.py Outdated
cutlass_kvp_scratch,
stream);
break;
case cuvs::distance::DistanceType::InnerProduct: break;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we route InnerProduct to a real non-cuTile implementation when cuTile isn't built or available?

On CUDA 12, try_fused_1nn_tile is compiled as false, so this function initializes the scratch output to (0, max), hits this empty case, and then unpacks that untouched sentinel. The new input_fp32_fused test includes InnerProduct unconditionally, so the supported CUDA 12 configuration returns incorrect results. What do you think about selecting the unfused path upstream for InnerProduct when cuTile is unavailable, or implementing the fallback here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The unfused path does not support InnerProduct for some reason. Let me see if we can support or just throw for now.

Comment thread c/src/cluster/kmeans.cpp Outdated
static_assert(std::is_same_v<IdxT, int> || std::is_same_v<IdxT, int64_t>);

constexpr int strict_pitch_elements = 16 / sizeof(DataT);
const bool use_strict_abi = k % strict_pitch_elements == 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Both the strict and relaxed signatures declare base_addr_divisible_by=16 for matrices, norms, indices, and distances, but this selection only checks row pitch. Valid RAFT subviews or DLPack byte offsets can therefore launch under a false compiler assumption; the int64 loop below also makes xn + max_i32 and nearest_dist + max_i32 non-16-byte-aligned on its second batch. cuTile documents violating an exported signature assumption as undefined behavior: “Launching the exported kernel with an array that doesn’t satisfy this assumption would then result in undefined behavior.”

Should we validate base alignment for every non-null array before launching, or export a base-alignment-1 fallback? What do you think about making alignment part of eligibility before choosing either ABI?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh I did not think at all about subviews or offsets, good catch. For now, let's just validate base pointer alignment. I can add to the follow-up issue to validate 1-byte alignment and what kind of cubin is produced by cutile?

Comment thread cpp/src/cluster/detail/kmeans_balanced.cuh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature request New feature or request non-breaking Introduces a non-breaking change

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

6 participants