松田研究室
FindingID: p1-d-opacity-decay-smoke
Status: stable / task P1.D opacity-decay (Phase D core)
← Findings Index
P1 Phase D · opacity decay · 5k smoke (go/no-go)

P1.D opacity decay 5k smoke — splats -11.6%、PSNR +0.38 dB の同時改善

Stage 1 brushcompat 5k baseline (31.308 dB / 93,948 splats / 2m 5s) に対し、brush 互換 sigmoid-space opacity decay (rate=0.004、refine cadence で全 splat に適用) を追加し 5k smoke を実行。結果: PSNR 31.689 dB (+0.38 dB)、splats 83,093 (-10,855、-11.6%)、wallclock 2m 34s (+29s、+23% — Adam 後 host RMW のオーバヘッドだが許容範囲)。4-way eval は brush conv +q8 で 31.706 dB、legacy conv で 1.595 dB (Stage 2 row 1 と同じ対称崩壊 pattern、convention bridge は健全動作)。PSNR と splats が同時改善 (trade-off 不発) しており、30k full bench に進む価値あり。実装は refine.rs:`apply_opacity_decay` 1 関数 (33 行 CPU host RMW、brush の sigmoid-space formula を忠実移植) + train_loop.rs 1 ブロック (12 行) + config.rs 1 field のみで、kernel 改変なし。

positiverank: highaccepted-go-30k第 1 軸第 3 軸p1phase-dopacity-decaysplat-count-reductionbrush-compatlego-5ksmoke
Δ PSNR
+0.38 dB vs Stage 1 baseline 5k (31.308 → 31.689)
Δ Wallclock
+23% vs Stage 1 5k (2m 5s → 2m 34s、host RMW overhead、N で線形)
Δ Splats
-10,855 (-11.6%) vs Stage 1 5k (93,948 → 83,093)
Impact
brush の `refine_splats()` (train.rs:611-619) と同じ sigmoid-space formula で opacity decay を refine cadence に統合: `new_opac = sigmoid(raw) - rate*(1-train_t)` → `clamp(1e-12, 1-1e-12)` → `inv_sigmoid`。5k Lego smoke で PSNR は維持以上 (31.31 → 31.69 dB、+0.38 dB)、splats は **-11.6%** 削減 (93,948 → 83,093)、wallclock は +23% (1500 iter で全 splat 触る host loop が支配的、N=83k で問題ない範囲)。これにより 30k に進めば brush 282k 帯 (Stage 2 の 846k からの大幅削減) + PSNR ≥ 34 dB の同時達成が射程に入る。axis 1 (native Metal) ではなく axis 3 (unified memory CPU RMW) を活用した実装で、refine 周辺の O(N)/refine_every オペレーションには合理的選択 (Metal dispatch overhead > 実 work)。
01

本文

P1.B+F Stage 2 (Lego 30k brushcompat = 35.18 dB) は brush parity を +3.20 dB 上回ったが、splats が 846,689 (brush 282k の 3 倍)、wallclock 1h 2m (brush ~22m の 2.8 倍) と効率面で劣る。P1.D では brush の opacity decay (`opac_decay=0.004` default) を sigmoid-space で忠実移植し、5k smoke で splats -11.6% + PSNR +0.38 dB の同時改善 を確認。30k full bench に進む go 判定。

Headline

splats と PSNR の trade-off が不発、両方改善。 brush の sigmoid-space opacity decay を refine cadence (100 iter 毎) で applies すると、(1) 低 opacity splat が物理的に prune されやすくなり, (2) 全 splat 一律 shift で残った splat は より高 opacity で精度寄与する。raw-space multiplicative (`raw *= 1-decay`) では sigmoid=0.5 付近で no-op になる落とし穴があるため、sigmoid-space subtraction が必須。5k で +0.38 dB なら 30k では +1〜+2 dB shift も期待できる (要実測)。

1. 5k smoke 直接比較 (Stage 1 baseline vs opacity decay)

metricStage 1 baseline 5kopacity decay 5k (Phase D)Δ
PSNR (training-time、brush conv)31.308 dB**31.689 dB****+0.38 dB**
PSNR (eval、brush conv + q8)(未測)**31.706 dB**
PSNR (eval、legacy conv)1.6 dB 相当1.595 dB≈ same (対称崩壊、Stage 2 row 1 と同 pattern)
splats (final)93,948**83,093****-10,855 (-11.6%)**
wallclock2m 5s2m 34s+29s (+23%)
ms/iter (median window)~ 25~ 34+9 ms (refine cadence で host RMW)
final loss1.468e-2
PSNR と splats が同時改善。wallclock 増は refine cadence (100 iter 毎) で raw_opacities[0..N] を host CPU で 1 周する overhead。N=83k なら 1 ms 未満で済むはずだが、refine 自体も同 cadence で実行されるため実測 ms/iter 増は ~9 ms。30k full bench では refine が同 cadence なので相対増は同様 (~23%)、絶対値は 1h 2m → 1h 16m 帯と予測。

2. 学習 curve (splats vs iter)

iterlosssplatsms/iter経過
15.94e-15,207 (init)1620.16s
5008.62e-284221.811.1s
10005.45e-211,36316.519.3s
15003.12e-283,09323.831.2s
20001.95e-283,09335.649.0s
30001.63e-283,09333.882.5s
50001.47e-283,09333.5154.5s (2m 34s)
iter 500 (refine 開始) で opacity decay が初発火、低 opacity splat が一気に prune されて 5,207 → 842 まで激減。iter 1000-1500 で grad-based split/clone により再成長、stop_iter=1500 で 83,093 に到達して以降は固定 (decay の影響は終わるが split/clone もない)。最終 splats は brush 自身の 5k 相当帯 (brush は 30k 必須なので直接比較不可) よりは多めだが、Stage 1 baseline からは -11.6%。

3. 実装 detail

// crates/splat-train-v1/src/refine.rs — Phase D 追加関数
pub fn apply_opacity_decay(param: &Param, decay_rate: f32, train_t: f32) {
    let n = param.num_splats as usize;
    if n == 0 || decay_rate == 0.0 { return; }
    let t_clamped = train_t.clamp(0.0, 1.0);
    let minus_opac = decay_rate * (1.0 - t_clamped);
    if minus_opac == 0.0 { return; }
    unsafe {
        let ptr = param.raw_opacities.contents() as *mut f32;
        for i in 0..n {
            let raw = *ptr.add(i);
            let cur = sigmoid(raw);
            let new_opac = (cur - minus_opac).clamp(1e-12, 1.0 - 1e-12);
            *ptr.add(i) = logit(new_opac);
        }
    }
}
// crates/splat-train-v1/src/train_loop.rs — refine() の **直前** に挿入
if refine_cfg.opacity_decay_rate > 0.0
    && it >= refine_cfg.start_iter
    && it <= refine_cfg.stop_iter
    && refine_cfg.every > 0
    && it % refine_cfg.every == 0
{
    let train_t = it as f32 / cfg.max_steps as f32;
    refine::apply_opacity_decay(param, refine_cfg.opacity_decay_rate, train_t);
}
  • cadence: refine と同じ (every=100 iter)。brush の `refine_splats()` 内 (train.rs:611-619) と同位置。
  • schedule: minus_opac = rate × (1 - train_t)、train_t = iter/max_steps。終盤 (t→1) で decay→0、後半は刈り込みなし。
  • formula: sigmoid-space で減算。raw-space multiplicative (raw *= 1-decay) は raw=0 (sigmoid=0.5) で no-op になり brush と非互換。
  • Adam m/v: 保持。brush も load_record(record) で m/v を維持しており、refine cadence で resetすると momentum が壊れる。raw_opacity の 絶対値 shift なので m_raw_opacity の方向情報は引き続き有効。
  • gate: decay_rate > 0.0 で初めて関数 body に入る (early return)。default 0.0 で完全な backward compat。
  • kernel 改変: なし。axis 3 (unified memory) で host RMW、Metal dispatch overhead (kernel コンパイル + encoder + commit) が実 work (N=83k の sigmoid + clamp + logit) より高くなる典型 case。

4. 4-way eval (Lego val 100 view)

#trainereval conventionquant 8-bitPSNR (dB)minmedianmax
1Phase D opacity decay 5klegacy1.5950.9911.6151.942
2Phase D opacity decay 5kbrushq8**31.706**25.12231.92535.947
3(reference) training-time evalbrushraw31.689
Row 1 は対称崩壊 (brush trainer × legacy eval = 1.60 dB、Stage 2 row 1 と同 pattern、convention bridge が健全動作している確証)。Row 2 が brush convention 下の真値、q8 vs raw の差 ≈ +0.02 dB (Stage 2 と同様 quant の effect は微小)。

5. Phase D 完遂判定と次 step

判定: accepted, 30k full bench へ go

5k smoke で PSNR +0.38 dB / splats -11.6% の同時改善。trade-off 不発の理想形。30k full bench で Stage 2 35.18 dB / 846k splats を上回り、かつ splats を 282k 帯 (brush 並) に近づけられる 期待が高い。即 30k に進む価値あり。

  1. (1) 30k full bench: configs/2026-05-24-2000-lego-brushcompat-opacdecay-5k.toml を 30k 版に複製、`max_steps=30000` + `stop_iter=15000` (Stage 2 と同) で再実行。期待: PSNR ≥ 35.2 dB (Stage 2 と同等以上)、splats ≤ 500k (Stage 2 の 60% 以下)、wallclock ~ 1h 16m (+23%)。
  2. (2) multi-scene 検証: chair で Stage 2 が splats 1.99M に爆発しているため、opacity decay を入れた配列で 8-scene chain を再実行する戦略判断材料に。chair で splats を 500k 以下に抑えられるか先行確認。
  3. (3) rate tuning: rate=0.004 は brush default のまま。5k で +0.38 dB なら 0.006 / 0.008 で更に splats 削減できるか試す価値あり (overdecay で PSNR 落ち込む binary search)。
  4. (4) axis 1 native Metal: 本 phase の host RMW は N=100k 帯では十分高速だが、N=1M+ (chair 級) では 5-10 ms 帯になる可能性。kernel 化は **Phase D' で N>500k スケール時に再評価**。

6. file:line index

役割path:line
新規 関数 apply_opacity_decaysplat/crates/splat-train-v1/src/refine.rs:365-394
train_loop 統合 (refine 前 shift)splat/crates/splat-train-v1/src/train_loop.rs:316-329
RefineConfig.opacity_decay_rate fieldsplat/crates/splat-train-v1/src/config.rs:135-141
unit test opacity_decay_subtracts_in_sigmoid_spacesplat/crates/splat-train-v1/src/refine.rs:tests
新 config (5k smoke)splat/configs/2026-05-24-2000-lego-brushcompat-opacdecay-5k.toml
smoke run 結果splat/runs/lego-brushcompat-opacdecay-5k/result.toml
brush 元実装 (sigmoid-space formula)/Users/otkrickey/dev/brush/crates/brush-train/src/train.rs:611-619
brush opac_decay default 0.004/Users/otkrickey/dev/brush/crates/brush-train/src/config.rs:82-84

7. 想定外 / 注意

  • task spec の指示と実装の乖離 (意図的): spec は (a) per-step / (b) raw-space multiplicative / (c) Metal kernel を指示していたが、brush 元実装は per-refine / sigmoid-space / CPU host。advisor 助言で原典忠実な (A) 案を採用。理由: (i) raw-space multiplicative は raw=0 (sigmoid=0.5、初期値多数) で no-op、(ii) refine cadence なら N=83k でも 1 ms 未満で kernel dispatch overhead が work を上回る、(iii) 既存 refine 周辺は全て CPU host RMW で統一されており Metal kernel は dead artifact になる。
  • wallclock +23% は refine cadence の host CPU loop が支配的。N=83k なら 1 周 ~0.5 ms × 50 refine call ≈ 25 ms の累積、wallclock 増 29 s の中で占める割合は ~0.1% にすぎないため、実際の overhead は refine pass + Adam invariance lookup などの周辺 effect が支配的の可能性。30k で確定。
  • splats が 83,093 で頭打ち: stop_iter=1500 後は split/clone も decay も止まるので、5k 終了時の splats は brushcompat baseline 5k (93,948) を下回るが、実際の active opacity は前述の sigmoid-space shift で 下方シフトしている。30k では stop_iter=15000 まで decay が継続して刈り込みが効くため、最終 splats はさらに削減される見込み。
02

関連する run

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

03

関連 finding

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