松田研究室
FindingID: p2-c3-tile-counting
Status: stable / task C3 per-tile counting sort worktree 統合 + GPU gate (2026-08-16 GPU 専有タスク)
← Findings Index
P2 gap sweep · C3 GPU gate · 棄却

C3 per-tile counting sort — parity PASS / PSNR guard PASS だが replay 有意差なしで棄却 (数値つき)

worktree (`worktree-agent-a61d27ad0ed372418`) で実装済みの C3 (count → CPU exclusive scan → exact-size scatter → per-tile bitonic sort、msplat 風) を main へ統合し GPU gate を完遂。<code>splat bench --parity</code> で正しさを確認 (tile_offsets byte-exact、grad buffer 5 種すべて tol 1e-4 以内 PASS)、12.5k full run の PSNR guard も通過 (36.226→36.190 dB、-0.036 dB)。しかし fixed-workload replay bench (final regime n=10) では <strong>+3.15% (むしろ僅かに遅い、閾値 max(2σ,1%)=4.45% 未満で非有意)</strong>、radix_sort kernel 単体も +7.5% で speedup 仮説は不成立。CPU 側 exclusive scan が GPU パイプラインに同期点を挟むことが原因と推定。full run 単体では -8.8% と matching しない結果が出たが、これは既知の full-run 分散 (~10%) の confound と判断し gate 判定には使わない — bench harness (p2-bench-harness-calibration) 導入の価値をそのまま裏付けた。<strong>default flip 見送り、flag として保持</strong>。副次効果として sort key に <code>splat_idx</code> を含めたことで depth-tie 非決定性が解消 (sorted_idx tie-order ambiguity が原理的に消える)。

negativerank: mediumrejected-no-significant-speedup-flag-retained第 1 軸gap-sweepphase-2c3tile-binningcounting-sortbitonic-sortgpu-gaterejectedbench-harnesslegodeterminism
Δ PSNR
12.5k full run PSNR guard: 36.226 → 36.190 dB = -0.036 dB (±0.1 dB guard 内、PASS)
Δ Wallclock
replay bench final n=10: GlobalRadix 32.383ms/iter (σ2.23%) vs TileCounting 33.403ms/iter (σ4.60%) = **+3.15% (非有意、閾値 4.45%)**。iter5000 n=3: +0.38% (非有意)。radix_sort kernel 単体: final n=10 で 5721us→6151us (+7.5%)。full run 単体 (n=1): 360.96s→329.11s (-8.8%、非有意判定と矛盾、confound と判断し不採用)
Impact
別 worktree で実装済みだった C3 (per-tile counting sort) を main の util.rs 分割後の構造 (bench harness commit fe68cfa 由来) に手で移植し統合。smoke compile + unit test 6 本 + 既存 106 test 全 pass、build warning 0。splat bench --parity で数学的正しさを確認 (tile_offsets byte-exact、5 grad buffer tol 1e-4 以内 PASS)。しかし fixed-workload replay (final checkpoint n=10) では +3.15% ― 閾値 max(2σ,1%)=4.45% 未満で有意な高速化なし。radix_sort kernel 単体も +7.5% 遅い。12.5k full run の PSNR guard は通過 (-0.036 dB) したが、同じ full run の wallclock は -8.8% と replay の非有意判定に矛盾しており、これは既知の full-run 分散 confound と判断し gate 判定には採用しない (むしろ bench harness の存在意義を裏付ける実例)。default flip は見送り、TileCounting は BackendConfig flag として保持 (正しさは証明済みなので将来 CPU scan を GPU 化する等の改良余地があれば再挑戦可能)。副次効果として sort key (depth_bits<<32)|splat_idx により depth-tie 非決定性を解消した。
01

本文

Phase 2 完了時点 (finding: p2-last-contributor) で radix_sort が 13.5% (絶対) 残っていた。別 worktree (worktree-agent-a61d27ad0ed372418) で先行実装されていた C3 (msplat 風 per-tile counting sort: count → CPU exclusive scan → exact-size scatter → per-tile bitonic sort、threadgroup memory 32KB = 4096 要素上限、超過 tile がある frame は正しさ優先で GlobalRadix に全面フォールバック) を main へ統合し GPU gate を完遂した。

1. 統合作業

項目内容
worktree`worktree-agent-a61d27ad0ed372418` (branch 同名)、分岐元 c8e5881 (main は fe68cfa / b053e35 の bench harness commit 2 本分先行)
conflict 要因`apply_backend_config` が `cmd/train.rs` の private fn から `crates/splat-cli/src/util.rs` の pub fn に抽出済みだった (bench harness 実装時)。tile_bin.rs / tile_bin.metal / config.rs は main 側で無変更だったため diff がそのまま apply、train.rs の C3 差分 (`tile_sort` destructuring + `use_tile_counting_sort` 配線 + `build_config_items` 2 行) のみ util.rs / train.rs へ手で移植
smoke compileMSL は runtime compile のため Rust build では未検証だった。`cargo test -p splat-metal tile_counting` (= `GpuTileBinner::new` 経由) で shader 構文を検証、6 test 一発で PASS (shader 構文エラーなし)
unit test5 本 (`tile_counting_matches_legacy_synthetic` / `_random_500k` / `_fallback_on_oversized_tile` / `_biased_single_tile_under_threshold` / `_empty_input_routes_to_legacy`) + panic guard 1 本 (`resident_sorted_bufs_panics_after_tile_counting_sort`、BatchedTail 併用の防衛) 全 PASS。既存 106 test も無傷
build`cargo build --release` warning 0、`cargo check --tests` clean

2. parity (正しさの証明)

`splat bench --config c3-parity-baseline.toml --parity c3-parity-candidate.toml` (final checkpoint、409,660 splats、GPU loss path) で baseline (GlobalRadix) と candidate (TileCounting) の 1 iteration を比較。

項目結果
loss_valuebaseline=0.004210821 candidate=0.004210905 diff=8.4e-8
tile_offsets**PASS (exact byte equality, n=2,500)** — per-tile count/scan は完全一致
means_grad / log_scales_grad / rot_quats_grad / raw_opacities_grad / sh_coeffs_grad**全 5 種 PASS** (tol atol=1e-4 rtol=1e-4、max_abs_diff は 2.8e-5〜5.8e-8 の範囲)
sorted_idx (informational)2,197 / 969,279 要素差 = **0.227%** — sort key layout の違い (legacy `(tile_id<<32)|depth_bits` vs C3 `(depth_bits<<32)|splat_idx`) による tie-order 差であって誤りではない (既存 harness 規約により gate 対象外)
PARITY OVERALL**PASS**

3. replay bench A/B (final + iter5000 checkpoint)

`splat bench` で final checkpoint (409,660 splats) / iter5000 checkpoint (373,933 splats) の 2 regime を各 n=3 で計測。final regime は有意性が閾値付近だったため n=10 に escalation した (n=3/n=5 の σ 推定自体がノイズを持つという p2-bench-harness-calibration の運用ガイドラインに従う)。

regimenvariantmedian ms/itermeanσ_pctdelta (vs GlobalRadix)閾値 max(2σ,1%)判定
final (409,660 splats)3GlobalRadix30.49631.0533.16%———
final3TileCounting32.47932.5690.53%+4.88%6.32%非有意 (境界付近)
final10GlobalRadix32.50332.3832.23%———
final10TileCounting33.83333.4034.60%**+3.15%****4.45%****非有意 (n=10 escalation で確定)**
iter5000 (373,933 splats)3GlobalRadix30.31330.5262.15%———
iter50003TileCounting31.18530.6413.66%+0.38%4.30%非有意 (ほぼゼロ)
全 regime・全 n で delta は閾値未満 (有意な高速化なし)。n=3→n=10 escalation で delta は 4.88%→3.15% に縮小し、有意性はより弱まった (n=3 が過大評価だった可能性)。方向は一貫して TileCounting が僅かに遅いかタイ。

4. per-kernel: radix_sort/counting-sort 区間そのものも高速化していない

regimenGlobalRadix avg_us/callTileCounting avg_us/calldelta
final105,721.46,150.9+7.5%
final35,771.65,846.0+1.3%
iter500035,578.35,726.8+2.7%
`ts_fwd_radix_sort` timing label (variant 問わず sort dispatch 全体を包む) の avg_us/call。C3 は count→CPU exclusive scan→scatter→bitonic の 4 段だが、CPU scan が GPU コマンドバッファの同期点を挟むため、per-tile bitonic sort 自体の理論的優位 (msplat 同様、binary search 抽出が不要) を CPU-GPU 往復コストが相殺している、と推定 (kernel 内訳の詳細分解は未実施、future work)。

5. 12.5k full run — PSNR guard PASS、しかし wallclock は replay と矛盾 (confound と判断)

同一 recipe (`lego-bench-calib-checkpoints` = 現行 default スタック resident C0/C1 + last_contributor 2-BWD) で `tile_sort = "tile_counting"` のみ変えた 12.5k full run を 1 本実施。

metricbaseline (lego-bench-calib-checkpoints)C3 (lego-c3-tile-counting-12500)delta
PSNR (mean val, 100 views)36.226 dB36.190 dB**-0.036 dB (±0.1 dB guard 内、PASS)**
final splats409,660405,293-1.1%
training wallclock (log 実測)360.96s329.11s-8.8%
wallclock -8.8% は gate 判定に使わない (bench harness の存在意義そのもの)

full run 単体の wallclock delta (-8.8%) は、n=10 replay bench が示した「非有意 (+3.15%、むしろ僅かに遅い)」と方向が逆転している。p2-bench-harness-calibration finding が実測した通り full run 間には ~10% の分散 (dataset load / refine cadence / relocation 等の交絡) があり、n=1 の full run 比較はこの分散に完全に飲み込まれる規模の delta。今回の C3 は replay bench (σ で統制された比較) が唯一の信頼できる速度判定材料であり、full run 単体の数値だけで判断していれば誤って +8.8% の高速化を採用していたことになる — bench harness 導入の価値をそのまま裏付ける実例。

6. 判定

  • **verdict: 棄却 (数値つき) — default flip 見送り、TileCounting は BackendConfig flag として保持**
  • 根拠: replay bench (統制された唯一の速度判定材料) で全 regime・全 n において有意な改善なし (n=10 final: +3.15% < 閾値 4.45%)。radix_sort kernel 単体も一貫して同等かやや遅い
  • PSNR guard は通過 (-0.036 dB) — 品質面の懸念はない。実装の正しさも parity で証明済み (tile_offsets byte-exact、grad 5 種 tol 内)
  • 副次効果 (positive): sort key を `(depth_bits<<32)|splat_idx` にしたことで depth tie が原理的に消え、sorted_idx の tie-order 非決定性 (production シーンで ~0.2%、p2-bench-harness-calibration が発見・gate 除外していたもの) が C3 経路では解消される。ただし現状 default は GlobalRadix のままなのでこの恩恵は享受できていない — 将来 default 化する場合の副次的な決定論化メリットとして記録
  • future work: CPU exclusive scan を GPU prefix scan に置き換えれば同期点を削減できる可能性があるが、Phase 2 の timebox 方針 (3.5 週上限、8 月は執筆月) に従い今回はここで打ち切り、flag のまま保持する

7. 関連

  • 実装: `splat/crates/splat-metal/src/kernels/tile_bin.rs` (`tile_counting_sort_resident` ほか)、shader: `splat/shaders/forward/tile_bin.metal` (`tile_count`/`tile_scatter`/`tile_bitonic_sort`/`tile_unpack_idx`)
  • config 配線: `splat/crates/splat-train-v1/src/config.rs::TileSortKind`、`splat/crates/splat-cli/src/util.rs::apply_backend_config`
  • gate 用 config: `splat/configs/2026-08-16-c3-*.toml` (parity 2 + replay bench 6 + full run 1)
  • gate run: `splat/runs/lego-c3-tile-counting-12500/`、replay bench raw 出力: `splat/runs/bench-c3/*.toml` (gitignore 対象、再生成可能)
  • bench harness 規約: `docs/findings/p2-bench-harness-calibration.toml`
02

関連する run

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

03

関連 finding

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