5 月の binary をそのまま使った brush (wgpu→Metal) と本実装の現行最終スタックを、2026-09-23 に同じ M4 Max で直列に 1 本ずつ Lego 30k 学習。wallclock は本実装が brush の 0.912× (process) / 0.941× (trainer 内部値)。PSNR は、brush PLY と本実装 PLY を互いの評価器にかけると、どちらの評価器でもその renderer で学習した側が約 1.8〜1.9 dB 有利 (brush 評価器: brush 37.41 / 本実装 35.58、splat eval q8: brush 34.57 / 本実装 36.47)。「同じ評価器で測った 1 つの PSNR」では優劣を決められない。
5 月の brush 記録 (m4-brush-bench: 9m08s / 37.40 dB) は load 記録がなく、日付も違ったので、2026-09-23 に同じ M4 Max で brush と本実装を直列に 1 本ずつ Lego 30k 学習させた。brush は 5 月と同じ binary と同じ引数。本実装は 2026-08-31-final-lego-30k.toml を run 名・run_dir・日付だけ変えて複製した config。学習時間は本実装が brush の 0.94× (trainer 内部値) / 0.91× (process)。PSNR は どちらの renderer で評価するかで本実装 +1.90 dB 〜 -1.82 dB と符号が反転した。
# (a) brush smoke (500 / 3000 iter): eval 出力・PLY header・splat eval での読込を事前確認
# 500 iter: brush 13.758 / splat eval q8 13.747 / raw 13.740 (差 0.01 dB)
# 3000 iter: brush 27.070 / splat eval q8 26.821 / raw 26.655 (差 -0.25 dB、学習が進むほど開く — §4)
# (b) 本番 (直列、driver 1 本)
nohup sh splat/scripts/brush-sameday-lego-30k.sh 2026-09-23-brush-sameday \
> splat/runs-logs/2026-09-23-brush-sameday-driver.log 2>&1 < /dev/null & disown
# ARM brush: 5 月 (m4-brush-bench.md) と同じ引数。export-path だけ /tmp から変更
RUST_LOG=info ~/dev/brush/target/release/brush /Users/otkrickey/dev/3dgs-workspace/datasets/nerf_synthetic/lego \
--total-train-iters 30000 --eval-every 5000 --export-every 30000 --max-resolution 800 \
--export-path /Users/otkrickey/dev/3dgs-workspace/brush-sameday-20260923/lego-30k/ --export-name 'export_{iter}.ply'
# ARM splat:
target/release/splat train --config configs/2026-09-23-brush-sameday-lego-30k.toml
# (c) splat eval (本実装側の評価器、q8 が公式値) を両 PLY に
DS=/Users/otkrickey/dev/3dgs-workspace/datasets/nerf_synthetic/lego
for P in /Users/otkrickey/dev/3dgs-workspace/brush-sameday-20260923/lego-30k/export_30000.ply \
runs/2026-09-23-brush-sameday-lego-30k/final.ply; do
./target/release/splat eval --ply $P --dataset $DS --split val --convention brush --quantize-8bit
./target/release/splat eval --ply $P --dataset $DS --split val --convention brush
done # → splat/runs-logs/2026-09-23-brush-sameday-eval.log
# (d) brush 評価器 (brush-train/src/eval.rs) を両 PLY に: dataset を symlink で複製し、
# init.ply に評価したい PLY を置いて start-iter=29999 / total=30000 で起動 (log は Took 0ns、最大 1 step の更新の後に eval 1 回)
# brush 自身の PLY で 37.406 dB (学習中の値 37.414 と差 0.008 dB) を確認し、この方法が使えることを確かめた
W=/Users/otkrickey/dev/3dgs-workspace/brush-sameday-20260923
RUST_LOG=info ~/dev/brush/target/release/brush $W/xeval-{brushply,splatply}/lego \
--start-iter 29999 --total-train-iters 30000 --eval-every 30000 --export-every 1000000 \
--max-resolution 800 --export-path $W/xeval-*/out/
| 実装 | 学習時間 (trainer 内部) | process wallclock | 自己評価 PSNR | splat eval q8 (本実装評価器) | splat eval raw | brush 評価器 | final splats | run 中 load1 (10s sampler) |
|---|---|---|---|---|---|---|---|---|
| brush (wgpu→Metal, ce6ef3f) | 417s (`Took FormattedDuration(417s)`、6 回の eval を含む) | 433.23s | 37.414 (brush 内蔵 eval、val 100 views、q8) | 34.570 | 32.163 | 37.406 (§1(d) の再評価) | 284,151 | 0.88–2.20 (mean 1.52、n=43) |
| 本実装 (最終スタック) | 392.39s (`training done in`) | 395.07s | 36.418 (trainer 内蔵 `mean val`、premultiplied) | **36.465** | 36.418 | 35.582 | 485,825 | 1.79–2.88 (mean 2.19、n=39) |
| 本実装 / brush、差分 | **0.941×** | **0.912×** | -0.996 dB (raw 36.418 基準。本実装の q8 36.465 で揃えると -0.949 dB) | **+1.895 dB** | +4.255 dB | **-1.824 dB** | 1.71× | — |
| 評価器 \ PLY | brush PLY | 本実装 PLY | 差 (本実装 - brush) |
|---|---|---|---|
| brush 評価器 (brush-train eval.rs、bg=0、q8) | **37.406** | 35.582 | -1.824 |
| splat eval --convention brush --quantize-8bit | 34.570 | **36.465** | +1.895 |
| splat eval --convention brush (raw) | 32.163 | 36.418 | +4.255 |
| その評価器で学習した側から見た、他方の評価器にかけたときの低下 | -2.836 (brush→splat q8) | -0.883 (splat q8→brush) | — |
r.clamp(0,1) してから丸める (splat-cli/src/cmd/eval.rs:250) のに対し、brush の eval.rs は clamp せずに (x*255).round()/255 する (brush の render 出力の内部で clamp されているかは未確認)。本実装 PLY の brush 評価器 35.58 dB には、clamp しないことで overshoot 分が罰される効果が一部含まれている可能性がある。どちらの評価器でも、その renderer で学習した PLY が約 1.8〜1.9 dB 有利になる。brush PLY を splat eval にかけると raw と q8 の差が 2.4 dB ある (本実装 PLY では 0.05 dB) ことから、本実装 renderer で brush PLY を描くと [0,1] 外に大きく overshoot する画素が多いとわかる。smoke では差が 500 iter 0.01 dB → 3000 iter 0.25 dB → 30k 2.84 dB と学習が進むほど開いた。原因は renderer の差 (小さい / 高 SH 次数の splat の扱いなど) と clamp の有無の組み合わせと考えられる。cov2d blur は両者 0.3 で一致していることを確認済み。原因の特定は本 task の範囲外で、コードは変更していない。依頼時の方針は『本実装の splat eval q8 を brush PLY にも適用すれば同一評価器比較になる』だった。しかし実測すると、評価器を brush 側に替えれば優劣が逆転する (本実装 +1.90 dB ↔ -1.82 dB)。p1-a-3 の convention coupling (学習と評価の convention が結びつく) と同じ形で、今回は renderer の実装差 (と eval 時の clamp の扱い) も学習と評価で結びつくことがわかった。過去 finding の『brush 自身 val 32.038 (q8: 34.484)』(p1-d-stage2 ほか) も、brush PLY を splat eval で測った値だと推定される (今回 32.163 / 34.570 とほぼ一致)。『本実装が brush を val で +x dB 上回った』という既存の主張は、本実装の評価器に有利な測り方であることに注意が必要。ゼミ資料では 2×2 表 (§4) をそのまま出すか、各実装の自己評価 (brush 37.41 / 本実装 36.42、-1.0 dB) を併記するのが誠実。
| 記録 | brush build | wallclock | PSNR | splats | load 記録 |
|---|---|---|---|---|---|
| 2026-04-24 (brush-eval.md 試行 3) | v0.3.0 (3dgs-workspace/brush、別 checkout) | 384s (6m24s) | 37.38 | — | なし |
| 2026-05-23 (m4-brush-bench) | ce6ef3f、本日と同一 binary | 548s (9m08s) | 37.397 | 284,323 (toml 側は 282,103 と記載、md と不一致) | なし |
| **2026-09-23 (本 finding)** | ce6ef3f、同一 binary (sha256 c460b12a…) | **433.23s** (process) / 417s (Took) | **37.414** | **284,151** | あり (0.88–2.20) |