Skip to content

Improve CUDA module performance and CUDA 12/13 build compatibility#15

Merged
unclearness merged 3 commits into
developfrom
cuda-perf-refactor
Jul 7, 2026
Merged

Improve CUDA module performance and CUDA 12/13 build compatibility#15
unclearness merged 3 commits into
developfrom
cuda-perf-refactor

Conversation

@unclearness

Copy link
Copy Markdown
Owner

CUDA module performance refactor (stages 1-3) + CUDA 12/13 build compatibility

This PR is the first three stages of an incremental performance refactor of src/cuda/, based on a static audit of the module. Every stage was verified on real hardware for both result consistency (bit-identical where the pipeline is deterministic, statistical equivalence where it is not) and measured speedup.

Measured results

Environment: RTX PRO 6000 Blackwell (sm_120), CUDA 13.2, VS2026, Release build.

Workload Before After Speedup Result check
kNN grid search (ex17, 2000 queries, k=200, 5M pts) 121.4 ms 56.0 ms 2.2x knngrid.ply bit-identical
BoxFilterCuda (ex06, per call, k=51) 0.60 ms 0.16 ms 3.8x deterministic after bug fix (see below)
ComputeNormalsCuda raw overload (ex06, per call) 2.97 ms 1.67 ms 1.8x deterministic after bug fix
ExtractMesh (ex21) 1.02 ms 0.51 ms 2.0x vertex count within noise band
ComputeVertexNormals (ex21) 0.092 ms 0.054 ms 1.7x same
GPU RemoveSmallConnectedComponents (ex21, cold call) 7.9 ms 7.1 ms modest* same

* ex21 calls this once, so the measurement includes first-call workspace allocation. The per-call thrust temporaries are now persistent, so warm calls no longer allocate at all — that benefit is structural and not visible in this one-shot benchmark.

Changes

Stage 1 — knn.cu / knn.cc

  • Removed per-thread in-kernel cudaMalloc/cudaFree (the single worst issue in the module: device-heap allocation serializes all query threads and can silently exhaust the default 8 MB heap). Each thread now uses its slice of the output buffers as the working sorted list.
  • Added a k-th-best guard so candidates that cannot enter the top-k skip the O(k) insertion scan.
  • Search-range determination now counts only the newly added Chebyshev shell per iteration instead of rescanning the whole cube (same resulting range).
  • A fixed-size local-array variant was also tried and measured slower (141 ms; register pressure/occupancy), so it was discarded — the shipped version is the measured winner.

Stage 2 — image.cu

  • BoxFilterCuda and the free-function normals path rebuilt their entire GPU pipeline every call (cudaMalloc3DArray + texture object + cudaMalloc + full teardown). Both now use grow-only persistent workspaces; redundant mid-pipeline cudaDeviceSynchronize calls removed.
  • NormalComputerCuda leaked one texture object per ComputeNormals call; the texture is now created once in Init (the depth array it views is persistent). Init also releases previous resources on re-init.

Stage 3 — mesh.cu / mesh.cuh / voxel.cu

  • RemoveSmallConnectedComponents allocated 7 thrust device vectors plus d_changed on every call despite receiving a reuse buffer; all scratch now lives in RemoveSmallConnectedComponentsBuf.
  • Label propagation now early-exits by default (checks d_changed every 8 iterations). Results are unchanged: once the propagation reaches its fixed point, remaining iterations were no-ops.
  • Removed the host-side sync between the two dependent kernels in ComputeVertexNormals (default-stream ordering already guarantees correctness).

Build compatibility (VS2022/VS2026 x CUDA 12/13)

  • The if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES) guard sat after enable_language(CUDA), where CMake has always already cached a detected default — so the project's multi-arch list was dead code and builds actually targeted only the compiler-default arch (e.g. sm_52). User intent is now captured before enable_language.
  • Architecture defaults are now toolkit-aware: CUDA 13.x -> 75;80;86;87;89;90;100;120 (CUDA 13 removed < sm_75), CUDA 12.8+ -> 50...120 (adds Blackwell), older 12.x -> 50...90.
  • -allow-unsupported-compiler is added automatically for CUDA 12.x + MSVC >= VS2026 (non-VS-generator builds). Note: VS2026 + CUDA 12.x via the VS generator is not possible regardless (NVIDIA ships no VS2026 MSBuild integration in 12.x).
  • Verified matrix: VS2026 + CUDA 13.2 and VS2022 + CUDA 12.8 both build all changed files and produce bit-identical knngrid.ply.

Bug fixes (results legitimately change)

  1. Uninitialized d_normals/d_points: the normals kernels return early for border pixels without writing them, so cudaMalloc garbage leaked into the outputs. Worse, in the fusion pipeline the garbage border points passed the invalid-point test (pt == (0,0,0) skip) in FuseOrganizedPointCloudMultiKernelNaive and contaminated the TSDF. Outputs are now zero-initialized per call; ex06's CUDA outputs became run-to-run deterministic, and ex21's ~1% mesh delta versus the old baseline is the corrected behavior.
  2. kNN CSR out-of-bounds read: both branches of the "last voxel" ternary dereferenced offsets[voxel + 1], but the host allocated exactly voxel_num entries. The offsets array now carries a sentinel (voxel_num + 1 entries).
  3. KNnGridCuda::Build() leaked device buffers on re-invocation; previous buffers are now released.

Verification methodology

  • Deterministic artifacts (knngrid.ply, ex06 CUDA images after fix 1) were compared by hash against pre-refactor baselines.
  • The ex21 voxel-fusion output was found to be inherently nondeterministic (float atomicAdd ordering; confirmed pre-existing via a stash A/B test, 4 runs each producing distinct hashes with vertex counts varying ~±1%). It was therefore verified statistically: post-refactor vertex counts (16.6k-16.9k raw, ~15.1k cleaned) sit in the same band as pre-refactor runs (baseline cleaned: 15.2k).

Notes

  • src/cuda/image.cu was converted from Shift-JIS to UTF-8 in passing (its encoding broke text-level tooling). Comments that were still valid Shift-JIS were preserved as proper UTF-8; comments that were already double-mangled in the repository history remain garbled (they were unrecoverable in any encoding).
  • Remaining audit items for future stages, in descending expected impact: marching cubes over an active-cell list instead of two full-grid passes (cost currently scales with grid volume, not surface), voxel AoS -> SoA + shared-memory atomic aggregation in fusion, persistent workspaces for FinalizeLabelAndCount / ReduceFlyingNoiseOnVoxels / label propagation, unified stream policy replacing the remaining per-kernel cudaDeviceSynchronize calls, and an RAII device buffer type to replace ~50 raw cudaMalloc sites.

🤖 Generated with Claude Code

unclearness and others added 3 commits July 7, 2026 17:22
- knn: remove per-thread in-kernel cudaMalloc/cudaFree, add k-th-best
  guard, incremental shell counting, CSR sentinel (OOB read fix),
  Build() re-entry leak fix (kNN query: 121ms -> 56ms, bit-identical)
- image: persistent grow-only workspaces for BoxFilterCuda and the
  free-function normals path (3.8x / 1.8x), create texture object once
  in NormalComputerCuda::Init (was leaking one per call), zero-init
  d_normals/d_points (uninitialized border pixels leaked garbage into
  outputs and TSDF fusion)
- mesh/voxel: move per-call thrust temporaries and d_changed into
  RemoveSmallConnectedComponentsBuf, enable label-propagation early
  exit by default, drop redundant mid-pipeline device syncs
  (ExtractMesh 2x, ComputeVertexNormals 1.7x)
- CMake: make CUDA architecture defaults toolkit-aware (CUDA 13
  dropped < sm_75; add sm_100/120 on 12.8+) and fix the arch guard
  that never fired after enable_language, leaving builds at sm_52

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The comments in image.cu, mesh.cu and voxel.h were double-mangled
(Shift-JIS bytes corrupted by an earlier encoding round-trip) and
unreadable in any encoding. Rewrite them in plain-ASCII English based
on the surrounding code. No code changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unlike image.cu/mesh.cu, the Japanese comments in voxel.cu were intact
(valid UTF-8), so they are translated faithfully. Comment-only change;
code lines are byte-identical apart from dropping the leading BOM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@unclearness
unclearness merged commit b806ef4 into develop Jul 7, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant