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
NanoVDB: assign each upper-node tile to a single GPU in DistributedPointsToGrid
The device segment boundaries produced by the initial even split were
rebalanced by adjusting only adjacent pairs of boundaries. That cannot
consolidate an upper-node tile spanning three or more GPUs, since a
fully-interior device lies entirely within the tile. Downstream
construction assumes each tile - and therefore each lower node, leaf node
and voxel - is owned by exactly one device, so multiple devices built the
same leaf concurrently and raced on its value mask, silently dropping
active voxels.
Replace the pairwise GPU rebalance with a global, monotonic snap performed
on the host over the (small) set of device boundaries. The keys are
globally sorted at that point, so a tile boundary is simply a position
where the key changes; fully-interior devices are left empty, which the
rest of the pipeline already handles.
Add two multi-GPU regression tests: SingleUpperNode, which places all
input in one upper-node tile and asserts the exact unique active-voxel
count, and MatchesSingleGpu, which cross-checks topology and occupancy
against the single-GPU builder on the same input.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Swartz <jonathan@jswartz.info>
// For each segment of sorted keys on each device, we count how many of the leftmost key occur past the left boundary of the segment. The same is done for the rightmost key with the right boundary of the segment.
654
-
auto leftIntervals = mIntervals;
655
-
auto rightIntervals = mIntervals + mDeviceMesh.deviceCount() + 1;
604
+
// Rebalance the device segments so that a device boundary always coincides
605
+
// with a change in key value. Because TileKeyFunctor assigns identical keys
606
+
// to every point that falls in the same upper-node "tile", this aligns the
607
+
// device ownership boundaries with tile boundaries. Downstream construction
608
+
// assumes each tile (and therefore each lower node, leaf node, and voxel) is
609
+
// owned by exactly one device; if a tile straddled a boundary, multiple
610
+
// devices would concurrently build the same leaf and race on its value mask.
611
+
//
612
+
// A single tile can span three or more devices (e.g. one dense leaf whose
613
+
// points are split evenly across the mesh). Adjusting only adjacent pairs of
614
+
// boundaries cannot consolidate such a tile because a fully-interior device
615
+
// lies entirely within it, so we compute the boundaries globally and
616
+
// monotonically. mKeys is globally sorted at this point, so a tile boundary
617
+
// is simply a position where mKeys changes. Snapping is performed on the host
618
+
// over the (small) set of device boundaries; fully-interior devices are left
619
+
// empty, which the rest of the pipeline already handles.
656
620
for (constauto& [deviceId, stream] : mDeviceMesh) {
657
621
cudaCheck(cudaSetDevice(deviceId));
658
-
659
-
auto deviceStripeCount = mStripeCounts[deviceId];
660
-
auto deviceStripeOffset = mStripeOffsets[deviceId];
// Rebalance the segments so that a device segment boundary also corresponds to a change in key value. Effectively, this aligns upper node boundaries with device ownership boundaries.
682
-
for (constauto& [deviceId, stream] : mDeviceMesh) {
683
-
cudaCheck(cudaSetDevice(deviceId));
684
-
685
-
if (deviceId > 0 && deviceStripeCounts[deviceId] && deviceStripeCounts[deviceId - 1])
0 commit comments