Skip to content

Support row-major RandomForest training inputs - #8324

Merged
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
RAMitchell:codex/rf-layout-benchmark
Jul 10, 2026
Merged

Support row-major RandomForest training inputs#8324
rapids-bot[bot] merged 7 commits into
NVIDIA:mainfrom
RAMitchell:codex/rf-layout-benchmark

Conversation

@RAMitchell

Copy link
Copy Markdown
Contributor

Summary

Addresses #8310.

Adds a runtime layout switch for RandomForest training so callers can pass row-major input directly instead of transposing to column-major first. Column-major remains the default.

For callers that already have row-major data, this avoids holding both the original row-major matrix and a transposed column-major copy during fit. Excluding RF/DT working space, the input data footprint is therefore reduced from two matrix copies to one, roughly halving input-data memory usage.

Changes

  • Add input_row_major = false to the C++ RandomForest fit APIs.
  • Thread row-major layout through RF, quantile computation, and DT training.
  • Add Dataset::value(row, col) and store n_rows / n_cols as int64_t.
  • Update split/partition kernels to use the dataset accessor instead of hard-coded column-major indexing.
  • Extend RF benchmarks to run both /col and /row layouts.

Validation

  • pre-commit run --from-ref upstream/main --to-ref HEAD
  • ninja -C /home/rorym/cuml-builds/codex-rf-layout-benchmark/cpp-rfbench cuml -j8

@copy-pr-bot

copy-pr-bot Bot commented Jul 6, 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.

@RAMitchell
RAMitchell requested a review from Copilot July 6, 2026 09:20
@RAMitchell RAMitchell added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Jul 6, 2026

Copilot AI 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.

Pull request overview

Adds an opt-in runtime switch to train cuML RandomForest directly from row-major feature matrices (keeping column-major as the default), reducing peak memory usage by avoiding an explicit transpose/copy for callers that already have row-major data.

Changes:

  • Extend C++ RandomForest fit APIs with input_row_major and thread it through quantile computation and DecisionTree training.
  • Introduce Dataset::value(row, col) plus n_rows/n_cols stored as int64_t, and update split/partition kernels to use the accessor instead of hard-coded column-major indexing.
  • Update single-GPU RF benchmarks to exercise both /col and /row layouts.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
cpp/src/randomforest/randomforest.cuh Adds input_row_major to RandomForest::fit and threads it into quantile computation and DT training.
cpp/src/randomforest/randomforest.cu Extends exported fit overloads to accept and forward input_row_major.
cpp/src/decisiontree/decisiontree.cuh Adds row_major plumbing into DecisionTree training entrypoint.
cpp/src/decisiontree/batched-levelalgo/quantiles.cuh Enables quantile sampling from either row-major or column-major inputs.
cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh Switches kernel element access to dataset.value(row, col).
cpp/src/decisiontree/batched-levelalgo/dataset.h Adds Dataset::value(row, col), n_rows/n_cols as int64_t, and a row_major flag.
cpp/src/decisiontree/batched-levelalgo/builder.cuh Threads row_major into Dataset construction and updates column-count references.
cpp/include/cuml/ensemble/randomforest.hpp Updates public C++ API declarations with input_row_major defaulting to false.
cpp/bench/sg/rf_regressor.cu Updates benchmark to run in both layouts and clear trees between runs (but needs an argument fix).
cpp/bench/sg/rf_classifier.cu Updates benchmark to run in both layouts and clear trees between runs (but needs an argument fix).
cpp/bench/sg/dataset.cuh Adds /row vs /col suffix to benchmark dataset naming.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cpp/bench/sg/rf_regressor.cu
Comment thread cpp/bench/sg/rf_classifier.cu
Comment thread cpp/src/decisiontree/batched-levelalgo/dataset.h Outdated
@RAMitchell
RAMitchell force-pushed the codex/rf-layout-benchmark branch from f94a1c9 to f421885 Compare July 6, 2026 09:32
@RAMitchell
RAMitchell marked this pull request as ready for review July 6, 2026 12:06
@RAMitchell
RAMitchell requested a review from a team as a code owner July 6, 2026 12:06
@RAMitchell
RAMitchell requested review from lowener and viclafargue July 6, 2026 12:06
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

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: 3a33c1fc-6799-4f00-bd77-8872e8273771

📥 Commits

Reviewing files that changed from the base of the PR and between 9081fd2 and b6df5e3.

📒 Files selected for processing (6)
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/tests/sg/rf_test.cu
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/tests/test_random_forest.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • python/cuml/tests/test_random_forest.py
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • cpp/tests/sg/rf_test.cu

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Random forest and decision tree training now accept a flag to specify feature input layout (row-major vs column-major), including Treelite export.
  • Bug Fixes
    • Training now resets the model before fitting, and benchmarks no longer skip row-major inputs.
    • Dataset parameter formatting now appends clearer /row or /col suffixes.
  • Tests
    • Added parameterized coverage to confirm prediction parity between C- and F-contiguous inputs (NumPy/CuPy; float32/float64) and validate Treelite feature count.

Walkthrough

This PR adds row-major input support to Random Forest training, threads the layout flag through C++ and Python APIs, updates decision-tree and quantile internals, and expands benchmarks and tests to exercise both memory layouts.

Changes

Row-major training support

Layer / File(s) Summary
Public fit APIs and Python plumbing
cpp/include/cuml/ensemble/randomforest.hpp, cpp/src/randomforest/randomforest.cu, python/cuml/cuml/ensemble/randomforest_common.pyx, python/cuml/cuml/ensemble/randomforestclassifier.py, python/cuml/cuml/ensemble/randomforestregressor.py
Adds input_row_major to RandomForest fit signatures and bindings, forwards it through wrappers, and updates input-order preprocessing.
RandomForest and DecisionTree thread layout
cpp/src/randomforest/randomforest.cuh, cpp/src/decisiontree/decisiontree.cuh
RandomForest::fit and DecisionTree::fit now pass the layout flag into quantile computation and Builder construction.
Dataset, Builder, and quantiles support row-major data
cpp/src/decisiontree/batched-levelalgo/dataset.h, cpp/src/decisiontree/batched-levelalgo/builder.cuh, cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh, cpp/src/decisiontree/batched-levelalgo/quantiles.cuh
Dataset storage uses explicit row/column counts and strides, Builder and quantile sampling accept row-major input, and split kernels read through layout-aware accessors.
Benchmarks cover both layouts
cpp/bench/sg/dataset.cuh, cpp/bench/sg/rf_classifier.cu, cpp/bench/sg/rf_regressor.cu
Benchmarks emit both layout variants, clear trees before fit, pass the expanded fit arguments, and label dataset output with layout suffixes.
Random forest tests add layout parity coverage
cpp/tests/sg/rf_test.cu, python/cuml/tests/test_random_forest.py
RF tests and Python parity tests now exercise C-order and F-order inputs and validate the resulting models and predictions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • rapidsai/cuml#8132: Both PRs extend the RandomForest/DecisionTree C++ training stack by adding a new per-fit parameter threaded into the batched-levelalgo builder and kernel plumbing.
  • rapidsai/cuml#8171: Both PRs modify cpp/src/decisiontree/batched-levelalgo/quantiles.cuh and its computeQuantiles path.
  • rapidsai/cuml#8296: Both PRs modify the RandomForest public training APIs in randomforest.hpp/cuh.

Suggested labels: CMake

Suggested reviewers: hcho3, dantegd, aamijar

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: enabling row-major RandomForest training inputs.
Description check ✅ Passed The description is directly related to the changeset and accurately summarizes the layout support, API updates, and validation.
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.

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 (3)
cpp/src/decisiontree/batched-levelalgo/quantiles.cuh (1)

123-152: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

MEDIUM: Document the row_major data contract consistently.

The doc now says both layouts are supported, but @param data still says column-major only and the new row_major parameter is missing. This leaves the row-major shape/stride contract ambiguous.

Proposed fix
- * `@param` data Column-major input matrix with shape `[n_cols, n_rows]`.
+ * `@param` data Input matrix. When `row_major` is false, data is column-major with shape
+ * `[n_cols, n_rows]`; when `row_major` is true, data is row-major with shape
+ * `[n_rows, n_cols]`.
@@
- * `@param` seed User seed for deterministic sampling.
+ * `@param` seed User seed for deterministic sampling.
+ * `@param` row_major Whether `data` is row-major instead of column-major.

As per coding guidelines, “Function parameters with ambiguous data format (row-major or column-major) must be explicitly documented or validated at the function entry point.”

🤖 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/quantiles.cuh` around lines 123 - 152,
The computeQuantiles documentation is inconsistent about input layout: the
function now supports both column-major and row-major, but the existing `@param`
data text still describes only column-major and does not document row_major.
Update the computeQuantiles comment to explicitly describe the layout contract
for data, add a `@param` row_major entry, and clarify the expected shape/stride
semantics when row_major is true versus false so the API contract is
unambiguous.

Source: Coding guidelines

cpp/src/randomforest/randomforest.cuh (1)

265-266: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Same stale "column major format" doc issue as randomforest.cu.

The input doc doesn't reflect the new input_row_major switch added below.

Based on learnings, treat headers under cpp/include/cuml/ as internal implementation details backing the Python API, but ensure clear, minimal notes to aid future maintainers — this internal .cuh doc block should stay accurate for the same reason.

🤖 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 265 - 266, Update the
`input` parameter doc in `randomforest.cuh` so it matches the new
`input_row_major` behavior instead of always saying column major format. Keep
the note minimal but accurate in the `RandomForest`/`RandomForest`-related doc
block, and reference the existing `input` parameter description so future
maintainers understand that the device pointer may now be interpreted by
row-major or column-major mode depending on the switch.

Source: Learnings

cpp/src/randomforest/randomforest.cu (1)

334-350: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Doc for input param is now stale after adding input_row_major.

The @param[in] input doc still says "train data (n_rows samples, n_cols features) in column major format," excluding labels, but layout is now caller-selectable via the new input_row_major flag. Same wording exists in the regressor fit doc block (around line 594).

📝 Suggested doc fix
- * `@param`[in] input: train data (n_rows samples, n_cols features) in column major format,
- *   excluding labels. Device pointer.
+ * `@param`[in] input: train data (n_rows samples, n_cols features), column-major by default
+ *   or row-major when input_row_major is true, excluding labels. Device pointer.
🤖 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 334 - 350, Update the fit
API docs for RandomForestClassificationFit (and the matching regressor fit
block) so the `@param`[in] input description no longer claims column-major only;
describe the data layout as controlled by input_row_major and keep the existing
labels/excluding labels wording. Make sure the parameter docs around
input_row_major clearly state that input may be row-major or column-major
depending on that flag, so the comments stay consistent with the new API
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.

Inline comments:
In `@cpp/bench/sg/rf_regressor.cu`:
- Around line 48-61: The fit call in RandomForestRegressorF is passing device
containers instead of raw pointers, which will not match fit()’s float*/double*
signature. In the loopOnState lambda, update the fit invocation to use the
underlying raw buffers from this->data.X and this->data.y by calling their
data() accessors, while keeping the rest of the arguments in the same order. Use
the fit() call site in rf_regressor.cu as the target for this fix.

---

Outside diff comments:
In `@cpp/src/decisiontree/batched-levelalgo/quantiles.cuh`:
- Around line 123-152: The computeQuantiles documentation is inconsistent about
input layout: the function now supports both column-major and row-major, but the
existing `@param` data text still describes only column-major and does not
document row_major. Update the computeQuantiles comment to explicitly describe
the layout contract for data, add a `@param` row_major entry, and clarify the
expected shape/stride semantics when row_major is true versus false so the API
contract is unambiguous.

In `@cpp/src/randomforest/randomforest.cu`:
- Around line 334-350: Update the fit API docs for RandomForestClassificationFit
(and the matching regressor fit block) so the `@param`[in] input description no
longer claims column-major only; describe the data layout as controlled by
input_row_major and keep the existing labels/excluding labels wording. Make sure
the parameter docs around input_row_major clearly state that input may be
row-major or column-major depending on that flag, so the comments stay
consistent with the new API behavior.

In `@cpp/src/randomforest/randomforest.cuh`:
- Around line 265-266: Update the `input` parameter doc in `randomforest.cuh` so
it matches the new `input_row_major` behavior instead of always saying column
major format. Keep the note minimal but accurate in the
`RandomForest`/`RandomForest`-related doc block, and reference the existing
`input` parameter description so future maintainers understand that the device
pointer may now be interpreted by row-major or column-major mode depending on
the switch.
🪄 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: 7003f7c1-49d3-424c-a5d3-d3202bf92f3a

📥 Commits

Reviewing files that changed from the base of the PR and between 857cc5a and f421885.

📒 Files selected for processing (11)
  • cpp/bench/sg/dataset.cuh
  • cpp/bench/sg/rf_classifier.cu
  • cpp/bench/sg/rf_regressor.cu
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/decisiontree/batched-levelalgo/builder.cuh
  • cpp/src/decisiontree/batched-levelalgo/dataset.h
  • cpp/src/decisiontree/batched-levelalgo/kernels/builder_kernels_impl.cuh
  • cpp/src/decisiontree/batched-levelalgo/quantiles.cuh
  • cpp/src/decisiontree/decisiontree.cuh
  • cpp/src/randomforest/randomforest.cu
  • cpp/src/randomforest/randomforest.cuh

Comment thread cpp/bench/sg/rf_regressor.cu
@RAMitchell
RAMitchell force-pushed the codex/rf-layout-benchmark branch from f421885 to 820764b Compare July 6, 2026 13:04
@csadorf
csadorf requested a review from chyunsu3 July 6, 2026 21:47

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

Thanks for the prototype and benchmarks. Given the issue context, can you clarify the plan for the remaining user-facing pieces (Python/Treelite path)?

Additionally, a C/F input parity coverage would be necessary for this to be considered production-ready, since benchmarks alone won’t catch layout-specific correctness regressions.

@RAMitchell

Copy link
Copy Markdown
Contributor Author

@viclafargue thanks, I will strengthen tests. I don't think there are any changes to user facing parts. The prediction/treelite path is already row major. It will be unfortunately non-trivial to extend it to col major as well. For some reason training was col major only and prediction row major only so this is definitely an improvement.

@RAMitchell
RAMitchell requested a review from a team as a code owner July 7, 2026 11:33
@RAMitchell
RAMitchell requested a review from jcrist July 7, 2026 11:33
@github-actions github-actions Bot added the Cython / Python Cython or Python issue label Jul 7, 2026

@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 (2)
cpp/include/cuml/ensemble/randomforest.hpp (1)

170-183: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new input_row_major parameter for fit_treelite.

fit_treelite has no Doxygen @param documentation, and the new input_row_major parameter isn't described anywhere (the corresponding fit overloads do document it in randomforest.cu, but fit_treelite doesn't inherit that doc). Consider adding a short @param[in] input_row_major note to keep the public header self-describing.

As per path instructions, "For public C++ API headers, additionally check: Doxygen documentation for all public functions/classes."

Also applies to: 256-268

🤖 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 170 - 183, The
public API declaration of fit_treelite is missing Doxygen for the new
input_row_major parameter, so update the comment block for fit_treelite to add a
concise `@param`[in] input_row_major description alongside the existing
parameters. Make sure the documentation in randomforest.hpp is self-contained
and matches the meaning used by the fit overloads in randomforest.cu, so the
signature with user_handle, RF_params, and the optional
sample_weight/input_row_major arguments is fully documented.

Source: Path instructions

python/cuml/tests/test_random_forest.py (1)

300-336: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extending parity coverage to bootstrap=True + sample_weight.

Current tests only cover the deterministic, non-bootstrapped, unweighted path. Bootstrapping and weighted sampling exercise different kernels (row-sampling, weighted histograms) that could have layout-specific edge cases not caught here.

Also applies to: 338-374

🤖 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 `@python/cuml/tests/test_random_forest.py` around lines 300 - 336, Extend the
random forest parity tests to cover the bootstrap and weighted paths, since the
current fit-order parity only exercises the deterministic unweighted case. Add
coverage in test_rf_classifier_fit_input_order_parity (and the matching sibling
test) for bootstrap=True plus sample_weight so both C- and F-ordered inputs are
validated through the row-sampling and weighted histogram code paths. Keep the
same assertion pattern by fitting curfc with the new parameters and comparing
predictions from c_model and f_model on the same X_pred.
🤖 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/include/cuml/ensemble/randomforest.hpp`:
- Around line 170-183: The public API declaration of fit_treelite is missing
Doxygen for the new input_row_major parameter, so update the comment block for
fit_treelite to add a concise `@param`[in] input_row_major description alongside
the existing parameters. Make sure the documentation in randomforest.hpp is
self-contained and matches the meaning used by the fit overloads in
randomforest.cu, so the signature with user_handle, RF_params, and the optional
sample_weight/input_row_major arguments is fully documented.

In `@python/cuml/tests/test_random_forest.py`:
- Around line 300-336: Extend the random forest parity tests to cover the
bootstrap and weighted paths, since the current fit-order parity only exercises
the deterministic unweighted case. Add coverage in
test_rf_classifier_fit_input_order_parity (and the matching sibling test) for
bootstrap=True plus sample_weight so both C- and F-ordered inputs are validated
through the row-sampling and weighted histogram code paths. Keep the same
assertion pattern by fitting curfc with the new parameters and comparing
predictions from c_model and f_model on the same X_pred.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4142525f-c08e-4a7e-be82-21926fd8e6a4

📥 Commits

Reviewing files that changed from the base of the PR and between 820764b and 9081fd2.

📒 Files selected for processing (8)
  • cpp/bench/sg/rf_regressor.cu
  • cpp/include/cuml/ensemble/randomforest.hpp
  • cpp/src/randomforest/randomforest.cu
  • cpp/tests/sg/rf_test.cu
  • python/cuml/cuml/ensemble/randomforest_common.pyx
  • python/cuml/cuml/ensemble/randomforestclassifier.py
  • python/cuml/cuml/ensemble/randomforestregressor.py
  • python/cuml/tests/test_random_forest.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • cpp/bench/sg/rf_regressor.cu

@jcrist jcrist left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving since implementation looks good to me!

I left a few comments on nits (most of them in test conciseness), feel free to do with them what you see fit.

Comment thread python/cuml/cuml/ensemble/randomforest_common.pyx Outdated
Comment thread python/cuml/tests/test_random_forest.py Outdated
Comment thread python/cuml/tests/test_random_forest.py Outdated
Comment thread python/cuml/tests/test_random_forest.py Outdated
Comment thread python/cuml/tests/test_random_forest.py Outdated
@RAMitchell

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit 165f978 into NVIDIA:main Jul 10, 2026
102 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CUDA/C++ Cython / Python Cython or Python issue 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.

6 participants