From c3b55bedac023c3a981578fdfdf5f0bb8f5a2b94 Mon Sep 17 00:00:00 2001 From: imdangernoodle Date: Wed, 20 May 2026 01:28:13 -0400 Subject: [PATCH] fix: per-iteration @autoreleasepool in training loop to stop memory growth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The training loop in cli/msplat created autoreleased Metal objects (command buffers, encoders, transient textures) every step but never drained them — they accumulated for the entire run and OOM-killed the process around step ~12k on a ~800k-1.5M gaussian cloud (no error, no saved scene, just SIGKILL from memory pressure). Renamed cli/msplat.cpp → cli/msplat.mm (project already enables OBJCXX) and wrapped the per-iteration loop body in @autoreleasepool so each step's autoreleased objects drain at the end of that step instead of living until process exit. Repro before fix: `msplat -n 30000 -d 2 <407-frame-nerfstudio>` dies ~step 12100. Death step scaled with downscale (3100 at full res, 9100 at -d 2 earlier, 12100 with a smaller seed) — classic signature of a fixed per-step leak rather than cloud growth (gaussian count was stable at ~800k while memory still climbed). Co-Authored-By: Claude Opus 4.7 --- CMakeLists.txt | 2 +- cli/{msplat.cpp => msplat.mm} | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) rename cli/{msplat.cpp => msplat.mm} (96%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 744a1c9..752774a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,7 +106,7 @@ add_dependencies(msplat_core metallib) # --- CLI binary --- -add_executable(msplat cli/msplat.cpp) +add_executable(msplat cli/msplat.mm) target_link_libraries(msplat PRIVATE msplat_core CLI11::CLI11 diff --git a/cli/msplat.cpp b/cli/msplat.mm similarity index 96% rename from cli/msplat.cpp rename to cli/msplat.mm index 730b358..835da53 100644 --- a/cli/msplat.cpp +++ b/cli/msplat.mm @@ -143,6 +143,13 @@ int main(int argc, char *argv[]) { auto bench_start = cpu_now(); for (; step <= (size_t)numIters; step++) { + // Per-iteration autorelease pool. Each step creates autoreleased + // Metal objects (command buffers, encoders, transient textures); + // without draining them every iteration they accumulate for the + // entire run and OOM-kill the process around step ~12k. Wrapping + // the body drains them at the end of each step. This file is + // compiled as Obj-C++ (.mm) so @autoreleasepool is available. + @autoreleasepool { Camera &cam = cams[camsIter.next()]; auto iter_start = cpu_now(); @@ -180,6 +187,7 @@ int main(int argc, char *argv[]) { memcpy(valImg.ptr(), rgb_cpu.data_ptr(), valImg.data.size() * sizeof(float)); imwriteRGB((fs::path(valRender) / (std::to_string(step) + ".png")).string(), valImg); } + } // @autoreleasepool } if (benchmarking && !bench_iter_ms.empty()) {