Skip to content

GaussianSplatting densification (split/prune during training) not exposed through the facade #1835

Description

@ooples

Problem

The 3D Gaussian Splatting paper's key training-time mechanism is densification — periodically SPLIT Gaussians whose accumulated gradient magnitude exceeds a threshold (they cover too much space) and PRUNE Gaussians whose opacity has collapsed to near-zero. This grows the initial cloud (~100k random Gaussians) into millions of specialized ones over the training run, and is what gives real GS its paper-quality output.

GaussianSplattingOptions exposes fields like DensificationInterval, SplitPositionJitter, SplitScaleFactor, SplitOpacityFactor — the config knobs exist. But nothing in the facade path (ConfigureModelBuildAsync) invokes densification during training. The Gaussian count stays fixed at whatever the constructor produced.

Combined with #1833 (facade optimizer settings silently ignored for GS), this caps GS quality at "random-init cloud with tiny gradient updates" — which is what the Week 11 Session B demo demonstrates visually (a few blurry blobs, unchanged across camera angles, regardless of training epochs).

Fix direction (agreed in design discussion)

Model-internal densification, following DDPM's specialization pattern.

DDPM handles its specialization (noise-prediction training, timestep sampling) entirely inside its inherited NeuralNetworkBase.Train(input, target) path — callers see a uniform Train(x, y) API; the specialized behavior is internal. No callback infrastructure was needed. GS densification is the same shape of problem — an interleaved-during-training operation that mutates the model — so it applies the same pattern.

Concretely: densification lives inside GaussianSplatting.TrainOnImageBatch (per #1834's IImageTrainable<T>). GaussianSplattingOptions.DensificationInterval and friends drive the schedule; the model decides when to densify based on its options. Facade calls TrainOnImageBatch per iteration; the model runs its gradient step + interleaves densification when the schedule fires. No new callback interface, no divergence from how DDPM already works.

Why this exceeds industry standard

  • Reference GS implementations (gsplat, nerfstudio, INRIA's official 3DGS repo) hardcode densification inside their training scripts. Users configure via YAML sections but the schedule is baked in — one code path per fork.
  • AiDotNet with model-internal densification: densification is a first-class configurable property of GaussianSplattingOptionssame options object exposes densification + hyperparameters + rendering all in one place. Reference impls fragment these across YAML sections (model config vs. training config vs. optimizer config).
  • No new callback infrastructure to teach; no divergence from how DDPM (and future specialized models) work. Uniform mental model.

Beyond-industry excellence goals

Four goals, all agreed to during design discussion:

1. All densification config in one strongly-typed options object (already partially there)

GaussianSplattingOptions already exposes DensificationInterval, SplitPositionJitter, SplitScaleFactor, SplitOpacityFactor, SplitOpacityMax. Extend to cover every industry-standard knob:

  • DensificationStartIteration — when to begin densifying (skip first N iters while cloud stabilizes)
  • DensificationEndIteration — when to stop (near end of training, freeze the cloud)
  • GradientNormThreshold — the actual split trigger (paper: τ_pos = 0.0002)
  • OpacityPruneThreshold — opacity below which a Gaussian is culled (paper: 0.005)
  • MaxGaussianCount — hard ceiling to prevent OOM
  • GradientAccumulationWindow — how many iterations of gradient norm to average for the split decision
    All nullable, all industry-standard defaults (from the 3DGS paper), user can override any.

2. Adaptive densification schedule (data-driven, not fixed interval)

Reference impls use a fixed every N iterations schedule. AiDotNet exceeds by exposing an AdaptiveSchedule option (default off, matches industry when off) that triggers densification when observed signals warrant it:

  • Gradient norm variance drops (cloud has stabilized enough that split noise won't destabilize)
  • Loss plateaus (cloud can't improve without more capacity)
    Adaptive schedule is fully customizable via a strategy interface; industry-standard fixed-interval schedule remains the default.

3. Post-training densification pruning + compression pass

After training, run a compression pass:

  • Prune all Gaussians below OpacityPruneThreshold
  • Merge nearby Gaussians whose bounding ellipses overlap by > threshold
  • Quantize spherical harmonics (float32 → int8 with a per-Gaussian scale)

Reference impls do this manually as post-processing scripts. AiDotNet exposes it as a first-class option: CompressOnBuildComplete = true (default false — industry-standard: user runs their own post-processing). When enabled, BuildAsync returns with the compressed cloud. Fully customizable via CompressionOptions (per-technique on/off).

4. Per-attribute learning rate schedules for split children

When a Gaussian is split, the two children inherit modified hyperparameters:

  • Position LR decreases (they're now local — small movements matter more, big jumps destabilize)
  • Scale LR increases (they're smaller — need more updates per step to converge)
  • Opacity LR unchanged
    Paper implements this manually. AiDotNet exposes via SplitChildLearningRateScales — nullable per-attribute multipliers, industry-standard defaults from the paper, user can override.

Wiring surface

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions