Skip to content

GridEdgeNetwork emits 8 vertices + 12 edges per voxel with no deduplication (~368 B/voxel) #720

Description

@swahtz

Summary

GridEdgeNetwork (src/fvdb/detail/ops/GridEdgeNetwork.cu:100-107) allocates its output as a fixed 8×/12× expansion of the input voxel count, with zero deduplication — a corner shared by 8 voxels is written 8 times, and an interior edge shared by 4 voxels is written 4 times:

buffer size B/voxel
outV 8V × 3 float32 96
outE 12V × 2 int64 192
outVBidx 8V int32 32
outEBidx 12V int32 48
368

(The two *Bidx arrays are also written and then discarded for batchSize == 1 — the existing FIXME at :151.)

This was catalogued alongside the coordinate-list topology builders addressed in #711 / #712, but it is a different problem: there is no _createNanoGridFromIJK and no radix sort here. It is purely an output-size issue, so #712 does not (and should not) cover it.

This is not a speed problem. Warmed, the op runs in 0.17 ms for 212k voxels. It is memory only — but 368 B/voxel puts a hard ceiling on the grid size that can be visualized (a 100M-voxel grid would need ~36.8 GB just for the wireframe).

Proposed fix: deduplicate via the dual grid

Both halves of this were verified experimentally, not just derived.

1. The deduplicated vertex set is exactly the dual grid.

dual(c) active ⇔ ∃d∈{0,1}³ primal(c−d) ⇔ c is a corner of some active voxel. Measured on a sparse grid (V = 212,803): unique vertices = 657,710, dual_grid().num_voxels = 657,710, sets element-wise equal.

Vertex positions become dualIjk − 0.5 (index space) or tx.applyInv(dualIjk − 0.5) (world) — numerically identical to today's values, so return_voxel_coordinates semantics are preserved exactly. Only the duplication goes away.

2. Edges need a 4-voxel incidence test.

Note that "both endpoints are dual-active" is necessary but not sufficient — two diagonally-adjacent voxels satisfy it without the segment being a voxel edge. The correct rule:

Edge (c, c + e_a) exists ⇔ any of the 4 primal voxels c − off is active, where off ranges over the offsets in {0,1}³ whose component along axis a is 0.

(Voxel v has corners v + {0,1}³; the segment lies in that corner set iff c − v has a 0 in axis a.)

Validated against ground truth (the geometrically-unique edge set produced by the current implementation) on a sparse irregular grid that exercises diagonal and corner cases: 165,527 edges from both, element-wise identical.

3. Endpoint indices come from ijk_to_index on the dual grid — no sort, no separate dedup pass.

Implementation sketch

One kernel over dual-grid voxels: for each c, apply the 4-voxel test on 3 axes to build a 3-bit mask; prefix-sum the popcounts to size outE exactly; fill. O(D) work, no expansion, no sort. Removes the discarded jidx arrays at the same time.

Expected savings

grid V current deduplicated
dense 128³ 2,097,152 771.8 MB (368 B/vox) 128.0 MB (61 B/vox) 6.0×
dense 64³ 262,144 96.5 MB 16.3 MB 5.9×
sparse (random points) 212,803 78.3 MB 31.2 MB 2.5×

On dense grids dual D ≈ 1.02×V and unique edges ≈ 3.05×V, so the win is largest exactly where V is big enough to matter. Extrapolated to 100M voxels: ~36.8 GB → ~6.1 GB.

Dependency

This should be stacked on #710, not built on main. It needs the dual grid, which is only cheap once the leaf-mask PadGrid lands — on main, buildPaddedGrid still takes the coordinate-list path, so you would trade one blow-up for another.

Compatibility notes

  • This changes the size and ordering of both returned tensors. The documented contract (fvdb/viz/_utils.py:11) promises only "an (N, 3) tensor of vertices and an (M, 2) tensor of indices" and does not pin N or M, so deduplication is within contract.
  • There are currently no tests for this opedge_network appears in tests/ only as a commented-out line (tests/unit/test_ray_marching.py:433). Nothing will break, but that is because coverage is absent; tests should be added as part of any change here.
  • The only consumer is fvdb.viz (grid_edge_network / gridbatch_edge_network). Rendering should look the same or better — interior edges are currently overdrawn 1.75–4×.
  • If changing the default is undesirable, a deduplicate: bool = True parameter would preserve the existing behaviour.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Topology OperationsIssues related to topology operations (prune, merge, dilate, etc.core libraryCore fVDB library. i.e. anything in the _Cpp module (C++) or fvdb python moduleoptimizationPerformance or memory optimizationtriageNeeds team review

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions