Skip to content

[copilot mirror] B3: PointsToGrid onto cuda::Buffer - #3

Closed
harrism wants to merge 6 commits into
nanovdb-temppool-bufferfrom
nanovdb-pointstogrid-buffer
Closed

[copilot mirror] B3: PointsToGrid onto cuda::Buffer#3
harrism wants to merge 6 commits into
nanovdb-temppool-bufferfrom
nanovdb-pointstogrid-buffer

Conversation

@harrism

@harrism harrism commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Copilot-review mirror of AcademySoftwareFoundation#2270, based on the B2 branch so only B3's payload shows.

harrism and others added 2 commits August 5, 2026 03:53
The bisection search over voxel size was written as a backward goto,
which made the lifetimes of the buffers it retries over non-lexical.
Rewrite it as while(true) with continue on retry and break on
convergence; the six hand-written frees before the jump are unchanged.
The change is easiest to review with whitespace ignored, since the loop
body re-indents: git diff -w shows 27 changed lines.

d_keys and d_node_count carry results past the loop, so their
declarations move above it, as does the copy event, which was created
inside the retried region on every iteration but destroyed only once at
the end -- each retry leaked the previous handle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Mark Harris <mharris@nvidia.com>
Every device array PointsToGrid allocates is now owned by a
Buffer<T, ResourceRef<ResourceT>> borrowing the injected resource --
members where the pipeline frees them in a later member function than
the one that allocated them (countNodes allocates; processUpperNodes,
processLeafNodes, processPoints and processBBox release), locals where
the lifetime is contained. The raw pointers survive only as views: the
device-visible fields inside mData, and the working pointers the cub
and kernel calls take. Every owner event -- assignment, swap, destroy
-- immediately refreshes its view, and released views are nulled so a
stale use faults instead of reading a freed block. The index ping-pong
becomes a swap of owners across the member/local boundary, replacing
the bare pointer swap whose safety depended on nothing reading the
device copy of d_indx between the two uploads.

The density-search retry keeps its free-before-reallocate order via
explicit destroy calls, so peak device memory is unchanged. The
hand-matched byte sizes at every free site disappear, and the arrays
released one line before scope exit now just leave scope. One behavior
change worth naming: the too-many-points-per-leaf throw previously
leaked the reduction scratch; ownership now releases it during unwind.

Verified against the previous commit with a counting resource over
three shapes (bulk segmented-sort branch, serial per-tile branch, and
the bisection retry engaged): allocation count, free count, and total
bytes are identical. Full CUDA and memory-resource suites unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Mark Harris <mharris@nvidia.com>
@harrism
harrism requested a lite review from Copilot August 5, 2026 04:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

This PR migrates NanoVDB’s CUDA PointsToGrid builder scratch/intermediate device allocations to nanovdb::cuda::Buffer (backed by an injectable memory resource), reducing manual allocate/free management and aligning with other GPU builders.

Changes:

  • Updated PointsToGrid to own device scratch + intermediate arrays via cuda::Buffer members and refresh raw-pointer views in mData.
  • Reworked the countNodes “dx search” loop to reuse a single CUDA event and to free buffers via Buffer::destroy() instead of paired allocate_async/deallocate_async.
  • Updated pending change notes to document the PointsToGrid migration.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
pendingchanges/nanovdb.txt Documents the PointsToGrid scratch/intermediate allocation change to cuda::Buffer.
nanovdb/nanovdb/tools/cuda/PointsToGrid.cuh Replaces raw async allocations with cuda::Buffer ownership and updates lifecycle management across the pipeline.

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

Comment on lines +643 to +644
cudaEvent_t copyEvent;
cudaCheck(cudaEventCreate(&copyEvent));

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 4957582 — the event is now owned by a scope guard, so the too-many-points-per-leaf throw releases it during unwinding, consistent with the buffer ownership this PR introduces.

harrism and others added 2 commits August 5, 2026 05:06
Signed-off-by: Mark Harris <mharris@nvidia.com>
The too-many-points-per-leaf throw unwinds past the event's manual
destroy, leaking the handle. Own it with a small guard so unwinding
releases it, consistent with the buffer ownership in this function.
Also assert the stream-ordered resource requirement on the class, so a
synchronous-only resource fails naming PointsToGrid rather than the
pool inside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Mark Harris <mharris@nvidia.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

nanovdb/nanovdb/tools/cuda/PointsToGrid.cuh:653

  • Calling cudaCheck(...) in a destructor is risky if cudaCheck can throw (it can lead to std::terminate during stack unwinding). Prefer making the destructor non-throwing: call cudaEventDestroy(event) directly (optionally ignoring errors), or adjust cudaCheck usage here to guarantee noexcept behavior.
    struct EventGuard {
        cudaEvent_t event;
        EventGuard() { cudaCheck(cudaEventCreate(&event)); }
        ~EventGuard() { cudaCheck(cudaEventDestroy(event)); }
        EventGuard(const EventGuard&) = delete;
        EventGuard& operator=(const EventGuard&) = delete;
    } eventGuard;

nanovdb/nanovdb/tools/cuda/PointsToGrid.cuh:722

  • This manual new[]/delete[] is not exception-safe if any of the CUDA/CUB checks throw between allocation and delete[]. Use an RAII container (e.g., std::vector<uint32_t> or std::unique_ptr<uint32_t[]>) so host memory is released automatically on early exits.
            uint32_t *points_per_tile = new uint32_t[mData.nodeCount[2]];
            cudaCheck(cudaMemcpyAsync(points_per_tile, d_points_per_tile, mData.nodeCount[2]*sizeof(uint32_t), cudaMemcpyDeviceToHost, mStream));
            pointsPerTileScratch.destroy(); d_points_per_tile = nullptr;
            for (uint32_t id = 0, offset = 0; id < mData.nodeCount[2]; ++id) {
                const uint32_t count = points_per_tile[id];
                util::cuda::offsetLambdaKernel<<<numBlocks(count), mNumThreads, 0, mStream>>>(count, offset, VoxelKeyFunctor<BuildT, PtrT>(), mDeviceData, points, id, d_keys, d_indx);
                cudaCheckError();
                CALL_CUBS(DeviceRadixSort::SortPairs, d_keys + offset, mData.d_keys + offset, d_indx + offset, mData.d_indx + offset, count, 0, 36);
                offset += count;
            }
            delete [] points_per_tile;

nanovdb/nanovdb/tools/cuda/PointsToGrid.cuh:408

  • ref() does not modify object state and can be marked const (and potentially noexcept) to improve API correctness and allow calling it from const contexts.
    nanovdb::cuda::ResourceRef<ResourceT> ref() { return nanovdb::cuda::ResourceRef<ResourceT>(*mResource); }

util::cuda::lambdaKernel<<<numBlocks(mData.nodeCount[1]), mNumThreads, 0, mStream>>>(mData.nodeCount[1], PropagateLowerBBoxFunctor<BuildT>(), mDeviceData);
mResource->deallocate_async(mData.d_lower_keys, mData.nodeCount[1]*sizeof(uint64_t), ResourceT::DEFAULT_ALIGNMENT, mStream);
mLowerKeysBuf.destroy(); mData.d_lower_keys = nullptr;
cudaCheckError()

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 2e1e237 — for consistency, not correctness: it compiled because cudaCheckError expands to a braced block, so no semicolon is required, but every other use in the file spells it as a statement. Pre-existing, incidentally (visible at line 1328 of the base).

harrism and others added 2 commits August 5, 2026 05:29
Signed-off-by: Mark Harris <mharris@nvidia.com>
Legal without one -- the macro expands to a braced block -- but every
other use in the file spells it as a statement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Mark Harris <mharris@nvidia.com>
@harrism

harrism commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

Copilot review cycles converged (final cycle: no new findings). All accepted changes are on the shared branch and reflected in the upstream PR; threads here document the dismissals. Branch retained.

@harrism harrism closed this Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants