Skip to content

Make permute deterministic given a seed#3075

Open
says1117 wants to merge 1 commit into
NVIDIA:mainfrom
says1117:bug-make-regression-deterministic-shuffle
Open

Make permute deterministic given a seed#3075
says1117 wants to merge 1 commit into
NVIDIA:mainfrom
says1117:bug-make-regression-deterministic-shuffle

Conversation

@says1117

@says1117 says1117 commented Jul 6, 2026

Copy link
Copy Markdown

Summary

raft::random::permute drew the coefficients of its affine permutation
from the unseeded global C rand(), so results were not reproducible
across calls. This surfaced in cuML as rapidsai/cuml#7871:
make_regression(..., shuffle=True) returned different data on repeated
calls with the same random_state.

Changes

  • Add a seed parameter to permute that derives the permutation
    coefficients from a local std::mt19937_64, replacing the global rand().
  • Thread the existing make_regression seed through both the sample and
    feature shuffles (a distinct derived seed each, so the two shuffles stay
    independent).
  • The public permute overloads default seed = 0, so existing callers
    are unaffected.
  • Add PermSeedTest verifying the same seed yields identical permutations
    and different seeds yield different ones.

Testing

All 667 tests in RANDOM_TEST pass locally (RTX 2060, CUDA arch 75).

Relates to rapidsai/cuml#7871

permute() drew its affine permutation coefficients from the unseeded global rand(), so make_regression produced different output on repeated calls with the same random_state (cuML issue #7871). Derive the coefficients from a local std::mt19937_64 seeded per call, thread the seed through make_regression's row and feature shuffles, and add a determinism test. Public wrappers default the seed so existing callers are unaffected.
@copy-pr-bot

copy-pr-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@says1117

says1117 commented Jul 6, 2026

Copy link
Copy Markdown
Author

This is a non-breaking bug fix (the new permute seed parameter is defaulted, so the public API stays backward-compatible). Could a maintainer add the bug and non-breaking labels? Thanks!

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added optional seeding for permutation operations, enabling deterministic and repeatable results when the same seed is used.
    • Sample and feature shuffles now use separate seeds, keeping their permutations independent while remaining reproducible.
  • Bug Fixes

    • Improved consistency of permutation generation across runs by replacing non-deterministic random selection with seeded behavior.

Walkthrough

The permute functions in RAFT's random module now accept an explicit seed parameter, replacing rand()-based coefficient generation with deterministic std::mt19937_64. The public mdspan and legacy overloads forward this seed with a default value. make_regression passes distinct seeds for sample and feature shuffles. A new test validates determinism.

Changes

Deterministic seeded permutation

Layer / File(s) Summary
Deterministic RNG in detail::permute
cpp/include/raft/random/detail/permute.cuh
Adds <random> include and a seed parameter; replaces rand() with std::mt19937_64 to deterministically generate the coprime multiplier a and offset b.
Public permute overloads forward seed
cpp/include/raft/random/permute.cuh
Both the mdspan-based and legacy raw-pointer permute overloads add an optional uint64_t seed = 0ULL parameter and forward it to detail::permute.
make_regression passes explicit seeds
cpp/include/raft/random/detail/make_regression.cuh
The shuffle step now passes seed for sample permutation and seed + 1 for feature permutation to keep them independent.
Determinism test for seeded permute
cpp/tests/random/permute.cu
Adds PermSeedTest suite asserting the same seed produces identical permutations and, for N >= 1024, different seeds produce different permutations; updates copyright year.

Estimated code review effort: 2 (Simple) | ~12 minutes

Related PRs: None identified from the provided context.

Suggested labels: bug, non-breaking

Suggested reviewers: cjnolet, tfeher

Poem:
A rabbit spins the wheel of chance,
No more rand()'s wild, random dance —
Seeds now steer each shuffle's fate,
Sample and feature, independent, straight.
Deterministic hops, precise and true,
Carrots counted, seed times two! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: making permute deterministic with a seed.
Description check ✅ Passed The description matches the implemented seed threading, deterministic RNG, defaults, and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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/include/raft/random/permute.cuh (1)

139-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Convenience permute overload doesn't forward seed.

This overload (used when passing std::nullopt for permsOut/out) never accepts or forwards a seed, so any caller through this entry point is silently locked to seed = 0 and cannot opt into deterministic seeding, unlike the other two updated overloads.

🐛 Proposed fix
 template <typename InputOutputValueType,
           typename IdxType,
           typename Layout,
           typename PermsOutType,
           typename OutType>
 void permute(raft::resources const& handle,
              raft::device_matrix_view<const InputOutputValueType, IdxType, Layout> in,
              PermsOutType&& permsOut,
-             OutType&& out)
+             OutType&& out,
+             uint64_t seed = 0ULL)
 {
   ...
   std::optional<perms_out_view_type> permsOut_arg = std::forward<PermsOutType>(permsOut);
   std::optional<out_view_type> out_arg            = std::forward<OutType>(out);
-  permute(handle, in, permsOut_arg, out_arg);
+  permute(handle, in, permsOut_arg, out_arg, seed);
 }
🤖 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/include/raft/random/permute.cuh` around lines 139 - 165, The convenience
permute overload in permute.cuh is missing seed support, so callers using the
std::nullopt path are forced to the default seed. Update this template overload
of permute to accept a seed parameter and forward it into the inner permute
call, matching the behavior of the other permute overloads; use the permute
function signature and the permsOut/out forwarding logic to locate the change.
🧹 Nitpick comments (2)
cpp/include/raft/random/permute.cuh (1)

57-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Doxygen not updated for new seed parameter.

Both public overloads gained a seed param (lines 94 and 196) but their doc comments (lines 57-88 and 169-187) were not updated with an @param[in] seed entry describing default/determinism semantics.

As per path instructions, "For public headers under cpp/include/raft ... also require Doxygen on new APIs and deprecation warnings on breaking changes."

Also applies to: 169-199

🤖 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/include/raft/random/permute.cuh` around lines 57 - 94, Update the Doxygen
for the public permute overloads so the new seed argument is documented in both
declarations of permute. Add an `@param`[in] seed entry alongside the existing
handle/in/permsOut/out docs, and describe its default behavior and how it
affects determinism so the public API comment stays in sync with the signature.

Source: Path instructions

cpp/tests/random/permute.cu (1)

345-411: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test_data_type alias instead of hardcoding float in the TEST_P body.

PermSeedTest<T>::SetUp() correctly uses T for the permute<T, int, int>(...) calls (lines 371-374), but the TEST_P(PermSeedTestF, SameSeedIsDeterministic) body hardcodes permute<float, int, int>(...) (line 401) instead of following the using test_data_type = T; pattern already used by sibling fixtures PermTest/PermMdspanTest (lines 37, 85). This works today only because PermSeedTest is solely instantiated for float; extending coverage to double (matching the existing PermTestD/PermMdspanTestD symmetry) would require copy-pasting this class and updating the hardcoded type, which is easy to overlook until it fails to compile.

♻️ Proposed refactor
 template <typename T>
 class PermSeedTest : public ::testing::TestWithParam<PermInputs<T>> {
+ public:
+  using test_data_type = T;
+
  protected:
   ...
 };

 using PermSeedTestF = PermSeedTest<float>;
 TEST_P(PermSeedTestF, SameSeedIsDeterministic)
 {
+  using test_data_type = PermSeedTestF::test_data_type;
   auto stream = resource::get_cuda_stream(handle);
   ...
   if (N >= 1024) {
     rmm::device_uvector<int> perms3(N, stream);
-    permute<float, int, int>(
+    permute<test_data_type, int, int>(
       perms3.data(), out.data(), in.data(), params.D, N, params.rowMajor, stream, params.seed + 1);
🤖 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/tests/random/permute.cu` around lines 345 - 411, Replace the hardcoded
float in the PermSeedTest fixture with a test_data_type alias, matching the
pattern used by PermTest and PermMdspanTest, and use that alias in the TEST_P
body. Update PermSeedTest<T> to define test_data_type = T, then change the
permute<float, int, int>(...) call in SameSeedIsDeterministic to
permute<test_data_type, int, int>(...) so the fixture stays generic and can be
reused for double without copy-pasting.
🤖 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/include/raft/random/permute.cuh`:
- Around line 139-165: The convenience permute overload in permute.cuh is
missing seed support, so callers using the std::nullopt path are forced to the
default seed. Update this template overload of permute to accept a seed
parameter and forward it into the inner permute call, matching the behavior of
the other permute overloads; use the permute function signature and the
permsOut/out forwarding logic to locate the change.

---

Nitpick comments:
In `@cpp/include/raft/random/permute.cuh`:
- Around line 57-94: Update the Doxygen for the public permute overloads so the
new seed argument is documented in both declarations of permute. Add an
`@param`[in] seed entry alongside the existing handle/in/permsOut/out docs, and
describe its default behavior and how it affects determinism so the public API
comment stays in sync with the signature.

In `@cpp/tests/random/permute.cu`:
- Around line 345-411: Replace the hardcoded float in the PermSeedTest fixture
with a test_data_type alias, matching the pattern used by PermTest and
PermMdspanTest, and use that alias in the TEST_P body. Update PermSeedTest<T> to
define test_data_type = T, then change the permute<float, int, int>(...) call in
SameSeedIsDeterministic to permute<test_data_type, int, int>(...) so the fixture
stays generic and can be reused for double without copy-pasting.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 870a805f-b734-4504-92c6-79a4fe214946

📥 Commits

Reviewing files that changed from the base of the PR and between e90dcc3 and e2b3ce7.

📒 Files selected for processing (4)
  • cpp/include/raft/random/detail/make_regression.cuh
  • cpp/include/raft/random/detail/permute.cuh
  • cpp/include/raft/random/permute.cuh
  • cpp/tests/random/permute.cu

@viclafargue

Copy link
Copy Markdown
Contributor

cc @vinaydes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants