松田研究室
FindingID: p3-ssim-float4
Status: stable / task 2026-09-seminar05-milestones M5a (A6 SSIM float4 化、3ch 単一 walk)
← Findings Index
2026-09 ゼミ milestones · M5a · A6 SSIM float4 化

M5a A6 SSIM float4 化 — stage1 (float4) は byte-exact 確認済み・PSNR riskゼロ、stage2 (float4_rcp) は 12.5k gate で PSNR guard 抵触・不採用

実 config (K=7) の SSIM forward/backward を対象に、fwd の 3ch 個別 walk (294 load/px) を 1 walk に統合する <code>ssim_forward_float4</code> (stage 1) と、bwd の window あたり 3 divide (441 divide/px) を削減する <code>ssim_backward_rcp</code> (stage 2、441→147 divide/px) を実装した。<strong>stage 1 は K=7/K=11 の単体 test に加え本番規模 (407,267 splats) の <code>splat bench --parity</code> でも byte 一致を確認</strong> — PSNR risk が原理的にゼロの再挑戦候補として残る。stage 2 は REL_TOL を実測ベースで確定 (K=7 max_rel=8.4519e-5 / K=11 6.3679e-5 → REL_TOL=2e-4)、parity は本番規模でも PASS したが、<strong>「採用候補の最終形 = float4_rcp」を対象にした 12.5k Lego 同一 session 交互 A/B n=3 gate で PSNR guard に抵触し不採用</strong>とした (on mean 36.1327 dB vs off mean 36.2133 dB、Δ -0.081 dB、3 pair 全てで on<off の系統的分離)。wallclock 側も Δ -0.055% で有意閾値 (2.53%) に届かない null という結果で、二重に採用基準を満たさなかった。

negativerank: lowrejected-psnr-guard-float4rcp-null-wallclock-float4-standalone-untested第 1 軸第 3 軸m5aseminar05-milestonesssimfloat4reciprocal-sharingfast-mathbit-exactgpu-gaterejectedpsnr-guard-faillego
Δ PSNR
12.5k 同一 session 交互 A/B n=3 (1 pair 再実行後): on **36.1327 dB** (36.156 / 36.170 / 36.072) vs off **36.2133 dB** (36.256 / 36.204 / 36.180) = **Δ -0.081 dB**。task 指定 guard (on mean ≥ off mean − 0.05 dB) に **fail** (-0.081 < -0.05)。3 pair とも on<off で完全分離、方向一致。on-r3 (36.072 dB) は config header の絶対 guard 帯 [36.102, 36.302] の下限を割り込む唯一の値
Δ Wallclock
12.5k full run 同一 session 交互 A/B n=3 (r1 は汚染疑いで 1 回再実行): **off mean 126.954s (σ 1.264%) → on mean 126.884s (σ 4.314%) = Δ -0.055%**。有意閾値 max(2σ_off,1%) = 2.528%、実測 Δ はこれに遠く届かない **null 結果**。3 pair 個別 Δ は +3.62% (r1, redo) / -2.24% (r2) / -1.61% (r3) と符号が一致しない。σ 非対称比 3.41× (on>off) は p3-tier1-bit-exact の汚染署名域に近いが、r1 は既に 1 回再実行済みのため追加再実行はしていない (§4 参照、PSNR guard が既に不採用を決定づけているため追加の GPU 消費は不要と判断)
Impact
SSIM の walk 方式を選べる BackendConfig `[backend] ssim_walk` (`per_channel` default / `float4` / `float4_rcp`) を実装し、shader (`ssim_forward_float4`, `ssim_backward_rcp`) と Rust 配線 (`SsimEvaluator`, `Trainer`, `apply_backend_config`, result.toml item) を完結させた。stage 1 (float4 化 forward) は K=7/K=11 の単体 test + 本番規模 (407,267 splats) `splat bench --parity` の両方で byte-exact — PSNR risk がゼロの再挑戦候補として残す。stage 2 (逆数共有 backward) の REL_TOL は実測ベースで 2e-4 に確定 (K=7 max_rel=8.4519e-5 / K=11 6.3679e-5、旧 2-divide 実装の 8.28e-5 とほぼ同水準 — fast math 近似逆数の精度floorが支配的で除算回数削減の理論的優位はほぼ観測できなかった)。3-way replay bench (`submission=per_kernel` で ssim label を分離) では float4_rcp の ssim label gpu_ms が per_channel 比 -20.8% (758.92→601.06ms/600calls) と明確に縮小したが、12.5k Lego 同一 session 交互 A/B n=3 gate (「採用候補の最終形 = float4_rcp」を一括計測) では PSNR guard に抵触し不採用: on mean 36.1327 dB (36.156/36.170/36.072) vs off mean 36.2133 dB (36.256/36.204/36.180)、Δ -0.081 dB は guard 閾値 -0.05 dB を超過、3 pair 全てで on
01

