Skip to content

Add C++ RF sample weight training support - #8258

Merged
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
RAMitchell:codex/enh-rf-sample-weight
Jun 26, 2026
Merged

Add C++ RF sample weight training support#8258
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
RAMitchell:codex/enh-rf-sample-weight

Conversation

@RAMitchell

Copy link
Copy Markdown
Contributor

Summary

Refs #8093.

This PR threads optional device sample_weight through the C++ Random Forest training path and into the batched-levelalgo histogram builders.

It adds weighted RF objective/kernel instantiations selected when sample_weight != nullptr. Histogram bins keep count as the minimum-samples metadata, while weighted bins additionally accumulate sample weight for impurity/objective math and leaf values.

Details

  • Adds sample_weight to the C++ RF fit path and decision tree dataset plumbing.
  • Adds weighted classification and regression histogram bins.
  • Keeps count += 1 for every sampled occurrence, including bootstrap duplicates.
  • Accumulates weight += sample_weight[row] for weighted bins.
  • Uses weighted label sums for regression objectives and weighted class mass for classification objectives.
  • Keeps min_samples_leaf and related split constraints count-based.
  • Rejects zero-weight split children through objective math.
  • Splits weighted RF kernel instantiations into dedicated weighted CUDA wrapper TUs.

Tests

  • Adds SG RF property-test coverage across weighted and unweighted training.
  • Uses weighted scoring for weighted deeper-tree monotonicity checks.
  • Covers weighted/unweighted objective behavior, zero-weight split rejection, min_samples_leaf count semantics, and bootstrap duplicate contribution.

Validation run:

conda run -n cuml_dev cmake --build /home/rorym/cuml-builds/codex-enh-rf-sample-weight/cpp-release --target SG_RF_TEST -j16
conda run -n cuml_dev bash -lc 'LD_PRELOAD=/home/rorym/cuml-builds/codex-enh-rf-sample-weight/cpp-release/libcuml.so /home/rorym/cuml-builds/codex-enh-rf-sample-weight/cpp-release/tests/SG_RF_TEST'

SG_RF_TEST: 282/282 passed.

@copy-pr-bot

copy-pr-bot Bot commented Jun 12, 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.

@RAMitchell RAMitchell added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Jun 12, 2026
@RAMitchell
RAMitchell marked this pull request as ready for review June 12, 2026 11:22
@RAMitchell
RAMitchell requested review from a team as code owners June 12, 2026 11:22
@coderabbitai

coderabbitai Bot commented Jun 12, 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

This PR adds optional per-sample weighting to RandomForest and DecisionTree training. Sample weights are passed through public fit APIs, stored in Dataset, forwarded by Builder and DecisionTree, used in objective histogram updates, wired into weighted CUDA translation units, and covered by new validation and behavior tests.

Changes

Per-sample weighted training

Layer / File(s) Summary
RandomForest public API and forwarding
cpp/include/cuml/ensemble/randomforest.hpp, cpp/src/randomforest/randomforest.cu
Adds sample_weight to RandomForest fit and fit_treelite declarations across classification/regression and float/double types; forwards it through the public overloads and updates treelite instantiations.
RandomForest sample_weight validation and dispatch
cpp/src/randomforest/randomforest.cuh
Adds sample-weight validation helpers, documents the parameter, and passes validated weights into per-tree DecisionTree training.
DecisionTree dataset and builder wiring
cpp/src/decisiontree/batched-levelalgo/dataset.h, cpp/src/decisiontree/batched-levelalgo/builder.cuh, cpp/src/decisiontree/decisiontree.cuh
Dataset gains sample_weight; Builder accepts and stores it; DecisionTree::fit branches on weighted vs unweighted objective builders.
Weighted bin signatures and objective IncrementHistogram
cpp/src/decisiontree/batched-levelalgo/bins.cuh, cpp/src/decisiontree/batched-levelalgo/objectives.cuh
Bin histogram APIs now take explicit weights, and objectives compute per-row weights from dataset.sample_weight before updating histograms.
Kernel interface, specializations, and build wiring
cpp/CMakeLists.txt, cpp/src/decisiontree/batched-levelalgo/kernels/*
Kernel templates drop the explicit bin template parameter, switch to objective-based histogram updates, and add explicit instantiations plus new weighted translation units.
Test suite: weighted scenarios and validation
cpp/tests/sg/rf_test.cu
Adds weighted parameterization, weighted scoring, deterministic sample-weight fixtures, invalid-weight checks, and weighted behavior tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • rapidsai/cuml#8132: Shares the weighted RandomForest/DecisionTree training path and weighted kernel plumbing in cpp/src/decisiontree/batched-levelalgo.
  • rapidsai/cuml#8233: Modifies the same batched-level decision-tree objective and builder path that this PR extends for sample_weight.
  • rapidsai/cuml#8247: Overlaps with the weighted histogram/bin and objective refactor used by the new weighted training path.

Suggested reviewers

  • hcho3
  • lowener
  • viclafargue
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.52% 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 clearly summarizes the main change: adding C++ Random Forest sample-weight training support.
Description check ✅ Passed The description is directly about the same sample-weight Random Forest training changes and test coverage.
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.

Actionable comments posted: 2

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.cu (1)

423-437: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Weighted fit_treelite still returns count-weighted feature importances.

These paths now allow weighted training, but compute_feature_importances() still scales each split gain by node.InstanceCount(). In this PR the node counts remain raw sampled occurrences while sample_weight only affects impurity/objective math, so weighted models will report feature importances biased by row count instead of total weight reaching the node. That makes the exported importances wrong whenever weights are non-uniform.

Also applies to: 667-680

🤖 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.cu` around lines 423 - 437,
compute_feature_importances currently multiplies split gains by
node.InstanceCount(), which yields count-weighted importances even when training
used sample_weight; change compute_feature_importances (and the same logic used
in fit_treelite paths) to weight split gains by the total sample weight that
reached the node instead of InstanceCount(): aggregate per-node weight using the
provided sample_weight array (and respect bootstrap_masks when sampling) and use
that sum (e.g., node total weight or SumWeight()) wherever node.InstanceCount()
is used to scale gains so feature importances reflect weighted training.
🤖 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/src/decisiontree/batched-levelalgo/dataset.h`:
- Around line 17-18: Update the SPDX/copyright header at the top of dataset.h so
the year range includes the current year (e.g., extend "2022" to "2022-2026" or
similar) to satisfy the repo's header checker; locate the header comment in
cpp/src/decisiontree/batched-levelalgo/dataset.h (above the declarations
including DataT and the sample_weight member) and replace the old year with the
updated range or current year.

In `@cpp/src/randomforest/randomforest.cuh`:
- Around line 127-128: The current assert only checks device residency; before
dispatching the weighted-training path (where sample_weight values are consumed
as histogram mass), add a validation step that scans the sample_weight buffer
on-device and rejects any NaN or negative values: ensure every sample_weight[i]
is finite and >= 0, and return/log an error if any invalid entries are found.
Keep the existing ASSERT(sample_weight == nullptr ||
DT::is_dev_ptr(sample_weight)) but follow it with a GPU-safe check (e.g., a
short kernel or thrust::any_of) that inspects the device buffer and fails early
if non-finite or negative weights are present so downstream uses in
histogram/weighted objective code are protected.

---

Outside diff comments:
In `@cpp/src/randomforest/randomforest.cu`:
- Around line 423-437: compute_feature_importances currently multiplies split
gains by node.InstanceCount(), which yields count-weighted importances even when
training used sample_weight; change compute_feature_importances (and the same
logic used in fit_treelite paths) to weight split gains by the total sample
weight that reached the node instead of InstanceCount(): aggregate per-node
weight using the provided sample_weight array (and respect bootstrap_masks when
sampling) and use that sum (e.g., node total weight or SumWeight()) wherever
node.InstanceCount() is used to scale gains so feature importances reflect
weighted training.
🪄 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: f0d57bf2-f2ca-4796-9c7c-ef5746f6ae37

📥 Commits

Reviewing files that changed from the base of the PR and between 8c1f46c and 2ee69d5.

📒 Files selected for processing (16)
  • cpp/CMakeLists.txt
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/batched-levelalgo/bins.cuh
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • cpp/src/decisiontree/batched-levelalgo/dataset.h
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/src/decisiontree/batched-levelalgo/kernels/weighted-classification-double.cu
  • cpp/src/decisiontree/batched-levelalgo/kernels/weighted-classification-float.cu
  • cpp/src/decisiontree/batched-levelalgo/kernels/weighted-regression-double.cu
  • cpp/src/decisiontree/batched-levelalgo/kernels/weighted-regression-float.cu
  • cpp/src/decisiontree/batched-levelalgo/objectives.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh
  • cpp/tests/sg/rf_test.cu

Comment thread cpp/src/decisiontree/batched-levelalgo/dataset.h
Comment thread cpp/src/randomforest/randomforest.cuh Outdated

@chyunsu3 chyunsu3 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.

The C++ changes look good overall.

Question. Can you point me which part of this PR address Rejects zero-weight split children through objective math.? I'm not seeing which lines of code checks for zero-weight splits.

Comment thread cpp/src/decisiontree/batched-levelalgo/kernels/weighted-classification-double.cu Outdated
@RAMitchell

Copy link
Copy Markdown
Contributor Author

/ok to test 03bb470

@RAMitchell
RAMitchell force-pushed the codex/enh-rf-sample-weight branch from 03bb470 to ab3c6b7 Compare June 26, 2026 12:28
@RAMitchell

Copy link
Copy Markdown
Contributor Author

/merge

@csadorf csadorf 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.

Awesome! 🚢

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

Labels

CMake CUDA/C++ improvement Improvement / enhancement to an existing function non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants