Skip to content

RF: add sample_weight to RandomForestRegressor - #8187

Closed
switch527 wants to merge 1 commit into
NVIDIA:mainfrom
switch527:enh-rf-sample-weight
Closed

RF: add sample_weight to RandomForestRegressor#8187
switch527 wants to merge 1 commit into
NVIDIA:mainfrom
switch527:enh-rf-sample-weight

Conversation

@switch527

Copy link
Copy Markdown
Contributor

Towards #8093. Adds sample_weight to RandomForestRegressor.fit, building on the classifier work in #8132. Single-GPU only; distributed (Dask) raises NotImplementedError (tracking #8186).

The regressor follows the companion-buffer approach the classifier already uses: a per-bin weighted-count buffer feeds the weighted gain and leaf mean, while the unweighted sample count still drives min_samples_leaf. Weighted MSE and Poisson match sklearn; Gamma and InverseGaussian apply the same substitution to cuml's existing formulas. Leaf vectors NaN-guard the all-zero-weight case.

Adds per-objective ground-truth gtests plus Python tests mirrored across RFC and RFR. The accel proxy now forwards sample_weight to the GPU path. xfail entries for sklearn's check_sample_weight_equivalence checks stay because cuml's quantile-binned splits don't satisfy the row-duplication assumption.

On the regressor bench, double-precision MSE runs ~11% slower than the pre-PR baseline (cost of the per-sample weight accumulator); float is flat and classifier is within noise. libcuml.so grows by 36 KB. Adjacent fix: re-enabled cpp/bench/sg/rf_regressor.cu (was disabled with a stale FIXME); fixed a couple of compile errors and scaled the workload down so it finishes in minutes.

@copy-pr-bot

copy-pr-bot Bot commented May 31, 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.

@github-actions github-actions Bot added Cython / Python Cython or Python issue CMake CUDA/C++ labels May 31, 2026
@switch527
switch527 marked this pull request as ready for review May 31, 2026 19:38
@switch527
switch527 requested review from a team as code owners May 31, 2026 19:38
@coderabbitai

coderabbitai Bot commented May 31, 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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5b195068-92d2-440b-863a-6086fc12541a

📥 Commits

Reviewing files that changed from the base of the PR and between 8fcce04 and 894ef35.

📒 Files selected for processing (26)
  • cpp/bench/CMakeLists.txt
  • cpp/bench/sg/rf_regressor.cu
  • 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/objectives.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh
  • cpp/tests/sg/rf_test.cu
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_random_forest.py
  • python/cuml/tests/test_sklearn_compatibility.py
💤 Files with no reviewable changes (1)
  • docs/source/cuml-accel/limitations.rst
✅ Files skipped from review due to trivial changes (1)
  • cpp/bench/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (21)
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • cpp/src/randomforest/randomforest.cuh
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • cpp/src/decisiontree/batched-levelalgo/dataset.h
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • cpp/include/cuml/ensemble/randomforest.hpp
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • cpp/tests/sg/rf_test.cu
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_random_forest.py
  • cpp/src/decisiontree/batched-levelalgo/objectives.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • GPU RandomForest (classifier/regressor) and DecisionTree training now accept per-sample weights for fit and score.
  • Benchmarks

    • RF regressor benchmark enabled; benchmark datasets scaled down for perf-watchdog.
  • Documentation

    • Removed prior CPU-fallback note for sample_weight in RandomForest accel docs.
  • Tests

    • Added unit/integration tests for weighted training/score behavior and updated sklearn-compatibility xfails.
  • Known Limitations

    • Distributed (Dask) RandomForest.fit.sample_weight raises NotImplementedError (use single-GPU API).

Walkthrough

Threads optional per-sample weights across decision-tree histograms, kernels, objectives, RandomForest/DecisionTree C++ APIs, Cython/Python bindings, and tests; Dask estimators explicitly reject non-None sample_weight; docs and benchmarks updated.

Changes

Per-Sample Weighting Implementation

Layer / File(s) Summary
Histogram and dataset foundations
cpp/src/decisiontree/batched-levelalgo/bins.cuh, cpp/src/decisiontree/batched-levelalgo/dataset.h
Add weight parameter to IncrementHistogram for CountBin and AggregateBin; add static_assert for AggregateBin layout; add optional Dataset::sample_weight pointer.
Builder companion buffers and workspace layout
cpp/src/decisiontree/batched-levelalgo/builder.cuh
Builder accepts optional sample_weight, adds objective-dependent companion buffers (int* unweighted for classifier, double* weighted for regressor), updates workspace sizing/assignment and shared-memory computation sizing.
Kernel interface
cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
Extend launchComputeSplitKernel signature with unweighted_histograms and weighted_count_histograms companion pointers and document conditional non-null usage.
Kernel implementation
cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
Refactor leafKernel and computeSplitKernel to apply per-sample weights during histogram updates, initialize/merge companion shared/global buffers, reload companions for last-block path, and specialize PDF→CDF and objective calls by BinT.
Weighted split-gain objectives
cpp/src/decisiontree/batched-levelalgo/objectives.cuh
Refactor Gini/Entropy/MSE/Poisson/Gamma/InverseGaussian to use weighted totals (W_total, W_left) for gain computations, enforce min_samples_leaf via unweighted counts, and update SetLeafVector for degenerate weighted totals.
DecisionTree public API wiring
cpp/src/decisiontree/decisiontree.cuh
Add optional sample_weight parameter to DecisionTree::fit and forward it into Builder construction for all split criteria.
RandomForest C++ API & impl
cpp/include/cuml/ensemble/randomforest.hpp, cpp/src/randomforest/randomforest.cu, cpp/src/randomforest/randomforest.cuh
Extend fit and fit_treelite signatures to accept optional sample_weight (float/double variants) and thread it through per-tree DecisionTree::fit; update explicit exported instantiations.
Cython bridge
python/cuml/cuml/ensemble/randomforest_common.pyx
Add sample_weight to fit_treelite externs; _fit_forest accepts optional sample_weight, maps None to NULL, and prepares float/double device-pointer variants to forward to C++.
Python single-GPU APIs
python/cuml/cuml/ensemble/randomforestclassifier.py, python/cuml/cuml/ensemble/randomforestregressor.py, python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
fit/score accept sample_weight, validate and forward it to GPU calls; removed accel override rejection so GPU methods receive weights.
Python Dask APIs
python/cuml/cuml/dask/ensemble/randomforestclassifier.py, python/cuml/cuml/dask/ensemble/randomforestregressor.py
Signatures accept sample_weight but runtime-guard NotImplementedError when non-None; docstrings document unsupported status.
C++ tests
cpp/tests/sg/rf_test.cu
Add tests ensuring sample_weight=1.0 matches nullptr training and objective-level weighted Gain/SetLeafVector correctness; ObjectiveTest now computes and passes weighted denominators.
Python tests
python/cuml/tests/test_random_forest.py, python/cuml/tests/dask/test_dask_random_forest.py, python/cuml/cuml_accel_tests/integration/test_rf_*.py, python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
Add unit and integration tests for classifier/regressor weighted behavior, Dask NotImplemented checks, and add xfails documenting sample_weight equivalence limits for sklearn compatibility.
Docs, bench, and config
docs/source/cuml-accel/limitations.rst, cpp/bench/CMakeLists.txt, cpp/bench/sg/rf_regressor.cu
Remove CPU-fallback claim for sample_weight in limitations; enable rf_regressor benchmark in CMake and scale down benchmark params/dataset sizes; update SPDX years.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Possibly related PRs

  • rapidsai/cuml#8132: Overlapping sample_weight plumbing through RandomForest/DecisionTree and batched-levelalgo internals.
  • rapidsai/cuml#8023: Related changes to accel/sklearn RandomForest override behavior around sample_weight.
  • rapidsai/cuml#7895: Related kernel/launcher interface edits in builder/kernels paths.

Suggested labels

improvement, feature request, sklearn-api-compat, non-breaking

Suggested reviewers

  • betatim
  • hcho3
  • divyegala
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.85% 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 'RF: add sample_weight to RandomForestRegressor' clearly and concisely summarizes the main change—adding sample_weight support to RandomForestRegressor. It is specific, relevant, and directly reflects the primary contribution of the changeset.
Description check ✅ Passed The description is comprehensive and directly related to the changeset. It explains the purpose (adding sample_weight to RandomForestRegressor.fit), the implementation approach (companion-buffer pattern), scope (single-GPU only, Dask raises NotImplementedError), testing additions, and performance/binary size impacts. It is well-detailed and clearly conveys the changes made.
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: 5

Caution

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

⚠️ Outside diff range comments (3)
cpp/src/randomforest/randomforest.cuh (1)

123-125: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate sample_weight pointer before training starts.

Line 124 validates input/labels, but the new sample_weight pointer is forwarded unchecked. A host pointer here can trigger invalid device-memory access in downstream training code.

Suggested patch
   raft::common::nvtx::range fun_scope("RandomForest::fit `@randomforest.cuh`");
   this->error_checking(input, labels, n_rows, n_cols, false);
+  if (sample_weight != nullptr) {
+    ASSERT(DT::is_dev_ptr(sample_weight),
+           "RF Error: Expected sample_weight to be a GPU pointer");
+  }
   const raft::handle_t& handle = user_handle;
🤖 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` around lines 123 - 125, The fit
entrypoint (RandomForest::fit) currently calls this->error_checking(input,
labels, n_rows, n_cols, false) but does not validate the new sample_weight
pointer; add a validation step before proceeding (similar to input/labels
checks) to ensure sample_weight is either nullptr or a valid host/device pointer
as expected by downstream code and raise/return an error if it's invalid; modify
the RandomForest::fit pre-checks to include sample_weight validation (or extend
error_checking to accept sample_weight) so downstream training routines never
receive an unchecked host pointer that could cause invalid device-memory access.
cpp/include/cuml/ensemble/randomforest.hpp (2)

145-166: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the old exported signatures as deprecation shims.

Appending sample_weight here changes the public C++ training signatures, which changes the mangled names for these exported entry points. That is an API/ABI break for existing libcuml consumers even though recompiled call sites can omit the new argument. Please keep the previous overloads as deprecated forwarders (or explicitly version/migrate this change) instead of replacing the signatures in place. As per coding guidelines, public headers under cpp/include/cuml/**/* must avoid breaking API changes without proper deprecation warnings.

Also applies to: 169-180, 230-249, 251-262

🤖 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/cuml/ensemble/randomforest.hpp` around lines 145 - 166, The new
optional sample_weight parameters changed the exported mangled names for the
public fit overloads (e.g., fit(const raft::handle_t&, RandomForestClassifierF*,
float*, int, int, int*, int, RF_params, rapids_logger::level_enum, bool*,
float*) and the double/ClassifierD variants); restore the previous public
signatures by adding back the original overloads without sample_weight as
deprecated forwarders that call the new implementations (or provide versioned
wrappers), mark them deprecated, and keep the new overloads with sample_weight
as the canonical implementation; apply the same treatment to the other changed
overload groups referenced in the comment (lines for the other float/double and
prediction/multi-class fit variants) so the ABI is preserved while guiding users
to the new APIs.

145-166: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Document the sample_weight contract in this header.

These public declarations now expose a new raw pointer without any parameter docs. Please add at least minimal Doxygen covering expected length, device/host memory expectations, nullptr behavior, and whether zero or negative weights are valid so downstream C++ callers have the same contract the Python layer already relies on. As per coding guidelines, public headers under cpp/include/cuml/**/* require Doxygen documentation for public functions and API changes to be flagged for docs updates.

Also applies to: 169-180, 230-249, 251-262

🤖 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/cuml/ensemble/randomforest.hpp` around lines 145 - 166, Add
Doxygen for the sample_weight parameter on the public fit overloads
(RandomForestClassifierF::fit, RandomForestClassifierD::fit and the other
affected overloads at the noted ranges) describing that sample_weight is an
optional pointer of length n_rows, whether it must point to device or host
memory (specify device memory if GPU-only, or host if copied), that nullptr
disables weighting, and the semantics for zero/negative values (e.g., zero means
exclude the sample, negative values invalid or treated as error) plus expected
numeric type (float/double) and ownership (caller retains ownership). Ensure the
docblock is added to each function declaration so the header documents the exact
contract used by the Python layer and downstream C++ callers.
🤖 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 `@python/cuml/cuml_accel_tests/integration/test_rf_classifier.py`:
- Around line 222-224: Replace the nondeterministic assertion by computing
predictions and comparing the weighted score to sklearn.metrics.accuracy_score:
call y_pred = clf.predict(X), compute expected = accuracy_score(y, y_pred,
sample_weight=w), and assert s_weighted == expected (or use pytest.approx if
floating-point tolerance is needed); reference the variables clf.score,
s_weighted, and use accuracy_score to perform the deterministic check.

In `@python/cuml/cuml_accel_tests/integration/test_rf_regressor.py`:
- Around line 206-208: Replace the flaky inequality check between reg.score(...)
variants with a deterministic metric equivalence: compute the weighted R² via
r2_score(y, reg.predict(X), sample_weight=w) and compare that expected value to
reg.score(X, y, sample_weight=w) using a tolerant numeric assertion (e.g.,
np.testing.assert_allclose or pytest.approx) so the test verifies equality to
the metric implementation rather than relying on !=; reference the reg.score
method, r2_score, and the sample_weight w when updating the assertion.

In `@python/cuml/cuml/dask/ensemble/randomforestregressor.py`:
- Around line 144-152: The fit method currently allows sample_weight as a
positional argument which shifts subsequent positional arguments (convert_dtype)
for callers; change the signature of fit so sample_weight is keyword-only by
moving it after the existing * (i.e., make parameters sample_weight,
convert_dtype, broadcast_data all keyword-only), update any internal references
if needed, and run tests to ensure existing callers that passed sample_weight
positionally now pass it by keyword; target the fit method and the parameters
sample_weight and convert_dtype when making this change.

In `@python/cuml/tests/dask/test_dask_random_forest.py`:
- Around line 523-527: The helper _tiny_dataset currently creates two partitions
which can produce empty partitions on multi-worker setups; change its Dask array
creation to use a single chunk equal to the number of samples so partitions are
topology-agnostic: for X and y call from_array(..., chunks=len(y)) (or
chunks=(len(y),) for 2D) and also wrap the sample-weight w with from_array(w,
chunks=len(y)) so all returned arrays have a single non-empty partition; apply
the same change to the other helper/test helpers referenced in the 530-565
range.

In `@python/cuml/tests/test_random_forest.py`:
- Around line 1538-1541: Replace the brittle inequality check by computing the
expected metrics with sklearn.metrics.accuracy_score and comparing clf.score
outputs to those expected values: call accuracy_score(y, clf.predict(X)) for the
unweighted expected score and accuracy_score(y, clf.predict(X), sample_weight=w)
for the weighted expected score, then assert s_unweighted == expected_unweighted
and s_weighted == expected_weighted; update the assertions around clf.score,
s_unweighted, s_weighted, X, y, w to use these deterministic comparisons (apply
same change at the other occurrence around s_unweighted/s_weighted at lines
~1593-1595).

---

Outside diff comments:
In `@cpp/include/cuml/ensemble/randomforest.hpp`:
- Around line 145-166: The new optional sample_weight parameters changed the
exported mangled names for the public fit overloads (e.g., fit(const
raft::handle_t&, RandomForestClassifierF*, float*, int, int, int*, int,
RF_params, rapids_logger::level_enum, bool*, float*) and the double/ClassifierD
variants); restore the previous public signatures by adding back the original
overloads without sample_weight as deprecated forwarders that call the new
implementations (or provide versioned wrappers), mark them deprecated, and keep
the new overloads with sample_weight as the canonical implementation; apply the
same treatment to the other changed overload groups referenced in the comment
(lines for the other float/double and prediction/multi-class fit variants) so
the ABI is preserved while guiding users to the new APIs.
- Around line 145-166: Add Doxygen for the sample_weight parameter on the public
fit overloads (RandomForestClassifierF::fit, RandomForestClassifierD::fit and
the other affected overloads at the noted ranges) describing that sample_weight
is an optional pointer of length n_rows, whether it must point to device or host
memory (specify device memory if GPU-only, or host if copied), that nullptr
disables weighting, and the semantics for zero/negative values (e.g., zero means
exclude the sample, negative values invalid or treated as error) plus expected
numeric type (float/double) and ownership (caller retains ownership). Ensure the
docblock is added to each function declaration so the header documents the exact
contract used by the Python layer and downstream C++ callers.

In `@cpp/src/randomforest/randomforest.cuh`:
- Around line 123-125: The fit entrypoint (RandomForest::fit) currently calls
this->error_checking(input, labels, n_rows, n_cols, false) but does not validate
the new sample_weight pointer; add a validation step before proceeding (similar
to input/labels checks) to ensure sample_weight is either nullptr or a valid
host/device pointer as expected by downstream code and raise/return an error if
it's invalid; modify the RandomForest::fit pre-checks to include sample_weight
validation (or extend error_checking to accept sample_weight) so downstream
training routines never receive an unchecked host pointer that could cause
invalid device-memory access.
🪄 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: 8eb93e75-d4b8-43cf-b988-d5c1830f2bfd

📥 Commits

Reviewing files that changed from the base of the PR and between 4d01e52 and 33a5cbe.

📒 Files selected for processing (28)
  • cpp/bench/CMakeLists.txt
  • cpp/bench/sg/rf_classifier.cu
  • cpp/bench/sg/rf_regressor.cu
  • 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/objectives.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh
  • cpp/tests/sg/rf_test.cu
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • python/cuml/cuml_accel_tests/test_onnx.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_random_forest.py
  • python/cuml/tests/test_sklearn_compatibility.py
💤 Files with no reviewable changes (2)
  • python/cuml/cuml_accel_tests/test_onnx.py
  • docs/source/cuml-accel/limitations.rst

Comment thread python/cuml/cuml_accel_tests/integration/test_rf_classifier.py Outdated
Comment thread python/cuml/cuml_accel_tests/integration/test_rf_regressor.py Outdated
Comment thread python/cuml/cuml/dask/ensemble/randomforestregressor.py
Comment thread python/cuml/tests/dask/test_dask_random_forest.py Outdated
Comment thread python/cuml/tests/test_random_forest.py Outdated
@switch527
switch527 force-pushed the enh-rf-sample-weight branch from 33a5cbe to 36264ad Compare May 31, 2026 23:27
@switch527

Copy link
Copy Markdown
Contributor Author

Pushed 36264ad addressing the five CodeRabbit threads in one commit:

  • Brittle != score assertions in test_rfc_score_with_sample_weight, test_rfr_score_with_sample_weight, and the two accel integration tests are now metric-equivalence checks via pytest.approx against accuracy_score / r2_score with the same sample_weight.
  • Dask fit() on both classifier and regressor: sample_weight is keyword-only after *; convert_dtype and broadcast_data stay positional-or-keyword. Docstring parameter order matches the signature.
  • _tiny_dataset collapsed to a single chunk; the *_none_does_not_raise tests pass ignore_empty_partitions=True; the *_raises tests don't, with an inline comment on the asymmetry.

Affected unit, accel, and dask suites pass locally. Backup tag backup-pr8187-pre-coderabbit-fix points at the previous SHA.

@switch527
switch527 force-pushed the enh-rf-sample-weight branch from 36264ad to a2d268b Compare June 2, 2026 03:50

@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: 1

🤖 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/sg/rf_test.cu`:
- Around line 741-752: The test comparing regressor forests (variables
forest_null and forest_ones) is missing assertions for tree depth_counter and
sparsetree size; update the tree-comparison loop in rf_test.cu (where tn and to
are compared) to also ASSERT_EQ(tn->depth_counter, to->depth_counter) and
ASSERT_EQ(tn->sparsetree.size(), to->sparsetree.size()) with the same diagnostic
text style used for leaf_counter and vector_leaf so the regressor unit-weight
test checks identical tree structure byte-for-byte.
🪄 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: ababa898-dceb-4bcc-b431-849679c47ddb

📥 Commits

Reviewing files that changed from the base of the PR and between 36264ad and a2d268b.

📒 Files selected for processing (26)
  • cpp/bench/CMakeLists.txt
  • cpp/bench/sg/rf_regressor.cu
  • 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/objectives.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh
  • cpp/tests/sg/rf_test.cu
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_random_forest.py
  • python/cuml/tests/test_sklearn_compatibility.py
💤 Files with no reviewable changes (1)
  • docs/source/cuml-accel/limitations.rst
🚧 Files skipped from review as they are similar to previous changes (22)
  • cpp/src/decisiontree/batched-levelalgo/dataset.h
  • cpp/bench/CMakeLists.txt
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • cpp/bench/sg/rf_regressor.cu
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • cpp/src/randomforest/randomforest.cuh
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/decisiontree.cuh
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • cpp/src/decisiontree/batched-levelalgo/bins.cuh
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • cpp/src/randomforest/randomforest.cu
  • python/cuml/tests/test_random_forest.py
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/src/decisiontree/batched-levelalgo/objectives.cuh

Comment thread cpp/tests/sg/rf_test.cu
@switch527
switch527 force-pushed the enh-rf-sample-weight branch from a2d268b to 8fcce04 Compare June 2, 2026 04:35

@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/decisiontree/batched-levelalgo/objectives.cuh (1)

