You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ONNX Runtime Support for fVDB Sparse Voxel Operations
Motivation
fVDB operations currently exist only within the PyTorch ecosystem. Users who want to deploy fVDB-based models (neural radiance fields, 3D reconstruction, sparse convolution networks, etc.) in ONNX Runtime -- for cross-language inference (C++, C#, Java, JS), hardware-specific execution providers, or standardized model interchange -- have no path to do so.
The core challenge is that ONNX graphs transport only tensors between nodes, while fVDB operations pass two non-tensor types: GridBatchData (a frozen C++ struct wrapping NanoVDB OnIndexGrid data in a contiguous byte buffer) and JaggedTensor (variable-length batched data). Grid topology is dynamic -- constructed at inference time from input data, not baked into the model.
Design Overview
The approach is split into two phases: (1) a custom ONNX operator domain with tensor-based representations of fVDB types, and (2) an optional ONNX Runtime Execution Provider plugin for optimized execution.
Phase 1: Custom ONNX Operator Domain (fvdb)
1.1 Tensor Representations
GridBatchData as a tensor bundle ("GridBatch Bundle")
GridBatchData (src/fvdb/detail/GridBatchData.h) is an immutable struct holding sparse voxel grid topology. Its NanoVDB data is stored as a single contiguous byte buffer via mGridHdl, a shared_ptr<nanovdb::GridHandle<TorchDeviceBuffer>> backed by a raw uint8_t* + size (src/fvdb/detail/TorchDeviceBuffer.h). The struct is decomposed into a group of tensors that always travel together through the ONNX graph:
Tensor
Dtype
Shape
Source in GridBatchData
grid_blob
uint8
[N] (dynamic)
mGridHdl->data() -- the raw NanoVDB buffer containing all grids packed sequentially
grid_byte_offsets
int64
[B]
Per-grid mCumBytes from GridMetadata
voxel_sizes
float64
[B, 3]
Per-grid mVoxelSize from GridMetadata
origins
float64
[B, 3]
Per-grid voxel origins via GridMetadata::voxelOrigin()
leaf_batch_indices
int32
[L] (dynamic)
mLeafBatchIndices
batch_offsets
int64
[B+1]
mBatchOffsets
list_indices
int32
[M] (dynamic)
mListIndices
Individual grids within the blob are accessed via pointer arithmetic into the contiguous buffer, as in GridBatchData::Accessor::grid():
Alignment: NanoVDB requires 32-byte alignment (NanoVDB.h ~L67-78). CUDA allocators satisfy this (typically 256B+). CPU allocators may not -- custom op implementations must validate or enforce alignment.
JaggedTensor as a tensor bundle ("JaggedTensor Bundle")
A JaggedTensor (src/fvdb/JaggedTensor.h) is decomposed into its constituent tensors:
Tensor
Dtype
Shape
Source
jdata
varies
[N, *esizes]
mData -- packed values
joffsets
int64
[T+1]
mOffsets -- CSR boundaries
jidx
int32
[N]
mBatchIdx -- per-element batch index
jlidx
int32
[T, ldim]
mListIdx -- list-of-lists indexing
1.2 Custom Operator Schemas
Each ONNX custom op in the fvdb domain maps 1:1 to a C++ free function bound in GridBatchOps.cpp (src/python/GridBatchOps.cpp). The fvdb.functional module (~120 exports in fvdb/functional/__init__.py) serves as the definitive op catalog.
Grid Construction Ops (produce a GridBatch Bundle from input tensors):
ONNX Custom Op
GridBatchOps.cpp binding
fvdb.functional entry point
fvdb.GridFromPoints
create_from_points -> ops::buildGridFromPoints
gridbatch_from_points / grid_from_points
fvdb.GridFromIJK
create_from_ijk -> ops::createNanoGridFromIJK
gridbatch_from_ijk / grid_from_ijk
fvdb.GridFromMesh
create_from_mesh -> ops::buildGridFromMesh
gridbatch_from_mesh / grid_from_mesh
fvdb.GridFromDense
create_dense -> ops::createNanoGridFromDense
gridbatch_from_dense / grid_from_dense
fvdb.GridFromNanoVDB
New -- wraps makeGridBatchData from GridBatchDataFactory.h
New
The fvdb.GridFromNanoVDB op is for users who provide a pre-built NanoVDB blob as input. It takes the raw bytes + voxel sizes + origins and derives metadata and index tensors via the existing makeGridBatchData factory.
Grid-Consuming Ops (take a GridBatch Bundle + data, produce outputs):
Priority ops for initial implementation:
ONNX Custom Op
GridBatchOps.cpp binding
fvdb.functional
fvdb.SampleTrilinear
sample_trilinear -> ops::sampleTrilinear
sample_trilinear_batch / _single
fvdb.SampleBezier
sample_bezier -> ops::sampleBezier
sample_bezier_batch / _single
fvdb.SplatTrilinear
splat_trilinear -> ops::splatTrilinear
splat_trilinear_batch / _single
fvdb.SplatBezier
splat_bezier -> ops::splatBezier
splat_bezier_batch / _single
fvdb.PointsInGrid
points_in_grid -> ops::pointsInGrid
points_in_grid_batch / _single
fvdb.IJKToIndex
ijk_to_index -> ops::ijkToIndex
ijk_to_index_batch / _single
fvdb.CoordsInGrid
coords_in_grid -> ops::coordsInGrid
coords_in_grid_batch / _single
fvdb.VoxelsAlongRays
voxels_along_rays -> ops::voxelsAlongRays
voxels_along_rays_batch / _single
fvdb.SegmentsAlongRays
segments_along_rays -> ops::segmentsAlongRays
segments_along_rays_batch / _single
fvdb.MaxPool
max_pool -> ops::maxPool
max_pool_batch / _single
fvdb.AvgPool
avg_pool -> ops::avgPool
avg_pool_batch / _single
fvdb.VoxelToWorld
voxel_to_world -> ops::voxelToWorld
voxel_to_world_batch / _single
fvdb.WorldToVoxel
world_to_voxel -> ops::worldToVoxel
world_to_voxel_batch / _single
Grid-to-Grid Ops (produce a new GridBatch Bundle):
Reconstructs a GridBatchData from the tensor bundle using makeGridBatchData (GridBatchDataFactory.h)
Reconstructs JaggedTensor views from component tensors
Calls the existing C++ ops::* free function (no kernel rewrite needed)
Decomposes the results back into output tensors
GridBatchData is immutable after construction and has a single factory entry point (makeGridBatchData in GridBatchDataFactory.h), which makes reconstruction straightforward:
// Reconstruction from tensor bundle -> GridBatchDataauto grid_hdl = wrapBlobAsGridHandle(grid_blob.Data(), grid_blob.NumberOfElement());
auto gbd = makeGridBatchData(
std::move(grid_hdl),
extractVoxelSizes(voxel_sizes),
extractOrigins(origins));
// Call the existing opauto result = ops::sampleTrilinear(*gbd, jt_points, voxel_data_tensor);
// Decompose result into output tensors...
1.4 ONNX Model Export
fVDB ops are bound to Python via pybind11 (src/python/GridBatchOps.cpp, src/python/Bindings.cpp), not torch.library, so torch.onnx.export() will not trace them automatically. Provide a Python utility for ONNX export:
Option A: Custom symbolic functions -- Register torch.onnx symbolic handlers for each _fvdb_cpp.* call that emit the corresponding fvdb.* custom ONNX nodes.
Option B: Manual graph construction -- Provide an fvdb.export_onnx(model, sample_inputs, path) utility that traces the model and builds the ONNX graph programmatically using onnx.helper, decomposing GridBatchData and JaggedTensor into tensor bundles at graph boundaries.
The fvdb.functional module serves as the reference for which ops need export support: every function in fvdb/functional/__init__.py's __all__ list is a candidate.
1.5 User-Provided Grids
Users who construct grids externally and pass them as inference inputs provide:
The raw NanoVDB bytes as a uint8 input tensor
Voxel sizes as a float64[B, 3] input tensor
Origins as a float64[B, 3] input tensor
The fvdb.GridFromNanoVDB op derives the remaining fields by calling makeGridBatchData from GridBatchDataFactory.h, which scans the NanoVDB buffer to compute per-grid metadata, leaf batch indices, batch offsets, and list indices.
Expand op coverage guided by fvdb/functional/__init__.py's __all__ list
Phase 2 milestones:
Scaffold EP plugin with ORT EP ABI
Implement GetCapability to claim fvdb.* subgraphs
Implement native GridBatchData state management within the EP (using makeGridBatchData for construction, storing c10::intrusive_ptr<GridBatchData> as EP state)
Register kernel implementations for all Phase 1 ops via KernelRegistry_AddKernel
Benchmark against Phase 1 (per-op reconstruction via makeGridBatchData) to quantify improvement
Key Source Files
File
Relevance
src/fvdb/detail/GridBatchData.h
Frozen struct: all public fields define what the tensor bundle must capture. Contains GridMetadata, GridBatchMetadata, Accessor
src/fvdb/detail/GridBatchDataFactory.h
makeGridBatchData -- the single entry point for constructing GridBatchData from a GridHandle + voxel sizes/origins. The ONNX adapter's reconstruction path
src/fvdb/detail/TorchDeviceBuffer.h
NanoVDB buffer wrapper -- the uint8_t* blob that becomes grid_blob
src/python/GridBatchOps.cpp
All C++ ops bound as free functions -- the definitive list of functions that ONNX custom ops will wrap
fvdb/functional/__init__.py
Complete __all__ export list (~120 ops) -- the ONNX custom op domain catalog
fvdb/functional/_interpolation.py
Representative pattern showing how ops call _fvdb_cpp free functions with GridBatchData
fvdb/functional/_constructors.py
Grid construction functions showing how _fvdb_cpp.gridbatch_from_* factories work
src/fvdb/detail/ops/*.h
~55 op implementations (CUDA kernels) that custom ops ultimately call
src/fvdb/JaggedTensor.h
Data members define the JaggedTensor Bundle tensors
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ONNX Runtime Support for fVDB Sparse Voxel Operations
Motivation
fVDB operations currently exist only within the PyTorch ecosystem. Users who want to deploy fVDB-based models (neural radiance fields, 3D reconstruction, sparse convolution networks, etc.) in ONNX Runtime -- for cross-language inference (C++, C#, Java, JS), hardware-specific execution providers, or standardized model interchange -- have no path to do so.
The core challenge is that ONNX graphs transport only tensors between nodes, while fVDB operations pass two non-tensor types:
GridBatchData(a frozen C++ struct wrapping NanoVDBOnIndexGriddata in a contiguous byte buffer) andJaggedTensor(variable-length batched data). Grid topology is dynamic -- constructed at inference time from input data, not baked into the model.Design Overview
The approach is split into two phases: (1) a custom ONNX operator domain with tensor-based representations of fVDB types, and (2) an optional ONNX Runtime Execution Provider plugin for optimized execution.
Phase 1: Custom ONNX Operator Domain (
fvdb)1.1 Tensor Representations
GridBatchData as a tensor bundle ("GridBatch Bundle")
GridBatchData(src/fvdb/detail/GridBatchData.h) is an immutable struct holding sparse voxel grid topology. Its NanoVDB data is stored as a single contiguous byte buffer viamGridHdl, ashared_ptr<nanovdb::GridHandle<TorchDeviceBuffer>>backed by a rawuint8_t*+ size (src/fvdb/detail/TorchDeviceBuffer.h). The struct is decomposed into a group of tensors that always travel together through the ONNX graph:GridBatchDatagrid_blobuint8[N](dynamic)mGridHdl->data()-- the raw NanoVDB buffer containing all grids packed sequentiallygrid_byte_offsetsint64[B]mCumBytesfromGridMetadatavoxel_sizesfloat64[B, 3]mVoxelSizefromGridMetadataoriginsfloat64[B, 3]GridMetadata::voxelOrigin()leaf_batch_indicesint32[L](dynamic)mLeafBatchIndicesbatch_offsetsint64[B+1]mBatchOffsetslist_indicesint32[M](dynamic)mListIndicesIndividual grids within the blob are accessed via pointer arithmetic into the contiguous buffer, as in
GridBatchData::Accessor::grid():Alignment: NanoVDB requires 32-byte alignment (
NanoVDB.h~L67-78). CUDA allocators satisfy this (typically 256B+). CPU allocators may not -- custom op implementations must validate or enforce alignment.JaggedTensor as a tensor bundle ("JaggedTensor Bundle")
A
JaggedTensor(src/fvdb/JaggedTensor.h) is decomposed into its constituent tensors:jdata[N, *esizes]mData-- packed valuesjoffsetsint64[T+1]mOffsets-- CSR boundariesjidxint32[N]mBatchIdx-- per-element batch indexjlidxint32[T, ldim]mListIdx-- list-of-lists indexing1.2 Custom Operator Schemas
Each ONNX custom op in the
fvdbdomain maps 1:1 to a C++ free function bound inGridBatchOps.cpp(src/python/GridBatchOps.cpp). Thefvdb.functionalmodule (~120 exports infvdb/functional/__init__.py) serves as the definitive op catalog.Grid Construction Ops (produce a GridBatch Bundle from input tensors):
GridBatchOps.cppbindingfvdb.functionalentry pointfvdb.GridFromPointscreate_from_points->ops::buildGridFromPointsgridbatch_from_points/grid_from_pointsfvdb.GridFromIJKcreate_from_ijk->ops::createNanoGridFromIJKgridbatch_from_ijk/grid_from_ijkfvdb.GridFromMeshcreate_from_mesh->ops::buildGridFromMeshgridbatch_from_mesh/grid_from_meshfvdb.GridFromDensecreate_dense->ops::createNanoGridFromDensegridbatch_from_dense/grid_from_densefvdb.GridFromNanoVDBmakeGridBatchDatafromGridBatchDataFactory.hThe
fvdb.GridFromNanoVDBop is for users who provide a pre-built NanoVDB blob as input. It takes the raw bytes + voxel sizes + origins and derives metadata and index tensors via the existingmakeGridBatchDatafactory.Grid-Consuming Ops (take a GridBatch Bundle + data, produce outputs):
Priority ops for initial implementation:
GridBatchOps.cppbindingfvdb.functionalfvdb.SampleTrilinearsample_trilinear->ops::sampleTrilinearsample_trilinear_batch/_singlefvdb.SampleBeziersample_bezier->ops::sampleBeziersample_bezier_batch/_singlefvdb.SplatTrilinearsplat_trilinear->ops::splatTrilinearsplat_trilinear_batch/_singlefvdb.SplatBeziersplat_bezier->ops::splatBeziersplat_bezier_batch/_singlefvdb.PointsInGridpoints_in_grid->ops::pointsInGridpoints_in_grid_batch/_singlefvdb.IJKToIndexijk_to_index->ops::ijkToIndexijk_to_index_batch/_singlefvdb.CoordsInGridcoords_in_grid->ops::coordsInGridcoords_in_grid_batch/_singlefvdb.VoxelsAlongRaysvoxels_along_rays->ops::voxelsAlongRaysvoxels_along_rays_batch/_singlefvdb.SegmentsAlongRayssegments_along_rays->ops::segmentsAlongRayssegments_along_rays_batch/_singlefvdb.MaxPoolmax_pool->ops::maxPoolmax_pool_batch/_singlefvdb.AvgPoolavg_pool->ops::avgPoolavg_pool_batch/_singlefvdb.VoxelToWorldvoxel_to_world->ops::voxelToWorldvoxel_to_world_batch/_singlefvdb.WorldToVoxelworld_to_voxel->ops::worldToVoxelworld_to_voxel_batch/_singleGrid-to-Grid Ops (produce a new GridBatch Bundle):
GridBatchOps.cppbindingfvdb.functionalfvdb.CoarsenedGridcoarsen_grid->ops::buildCoarseGridFromFinecoarsened_grid_batch/_singlefvdb.RefinedGridupsample_grid->ops::buildFineGridFromCoarserefined_grid_batch/_singlefvdb.DilatedGriddilate_grid->ops::dilateGriddilated_grid_batch/_singlefvdb.ConvGridconv_grid->ops::buildGridForConvconv_grid_batch/_singlefvdb.ConvTransposeGridconv_transpose_grid->ops::buildGridForConvTransposeconv_transpose_grid_batch/_singlefvdb.PrunedGridprune_grid->ops::pruneGridpruned_grid_batch/_singlefvdb.MergedGridmerge_grids->ops::mergeGridsmerged_grid_batch/_singleThe full op list need not all be implemented at once. The above covers the most common inference workloads.
1.3 Custom Op Library Implementation
Build a shared library (
libfvdb_onnx_ops.so/fvdb_onnx_ops.dll) that:RegisterCustomOpsper the ONNX Runtime custom op library conventionfvdbdomain usingOrt::CustomOpDomain+Ort::Custom::CreateLiteCustomOpOrt::Custom::CudaContextfor GPU opsEach custom op is a thin adapter that:
GridBatchDatafrom the tensor bundle usingmakeGridBatchData(GridBatchDataFactory.h)JaggedTensorviews from component tensorsops::*free function (no kernel rewrite needed)GridBatchDatais immutable after construction and has a single factory entry point (makeGridBatchDatainGridBatchDataFactory.h), which makes reconstruction straightforward:1.4 ONNX Model Export
fVDB ops are bound to Python via pybind11 (
src/python/GridBatchOps.cpp,src/python/Bindings.cpp), nottorch.library, sotorch.onnx.export()will not trace them automatically. Provide a Python utility for ONNX export:torch.onnxsymbolic handlers for each_fvdb_cpp.*call that emit the correspondingfvdb.*custom ONNX nodes.fvdb.export_onnx(model, sample_inputs, path)utility that traces the model and builds the ONNX graph programmatically usingonnx.helper, decomposingGridBatchDataandJaggedTensorinto tensor bundles at graph boundaries.The
fvdb.functionalmodule serves as the reference for which ops need export support: every function infvdb/functional/__init__.py's__all__list is a candidate.1.5 User-Provided Grids
Users who construct grids externally and pass them as inference inputs provide:
uint8input tensorfloat64[B, 3]input tensorfloat64[B, 3]input tensorThe
fvdb.GridFromNanoVDBop derives the remaining fields by callingmakeGridBatchDatafromGridBatchDataFactory.h, which scans the NanoVDB buffer to compute per-grid metadata, leaf batch indices, batch offsets, and list indices.Phase 2: fVDB Execution Provider Plugin
Build an ONNX Runtime EP plugin (
libfvdb_ep.so) using the EP ABI kernel-based plugin API introduced in ORT v1.24.2.1 Architecture
The EP claims subgraphs of
fvdb.*custom ops viaOrtEp::GetCapability+EpGraphSupportInfo_LookUpKernel. Inside the EP:GridBatchDataobjects are maintained as native C++ state, not reconstructed from tensor bundles per-opGridBatchDataviamakeGridBatchData(GridBatchDataFactory.h) and store the result in EP-managed stateGridBatchDatadirectly, calling the sameops::*free functions fromsrc/fvdb/detail/ops/The EP kernel registry (
OrtKernelRegistry+KernelRegistry_AddKernel) contains entries for each fVDB op, each with aComputemethod that:GridBatchDatafrom EP state (not from tensor inputs)OrtKernelContextops::*free functionOrtKernelContext2.2 Key ORT APIs
OrtEp::GetCapabilityfvdb.*subgraph nodesOrtEp::GetKernelRegistryOrtKernelImpl::ComputeOrtEpApi::KernelRegistry_AddKernelOrtEpApi::GetEnvConfigEntries2.3 Benefits Over Phase 1 Alone
GridBatchDatareconstruction from the tensor bundle (themakeGridBatchDatacall that scans the NanoVDB buffer)Implementation Plan
Phase 1 milestones:
bundleFromGridBatchData/gridBatchDataFromBundlehelper functionsfvdb.GridFromPointsandfvdb.GridFromNanoVDBconstruction ops wrappingops::buildGridFromPointsandmakeGridBatchDatafvdb.SampleTrilinearandfvdb.PointsInGridas initial consuming ops wrappingops::sampleTrilinearandops::pointsInGridfvdb.functionalcalls to ONNX custom nodesfvdb/functional/__init__.py's__all__listPhase 2 milestones:
GetCapabilityto claimfvdb.*subgraphsGridBatchDatastate management within the EP (usingmakeGridBatchDatafor construction, storingc10::intrusive_ptr<GridBatchData>as EP state)KernelRegistry_AddKernelmakeGridBatchData) to quantify improvementKey Source Files
src/fvdb/detail/GridBatchData.hGridMetadata,GridBatchMetadata,Accessorsrc/fvdb/detail/GridBatchDataFactory.hmakeGridBatchData-- the single entry point for constructingGridBatchDatafrom aGridHandle+ voxel sizes/origins. The ONNX adapter's reconstruction pathsrc/fvdb/detail/TorchDeviceBuffer.huint8_t*blob that becomesgrid_blobsrc/python/GridBatchOps.cppfvdb/functional/__init__.py__all__export list (~120 ops) -- the ONNX custom op domain catalogfvdb/functional/_interpolation.py_fvdb_cppfree functions withGridBatchDatafvdb/functional/_constructors.py_fvdb_cpp.gridbatch_from_*factories worksrc/fvdb/detail/ops/*.hsrc/fvdb/JaggedTensor.hnanovdb/nanovdb/NanoVDB.hGridData(672B header), contiguous buffernanovdb/nanovdb/GridHandle.hGridHandle<BufferT>-- owns the buffer, multi-grid metadataKey External References
OrtKernelImpl,OrtKernelRegistry,KernelRegistry_AddKernel,EpGraphSupportInfo_LookUpKernelOrtEp::GetCapability, Graph IR APIsCreateEnvWithOptions,GetEnvConfigEntriesAll reactions