Skip to content

[libcu++] Implement internal resizable buffer - #10221

Open
pciolkosz wants to merge 8 commits into
NVIDIA:mainfrom
pciolkosz:cuda-buffer-capacity-resize
Open

[libcu++] Implement internal resizable buffer#10221
pciolkosz wants to merge 8 commits into
NVIDIA:mainfrom
pciolkosz:cuda-buffer-capacity-resize

Conversation

@pciolkosz

Copy link
Copy Markdown
Contributor

closes #10213

Adds this functionality as internal separate class because of ABI constraints. Instead of adding a bunch of make_* functions it exposes rvalue constructor from normal buffer, but exposes the constructors too since that part was easy.

We can see how internal usage of it goes and consider folding it into the buffer in the next major version when we can break ABI.

The new buffer class has a capacity separate from size and size has a few variants, the less arguments provided the less cases are supported by it.

We could consider adding append that actually initializes growing the buffer, but I think its not necessary right now.
We could also think about exposing this type in cuda::experimental, but I want to start as internal only

@pciolkosz
pciolkosz requested a review from a team as a code owner July 24, 2026 03:40
@pciolkosz
pciolkosz requested a review from ericniebler July 24, 2026 03:40
@github-project-automation github-project-automation Bot moved this to Todo in CCCL Jul 24, 2026
@pciolkosz
pciolkosz requested a review from Jacobfaib July 24, 2026 03:40
@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Review in CCCL Jul 24, 2026
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

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

Adds internal cuda::__resizable_buffer, which tracks capacity separately from logical size, supports preserving and discard resizing, and manages capacity-aware destruction. Supporting buffer helpers enable allocation replacement and counted asynchronous copies. CUDA tests cover the behavior.

Resizable buffer

Layer / File(s) Summary
Buffer allocation and copy plumbing
libcudacxx/include/cuda/__container/buffer.h, libcudacxx/include/cuda/__container/uninitialized_async_buffer.h
Adds internal ownership, size, destruction, allocation-replacement, and counted cross-buffer copy helpers.
Capacity-aware buffer behavior
libcudacxx/include/cuda/__container/resizable_buffer.h
Defines __resizable_buffer with capacity accessors, resize variants, discard resizing, move/swap support, and capacity-based destruction.
Resizable buffer behavioral tests
libcudacxx/test/libcudacxx/cuda/containers/buffer/resizable_buffer.cu
Tests construction, capacity tracking, preservation and discard semantics, reallocation, streams, move operations, and swapping.

Assessment against linked issues

