task の前提「refine の host RMW = CPU 1-thread bound = wallclock -5x の余地」を `SPLAT_TIMING=1` を導入して 5k smoke で実測したところ、refine 系 ops (compact + accumulate + opacity_decay) の合計 wallclock 寄与は **<1%** と判明。真の bottleneck は forward (60.1%) + loss_gpu (28.0%) + adam/target_upload (~10%) で、refine を完璧に GPU 化しても全体 wallclock は **誤差レベル** しか変わらない。Phase E は方針を切替えて (1) `SPLAT_TIMING=1` env で kernel timing を有効化する instrumentation を追加、(2) forward を 5 sub-kernel に分割計測して bottleneck の解像度を上げ、(3) **demo kernel として `refine_opacity_decay.metal` を実装** (kernel + Rust pipeline + `refine.gpu_path` flag の plumbing pattern を validate)、(4) GPU/CPU 経路の bit-close 検証 (max diff < 1e-3、5k full PSNR delta -0.21 dB = 許容内) を実証。卒論 narrative としては「Metal kernel 直叩きの limit を測定した負の finding」として価値あり (memory `autonomous_plan_brush_parity.md` の P1.E 想定通り)。次 step (Phase F) は forward subdivision で判明した `ts_fwd_sort` 15.5% / `ts_fwd_emit` 12.8% の tile-binning chain と、5x sequential `cmd.wait` の Adam batching が target。
本 Phase E は task framing「refine の host RMW loop が wallclock -5x の余地」を SPLAT_TIMING profile で falsified させた。5k smoke の breakdown で refine 系 ops の合計寄与は <1%、真の bottleneck は forward 60% + loss 28%。代わりに demo kernel として `refine_opacity_decay.metal` を実装し kernel plumbing pattern を validate、Phase F (真の bottleneck) に向けた forward subdivision profile を残した。卒論 axis 1 narrative の「Metal kernel 直叩きの limit」を測定値として確定する負の finding。
SPLAT_TIMING profile (5k smoke, 84k splats) で refine 系 ops の wallclock 寄与は <1%: ts_refine_compact 0.6% / ts_refine_accumulate 0.3% / ts_opacity_decay 0.005%。一方 ts_forward 60.1% + ts_loss_gpu 28.0% が dominant。refine を fully GPU 化しても全体 wallclock は誤差レベル (~-1%) しか変わらない。axis 1 contribution は方針転換: (a) Phase F で tile-binning chain (sort 15.5% + emit 12.8% = 28%) を target、(b) demo kernel `refine_opacity_decay.metal` で kernel plumbing pattern を validate (CPU vs GPU bit-close 1.5e-5、PSNR delta -0.21 dB / 許容内)。
| kernel | total wallclock | calls | avg / call | % total |
|---|---|---|---|---|
| **ts_forward (total)** | **123.146s** | 5000 | 24.629ms | **60.1%** |
| — ts_fwd_sort | 33.839s | 5000 | 6.768ms | 15.6% |
| — ts_fwd_emit | 27.511s | 5000 | 5.502ms | 12.7% |
| — ts_fwd_rasterize | 11.598s | 5000 | 2.320ms | 5.4% |
| — ts_fwd_project | 3.179s | 5000 | 635µs | 1.5% |
| — ts_fwd_offsets | 2.072s | 5000 | 414µs | 1.0% |
| **ts_loss_gpu** | 57.374s | 5000 | 11.475ms | **28.0%** |
| **ts_adam** | 9.893s | 5000 | 1.979ms | **4.8%** |
| ts_target_upload | 8.053s | 5000 | 1.611ms | 3.9% |
| ts_project_back | 4.750s | 5000 | 950µs | 2.3% |
| **ts_refine_compact** | **1.137s** | 11 | 103.4ms | **0.6%** |
| **ts_refine_accumulate** | **605ms** | 5000 | 121µs | **0.3%** |
| **ts_opacity_decay (CPU)** | **957µs** | 11 | 87µs | **0.005%** |
| TOTAL (sum) | 204.958s | — | — | 100% |
| 項目 | task 仮説 | 実測 (5k smoke) | 判定 |
|---|---|---|---|
| refine の host RMW = bottleneck | wallclock の主因 | 0.85% (compact + accumulate + decay) | **falsified** |
| CPU 63.4% = 1 core only | refine RMW で 1-thread bound | 実測 ~42-51% (M4 Max 16 core 中 0.4-0.5 core) | **GPU dispatch wait** で thread block 中 (CPU 使ってない) |
| refine fully GPU 化 → wallclock -5x | 42m → 9m (brush parity) | 上限 -1% (refine 全体が 0.85% のため) | **not achievable** |
| axis 1 contribution = refine kernel | split / clone / decay の Metal 化 | demo `refine_opacity_decay.metal` のみ | **方針転換** (Phase F へ) |
| axis 1 真の target | (未定義) | forward tile-binning chain 28% | **Phase F の中心** |
shaders/opt/refine_opacity_decay.metal: brush 互換 opacity decay (sigmoid-space) を element-wise kernel として実装。30 行 MSL、1 thread / 1 splat。crates/splat-metal/src/kernels/refine.rs: `RefineOps` struct (Library + PSO 保持) + `dispatch_opacity_decay()` (adam.rs と同 pattern)。3 unit tests (CPU と max diff 1.5e-5、minus_opac=0 no-op、100 splat ramp 検証) all pass。crates/splat-metal/src/kernels/mod.rs: `pub mod refine;` 追加。crates/splat-train-v1/src/config.rs: `RefineConfig.gpu_path: bool` 追加 (default false で backward compat)。crates/splat-train-v1/src/trainer.rs: `Trainer.refine_ops: RefineOps` field 追加、`forward_with_state` に 5 つの timing::record 挿入 (`ts_fwd_project / _emit / _sort / _offsets / _rasterize`)。crates/splat-train-v1/src/train_loop.rs: `SPLAT_TIMING=1` env で `timing::enable()` + 終了時 `print_summary()`、refine cadence ブロックに `ts_refine_accumulate / ts_refine_compact / ts_opacity_decay[_gpu] / ts_opacity_reset / ts_mcmc_noise` を追加、`refine.gpu_path` flag で opacity_decay GPU 経路に分岐。configs/2026-05-25-p1-e-profile-{1k,5k}.toml: SPLAT_TIMING 計測用 config (gpu_path = false)。configs/2026-05-25-p1-e-gpu-decay-5k.toml: gpu_path = true smoke。CPU baseline 31.92 dB vs GPU 31.71 dB (-0.21 dB / 許容 ±0.5 dB)、wallclock 144→146s (+1.4% noise)、final splats 84629→84236 (-0.5% / bit-close prune cascade)。| test | 条件 | 結果 | 判定 |
|---|---|---|---|
| `opacity_decay_gpu_matches_cpu_formula` | 3 splat (σ=0.5/0.9/0.001), minus=0.1 | |GPU - 期待| < 1e-4 | ✅ pass |
| `opacity_decay_gpu_zero_is_noop` | 2 splat, minus_opac=0 | |before-after| < 1e-4 | ✅ pass |
| `opacity_decay_gpu_vs_cpu_100splats` | 100 splat ramp, minus=0.05 | max |CPU - GPU| 1.5e-5 | ✅ pass (bit-close) |
| 5k smoke PSNR delta | Lego, gpu_path on/off | -0.21 dB | ✅ 許容 ±0.5 dB 内 |
| 5k smoke wallclock delta | Lego, gpu_path on/off | +1.4% (144.32 → 146.32s) | ✅ ~unchanged (noise) |
| 5k smoke final splats delta | Lego, gpu_path on/off | -0.5% (84629 → 84236) | ✅ bit-close RNG cascade |
memory autonomous_plan_brush_parity.md は 「失敗してもデータ自体は卒論『Metal kernel 直叩きの限界 / 成功例』として価値あり」 を Phase E 設計時に明記。本 Phase E はまさにその「限界の測定」であり、「卒論軸 1 の core contribution は refine の Metal 化ではなく、forward の tile-binning chain (Phase F target、28%) と Adam の cmd buffer batching (5%)」 という方向修正を **実測値ベース** で提示する。SPLAT_TIMING instrumentation + 5 forward sub-kernel timing + demo kernel pattern は、Phase F 以降の「真の bottleneck の最適化」のための **infrastructure 整備** として 100% 流用可能。
p1-e-refine-gpu-smoke profile table を引用、Phase F の選択肢を実測値で justify する narrative 構成。# 1. Build (Phase E 実装含む)
cd splat
cargo build --release -p splat-cli
# 2. 5k smoke CPU baseline (gpu_path = false) + timing
SPLAT_TIMING=1 ./target/release/splat train --config configs/2026-05-25-p1-e-profile-5k.toml
# → ts_refine_compact 0.6% / ts_opacity_decay 0.005% / ts_forward 60.1%
# → val PSNR 31.92 dB, wallclock 144s
# 3. 5k smoke GPU path (gpu_path = true, opacity_decay GPU 経由)
SPLAT_TIMING=1 ./target/release/splat train --config configs/2026-05-25-p1-e-gpu-decay-5k.toml
# → ts_opacity_decay_gpu 182µs/call (CPU 87µs より遅い、dispatch overhead で逆転)
# → val PSNR 31.71 dB, wallclock 146s, splats 84236
# 4. unit tests (kernel bit-close)
cargo test --release -p splat-metal --lib refine
# → 3 tests pass (max diff 1.5e-5)
p1-d-stage2-30k-resultsp1-d-opacity-decay-smokem4-brush-benchautonomous_plan_brush_parity.md