Skip to content

Make CUDA floating-point prefix sums deterministic - #211

Open
matla91 wants to merge 1 commit into
mitsuba-renderer:masterfrom
matla91:fix/cuda-deterministic-prefix-add
Open

Make CUDA floating-point prefix sums deterministic#211
matla91 wants to merge 1 commit into
mitsuba-renderer:masterfrom
matla91:fix/cuda-deterministic-prefix-add

Conversation

@matla91

@matla91 matla91 commented Aug 17, 2026

Copy link
Copy Markdown

Summary

  • Make CUDA floating-point prefix additions deterministic while preserving the existing decoupled look-back structure.
  • Restrict the deterministic path to Float32/Float64 addition; other reductions and types keep the existing implementation.
  • Add a CUDA repeatability regression covering both Float32 and Float64.

Root cause

The CUDA prefix reduction uses decoupled look-back. For floating-point addition, a block can observe predecessors in different PARTIAL/INCLUSIVE states depending on GPU scheduling. That changes the association order of the chunk-prefix additions and can therefore change the resulting bit pattern across otherwise identical runs.

This propagated to Mitsuba mesh area CDF construction and caused repeated sampling of an identical mesh with identical random samples to return different positions and PDFs. The original investigation is documented in mitsuba-renderer/mitsuba3#1938.

Implementation

The revised patch keeps the single-pass decoupled look-back kernel.

For Float32 and Float64 addition, look-back is divided into fixed warp-sized anchor intervals. Each lane examines a fixed predecessor relative to the current chunk, waits until the interval anchor has reached the inclusive state, and then performs a fixed-order warp reduction through that anchor. Only fixed anchor chunks publish an inclusive prefix for the next interval.

This makes the floating-point association order independent of whether intermediate predecessors happen to be observed as partial or inclusive due to scheduling.

The original decoupled look-back path is retained unchanged for other reduction operations and data types.

Validation

  • New CUDA regression: 20 repeated scans of a 164,738-element input for both Float32 and Float64.
    • Fails with the original decoupled look-back implementation.
    • Passes with this patch.
  • Repeatability matrix validated for Float32 and Float64, forward/reverse, inclusive/exclusive, full-array and segmented scans, including inputs spanning more than one 32-chunk anchor interval.
  • Exact CPU-reference checks were used during development to validate the intended fixed association order for forward/reverse and inclusive/exclusive scans.
  • test_reductions -c: 15/15 passed (43 skipped).
  • CTest: 8/8 other test targets passed. graphviz has a pre-existing reference-output mismatch and produces byte-identical failing output with the baseline and patched libraries.
  • CUDA 12.2 ptxas resource usage for the modified kernels:
    • Float32: 19 registers, 0 spills.
    • Float64: 22 registers, 0 spills.

Microbenchmark

RTX 2060, CUDA kernel execution + synchronization, median of repeated runs:

Case Float32 Float64
16,384 elements +6.57% +18.75%
164,738 elements -0.90% +11.86%
200,000 elements, block size 65,537 +3.89% +5.31%
1,048,577 elements -3.11% +2.24%

The generated CUDA kernel artifacts were rebuilt with CUDA 12.2.140.

@wjakob

wjakob commented Aug 17, 2026

Copy link
Copy Markdown
Member

Hi @matla91,

I do not wish to avoid the decoupled loopback algorithm, which remains the optimal way to do these kinds of reductions on NVIDIA GPUs. Probably the better fix would be something that avoids FP nondeterminism while still keeping the loopback structure.

@matla91
matla91 force-pushed the fix/cuda-deterministic-prefix-add branch from 3048dcc to 16364ba Compare August 17, 2026 15:48
@matla91
matla91 force-pushed the fix/cuda-deterministic-prefix-add branch from 16364ba to 6bf7e8c Compare August 17, 2026 16:06
@matla91

matla91 commented Aug 17, 2026

Copy link
Copy Markdown
Author

Hi @wjakob,

Thanks for the feedback. I reworked the patch to keep the decoupled look-back structure.

The revised implementation uses fixed 32-chunk anchor intervals for Float32/Float64 addition. Each look-back warp waits for a fixed inclusive anchor and combines the preceding values in a fixed order, so the floating-point association no longer depends on whether intermediate chunks are observed as PARTIAL or INCLUSIVE.

The original DLB path is unchanged for the other operations and types.

I also replaced the previous regression with a test that runs both Float32 and Float64 scans 20 times on the original 164,738-element reproducer. The test fails with the original DLB kernel and passes with the revised one.

Validation on my side:

  • test_reductions -c: 15/15 passed
  • full test_reductions: 44/44 passed when run directly; the UInt64/OptiX assertion seen under CTest also reproduces on the clean upstream f9cf67f baseline
  • graphviz has a pre-existing reference mismatch and produces identical output with baseline/patched libraries
  • no spills: 19 registers for f32, 22 for f64
  • repeatability also checked for forward/reverse, inclusive/exclusive, and segmented scans

I force-pushed the revised commit.

@matla91
matla91 force-pushed the fix/cuda-deterministic-prefix-add branch from 6bf7e8c to a5c2594 Compare August 18, 2026 06:49
Keep the existing decoupled look-back prefix scan while making Float32 and Float64 addition deterministic.

Use fixed warp-sized anchor intervals for floating-point additions so that the reduction association order no longer depends on whether predecessors are observed in partial or inclusive states.

Other reduction operations and data types retain the existing decoupled look-back path.

Add a CUDA regression test exercising repeated Float32 and Float64 prefix sums.
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.

2 participants