Skip to content
Open
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
99dc47f
Add dry-run memory resources for allocation profiling without real me…
achirkin Feb 18, 2026
695a8a3
First batch of dry-run guards
achirkin Feb 18, 2026
42d8ad4
Dry run compliance for raft::linalg namespace
achirkin Feb 19, 2026
6db7ec8
Update developer guide with the dry run protocol
achirkin Feb 19, 2026
d91a1c6
BREAKING CHANGE: replaced pinned_container with host_container using …
achirkin Feb 19, 2026
1a114f6
Dry run compliance for raft::matrix namespace
achirkin Feb 19, 2026
dec5e95
Dry run compliance for raft::random namespace
achirkin Feb 19, 2026
f84d9a9
Dry run compliance for raft::solver namespace
achirkin Feb 19, 2026
44793cd
Dry run compliance for raft::sparse namespace
achirkin Feb 20, 2026
d566fe9
Dry run compliance for raft::spectral namespace
achirkin Feb 20, 2026
fc3bde6
Dry run compliance for raft::stats namespace
achirkin Feb 20, 2026
b0ddbc8
Add a little bit more tests
achirkin Feb 20, 2026
15c07a1
Add the Dry Run Protocol Overview
achirkin Feb 20, 2026
1c57abb
Fix C++ example in the docs
achirkin Feb 20, 2026
d916b45
Merge branch 'main' into fea-dry-run-protocol
achirkin Feb 20, 2026
9d24480
Add a few more tests and fix a missed CUDA call in QR algorithm
achirkin Feb 20, 2026
7577e56
Fix excess subsample doing work in dry run
achirkin Feb 20, 2026
99faf68
Add dry run compliance to the raft::copy on mdspans
achirkin Feb 20, 2026
b859894
Merge branch 'main' into fea-dry-run-protocol
achirkin Feb 20, 2026
57d4c19
Revert changing includes from public to detail namespace to avoid bre…
achirkin Feb 23, 2026
694ec63
Merge branch 'main' into fea-dry-run-protocol
achirkin Feb 23, 2026
a2dd18c
Merge rapidsai/main into fea-dry-run-protocol
achirkin Feb 24, 2026
45e2d49
Rename device_uvector_policy -> device_container_policy and add non-i…
achirkin Feb 26, 2026
65d4570
Declare the new resources in raft handle
achirkin Feb 26, 2026
d86638f
Renamed managed policy
achirkin Feb 26, 2026
d6788f6
Add raft::resources for pinned and managed resources and the type-era…
achirkin Feb 26, 2026
e7bea48
Updated container policies
achirkin Feb 26, 2026
2514621
All but host memory resource are done
achirkin Feb 26, 2026
49735a5
Simplify the implementation
achirkin Feb 27, 2026
22b4048
Make the host container policy use the resource concept
achirkin Feb 27, 2026
557cc8c
Settle down with raft::mr::*et_default_host_resource()
achirkin Feb 27, 2026
cc7a4b0
Add some thread-safety
achirkin Feb 27, 2026
e77fe2a
Merge branch 'main' into fea-unify-memory-resources
achirkin Feb 27, 2026
8922b8f
Merge branch 'main' into fea-dry-run-protocol
achirkin Feb 27, 2026
866211e
C++17 backwards-compatibility
achirkin Feb 28, 2026
c171d84
Merge branch 'main' into fea-unify-memory-resources
achirkin Feb 28, 2026
268eb1b
newline
achirkin Feb 28, 2026
5c718d6
Add raft::mr::device_resource wrapper for cuda::mr::any_resource
achirkin Mar 1, 2026
c5ab9c4
Copy semantics and return resource refs
achirkin Mar 2, 2026
6af142e
Rework workspace resources to avoid nesting bridge layers
achirkin Mar 2, 2026
ece1990
Fix the argument order in tests
achirkin Mar 2, 2026
3c17e3e
Merge branch 'main' into fea-dry-run-protocol
achirkin Mar 2, 2026
4dd256b
Merge branch 'main' into fea-unify-memory-resources
achirkin Mar 3, 2026
a26357d
Add explicit conversion through cuda::mr refs to rmm ref
achirkin Mar 3, 2026
2a90680
Switch from rmm host and host_device resource reference wrappers to r…
achirkin Mar 4, 2026
59c3793
Merge branch 'main' into fea-unify-memory-resources
achirkin Mar 4, 2026
3a40d22
Prefer rmm::mr::get_current_device_resource_ref() over rmm::mr::get_c…
achirkin Mar 4, 2026
cce4f45
Remove raft pinned and managed memory resources in favor of cuda::mr …
achirkin Mar 4, 2026
fb56025
Merge branch 'main' into fea-dry-run-protocol
achirkin Mar 4, 2026
ff20962
Merge fea-unify-memory-resources into fea-dry-run-protocol
achirkin Mar 5, 2026
e76bf7c
Adapt to fea-unify-memory-resources
achirkin Mar 5, 2026
2d3f8fc
Refactor dry_run_resources as a child of raft::resources to better ke…
achirkin Mar 5, 2026
d2cf85e
Merge branch 'main' into fea-dry-run-protocol
achirkin Mar 9, 2026
e86b56d
Merge branch 'main' into fea-dry-run-protocol
achirkin Mar 14, 2026
d9a0abf
Fix style after merge commit
achirkin Mar 16, 2026
16324fb
Merge branch 'main' into fea-dry-run-protocol
achirkin Mar 18, 2026
ced0e6e
Fix merge commit typo
achirkin Mar 18, 2026
c9bf618
Merge branch 'main' into fea-dry-run-protocol
achirkin Mar 19, 2026
1acf6cf
Fix some sparse routines not being dry-run compliant
achirkin Mar 23, 2026
2326061
Unify the looks of the three custom raft::resources
achirkin Mar 24, 2026
d4ff16e
Expand test coverage Part 1
achirkin Mar 24, 2026
fee4b62
Expand test coverage Part 2
achirkin Mar 24, 2026
f1b7aca
Update docs to reflect unify memory resources PR changes
achirkin Mar 24, 2026
156a437
Fix segfault in sparse tests caused by invalid thrust exec policy
achirkin Mar 25, 2026
51c0b16
Better allocation estimates in the sparse namespace
achirkin Mar 30, 2026
c99b879
Fixing more failing tests
achirkin Apr 1, 2026
e535124
Fixing last failing tests
achirkin Apr 1, 2026
9971c71
Merge branch 'main' into fea-dry-run-protocol
achirkin Apr 1, 2026
b682d46
Fix not initialize the mdarray scalars only in dry run mode
achirkin Apr 1, 2026
69543a1
Clarify that all workspace resources are actually counted independent…
achirkin Apr 2, 2026
5db5727
Rename the dry_run_resources header file for conistency
achirkin Apr 2, 2026
d1cf594
Dry-run compliance for coo_sort
achirkin Apr 2, 2026
e47c41f
Fix the expected minimum allocation calculation
achirkin Apr 2, 2026
3d93b0f
Merge branch 'main' into fea-dry-run-protocol
achirkin Apr 3, 2026
e60a048
Merge branch 'main' into fea-dry-run-protocol
achirkin Apr 8, 2026
f8754d9
Merge branch 'main' into fea-dry-run-protocol
achirkin Apr 10, 2026
0f65503
Merge branch 'main' into fea-dry-run-protocol
achirkin Apr 13, 2026
969b868
Merge remote-tracking branch 'rapidsai/main' into fea-dry-run-protocol
achirkin Apr 22, 2026
217ca58
Fix tests after rmm breaking change
achirkin Apr 22, 2026
e72e872
Store the device resources by values to safely keep them alive while …
achirkin Apr 23, 2026
1c4deb8
Switch to owning semantics for both host and per-device resources
achirkin Apr 24, 2026
8fdf194
Don't let allocations cross dry-run/normal scopes
achirkin Apr 29, 2026
1a7501c
More thorough tests for bitset/bitmap in dry run mode
achirkin Apr 29, 2026
65dda3b
Merge branch 'main' into fea-dry-run-protocol
achirkin May 6, 2026
a710e97
make bitset.count() dry-run-compliant
achirkin May 7, 2026
3a65638
Merge branch 'main' into fea-dry-run-protocol
achirkin May 7, 2026
7f1210e
Merge branch 'main' into fea-dry-run-protocol
achirkin May 11, 2026
592b8e0
Merge branch 'main' into fea-dry-run-protocol
achirkin May 15, 2026
0f5641e
Merge branch 'main' into fea-dry-run-protocol
achirkin Jun 10, 2026
205cffa
Make randomized SVD dry run compliant (adopting new features to dry run)
achirkin Jun 10, 2026
ec1a794
Merge branch 'main' into fea-dry-run-protocol
achirkin Jun 15, 2026
8d56307
Merge remote-tracking branch main into fea-dry-run-protocol
achirkin Jun 17, 2026
a9bc0af
Follow up on merge commit
achirkin Jun 17, 2026
91107c3
Merge branch 'main' into fea-dry-run-protocol
achirkin Jun 23, 2026
fa24967
Merge branch 'main' into fea-dry-run-protocol
achirkin Jul 6, 2026
8e1855d
Fix style
achirkin Jul 6, 2026
584cac8
Merge branch 'main' into fea-dry-run-protocol
achirkin Jul 7, 2026
a0ee878
Revert changes to .clang-format
achirkin Jul 7, 2026
b8e7928
Merge branch 'main' into fea-dry-run-protocol
achirkin Jul 15, 2026
721135a
Merge branch 'main' into fea-dry-run-protocol
achirkin Jul 22, 2026
8971552
Update to account for the predictable resources and PCA PRs
achirkin Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cpp/include/raft/core/coo_matrix.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

/*
* SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand Down Expand Up @@ -179,8 +179,8 @@ class coordinate_structure : public coordinate_structure_t<RowType, ColType, NZT
void initialize_sparsity(nnz_type nnz)
{
sparse_structure_type::initialize_sparsity(nnz);
c_rows_.resize(nnz);
c_cols_.resize(nnz);
c_rows_.reallocate(nnz);
c_cols_.reallocate(nnz);
}

protected:
Expand Down
6 changes: 3 additions & 3 deletions cpp/include/raft/core/csr_matrix.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand Down Expand Up @@ -188,8 +188,8 @@ class compressed_structure
void initialize_sparsity(NZType nnz) override
{
sparse_structure_type::initialize_sparsity(nnz);
c_indptr_.resize(this->get_n_rows() + 1);
c_indices_.resize(nnz);
c_indptr_.reallocate(this->get_n_rows() + 1);
c_indices_.reallocate(nnz);
}

protected:
Expand Down
5 changes: 5 additions & 0 deletions cpp/include/raft/core/detail/copy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <raft/core/host_mdspan.hpp>
#include <raft/core/logger.hpp>
#include <raft/core/mdspan.hpp>
#include <raft/core/resource/dry_run_flag.hpp>
#include <raft/core/resource/stream_view.hpp>
#include <raft/core/resources.hpp>

Expand Down Expand Up @@ -398,6 +399,10 @@ mdspan_copyable_t<DstType, SrcType> copy(resources const& res, DstType&& dst, Sr
RAFT_EXPECTS(src.extent(i) == dst.extent(i), "Must copy between mdspans of the same shape");
}

// Dry-run guard: raft::copy is a pure data-movement utility with no
// allocations that callers would need tracked.
if (resource::get_dry_run_flag(res)) { return; }
Comment thread
achirkin marked this conversation as resolved.

if constexpr (config::use_intermediate_src) {
#ifndef RAFT_DISABLE_CUDA
// Copy to intermediate source on device, then perform necessary
Expand Down
23 changes: 23 additions & 0 deletions cpp/include/raft/core/device_container_policy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,29 @@ class device_uvector {

void resize(size_type size) { data_.resize(size, data_.stream()); }

/**
* @brief Resize the internal buffer without copying old data.
*
* Unlike resize(), this never copies old data.
* Thus, unlike in resize(), there's no point in time where the old and the new buffers are both
* alive, and the peak memory usage is lower.
*
* Unlike resize(), this deallocates the old buffer even if the new size is smaller.
* This ensures the memory is released promptly.
*/
void reallocate(size_type size)
{

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.

Although your implementation avoids peak data (old + new) when the new size is larger than the current size, it has two issues:

  • when the new size is the same -> it does NOT reallocate while its name is "reallocate"
  • when the new size is smaller -> it DOES allocate while it could more efficiently shrink the current buffer (similar to what resize() already does)

I would suggest to do the current implementation only when the new size is larger. When the new size is less or equal, simply call resize() and clear the buffer.

I am not sure about clearing the buffer. It adds some overhead, but it sounds safer to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Funny enough, both of these are pretty much intentional features. Here's a bit more context for this.

By design, mdarrays and mdspans are not supposed to be resizeable at all. However, at some point we decided to implement sparse matrices using the same container policies as mdarrays. For sparse data, one needs the resize functionality to allow changing sparsity pattern. So the resize function is exclusively used for sparse data and only to allocate a new sparse matrix storage of the required size (initialize_sparsity).

Back to reallocate addressing your concerns. The intention here is to always free up the memory as soon as requested: the released buffer may be huge (e.g. half of GPU memory); the user may want to use the memory as soon as possible, so we want it to be available.

Essentially reallocate is almost the same as running resize + shrink_to_fit on an underlying RMM container. But there's a catch: reallocate guarantees to NOT copy the data. This catch is important for dry run: we fake the memory allocations, so any memory copy would cause OOM / break the program.

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.

Thank you for sharing the context with sparse matrix.

I still don't understand why when the new size is less than or equal to the current size, we don't simply free the redundant memory and keep the remaining new size part. This will give back immediately current_size - new_size to the user. Or do you mean we have to free everything at the current address and then allocate the new size some where else? If so, why?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The current implementation makes sense to me only when the new size is greater than the current size

if (size != data_.size()) {
auto stream = data_.stream();
auto mr = data_.memory_resource();
// Resize and shrink rmm::device_uvector: force deallocation without copying old data
data_.resize(0, data_.stream());
data_.shrink_to_fit(data_.stream());
// Assign a new value after the old one is deallocated
data_ = rmm::device_uvector<T>(size, stream, mr);
}
}

[[nodiscard]] auto data() noexcept -> pointer { return data_.data(); }
[[nodiscard]] auto data() const noexcept -> const_pointer { return data_.data(); }
};
Expand Down
3 changes: 2 additions & 1 deletion cpp/include/raft/core/device_mdarray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <raft/core/device_container_policy.hpp>
#include <raft/core/device_mdspan.hpp>
#include <raft/core/mdarray.hpp>
#include <raft/core/resource/dry_run_flag.hpp>
#include <raft/core/resources.hpp>

#include <rmm/resource_ref.hpp>
Expand Down Expand Up @@ -163,7 +164,7 @@ auto make_device_scalar(raft::resources const& handle, ElementType const& v)
using policy_t = typename device_scalar<ElementType, IndexType>::container_policy_type;
policy_t policy{};
auto scalar = device_scalar<ElementType, IndexType>{handle, extents, policy};
scalar(0) = v;
if (!resource::get_dry_run_flag(handle)) { scalar(0) = v; }

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.

I guess we don't need to guard this scalar operation (same for other resources). It is lightweight to run and keep the code a little bit simpler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The scalar is on device - in the default per-device memory resource, which is faked in dry run mode. Therefore we have to guard it (and it falls under "GPU work" category anyway).

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.

Acknowledged.

return scalar;
}

Expand Down
21 changes: 21 additions & 0 deletions cpp/include/raft/core/host_container_policy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,27 @@ requires cuda::mr::synchronous_resource_with<MR, cuda::mr::host_accessible>
*this = std::move(new_container);
}

/**
* @brief Resize the internal buffer without copying old data.
*
* Unlike resize(), this never copies old data.
* Thus, unlike in resize(), there's no point in time where the old and the new buffers are both
* alive, and the peak memory usage is lower.
*
* Unlike resize(), this deallocates the old buffer even if the new size is smaller.
* This ensures the memory is released promptly.
*/
void reallocate(size_type count)
{
if (bytesize_ == sizeof(value_type) * count) { return; }
if (data_ != nullptr) {
mr_.deallocate_sync(data_, bytesize_);
data_ = nullptr;
}
auto tmp = host_container{count, mr_};
std::swap(tmp, *this);
}

[[nodiscard]] auto data() noexcept -> pointer { return data_; }
[[nodiscard]] auto data() const noexcept -> const_pointer { return data_; }
};
Expand Down
3 changes: 2 additions & 1 deletion cpp/include/raft/core/host_mdarray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <raft/core/host_container_policy.hpp>
#include <raft/core/host_mdspan.hpp>
#include <raft/core/mdarray.hpp>
#include <raft/core/resource/dry_run_flag.hpp>
#include <raft/core/resources.hpp>

#include <cstdint>
Expand Down Expand Up @@ -223,7 +224,7 @@ auto make_host_scalar(raft::resources const& res, ElementType const& v)
using policy_t = typename host_scalar<ElementType, IndexType>::container_policy_type;
policy_t policy;
auto scalar = host_scalar<ElementType, IndexType>{res, extents, policy};
scalar(0) = v;
if (!resource::get_dry_run_flag(res)) { scalar(0) = v; }
return scalar;
}

Expand Down
3 changes: 2 additions & 1 deletion cpp/include/raft/core/managed_mdarray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <raft/core/managed_container_policy.hpp>
#include <raft/core/managed_mdspan.hpp>
#include <raft/core/mdarray.hpp>
#include <raft/core/resource/dry_run_flag.hpp>
#include <raft/core/resources.hpp>

#include <cstdint>
Expand Down Expand Up @@ -117,7 +118,7 @@ auto make_managed_scalar(raft::resources const& handle, ElementType const& v)
using policy_t = typename managed_scalar<ElementType>::container_policy_type;
policy_t policy{};
auto scalar = managed_scalar<ElementType>{handle, extents, policy};
scalar(0) = v;
if (!resource::get_dry_run_flag(handle)) { scalar(0) = v; }
return scalar;
}

Expand Down
3 changes: 2 additions & 1 deletion cpp/include/raft/core/pinned_mdarray.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <raft/core/mdarray.hpp>
#include <raft/core/pinned_container_policy.hpp>
#include <raft/core/pinned_mdspan.hpp>
#include <raft/core/resource/dry_run_flag.hpp>
#include <raft/core/resources.hpp>

#include <cstdint>
Expand Down Expand Up @@ -117,7 +118,7 @@ auto make_pinned_scalar(raft::resources const& handle, ElementType const& v)
using policy_t = typename pinned_scalar<ElementType>::container_policy_type;
policy_t policy{};
auto scalar = pinned_scalar<ElementType>{handle, extents, policy};
scalar(0) = v;
if (!resource::get_dry_run_flag(handle)) { scalar(0) = v; }
return scalar;
}

