松田研究室
FindingID: mcmc-noise-calibration
Status: stable / task A.2
← Findings Index
Negative finding · MCMC calibration / L1 全滅

A.2 MCMC 検証で発覚した noise gate 不整合と L1 全滅 segfault

SGLD gate を paper 流に書き換えたら mean_noise_weight が ~50x ズレ、calibration を直しても iter 240/500 で SIGSEGV。Bisect smoke で真の root cause は『L1 regularizer が opacity を 0 まで shrink → refine prune が全 splat を削除 → 空 buffer で SIGSEGV』と判明。

negativerank: highinvestigative第 1 軸phase-5mcmcsgld-noisecalibrationregressionsmokesegfault
Δ PSNR
2.5 dB (sh=3 + 全部入り、50 iter で発散)
Δ Loss
0.34 → 0.91 (発散)
Δ Splats
500 iter で 0 まで shrink
Impact
SGLD gate を paper 式に揃えた結果 mean_noise_weight が ~50-150x スケールズレし、calibration 補正 (5e5→5e3) でも iter 240 前後で SIGSEGV。Bisect smoke で真因が L1 全滅 → refine prune → 空 buffer crash というアルゴリズム順序問題と判明。Calibration ≠ correctness。
01

本文

A.2 part 1 で apply_mcmc_noise の SGLD gate を paper 流 σ(-k(o-t)) に書き換えたところ、mean_noise_weight = 5e5 が旧 gate 前提のチューニング値だったため有効 noise が ~50-150 倍に膨張し loss 発散 → kernel segfault。Calibration を 1/100 にしても iter ~240 で同じ SIGSEGV。Bisect smoke 4 件で真の root cause は L1 regularizer (opacity_l1 + scale_eigen_l1) が opacity を 0 まで shrink → refine prune が全 splat を削除 → 空 buffer で次 forward kernel が SIGSEGV、というアルゴリズム順序問題と判明した。

発生日
2026-05-22 23:50 JST
発見
A.2 実装直後の lego-mcmc-30k 30k full run が iter 1 直後に segfault
原因 (初期診断)
SGLD gate 変更と mean_noise_weight 値の不整合 (calibration ズレ)
原因 (真因)
L1 が opacity を 0 まで shrink → refine prune が全 splat 削除 → 空 buffer SIGSEGV
状態
実装は正しい、アルゴリズム順序とハイパパラ両方要調整。コードレベルの bug は無し。

観測

  1. lego-mcmc-30k (paper §3 全部入り、mean_noise_weight = 5e5) を 30k full で実行 → iter 1 直後に Metal kernel が segfault (loss 発散 → NaN → kernel crash と推定)
  2. smoke 切り分け: mean_noise_weight=5e5 のみ (scale_l1 / opacity_l1 OFF) で 100 iter 完走、loss は ~0.21 で安定 (sh=0)
  3. scale_eigen_l1=0.01 のみ: 100 iter 完走、loss は ~0.28 で安定 (sh=0)
  4. 3 つ全部入り + sh=3 + capacity=1M: 50 iter で loss が 0.34 → 0.70 に発散、PSNR 2.5 dB (catastrophic)。30k 走らせれば segfault に至る (splats が NaN → kernel 範囲外 read)

原因解析 (初期診断: gate factor 変更)

A.2 part 1 で apply_mcmc_noise の gate factor を以下に変更した。

// 旧 (brush 流近似): low-opacity 完全 reject
let factor = (1.0 - sigmoid(raw_opac)).powi(150);

// 新 (paper SGLD): low-opacity full noise
let factor = sigmoid(-cfg.noise_gate_k * (opac - cfg.noise_gate_t));
// k=100, t=0.995 (paper default)

両式の振る舞いを o (opacity = sigmoid(raw_opac)) で比較する。

o旧 (1-σ)^150新 σ(-100·(o-0.995))
0.01.01.0
0.1~5e-500.9999
0.5~7e-460.9999
0.9~1e-1500.9999
0.995~6e-3450.5
0.999~00.0179
旧式は実質「全 splat で noise OFF」、新式は「o < 0.995 全体で noise FULL」

mean_noise_weight = 5e5 は旧式 (実質ゼロ gate) 前提でチューニングされた値。新式 (フル gate) に切り替えると、noise の実効大きさが ~50-150 倍 に。Adam step (lr_means = 1.6e-4) の数千倍の random walk が means に注入され、image gradient signal が埋もれて loss が発散する。

数値的見積もり

新式での per-iter noise (Lego 5207 splats, low-opacity 80%):

coeff       = lr_mean * median_scale * mean_noise_weight
            = 1.6e-4 * 0.01 * 5e5 = 0.8
factor      ≈ 1.0  (低 opacity splat)
sample      ~ N(0, 1)
noise_per_axis = sample * 0.8, clamp(-median_scale, median_scale) = ±0.01

vs Adam step on means ≈ 1.6e-4 * |grad| ≈ 1.6e-6

ratio: noise / Adam = 0.01 / 1.6e-6 ≈ 6000x

純粋ランダムウォークが image-loss 由来の最適化を覆い隠す状態。

対応案 (3 案、優先度順)

案 A (推奨): mean_noise_weight を 1/50 〜 1/100 に下げる

5e5 → 5e3 〜 1e4 に。新 gate で「旧式と同程度の有効 noise」を目指す。config 修正のみ、コード変更不要。次回 verify run で mean_noise_weight = 5e3 から始め、PSNR 25 dB レンジに収束するかで判断。

案 B: SGLD coef を paper の正確な式に揃える

paper Eq.: ε = λ_lr · σ(-k(o-t)) · Σ · η。ここで λ_lr は Adam lr (1.6e-4)、Σ は covariance matrix (3x3、log_scales + rot_quats から再構成)、ηN(0, I)。現状実装は Σmedian_scale スカラーで近似 (rotation 不変の axis-aligned 単一値)。これを正確な Σ · η に置換 (rotation 回せば論文同等)。mean_noise_weight はそもそも paper 式には登場しないので、5e5 は別由来 (brush の流儀)。工数: 半日 (rotation rebuild + Σ · η 計算が apply_mcmc_noise に必要)。

案 C: warmup gate を追加

iter < 500 (= relocation_start) の間は noise も OFF。paper §3.2 は relocation のみ warmup 記述だが、本実装で必要なら拡張。工数: 1h (gate を train_loop 側で挟む)。

暫定処置 (今セッションでの対応)

  • lego-mcmc-30k config を 手付けせず、本書に「次回検証時の手順」として残す
  • docs/findings/mcmc-3-defects.md の A.2 完了基準を「ハイパパラ calibration が次セッション残課題」と注記
  • A.5 final ablation 表の MCMC 行は TBD のまま

次回手順 (案 A 採用前提)

  1. lego-mcmc-30k.tomlmean_noise_weight5.0e5 → 5.0e3
  2. smoke (50 iter sh=3) で loss が monotonic decreasing か確認
  3. 300 iter / 1000 iter で PSNR の trajectory を見る
  4. PSNR 21 dB レンジに 1000 iter で達するなら 30k full run kickoff
  5. 結果を A.5 表に追記

2026-05-23 01:00 retry 結果: 失敗継続 — calibration 修正のみでは不十分

mean_noise_weight = 5.0e3 (50x 補正) でも iter 1 直後に同じ segfault。

2026-05-23 01:15 切り分け smoke (4 件) → 真の root cause 判明

短期 smoke で各成分を bisect 実行 (splat/configs/2026-05-23-01XX-mcmc-*.toml)。

smokecomponents300-500 iter 結果
mcmc-l1-only-smoke (300 iter)scale_l1=0.01 + opacity_l1=0.01、noise=0、relocation=OFF完走、PSNR 1.08 dB、loss 0.34→0.91 発散
mcmc-noise-sh3-smoke (300 iter)noise=5e3、L1=0、relocation=OFF完走、PSNR 12.90 dB、loss 0.34→0.12 収束
mcmc-combo-iter-bisect (20 iter)noise=5e3 + L1=0.01、relocation=OFF完走、PSNR 5.19 dB
mcmc-combo-500 (500 iter)同上、500 iteriter ~240 で SIGSEGV (exit=139)
mcmc-l1-500 (500 iter)scale_l1 + opacity_l1 のみ、noise=0、relocation=OFFiter 500 で splats 0 → eval が SIGSEGV (exit=139)

真の root cause

Root cause

L1 regularizer (opacity_l1 + scale_eigen_l1) が opacity を 0 まで shrink → refine prune が全 splat を削除 → 空 buffer で次の forward kernel が SIGSEGV

iter 500 / 500  loss 9.0672e-1  splats      0  12.6 ms/iter
training done in 6.82s
final splats: 0
-- evaluation (val) --
[SIGSEGV]

paper の MCMC 設計では (1) L1 が opacity shrink を強制 → (2) relocation tick (default iter 500 以降毎 100 iter) が dead splat を live splat の clone で復活 → (3) 結果として live 数は維持される、という流れ。本実装の場合、relocation が refine prune の prune slot に依存しており (enable_mcmc_respawn=true だった旧経路)、relocation を独立 tick として組み込んだ新経路 (A.2 part 2) では「relocation tick の前に refine prune が全 splat を消す」順序問題が出る。

確定した修正方針 (次セッション着手)

  1. L1 weights をまず下げる: λ_Σ = λ_o = 0.01 → 0.001 (paper default の 1/10)、relocation 機能を活かして 30k 走らせ、PSNR がどこまで上がるかで再評価
  2. relocation の閾値を refine prune より緩く: opacity_threshold = 0.0050.001 等にし、refine が prune する前に MCMC relocation が処理できる順序にする
  3. train_loop で relocation tick を refine よりも前に走らせる (現状は train_step → refine → relocation の順なら refine 後の prune slot は relocation で埋まる、要コード確認)
  4. enable_mcmc_respawn=true の旧経路を MCMC 有効時のみ default に: refine prune と relocation を同一 tick で密結合する旧設計のほうが安定するかも

paper の精神は「shrink + 再配置で active 数を保つ」が、本実装の prune が 全滅させてしまう点が真の bug。calibration ではなくアルゴリズム順序の問題。

学び (卒論 §5.4 Negative Findings への素材)

  • paper 公式と既存実装の hybrid は危険: gate 関数を 1 行変えると hyperparameter の scale が ~50x ズレる
  • 必須 smoke gate: 大規模 (30k iter) 着手前に短時間 smoke で loss trajectory を見るべき。今回は smoke 経由せず full 着手して 1 サイクル無駄にした
  • calibration ≠ correctness: 単体テスト (26→31 pass) は通過するが、production hyperparameter は別途検証が必要

関連

  • A.2 実装 spec: mcmc-3-defects.md
  • 設定: splat/configs/2026-05-22-2305-lego-mcmc-30k.toml
  • smoke 切り分け config 群: 2026-05-22-2355-mcmc-noise-only-smoke.toml, 2026-05-22-2356-mcmc-scale-l1-smoke.toml, 2026-05-22-2357-mcmc-all-smoke.toml
  • 関連 memory: [[autonomous-plan-a-b]]
02

関連する run

この finding が観測された / 言及している実験 run。

03

関連 finding

© 2026 大竹律輝 — 松田研究室