Conversation
5 tasks
swahtz
force-pushed
the
nanovdb-topology-builder-batch
branch
from
September 15, 2026 22:15
725f2f4 to
8f111e4
Compare
TopologyBuilder can now build B independent grids into one buffer in a single pass of kernel launches, laid out back to back with mGridIndex = g and mGridCount = B. Single-grid builds are unchanged: the existing consumers are untouched and produce byte-identical output. TopologyBuilderData carries the grid's batch position and the first index of its tiles and nodes in the batch-wide scratch arrays; the builder holds one per grid. Processed roots are staged per grid in one pinned buffer (allocateProcessedRoots), countNodes gathers per-grid counts and bases at each grid's tile boundaries with one readback, getBuffer lays the grids back to back, and the build functors rebase batch-wide node offsets into each grid's own node arrays. d_upperOffsets is now set for every grid, including empty ones. ProcessLowerNodesFunctor gains a batched overload; the single-grid one stays and both share one body. topology::detail gains the helpers a batch caller needs to stage one speculative root per grid (tileSortKey, ProcessedTileMap, insertProcessedTile(s), packProcessedRoot). The existing consumers keep their own root code; moving them onto the helpers is a separate change. Adds TestNanoVDBCUDA.TopologyBuilderBatch_ValueOnIndex: three members (the refine/coarsen fixture, an empty grid, voxels across the +-4096 tile boundaries) refined in one pass through the new helpers and byte-compared against RefineGrid, on its original root enumeration, for each member alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…onventions and document it Follows the rules of record from AcademySoftwareFoundation#2232 and AcademySoftwareFoundation#2322 in the new code: the tile-to-grid table is staged in a pinned cuda::Buffer<uint32_t> so every transfer the builder issues is asynchronous, and the batch test builds on single-space GridHandle<cuda::Buffer<std::byte>> handles and reads results through cuda::copyTo instead of the deprecated dual accessors. Adds a class-level usage example with the synchronization contract and declaration comments on each stage, keeps the private type aliases in one block, makes updateTileLayout private, has allocateProcessedRoots return void, drops the this-> prefixes the header does not otherwise use, gives the test a typed device-upload helper, and records the capability in pendingchanges/nanovdb.txt. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
swahtz
force-pushed
the
nanovdb-topology-builder-batch
branch
from
September 15, 2026 23:17
4a07469 to
e8e12aa
Compare
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.
Summary
tools::cuda::TopologyBuildercan now build a batch of B independent grids into one buffer in a single pass of kernel launches, laid out back to back withmGridIndex = gandmGridCount = B. Single-grid builds are unchanged: every consumer (RefineGrid,CoarsenGrid,DilateGrid,PruneGrid,MergeGrids,MeshToGrid) compiles without edits and produces byte-identical output.Motivation: downstream users that build many small grids per step (fVDB's generated convolution topologies, openvdb/fvdb-core#785) currently loop over members, paying several stream synchronizations per grid plus one more per grid to merge the handles. With this change they run the builder once over the batch and pay the synchronizations once.
What changed
TopologyBuilderDatagains the grid's batch position (gridIndex,gridCount) and the first index of its tiles and nodes in the batch-wide scratch arrays (tileCount,tileBase,upperBase,lowerBase,leafBase,processedRootOffset). The builder holds oneDataper grid (pinned host staging plus a device array);data()still means grid 0.Processed roots are staged per grid in one pinned buffer.
allocateProcessedRoots(tileCounts)lays out B roots;allocateProcessedRoot(bytes)is the single-grid form and resets the batch to one grid.uploadProcessedRootalso uploads atileToGridtable for batches.hostProcessedRoot(g)/deviceProcessedRoot(g)default to grid 0.countNodeskeeps its batch-wideEnumerateNodesFunctorlaunch and inclusive scans; the three scalar total readbacks become one B-thread gather of per-grid counts and bases at each grid's tile boundaries and one D2H of theDataarray.getBufferlays the grids back to back in one allocation.d_upperOffsetsis now set for every grid, including empty ones (it was left uninitialized whennodeCount[2] == 0).Build functors locate their grid and rebase batch-wide node offsets into that grid's node arrays:
BuildGridTreeRootFunctor/InitGridTreeRootFunctorrun over B threads and write the grid's own index and count;BuildUpperNodesFunctorandProcessLowerNodesFunctorresolve the grid throughtileToGrid; the leaf-offset, bbox and post-process functors resolve it by binary search over the per-grid bases (topology::detail::gridOfIndex). LeafmOffsetvalues andtree.mVoxelCountare rebased per grid. NewprocessGridTreeRoot(stream)launches the header functor over the batch.ProcessLowerNodesFunctor(util/cuda/Morphology.cuh) gets a batched overload taking theDataarray andtileToGrid; the single-grid overload stays and both share one body.Speculative-root helpers in
topology::detail, for batch callers that must stage one root per grid:tileSortKey(the PointsToGrid offset-shifted key),ProcessedTileMap,insertProcessedTile,insertProcessedTiles(every 4096-aligned tile overlapping a bbox, with floor semantics for negative coordinates) andpackProcessedRoot. The existing consumers keep their own root enumeration, so the batch test checks these helpers against that code rather than against themselves.The mask-fill and leaf-fill functors (
*InternalNodesFunctor,*LeafMasksFunctor) need no changes: a batch driver offsets their processed-root and mask-row pointers per grid, as the new test does.Memory-resource conventions (#2232, #2322). All new scratch is typed
cuda::Buffer<T>sized in elements with the mandatorynoInit, memsets usesize_bytes(), device buffers borrow the injected resource throughResourceRef, and only the variable-length root image stays a byte buffer. The builder's host-side staging is pinned throughout: the per-gridDataarray (Buffer<Data, PinnedResource>, replacing the pageableDatamember, so thecountNodesreadback is now genuinely asynchronous), the processed roots, and the tile-to-grid table. No pageable memory is handed tocudaMemcpyAsyncanywhere in the builder.How the batched build works
The example is the new unit test: three grids refined in one pass, with g1 empty. The top panel is master, where batching means B independent builds (each with its root readback, count sync, final sync and the two syncs in the
GridHandleconstructor) followed bymergeGridHandlesand one more sync per grid. The bottom panel is this PR: scratch is indexed by processed tile across the batch,countNodesgathers each grid's node counts and bases at its tile boundaries with one readback,getBufferlays the grids back to back, and every build kernel resolves its grid (tileToGrid[t]for tile-indexed kernels, a binary search over the per-grid bases for node-indexed ones) and subtracts that grid's bases. The consumer's mask-fill and leaf-fill functors are unchanged; a batch driver only offsets their processed-root and mask-row pointers per grid.Compatibility
Not a breaking change for existing consumers, whose files are unchanged in this PR. Every method, accessor and
Datafield they use keeps its signature and meaning for one grid; their ownlambdaKernel<<<1,1>>>(1, BuildGridTreeRootFunctor, deviceData())launches keep working (index 0 of a one-element array). The only behavioral change is insidecountNodes, where one D2H of theDataarray replaces three scalar copies under the same sync-before-getBuffercontract.Testing
TestNanoVDBCUDA.TopologyBuilderBatch_ValueOnIndex: three members (the refine/coarsen fixture, an empty grid, voxels across the +-4096 tile boundaries and in negative octants) refined in one builder pass on single-spaceGridHandle<cuda::Buffer<std::byte>>handles; the device handle constructor validates the three-grid chain, and the result is brought to a pinned handle withcuda::copyToand byte-compared againstRefineGridon each member alone after setting the grid index and count. The test uses no dual-buffer accessors.nanovdb_test_cudarun: 65 of 66 pass; the one failure,UnifiedBuffer_IO, cannot opendata/3_spheres.nvdbfrom the build directory and fails identically without this change.nanovdb_test_cuda_memory_resource: 19 of 19 pass.compute-sanitizer --tool memcheck(2025.3 and 2025.4) reportscudaErrorIllegalInstructioninside theProcessLowerNodesFunctorlaunch on both this branch and untouched master with the same toolchain, so it is a pre-existing sanitizer/toolchain interaction on this setup, not something this change introduces. The tests themselves pass outside the sanitizer.Built with
NANOVDB_CUDA_WERROR=ON, CUDA 13.2, sm_120 (RTX PRO 6000 Blackwell). Rebased on current master;pendingchanges/nanovdb.txthas an entry under Improvements.🤖 Generated with Claude Code