Expand Down
10 changes: 8 additions & 2 deletions cpp/include/raft/core/resource/cuda_stream.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2022-2024, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#include <raft/core/interruptible.hpp>
#include <raft/core/resource/dry_run_flag.hpp>
#include <raft/core/resource/resource_types.hpp>
#include <raft/core/resources.hpp>
#include <raft/util/cudart_utils.hpp>
Expand Down Expand Up @@ -82,13 +83,18 @@ inline void set_cuda_stream(resources const& res, rmm::cuda_stream_view stream_v
*/
inline void sync_stream(const resources& res, rmm::cuda_stream_view stream)
{
if (raft::resource::get_dry_run_flag(res)) { return; }

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.

What is the motivation for guarding sync_stream?

Your dry_run_protocol.md suggests the opposite that sync_stream is safe without guard.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The guide says it's safe to call this function, because it is dry-run compliant (takes the raft::resources as an argument) - hence it guards from any CUDA work inside.
cudaStreamSynchronize is cuda CUDA and it takes time, so we must guard it.

@huuanhhuyn huuanhhuyn Jul 14, 2026

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.

If I understand dry-run correctly, when res is in dry-run mode, the stream contains no CUDA work and contains only light-weight fake memory operations. Therefore it would be fast to sync.

If user intentionally adds a CUDA/memory work to the stream without guarding, he wants the sync_stream to work I suppose.

I mean this guarding on sync stream wouldn't bring any benefit but limit its usage a little.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is still CUDA work and a call to cuda context, which may briefly lock. In dry run mode, nobody should ever call CUDA, that is a hard constraint.

interruptible::synchronize(stream);
}

/**
* @brief synchronize main stream on the resources instance
*/
inline void sync_stream(const resources& res) { sync_stream(res, get_cuda_stream(res)); }
inline void sync_stream(const resources& res)
{
if (raft::resource::get_dry_run_flag(res)) { return; }
sync_stream(res, get_cuda_stream(res));
}

/**
* @}
Expand Down
89 changes: 89 additions & 0 deletions cpp/include/raft/core/resource/dry_run_flag.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once

#include <raft/core/resource/resource_types.hpp>
#include <raft/core/resources.hpp>

#include <memory>

namespace raft::resource {

/**
* @defgroup dry_run_flag Dry-run flag resource
* @{
*/

/**
* @brief Resource that holds a boolean dry-run flag.
*
* When the dry-run flag is set, algorithms should skip kernel execution
* and only perform allocations to measure memory usage.
*/
class dry_run_flag_resource : public resource {
public:
dry_run_flag_resource() = default;
explicit dry_run_flag_resource(bool value) : flag_(value) {}
~dry_run_flag_resource() override = default;

auto get_resource() -> void* override { return &flag_; }

void set(bool value) { flag_ = value; }
[[nodiscard]] auto get() const -> bool { return flag_; }

private:
bool flag_{false};
};

/**
* @brief Factory that creates a dry_run_flag_resource.
*/
class dry_run_flag_resource_factory : public resource_factory {
public:
explicit dry_run_flag_resource_factory(bool initial_value = false) : initial_value_(initial_value)
{
}

auto get_resource_type() -> resource_type override { return resource_type::DRY_RUN_FLAG; }
auto make_resource() -> resource* override { return new dry_run_flag_resource(initial_value_); }

private:
bool initial_value_;
};

/**
* @brief Get the dry-run flag from a resources handle.
*
* @param res raft resources object
* @return true if dry-run mode is active
*/
inline auto get_dry_run_flag(resources const& res) -> bool
{
if (!res.has_resource_factory(resource_type::DRY_RUN_FLAG)) {
res.add_resource_factory(std::make_shared<dry_run_flag_resource_factory>());

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.

How about just return False here? I think we don't need to create the factory at all in dry-run-free mode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The argument res here can be instantiated by raft::dry_run_resources, i.e. the function get_dry_run_flag really is used in both dry run and regular mode.
You may suggest to use dynamic_cast here to check if this is dry run resources handle, but that would also not work: any algorithm is free to copy the resources handle (to make it non-const and override one or more resources); so we may get a regular resources handle here that is still in dry run mode.

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.

Hmm, when we instantiate a raft::dry_run_resources from a non-dryrun existing resource, we init the dry run flag with resource::set_dry_run_flag(*this, true);. That would create a dry run resource factory already. Afterwards get_dry_run_flag() will not execute line 65 because the factory is there. Even when you copy a dry-run resource into another resource, the factories are copied over to the new resource.

Therefore, line 65 is only executed if the input resource hasn't been added with a dry run factory before.

I may overlook somewhere though. Even if I am right, this is a minor thing IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ah, you mean specifically remove the add_resource_factory on line 65? Probably this could work. But I think it's ok to keep the line there for homogeneity with the other resource types.

}
return *res.get_resource<bool>(resource_type::DRY_RUN_FLAG);
}

/**
* @brief Set the dry-run flag on a resources handle.
*
* @param res raft resources object
* @param value true to enable dry-run mode, false to disable
*/
inline void set_dry_run_flag(resources const& res, bool value)
{
if (!res.has_resource_factory(resource_type::DRY_RUN_FLAG)) {
res.add_resource_factory(std::make_shared<dry_run_flag_resource_factory>(value));
} else {
// The resource may already be instantiated; update it directly
auto* flag = res.get_resource<bool>(resource_type::DRY_RUN_FLAG);
*flag = value;
}
}

/** @} */

} // namespace raft::resource
1 change: 1 addition & 0 deletions cpp/include/raft/core/resource/resource_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum resource_type {
MULTI_GPU, // resource that tracks resource of each device in multi-gpu world
PINNED_MEMORY_RESOURCE, // memory resource for pinned (page-locked) host allocations
MANAGED_MEMORY_RESOURCE, // resource for managed (unified) allocations
DRY_RUN_FLAG, // dry-run mode flag for allocation profiling

LAST_KEY // reserved for the last key
};
Expand Down
4 changes: 2 additions & 2 deletions cpp/include/raft/core/sparse_types.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2023, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2023-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
Expand Down Expand Up @@ -177,7 +177,7 @@ class sparse_matrix {

~sparse_matrix() noexcept(std::is_nothrow_destructible<container_type>::value) = default;

void initialize_sparsity(nnz_type nnz) { c_elements_.resize(nnz); };
void initialize_sparsity(nnz_type nnz) { c_elements_.reallocate(nnz); };

raft::span<ElementType, is_device> get_elements()
{
Expand Down
28 changes: 27 additions & 1 deletion cpp/include/raft/label/classlabels.cuh
Original file line number Diff line number Diff line change
@@ -1,17 +1,43 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2022, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __CLASS_LABELS_H
#define __CLASS_LABELS_H

#pragma once

#include <raft/core/resource/cuda_stream.hpp>
#include <raft/core/resource/dry_run_flag.hpp>
#include <raft/core/resources.hpp>
#include <raft/label/detail/classlabels.cuh>

namespace raft {
namespace label {

/**
* Get unique class labels.
*
* The y array is assumed to store class labels. The unique values are selected
* from this array.
*
* @tparam value_t numeric type of the arrays with class labels
* @param [in] handle raft resources handle (dry-run aware)
* @param [inout] unique output unique labels
* @param [in] y device array of labels, size [n]
* @param [in] n number of labels
* @returns number of unique labels (upper bound in dry-run mode)
*/
template <typename value_t>
int getUniquelabels(raft::resources const& handle,
rmm::device_uvector<value_t>& unique,
value_t* y,
size_t n)
{
return detail::getUniquelabels<value_t>(
resource::get_dry_run_flag(handle), unique, y, n, resource::get_cuda_stream(handle));
}

/**
* Get unique class labels.
*
Expand Down
Loading
Loading