defaults:
  - backbone: da3g
  - _self_

name: networks.zipsplat
compile_loss: false
weights: null

# General
img_size: 252
gaussians_per_prototype: 32

# Layer role assignment (0=deepest=L39, 1=middle=L29, 2=shallowest=L19)
# clustering_layer=0: L29 post-prepare collapses to rank 1 in trained models — cluster on L39 instead.
clustering_layer: 0
scene_init_layer: 0

# Attention (num_heads = embed_dim / 64 for head_dim=64)
num_heads: ${eval:'${.backbone.embed_dim} // 64'}
mlp_ratio: 4.0
fuse_layer_scale: 0.01

# Query clustering
query_sample_ratio: [0.5, 1.0]
query_ratio_schedule: 0.5
query_scale_with_views: 0.5
knn:
  init_mode: uniform
  chunk_size: 2048
  n_iters: 5

# Token selection: kmeans (hard, detached) | soft_kmeans (differentiable) | stride | random.
# soft_kmeans = arm B of the diff-kmeans A/B (gradient flows through the cluster assignment).
token_selection_mode: kmeans
soft_kmeans:
  temperature: 0.1
  max_iters: 5
  es_tol: 0.0
  init_mode: uniform
  compute_dtype: bfloat16

# Color skip (ca_concat-style, always on when with_color_skip=true)
with_color_skip: true
color_skip_dim: 128

# Runtime
return_attention: true
use_checkpoint: false

# Gaussian head
gaussian_head:
  sh_degree: 1
  max_scale: 0.1
  render_max_scale: 15.0  # hard upper clamp on activated scale; lower (~5) bounds early isect_tiles footprint
  random_background: false
  coupled_init: true
  use_l1_loss: true
  use_chamfer: true
  loss_on_context: false
  mse_weight: 1.0
  lpips_weight: 0.05
  chamfer_weight: 0.1
  depth_weight: 0.01
  # Gaussian representation + image loss (see GaussianHead.default_conf)
  gaussian_type: 3dgs      # 3dgs | 2dgs
  image_loss: lpips        # lpips | perceptual
  lambda_normal: 0.0       # 2dgs normal-consistency weight (set ~0.05 for 2dgs runs)
  lambda_dist: 0.0         # reserved (distortion loss deferred)
  # Detach chamfer/geo loss from gaussians that activated (rendered). 2dgs has no activated
  # mask -> set false there; the fair 3dgs control also sets false (chamfer on all gaussians).
  detach_activated_gaussians: true

# Token-space TTO distillation teacher (off by default; mirrors ZipSplat.default_conf).
# When on, an extra term pulls the encoder tokens toward a K-step token-TTO refinement of
# themselves (frozen decoder), so the feed-forward init internalizes the gain without TTO.
use_tto_distill: false
tto_distill:
  weight: 10.0          # added directly to the total loss (l1 distill -> O(10) is sane)
  num_steps: 1          # K inner TTO steps (1 = cheap fixed-point distillation)
  lr: 3e-3              # inner-loop lr (Adam ~3e-3; SGD needs a much larger, separate lr)
  inner_optimizer: adam # adam (sign-like step) | sgd (true-gradient step)
  render_scale: 0.6     # render the inner gradient at this fraction of HxW (speed)
  loss_type: l1         # outer distill loss (mean over tokens; l1 -> saner weight)
  warmup_steps: 0       # apply the term only after this many training steps
