Route NanoVDB builder scratch through PyTorch's active CUDA allocator via upstream memory-resource seams - #732
Conversation
Points the nanovdb pin at the merge of AcademySoftwareFoundation/openvdb PRs #2268, #2269, #2270, #2272 and #2273 (tracking issue #2232), which give every CUDA builder fvdb uses a ResourceT injection seam. Temporary until the stack merges upstream; the pin is a fast-forward of the previous one (f9754140 is an ancestor of the stack's base). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Adds fvdb::TorchResource, a stream-ordered nanovdb memory resource backed by c10::cuda::CUDACachingAllocator (raw_alloc_with_stream / raw_delete), and passes it as the ResourceT template parameter at every builder call site: voxelsToGrid and the DilateGrid / MergeGrids / PruneGrid / RefineGrid / CoarsenGrid ops. Builder scratch (sort keys, CUB temp storage, topology mask buffers) now lives in the same pool as fvdb / PyTorch tensors instead of a second cudaMallocAsync pool that fragments VRAM against it. This supersedes the forked-header approach of openvdb#655: same allocator routing, but through upstream nanovdb's injection seams instead of shadowed copies of DeviceBuffer.h / DeviceResource.h. The FVDB_NANOVDB_TRACE_ALLOCS env var from that PR is preserved inside TorchResource (=1 traces allocs >= 256 KiB, =2 traces all). Not routed (no upstream seam yet, all off the hot paths): DistributedPointsToGrid multi-GPU scratch, indexToGrid scratch in SaveNanoVDB, and the builders' small dual-space mProcessedRoot / mData buffers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…esource work Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Two remaining device-scratch sites bypassed TorchResource and allocated from nanoVDB's separate cudaMallocAsync pool, partitioning VRAM against torch's. fvdb's own morphology::PadGrid drives nanoVDB's TopologyBuilder (internal mask buffers, countNodes CUB scratch, TempPool) but hardcoded the default DeviceResource. Add a ResourceT template parameter mirroring the upstream DilateGrid signature and forward it to TopologyBuilder, then pass TorchResource at all seven call sites. This removes a split where the odd-kernel branch of the conv builders used DilateGrid<..., TorchResource> while the even-kernel branch immediately below used PadGrid on the rival pool. BuildFineGridFromCoarse allocated CUB DeviceSegmentedReduce temp storage with a bare cudaMallocAsync; route it through TorchResource and check both cub calls, which were previously unchecked. The ResourceT default keeps PadGrid source-compatible. Raise the from_nearest_voxels_to_points peak-memory bound from 150 to 200 MiB. PadGrid scratch (~78 MiB at 2M points) is now visible to torch.cuda.max_memory_allocated() where it previously was not. Total device consumption is unchanged -- only the accounting moved -- and the guard against the old >300 MiB coordinate-materialization path is retained. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
Bump the nanovdb pin to openvdb master 7946f17e to pick up the small-builder ResourceT seams (AcademySoftwareFoundation/openvdb#2286), plus the synchronous resource adapters (#2272) and the MeshToGrid CALL_CUBS undef fix (#2284). The CUDA save path allocated its largest buffers from nanoVDB's default DeviceBuffer pool: the per-batch (N+1)-element value staging buffer, the indexToGrid output grid handle, and the defensive host-upload buffer. All three now use TorchDeviceBuffer, and indexToGrid's internal scratch (its device NodeAccessor) routes through TorchResource via the new #2286 seam. Stream-ordering semantics are preserved: the replaced DeviceBuffer constructors were stream-ordered cudaMallocAsync on the current stream, and TorchDeviceBuffer allocates via raw_alloc, which torch orders on the current stream -- identical here since every construction sits under the existing CUDAGuard with the same current stream the copies and kernels are queued on. The host path (indexToGridHost) and the HostBuffer file-staging buffers are unchanged. Verified: tests/unit/test_io.py (622 passed) and a traced save (FVDB_NANOVDB_TRACE_ALLOCS=2) showing the indexToGrid scratch flowing through TorchResource. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
…DA allocator c10::cuda::CUDACachingAllocator::raw_alloc_with_stream / raw_delete are namespace-level dispatchers through CUDACachingAllocator::get(), so TorchResource follows whatever allocator the Torch runtime has installed: the native caching allocator, the cudaMallocAsync backend, or a CUDAPluggableAllocator installed via change_current_allocator. Reword the header comments to say so rather than implying a hardcoded binding to the native caching allocator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
|
One suggestion on plumbing, not policy. The default here is right — But naming The future that needs it is already on the books: the ONNX plan (#579) runs these builders in a non-torch process where c10 doesn't exist, and an |
…rce alias Per review feedback from @harrism: naming TorchResource at every leaf call site hard-wires the allocator policy the same way nanoVDB itself just un-hard-wired it. All 22 ResourceT bindings across the 13 ops files now name fvdb::BuilderResource, a one-line alias (BuilderResource.h) currently set to TorchResource. A build that must run the builders without torch (e.g. the ONNX Runtime EP planned in openvdb#579) retargets the alias in one place instead of touching every op. The seam is compile-time and relies on the resource being stateless; a stateful resource (per-session allocator handle) would additionally need an instance plumbed through the call sites — noted in the header. All 13 affected translation units compile cleanly against the branch's nanovdb pin. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
|
Agreed — done in 416fa43. All 22 Two notes on scope, called out in the header:
All 13 affected TUs compile cleanly against the branch's nanovdb pin. |
Summary
fvdb's grid builders allocate their device scratch from nanoVDB's default
DeviceResource— a secondcudaMallocAsyncpool that partitions VRAM against PyTorch's. Large workloads (e.g. multi-frame TSDF integration) then hit a clean OOM even when the GPU has free memory in aggregate.This routes that scratch — O(N-points) sort keys, CUB temp storage, topology mask buffers — through PyTorch's CUDA allocator instead, so it shares one pool with fvdb / PyTorch tensors. 22 sites across 13
.cufiles plusPadGrid.cuh.Note this is not hardcoded to Torch's native caching allocator:
c10::cuda::CUDACachingAllocatoris a namespace, and itsraw_alloc_with_stream/raw_deletefree functions dispatch throughCUDACachingAllocator::get()— the runtime-swappable allocator Torch itself allocates tensors from. fvdb's scratch therefore follows whatever allocator the user has installed: the native caching allocator (includingPYTORCH_CUDA_ALLOC_CONFknobs), thecudaMallocAsyncbackend (PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync), or a custom allocator installed viatorch.cuda.memory.change_current_allocator(CUDAPluggableAllocator(...)).Supersedes #655, which vendored modified nanoVDB headers into the tree. This instead uses the injectable-memory-resource seams we developed upstream (AcademySoftwareFoundation/openvdb#2232; PRs #2268, #2269, #2270, #2272, #2273) — now merged, so the pin is plain upstream master. No fork, no include-path shadowing, no resync procedure.
What's in this PR
Pin nanovdb to upstream master.
src/cmake/get_nanovdb.cmake→AcademySoftwareFoundation/openvdb @ 7946f17e, which includes the small-builderResourceTseams (#2286), the synchronous resource adapters (#2272), and theMeshToGridCALL_CUBS#undeffix (#2284).fvdb::TorchResource. A ~40-line stateless resource (src/fvdb/TorchResource.h) modeling nanoVDB's stream-orderedAsyncResourceconcept overc10::cuda::CUDACachingAllocator::raw_alloc_with_stream/raw_delete— the dispatchers to Torch's currently active CUDA allocator (see Summary). Passed as theResourceTtemplate parameter at all 13 upstream builder call sites —voxelsToGrid,DilateGrid,MergeGrids,PruneGrid,RefineGrid,CoarsenGrid— always via thefvdb::BuilderResourcealias (src/fvdb/BuilderResource.h), never named directly, so the allocator policy lives in a single line (a non-torch build, e.g. the ONNX Runtime EP planned in Proposal: ⚫ ONNX Runtime support for fVDB operations #579, retargets the alias there instead of touching every op). Being stateless, it binds through each builder's defaulted constructor argument, so no instance is plumbed through. Retains Fork a small set of nanoVDB headers to share PyTorch's CUDA allocator #655'sFVDB_NANOVDB_TRACE_ALLOCStracing (=1traces ≥ 256 KiB, a value starting with2traces everything).PadGridgains aResourceTseam. The conv builders usedDilateGrid<..., TorchResource>for odd kernels andPadGrid— on the rival pool — for even ones: same loop, same grid, a different allocator depending on kernel parity. fvdb's ownmorphology::PadGriddrives nanoVDB'sTopologyBuilder(internal mask buffers,countNodesCUB scratch,TempPool) but hardcodedDeviceResource. It now takes aResourceTparameter mirroring the upstreamDilateGridsignature and forwards it, withBuilderResourcepassed at all 7 call sites. The default keeps it source-compatible.CUB scratch in
BuildFineGridFromCoarse.cub::DeviceSegmentedReducetemp storage used a barecudaMallocAsync; it now routes throughBuilderResource. Bothcubcalls are also nowC10_CUDA_CHECK-wrapped — previously unchecked, as was the allocation.The
SaveNanoVDBCUDA path. The save path allocated its largest device buffers from nanoVDB's default pool: the per-batch(N+1)-element value staging buffer, theindexToGridoutput grid handle, and the defensive host-upload buffer. All three now useTorchDeviceBuffer, andindexToGrid's internal scratch routes throughTorchResourcevia the #2286 seam. Stream-ordering is preserved: the replaced stream-orderedDeviceBufferconstructors becomeraw_allocon the same current stream the copies and kernels are queued on. The host path (indexToGridHost) and theHostBufferfile-staging buffers are unchanged.Not routed (no upstream seam yet; all off the hot paths)
DistributedPointsToGridmulti-GPU scratch (deferred upstream behind NanoVDB: handle empty partitions in the distributed merge path search AcademySoftwareFoundation/openvdb#2248) — the most valuable remaining seamVoxelBlockManager/buildVoxelBlockManagerscratch inReinitializeSdf.cumProcessedRoot/mDatabuffers (upstream roadmap Step 3)MeshToGridis the one merged seam fvdb does not use:BuildGridFromMesh.cudoes its own parametric surface sampling and goes through_createNanoGridFromIJK, so there is nothing to route.Test plan
./build.sh installsucceeds on a clean tree against the new pin (full CUDA build,-Werror).FVDB_NANOVDB_TRACE_ALLOCS: a 500k-pointGrid.from_points+dilated_grid(2)prints 42TorchResourcetraces with correct results (484,631 → 8,461,871 voxels);from_nearest_voxels_to_pointsat 2M points showsPadGridscratch routed.BuildFineGridFromCoarseCUB path).test_io.py— 622 passed against the7946f17epin; a tracedsave_nanovdb(FVDB_NANOVDB_TRACE_ALLOCS=2) shows theindexToGridscratch flowing throughTorchResource.Followups
TorchResourcethrough the new ops it adds.🤖 Generated with Claude Code