本文

milestone 本文 (M5a) の要求は「3ch 単一 walk で fwd 294→98 load/px、bwd の除算 441 回/px が本丸、第 1 段 (load 統合) は加算順不変で bit-exact、除算削減で丸めが変わる場合は第 2 段として別 flag 値に分ける」。この構造どおり `SsimWalkKind { PerChannel(default), Float4, Float4Rcp }` を実装した。Float4 は forward のみ新 kernel (`ssim_forward_float4`) を使い backward は既存 `ssim_backward` を共有する (backward には load 統合の余地が実質ない — x/y は channel ごとに window loop の外で 1 回だけ load 済み、window loop 内の load は `stats_in` のみで AoS stride 5 float のため vectorize してもメリットがない)。本 finding は前回 draft (implementation-only、GPU 未検証) からの続きとして、検証 → gate → 結論確定までを完結させる。

1. stage1 (float4 forward walk) — 検証: K=7/K=11 単体 test + 本番規模 parity で byte-exact

test / 経路何を pin するか結果
`ssim_walk_float4_is_byte_exact` (K=7, sigma=1.0 / K=11, sigma=1.5)24×24 非定数 synthetic RGB で mean ssim + grad buffer 全要素が bit 一致**PASS (両 K)**
`splat bench --parity` (本番規模、407,267 splats、Lego 12.5k checkpoint、baseline=per_channel vs candidate=float4)tile_offsets byte 一致 + grad 5 種 tol PASS + loss diff 極小**PASS OVERALL** (loss diff=1.770e-8, means_grad max_abs_diff=1.080e-5, 他 4 grad も tol 内)
stage1 は PSNR risk がゼロの再挑戦候補

加算順を変えない load 統合という設計方針が、24×24 synthetic だけでなく本番規模 (407,267 splats、実データ) でも bit-exact であることを確認した。Float4 単独 (backward は従来のまま) は本 gate では計測していない — stage2 (float4_rcp) を「採用候補の最終形」として一括計測する milestone 方針のためであり、Float4 単独の 12.5k wallclock 効果は未知数だが、byte-exact ゆえに PSNR guard で落ちるリスクは構造的にゼロ。次回 M5a 系を revisit するなら、まず Float4 単独を gate してから stage2 を検討する順序の方が PSNR risk を切り分けやすい。

2. stage2 (逆数共有 backward) — REL_TOL 実測確定: K=7 8.4519e-5 / K=11 6.3679e-5 → REL_TOL=2e-4

当初仕様
相対誤差 <=1e-6 (draft finding 時点)
第一版 (2-divide 独立計算) 実測
8.28e-5 (draft finding 記載、本 session では再確認していない)
第二版 (1-divide 恒等式、本実装) 実測
**K=7: max_rel=8.4519e-5** (at a=-2.4209792e-6, b=-2.4211838e-6) / **K=11: max_rel=6.3679e-5** — 旧 2-divide 実装とほぼ同水準で、除算回数削減 (294→147 divide/px) による理論上の改善はほぼ観測できなかった。fast math 近似逆数命令の相対精度 floor (~2^-14≈6e-5) が支配的という仮説と整合し、K を大きくしても誤差は増えない (K=11 < K=7) ことも確認 — 「除算回数の蓄積」より「近似逆数命令の精度floor」が支配的
確定 REL_TOL
**2e-4** (実測最大値 8.4519e-5 に ~2.4x のマージン)。`ssim_walk_float4_rcp_matches_within_tol` test は K=7/K=11 の両方をループで検証するよう拡張した (advisor レビュー指摘: window サイズで誤差が増大しないかを確認する目的)
本番規模 parity
`splat bench --parity` (407,267 splats、baseline=per_channel vs candidate=float4_rcp) で **PASS** — loss diff=4.191e-9, means_grad max_abs_diff=2.815e-5 (tol 1e-4 内), 他 4 grad も tol 内。tol PASS であり byte 一致ではない (仕様通り)

3. 3-way replay bench screen (submission=per_kernel、ssim label 分離、n=3、方向性の先行確認)

chained (production default) は ssim label を分離できない — submission=per_kernel を使う補助計測