Objective Addressed Explanation
Provide an internal cuda::buffer variant with separate capacity and size, including resizing support [#10213]

Suggested reviewers: ericniebler


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
libcudacxx/include/cuda/__container/buffer.h (1)

829-846: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

suggestion: Add _CCCL_HOST_API to both __copy_cross_buffers overloads; these helpers are the only functions in this section missing the visibility macro.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b3e156b4-7280-4f44-b335-ff2b626a3807

📥 Commits

Reviewing files that changed from the base of the PR and between f59807c and 18d178c.

📒 Files selected for processing (4)
  • libcudacxx/include/cuda/__container/buffer.h
  • libcudacxx/include/cuda/__container/resizable_buffer.h
  • libcudacxx/include/cuda/__container/uninitialized_async_buffer.h
  • libcudacxx/test/libcudacxx/cuda/containers/buffer/resizable_buffer.cu

@github-actions

This comment has been minimized.

Comment thread libcudacxx/include/cuda/__container/uninitialized_async_buffer.h
Comment on lines +155 to +163
_CCCL_HOST_API void resize(size_type __new_size, ::cuda::no_init_t)
{
if (__new_size > __capacity_)
{
_CCCL_THROW(::std::invalid_argument,
"cuda::__resizable_buffer::resize requires an explicit stream to grow beyond capacity");
}
__base_t::__set_size_unsynchronized(__new_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.

We can delete this overload and just require the stream always.

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.

I would like to keep at least one of the non-stream overloads, I think as long as reallocation is not triggered and the user knows the reallocation won't happen, its good to have an option to do it in non-stream order

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 is should be called resize_unsynchronized() or something like that. I also don't really see the utility of it over resize_discard(). Both throw the old end elements into the oubliette, but this one will throw if it tries to grow. I guess it is useful if you want to ensure you aren't reallocating? But then it should have a different name.

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.

I think the main difference is semantics. The one that takes a stream is stream ordered and if there is any work in flight that uses the buffer you have to use a stream that captures that work in the resize call, otherwise you risk reallocation racing with the work. I think in most cases you should be able to provide a stream like that, but I thought we could have a variant that is non-stream ordered and only updates the CPU view of the memory. I don't know how useful it will be but for internal version I wanted to try it

Comment thread libcudacxx/include/cuda/__container/resizable_buffer.h Outdated
Comment thread libcudacxx/include/cuda/__container/resizable_buffer.h Outdated
Comment thread libcudacxx/include/cuda/__container/uninitialized_async_buffer.h Outdated
Comment thread libcudacxx/test/libcudacxx/cuda/containers/buffer/resizable_buffer.cu Outdated
Comment thread libcudacxx/test/libcudacxx/cuda/containers/buffer/resizable_buffer.cu Outdated
Comment thread libcudacxx/test/libcudacxx/cuda/containers/buffer/resizable_buffer.cu Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
libcudacxx/test/libcudacxx/cuda/containers/buffer/resizable_buffer.cu (1)

28-49: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

suggestion: check_prefix's second parameter (the expected-values array) is never used — check_prefix_kernel only compares against the global device_data, not the array passed in. All current call sites happen to pass a prefix of device_data, so this coincidentally works, but the function doesn't actually verify the values callers pass; a future call with different expected data would silently pass incorrectly.

Proposed fix: actually compare against the passed values
-template <typename _Iter>
-__global__ void check_prefix_kernel(_Iter ptr, cuda::std::size_t size)
-{
-  for (cuda::std::size_t i = 0; i != size; ++i)
-  {
-    if (ptr[i] != device_data[i])
-    {
-      __trap();
-    }
-  }
-}
-
-template <cuda::std::size_t _Size>
-void check_prefix(const buffer_t& buf, const cuda::std::array<int, _Size>&)
+template <typename _Iter, typename _Expected>
+__global__ void check_prefix_kernel(_Iter ptr, _Expected expected, cuda::std::size_t size)
+{
+  for (cuda::std::size_t i = 0; i != size; ++i)
+  {
+    if (ptr[i] != expected[i])
+    {
+      __trap();
+    }
+  }
+}
+
+template <cuda::std::size_t _Size>
+void check_prefix(const buffer_t& buf, const cuda::std::array<int, _Size>& expected)
 {
   REQUIRE(buf.size() >= _Size);

   cuda::__ensure_current_context guard{buf.stream()};
-  check_prefix_kernel<<<1, 1, 0, buf.stream().get()>>>(buf.begin(), _Size);
+  check_prefix_kernel<<<1, 1, 0, buf.stream().get()>>>(buf.begin(), expected, _Size);
   REQUIRE(::cudaGetLastError() == ::cudaSuccess);
   buf.stream().sync();
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: bc5d2c26-299c-4a09-aef1-30fc39b5f427

📥 Commits

Reviewing files that changed from the base of the PR and between efa0815 and e62716b.

📒 Files selected for processing (4)
  • libcudacxx/include/cuda/__container/buffer.h
  • libcudacxx/include/cuda/__container/resizable_buffer.h
  • libcudacxx/include/cuda/__container/uninitialized_async_buffer.h
  • libcudacxx/test/libcudacxx/cuda/containers/buffer/resizable_buffer.cu
🚧 Files skipped from review as they are similar to previous changes (1)
  • libcudacxx/include/cuda/__container/uninitialized_async_buffer.h

@Jacobfaib Jacobfaib left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Some bikeshedding

//! @brief Helper to check container is compatible with this buffer
template <class _Range>
static constexpr bool __compatible_range = (::cuda::std::ranges::__container_compatible_range<_Range, _Tp>);
[[nodiscard]] static constexpr bool __compatible_range() noexcept

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.

Why make these functions?

Comment on lines +82 to +84
const auto __old_capacity = __capacity_;

__capacity_ = 0;

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.

Suggested change
const auto __old_capacity = __capacity_;
__capacity_ = 0;
const auto __old_capacity = ::cuda::std::exchange(__capacity_, 0);

{
const auto __old_size = this->size();
const auto __old_capacity = __capacity_;
_CCCL_ASSERT(__old_size <= __new_capacity, "cuda::__resizable_buffer cannot reallocate below 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.

Nit: the error message is a little confusing here

Comment on lines +155 to +163
_CCCL_HOST_API void resize(size_type __new_size, ::cuda::no_init_t)
{
if (__new_size > __capacity_)
{
_CCCL_THROW(::std::invalid_argument,
"cuda::__resizable_buffer::resize requires an explicit stream to grow beyond capacity");
}
__base_t::__set_size_unsynchronized(__new_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.

The is should be called resize_unsynchronized() or something like that. I also don't really see the utility of it over resize_discard(). Both throw the old end elements into the oubliette, but this one will throw if it tries to grow. I guess it is useful if you want to ensure you aren't reallocating? But then it should have a different name.

@github-actions

This comment has been minimized.

@Jacobfaib Jacobfaib mentioned this pull request Jul 24, 2026
2 tasks
@github-actions

Copy link
Copy Markdown
Contributor

😬 CI Workflow Results

🟥 Finished in 1h 46m: Pass: 95%/120 | Total: 1d 11h | Max: 1h 31m | Hits: 99%/336859

See results here.

@pciolkosz
pciolkosz force-pushed the cuda-buffer-capacity-resize branch from be592cf to 7940e31 Compare July 27, 2026 20:01
Comment on lines +445 to +452
const auto __old_stream = __stream_;
__uninitialized_async_buffer __ret{
__fake_resource_ref{::cuda::std::addressof(__mr_)}, __stream_, __count, __alignment_};
__fake_resource_ref{::cuda::std::addressof(__mr_)}, __stream, __count, __alignment_};
::cuda::std::swap(__count_, __ret.__count_);
::cuda::std::swap(__alignment_, __ret.__alignment_);
::cuda::std::swap(__buf_, __ret.__buf_);
__stream_ = __stream;
__ret.__stream_ = __old_stream;

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.

Couldnt we just use cuda::std::exchange

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.

Also why is this creating the new allocation on __stream and then destroying it on __old_stream afterwards

Comment on lines 135 to +147
template <class _Range>
static constexpr bool __compatible_range = (::cuda::std::ranges::__container_compatible_range<_Range, _Tp>);
[[nodiscard]] static constexpr bool __compatible_range() noexcept
{
return ::cuda::std::ranges::__container_compatible_range<_Range, _Tp>;
}

//! @brief Helper to check whether a different buffer still satisfies all
//! properties of this one
template <class... _OtherProperties>
static constexpr bool __properties_match =
::cuda::std::__type_set_contains_v<::cuda::std::__make_type_set<_OtherProperties...>, _Properties...>;
[[nodiscard]] static constexpr bool __properties_match() noexcept
{
return ::cuda::std::__type_set_contains_v<::cuda::std::__make_type_set<_OtherProperties...>, _Properties...>;
}

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 am not sure about those, they should stay static constexpr bool variables afaik

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

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

[FEA]: Internal cuda::buffer variant that has capacity and size

3 participants