松田研究室
FindingID: a-9-f16-forward-negative
Status: stable / task A.9
← Findings Index
Negative finding · Mixed precision

A.9 f16 forward — half3 accumulator が underflow + cast overhead で二重 negative

rasterize forward の per-pixel RGB accumulator を half3 化 (中間計算は f32 維持) した試行。env SPLAT_F16_FORWARD=1 で kernel 切替。30k bench で PSNR 14.873 dB (-10.0 dB) / wallclock 40m40s (+75%)、二重 negative finding。

negativerank: highrejected第 3 軸phase-5f16mixed-precisionmetalunderflowapple-silicon
Δ PSNR
-10.006 dB (14.873 vs 24.879)
Δ Wallclock
+75.1% (2439.73s vs 1393s)
Δ Splats
+13.3% (94,900 vs 83,734)
Impact
half3 per-pixel accumulator が low-T 領域 (alpha * T < 6e-5) で underflow → 寄与 splat の累積消失で PSNR -10.0 dB。さらに half↔float cast が compute bound でも重く wallclock +75%。Apple Silicon SIMD は half と float が同 throughput、bandwidth bound でないので f16 化は loss-only。
01

本文

render_splats_f16 kernel (per-pixel half3 pix_out accumulator + 中間計算 f32 維持) を shaders/forward/rasterize.metal に追加し、env SPLAT_F16_FORWARD=1 で切替可能にした。30k bench (lego sh=3, seed=42, capacity=1M) で baseline 24.879 dB / 23m13s に対し PSNR 14.873 dB (-10.0 dB) + wallclock 40m40s (+75%)、精度と速度の二重悪化が確定。Negative finding。

実装日
2026-05-23
前提 baseline
lego-sh3-30k 24.879 dB / 23m13s / 83,734 splats, seed=42
本 run
lego-a9-f16-30k 14.873 dB / 40m40s / 94,900 splats, seed=42
config
configs/2026-05-23-0700-lego-a9-f16-30k.toml
活性化
env SPLAT_F16_FORWARD=1 (rasterize.rs::forward の pso 切替)

実装方針

  1. shaders/forward/rasterize.metalrender_splats_f16 を新規追加、render_splats_f32 をコピーして float3 pix_outhalf3 pix_out に変更
  2. 中間計算 (sigma / alpha / exp / T / next_T) は f32 維持 (under flow 防止)
  3. term の add 累積のみ half 化: pix_out += half3(rgb * (alpha * T))
  4. 出力 float4 は f32 維持 (loss 計算 / backward path は変更なし)、出力時に float3(pix_out) で cast
  5. rasterize.rspso_forward_f16 field 追加、forward() 内で env 確認して pso 切替 (Trainer API は不変)
  6. cargo test 23/23 pass (既存 f32 path は変更ナシ)
half3 pix_out = half3(0.0h);
float T       = 1.0f;
// 中間計算は f32 維持
float alpha = min(0.999f, splat.color_a * exp(-sigma));
float next_T = T * (1.0f - alpha);
float3 rgb = float3(splat.color_r, splat.color_g, splat.color_b);
// term は f32 で計算、half に cast して accumulate
pix_out += half3(rgb * (alpha * T));
T = next_T;

結果

metricbaseline (f32 forward)A.9 (f16 forward)Δ
PSNR (val 100 views)24.879 dB14.873 dB-10.006 dB
wallclock (30k iter)23m13s (1393s)40m40s (2439.73s)+1046.73s (+75.1%)
ms/iter (avg)46.4 ms81.3 ms+34.9 ms (+75.2%)
ms/iter (early, iter 1-2k)~46 ms~50 ms+4 ms (+8.7%)
ms/iter (late, iter 24k+)~46 ms~99 ms+53 ms (+115%)
final splats83,73494,900+11,166 (+13.3%)
wallclock 悪化は iter 進行とともに加速 — splats 増加で per-pixel 寄与 splat 数 (≈ tile load 回数) が増え、half↔float cast の compute コストが顕在化

原因分析

(1) half3 accumulator の low-T underflow

front-to-back α 合成では、後段 splat ほど T (transmittance) が小さくなる。具体的に T < 1e-3 領域では alpha * T * rgb~1e-5 以下になりやすく、これは half (IEEE 754 binary16) の normal range 下限 6.1e-5 を下回り denormal もしくは zero に丸められる。背景に近い半透明 splat の寄与が消失し、画像の low-frequency 成分が削れて PSNR が大幅低下。

(2) half↔float cast の compute overhead

Apple Silicon GPU の SIMD ALU は half と float の演算 throughput が同じ (4-wide vector で per-cycle 同数 op)。memory bandwidth bound でない workload (rasterize は threadgroup memory に 256 splat load して再利用、bandwidth ではなく compute bound) では f16 化のメリットゼロ。さらに add 1 回ごとに half3(rgb * alpha * T) の cast 命令 (3 命令) が追加され、純粋 overhead として残る。

(3) refine が破綻

underflow で寄与が消えた splat に対し gradient が薄まる → refine が「効果のない splat を split」する誤判定を起こし、不要な splat が増加 (final 94,900 vs baseline 83,734、+13.3%)。これがさらに per-pixel 寄与 splat 数を増やし、ms/iter を悪化させるフィードバック loop。

卒論への含意

第 3 軸 (Apple Silicon 固有最適化) の Negative finding。「Apple Silicon は half と float が同 throughput、bandwidth bound でない rasterize の f16 化は purely loss」を実測で示した。これは brush (Vulkan / Metal) や gsplat (CUDA) も同じ理由で f16 forward を採用していないことの傍証になる。Negative finding 章 (D.3) の 4 つ目のストーリーとして組み込み可能。

Lesson

GPU の f16 化は memory bandwidth bound な workload にのみ適用すべき。Rasterize forward は threadgroup memory 内再利用で compute bound、しかも IEEE 754 binary16 の normal range は alpha 合成の low-T 領域と相性が悪い。Apple Silicon に限った話ではなく一般則として記録。

実装の現状

  • render_splats_f16 kernel は shaders/forward/rasterize.metal に残置 (Negative finding として参照可能、削除しない)
  • rasterize.rs::forward の env 切替 (SPLAT_F16_FORWARD) も残置、default OFF で baseline 同一動作
  • cargo test 23/23 pass (env OFF 時)
  • main branch は変更なし (defaults f32)

関連

  • A.10 Kahan Negative finding (compiler が compensator 削除): a-10-kahan-negative — Metal compiler の挙動という共通主題
  • A.6 #feat.G f16 packed Splat (storage 全体 f16): a-6-feat-g-packed-investigation — A.9 の二重 negative を考えると、A.6 の packed も trainer 統合しても同様の risk あり、ROI ~1% の追求は妥当
  • A.7 batched cmd buffer (Mildly positive): a-7-icb-batching-results — 第 3 軸の対比として positive 1 例 / negative 2 例 (A.9 + A.10) の構造に
  • A.5 final ablation 表 第 3 軸 row "Mixed precision (A.9)": final-ablation-table
02

関連する run

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

03

関連 finding

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