production default (`submission=chained`, W2) では ssim/l1combine/backward/adam が 1 CB に畳まれ `ts_ssim_fwd_grad` は encode コストしか計れない (train_loop.rs コメント)。ssim kernel 単体の gpu_ms を切り出すため、この節だけ `submission=per_kernel` (W2 gate で敗退・default 交代済みの legacy mode、production では使わない) で計測している — p3-radix-ballot §4 の `forward_submission=per_stage` と同型の位置づけ。`ts_ssim_fwd_grad` は forward+backward を合わせて計測する (`forward_and_grad_buf` 呼び出し全体)。

arm (`splat bench`, warmup=20, iters=200, repeats=3, submission=per_kernel)ms/iter 全体 (median)sigmats_ssim_fwd_grad gpu_ms 合計 (600 calls)gpu_ms/call
per_channel (baseline)14.4158.55%758.9171.265
float4 (stage1 のみ)17.558**36.01% (使用不可、ambient contention 疑い)**780.8061.301
float4_rcp (stage1+stage2、採用候補の最終形)13.8210.55%601.0641.002
float4 arm は σ 36% で方向性判定に使えない、float4_rcp arm は ssim label で -20.8%

float4 arm の 3 repeat (17.558 / 14.397 / 28.174 ms/iter) は ambient load (session 開始時 load1 15.24) との contention が疑われ σ 36% と極端に大きく、per_channel との比較 (全体 ms/iter も ssim label gpu_ms も) は方向性判定にすら使えない。float4_rcp arm は σ 0.55% とクリーンで、ts_ssim_fwd_grad label の gpu_ms が per_channel 比 -20.8% (758.917→601.064ms/600calls) と明確に縮小した — stage2 (逆数共有 backward) の効果が単体では確かに存在することを示す。ただしこの label は forward+backward 合算であり、per_channel と float4 (forward のみ変更) の比較が取れていないため、効果の内訳 (forward 由来 vs backward 由来) は本節だけでは分離できない。

4. 12.5k full run GPU gate — 実測 (PSNR guard 抵触で不採用)

電源 / 機体 / GPU 権
AC 接続 (`pmset -g batt` で series 開始前・各 run 前後・series 終了後まで一貫して `AC Power`)。GPU 権は main から付与済み、他 process なし (`pgrep -fl target/release/splat` 事前確認で空)。M4 Max
ambient load
series 開始時 load1 15.24 → series 終盤には 1.77〜2.98 まで単調に低下 (直近 reboot 後 Spotlight 再インデックスの収束と推定、p3-radix-ballot と同型の環境)。`LOAD_LIMIT=12 QUIESCE_MAX=12` (120s cap) で起動
driver script
`splat/scripts/gate-ab-n3.sh configs/2026-08-30-m5a-12500-off configs/2026-08-30-m5a-12500-on m5a-12500`。off-r1 の初回起動が config path prefix 誤り (`configs/` 抜け) で `error: config IO error` (rc=1) となり、prefix を修正して即座に再起動 (**学習が 1 iteration も走っていない起動失敗**であり gate run のやり直しではない)
binary provenance
全 8 run (元 6 run + r1 再実行 2 run) は単一 binary (sha256 `ee060d188e2d321da26dde0680f6bee1b20bae68d4e87eec9f8ca57dd558b1fe`、series 前後・redo 前後で一致確認)、git rev `324d0523` で不変
flag 実測確認
各 log の `[backend] BackendConfig { … }` 行から抽出 — 6 run 全てで `radix_scatter: SimdBallot` (6/6、M4 flip 後の現行 default)、Tier1 4 flag (`ssim_clear: HostMemset` / `raster_grad_clear: HostMemset` / `backward_uniform: HostBuffer` / `lazy_c3_scratch: false`) が全て default 値 (6/6、milestone 指示通り Tier1 は明示 OFF)、`ssim_walk` は off 側 3/3 が `PerChannel`、on 側 3/3 が `Float4Rcp` — 意図した arm が正しく走っていることの直接証拠

5. r1 pair の汚染疑いと 1 回再実行 — 発見・対応・限界

発見
元の on-r1 (150.566s) は off-r1 (126.217s) の +19.3% という、他 2 pair (r2: -2.24%, r3: -1.61%) と符号が逆転した極端な外れ値だった。ms/iter の推移を確認すると、on-r1 だけが iter 4000 以降 <strong>単調に増加し続ける</strong> (12.5→13.8→14.5→13.3→14.3→15.0→15.2 ms/iter) — 他 5 run (元の off-r1 含む) は iter 8000 前後で 11.5〜12.4 ms/iter に定常化しており、この単調増加パターンは on-r1 だけに固有だった。独立 load sampler (10s 間隔) は on-r1 の実行窓で load1 11.22→7.24 と滑らかに低下しており、明白な load spike は捉えていない (10s 粒度のため短時間バーストは見落とし得る、既知の限界)
対応 — 1 回再実行
milestone 規律「汚染疑いなら該当 pair を 1 回再実行」に従い、off-r1/on-r1 のペアを ambient load が十分低い時点 (load1 2.12〜2.85) で back-to-back に再実行した。**この再実行は `gate-ab-n3.sh` を使わず** (単発ペアのため)、同じ config・同じ binary・同じ手順 (meta 記録、AC 確認) を手動で再現する簡易 driver を使った — **独立 load sampler と quiesce ゲートは付いていない** (限界として明記)。汚染された元の log/meta は `runs-logs/m5a-12500-{off,on}-r1.contaminated.{log,meta}` に保存 (破棄ではなく記録として残す)
再実行の結果
新 off-r1 = 128.529s (12.1〜12.9 ms/iter で定常、単調増加パターンは消失)、新 on-r1 = 133.186s (12.1〜12.9 ms/iter で定常)。元の on-r1 に見られた「壊れた」シグネチャ (単調発散) は再実行では再現しなかった — 環境要因 (一時的な contention) だった可能性が高い。ただし新ペアの Δ は **+3.62%** (on が off より遅い) で、r2/r3 (-2.24%/-1.61%) と符号が逆
限界1: 全体 n=3 の σ 非対称が残る
全 3 pair (redo 後) で off σ=1.264% / on σ=4.314%、非対称比 **3.41×** — p3-tier1-bit-exact が汚染署名とした基準に近い。しかし r1 は既に milestone の「1 回」再実行枠を使い切っており、追加再実行は行っていない。**追加再実行を行わなかった理由**: (a) PSNR guard が既に 3/3 完全分離で不採用を決定づけており、wallclock の再測定では結論を覆せない、(b) 「1 回」という上限は結果が気に入らない場合の再試行 (p-hacking) を防ぐための規律であり、これを破る正当化は本件にはない
限界2: redo pair だけ環境が違う (交絡)
series 全体で ambient load が単調に低下する中 (12.2→1.8)、生存した r2/r3 pair は load1 ~5〜9 の窓で走った一方、redo された r1 pair は series 中で最も静かな窓 (load1 2.2〜2.9) で走った — pool された mean はこの窓の違いを均していない。また各 pair 内で on は常に off の直後 (2 番目) に走るため、pair 内の熱的ドリフトがあるとすれば系統的に on に不利に働く方向 (ただし本 gate ではこの効果を裏付ける独立証拠はない)
参考 (使わない数値): r1 を除いた n=2
off {127.012, 125.321} / on {124.167, 123.300} だけで計算すると Δ=-1.93%、閾値 max(2σ_off,1%)=1.90% となり**名目上は僅かに閾値を超える**。しかしこれは (a) 既に 1 回再実行した pair をさらに恣意的に除外する行為であり、(b) PSNR guard は r2/r3 単独でも on<off (-0.034 dB / -0.108 dB) で guard 相当の傾向を示しており、この部分集合を採用の根拠にすることは正当化できない。**参考値として記録するが、判定には使わない**

6. 12.5k full run gate — 実測表 (使用データ: redo 後の r1 + r2 + r3)

実行順armconfigtrain elapsed (s)PSNR (dB)final splats備考
1 (redo)off (A)m5a-12500-off-r1128.52936.256407,399元 run は汚染疑いで破棄 (§5)、load1 2.16→2.85 の窓で再実行
2 (redo)on (B)m5a-12500-on-r1133.18636.156404,901元 run は汚染疑いで破棄 (§5)、load1 2.85→2.48 の窓で再実行
3off (A)m5a-12500-off-r2127.01236.204408,172load1 窓 5.11→3.78
4on (B)m5a-12500-on-r2124.16736.170407,657load1 窓 3.23→2.49
5off (A)m5a-12500-off-r3125.32136.180405,121load1 窓 2.51→2.22
6on (B)m5a-12500-on-r3123.30036.072408,826load1 窓 2.16→1.77、on-r3 は絶対 guard 帯下限 36.102 を割り込む
**mean****off**—**126.954** (σ 1.264%)**36.2133**406,897—
**mean****on**—**126.884** (σ 4.314%)**36.1327**407,128—
**Δ**——**-0.055%** (null)**-0.0807 dB**+0.06%—
判定基準: wallclock も PSNR guard も不採用を示す — PSNR が決定的

**wallclock**: Δ -0.055% は有意閾値 max(2σ_off,1%)=2.528% に遠く届かない **null** (効果なしとも効果ありとも言えない、σ_on が大きいことによる検出力不足)。**PSNR guard (決定的)**: on mean 36.1327 dB は off mean 36.2133 dB − 0.05 dB = 36.1633 dB を **下回る** (Δ -0.0807 dB)。3 pair 全てで on (p3-tier1-bit-exact が「怪しい」とした系統的分離のパターンと同型、p3-radix-ballot が「systematic な分離は見られない」ことを健全性の証拠にしたのとは対照的)。final splats は off 406,897 / on 407,128 で ±0.3% 未満の差 (mixed 方向: off{407399,408172,405121} vs on{404901,407657,408826}) であり、splat 数の違いによる PSNR 差ではない。stage2 が非 bit-exact な変更であることを踏まえると、この系統的な質低下は「丸めが変わる変更が学習の収束経路をわずかに悪化させた」という想定内の失敗モードであり、異常というより stage2 採用に伴う典型的なリスクが顕在化したものと解釈する。

7. なぜ wallclock 効果が小さかったのか — budget share からの予測との整合

ssim の budget share (11.80%) × isolated-label 効果 (-20.8%) ≈ 予測 wallclock -2.5% — この gate の解像度限界とほぼ同水準

`p3-kernel-share-post-s3` (M1, post-S3 share) は ssim の share を **11.80%** と報告している。§3 の isolated-label screen (submission=per_kernel、float4_rcp の ts_ssim_fwd_grad が per_channel 比 -20.8%) をこの share に単純に適用すると、予測される wallclock 寄与は 11.80% × 20.8% ≈ **-2.45%** — 本 gate の有意閾値 (2.528%) とほぼ同水準である。milestone 本文が挙げた期待 delta (-4〜6%) は、この budget share から見ると約 2 倍楽観的だったことになる。加えて chained (production) 経路では ssim/l1combine/backward が 1 CB に統合され encoder 間 hazard 直列化のオーバーヘッドが乗るため、isolated-label (per_kernel mode) の効果がそのまま production の wallclock に転写されるとは限らない。今後 M5a 系を revisit するなら n=5 (M2 Tier1 の再 gate と同様の理由 — 効果量が gate の検出力ぎりぎりの領域にあるため) を推奨する。

8. 結論 — 不採用、default flip なし

結論
**不採用**。`splat_train_v1::config::SsimWalkKind::default()` (`#[default]` 属性) と `BackendConfig::default()` の両方を `PerChannel` のまま**変更していない**。config.rs / ssim.rs の `Float4Rcp` doc comment の verdict を「未検証 (GPU gate 前)」→「不採用 (12.5k gate 実施済み、PSNR guard 抵触)」に更新、`Float4` の verdict も「byte-exact 確認済みだが単独では 12.5k gate 未実施、PSNR risk ゼロの再挑戦候補」に更新 (3 点セット規約)
test / build 確認
`ssim_walk_float4_rcp_matches_within_tol` の `REL_TOL` を実測 (K=7 8.4519e-5, K=11 6.3679e-5) に基づき暫定 1e-4 → 確定 2e-4 に更新、test を K=7/K=11 両方をループで検証するよう拡張。`cargo build --release` (workspace) / `cargo test --workspace --release` 全 pass (splat-metal 88 passed + 2 ignored、splat-train-v1 70、他 crate 0 failure)
config 上の Tier1 明示 OFF
milestone 指示に従い、gate 用 6 config から M2 Tier1 の 4 flag (`ssim_clear` / `raster_grad_clear` / `backward_uniform` / `lazy_c3_scratch`) を削除し default 値 (全て OFF/HostMemset/HostBuffer/false) に落とした — Tier1 が棄却保留中のため。他の explicit `[backend]` field は全て `BackendConfig::default()` と一致することを確認済み (radix_scatter は省略により現行 default `SimdBallot` を継承)。`splat check` で 6 config 全て構文検証 PASS
次の一手
`Float4` (stage1 単独) は PSNR risk ゼロなので、次回 GPU 権があるときに単独で 12.5k gate してみる価値がある (本 finding では未実施)。stage2 (float4_rcp) は budget share の物理的上限 (~-2.5%) を踏まえると、SSIM 単体の最適化よりも share の大きい backward_raster (33.86%) や adam (12.68%) 側の施策の方が期待値が高い (`p3-kernel-share-post-s3` 参照)。M5a を再挑戦するなら n=5 + Float4 単独 gate を先に、が推奨順序
02

関連する run

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

03

関連 finding

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