第 1 版 (<code>rasterize_backwards_per_splat</code>、32-thread/threadgroup、bench で default 比 +34.1% regression) の finding が挙げた「低 occupancy が最有力仮説」を検証するため、<strong>アルゴリズム・diagonal scheduling は完全に同一のまま `PER_SPLAT_BATCH` だけ 32→256 (= 16×16 tile 全体、`pso_min256` 対象) に変えた</strong> 新 kernel <code>rasterize_backwards_per_splat_tg256</code> (flag <code>backward_scheme = "per_splat_tg256"</code>) を追加した。GPU 検証 (PSO compile / parity 3 本 / trainer 統合 parity / 4-way bench) を実施した結果、<strong>tg256 は第 1 版比 -4.22% (occupancy 仮説の方向は正しい) だが、default per_pixel 比 +32.8% で依然として大幅に遅く NO-GO 確定</strong>。12.5k gate は実施していない (bench の時点で明確に劣後のため)。
本 finding は 「実装完了、GPU 検証は次段」という中間状態を記録する。到達点がどうであれ finding を追記する規約 (`docs/architecture/backend-and-compiler-conventions.md`) に従い、実装の設計・第 1 版との差分・parity test の設計・そして まだ検証していないことを検証していないと明示するために書く。GO/NO-GO の判定は次段の GPU session に持ち越し。
本タスクの依頼文は「batch 境界の barrier は 1 batch あたり 1-2 回に抑える」ことを目標に挙げていたが、これは『1 thread = 1 splat + splat 側勾配を register に累積 (atomic 不要)』という制約と構造的に両立しない — alpha compositing は pixel ごとに順序依存 (前の splat の合成結果 `pix_acc`/`T` を次の splat が読む) なので、diagonal scheduling で 1 thread = 1 splat を保ったまま複数 splat の合成を並列化するには、diagonal の 1 iteration ごとに pixel 側 state の可視性を保証する `threadgroup_barrier` が必須になる (brush PR #401 自身も同じ制約を持つ)。「barrier を 1-2 回/batch に抑える」を文字通り実現する唯一の経路は『1 thread = 1 pixel、splat を threadgroup memory に staging』という構造だが、これは (a) register 累積を失い atomic を再導入する、(b) 既に禁忌リスト入りしている `bwd-tg` staging パターン (backward への Tg staging = busy -79%、docs/strategy/2026-08-msplat-gap-sweep.md 大原則 4) と同型になる、の 2 点で不適格。本実装は「1 thread = 1 splat + register 累積」という依頼の他の要件を優先し、barrier 回数の目標は『batch あたり 1-2 回』ではなく『barrier 総数を第 1 版比で削減する』(§3 参照) という代替目標に読み替えた。
| test | 何を pin するか | GPU 実行状況 |
|---|---|---|
| `rasterize_psos_compile` | 全 PSO 構築 (`rasterize_backwards_per_splat_tg256` 含む)。`pso_min256` は `maxTotalThreadsPerThreadgroup < 256` で `Err` を返すため、レジスタ圧迫で 256-thread floor を割り込んでいないかもここで判明する | **PASS** — floor 達成、`Err` なし |
| `backward_per_splat_tg256_matches_per_pixel_original` | 第 1 版と同一 fixture (32×32 / 24×24、24 splat)。`abs_tol/rel_tol = 1e-4` dual-threshold | **PASS** (32×32: max_abs=4.272e-4/max_rel=1.968e-5、24×24: max_abs=1.831e-4/max_rel=3.925e-6、dual-threshold で pass) |
| `backward_per_splat_tg256_matches_per_pixel_original_multi_round` | n_splats=600 (tile あたり 600 splat、`rounds = ceil(600/256) = 3`) で round 境界の `pix_T`/`pix_acc` 持ち越し + per-round gradient flush を確認。**advisor レビューで指摘された懸念が残る**: この fixture は `color_a = 0.88` で T が最初の数十 splat で `1e-4` sentinel に落ちるため、round 2/3 では『生きた』pix_T/pix_acc の持ち越しではなく『すでに死んだ pixel への no-op』を通るだけの可能性が高い — round 境界の re-init バグ (最も起きやすい種類のバグ) は検出できるが、doc comment が主張する『round を跨ぐ生きた状態遷移』の直接証明にはなっていない | **PASS** (max_abs=5.150e-5、max_rel=5.783e-2 — abs 側で dual-threshold pass。上記の fixture 限界の懸念はこの実測では解消されていない、次段への申し送り事項として残す) |
| `backward_per_splat_tg256_byte_equality_single_pixel` | 単一 pixel 非ゼロ dldr で Original+FullRange+Off と bit 一致 (第 1 版と同型) | **PASS** |
| `splat bench --parity` (実 checkpoint 409,660 splats) | 第 1 版と同じ 5 grad tensor (means/log_scales/rot_quats/raw_opacities/sh_coeffs) の abs/rel 1e-4 比較。**これが multi-round path の本命の検証** — Lego の実 tile は密な tile で 256 splat を超えるため、上記 unit fixture より遥かに現実的な multi-round 実行を踏む | **PASS OVERALL** — 5 grad tensor 全て tol 内 (means max_abs_diff=1.014e-5 が最大)、loss_value diff=3.539e-8、`sorted_idx` は非 gate の既知 tie-order 非決定性 (0.222%) |
実装完了後、GPU 検証の直前に coordinator から「現在 12.5k gate (M2 Tier1 A1-A4) が直列で走っており、run の合間の短い隙間に bench を差し込むと gate を汚染する。以後 GPU を使う実行は行わず、実装と CPU 側 test・ビルドのみ進めよ」という指示を受け、実装 session はここで止めた。**本 finding の §4/§5b は後続の別 GPU 検証 session (2026-08-30、A7 gate 完了後の GPU 空き窓) での実測を追記したもの** — GPU 権は main から明示的に付与され、series 直列化 (A7 gate → M3b 検証) を確認した上で実行した。
本 §5b の全計測 (PSO compile / parity 3 本 / trainer統合 parity / 4-way bench) は binary sha256 `23bf1d73dbc000515fcf09b480998f829afd5f473e99472a9777db0858b4abb7` (git rev `3fc6dea`、`refine_scratch_persist`/`refine_direct_write` の default は **まだ false**) で実行した。同一セッション後半で A7 gate 採用に伴い上記 2 flag の default を true に flip している (`p3-refine-3pass.toml` 参照) が、`splat bench` は `train_step` を直接叩く replay harness で refine tick (100 iter に 1 回) を経由しないため、この flip は本 §5b の数値に影響しない。ただし 4-way bench の 4 config (`2026-08-30-m3*-bench-*-final.toml`) はいずれも refine flag を明示しておらず、flip 後に再実行すると effective config が変わる点は再現時の注意事項として明記する。
| config | backward_scheme | median ms/iter (n=3) | σ (%) | vs default per_pixel | GPU busy% |
|---|---|---|---|---|---|
| 2026-08-30-m3-bench-perpixel-final (default) | PerPixel | 13.548 | 0.59% | — | 79.3% |
| 2026-08-30-m3-bench-perpixel-unopt-final | PerPixel (unopt: Original/FullRange/Off) | 15.701 | 0.34% | +15.9% | 82.0% |
| 2026-08-30-m3-bench-persplat-final (第 1 版) | PerSplat | 18.787 | 0.12% | +38.7% | 85.0% |
| 2026-08-30-m3b-bench-persplat-tg256-final (第 2 版) | PerSplatTg256 | 17.994 | 0.35% | **+32.8%** | 84.3% |
tg256 (17.994ms) は第 1 版 (18.787ms) より **-4.22%** 速く、§3 が挙げた 3 効果 (barrier 総数 4.5 分の 1 に改善 / thread-iteration 効率 1.78 分の 1 に悪化 / barrier 単価上昇) の net が僅かに改善方向に振れたことを示す — occupancy 仮説そのものは方向として裏付けられた。しかし default per_pixel (13.548ms) と比べると tg256 は依然として +32.8% 遅く、`max(2σ,1%)` のどんな緩い閾値でも埋まらない差である。per-splat backward (atomic なし構造、brush PR #401 型) は 32-thread 版・256-thread 版のいずれの構成でも本実装の per_pixel (SIMD warp-level 最適化済み、atomic 使用) に対して優位性を示せなかった — M3 系列 (per-splat backward) はこの bench harness 上で NO-GO 確定とする。12.5k gate は実施していない (bench 時点で閾値を大幅に超えて劣後することが明白なため)。`BackwardSchemeKind` の default は `PerPixel` のまま、`.rs` は無変更。
§3 が示唆した『diagonal を batch 境界を跨いで連続させる』variant (thread lid が splat lid, lid+256, lid+512, ... を継続担当し、round 復元のたびに diagonal をリスタートしない) は同じ 256-thread occupancy を保ちつつ thread-iteration 効率を ~80% 程度まで戻せる見込みだったが、+32.8% のギャップを埋めるには焼け石に水の可能性が高い (効率改善だけで barrier 単価増・atomic なし化のオーバーヘッドを覆すには不十分と見られる)。M3 系列への追加投資は費用対効果が低いと判断し、本 finding をもって M3 系列の探索は打ち切りとする。