松田研究室
FindingID: p2-c0-resident-binning
Status: stable / task 2026-08 msplat gap sweep Phase 2 C0/C1
← Findings Index
P2 gap sweep · Phase 2 · C0 + C1

P2 gap sweep Phase 2 — C0 resident arena + C1 pass skip で -48.4% (bit-exact)

binning 経路 55% の主犯 (per-iter ~1.25GB buffer 確保 + Vec 三重往復) を grow-only resident arena + Buffer 直結で撤廃し、radix pass を 16 → 11 に削減。Lego 12.5k gate run で <strong>wallclock 766.07 → 395.59s = -48.4%</strong>、PSNR 36.216 → 36.183 dB (-0.033、atomic guard ±0.1 内)。emit_pairs 23.47 → 0.47ms/call (<strong>-98%</strong>)、radix_sort 10.19 → 4.79ms (-53%)、extract 757 → 145µs (-81%)。Day 0 attribution で機構を一段修正: Metal new_buffer は lazy なので host 計測 alloc は 42µs しかなく、<strong>1.25GB/iter の実コストは first-touch page fault として emit の GPU wait に現れていた</strong> (fresh-alloc 26.5ms vs buffer 再利用 0.61ms = 43×)。数学的同値 (演算順序・数値不変) を parity test 群で証明し、default を Resident に flip。

positiverank: highc0-c1-adopted-default-flip-minus-48-percent第 1 軸第 3 軸gap-sweepphase-2c0-resident-arenac1-radix-pass-skipbinningbit-exactunified-memorypage-faultlego
Δ PSNR
12.5k gate: 36.216 → 36.183 dB = -0.033 dB (bit-exact 実装、atomic 非決定性 guard ±0.1 内で通過)
Δ Wallclock
12.5k gate: 766.07 → 395.59s = **-48.4%** (emit -98% / radix -53% / extract -81%)
Impact
単発 -48.4% は本 project 最大の 1 施策改善で、msplat 実測 gap (11m48s vs 3m40s ≈ 3.2×、Lego 30k) を大幅に詰める lever が確定。766.07 → 395.59s は kernel を 1 行も触らず host 側 buffer 管理だけで達成 (bit-exact)。emit_pairs は 38.2% → 1.2% に消滅し、新 bottleneck は backward_raster 31.4% (→ 2-BWD last_contributor の trigger 25% を初超過) と radix_sort 11.7% (GPU compute 本体)。STP (feat.F) は emit の alloc 成分が消えた今、相対効果の再 bench が必要 (Phase 1a の -7.45% は大半が同じ page fault の削減で C0 と重複していた可能性が高い)。教訓: unified memory 環境の profiling では「GPU 時間」に host メモリ管理コストが化ける — per-kernel wallclock だけでなく buffer lifecycle を疑うこと。
01

本文

binning 経路 55% (p2-gap-sweep-baseline) の主犯を grow-only resident arena + Buffer 直結 (C0) と radix 定数桁 pass skip 16 → 11 (C1) で是正。演算順序・数値を一切変えない bit-exact 施策のみで Lego 12.5k が 766.07 → 395.59s (-48.4%)。Day 0 attribution が機構を一段修正した: 主犯は host alloc 時間ではなく、毎 iter 新規確保される ~1.25GB buffer への first-touch page fault が GPU wait に化けていた。

1. Day 0 attribution — 仮説の機構修正 (gate 判定)

計測regimeemit 内訳値
1k smoke (profile)6.8k splats / cap 1.7M pairsgpu / readback / upload / alloc523 / 87 / 24 / 9 µs (host 系 18.5%)
micro-bench fresh-alloc407k splats / cap 104M pairsgpu / readback / upload / alloc26.50ms / 2.09ms / 824µs / **42µs** (gpu 90%)
micro-bench buffer 再利用同上 (同一 dispatch 列)gpu のみ**0.61ms** (fresh-alloc の 1/43)
機構修正 — alloc コストは GPU wait に化ける

設計調査の仮説「emit の主犯は host alloc + Vec 往復」は方向として正しかったが、attribution の宛先が違った。Metal の new_buffer は lazy なので host 計測 alloc は 42µs しかない — 毎 iter 新規の keys 833MB + idx 417MB へ GPU が最初に書き込む瞬間の first-touch page fault (zero-fill) が wait_until_completed に紛れ込み、emit の「GPU 時間」26.5ms の 97% を占めていた。文言通りの gate (host 系 ≥50%) は 1k smoke で 18.5% と不発だが、regime 不一致 (6.8k vs 407k splats = capacity 60×) を micro-bench で補正すると非 kernel コストは ~98% で gate は決定的に通過。C0 本実装を GO とした。

2. 実装 (C0 / C1、いずれも bit-exact)

項目内容同値性の証明
**C0 resident arena** (`BinningKind::Resident`)splats/depths 入力・keys/idx ping-pong・hist/offsets・scalar uniform 群・tile_offsets を grow-only arena として iter 間再利用。emit → radix → extract を Buffer 直結、host に降ろすのは sorted_idx と tile_offsets のみ。capacity = max(1<<16, n×16, 前 iter 実績×1.5)、overflow は arena grow + 再 emit (12.5k run で発火 1 回 = iter 1 のみ)。batched_tail (A.7) も arena 直結対応resident vs per_iter_alloc で sorted keys / sorted idx / tile_offsets の byte equality (synthetic + 40k splats ~515k pairs + arena 再利用 + 空入力) + forward 1-frame bit-exact (per-stage / batched_tail)
**C1 radix pass skip** (`radix_passes_for_tiles`)sort key = (tile_id<<32)|depth_bits で tile_id < num_tiles → 上位の all-zero 4-bit digit pass は stable radix の恒等置換。pass 数 = min(16, 8 + ceil(bits(num_tiles-1)/4))、Lego 2500 tiles で 11。enum flag なしの直接置換 (legacy 経路にも適用)、奇数 pass の ping-pong 出力先も検証radix_sort_binned vs 16-pass 版 vs CPU stable sort の byte equality (2500 tiles = 11 pass 奇数 / 40000 tiles = 12 pass 偶数 / u32::MAX = 16 pass、各 200k keys)

3. A/B 1k smoke (C0 単独分離、両 arm C1 込み)

metricper_iter_allocresidentΔ
wallclock (1k)11.36s10.66s-6.2%
emit_pairs588µs/call231µs/call**-60.7%**
radix_sort2.470ms/call2.196ms/call-11.1%
extract_off233µs/call132µs/call-43.3%
PSNR (100 views)19.480 dB19.440 dB-0.04 (refine 非決定性圏内)
C1 単独の寄与

Day 0 run (C1 前、16 pass) の radix 3.685ms/call → C1 適用 per_iter_alloc arm で 2.470ms/call = -33%。pass 数 16 → 11 (-31%) とほぼ整合。1k regime は splats 6.8k で C0 の効果が小さく出る (capacity 60 分の 1) ため、C0 の本判定は 12.5k gate で行った。

4. 12.5k gate run (baseline 766.07s / 36.216 dB との比較)

metricbaseline (per_iter_alloc, 7e5ca2d)resident (C0+C1, 0abdaa2)Δ
**wallclock (training)**766.07s395.59s**-48.4%**
PSNR (100 views)36.216 dB36.183 dB-0.033 dB (guard ±0.1 内)
final splats407,267407,901+0.16% (軌道等価)
ts_fwd_emit_pairs293.3s (23.47ms/call, 38.2%)5.9s (0.47ms/call, 1.2%)**-98.0%**
ts_fwd_radix_sort127.4s (10.19ms/call, 16.6%)59.8s (4.79ms/call, 11.7%)**-53.1%**
ts_fwd_extract_off9.5s (757µs/call)1.8s (145µs/call)-80.8%
ts_backward_raster164.5s (13.16ms/call, 21.5%)160.4s (12.83ms/call, **31.4%**)-2.5% (share は最大化)
ts_fwd_rasterize43.3s (3.47ms/call)41.2s (3.30ms/call)-4.9%
verdict: 採用 — `binning` default を Resident に flip

flip 条件 (-15% & parity 通過) を大幅超過。PSNR -0.033 dB は bit-exact 実装下の run-to-run atomic 非決定性 (backward の atomic<float> → refine 軌道 ±、splats +0.16%) で説明され、guard ±0.1 dB 内。PerIterAlloc は敗者 variant として enum に保持 (A/B baseline 用)。eval / render など apply_backend_config を通らない caller も binner Cell default の同期 flip で resident を踏む。

5. Phase 2 残課題への影響

候補C0 前C0 後判定
2-BWD last_contributorbackward 21.5% (trigger 25% 未達で保留)**31.4%****trigger 初超過 — 次の最有力候補**
radix sort 本体 (GPU compute)16.6% (alloc 混在)11.7% (compute 主体)#5.40 8-bit radix の再評価余地はあるが優先度低
STP (feat.F) default 再判定-7.45% (gate -8% に僅差未達)要再 benchPhase 1a の -7.45% は emit の page fault 削減と重複していた可能性が高い (emit share 38.2% → 1.2% で寄与源が消滅)。C0 後 baseline で paired A/B を取り直してから default 判定
separable SSIM / SH-Adam fusion5.2% / 6.1%6.6% / ~9.5%不発維持 (相対 share は上昇したが trigger 未達)
教訓 — unified memory の profiling 罠

per-kernel wallclock で「GPU が遅い」ように見える区間に host のメモリ管理コスト (lazy alloc の first-touch) が化けることがある。F.1 / F.3 / #5.40 が kernel 側の最適化で 3 連敗した理由も遡って整合する — 遅かったのは kernel ではなく buffer lifecycle だった。今後の kernel 最適化判断は「buffer 再利用 micro-bench で kernel 本体時間を分離してから」を必須手順とする。

02

関連する run

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

03

関連 finding

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