Skip to content

Route NanoVDB builder scratch through PyTorch's active CUDA allocator via upstream memory-resource seams - #732

Merged
swahtz merged 9 commits into
openvdb:mainfrom
swahtz:feat/nanovdb-torch-resource
Aug 20, 2026
Merged

Route NanoVDB builder scratch through PyTorch's active CUDA allocator via upstream memory-resource seams#732
swahtz merged 9 commits into
openvdb:mainfrom
swahtz:feat/nanovdb-torch-resource

Conversation

@swahtz

@swahtz swahtz commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

fvdb's grid builders allocate their device scratch from nanoVDB's default DeviceResource — a second cudaMallocAsync pool 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 .cu files plus PadGrid.cuh.

Note this is not hardcoded to Torch's native caching allocator: c10::cuda::CUDACachingAllocator is a namespace, and its raw_alloc_with_stream / raw_delete free functions dispatch through CUDACachingAllocator::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 (including PYTORCH_CUDA_ALLOC_CONF knobs), the cudaMallocAsync backend (PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync), or a custom allocator installed via torch.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

  1. Pin nanovdb to upstream master. src/cmake/get_nanovdb.cmakeAcademySoftwareFoundation/openvdb @ 7946f17e, which includes the small-builder ResourceT seams (#2286), the synchronous resource adapters (#2272), and the MeshToGrid CALL_CUBS #undef fix (#2284).

  2. fvdb::TorchResource. A ~40-line stateless resource (src/fvdb/TorchResource.h) modeling nanoVDB's stream-ordered AsyncResource concept over c10::cuda::CUDACachingAllocator::raw_alloc_with_stream / raw_delete — the dispatchers to Torch's currently active CUDA allocator (see Summary). Passed as the ResourceT template parameter at all 13 upstream builder call sites — voxelsToGrid, DilateGrid, MergeGrids, PruneGrid, RefineGrid, CoarsenGrid — always via the fvdb::BuilderResource alias (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's FVDB_NANOVDB_TRACE_ALLOCS tracing (=1 traces ≥ 256 KiB, a value starting with 2 traces everything).

  3. PadGrid gains a ResourceT seam. The conv builders used DilateGrid<..., TorchResource> for odd kernels and PadGrid — on the rival pool — for even ones: same loop, same grid, a different allocator depending on kernel parity. fvdb's own morphology::PadGrid drives nanoVDB's TopologyBuilder (internal mask buffers, countNodes CUB scratch, TempPool) but hardcoded DeviceResource. It now takes a ResourceT parameter mirroring the upstream DilateGrid signature and forwards it, with BuilderResource passed at all 7 call sites. The default keeps it source-compatible.

  4. CUB scratch in BuildFineGridFromCoarse. cub::DeviceSegmentedReduce temp storage used a bare cudaMallocAsync; it now routes through BuilderResource. Both cub calls are also now C10_CUDA_CHECK-wrapped — previously unchecked, as was the allocation.

  5. The SaveNanoVDB CUDA path. The save path allocated its largest device buffers from nanoVDB's default 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 routes through TorchResource via the #2286 seam. Stream-ordering is preserved: the replaced stream-ordered DeviceBuffer constructors become raw_alloc on the same current stream the copies and kernels are queued on. The host path (indexToGridHost) and the HostBuffer file-staging buffers are unchanged.

Not routed (no upstream seam yet; all off the hot paths)

MeshToGrid is the one merged seam fvdb does not use: BuildGridFromMesh.cu does its own parametric surface sampling and goes through _createNanoGridFromIJK, so there is nothing to route.

Test plan

  • ./build.sh install succeeds on a clean tree against the new pin (full CUDA build, -Werror).
  • Injection verified live via FVDB_NANOVDB_TRACE_ALLOCS: a 500k-point Grid.from_points + dilated_grid(2) prints 42 TorchResource traces with correct results (484,631 → 8,461,871 voxels); from_nearest_voxels_to_points at 2M points shows PadGrid scratch routed.
  • 584 tests passed — conv semantics + integration (203), conv/conv-transpose default + prune + empty grids (103), basic ops (276, 1 skipped), sliced batch (2, covering the BuildFineGridFromCoarse CUB path).
  • test_io.py — 622 passed against the 7946f17e pin; a traced save_nanovdb (FVDB_NANOVDB_TRACE_ALLOCS=2) shows the indexToGrid scratch flowing through TorchResource.

Followups

🤖 Generated with Claude Code

swahtz and others added 2 commits August 7, 2026 17:03
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>
@swahtz
swahtz requested a review from a team as a code owner August 7, 2026 05:09
@swahtz
swahtz requested review from blackencino and phapalova August 7, 2026 05:09
@swahtz
swahtz marked this pull request as draft August 7, 2026 05:09
@swahtz swahtz added core library Core fVDB library. i.e. anything in the _Cpp module (C++) or fvdb python module Topology Operations Issues related to topology operations (prune, merge, dilate, etc. optimization Performance or memory optimization labels Aug 7, 2026
@swahtz swahtz added this to the v0.6 milestone Aug 7, 2026
@swahtz swahtz self-assigned this Aug 7, 2026
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
swahtz and others added 3 commits August 18, 2026 18:04
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>
@swahtz
swahtz marked this pull request as ready for review August 18, 2026 06:40
@swahtz swahtz closed this Aug 18, 2026
@swahtz swahtz reopened this Aug 18, 2026
@swahtz swahtz changed the title Route nanoVDB builder scratch through PyTorch's caching allocator via upstream memory-resource seams Route NanoVDB builder scratch through PyTorch's caching allocator via upstream memory-resource seams Aug 18, 2026
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>
@swahtz
swahtz requested a balanced review from Copilot August 19, 2026 22:37

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@swahtz
swahtz requested a review from harrism August 19, 2026 22:38
@swahtz swahtz mentioned this pull request Aug 20, 2026
…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>
@harrism

harrism commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

One suggestion on plumbing, not policy. The default here is right — raw_alloc_with_stream dispatches through CUDACachingAllocator::get(), i.e. the currently set torch allocator, so an application that installs a pluggable allocator (RMM's torch plug, etc.) already re-routes all of this scratch with no fvdb changes.

But naming TorchResource at ~22 leaf call sites hard-wires the choice the same way nanoVDB itself just spent five PRs un-hard-wiring. A one-line internal seam — namespace fvdb { using BuilderResource = TorchResource; } (or a member of the ops' context) — with the call sites naming BuilderResource gives the same binary and the same behavior, but the policy becomes a single line.

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 OrtAllocatorResource over ORT's arena would splice in at that alias (or graduate to a real injected instance through the ops' factory) instead of re-touching all 22 sites.

@swahtz swahtz changed the title Route NanoVDB builder scratch through PyTorch's caching allocator via upstream memory-resource seams Route NanoVDB builder scratch through PyTorch's active CUDA allocator via upstream memory-resource seams Aug 20, 2026
…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>
@swahtz

swahtz commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Agreed — done in 416fa43. All 22 ResourceT bindings across the 13 ops files now name fvdb::BuilderResource, a one-line alias in src/fvdb/BuilderResource.h currently set to TorchResource.

Two notes on scope, called out in the header:

  • The seam is compile-time and leans on the resource being stateless (builders bind nanovdb::cuda::default_resource<BuilderResource>() through their defaulted constructor args). An OrtAllocatorResource holding a per-session OrtAllocator* would additionally need an instance plumbed through the call sites — your "graduate to a real injected instance" — so the alias removes the naming churn but not that eventual plumbing. A non-torch build would also put the TorchResource include behind a build-time switch in this one header.
  • The alias covers builder scratch only; allocations that are torch tensors by design (TorchDeviceBuffer, the SaveNanoVDB staging buffers) still name their types directly.

All 13 affected TUs compile cleanly against the branch's nanovdb pin.

@swahtz
swahtz enabled auto-merge (squash) August 20, 2026 06:23
@swahtz
swahtz merged commit 419fcb6 into openvdb:main Aug 20, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core library Core fVDB library. i.e. anything in the _Cpp module (C++) or fvdb python module optimization Performance or memory optimization Topology Operations Issues related to topology operations (prune, merge, dilate, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants