Distributed RF: add gtests, handle empty partitions, fix bug in leaf output - #8394
Conversation
|
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. |
|
Counterexample: a single tree stump Single-GPU: 4 GPUs: the workers disagree on the content of leaf output |
|
Fix: Run an AllReduce on the statistics histogram before computing the leaf value. |
RAMitchell
left a comment
There was a problem hiding this comment.
New changes look correct!
RAMitchell
left a comment
There was a problem hiding this comment.
One more thing:
Distributed n_rows == 0 is now allowed, but in weighted bootstrap, compute_sample_weight_sum() reads sample_weight_cdf_.data() + n_rows_ - 1. This is out of bounds.
For non-bootstrap the reduction returns 0.0 and then fails local sample_weight_sum_ > 0.0.
So it looks like we need coverage for a worker with 0 rows for weighted and unweighted.
|
TODOs
|
csadorf
left a comment
There was a problem hiding this comment.
Looks mostly good to me, just some minor concerns.
|
I addressed all review comments. Can you take another look? @RAMitchell @csadorf |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cpp/src/randomforest/randomforest.cuh (1)
120-120: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winEarly return should check
n_rows_ == 0instead ofselected_rows.size() == 0.The validation at cpp/src/randomforest/randomforest.cu:548–550 confirms that
max_samplesis permitted in the range (0, 1]. For small partitions with smallmax_samplesvalues,round(max_samples * n_rows)produces zero. For example,round(0.1 * 5) = 0. In such cases,n_rows_is non-zero butn_sampled_rows_is zero, soselected_rows.size() == 0is true even though the partition is not empty.The early return at line 120 skips the subsequent call to
store_bootstrap_mask()at line 168. Ifbootstrap_masks_is non-null, the bootstrap mask for that tree is never initialized, leaving the caller's buffer uninitialized or stale.The
store_bootstrap_mask()method safely handles null pointers at line 180, so it is safe to call unconditionally. Change the condition toif (n_rows_ == 0) { return selected_rows; }to skip initialization only for empty partitions.🤖 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/randomforest/randomforest.cuh` at line 120, Update the early-return condition in the random-forest sampling flow to check n_rows_ == 0 instead of selected_rows.size() == 0. Preserve execution of store_bootstrap_mask() when n_rows_ is nonzero but sampling produces zero rows, while retaining the return for genuinely empty partitions.Source: Path instructions
🧹 Nitpick comments (2)
cpp/tests/mg/rf_test.cu (2)
291-301: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider zero-filling the placeholder weight element.
Line 296 allocates one
doublefor an empty rank to keep the pointer non-null. Line 299 copies onlyh_sample_weights.size()elements, so that element keeps uninitialized device memory. The builder should not read it when the local row count is zero, but a zero-filled buffer removes the dependency on that assumption and makes a future regression fail deterministically instead of nondeterministically.♻️ Proposed change
rmm::device_uvector<double> sample_weights(sample_weight_buffer_size, handle.get_stream()); + RAFT_CUDA_TRY(cudaMemsetAsync(sample_weights.data(), + 0, + sample_weights.size() * sizeof(double), + handle.get_stream())); raft::update_device(X.data(), h_X.data(), h_X.size(), handle.get_stream());🤖 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/mg/rf_test.cu` around lines 291 - 301, Zero-initialize the placeholder element in sample_weights when params.use_sample_weights is true and h_sample_weights is empty, while preserving the existing host-to-device copy for actual weights. Update the sample_weights allocation or initialization near sample_weight_buffer_size and keep sample_weight_ptr behavior unchanged.
640-657: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a weighted classification case.
use_sample_weightsistrueonly for the final MSE case. The stack also changes the weighted classification kernels, and the reported four-GPU leaf-value counterexample was a classification stump. One weightedGINIentry withPartitionKind::EmptyNonRootRankswould cover that path with the same fixture. Apply theglobal_row-keyed weight fix first, so a weightedStridedorImbalancedentry also stays valid.🤖 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/mg/rf_test.cu` around lines 640 - 657, Add a weighted classification test case alongside the existing parameterized cases, using GINI with use_sample_weights enabled and PartitionKind::EmptyNonRootRanks while preserving the fixture’s expected values. Ensure the global_row-keyed weight fix is applied so the new weighted Strided or Imbalanced variants remain valid.
🤖 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/tests/mg/rf_test.cu`:
- Line 197: Update the sample-weight assignment in make_local_dataset to derive
the parity from global_row rather than the local index i, so distributed and
reconstructed single-node datasets assign identical weights regardless of rank
partition sizes.
---
Outside diff comments:
In `@cpp/src/randomforest/randomforest.cuh`:
- Line 120: Update the early-return condition in the random-forest sampling flow
to check n_rows_ == 0 instead of selected_rows.size() == 0. Preserve execution
of store_bootstrap_mask() when n_rows_ is nonzero but sampling produces zero
rows, while retaining the return for genuinely empty partitions.
---
Nitpick comments:
In `@cpp/tests/mg/rf_test.cu`:
- Around line 291-301: Zero-initialize the placeholder element in sample_weights
when params.use_sample_weights is true and h_sample_weights is empty, while
preserving the existing host-to-device copy for actual weights. Update the
sample_weights allocation or initialization near sample_weight_buffer_size and
keep sample_weight_ptr behavior unchanged.
- Around line 640-657: Add a weighted classification test case alongside the
existing parameterized cases, using GINI with use_sample_weights enabled and
PartitionKind::EmptyNonRootRanks while preserving the fixture’s expected values.
Ensure the global_row-keyed weight fix is applied so the new weighted Strided or
Imbalanced variants remain valid.
🪄 Autofix
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: 10dde977-a3b6-48b2-9526-8cfff82c5896
📒 Files selected for processing (2)
cpp/src/randomforest/randomforest.cuhcpp/tests/mg/rf_test.cu
|
A few more from codex: Findings |
csadorf
left a comment
There was a problem hiding this comment.
Please address @RAMitchell 's and the CodeRabbit comments.
|
@chyunsu3 can you also update the PR title? I don't think it's fully matching the PR scope and intent anymore. |
|
I addressed all review comments and updated the title and the description. |
|
/merge |
|
/merge |
Description
Adds C++ multi-GPU coverage for the distributed random forest training path and fixes the local/global count bookkeeping needed for ranks with uneven or empty local partitions.
This is preparation for enabling distributed random forest end to end. The tests exercise distributed histogram/split selection with balanced, imbalanced, and empty-rank row partitions, while keeping local partition ranges rank-local.
Key changes:
MG_RF_TESTtarget.local_nLeftscoped to local partition range updates.Validation
Result:
8/8tests passed./home/rorym/cuml-builds/codex-enh-rf-mg-tests/cpp-mg-test-2610-mpi/tests/SG_RF_TEST \ --gtest_filter='RfTests.EmptyGlobalRowsRejected'Result: passed.
Result: passed.