A CUDA implementation of two sorting-free, stochastic approaches for rendering 3D Gaussian Splatting models with zero VRAM overhead: if the model fits in VRAM, it renders exactly.
This project is built on the following papers and their open-source implementations. Consider a citation when applicable.
@inproceedings{kheradmand2025stochasticsplats,
title = {{StochasticSplats}: Stochastic Rasterization for Sorting-Free 3D Gaussian Splatting},
author = {Kheradmand, Shakiba and Vicini, Delio and Kopanas, George and Lagun, Dmitry and Yi, Kwang Moo and Matthews, Mark and Tagliasacchi, Andrea},
booktitle = {IEEE/CVF International Conference on Computer Vision (ICCV)},
year = {2025},
pages = {26326--26335},
doi = {10.1109/ICCV51701.2025.02443},
url = {https://ubc-vision.github.io/stochasticsplats/}
}@article{rijsdijk2026gaussianpointsplatting,
title = {Gaussian Point Splatting},
author = {Rijsdijk, Joris and Peters, Christoph and Weinnman, Michael and Marroquim, Ricardo},
journal = {ACM Trans. Graph.},
volume = {45},
number = {4},
publisher = {Association for Computing Machinery},
year = {2026},
doi = {10.1145/3811272},
url = {https://jorisar.nl/gaussian_point_splatting/}
}@inproceedings{hahlbohm2026fastergs,
title = {{Faster-GS}: Analyzing and Improving Gaussian Splatting Optimization},
author = {Hahlbohm, Florian and Franke, Linus and Eisemann, Martin and Magnor, Marcus},
booktitle = {IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
year = {2026},
pages = {18946--18957},
doi = {tbd},
url = {https://fhahlbohm.github.io/faster-gaussian-splatting/}
}Overview
The backend offers two accumulation modes, selected by the USE_GPS flag:
- Stochastic transparency (
accumulate_st_cu): for every pixel inside a Gaussian's screen-space AABB, the Gaussian is accepted with probability equal to its evaluated alpha at that pixel. Accepted fragments race via a singleatomicMinon a 64-bit framebuffer entry that packs(depth_bits << 32) | gaussian_index, so each pixel retains the closest accepted Gaussian. This applies the classic stochastic transparency idea to splatting, in the spirit of StochasticSplats. - Gaussian Point Splatting (GPS) (
accumulate_gps_cu): instead of testing pixel centers, each Gaussian emits a Poisson-distributed number of sample points drawn from its own 2D distribution, splatting each as a point. This integrates the Gaussian over the pixel footprint by construction (see below).
A separate resolve_cu pass reads the winning fragment per (sub)pixel, evaluates spherical harmonics for the
single winning Gaussian, and writes color, depth, and alpha. Averaging SAMPLES_PER_PIXEL independent
frames — or accumulating them progressively across calls — converges to the correctly blended image. An
optional integer SUPERSAMPLING_FACTOR renders the framebuffer at higher resolution and box-filters down.
Current Load Balancing Scheme and Its Limitations
Both accumulation kernels launch one thread per Gaussian and balance work within a warp: Gaussians with at
most n_sequential_threshold (= 4) fragments are processed sequentially by their owning thread, while larger
Gaussians are handled cooperatively — the whole 32-lane warp picks them up one at a time (parameters broadcast
via warp.shfl) and splits their fragments across lanes. This keeps small primitives cheap while preventing a
single fat Gaussian from stalling its lane.
The limitations are inherent to the per-thread mapping:
- No balancing across warps/blocks. A warp that happens to contain one enormous Gaussian iterates many times while sibling warps sit idle — a classic tail effect that underutilizes the GPU.
- Serialized large Gaussians within a warp. The cooperative loop walks the warp's large primitives one after another, so a warp with several big Gaussians processes them sequentially.
- The
max_fragmentscap trades correctness for bounded cost: very large Gaussians are under-sampled, which can show up as artifacts. - GPS is worse here, since the per-Gaussian point count is Poisson-distributed and effectively unbounded, making the imbalance both larger and data-dependent.
- Supersampling scales well for GPS but poorly for ST. A factor
smultiplies each Gaussian's footprint bys²(more pixels in the AABB for ST, more sample points for GPS). GPS absorbs this as additional independent points at fine, uniform granularity, so it parallelizes cleanly and supersampling barely hurts throughput. ST instead piles thes²-larger fragment count onto the coarse per-Gaussian (warp-cooperative) work unit, so the within-warp serial iteration and the tail effect grow quadratically.
A two-pass design could remove the imbalance at the cost of some VRAM. Rather than queuing every fragment, only the large Gaussians (those above the cooperative threshold) need to be expanded: Pass 1 counts their fragments and prefix-sums them into a global work buffer; Pass 2 then launches one thread per queued fragment, giving those Gaussians uniform granularity, full occupancy, and no warp tail, while small Gaussians keep the cheap per-thread path. This caps the extra VRAM at the large Gaussians' fragment count rather than the whole scene. The original GPS implementation already applies this expand-and-distribute strategy to every Gaussian, large or small — which is why it outperforms this implementation for views dominated by large Gaussians (their work spreads more evenly), but falls behind on scenes with many small ones, where each tiny primitive needs its parameters to be loaded twice and pays a per-point bookkeeping cost that the sequential fast path avoids.
Pre-computation and Quantization/Compression
- Pre-compute the 3D covariance and opacity activation.
project_gaussianrebuildscov3d(quaternion + scales) and appliessigmoid(raw_opacity)on every invocation, i.e. once per Gaussian per sample. Both can be computed once at load time; storing the 6-floatcov3din place of the scale/rotation buffers keeps the VRAM cost near zero. Gaussians with opacity below theopacity_thresholdcould also be culled at load time. - Quantize the Gaussian parameters. Means,
log-scales, quaternions, andlogit-opacity are read in the hot loop, so smaller encodings (fp16, smallest-three quaternions, chunked low-bit quantization) help VRAM and bandwidth. Similar ideas could also be used for the SH coefficients. Many web viewers like SuperSplat/Spark and GPS already implement these ideas.
Further Reading
If you are interested in the underlying ideas, you may find the following resources useful in addition to the papers cited above:
- Stochastic transparency: The order-independent transparency technique this renderer builds on.
- atomicMin rendering: Markus Schütz et al.'s compute-shader point
renderer popularized the 64-bit
atomicMin(depth | payload)technique this renderer uses to resolve depth without sorting. - CuRast: A CUDA software rasterizer also by Markus Schütz et al. that tackles a similar per-primitive load-balancing problem outlined above in the context of triangle rasterization.
Installation
This method is provided as an extension to NeRFICG, a radiance field and
view synthesis framework. The StochasticGSCudaBackend can also be built standalone as a PyTorch extension for
integration into other codebases.
Requirements
- An NVIDIA GPU
- Linux or Windows
- A recent CUDA SDK (CUDA Toolkit 12.8 recommended) and a compatible C++ compiler
- Anaconda / Miniconda installed
Setup
As a preparatory step, the NeRFICG framework needs to be set up. Please follow the instructions in its README to set up a compatible Conda environment.
Now add Stochastic Gaussian Splatting as an additional method by cloning this repository into src/Methods/StochasticGS:
# HTTPS git clone https://github.com/fhahlbohm/stochastic-gaussian-splatting.git src/Methods/StochasticGS
or
# SSH git clone git@github.com:fhahlbohm/stochastic-gaussian-splatting.git src/Methods/StochasticGS
Next, install all method-specific dependencies and CUDA extensions using:
python ./scripts/install.py -m StochasticGS
Note: The framework determines on-the-fly what extra modules need to be installed. Sometimes this causes unnecessary errors/warnings that can interrupt the installation process. In this case, first try to rerun the command before investigating the error in detail.
The CUDA backend can also be built standalone as a PyTorch extension:
pip install git+https://github.com/fhahlbohm/stochastic-gaussian-splatting/#subdirectory=StochasticGSCudaBackend --no-build-isolation
Usage
StochasticGS is a renderer, not an optimizer: it loads a pretrained 3DGS .ply (MODEL.PLY_PATH) and renders it,
so the example configs set NUM_ITERATIONS: 0 and enable the interactive GUI. Two starting points are provided in
configs/:
m360_garden.yaml— render and evaluate a model against a real dataset. It usesDATASET_TYPE: MipNeRF360with a held-out test split (TEST_STEP: 8), so you can compare renders against ground truth. SetMODEL.PLY_PATHto your.plyandDATASET.PATHto the dataset.jastrzebia_gora.yaml— free-viewpoint rendering of a standalone.plywith no dataset (DATASET_TYPE: Empty) along an optional predefinedCAMERA_PATH. This is the right template for large scenes and fly-throughs; the example targets a 106M-Gaussian capture with a 600-frame camera path. SetMODEL.PLY_PATHandCAMERA_PATH.
To launch, fill in the /TODO/ paths and run the config through the standard NeRFICG entry point:
python ./scripts/train.py -c src/Methods/StochasticGS/configs/m360_garden.yaml
With NUM_ITERATIONS: 0 and GUI.ACTIVATE: true this loads the model, skips training, and opens the interactive
viewer. For a non-Empty dataset (e.g. m360_garden.yaml), the NeRFICG script inference.py can provide quality
metrics, FPS benchmarking, etc. as described in the NeRFICG repository.
With an Empty dataset (e.g. jastrzebia_gora.yaml) there is no dataset to evaluate against — read Trainer.py to see
how the code loads MODEL.CAMERA_PATH and uses it for benchmarking FPS or rendering video frames.