Skip to content

Merge raft_proto into nvforest namespace - #132

Merged
rapids-bot[bot] merged 14 commits into
rapidsai:mainfrom
chyunsu3:merge_raft_proto
Jul 16, 2026
Merged

rapids-bot[bot] merged 14 commits into
rapidsai:mainfrom
chyunsu3:merge_raft_proto

Conversation

@chyunsu3

Copy link
Copy Markdown
Contributor

Closes #124
Closes #129

@chyunsu3
chyunsu3 requested review from a team as code owners May 25, 2026 03:30
@chyunsu3
chyunsu3 requested a review from KyleFromNVIDIA May 25, 2026 03:30
@chyunsu3 chyunsu3 added breaking Introduces a breaking change improvement Improves an existing functionality and removed conda Relates to conda packaging ci Cython / Python CMake CUDA/C++ labels May 25, 2026
@coderabbitai

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

Changes

Namespace and library migration

Layer / File(s) Summary
Public API and data contracts
cpp/include/nvforest/*.hpp
Public C++ types, buffers, handles, streams, exceptions, forest APIs, and Treelite importer signatures move from raft_proto to nvforest.
Internal device and inference utilities
cpp/include/nvforest/detail/*, cpp/src/infer*
Internal device, CUDA, buffer, kernel, and inference specialization wiring is updated to use the new namespaces and target types.
Bindings, packaging, and validation
python/*, cpp/tests/*, cpp/CMakeLists.txt, ci/*, conda/*
Cython bindings, library loading, build targets, wheel/Conda configuration, tests, and generated inference instantiations are updated for nvforest and libnvforest.so.
Documentation and examples
docs/source/*, cpp/include/nvforest/*.md
Build instructions, C++ examples, and API documentation replace raft_proto, nvforest++, and libnvforest++ references.

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

Suggested reviewers: kylefromnvidia, fallintoplace

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Some unrelated changes remain, including SPDX copyright header edits and a pyproject.toml line-length bump. Split incidental license/config changes into a separate PR or document why they are required for #124/#129.
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main namespace merge in the PR.
Description check ✅ Passed The description is related to the PR because it references the two linked issues being closed.
Linked Issues check ✅ Passed The diff fulfills #124 and #129 by moving raft_proto APIs into nvforest and renaming the CMake target/library.
✨ 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: 7

Caution

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

⚠️ Outside diff range comments (8)
cpp/include/nvforest/cuda_stream.hpp (1)

16-20: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate CUDA synchronization failures from nvforest::synchronize

  • cpp/include/nvforest/cuda_stream.hpp calls cudaStreamSynchronize(stream) and drops the returned cudaError_t, making CUDA failures silent at the API boundary (Line 19).
Suggested fix
 `#ifdef` NVFOREST_ENABLE_GPU
 `#include` <cuda_runtime_api.h>
+#include <nvforest/detail/cuda_check.hpp>
 `#endif`
@@
 `#ifdef` NVFOREST_ENABLE_GPU
-  cudaStreamSynchronize(stream);
+  detail::cuda_check<device_type::gpu>(cudaStreamSynchronize(stream));
 `#endif`
 }
🤖 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/nvforest/cuda_stream.hpp` around lines 16 - 20, The synchronize
function currently ignores the cudaStreamSynchronize return value, so CUDA
errors are dropped; update inline void synchronize(cuda_stream stream) to
capture the cudaError_t result from cudaStreamSynchronize, check it, and
propagate failures (either change the signature to return cudaError_t or throw a
std::runtime_error on non-success). Concretely, in synchronize call cudaError_t
err = cudaStreamSynchronize(stream); if (err != cudaSuccess) throw
std::runtime_error(std::string("cudaStreamSynchronize failed: ") +
cudaGetErrorString(err)); — reference symbols: synchronize,
cudaStreamSynchronize, cudaError_t, cudaGetErrorString.
cpp/include/nvforest/detail/exceptions.hpp (1)

8-56: ⚠️ Potential issue | 🟠 Major

Restore public exception names (nvforest::bad_cuda_call, etc.) or provide aliases in nvforest/exceptions.hpp.

cpp/include/nvforest/exceptions.hpp defines only nvforest::{model_import_error,type_error,traversal_exception,runtime_error} and does not re-export/alias nvforest::detail::{bad_cuda_call,out_of_bounds,wrong_device_type,mem_type_mismatch,wrong_device}. This makes the namespace move a downstream-breaking exception API change rather than a merge.

🤖 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/nvforest/detail/exceptions.hpp` around lines 8 - 56, The public
API lost symbols when exception types were moved into nvforest::detail; restore
the original nvforest::* names by adding aliases in the public header
(nvforest/exceptions.hpp): create using declarations that map
nvforest::bad_cuda_call, out_of_bounds, wrong_device_type, mem_type_mismatch,
and wrong_device to their nvforest::detail:: counterparts (e.g., using
bad_cuda_call = detail::bad_cuda_call;), ensure the header includes or
forward-declares nvforest::detail types so the aliases compile, and keep the
original exception type names visible in the nvforest namespace to avoid
breaking downstream code.
cpp/include/nvforest/detail/infer/gpu.hpp (1)

13-24: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing <type_traits> include for std::enable_if_t

cpp/include/nvforest/detail/infer/gpu.hpp uses std::enable_if_t in the infer template declaration but doesn’t include <type_traits>, relying on transitive includes.

Proposed fix
 `#include` <cstddef>
 `#include` <optional>
+#include <type_traits>
🤖 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/nvforest/detail/infer/gpu.hpp` around lines 13 - 24, The header
declares the infer template using std::enable_if_t (template signature with
device_type D and infer(...)) but omits the required <type_traits> include; add
`#include` <type_traits> near the other includes so std::enable_if_t is defined
(ensuring the declaration of template <device_type D, bool
has_categorical_nodes, typename forest_t, ...> std::enable_if_t<D ==
device_type::gpu, void> infer(...) compiles without relying on transitive
includes).
cpp/include/nvforest/handle.hpp (1)

16-29: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject null GPU raft_handle_ in nvforest::handle_t

In NVFOREST_ENABLE_GPU builds, nvforest::handle_t’s constructor defaults handle_ptr to nullptr, but get_next_usable_stream(), get_stream_pool_size(), and synchronize() dereference raft_handle_ unconditionally. forest_model::predict() calls handle.get_next_usable_stream() / handle.get_usable_stream_count() without guarding, so nvforest::handle_t{} will crash on the first inference. Tests use a real raft::handle_t, but the README shows auto handle = nvforest::handle_t{};, which is unsafe for GPU builds.

Proposed fix
+#include <stdexcept>
+
 struct handle_t {
-  handle_t(raft::handle_t const* handle_ptr = nullptr) : raft_handle_{handle_ptr} {}
+  handle_t() = delete;
+  explicit handle_t(raft::handle_t const* handle_ptr) : raft_handle_{handle_ptr}
+  {
+    if (handle_ptr == nullptr) { throw std::invalid_argument("handle_ptr must not be null"); }
+  }
   handle_t(raft::handle_t const& raft_handle) : raft_handle_{&raft_handle} {}

Also update the public docs/README to reflect GPU builds must construct nvforest::handle_t from a valid raft::handle_t.

🤖 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/nvforest/handle.hpp` around lines 16 - 29, The constructor
handle_t(raft::handle_t const* handle_ptr = nullptr) currently allows a null
raft_handle_ but methods get_next_usable_stream, get_stream_pool_size,
get_usable_stream_count and synchronize dereference it unconditionally; change
the ctor to reject null (e.g., remove the default nullptr and/or throw
std::invalid_argument or assert when handle_ptr==nullptr) so a
nvforest::handle_t must be constructed from a valid raft::handle_t, and update
the public README to state GPU builds require constructing nvforest::handle_t
from an existing raft::handle_t (also audit usages of nvforest::handle_t{} in
tests/docs and replace with an explicit valid raft::handle_t where needed).
cpp/include/nvforest/README.md (1)

46-70: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Unify parameter naming for the device-type argument.

Line 46 labels the argument as mem_type, but Line 70 documents it as dev_type. Please use one name consistently in both the example and the argument docs.

Suggested doc tweak
-**dev_type**: This argument controls where the model will be executed. If `nvforest::device_type::gpu`, then it will be executed on GPU. If `nvforest::device_type::cpu`, then it will be executed on CPU.
+**mem_type**: This argument controls where the model will be executed. If `nvforest::device_type::gpu`, then it will be executed on GPU. If `nvforest::device_type::cpu`, then it will be executed on CPU.

As per coding guidelines, "Consistency: Version numbers, parameter types, and terminology match code".

🤖 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/nvforest/README.md` around lines 46 - 70, The docs use two
different names for the device argument—`mem_type` in the example call comment
and `dev_type` in the parameter description—so make them consistent: pick one
name (prefer `dev_type`) and update the example comment `// mem_type` to `//
dev_type` and any occurrences in the descriptive section (`mem_type` ->
`dev_type`), ensuring references like `nvforest::device_type::gpu` remain
unchanged and the parameter description that begins "dev_type: This argument
controls where the model will be executed..." matches the example and wording
elsewhere.
cpp/include/nvforest/treelite_importer.hpp (1)

515-529: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard against a null TreeliteModelHandle.

This wrapper dereferences tl_handle unconditionally. If a caller passes nullptr, the public API segfaults instead of raising a model import error.

Suggested fix
 inline auto import_from_treelite_handle(TreeliteModelHandle tl_handle,
                                         tree_layout layout     = preferred_tree_layout,
                                         index_type align_bytes = index_type{},
                                         std::optional<bool> use_double_precision = std::nullopt,
                                         nvforest::device_type dev_type = nvforest::device_type::cpu,
                                         int device                     = 0,
                                         nvforest::cuda_stream stream   = nvforest::cuda_stream{})
 {
+  if (tl_handle == nullptr) {
+    throw model_import_error{"Treelite model handle must not be null"};
+  }
   return import_from_treelite_model(*static_cast<treelite::Model*>(tl_handle),
                                     layout,
                                     align_bytes,
                                     use_double_precision,
                                     dev_type,
                                     device,
                                     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/include/nvforest/treelite_importer.hpp` around lines 515 - 529, The
wrapper import_from_treelite_handle dereferences tl_handle without checking for
nullptr; add a null check at the start of import_from_treelite_handle and if
tl_handle is null throw a suitable exception (e.g. std::invalid_argument or a
library-specific import error) with a clear message like "null
TreeliteModelHandle"; only call
import_from_treelite_model(*static_cast<treelite::Model*>(tl_handle), ...) when
tl_handle is non-null to avoid segfaults.
cpp/include/nvforest/buffer.hpp (1)

329-339: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

The checked copy guard underflows when an offset is already out of range.

These comparisons subtract size_t values before proving that src_offset <= src.size() and dst_offset <= dst.size(). If either offset is already too large, the subtraction wraps and the check passes, turning the “checked” overloads into out-of-bounds copies.

Suggested fix
   if constexpr (bounds_check) {
-    if (src.size() - src_offset < size || dst.size() - dst_offset < size) {
+    if (src_offset > src.size() || dst_offset > dst.size() ||
+        src.size() - src_offset < size || dst.size() - dst_offset < size) {
       throw detail::out_of_bounds("Attempted copy to or from buffer of inadequate size");
     }
   }

Apply the same guard to the rvalue-overload block below as well.

Also applies to: 361-372

🤖 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/nvforest/buffer.hpp` around lines 329 - 339, The bounds check in
the checked copy path incorrectly subtracts size_t values (src.size() -
src_offset and dst.size() - dst_offset) before verifying the offsets are in
range, which underflows when src_offset or dst_offset > src.size()/dst.size();
update the guard in the copy(...) path to first assert src_offset <= src.size()
and dst_offset <= dst.size() (or check offsets against sizes) and only then
compare remaining sizes against size, and apply the identical fix to the
rvalue-overload block (the subsequent copy(...) overload around the rvalue
overload lines) so both checked overloads prevent underflow.
cpp/include/nvforest/detail/infer/gpu.cuh (1)

153-163: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle the zero-shared-memory path before the occupancy math.

shared_mem_per_block can be 0 here when output falls back to global workspace and rows are not staged in shared memory. In that case ceildiv(max_shared_mem_per_sm, shared_mem_per_block) divides by zero, and the later "divide shared mem evenly" step has the same problem.

Suggested fix
-  auto resident_blocks_per_sm =
-    min(ceildiv(max_shared_mem_per_sm, shared_mem_per_block), max_resident_blocks);
+  auto resident_blocks_per_sm =
+    (shared_mem_per_block == 0)
+      ? max_resident_blocks
+      : min(ceildiv(max_shared_mem_per_sm, shared_mem_per_block), max_resident_blocks);
@@
-  shared_mem_per_block = std::min(
-    max_overall_shared_mem, max_shared_mem_per_sm / (max_shared_mem_per_sm / shared_mem_per_block));
+  if (shared_mem_per_block != 0) {
+    shared_mem_per_block = std::min(
+      max_overall_shared_mem,
+      max_shared_mem_per_sm / (max_shared_mem_per_sm / shared_mem_per_block));
+  }
🤖 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/nvforest/detail/infer/gpu.cuh` around lines 153 - 163,
shared_mem_per_block can be zero (when output_workspace_size_bytes and row
staging were cleared), so the occupancy math calling
ceildiv(max_shared_mem_per_sm, shared_mem_per_block) and the later "divide
shared mem evenly" step will divide by zero; add an early guard after computing
shared_mem_per_block to detect shared_mem_per_block == 0 and handle that path
explicitly (for example set resident_blocks_per_sm = max_resident_blocks or
another safe default and skip any further divisions that distribute shared
memory per block), and ensure any subsequent logic that divides by
shared_mem_per_block or expects nonzero shared bytes checks this flag; update
references around shared_mem_per_block, resident_blocks_per_sm, ceildiv, and the
later "divide shared mem evenly" calculations to use this guarded path.
🧹 Nitpick comments (3)
cpp/include/nvforest/device_type.hpp (1)

7-7: ⚡ Quick win

Add Doxygen docs for the new public device_type enum.

Line 7 introduces a public API type without a doc comment. Please add a brief Doxygen block describing valid values and intended usage.

Suggested update
 namespace nvforest {
+/**
+ * `@brief` Execution target for inference routines.
+ */
 enum class device_type { cpu, gpu };
 }  // namespace nvforest

As per coding guidelines, "For public header files (C++ API): Check if new public functions/classes have documentation comments (Doxygen format)".

🤖 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/nvforest/device_type.hpp` at line 7, Add a Doxygen comment block
immediately above the public enum class device_type describing that it
represents the execution device for NVForest operations, listing the valid enum
values (cpu and gpu) with a short description for each, and note intended usage
(e.g., selecting target device for computation or resource allocation). Use
brief `@enum/`@brief style tags and mention any default behavior or constraints if
applicable so the public API is documented for users.
cpp/include/nvforest/detail/device_setter.hpp (1)

6-10: 💤 Low value

Consider reordering includes to place dependencies first.

The include order places device_type.hpp after device_setter/base.hpp and device_setter/gpu.hpp. If these headers depend on device_type, it would be clearer to include device_type.hpp first.

♻️ Suggested include order
 `#pragma` once
+#include <nvforest/device_type.hpp>
 `#include` <nvforest/detail/device_setter/base.hpp>
 `#ifdef` NVFOREST_ENABLE_GPU
 `#include` <nvforest/detail/device_setter/gpu.hpp>
 `#endif`
-#include <nvforest/device_type.hpp>
🤖 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/nvforest/detail/device_setter.hpp` around lines 6 - 10, Reorder
the includes in device_setter.hpp so that the dependency header device_type.hpp
is included before device_setter/base.hpp and device_setter/gpu.hpp;
specifically, move the include for nvforest/device_type.hpp above the includes
for nvforest/detail/device_setter/base.hpp and (conditionally)
nvforest/detail/device_setter/gpu.hpp to ensure device_type is available to the
base and gpu headers (symbols: device_setter.hpp, device_setter::base,
device_setter::gpu, and device_type.hpp).
cpp/include/nvforest/detail/specializations/device_initialization_macros.hpp (1)

11-13: ⚡ Quick win

Fully qualify the generated nvforest types.

This macro now depends on the expansion site already being inside namespace nvforest. Emitting ::nvforest::device_type and ::nvforest::device_id keeps it stable if an instantiation moves or lives at global scope.

Proposed fix
 `#define` NVFOREST_INITIALIZE_DEVICE(template_type, variant_index)                          \
