Skip to content

Commit 4a6b0e8

Browse files
authored
Morton/Hilbert fixes (#316)
Cleanup and validation for Morton/Hilbert encoding of ijk values of gridbatch. --------- Signed-off-by: Christopher Horvath <chorvath@nvidia.com>
1 parent 64722e2 commit 4a6b0e8

File tree

8 files changed

+553
-355
lines changed

8 files changed

+553
-355
lines changed

fvdb/grid_batch.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,7 +2606,7 @@ def morton(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
26062606
else:
26072607
offset = to_Vec3i(offset)
26082608

2609-
return self._impl.morton(offset)
2609+
return JaggedTensor(impl=self._impl.morton(offset))
26102610

26112611
def morton_zyx(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
26122612
"""
@@ -2628,7 +2628,7 @@ def morton_zyx(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
26282628
else:
26292629
offset = to_Vec3i(offset)
26302630

2631-
return self._impl.morton_zyx(offset)
2631+
return JaggedTensor(impl=self._impl.morton_zyx(offset))
26322632

26332633
def hilbert(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
26342634
"""
@@ -2650,7 +2650,7 @@ def hilbert(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
26502650
else:
26512651
offset = to_Vec3i(offset)
26522652

2653-
return self._impl.hilbert(offset)
2653+
return JaggedTensor(impl=self._impl.hilbert(offset))
26542654

26552655
def hilbert_zyx(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
26562656
"""
@@ -2672,7 +2672,7 @@ def hilbert_zyx(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
26722672
else:
26732673
offset = to_Vec3i(offset)
26742674

2675-
return self._impl.hilbert_zyx(offset)
2675+
return JaggedTensor(impl=self._impl.hilbert_zyx(offset))
26762676

26772677
@property
26782678
def jidx(self) -> torch.Tensor:

src/fvdb/detail/ops/SerializeEncode.cu

Lines changed: 40 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -3,186 +3,78 @@
33
//
44
#include <fvdb/detail/GridBatchImpl.h>
55
#include <fvdb/detail/ops/SerializeEncode.h>
6-
#include <fvdb/detail/utils/AccessorHelpers.cuh>
7-
#include <fvdb/detail/utils/ForEachCPU.h>
86
#include <fvdb/detail/utils/HilbertCode.h>
97
#include <fvdb/detail/utils/MortonCode.h>
10-
#include <fvdb/detail/utils/cuda/ForEachCUDA.cuh>
11-
#include <fvdb/detail/utils/cuda/ForEachPrivateUse1.cuh>
12-
13-
#include <c10/cuda/CUDAException.h>
8+
#include <fvdb/detail/utils/SimpleOpHelper.h>
149

1510
#include <cuda_runtime.h>
1611

17-
#include <vector>
18-
1912
namespace fvdb {
2013
namespace detail {
2114
namespace ops {
2215

23-
/// @brief Per-voxel callback which computes the space-filling curve code (Morton or Hilbert) for
24-
/// each active voxel in a batch of grids
25-
template <template <typename T, int32_t D> typename TorchAccessor>
26-
__hostdev__ inline void
27-
serializeEncodeVoxelCallback(int64_t batchIdx,
28-
int64_t leafIdx,
29-
int64_t voxelIdx,
30-
GridBatchImpl::Accessor gridAccessor,
31-
TorchAccessor<int64_t, 2> outMortonCodes,
32-
const nanovdb::Coord &offset,
33-
int order_type) {
34-
const nanovdb::OnIndexGrid *grid = gridAccessor.grid(batchIdx);
35-
const typename nanovdb::OnIndexGrid::LeafNodeType &leaf =
36-
grid->tree().template getFirstNode<0>()[leafIdx];
37-
const int64_t baseOffset = gridAccessor.voxelOffset(batchIdx);
38-
39-
const nanovdb::Coord &ijk = leaf.offsetToGlobalCoord(voxelIdx);
40-
if (leaf.isActive(voxelIdx)) {
41-
const int64_t idx = baseOffset + (int64_t)leaf.getValue(voxelIdx) - 1;
16+
namespace {
4217

18+
template <torch::DeviceType DeviceTag>
19+
struct Processor : public BaseProcessor<DeviceTag, Processor<DeviceTag>, int64_t> {
20+
nanovdb::Coord offset = nanovdb::Coord{0, 0, 0};
21+
SpaceFillingCurveType order_type = SpaceFillingCurveType::ZOrder;
22+
23+
// Per-voxel callback which computes the space-filling
24+
// curve code (Morton or Hilbert) for
25+
// each active voxel in a batch of grids
26+
__hostdev__ void
27+
perActiveVoxel(nanovdb::Coord const &ijk, int64_t const feature_idx, auto out_accessor) const {
4328
// Apply offset to coordinates
44-
int32_t offset_i = offset[0];
45-
int32_t offset_j = offset[1];
46-
int32_t offset_k = offset[2];
29+
auto const i = static_cast<uint32_t>(ijk[0] + offset[0]);
30+
auto const j = static_cast<uint32_t>(ijk[1] + offset[1]);
31+
auto const k = static_cast<uint32_t>(ijk[2] + offset[2]);
4732

4833
// Compute Morton or Hilbert code with offset to ensure non-negative coordinates
4934
uint64_t space_filling_code;
50-
switch (static_cast<SpaceFillingCurveType>(order_type)) {
51-
case SpaceFillingCurveType::ZOrder: // Regular z-order: xyz
52-
space_filling_code = utils::morton_with_offset(ijk[0],
53-
ijk[1],
54-
ijk[2],
55-
static_cast<uint32_t>(offset_i),
56-
static_cast<uint32_t>(offset_j),
57-
static_cast<uint32_t>(offset_k));
35+
switch (order_type) {
36+
case SpaceFillingCurveType::ZOrder: // Regular z-order: xyz
37+
space_filling_code = utils::morton(i, j, k);
5838
break;
59-
case SpaceFillingCurveType::ZOrderTransposed: // Transposed z-order: zyx
60-
space_filling_code = utils::morton_with_offset(ijk[2],
61-
ijk[1],
62-
ijk[0],
63-
static_cast<uint32_t>(offset_k),
64-
static_cast<uint32_t>(offset_j),
65-
static_cast<uint32_t>(offset_i));
39+
case SpaceFillingCurveType::ZOrderTransposed: // Transposed z-order: zyx
40+
space_filling_code = utils::morton(k, j, i);
6641
break;
67-
case SpaceFillingCurveType::Hilbert: // Regular Hilbert curve: xyz
68-
space_filling_code = utils::hilbert_with_offset(ijk[0],
69-
ijk[1],
70-
ijk[2],
71-
static_cast<uint32_t>(offset_i),
72-
static_cast<uint32_t>(offset_j),
73-
static_cast<uint32_t>(offset_k));
42+
case SpaceFillingCurveType::Hilbert: // Regular Hilbert curve: xyz
43+
space_filling_code = utils::hilbert(i, j, k);
7444
break;
7545
case SpaceFillingCurveType::HilbertTransposed: // Transposed Hilbert curve: zyx
76-
space_filling_code = utils::hilbert_with_offset(ijk[2],
77-
ijk[1],
78-
ijk[0],
79-
static_cast<uint32_t>(offset_k),
80-
static_cast<uint32_t>(offset_j),
81-
static_cast<uint32_t>(offset_i));
46+
space_filling_code = utils::hilbert(k, j, i);
8247
break;
8348
default:
8449
// Invalid order type - use assert for device code
8550
space_filling_code = 0;
8651
break;
8752
}
8853

89-
outMortonCodes[idx][0] = static_cast<int64_t>(space_filling_code);
54+
out_accessor[feature_idx] = static_cast<int64_t>(space_filling_code);
9055
}
91-
}
56+
};
9257

93-
/// @brief Get the space-filling curve codes for active voxels in a batch of grids
94-
/// @param gridBatch The batch of grids
95-
/// @param outMortonCodes Tensor which will contain the output space-filling curve codes
96-
/// @param offset Offset to apply to voxel coordinates before encoding
97-
/// @param order_type Integer representing the order type (0=z, 1=z-trans, 2=hilbert,
98-
/// 3=hilbert-trans)
99-
template <torch::DeviceType DeviceTag>
100-
void
101-
GetSerializeEncode(const GridBatchImpl &gridBatch,
102-
torch::Tensor &outMortonCodes,
103-
const nanovdb::Coord &offset,
104-
int order_type) {
105-
auto outCodesAcc = tensorAccessor<DeviceTag, int64_t, 2>(outMortonCodes);
58+
} // End anonymous namespace
10659

107-
if constexpr (DeviceTag == torch::kCUDA) {
108-
auto cb = [=] __device__(int64_t batchIdx,
109-
int64_t leafIdx,
110-
int64_t voxelIdx,
111-
int64_t,
112-
GridBatchImpl::Accessor gridAccessor) {
113-
serializeEncodeVoxelCallback<TorchRAcc32>(
114-
batchIdx, leafIdx, voxelIdx, gridAccessor, outCodesAcc, offset, order_type);
115-
};
116-
forEachVoxelCUDA(1024, 1, gridBatch, cb);
117-
} else if constexpr (DeviceTag == torch::kPrivateUse1) {
118-
auto cb = [=] __device__(int64_t batchIdx,
119-
int64_t leafIdx,
120-
int64_t voxelIdx,
121-
int64_t,
122-
GridBatchImpl::Accessor gridAccessor) {
123-
serializeEncodeVoxelCallback<TorchRAcc32>(
124-
batchIdx, leafIdx, voxelIdx, gridAccessor, outCodesAcc, offset, order_type);
125-
};
126-
forEachVoxelPrivateUse1(1, gridBatch, cb);
127-
} else {
128-
auto cb = [=](int64_t batchIdx,
129-
int64_t leafIdx,
130-
int64_t voxelIdx,
131-
int64_t,
132-
GridBatchImpl::Accessor gridAccessor) {
133-
serializeEncodeVoxelCallback<TorchAcc>(
134-
batchIdx, leafIdx, voxelIdx, gridAccessor, outCodesAcc, offset, order_type);
135-
};
136-
forEachVoxelCPU(1, gridBatch, cb);
137-
}
138-
}
139-
140-
/// @brief Get the space-filling curve codes for active voxels in a batch of grids
141-
/// @tparam DeviceTag Which device to run on
142-
/// @param gridBatch The batch of grids to get the space-filling curve codes for
143-
/// @param order_type The type of space-filling curve to use for encoding
144-
/// @param offset Offset to apply to voxel coordinates before encoding
145-
/// @return A JaggedTensor of shape [B, -1, 1] of space-filling curve codes for active voxels
14660
template <torch::DeviceType DeviceTag>
14761
JaggedTensor
148-
SerializeEncode(const GridBatchImpl &gridBatch,
149-
SpaceFillingCurveType order_type,
150-
const nanovdb::Coord &offset) {
151-
gridBatch.checkNonEmptyGrid();
152-
auto opts = torch::TensorOptions().dtype(torch::kInt64).device(gridBatch.device());
153-
torch::Tensor outMortonCodes = torch::empty({gridBatch.totalVoxels(), 1}, opts);
154-
155-
// Convert enum to integer for kernel
156-
const int order_type_int = static_cast<int>(order_type);
157-
158-
GetSerializeEncode<DeviceTag>(gridBatch, outMortonCodes, offset, order_type_int);
159-
160-
return gridBatch.jaggedTensor(outMortonCodes);
161-
}
162-
163-
template <>
164-
JaggedTensor
165-
dispatchSerializeEncode<torch::kCUDA>(const GridBatchImpl &gridBatch,
166-
SpaceFillingCurveType order_type,
167-
const nanovdb::Coord &offset) {
168-
return SerializeEncode<torch::kCUDA>(gridBatch, order_type, offset);
62+
dispatchSerializeEncode(GridBatchImpl const &gridBatch,
63+
SpaceFillingCurveType order_type,
64+
nanovdb::Coord const &offset) {
65+
Processor<DeviceTag> processor{.offset = offset, .order_type = order_type};
66+
return processor.execute(gridBatch);
16967
}
17068

171-
template <>
172-
JaggedTensor
173-
dispatchSerializeEncode<torch::kCPU>(const GridBatchImpl &gridBatch,
174-
SpaceFillingCurveType order_type,
175-
const nanovdb::Coord &offset) {
176-
return SerializeEncode<torch::kCPU>(gridBatch, order_type, offset);
177-
}
178-
179-
template <>
180-
JaggedTensor
181-
dispatchSerializeEncode<torch::kPrivateUse1>(const GridBatchImpl &gridBatch,
182-
SpaceFillingCurveType order_type,
183-
const nanovdb::Coord &offset) {
184-
return SerializeEncode<torch::kPrivateUse1>(gridBatch, order_type, offset);
185-
}
69+
template JaggedTensor dispatchSerializeEncode<torch::kCUDA>(GridBatchImpl const &,
70+
SpaceFillingCurveType,
71+
nanovdb::Coord const &);
72+
template JaggedTensor dispatchSerializeEncode<torch::kCPU>(GridBatchImpl const &,
73+
SpaceFillingCurveType,
74+
nanovdb::Coord const &);
75+
template JaggedTensor dispatchSerializeEncode<torch::kPrivateUse1>(GridBatchImpl const &,
76+
SpaceFillingCurveType,
77+
nanovdb::Coord const &);
18678

18779
} // namespace ops
18880
} // namespace detail

src/fvdb/detail/ops/SerializeEncode.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@ namespace fvdb {
1616
namespace detail {
1717
namespace ops {
1818

19+
/// @brief Get the space-filling curve codes for active voxels in a batch of grids
20+
/// @tparam DeviceTag Which device to run on
21+
/// @param gridBatch The batch of grids to get the space-filling curve codes for
22+
/// @param order_type The type of space-filling curve to use for encoding
23+
/// @param offset Offset to apply to voxel coordinates before encoding
24+
/// @return A JaggedTensor of shape [B, -1, 1] of space-filling curve codes for active voxels
1925
template <torch::DeviceType>
20-
JaggedTensor dispatchSerializeEncode(const GridBatchImpl &gridBatch,
26+
JaggedTensor dispatchSerializeEncode(GridBatchImpl const &gridBatch,
2127
SpaceFillingCurveType order_type,
22-
const nanovdb::Coord &offset);
28+
nanovdb::Coord const &offset);
2329

2430
} // namespace ops
2531
} // namespace detail

src/fvdb/detail/utils/HilbertCode.h

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,13 @@ gray_stream_to_uint64(uint32_t g0, uint32_t g1, uint32_t g2) {
9797
return value;
9898
}
9999

100+
/// @brief Compute Hilbert curve index for 3D coordinates
101+
/// @param i I coordinate (must be 0 <= i < 2^21)
102+
/// @param j J coordinate (must be 0 <= j < 2^21)
103+
/// @param k K coordinate (must be 0 <= k < 2^21)
104+
/// @return 63-bit Hilbert index
100105
__hostdev__ inline uint64_t
101-
hilbert_order21_index(uint32_t i, uint32_t j, uint32_t k) {
106+
hilbert(uint32_t i, uint32_t j, uint32_t k) {
102107
// encoder: (i, j, k) -> uint64_t code.
103108

104109
i = i & _HILBERT_MASK;
@@ -117,29 +122,6 @@ hilbert_order21_index(uint32_t i, uint32_t j, uint32_t k) {
117122
return gray_stream_to_uint64(i, j, k);
118123
}
119124

120-
/// @brief Compute Hilbert curve index for 3D coordinates with offset handling
121-
/// @param i I coordinate (can be negative)
122-
/// @param j J coordinate (can be negative)
123-
/// @param k K coordinate (can be negative)
124-
/// @param offset_i Offset to make i non-negative
125-
/// @param offset_j Offset to make j non-negative
126-
/// @param offset_k Offset to make k non-negative
127-
/// @return 63-bit Hilbert index
128-
__hostdev__ inline uint64_t
129-
hilbert_with_offset(int32_t const i,
130-
int32_t const j,
131-
int32_t const k,
132-
uint32_t const offset_i,
133-
uint32_t const offset_j,
134-
uint32_t const offset_k) {
135-
// Add offsets to ensure non-negative coordinates
136-
uint32_t x = static_cast<uint32_t>(i + static_cast<int32_t>(offset_i));
137-
uint32_t y = static_cast<uint32_t>(j + static_cast<int32_t>(offset_j));
138-
uint32_t z = static_cast<uint32_t>(k + static_cast<int32_t>(offset_k));
139-
140-
return hilbert_order21_index(x, y, z);
141-
}
142-
143125
} // namespace utils
144126
} // namespace detail
145127
} // namespace fvdb

0 commit comments

Comments
 (0)