NanoVDB: per-leaf VBM build kernel; cache leafCount for async rebuilds - #2291
Open
swahtz wants to merge 3 commits into
Open
NanoVDB: per-leaf VBM build kernel; cache leafCount for async rebuilds#2291swahtz wants to merge 3 commits into
swahtz wants to merge 3 commits into
Conversation
Rework the CUDA VoxelBlockManager build for speed: - BuildVoxelBlockManagerFunctor now launches one thread per leaf node, indexing the dense leaf array directly (valid since the build requires isSequential()), instead of one thread per lower-node child slot gated on the childMask. This removes the 2D (lowerCount x slices) launch, the getChild indirection, and all idle threads on sparse lower nodes, matching the structure of the host-side builder. nBlocks is now uint64_t end-to-end. - VoxelBlockManagerHandle caches the source grid's leaf count (defaulted trailing constructor parameter; existing callers unaffected). The in-place device rebuild uses it instead of reading TreeData back from device memory, making repeated rebuilds fully stream-asynchronous; if absent it is read once and cached. The allocating device overload now performs a single D2H TreeData read for both the voxel count and the leaf count. Verified byte-identical firstLeafID/jumpMap output against the unchanged CPU builder on dense and sparse domains at Log2BlockWidth 6/7/12, including repeated rebuilds through the cached-leafCount path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…uild The build kernel now reads the authoritative leaf count from the device-resident tree and iterates with a grid-stride loop, so the host-side (possibly cached) count only sizes the launch: an undersized launch strides over the tail, an oversized one retires excess threads at the device-side bound. A stale cached count - e.g. after regenerating the grid in place under a live handle - can therefore no longer cause out-of-bounds leaf-array reads or missed leaves, restoring the robustness of the pre-cache code while keeping rebuilds fully stream-asynchronous. Also zero the jumpMap before the leafCount==0 early return, so a rebuild against an empty grid leaves zeroed metadata instead of stale bits, and update the handle/rebuild doc comments to the hint-only semantics. Verified byte-identical against the CPU reference builder for the allocating build, a normal cached rebuild, and rebuilds with the cache deliberately poisoned to ~1/7x and ~3x of the true count, across dense and sparse domains at Log2BlockWidth 6/7/12. Rebuild times on a 317M-voxel grid are unchanged within noise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two optimizations to the CUDA VoxelBlockManager build (
nanovdb/tools/cuda/VoxelBlockManager.cuh):Per-leaf build kernel.
BuildVoxelBlockManagerFunctornow launches one thread per leaf node, indexing the dense leaf array directly (valid since the build requiresisSequential()), instead of one thread per lower-node child slot gated on thechildMask. This removes the 2D(lowerCount × slices)launch, thegetChildindirection, and all idle threads on sparse lower nodes, and matches the structure of the host-side builder.nBlocksis nowuint64_tend-to-end.Stream-asynchronous rebuilds.
VoxelBlockManagerHandlecaches the source grid's leaf count (defaulted trailing constructor parameter; existing callers unaffected). The in-place device rebuild overload uses the cached value instead of readingTreeDataback from device memory on every call, removing a synchronous D2H copy from the rebuild path; if the handle lacks a cached count it is read once and cached. The allocating device overload now performs a single D2HTreeDataread supplying both the voxel count and the leaf count (previously up to two).The cached count is a launch-sizing hint only: the build kernel reads the authoritative leaf count from the device-resident tree and iterates with a grid-stride loop, so an undersized launch strides over the tail and an oversized one retires excess threads at the device-side bound. A stale cache (e.g. after regenerating the grid in place under a live handle) therefore degrades launch geometry but cannot cause out-of-bounds access or wrong output — verified byte-identical with the cache deliberately poisoned to ~1/7× and ~3× of the true count. The jumpMap is also zeroed before the empty-grid early return, so a rebuild against a grid reporting zero leaves leaves zeroed metadata rather than stale bits.
Performance
Measured on a 317M-active-voxel OnIndex grid (782k leaves, 1266 lower nodes, ~15% lower-node child occupancy) on an RTX PRO 6000 (Blackwell), CUDA 13.2. Per-rebuild averages over 100 iterations of the in-place rebuild overload:
Notes on attribution of the gain:
getTreeDataD2H copy serialized every call; the new rebuild pipelines down to ~18 µs and is genuinely stream-ordered.Verification
Device-built
firstLeafID/jumpMaparrays compared byte-for-byte against the unchanged CPU reference builder (nanovdb/tools/VoxelBlockManager.h) on a dense 21³ box and a sparse spherical shell at Log2BlockWidth 6, 7, and 12, including repeated rebuilds through the cached-leafCount path — all identical. The existingTestNanoVDBCUDA.VoxelBlockManager_*unit tests cover the decode path end-to-end on top of this metadata.🤖 Generated with Claude Code