-  template_type void initialize_device<NVFOREST_FOREST(variant_index), device_type::gpu>( \
-    device_id<device_type::gpu>);
+  template_type void                                                                     \
+  initialize_device<NVFOREST_FOREST(variant_index), ::nvforest::device_type::gpu>(      \
+    ::nvforest::device_id<::nvforest::device_type::gpu>);
🤖 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/nvforest/detail/specializations/device_initialization_macros.hpp`
around lines 11 - 13, The macro NVFOREST_INITIALIZE_DEVICE emits types like
device_type and device_id without global qualification, which breaks if the
expansion isn't inside namespace nvforest; update the macro so generated symbols
in initialize_device<NVFOREST_FOREST(variant_index),
device_type::gpu>(device_id<device_type::gpu>) are fully qualified (e.g.,
::nvforest::device_type and ::nvforest::device_id and any other nvforest symbols
referenced) so the instantiation is stable regardless of the expansion scope.
🤖 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/CMakeLists.txt`:
- Line 126: The exported CMake target name changed and you need to restore a
deprecated compatibility alias so downstream projects linking
nvforest::nvforest++ keep working: after NVFOREST_CPP_TARGET is set and after
the existing add_library(nvforest::${NVFOREST_CPP_TARGET} ALIAS
${NVFOREST_CPP_TARGET}) line, create a second alias target mapping
nvforest::nvforest++ to the same implementation target (i.e., add an alias
nvforest::nvforest++ that points to ${NVFOREST_CPP_TARGET}) and ensure the alias
is included in the rapids_export/GLOBAL_TARGETS (or the package config) so the
installed package exports the old name as well; keep a clear deprecation comment
and only add the alias (no API changes).

In `@cpp/include/nvforest/decision_forest.hpp`:
- Around line 460-463: The categorical index-size check in
double_indexes_required uses (detail::ceildiv(max_num_categories,
max_local_categories) + 1 * num_categorical_nodes) which computes bins_per_node
+ num_categorical_nodes instead of (bins_per_node + 1) * num_categorical_nodes;
update the expression in the double_indexes_required calculation to multiply the
entire (bins_per_node + 1) factor by num_categorical_nodes (i.e. use
(detail::ceildiv(max_num_categories, max_local_categories) + 1) *
num_categorical_nodes) so the comparison against
std::numeric_limits<small_index_t>::max() correctly reflects required index
range for categorical storage.
- Around line 149-163: The decision_forest constructor currently accepts rvalue
references (nodes, root_node_indexes, node_id_mapping, bias,
optional<vector_output>, optional<categorical_storage>) but then copies them
because the member initializer uses the parameter names as lvalues; change the
initializer to transfer ownership by moving these parameters into the members
(e.g., use std::move(nodes), std::move(root_node_indexes),
std::move(node_id_mapping), std::move(bias) and std::move on the optionals like
vector_output and categorical_storage) or switch the signature to take by value
and move from those local variables into members; update the member initializers
in the decision_forest constructor accordingly so buffers are moved, not
deep-copied.

In `@cpp/include/nvforest/detail/device_initialization/gpu.cuh`:
- Around line 40-201: The repeated cudaFuncSetAttribute calls for many
infer_kernel specializations are brittle; create a compile-time helper (e.g., a
templated helper function named set_infer_kernel_max_shared<...>) that accepts
the same template parameters as infer_kernel and calls
cudaFuncSetAttribute(infer_kernel<...>,
cudaFuncAttributeMaxDynamicSharedMemorySize, max_shared_mem_per_block); then
replace the long list with a small set of invocations of that helper (or a
constexpr list/pack of kernel-parameter tuples expanded via fold/pack expansion)
for the combinations used. Refer to infer_kernel, cudaFuncSetAttribute,
cudaFuncAttributeMaxDynamicSharedMemorySize, and max_shared_mem_per_block when
implementing the templated set_infer_kernel_max_shared helper and the pack/array
expansion so behavior remains identical.

In `@cpp/include/nvforest/detail/infer/gpu.cuh`:
- Around line 190-193: The code computes num_blocks from row_count and proceeds
to allocate global_workspace and launch kernels even when row_count == 0; add an
early return guard in the function (in gpu.cuh around the block using
num_blocks/row_output_size/global_workspace) that checks if row_count == 0 (or
num_blocks == 0) and returns immediately before any buffer<output_t> allocation
or kernel launch; apply the same guard to the later region noted (lines
~229-320) so no CUDA allocations or kernel launches occur with zero grid/block
dimensions.

In `@cpp/include/nvforest/detail/specializations/forest_macros.hpp`:
- Around line 17-18: The macro NVFOREST_SPEC expands to an unqualified
specialization_variant which causes scope-dependent failures; update the macro
definition so it references the fully-qualified type (e.g.,
::nvforest::detail::specialization_variant) instead of the bare name so
expansions work from any namespace or translation unit; locate NVFOREST_SPEC in
this header and replace the unqualified specialization_variant with the
fully-qualified symbol.

In `@python/nvforest/nvforest/detail/forest_inference.pyx`:
- Around line 105-106: Validate the incoming device string before mapping to
nvforest_device_t: only accept "gpu" or "cpu" and if it's anything else raise a
clear Python error (e.g., ValueError) that includes the actual value and the
expected options; then set dev_type = nvforest_device_t.gpu if device == "gpu"
else nvforest_device_t.cpu. Update the mapping around the dev_type variable in
forest_inference.pyx (where device and nvforest_device_t are used) so invalid
inputs fail early instead of silently falling back to CPU and causing assertions
later in predict().

---

Outside diff comments:
In `@cpp/include/nvforest/buffer.hpp`:
- Around line 329-339: The bounds check in the checked copy path incorrectly
subtracts size_t values (src.size() - src_offset and dst.size() - dst_offset)
before verifying the offsets are in range, which underflows when src_offset or
dst_offset > src.size()/dst.size(); update the guard in the copy(...) path to
first assert src_offset <= src.size() and dst_offset <= dst.size() (or check
offsets against sizes) and only then compare remaining sizes against size, and
apply the identical fix to the rvalue-overload block (the subsequent copy(...)
overload around the rvalue overload lines) so both checked overloads prevent
underflow.

In `@cpp/include/nvforest/cuda_stream.hpp`:
- Around line 16-20: The synchronize function currently ignores the
cudaStreamSynchronize return value, so CUDA errors are dropped; update inline
void synchronize(cuda_stream stream) to capture the cudaError_t result from
cudaStreamSynchronize, check it, and propagate failures (either change the
signature to return cudaError_t or throw a std::runtime_error on non-success).
Concretely, in synchronize call cudaError_t err = cudaStreamSynchronize(stream);
if (err != cudaSuccess) throw
std::runtime_error(std::string("cudaStreamSynchronize failed: ") +
cudaGetErrorString(err)); — reference symbols: synchronize,
cudaStreamSynchronize, cudaError_t, cudaGetErrorString.

In `@cpp/include/nvforest/detail/exceptions.hpp`:
- Around line 8-56: The public API lost symbols when exception types were moved
into nvforest::detail; restore the original nvforest::* names by adding aliases
in the public header (nvforest/exceptions.hpp): create using declarations that
map nvforest::bad_cuda_call, out_of_bounds, wrong_device_type,
mem_type_mismatch, and wrong_device to their nvforest::detail:: counterparts
(e.g., using bad_cuda_call = detail::bad_cuda_call;), ensure the header includes
or forward-declares nvforest::detail types so the aliases compile, and keep the
original exception type names visible in the nvforest namespace to avoid
breaking downstream code.

In `@cpp/include/nvforest/detail/infer/gpu.cuh`:
- Around line 153-163: shared_mem_per_block can be zero (when
output_workspace_size_bytes and row staging were cleared), so the occupancy math
calling ceildiv(max_shared_mem_per_sm, shared_mem_per_block) and the later
"divide shared mem evenly" step will divide by zero; add an early guard after
computing shared_mem_per_block to detect shared_mem_per_block == 0 and handle
that path explicitly (for example set resident_blocks_per_sm =
max_resident_blocks or another safe default and skip any further divisions that
distribute shared memory per block), and ensure any subsequent logic that
divides by shared_mem_per_block or expects nonzero shared bytes checks this
flag; update references around shared_mem_per_block, resident_blocks_per_sm,
ceildiv, and the later "divide shared mem evenly" calculations to use this
guarded path.

In `@cpp/include/nvforest/detail/infer/gpu.hpp`:
- Around line 13-24: The header declares the infer template using
std::enable_if_t (template signature with device_type D and infer(...)) but
omits the required <type_traits> include; add `#include` <type_traits> near the
other includes so std::enable_if_t is defined (ensuring the declaration of
template <device_type D, bool has_categorical_nodes, typename forest_t, ...>
std::enable_if_t<D == device_type::gpu, void> infer(...) compiles without
relying on transitive includes).

In `@cpp/include/nvforest/handle.hpp`:
- Around line 16-29: The constructor handle_t(raft::handle_t const* handle_ptr =
nullptr) currently allows a null raft_handle_ but methods
get_next_usable_stream, get_stream_pool_size, get_usable_stream_count and
synchronize dereference it unconditionally; change the ctor to reject null
(e.g., remove the default nullptr and/or throw std::invalid_argument or assert
when handle_ptr==nullptr) so a nvforest::handle_t must be constructed from a
valid raft::handle_t, and update the public README to state GPU builds require
constructing nvforest::handle_t from an existing raft::handle_t (also audit
usages of nvforest::handle_t{} in tests/docs and replace with an explicit valid
raft::handle_t where needed).

In `@cpp/include/nvforest/README.md`:
- Around line 46-70: The docs use two different names for the device
argument—`mem_type` in the example call comment and `dev_type` in the parameter
description—so make them consistent: pick one name (prefer `dev_type`) and
update the example comment `// mem_type` to `// dev_type` and any occurrences in
the descriptive section (`mem_type` -> `dev_type`), ensuring references like
`nvforest::device_type::gpu` remain unchanged and the parameter description that
begins "dev_type: This argument controls where the model will be executed..."
matches the example and wording elsewhere.

In `@cpp/include/nvforest/treelite_importer.hpp`:
- Around line 515-529: The wrapper import_from_treelite_handle dereferences
tl_handle without checking for nullptr; add a null check at the start of
import_from_treelite_handle and if tl_handle is null throw a suitable exception
(e.g. std::invalid_argument or a library-specific import error) with a clear
message like "null TreeliteModelHandle"; only call
import_from_treelite_model(*static_cast<treelite::Model*>(tl_handle), ...) when
tl_handle is non-null to avoid segfaults.

---

Nitpick comments:
In `@cpp/include/nvforest/detail/device_setter.hpp`:
- Around line 6-10: Reorder the includes in device_setter.hpp so that the
dependency header device_type.hpp is included before device_setter/base.hpp and
device_setter/gpu.hpp; specifically, move the include for
nvforest/device_type.hpp above the includes for
nvforest/detail/device_setter/base.hpp and (conditionally)
nvforest/detail/device_setter/gpu.hpp to ensure device_type is available to the
base and gpu headers (symbols: device_setter.hpp, device_setter::base,
device_setter::gpu, and device_type.hpp).

In
`@cpp/include/nvforest/detail/specializations/device_initialization_macros.hpp`:
- Around line 11-13: The macro NVFOREST_INITIALIZE_DEVICE emits types like
device_type and device_id without global qualification, which breaks if the
expansion isn't inside namespace nvforest; update the macro so generated symbols
in initialize_device<NVFOREST_FOREST(variant_index),
device_type::gpu>(device_id<device_type::gpu>) are fully qualified (e.g.,
::nvforest::device_type and ::nvforest::device_id and any other nvforest symbols
referenced) so the instantiation is stable regardless of the expansion scope.

In `@cpp/include/nvforest/device_type.hpp`:
- Line 7: Add a Doxygen comment block immediately above the public enum class
device_type describing that it represents the execution device for NVForest
operations, listing the valid enum values (cpu and gpu) with a short description
for each, and note intended usage (e.g., selecting target device for computation
or resource allocation). Use brief `@enum/`@brief style tags and mention any
default behavior or constraints if applicable so the public API is documented
for users.
🪄 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: 5877b175-08a8-4c41-81f5-f28cab06a27f

📥 Commits

Reviewing files that changed from the base of the PR and between a9defae and 10b6f20.

📒 Files selected for processing (107)
  • build.sh
  • ci/build_wheel_nvforest.sh
  • conda/recipes/libnvforest/recipe.yaml
  • cpp/CMakeLists.txt
  • cpp/include/nvforest/Implementation.md
  • cpp/include/nvforest/README.md
  • cpp/include/nvforest/buffer.hpp
  • cpp/include/nvforest/cuda_stream.hpp
  • cpp/include/nvforest/decision_forest.hpp
  • cpp/include/nvforest/detail/bitset.hpp
  • cpp/include/nvforest/detail/ceildiv.hpp
  • cpp/include/nvforest/detail/const_agnostic.hpp
  • cpp/include/nvforest/detail/copy.hpp
  • cpp/include/nvforest/detail/copy/cpu.hpp
  • cpp/include/nvforest/detail/copy/gpu.hpp
  • cpp/include/nvforest/detail/cuda_check.hpp
  • cpp/include/nvforest/detail/cuda_check/base.hpp
  • cpp/include/nvforest/detail/cuda_check/gpu.hpp
  • cpp/include/nvforest/detail/decision_forest_builder.hpp
  • cpp/include/nvforest/detail/device_id.hpp
  • cpp/include/nvforest/detail/device_id/base.hpp
  • cpp/include/nvforest/detail/device_id/cpu.hpp
  • cpp/include/nvforest/detail/device_id/gpu.hpp
  • cpp/include/nvforest/detail/device_initialization.hpp
  • cpp/include/nvforest/detail/device_initialization/cpu.hpp
  • cpp/include/nvforest/detail/device_initialization/gpu.cuh
  • cpp/include/nvforest/detail/device_initialization/gpu.hpp
  • cpp/include/nvforest/detail/device_setter.hpp
  • cpp/include/nvforest/detail/device_setter/base.hpp
  • cpp/include/nvforest/detail/device_setter/gpu.hpp
  • cpp/include/nvforest/detail/evaluate_tree.hpp
  • cpp/include/nvforest/detail/exceptions.hpp
  • cpp/include/nvforest/detail/forest.hpp
  • cpp/include/nvforest/detail/gpu_introspection.hpp
  • cpp/include/nvforest/detail/gpu_support.hpp
  • cpp/include/nvforest/detail/host_only_throw.hpp
  • cpp/include/nvforest/detail/host_only_throw/base.hpp
  • cpp/include/nvforest/detail/host_only_throw/cpu.hpp
  • cpp/include/nvforest/detail/infer.hpp
  • cpp/include/nvforest/detail/infer/cpu.hpp
  • cpp/include/nvforest/detail/infer/gpu.cuh
  • cpp/include/nvforest/detail/infer/gpu.hpp
  • cpp/include/nvforest/detail/infer_kernel/cpu.hpp
  • cpp/include/nvforest/detail/infer_kernel/gpu.cuh
  • cpp/include/nvforest/detail/node.hpp
  • cpp/include/nvforest/detail/non_owning_buffer.hpp
  • cpp/include/nvforest/detail/non_owning_buffer/base.hpp
  • cpp/include/nvforest/detail/owning_buffer.hpp
  • cpp/include/nvforest/detail/owning_buffer/base.hpp
  • cpp/include/nvforest/detail/owning_buffer/cpu.hpp
  • cpp/include/nvforest/detail/owning_buffer/gpu.hpp
  • cpp/include/nvforest/detail/padding.hpp
  • cpp/include/nvforest/detail/postprocessor.hpp
  • cpp/include/nvforest/detail/raft_proto/cuda_check.hpp
  • cpp/include/nvforest/detail/raft_proto/detail/const_agnostic.hpp
  • cpp/include/nvforest/detail/raft_proto/detail/host_only_throw.hpp
  • cpp/include/nvforest/detail/raft_proto/detail/non_owning_buffer.hpp
  • cpp/include/nvforest/detail/raft_proto/detail/owning_buffer.hpp
  • cpp/include/nvforest/detail/raft_proto/device_id.hpp
  • cpp/include/nvforest/detail/raft_proto/device_setter.hpp
  • cpp/include/nvforest/detail/raft_proto/device_type.hpp
  • cpp/include/nvforest/detail/specializations/device_initialization_macros.hpp
  • cpp/include/nvforest/detail/specializations/forest_macros.hpp
  • cpp/include/nvforest/detail/specializations/infer_macros.hpp
  • cpp/include/nvforest/device_type.hpp
  • cpp/include/nvforest/forest_model.hpp
  • cpp/include/nvforest/handle.hpp
  • cpp/include/nvforest/treelite_importer.hpp
  • cpp/src/infer0.cpp
  • cpp/src/infer0.cu
  • cpp/src/infer1.cpp
  • cpp/src/infer1.cu
  • cpp/src/infer10.cpp
  • cpp/src/infer10.cu
  • cpp/src/infer11.cpp
  • cpp/src/infer11.cu
  • cpp/src/infer2.cpp
  • cpp/src/infer2.cu
  • cpp/src/infer3.cpp
  • cpp/src/infer3.cu
  • cpp/src/infer4.cpp
  • cpp/src/infer4.cu
  • cpp/src/infer5.cpp
  • cpp/src/infer5.cu
  • cpp/src/infer6.cpp
  • cpp/src/infer6.cu
  • cpp/src/infer7.cpp
  • cpp/src/infer7.cu
  • cpp/src/infer8.cpp
  • cpp/src/infer8.cu
  • cpp/src/infer9.cpp
  • cpp/src/infer9.cu
  • cpp/tests/CMakeLists.txt
  • cpp/tests/buffer/buffer.cpp
  • cpp/tests/buffer/buffer.cu
  • cpp/tests/treelite_importer.cpp
  • docs/source/getting_started.rst
  • python/libnvforest/CMakeLists.txt
  • python/libnvforest/libnvforest/load.py
  • python/nvforest/CMakeLists.txt
  • python/nvforest/nvforest/detail/cuda_stream.pxd
  • python/nvforest/nvforest/detail/device_type.pxd
  • python/nvforest/nvforest/detail/forest_inference.pyx
  • python/nvforest/nvforest/detail/handle.pxd
  • python/nvforest/nvforest/detail/raft_proto/__init__.py
  • python/nvforest/nvforest/detail/raft_proto/device_type.pxd
  • python/nvforest/nvforest/detail/raft_proto/optional.pxd
💤 Files with no reviewable changes (11)
  • cpp/include/nvforest/detail/raft_proto/cuda_check.hpp
  • cpp/include/nvforest/detail/raft_proto/device_setter.hpp
  • cpp/include/nvforest/detail/raft_proto/detail/host_only_throw.hpp
  • cpp/include/nvforest/detail/raft_proto/detail/const_agnostic.hpp
  • python/nvforest/nvforest/detail/raft_proto/device_type.pxd
  • cpp/include/nvforest/detail/raft_proto/device_id.hpp
  • python/nvforest/nvforest/detail/raft_proto/init.py
  • cpp/include/nvforest/detail/raft_proto/detail/owning_buffer.hpp
  • cpp/include/nvforest/detail/raft_proto/detail/non_owning_buffer.hpp
  • python/nvforest/nvforest/detail/raft_proto/optional.pxd
  • cpp/include/nvforest/detail/raft_proto/device_type.hpp

Comment thread cpp/CMakeLists.txt
Comment on lines +149 to +163
decision_forest(
buffer<node_type>&& nodes,
buffer<index_type>&& root_node_indexes,
buffer<index_type>&& node_id_mapping,
buffer<io_type>&& bias,
index_type num_features,
index_type num_outputs = index_type{2},
bool has_categorical_nodes = false,
std::optional<buffer<io_type>>&& vector_output = std::nullopt,
std::optional<buffer<typename node_type::index_type>>&& categorical_storage = std::nullopt,
index_type leaf_size = index_type{1},
row_op row_postproc = row_op::disable,
element_op elem_postproc = element_op::disable,
io_type average_factor = io_type{1},
io_type postproc_constant = io_type{1})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

These && buffers are still copied, not moved.

The constructor advertises transfer of ownership, but the member initializers below use the named parameters as lvalues. That deep-copies every model buffer instead of moving it, which can double import-time allocation/copy cost and OOM large GPU models.

Suggested fix
   decision_forest(
     buffer<node_type>&& nodes,
     buffer<index_type>&& root_node_indexes,
     buffer<index_type>&& node_id_mapping,
     buffer<io_type>&& bias,
@@
     index_type leaf_size                                                        = index_type{1},
     row_op row_postproc                                                         = row_op::disable,
     element_op elem_postproc  = element_op::disable,
     io_type average_factor    = io_type{1},
     io_type postproc_constant = io_type{1})
-    : nodes_{nodes},
-      root_node_indexes_{root_node_indexes},
-      node_id_mapping_{node_id_mapping},
-      bias_{bias},
-      vector_output_{vector_output},
-      categorical_storage_{categorical_storage},
+    : nodes_{std::move(nodes)},
+      root_node_indexes_{std::move(root_node_indexes)},
+      node_id_mapping_{std::move(node_id_mapping)},
+      bias_{std::move(bias)},
+      vector_output_{std::move(vector_output)},
+      categorical_storage_{std::move(categorical_storage)},
       num_features_{num_features},
       num_outputs_{num_outputs},
       leaf_size_{leaf_size},
🤖 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/nvforest/decision_forest.hpp` around lines 149 - 163, The
decision_forest constructor currently accepts rvalue references (nodes,
root_node_indexes, node_id_mapping, bias, optional<vector_output>,
optional<categorical_storage>) but then copies them because the member
initializer uses the parameter names as lvalues; change the initializer to
transfer ownership by moving these parameters into the members (e.g., use
std::move(nodes), std::move(root_node_indexes), std::move(node_id_mapping),
std::move(bias) and std::move on the optionals like vector_output and
categorical_storage) or switch the signature to take by value and move from
those local variables into members; update the member initializers in the
decision_forest constructor accordingly so buffers are moved, not deep-copied.

Comment on lines 460 to 463
auto double_indexes_required =
(max_num_categories > max_local_categories &&
((raft_proto::ceildiv(max_num_categories, max_local_categories) + 1 * num_categorical_nodes) >
((detail::ceildiv(max_num_categories, max_local_categories) + 1 * num_categorical_nodes) >
std::numeric_limits<small_index_t>::max())) ||

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix the categorical-storage size formula.

detail::ceildiv(max_num_categories, max_local_categories) + 1 * num_categorical_nodes evaluates as bins_per_node + num_categorical_nodes, not (bins_per_node + 1) * num_categorical_nodes. That underestimates the required index range and can select the wrong forest specialization for large categorical models.

Suggested fix
-    (max_num_categories > max_local_categories &&
-     ((detail::ceildiv(max_num_categories, max_local_categories) + 1 * num_categorical_nodes) >
+    (max_num_categories > max_local_categories &&
+     (((detail::ceildiv(max_num_categories, max_local_categories) + 1) *
+       num_categorical_nodes) >
       std::numeric_limits<small_index_t>::max())) ||
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto double_indexes_required =
(max_num_categories > max_local_categories &&
((raft_proto::ceildiv(max_num_categories, max_local_categories) + 1 * num_categorical_nodes) >
((detail::ceildiv(max_num_categories, max_local_categories) + 1 * num_categorical_nodes) >
std::numeric_limits<small_index_t>::max())) ||
auto double_indexes_required =
(max_num_categories > max_local_categories &&
(((detail::ceildiv(max_num_categories, max_local_categories) + 1) *
num_categorical_nodes) >
std::numeric_limits<small_index_t>::max())) ||
🤖 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/nvforest/decision_forest.hpp` around lines 460 - 463, The
categorical index-size check in double_indexes_required uses
(detail::ceildiv(max_num_categories, max_local_categories) + 1 *
num_categorical_nodes) which computes bins_per_node + num_categorical_nodes
instead of (bins_per_node + 1) * num_categorical_nodes; update the expression in
the double_indexes_required calculation to multiply the entire (bins_per_node +
1) factor by num_categorical_nodes (i.e. use
(detail::ceildiv(max_num_categories, max_local_categories) + 1) *
num_categorical_nodes) so the comparison against
std::numeric_limits<small_index_t>::max() correctly reflects required index
range for categorical storage.