319-329: 💤 Low value

Consider extracting common regression SetLeafVector logic.

The SetLeafVector implementations for MSE, Poisson, Gamma, and InverseGaussian are identical. If the regression objectives continue to share this pattern, a shared helper or base class method could reduce future maintenance burden.

This is a minor observation—the current duplication is manageable and the code is clear.

Also applies to: 420-431, 518-528, 615-625

🤖 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/decisiontree/batched-levelalgo/objectives.cuh` around lines 319 -
329, Multiple regression objective implementations duplicate the same
SetLeafVector logic; extract a shared helper function (e.g., a templated inline
helper SetLeafVectorCommon or move into a common base used by
MSE/Possion/Gamma/InverseGaussian) that accepts BinT const* shist, int nclasses,
DataT* out, double weighted_total and contains the current guard for
weighted_total <= 0.0 and the loop assigning out[i] = DataT(shist[i].label_sum /
weighted_total). Replace the per-objective SetLeafVector implementations (the
functions named SetLeafVector in these objective files) to call that helper to
remove duplication while preserving types and behavior.
🤖 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/decisiontree/batched-levelalgo/objectives.cuh`:
- Around line 319-329: Multiple regression objective implementations duplicate
the same SetLeafVector logic; extract a shared helper function (e.g., a
templated inline helper SetLeafVectorCommon or move into a common base used by
MSE/Possion/Gamma/InverseGaussian) that accepts BinT const* shist, int nclasses,
DataT* out, double weighted_total and contains the current guard for
weighted_total <= 0.0 and the loop assigning out[i] = DataT(shist[i].label_sum /
weighted_total). Replace the per-objective SetLeafVector implementations (the
functions named SetLeafVector in these objective files) to call that helper to
remove duplication while preserving types and behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b9c91de9-3c58-4696-8ec8-a66751ecc74f

📥 Commits

Reviewing files that changed from the base of the PR and between a2d268b and 8fcce04.

📒 Files selected for processing (26)
  • cpp/bench/CMakeLists.txt
  • cpp/bench/sg/rf_regressor.cu
  • 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/objectives.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh
  • cpp/tests/sg/rf_test.cu
  • docs/source/cuml-accel/limitations.rst
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_random_forest.py
  • python/cuml/tests/test_sklearn_compatibility.py
💤 Files with no reviewable changes (1)
  • docs/source/cuml-accel/limitations.rst
🚧 Files skipped from review as they are similar to previous changes (22)
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels.cuh
  • cpp/bench/CMakeLists.txt
  • python/cuml/cuml_accel_tests/integration/test_rf_classifier.py
  • cpp/src/randomforest/randomforest.cuh
  • python/cuml/tests/dask/test_dask_random_forest.py
  • python/cuml/tests/test_sklearn_compatibility.py
  • python/cuml/cuml/dask/ensemble/randomforestregressor.py
  • cpp/include/cuml/ensemble/randomforest.hpp
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • cpp/src/decisiontree/decisiontree.cuh
  • python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml
  • python/cuml/tests/test_random_forest.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/cuml/dask/ensemble/randomforestclassifier.py
  • cpp/bench/sg/rf_regressor.cu
  • python/cuml/cuml/accel/_overrides/sklearn/ensemble.py
  • python/cuml/cuml_accel_tests/integration/test_rf_regressor.py
  • cpp/src/randomforest/randomforest.cu
  • cpp/tests/sg/rf_test.cu
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh

Towards NVIDIA#8093. Extends sample_weight to RandomForestRegressor.fit,
building on the classifier work in NVIDIA#8132. Single-GPU only; distributed
(Dask) raises NotImplementedError and points users at the single-GPU
class (tracking NVIDIA#8186).

Regressor mirrors the classifier companion-buffer pattern with a
double* weighted_count_histograms alongside the existing AggregateBin
histogram. The companion holds sum(weight) per bin and feeds Gain for
the weighted denominator and SetLeafVector for the leaf mean.
AggregateBin.count stays unweighted so min_samples_leaf and Split::nLeft
still operate on integer sample counts. The four regressor objectives
substitute n with sum(weight) following sklearn 1.7.2 _criterion.pyx
proxy_impurity_improvement (MSE :1089-1118; Poisson :1597-1642); Gamma
and InverseGaussian apply the same substitution to cuml's existing
formulas. All six SetLeafVector paths NaN-guard the all-zero-weight
leaf.

Tests add per-objective weighted ground-truth gtests with hand-derived
expected values, NaN-guard gtests for both SetLeafVector paths, and
Python tests mirrored across RFC and RFR. Accel proxy forwards
sample_weight to GPU. test_sklearn_compatibility.py and xfail-list.yaml
xfail check_sample_weight_equivalence for both estimators (quantile
binning != row duplication).

Perf: RFR<double> MSE +11.1% on the rf_regressor bench vs the pre-PR
baseline (extra per-sample atomicAdd into the companion buffer + extra
pdf_to_cdf<double> scan); RFR<float> flat; classifier within noise.
libcuml.so +36 KB. Adjacent fix: re-enabled cpp/bench/sg/rf_regressor.cu
(disabled in a 2024 FIXME); pre-existing compile errors fixed and
workload scaled down so it finishes in minutes.
@switch527
switch527 force-pushed the enh-rf-sample-weight branch from 8fcce04 to 894ef35 Compare June 2, 2026 15:23
@csadorf

csadorf commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

/ok to test 894ef35

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

This PR is way too large again and is pretty hacky.

The dilemma here is that we might need both integer counts or sums of weights to implement both min_samples_leaf and min_weight_leaf.

We could:

  1. Create new templated bins/objectives with weighted versions for every objective. This would double compile time and binary size.
  2. We expand the bin types to all include both integer count and weight accumulators. This will increase memory and presumably decrease performance across the board.
  3. Create a separate buffer like you have done here, but this creates branches everywhere and is hard to read and maintain.
  4. We diverge slightly from sklearn and min_samples means mininum weight sum in the weighted case. Implementation is elegant and fast. This is pretty reasonable but just a slightly different behaviour.

@switch527

Copy link
Copy Markdown
Contributor Author

Option 4 sounds right. Will drop the companion buffers here, on #8188, and in future ExtraTrees work, and document min_samples_leaf as weight-sum under sample_weight.

@RAMitchell

Copy link
Copy Markdown
Contributor

@switch527 this ended up a bit more complicated than expected and I am still thinking about the best way forward, so we can wait a bit.

@switch527

Copy link
Copy Markdown
Contributor Author

@RAMitchell, no worries. Let me know when you decide and how you want to proceed.

@switch527 switch527 closed this Jun 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants