v1.1.4: bound depth-chunk K_max by the per-tile cap - #7
Merged
Conversation
K_max, the number of 512-gaussian depth chunks a tile is split into, was derived from capacity (num_points * 16) — a buffer size, not an intersection count. scatter_to_prealloc_bins clamps every tile counter to MAX_TILE_ELEMS, so 4 chunks cover any tile; it was dispatching ~190, writing hundreds of MB of chunk buffers and near-empty threadgroups per iteration. garden 7K at the default 2 downscales: 44s -> 36s. Full-resolution runs never chunk (num_tiles >= 400), so --num-downscales 0 is unchanged. Also fixed: - ensure_chunks compared against img_height/img_width, which ensure_forward had already updated, so chunk buffers never resized on a resolution change. - Packed buffers sized by num_tiles * MAX_TILE_ELEMS, a provable bound; num_points * 16 bounded nothing, since tile footprint is unclamped. - Backward rasterizers skipped T *= 1/(1-alpha) when alpha hit the 0.999 cap, leaving T too small for every nearer gaussian on the pixel. The cap now zeroes only the sigma-routed gradients, which are genuinely zero there. - PROFILE_STAGES applied the mach timebase to counter timestamps already in nanoseconds, scaling every stage by 41.67x. Tooling: shaders build with -frecord-sources -gline-tables-only so a .gputrace carries shader source and per-line cost; msplat_begin/end_gpu_capture plus MSPLAT_GPUTRACE* write one from the CLI; BENCHMARK=1 reports per-resolution-phase timings and intersection counts against the per-tile cap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
K_max, the number of 512-gaussian depth chunks a tile is split into, was derivedfrom
capacity(num_points * 16): a buffer size, not an intersection count.scatter_to_prealloc_bins_kernelclamps every tile counter toMAX_TILE_ELEMS,so 4 chunks cover any tile. It was dispatching ~190, writing hundreds of MB of
chunk buffers and near-empty threadgroups every iteration.
Chunking only engages below 400 tiles, so this affects downscaled steps only.
Numbers
garden/bicycle/counter/room 7K,
--num-downscales 2(the CLI default), M4 Max,mean of 2 interleaved reps:
PSNR/SSIM unchanged on all four (bicycle 23.38/0.605, counter 27.35/0.871,
garden 24.93/0.716, room 29.9/0.891). bicycle gains most: most gaussians, fewest
tiles, so
K_maxwas most inflated. counter and room sit at 425 tiles at 1/4resolution and never chunk.
--num-downscales 0is unaffected — measured on garden 7K, 79.0s → 79.6s.Also fixed
ensure_chunksnever resized on a resolution change. It comparedih/iwagainst
img_height/img_width, whichensure_forwardupdates earlier in thesame step, so the guard was tautological. Chunk buffers kept the previous
resolution's size. Reachable whenever chunking is active at more than one
resolution; garden dodges it because its 1/2-resolution phase has 1107 tiles and
takes the monolithic path.
num_tiles * MAX_TILE_ELEMSreplaces
num_points * 16, which bounded nothing — a gaussian's tile footprintis unclamped, so the sort could write past the end of
gaussian_ids/packed_xy_opac/packed_conic/packed_rgb.guarded the whole gradient block on
alpha < 0.999f, skippingT *= 1/(1-alpha)but the forward multiplied
Tby(1 - alpha)using that same capped alpha, leavingT1000x too small for every nearer gaussian on the pixel. The cap now zeroes only the sigma-routed gradients (v_conic,v_xy,v_opacity). Quality-neutral on garden 7K.PROFILE_STAGESreported real times. It applied the mach timebase toMTLCounterResultTimestampvalues that are already nanoseconds, scaling everystage by 41.67x. Stage totals now reconcile with command-buffer GPU time.
Tooling
-frecord-sources -gline-tables-only, so a.gputracecarries shader source and per-line cost in Xcode. Verified with
xcrun metal-source. Neither flag changes codegen; measured free (30.5s vs30.8s on garden).
msplat_begin_gpu_capture/msplat_end_gpu_capturewrite a.gputracedocument. CLI exposes them via
MSPLAT_GPUTRACE,MSPLAT_GPUTRACE_STEP,MSPLAT_GPUTRACE_ITERS; needsMETAL_CAPTURE_ENABLED=1. Both edges sync so thewindow holds whole command buffers under
commitAndContinue.BENCHMARK=1reports per-resolution-phase timings, refine/densify share, andintersection counts against the per-tile cap.
Known issues
scenes. Suspected cause is the capacity bound, not
K_max: for counter at fullresolution
num_tiles * 2048= 13M againstnum_points * 16= 6.8M at ~424Kgaussians, so the packed buffers roughly double during the 1x phase. garden and
bicycle have enough gaussians that the old term dominated and nothing changed.
Sizing from what the GPU last reported needing would be sound and tight, but
that would be an additional readback + capacity clamp in the sort kernel.
Not addressed
MAX_TILE_ELEMS = 2048per-tile cap saturates (max=2048from ~step 2500)at both
--num-downscales 0and 2, so gaussians are being dropped. The droppedset is chosen in atomic arrival order before the depth sort, and a dropped
gaussian still increments
vis_countswhile contributing nothing toxys_grad_norm, biasingavg_graddown in exactly the tiles that are starved.saturate()on the forward output has no matching mask in the backward, sogradients are nonzero where the composite clips.
h/t @frs0n for finding two of these bugs in frs0n/msplat-ios:
context (iPhone 15 Pro, 20852 gaussians, 906292 slots needed against 333136
allocated)
ensure_chunksresize bug worth PSNR 20.56 → 21.68 on a4-downscale-level config.