Comment thread cpp/include/nvforest/detail/device_initialization/gpu.cuh
Comment on lines +190 to +193
auto num_blocks = std::min(ceildiv(row_count, rows_per_block_iteration), MAX_BLOCKS);
if (row_output_size == 0) {
global_workspace = raft_proto::buffer<output_t>{
output_workspace_size * num_blocks, raft_proto::device_type::gpu, device.value(), stream};
global_workspace = buffer<output_t>{
output_workspace_size * num_blocks, device_type::gpu, device.value(), stream};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return early for empty batches.

When row_count == 0, num_blocks becomes 0 and the kernel launches below become invalid CUDA launches. This path needs an early return before workspace allocation/launch selection.

Suggested fix
 {
   using output_t = typename forest_t::template raw_output_type<vector_output_t>;
+
+  if (row_count == 0) { return; }
 
   auto sm_count                       = get_sm_count(device);

As per coding guidelines, Kernel launch with zero blocks/threads or invalid grid/block dimensions must be validated before kernel execution.

Also applies to: 229-320

🤖 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/nvforest/detail/infer/gpu.cuh` around lines 190 - 193, The code
computes num_blocks from row_count and proceeds to allocate global_workspace and
launch kernels even when row_count == 0; add an early return guard in the
function (in gpu.cuh around the block using
num_blocks/row_output_size/global_workspace) that checks if row_count == 0 (or
num_blocks == 0) and returns immediately before any buffer<output_t> allocation
or kernel launch; apply the same guard to the later region noted (lines
~229-320) so no CUDA allocations or kernel launches occur with zero grid/block
dimensions.

Comment thread cpp/include/nvforest/detail/specializations/forest_macros.hpp Outdated
Comment on lines +105 to +106
cdef nvforest_device_t dev_type
dev_type = nvforest_device_t.gpu if device == "gpu" else nvforest_device_t.cpu

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unknown device values here.

Anything other than "gpu" currently falls back to CPU, but predict() later assumes the only non-CPU value is "gpu". A typo like "cuda" therefore imports the model on CPU and fails later with an unhelpful assertion instead of a clear validation error.

Suggested fix
-        dev_type = nvforest_device_t.gpu if device == "gpu" else nvforest_device_t.cpu
+        if device == "gpu":
+            dev_type = nvforest_device_t.gpu
+        elif device == "cpu":
+            dev_type = nvforest_device_t.cpu
+        else:
+            raise ValueError(f"device must be 'cpu' or 'gpu', got {device!r}")

As per coding guidelines, Error messages should be helpful and include expected vs actual values.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cdef nvforest_device_t dev_type
dev_type = nvforest_device_t.gpu if device == "gpu" else nvforest_device_t.cpu
cdef nvforest_device_t dev_type
if device == "gpu":
dev_type = nvforest_device_t.gpu
elif device == "cpu":
dev_type = nvforest_device_t.cpu
else:
raise ValueError(f"device must be 'cpu' or 'gpu', got {device!r}")
🤖 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/nvforest/nvforest/detail/forest_inference.pyx` around lines 105 - 106,
Validate the incoming device string before mapping to nvforest_device_t: only
accept "gpu" or "cpu" and if it's anything else raise a clear Python error
(e.g., ValueError) that includes the actual value and the expected options; then
set dev_type = nvforest_device_t.gpu if device == "gpu" else
nvforest_device_t.cpu. Update the mapping around the dev_type variable in
forest_inference.pyx (where device and nvforest_device_t are used) so invalid
inputs fail early instead of silently falling back to CPU and causing assertions
later in predict().

@github-actions github-actions Bot added conda Relates to conda packaging ci Cython / Python CMake labels May 25, 2026
@chyunsu3

Copy link
Copy Markdown
Contributor Author

@coderabbitai Please create a new issue to remind us to address the unresolved comments here.

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@hcho3 I've created issue #133 to track all the unresolved review comments: #133

The issue captures the following items from the PR review:

Critical (1)

  • cpp/include/nvforest/buffer.hpp — size_t underflow in the bounds-checked copy overloads

Major (8)

  • cpp/include/nvforest/decision_forest.hpp — buffers deep-copied instead of moved in the constructor
  • cpp/include/nvforest/decision_forest.hpp — operator-precedence bug in the categorical storage size formula
  • cpp/include/nvforest/detail/infer/gpu.cuh — no early return when row_count == 0
  • cpp/include/nvforest/detail/infer/gpu.cuh — potential divide-by-zero when shared_mem_per_block == 0
  • cpp/include/nvforest/cuda_stream.hpp — cudaStreamSynchronize error silently dropped
  • cpp/include/nvforest/detail/exceptions.hpp — exception types in nvforest::detail not re-exported under nvforest::
  • cpp/include/nvforest/handle.hpp — null raft_handle_ causes crash in GPU builds
  • cpp/include/nvforest/treelite_importer.hpp — null TreeliteModelHandle not guarded
  • python/nvforest/nvforest/detail/forest_inference.pyx — unknown device strings silently fall back to CPU

Minor (2)

  • cpp/include/nvforest/detail/infer/gpu.hpp — missing <type_traits> include
  • cpp/include/nvforest/README.md — inconsistent parameter name (mem_type vs dev_type)

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

LGTM. I think your build.rst still refers to libnvforest++

Also, do you even need raft?

@chyunsu3

Copy link
Copy Markdown
Contributor Author

Also, do you even need raft?

nvForest currently uses raft::resources to manage device resources.

@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 `@docs/source/build.rst`:
- Line 145: Update the numbered manual-step headings in the build instructions
to match their commands: revise step 2, which only configures libnvforest, and
step 3, which performs the build and installation. Apply the same heading
correction to the corresponding repeated section referenced by the review.
🪄 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: 1a8b85fc-fbb4-41e2-b02e-a6ea73d3907e

📥 Commits

Reviewing files that changed from the base of the PR and between 10b6f20 and a361ba8.

📒 Files selected for processing (5)
  • ci/build_wheel_nvforest.sh
  • cpp/CMakeLists.txt
  • cpp/tests/CMakeLists.txt
  • docs/source/build.rst
  • docs/source/getting_started.rst
✅ Files skipped from review due to trivial changes (1)
  • docs/source/getting_started.rst
🚧 Files skipped from review as they are similar to previous changes (3)
  • cpp/tests/CMakeLists.txt
  • ci/build_wheel_nvforest.sh
  • cpp/CMakeLists.txt

Comment thread docs/source/build.rst

@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 (2)
cpp/include/nvforest/decision_forest.hpp (2)

179-187: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate every model buffer’s memory type and device.

Only nodes and root_node_indexes are checked. A mismatched bias, node_id_mapping, vector_output, or categorical_storage buffer can later be passed to inference on the wrong host/device memory space.

🤖 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/nvforest/decision_forest.hpp` around lines 179 - 187, Extend the
validation in the forest initialization path around nodes and root_node_indexes
to check every model buffer—bias, node_id_mapping, vector_output, and
categorical_storage—for matching memory type and device. Reuse the existing
mem_type_mismatch handling and ensure all buffers are validated before
detail::initialize_device<forest_type>(nodes).

269-290: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate batch dimensions before computing row counts.

input.size() / num_features_ can divide by zero when num_features_ == 0, silently truncates inputs whose size is not divisible by num_features_, and output capacity is never checked before kernel writes. Reject invalid dimensions and verify the output buffer size before dispatch.

🤖 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/nvforest/decision_forest.hpp` around lines 269 - 290, Update the
prediction dispatch around the visible detail::infer calls to validate
dimensions before computing the row count: reject num_features_ == 0, require
input.size() to be divisible by num_features_, and verify output has capacity
for the requested rows and num_outputs(predict_type). Perform these checks
before any division or kernel dispatch while preserving valid prediction
behavior.
🧹 Nitpick comments (1)
cpp/include/nvforest/decision_forest.hpp (1)

6-15: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the public namespace/type migration.

The constructor and predict API now expose nvforest types instead of raft_proto types. Update public documentation and release notes with the migration path for downstream callers. As per path instructions, public API changes require corresponding documentation and migration guidance.

Also applies to: 149-163, 249-253

🤖 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/nvforest/decision_forest.hpp` around lines 6 - 15, Document the
public API migration from raft_proto types to nvforest types for the decision
forest constructor and predict API, including the required downstream caller
migration path. Update the relevant public API documentation and release notes,
covering the additional affected sections noted in the review.

Source: Path instructions

🤖 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/nvforest/decision_forest.hpp`:
- Around line 179-187: Extend the validation in the forest initialization path
around nodes and root_node_indexes to check every model buffer—bias,
node_id_mapping, vector_output, and categorical_storage—for matching memory type
and device. Reuse the existing mem_type_mismatch handling and ensure all buffers
are validated before detail::initialize_device<forest_type>(nodes).
- Around line 269-290: Update the prediction dispatch around the visible
detail::infer calls to validate dimensions before computing the row count:
reject num_features_ == 0, require input.size() to be divisible by
num_features_, and verify output has capacity for the requested rows and
num_outputs(predict_type). Perform these checks before any division or kernel
dispatch while preserving valid prediction behavior.

---

Nitpick comments:
In `@cpp/include/nvforest/decision_forest.hpp`:
- Around line 6-15: Document the public API migration from raft_proto types to
nvforest types for the decision forest constructor and predict API, including
the required downstream caller migration path. Update the relevant public API
documentation and release notes, covering the additional affected sections noted
in the review.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f2aee95c-5d3c-4d02-a081-411157982ed1

📥 Commits

Reviewing files that changed from the base of the PR and between df3abd0 and f95034f.

📒 Files selected for processing (96)
  • build.sh
  • ci/build_wheel_nvforest.sh
  • cmake/RAPIDS.cmake
  • conda/recipes/libnvforest/recipe.yaml
  • cpp/CMakeLists.txt
  • cpp/include/nvforest/buffer.hpp
  • cpp/include/nvforest/cuda_stream.hpp
  • cpp/include/nvforest/decision_forest.hpp
  • cpp/include/nvforest/detail/bitset.hpp
  • cpp/include/nvforest/detail/ceildiv.hpp
  • cpp/include/nvforest/detail/const_agnostic.hpp
  • cpp/include/nvforest/detail/copy.hpp
  • cpp/include/nvforest/detail/copy/cpu.hpp
  • cpp/include/nvforest/detail/copy/gpu.hpp
  • cpp/include/nvforest/detail/cuda_check.hpp
  • cpp/include/nvforest/detail/cuda_check/base.hpp
  • cpp/include/nvforest/detail/cuda_check/gpu.hpp
  • cpp/include/nvforest/detail/decision_forest_builder.hpp
  • cpp/include/nvforest/detail/device_id.hpp
  • cpp/include/nvforest/detail/device_id/base.hpp
  • cpp/include/nvforest/detail/device_id/cpu.hpp
  • cpp/include/nvforest/detail/device_id/gpu.hpp
  • cpp/include/nvforest/detail/device_initialization.hpp
  • cpp/include/nvforest/detail/device_initialization/cpu.hpp
  • cpp/include/nvforest/detail/device_initialization/gpu.cuh
  • cpp/include/nvforest/detail/device_initialization/gpu.hpp
  • cpp/include/nvforest/detail/device_setter.hpp
  • cpp/include/nvforest/detail/device_setter/base.hpp
  • cpp/include/nvforest/detail/device_setter/gpu.hpp
  • cpp/include/nvforest/detail/evaluate_tree.hpp
  • cpp/include/nvforest/detail/exceptions.hpp
  • cpp/include/nvforest/detail/forest.hpp
  • cpp/include/nvforest/detail/gpu_introspection.hpp
  • cpp/include/nvforest/detail/gpu_support.hpp
  • cpp/include/nvforest/detail/host_only_throw.hpp
  • cpp/include/nvforest/detail/host_only_throw/base.hpp
  • cpp/include/nvforest/detail/host_only_throw/cpu.hpp
  • cpp/include/nvforest/detail/infer.hpp
  • cpp/include/nvforest/detail/infer/cpu.hpp
  • cpp/include/nvforest/detail/infer/gpu.cuh
  • cpp/include/nvforest/detail/infer/gpu.hpp
  • cpp/include/nvforest/detail/infer_kernel/cpu.hpp
  • cpp/include/nvforest/detail/infer_kernel/gpu.cuh
  • cpp/include/nvforest/detail/infer_kernel/shared_memory_buffer.cuh
  • cpp/include/nvforest/detail/node.hpp
  • cpp/include/nvforest/detail/non_owning_buffer.hpp
  • cpp/include/nvforest/detail/non_owning_buffer/base.hpp
  • cpp/include/nvforest/detail/owning_buffer.hpp
  • cpp/include/nvforest/detail/owning_buffer/base.hpp
  • cpp/include/nvforest/detail/owning_buffer/cpu.hpp
  • cpp/include/nvforest/detail/owning_buffer/gpu.hpp
  • cpp/include/nvforest/detail/padding.hpp
  • cpp/include/nvforest/detail/postprocessor.hpp
  • cpp/include/nvforest/detail/specializations/device_initialization_macros.hpp
  • cpp/include/nvforest/detail/specializations/infer_macros.hpp
  • cpp/include/nvforest/device_type.hpp
  • cpp/include/nvforest/forest_model.hpp
  • cpp/include/nvforest/handle.hpp
  • cpp/include/nvforest/treelite_importer.hpp
  • cpp/src/infer0.cpp
  • cpp/src/infer0.cu
  • cpp/src/infer1.cpp
  • cpp/src/infer1.cu
  • cpp/src/infer10.cpp
  • cpp/src/infer10.cu
  • cpp/src/infer11.cpp
  • cpp/src/infer11.cu
  • cpp/src/infer2.cpp
  • cpp/src/infer2.cu
  • cpp/src/infer3.cpp
  • cpp/src/infer3.cu
  • cpp/src/infer4.cpp
  • cpp/src/infer4.cu
  • cpp/src/infer5.cpp
  • cpp/src/infer5.cu
  • cpp/src/infer6.cpp
  • cpp/src/infer6.cu
  • cpp/src/infer7.cpp
  • cpp/src/infer7.cu
  • cpp/src/infer8.cpp
  • cpp/src/infer8.cu
  • cpp/src/infer9.cpp
  • cpp/src/infer9.cu
  • cpp/tests/CMakeLists.txt
  • cpp/tests/buffer/buffer.cpp
  • cpp/tests/buffer/buffer.cu
  • cpp/tests/infer_kernel/shared_memory_buffer.cu
  • cpp/tests/treelite_importer.cpp
  • pyproject.toml
  • python/libnvforest/CMakeLists.txt
  • python/libnvforest/libnvforest/load.py
  • python/nvforest/CMakeLists.txt
  • python/nvforest/nvforest/detail/cuda_stream.pxd
  • python/nvforest/nvforest/detail/device_type.pxd
  • python/nvforest/nvforest/detail/forest_inference.pyx
  • python/nvforest/nvforest/detail/handle.pxd
🚧 Files skipped from review as they are similar to previous changes (90)
  • cpp/src/infer2.cpp
  • ci/build_wheel_nvforest.sh
  • cpp/src/infer1.cpp
  • cpp/include/nvforest/detail/non_owning_buffer.hpp
  • cpp/include/nvforest/detail/device_setter.hpp
  • cpp/src/infer9.cpp
  • cpp/src/infer6.cu
  • python/nvforest/CMakeLists.txt
  • cpp/src/infer3.cpp
  • cpp/include/nvforest/detail/owning_buffer.hpp
  • cpp/src/infer5.cpp
  • cpp/src/infer10.cpp
  • python/nvforest/nvforest/detail/device_type.pxd
  • cpp/include/nvforest/detail/host_only_throw.hpp
  • cpp/src/infer5.cu
  • cpp/src/infer4.cpp
  • cpp/tests/CMakeLists.txt
  • cpp/src/infer9.cu
  • cpp/src/infer2.cu
  • python/libnvforest/libnvforest/load.py
  • cpp/include/nvforest/detail/device_id.hpp
  • cpp/src/infer7.cpp
  • cpp/src/infer6.cpp
  • cpp/include/nvforest/detail/evaluate_tree.hpp
  • cpp/include/nvforest/detail/const_agnostic.hpp
  • build.sh
  • cpp/src/infer11.cu
  • cpp/src/infer1.cu
  • cpp/include/nvforest/detail/cuda_check/gpu.hpp
  • cpp/include/nvforest/detail/gpu_support.hpp
  • cpp/src/infer8.cu
  • cpp/src/infer3.cu
  • cpp/include/nvforest/device_type.hpp
  • cpp/include/nvforest/detail/bitset.hpp
  • cpp/src/infer0.cpp
  • cpp/include/nvforest/detail/specializations/device_initialization_macros.hpp
  • cpp/include/nvforest/detail/owning_buffer/base.hpp
  • cpp/src/infer0.cu
  • cpp/src/infer8.cpp
  • cpp/src/infer10.cu
  • cpp/include/nvforest/detail/node.hpp
  • conda/recipes/libnvforest/recipe.yaml
  • cpp/include/nvforest/detail/postprocessor.hpp
  • cpp/include/nvforest/detail/cuda_check.hpp
  • python/nvforest/nvforest/detail/cuda_stream.pxd
  • cpp/include/nvforest/detail/host_only_throw/cpu.hpp
  • python/libnvforest/CMakeLists.txt
  • cpp/include/nvforest/detail/infer/gpu.hpp
  • cpp/include/nvforest/detail/infer_kernel/gpu.cuh
  • cpp/src/infer11.cpp
  • cpp/src/infer4.cu
  • python/nvforest/nvforest/detail/handle.pxd
  • cpp/include/nvforest/detail/non_owning_buffer/base.hpp
  • cpp/include/nvforest/detail/copy/gpu.hpp
  • cpp/include/nvforest/detail/forest.hpp
  • cpp/include/nvforest/detail/infer.hpp
  • cpp/include/nvforest/handle.hpp
  • cpp/include/nvforest/detail/ceildiv.hpp
  • cpp/include/nvforest/detail/device_initialization/gpu.hpp
  • cpp/include/nvforest/detail/padding.hpp
  • cpp/include/nvforest/detail/specializations/infer_macros.hpp
  • cpp/include/nvforest/detail/host_only_throw/base.hpp
  • cpp/src/infer7.cu
  • cpp/include/nvforest/detail/infer/cpu.hpp
  • cpp/include/nvforest/detail/device_id/base.hpp
  • cpp/tests/treelite_importer.cpp
  • cpp/tests/buffer/buffer.cu
  • cpp/include/nvforest/detail/device_id/gpu.hpp
  • cpp/include/nvforest/detail/infer_kernel/cpu.hpp
  • cpp/include/nvforest/detail/device_initialization/gpu.cuh
  • cpp/include/nvforest/detail/device_initialization.hpp
  • cpp/include/nvforest/detail/owning_buffer/cpu.hpp
  • cpp/include/nvforest/detail/device_setter/gpu.hpp
  • cpp/CMakeLists.txt
  • cpp/include/nvforest/detail/device_initialization/cpu.hpp
  • cpp/tests/buffer/buffer.cpp
  • cpp/include/nvforest/detail/exceptions.hpp
  • cpp/include/nvforest/detail/device_setter/base.hpp
  • cpp/include/nvforest/detail/device_id/cpu.hpp
  • python/nvforest/nvforest/detail/forest_inference.pyx
  • cpp/include/nvforest/cuda_stream.hpp
  • cpp/include/nvforest/detail/cuda_check/base.hpp
  • cpp/include/nvforest/buffer.hpp
  • cpp/include/nvforest/detail/owning_buffer/gpu.hpp
  • cpp/include/nvforest/detail/gpu_introspection.hpp
  • cpp/include/nvforest/detail/copy.hpp
  • cpp/include/nvforest/treelite_importer.hpp
  • cpp/include/nvforest/detail/decision_forest_builder.hpp
  • cpp/include/nvforest/detail/infer/gpu.cuh
  • cpp/include/nvforest/forest_model.hpp

@chyunsu3

Copy link
Copy Markdown
Contributor Author

/merge

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

git grep -E 'nvforest\+\+' shows no remaining traces of the old name. Approving.

Note that since this changes the name of the library, it is a breaking change.

@rapids-bot
rapids-bot Bot merged commit a09f064 into rapidsai:main Jul 16, 2026
66 checks passed
rapids-bot Bot pushed a commit to NVIDIA/cuml that referenced this pull request Jul 17, 2026
Update cuML to build with 26.08 nvForest, after a breaking change (rapidsai/nvforest#132).

Authors:
  - Philip Hyunsu Cho (https://github.com/hcho3)

Approvers:
  - Simon Adorf (https://github.com/csadorf)
  - Bradley Dice (https://github.com/bdice)

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

Labels

breaking Introduces a breaking change ci CMake conda Relates to conda packaging CUDA/C++ Cython / Python improvement Improves an existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rename CMake target nvforest++ to nvforest Merge raft_proto into nvforest namespace

5 participants