松田研究室
FindingID: p3-backward-audit
Status: stable / task p3-backward-audit (段1: 監査 / 段2: M8 atomic emit lane 分散の実装+gate screen)
← Findings Index
backward_raster kernel 監査 · M8 atomic emit lane 分散

backward_raster kernel 監査 — per-pixel 構造保持の残余候補 6 件、atomic emit lane 分散 (M8) を実装するも 3/3 pair で小さい regression (NO-GO)

per-splat backward (brush #401 型、M3/M3b) が 2 版とも regression で打ち切られたあと、<code>rasterize_backwards.metal</code> の per-pixel 構造を保ったまま <code>backward_raster</code> (kernel 内最大 share 40.07%) をさらに削れる余地を audit した。既存の勝者施策 (SIMD reduction / W1a warp sparse skip / last_contributor) と禁忌リスト (Tg staging -79% busy、f16、SIMD prefix、reduction) を踏まえると、<strong>bit-exact-class の候補で harness の分解能 (~1%、M2 Tier1 / M5a で確認済みの床) を超えるものは見つからなかった</strong> (bit-inexact な reciprocal division 候補 1 件は AIR レベルで headroom があると確認できたが実測は持ち越し)、というのが本 audit の主結論。最有力候補 (<code>rasterize_backwards_simd</code> の 9 atomic emit を lane 0 だけでなく lane 0..8 の 9 lane に分散する M8) を実装し <code>BackendConfig.raster_backward_atomic</code> (default <code>lane_zero</code>) で flag-gate した。正しさは新規 unit test 2 本 (bit-exact) と実 checkpoint (408,469 splats) の <code>splat bench --parity</code> で検証 — 後者は既存 backward が持つ (原因未確定の) self-noise と <strong>候補の diff が統計的に見分けがつかない</strong>ことを確認し、spread が新しい誤差源を持ち込んでいないことを示した。<strong>性能は whole-iteration・backward kernel 単体 gpu_ms のどちらでも一貫して遅い側</strong> (whole-iteration median ms/iter: off 13.586 → on 13.765、<strong>+1.32%</strong>、GPU busy +2.02%;<code>ts_backward_raster</code> gpu_ms/call: off 4.2016 → on 4.3670ms、<strong>+3.94%</strong>) — <strong>NO-GO</strong>、default は <code>lane_zero</code> のまま。12.5k full gate 用 config 6 本 (<code>2026-08-30-bwd1-12500-{off,on}-r{1,2,3}.toml</code>) は作成のみ (`splat check` で deserialize 確認済み)、明確な regression のため実行はしなかった。

negativerank: mediumm8-atomic-spread-correct-but-slower-not-recommended-for-default / audit-finds-no-headroom-above-harness-floor第 1 軸backward-rasterkernel-auditatomic-floatsimd-sumparityself-noisebackend-configregressionlegoper-pixelm8
Δ PSNR
N/A (bench replay は 1 checkpoint に対する forward+backward+adam 反復であり学習軌道ではない。12.5k gate は regression 判明のため未実施)
Δ Wallclock
**whole-iteration replay bench 3 pair 交互 (実 checkpoint 408,469 splats、800×800、iters=50×repeats=3、pipelining=off、production regime = submission/forward_submission ともに chained、`configs/2026-08-30-bwd1-bench-{off,on}.toml`)**: median ms/iter off→on = 13.564→13.731 (+1.23%) / 13.600→13.812 (+1.56%) / 13.595→13.753 (+1.16%)、平均 13.586→13.765ms/iter = **+1.32%**。GPU busy ms/iter 平均 10.752→10.969 = **+2.02%**、sync-tax はほぼ不変 (2.83ms/iter 前後、3 pair とも)。σ は各 repeat 内 n=3 で 0.03〜1.19%。**backward kernel 単体 (`ts_backward_raster` gpu_ms、per_kernel/per_stage 計測 regime、`configs/2026-08-30-bwd1-bench-{off,on}-perkernel.toml`、150 call = 3 repeats×50 iters)**: off **4.2016 ms/call** → on **4.3670 ms/call** = **+3.94%**。この regime の wallclock (off 15.191ms/iter, GPU busy 69.0% / on 15.361ms/iter, busy 69.4%) は non-production 専用 (busy が低く比較に使えない、上記 production regime の値が主) だが、`ts_backward_raster` gpu_ms は kernel 単体の直接計測として有効。
Impact
backward_raster (per-kernel share v2 で 40.07%、単独最大) を per-pixel 構造を保ったまま削る候補を 6 件監査した (段 1)。すでに勝者化済みの施策 (SIMD group reduction / W1a warp sparse skip+exp-skip / last_contributor 早期終了) と禁忌リスト (Tg staging、f16、SIMD prefix、reduction) を除くと、残る候補はいずれも大きい Δ を期待できず、harness の実測分解能 (~1%、M2 Tier1 が -1.11% を「判定不能」、M5a が -0.055% を「null」と判定した床) 前後に収まる可能性が高いという見立てになった。最有力候補として選んだのは M8: `rasterize_backwards_simd` の 9 atomic emit を lane 0 の 1 lane だけでなく lane 0..8 の 9 lane に分散発行する変更 — `simd_sum` が 9 成分の合計を全 32 lane に broadcast する性質を使い、emit する (address, value) の集合を一切変えずに発行 lane だけ変える (新しい算術を持ち込まない「弱い意味で bit-exact」な変更)。`BackendConfig.raster_backward_atomic` (`lane_zero` default / `spread_lanes`) で flag-gate し、専用 PSO は現行 default combo (`Simd` + `LastContributor` + `WarpAny`) にのみ追加した (他 combo は validate warning 付きで silent fallback、既存の `raster_backward_sparse` と同じ規約)。**正しさ**: 24-splat 合成 tile の単一 pixel fixture で bit 一致 (新規 test 2 本、`backward_atomic_spread_byte_equality_single_pixel` / `_parity_simd`)、実 checkpoint (12500 iter 生成、408,469 splats、PSNR 36.202 dB) での `splat bench --parity` は候補比較が FAIL することがあるが、**同じ config を自分自身と比較する self-check でも全く同じ頻度・同じ max_abs_diff (通常時 5.245e-5 / 稀に 1.469e-4) で FAIL/PASS する**ことを確認し、これが M8 固有の誤差でないことを実証した (発生機序自体は atomic 順序 self-noise / `sorted_idx` tie-order 非決定性のどちらとも確定できておらず、§4 callout に切り分けの詳細を記す)。**性能 (whole-iteration)**: 短時間 replay bench (12.5k 相当 checkpoint、iters=50×repeats=3、pipelining=off、production regime = submission/forward_submission ともに `chained`) を off/on 交互 3 pair 実施したところ、**3/3 pair で on が遅い**(median ms/iter: 13.564→13.731 / 13.600→13.812 / 13.595→13.753、平均 13.586→13.765 = **+1.32%**、GPU busy 平均 10.752→10.969 ms/iter = **+2.02%**)。**性能 (backward kernel 単体 gpu_ms)**: production regime では `ts_backward_raster` の `gpu_ms` が非ブロッキング harvest の帰属漏れで 0 に落ちる (既知の限界、`docs/architecture/backend-and-compiler-conventions.md` §4) ため、p3-kernel-share-post-s3 C1 と同じ `submission=per_kernel`/`forward_submission=per_stage` regime で同一 checkpoint を計測した。**`ts_backward_raster` gpu_ms/call: off 4.2016ms → on 4.3670ms = +3.94%** (150 call = 3 repeats × 50 iters、両 arm とも n=3 の repeat 内 σ ≤0.4%) — whole-iteration の +1.32%/+2.02% が「kernel 自体が遅くなったこと」に直接帰属できることを、share からの推論ではなく実測で確認した。advisor が指摘していた「lane 0 の 9 atomic は既にハードウェアがパイプライン化している可能性があり、spread の win は nil かもしれない」という懸念が的中し、むしろ select chain のオーバーヘッドがわずかに上回った形の **小さいが一貫した regression**。**NO-GO、default は `lane_zero` のまま変更しない**。12.5k full gate 用 config 6 本は `splat check` で deserialize 確認済みだが、bench screen の時点で明確な regression のため実行しなかった。`cargo build --release` + `cargo test --workspace --release` (全 175 test、新規 2 本含む) は全 pass、default 動作 (既存挙動) は無変更。
01

本文

per-splat backward (brush PR #401 型) の 2 版 (M3/M3b) が per-kernel share GO 判定 (backward_raster ≥25%) を受けて実装されたにもかかわらず、いずれも default (per_pixel: Simd+WarpAny+LastContributor) 比 +33〜39% の regression で打ち切られた (finding: p3-per-splat-backward / -tg256)。post-M4/A7 の per-kernel share v2 でも backward_raster は 40.07% まで share を伸ばしており (分母の radix 縮小による相対効果が主、絶対 gpu_ms 自体はほぼ不変)、依然として単独最大ブロックである。本 finding は「transpose (per-splat 化) 抜きで、per-pixel 構造を保ったまま backward_raster を削れる余地はまだあるか」を監査し (段 1)、最有力候補 1 件を実装して gate screen まで行った (段 2) 記録である。

1. 監査 — 観点別 (a)〜(f) の考察

(a) atomic 発行数削減
`rasterize_backwards_simd` は既に `simd_sum` で 32 lane を 1 SIMD group 単位に集約してから 1 atomic (256→8 atomic/splat、#5.33 M-2 採用) + W1a `simd_any` による all-invalid warp の静的 skip、を実施済み。threadgroup 全体まで畳む `_tg` variant (1 atomic/splat) は `threadgroup_barrier` コストが上回り既に敗退 (#5.33c 禁忌)。残っていた余地は「9 atomic を lane 0 だけで発行している低占有」で、これが M8 (段 2 で実装、後述)
(b) 再計算 vs. 保存
forward の中間値 (per-splat per-pixel の alpha/T) を保存して backward の再計算を減らす案は、必要な保存量が `O(pixels × splats-per-tile)` — 800×800 image で tile あたり数百 splat が典型のこの workload では GB 級になり非現実的。msplat / Inria 3DGS 原典も同じ理由で replay 方式を採る。**却下 (infeasible)**、候補として立てない
(c) last_contributor 早期終了の粒度
現行 default は 2 層構造で既にほぼ最適: `Simd` kernel は `simd_max(my_stop)` で SIMD group 単位に loop bound を縮め (lockstep 維持のため per-pixel には出来ない)、その上で W1a `simd_any(contrib)` が「bound と実際の stop の間、全 lane が寄与ゼロの iteration」を静的に skip する。これより細かい粒度 (sub-warp) にするには SIMD reduction の lockstep を諦めて atomic-per-smaller-group に戻る必要があり、これは `Original` 系への回帰 (#5.33 で敗退済み) と同型。**追加の候補なし、現状が実質的な天井**
(d) memory layout (AoS/SoA)
`Splat2D` を「sigma/alpha 計算に要る hot field (xy/conic/color_a)」と「v_rgb にしか要らない cold field (rgb)」に分割する案を検討したが、**期待 Δ は低いと判断し候補から外した**: `range`/`end` は threadgroup (`range`) または SIMD group (`end`、lockstep 用) 単位で uniform なので、ある iteration `i` において loop 内の全 lane は**同じ** `sorted_idx[i]` と**同じ** `splats[splat_idx]` を読む — 1 threadgroup あたり 1 回の 36-byte L1 読み出しが全 lane に broadcast されるだけで、AoS→SoA 分割で削れるのは「broadcast 済みの L1 read が 36→24 byte になる」程度であり、GPU の L1 transaction 粒度 (通常 32/64/128 byte 単位) を考えると実質的な transaction 数は変わらない可能性が高い。producer (`project.rs`) と consumer (forward kernel) 両方に触る高 effort に対して見合わない
(e) register pressure / occupancy
静的ツールでの検証には限界があった。`xcrun metal -O3 -std=metal3.1 -c` でのオフライン compile はエラー・warning 無しで通ったが (function constant 未解決のため specialization 前の状態)、AIR は register 割り当て前の LLVM IR であり、実際の register 割り当ては driver JIT が on-device で行うため、**この環境 (Xcode GPU Frame Capture 不可) では spill / occupancy の直接観測ができない**。既存 `[backend] profile = true` の PSO reflection ログ (`maxTotalThreadsPerThreadgroup`) は 256-thread floor の充足有無しか見せず、これも `docs/architecture/backend-and-compiler-conventions.md` 自身が既知の射程漏れ (D-2) として記録済みの限界と同じ。**ツール限界として記録、追加候補は立てられなかった**
(f) dL/dcolor の SH 次数依存分岐
`rasterize_backwards.metal` は SH 評価済みの RGB (`Splat2D.color_r/g/b`、project 段階で SH 係数から評価済み) を受け取るだけで、SH 自体の勾配 (`sh_coeffs_grad`) は `project_backwards` (per-kernel share 3.61%) 側の責務。**本 kernel には該当なし (N/A)**

2. 候補表 (上位 5 件 + 既存の敗退/禁忌の参考枠)

候補観点期待 Δ (backward 単体)bit-exact 性工数既知の敗因との衝突
**M8: atomic emit lane 分散** (実装・gate screen 済み)(a)不明瞭 — lane 0 の 9 atomic は既に HW がパイプライン化している可能性 (advisor 指摘)。実測: **+1.3%〜+2.0% regression**弱い意味で bit-exact (同一 (addr,value) 集合、発行 lane と順序のみ変化、既存の atomic 順序非決定性の範疇)低 (shader 1 file + Rust 配線)衝突なし (Tg staging/reduction とは別物) だが実測 NO-GO
`one_m_alpha` 除算 2 回 → reciprocal 1 回 + 乗算(e) ALU throughput**headroom 確認済み、gain は未実測**。function constant を default 値 (lc=true, sparse=true, spread=false) に固定した AIR (`xcrun -sdk macosx metal -O3 -std=metal3.1 -ffast-math -S`) を確認したところ、`rasterize_backwards_simd` 本体に `fdiv fast <3 x float>` (= `after_me / one_m_alpha`) と `fdiv fast float` (= `dL_dA * T_final / one_m_alpha`) の **2 命令が残っており、同一 denominator への CSE (reciprocal 共有) は起きていない** — 「fast-math が既にやっているので no-op」という当初の懸念は LLVM IR レベルでは反証された。ただし最終 GPU ISA への lowering は on-device compiler 依存でこの環境では観測不能なため、**理論的な headroom はあるが実測 gain は未確認**bit-inexact (rounding が変わる、rel ~1e-5 オーダー想定)低**p3-ssim-float4 と同型の衝突**: `float4_rcp` は ssim 単体 -20.8% でも 12.5k PSNR guard に抵触 (3/3 分離、rel 8e-5 が効いた) して不採用。backward は Adam に直結するので同種リスクがより重い
splat read の software prefetch (次 splat を先読みしつつ現 splat を計算)(e) memory latency hiding不明瞭 — 256-thread/tile で occupancy は既に高く (busy 79〜91%)、TLP がレイテンシを隠せている可能性が高いbit-exact (loop-carried な T/pix_acc の演算順序は不変、読み出しの順序だけ変更)低〜中(loop 構造の手動 unroll)衝突なし、ただし priori に効果を見積もれず prototyping が要る
`Splat2D` AoS→SoA 分割 (hot/cold field 分離)(d)低 (§1(d) の broadcast-read 論証参照 — L1 は既に broadcast 済みで削減余地が薄い)bit-exact**高** (producer `project.rs` + forward kernel consumer の両方に触る)衝突なし、ただし工数/期待 Δ 比が悪い
last_contributor の粒度を sub-warp まで細分化(c)無し — 現行 (simd_max + warp_any) が既に理論上の天井に近い。細分化には SIMD lockstep 放棄 = atomic-per-small-group への回帰が必要N/A (実装しても Original 系への逆行)N/A**#5.33 (Original 敗退) と同型の衝突** — 追加候補として立てず
(参考) threadgroup 全体 reduction (`_tg` variant の再挑戦)(a)既知: +63.6% (S2 後再判定)bit-exact—**#5.33c 禁忌、再挑戦せず**
(参考) backward への Tg staging (splat を threadgroup memory に事前ロード)(d)既知: busy -79%bit-exact—**禁忌リスト明記 (bwd-tg)、再挑戦せず**
(参考) per-splat transpose (brush #401 型、M3/M3b)構造転換既知: +34.1% / +32.8%PASS (abs/rel 1e-4)—**M3/M3b で 2 回敗退、探索打ち切り済み**
audit の主結論 — bit-exact-class の候補で harness 分解能を超えるものは残っていない (bit-inexact な候補 1 件は理論的な headroom があるが未実測)

M2 Tier1 (A1-A4 まとめ) は 12.5k n=5 で Δ -1.11% を「wallclock 効果量 ≤1% で分解能未満、判定不能」として棄却し、M5a (SSIM float4_rcp) は 12.5k で Δ -0.055% を「null」と判定した。この 2 つの実測が、本 harness (12.5k Lego、n=3〜5) が確実に検出できる下限が概ね 1% 前後であることを示している。段 1 の 6 候補のうち、(b)(c)(f) は候補として成立せず、(d) は broadcast-read 論証により低 payoff、(a) (M8) は実測で harness の床未満どころか **逆方向 (+1.3% whole-iteration / +3.94% backward kernel 単体)** だった。**(e) reciprocal division は唯一「headroom が無いこと」を実測で反証できなかった候補** — AIR レベルで 2 fdiv が残っていることを確認したが (未 CSE)、bit-inexact な変更であり p3-ssim-float4 と同型の PSNR guard 抵触リスクを持つため、実装・gate までは本 session のスコープ外とした。**正確には「per-pixel 構造を保ったまま bit-exact-class で大きく効く候補はもう残っていない」が本 audit の実測的な結論** (bit-inexact な候補は未走査のまま 1 件残る)。次に有意な Δ を狙うなら per-pixel 構造そのものを離れる (= per-splat 系、既に 2 回敗退) か、reciprocal division の実測 (bit-inexact、PSNR guard リスクあり) か、禁忌リストの改訂 (8-bit radix 再挑戦の J1 判断待ちと同型の意思決定) のいずれかが必要になる。

3. 実装した候補 — M8 atomic emit lane 分散

変更箇所
`splat/shaders/backward/rasterize_backwards.metal` (`rasterize_backwards_simd` のみ、function constant `USE_ATOMIC_SPREAD_FC` index 2 で specialize)、`splat/crates/splat-metal/src/kernels/rasterize.rs` (`RasterBackwardAtomicKind` enum + PSO 1 本追加 + `backward`/`backward_from_buf` シグネチャ拡張)、`splat/crates/splat-train-v1/src/{config.rs,trainer.rs}`、`splat/crates/splat-cli/src/util.rs` (配線)
発想
`simd_sum` は reduction 結果を**全 32 lane に broadcast**する (partial sum を返す `simd_prefix_*` とは異なる) ため、9 成分の合計値はどの lane も既にレジスタに持っている。従来 (`LaneZero`) は 9 回の `atomic_fetch_add` をすべて `simd_lane_id == 0` の 1 lane が発行する — SIMD グループは常に 32 lane 分の発行資源を lockstep で持つので、これは「1/32 lane しか active でない SIMD 命令」を 9 回連続発行するのと同義。`SpreadLanes` は 9 成分を lane 0..8 の 9 lane に 1 個ずつ割り当て、9 回の低占有発行を 1 回の (9/32 lane active な) 発行に畳む
PSO 行列の範囲
`(BackwardKind::Simd, RasterBackwardRangeKind::LastContributor, RasterBackwardSparseKind::WarpAny)` — 現行 default combo — にのみ spread 版 PSO を追加した。他の combo (FullRange や sparse=Off) には広げていない (default combo の A/B 以外に使う予定がないため、PSO 行列を 4→8 に倍化するコストに見合わないと判断)。他 combo で `spread_lanes` を指定すると `BackendConfig::validate` が warning を出し、黙って `lane_zero` 相当の PSO にフォールバックする (`raster_backward_sparse` の Simd 専用扱いと同じ規約)

4. 正しさの証拠

test / 手法何を pin するか結果
`backward_atomic_spread_byte_equality_single_pixel`dldr を 1 pixel だけ非ゼロにして atomic 順序の影響を消した条件で `LaneZero` vs `SpreadLanes` が bit 一致**PASS**
`backward_atomic_spread_parity_simd`24 splat 重畳 fixture (multi-pixel) で `abs/rel 1e-4` dual-threshold (既存 W1a parity test と同一形式)**PASS**
`rasterize_psos_compile`MSL コンパイル + 全 PSO 構築 (新規 `pso_backward_simd_lc_sparse_spread` 含む)**PASS**
workspace 全体 (`cargo test --workspace --release`)splat-core 7 + splat-io 1 + splat-metal 93 (新規 2 本含む) + splat-train-v1 76、default 挙動 (LaneZero) が無変更であること**PASS / failure 0 (2 件 ignore は既存の baseline 専用 test で本変更と無関係)**
`splat bench --parity` (実 checkpoint 408,469 splats、`configs/2026-08-30-bwd1-bench-off.toml` vs `-parity-on-candidate.toml`)means/log_scales/rot_quats/raw_opacities/sh_coeffs の全 grad tensor、tolerance は既存 backward variant 比較と同一の `abs=1e-4 rtol=1e-4`**intermittent FAIL (means_grad, 1/1,225,407 要素)** — 下記 callout 参照、既存 self-noise と同型と判明
`splat bench --parity` の intermittent FAIL は M8 固有の誤差ではない — ただし正確な発生機序 (atomic 順序 vs sorted_idx tie-order) は未確定

実 checkpoint (408,469 splats) での `--parity` は複数回実行すると PASS することも FAIL することもある (`means_grad` の max_abs_diff が通常時 5.245e-5 で PASS、稀に厳密に 1.469e-4 まで跳ねて `abs=1e-4` を超え FAIL)。原因を切り分けるため configs/2026-08-30-bwd1-parity-off-selfcheck.toml (baseline = lane_zero と全く同一の `[backend]`) を candidate に指定し、同一 config を自分自身と比較する self-check を実施したところ、M8 (spread vs lane_zero) と全く同じ頻度・全く同じ max_abs_diff (通常時 5.245e-5 / 稀に 1.469e-4) で FAIL/PASS した — これは M8 固有の誤差ではないことの直接証拠になる (同一 kernel を自分自身と比較しても同じ現象が起きるため)。ただし発生機序は 2 通り考えられ、切り分けられていない: 6 回の試行 (self-check 3 回、spread-check 3 回) は {PASS: 5.245e-5} か {FAIL: 1.469e-4} という 離散的な 2 値しか観測せず、連続的な summation-order noise (atomic 加算の実行順序が run ごとに変わることによる浮動小数点非結合性、file 冒頭 C7 注記) が単独の原因なら本来は連続的な分布になるはずで、ちょうど 2 値に固定されるのは不自然。一方、`--parity` の出力は毎回 `sorted_idx` が 2086〜2321 要素 (0.21〜0.24%) で INFO (not gated) の tie-order 非決定性を報告しており、これは既知の radix sort tie-break の非決定性 (p3-per-splat-backward 等で既出) で、atomic 加算順序より大きな摂動 (ある splat の compositing 順序そのものが変わりうる) になり得る。tie flip で特定の splat が `raw_alpha < 0.999` や `alpha >= 1/255` の分岐境界を跨げば、離散的な 2 値(「境界を跨いだ run」と「跨がなかった run」)が生まれても不思議ではない。**本 finding は発生機序を atomic 順序 self-noise と sorted_idx tie-order のどちらか (あるいは両方) に確定できていない** — どちらであっても self-check が同じ現象を再現している以上 M8 の correctness には影響しないという結論は変わらないが、「atomic 順序 self-noise の tail が 1e-4 を超える」という主張は unit-level の bit-exact test (developer が完全に制御できる fixture、こちらは正しさのより強い直接証拠) ほど確立していない点に注意 (§7 に次段の切り分け手順を記載)。

5. 性能 — replay bench 3 pair、3/3 で regression

pairoff (lane_zero) median ms/iteron (spread_lanes) median ms/iterΔ wallclockoff busy ms/iteron busy ms/iterΔ busy
113.56413.731+1.23%10.73610.953+2.02%
213.60013.812+1.56%10.75610.991+2.18%
313.59513.753+1.16%10.76510.963+1.84%
**平均****13.586****13.765****+1.32%****10.752****10.969****+2.02%**
計測 (backward kernel 単体、`ts_backward_raster`)off (lane_zero)on (spread_lanes)Δ
gpu_ms/call (150 call = 3 repeats × 50 iters)4.20164.3670**+3.94%**
NO-GO — spread は whole-iteration でも backward kernel 単体 gpu_ms でも一貫して遅い、default は lane_zero のまま

advisor は実装前に「lane 0 の 9 atomic は既に個別に (依存関係の無い independent instruction として) パイプライン化されている可能性があり、spread の win は nil かもしれない」と明示的に caveat していた。実測はさらに一歩進み、nil どころか 3/3 pair で一貫した小さい regression (whole-iteration wallclock +1.32%、GPU busy +2.02%) になった。これが「backward 以外の何かが遅くなった」ことによる見かけの regression でないことは、ts_backward_raster 単体の gpu_ms を per_kernel/per_stage 計測 regime で直接測ることで確認した (+3.94%、上表) — whole-iteration の delta は backward_raster share (40.07%) からの推論ではなく、kernel 自体の直接計測に帰属できる。推測される原因: `simd_lane_id < 9u` の select chain (8 段の三項演算子) を 32 lane 全員が (23 lane は結果を使わないマスク済み実行として) 評価する必要があり、この命令列コストが「lane 0 だけが 9 回 atomic を発行する」ことの低占有コストを上回った可能性がある (未検証の仮説、§7 参照)。`RasterBackwardAtomicKind` の default は `LaneZero` のまま変更しない。

6. gate config (作成のみ、未実行)

  • `splat/configs/2026-08-30-bwd1-12500-off-r{1,2,3}.toml` / `-on-r{1,2,3}.toml` — `splat/configs/2026-08-30-final-12500-r{1,2,3}.toml` (M7 最終スタック gate) をベースに `[backend] raster_backward_atomic` を明示しただけの差分 (他の全 flag は完全に同一)。6 本とも `splat check --config ` で deserialize + validate PASS 確認済み
  • **実行はしなかった** — 段 2 の短時間 replay bench (n=3 alternating) の時点で 3/3 pair 一貫した regression が出ているため、12.5k full run (各 arm 2 分強 × 6 run = 12〜15 分の GPU 専有) を追加で消費する前に本 finding を残す方を優先した。regression の符号が覆る可能性は低いと考えるが、main が念のため 12.5k で再確認したい場合はこの 6 config でそのまま `splat train` を回せる状態にしてある

7. 未解決点・次段への引き継ぎ

  • **候補 2 (reciprocal division) は fdiv 命令数を確認済み・実測は未実施** — function constant を default 値に固定した AIR (`xcrun -sdk macosx metal -O3 -std=metal3.1 -ffast-math -S`) で `rasterize_backwards_simd` 本体を確認したところ `fdiv fast` が 2 命令残っており (CSE 未実施)、理論的な headroom は確認できた。次の一手は実装 (`one_m_alpha` の reciprocal を 1 回計算して 2 箇所で乗算に置換) + unit parity + 12.5k gate (PSNR guard、p3-ssim-float4 と同じ懸念があるため要注意) — 本 session のスコープ外として持ち越し
  • **候補 3 (splat read の software prefetch) は未 prototype** — GPU busy が既に 79〜91% と高く TLP がレイテンシを隠せている可能性が高いという定性的な予想はあるが、実装して bench するまで gain の符号すら分からない
  • **register spill / occupancy の直接観測ができる環境が無い** — Xcode GPU Frame Capture 相当のツールがこの (headless) 環境には無く、AIR (LLVM IR、register 割り当て前) の静的検査では (e) の audit を完了できなかった。GUI 環境がある session であれば `rasterize_backwards_simd` の実際の register 使用数・occupancy% を直接見て (e) を再監査する価値がある
  • **`splat bench --parity` の intermittent FAIL の発生機序が未確定** (§4 callout) — atomic 順序 self-noise と `sorted_idx` tie-order 非決定性のどちらが (あるいは両方が) 離散的な 2 値 (5.245e-5 / 1.469e-4) を作っているかを切り分けられていない。切り分け方法の一案: tie-order の影響を消すため単一 pixel だけ非ゼロにした dldr で同じ full-scale checkpoint を比較する (unit test の `_byte_equality_single_pixel` と同じ手法をこの規模に適用)。もし tie-order が主因なら、これは M8 と無関係な既存の radix sort 非決定性の問題として別 finding に切り出す価値がある
  • **M8 の実装・flag・test は資産として残す** — default は変更していないので既存挙動への影響はゼロ。将来 (e) の register 分析や別ハードウェア世代で再評価する場合の土台として `RasterBackwardAtomicKind::SpreadLanes` はそのまま使える
03

関連 finding

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