From 475d9125a896b162537156239f212d4e3563098a Mon Sep 17 00:00:00 2001 From: Hmission <908972967@qq.com> Date: Wed, 2 Sep 2026 23:44:15 +0800 Subject: [PATCH] fix: 512px VAE decode OOM - effective half-size tiling + unconditional retry - backend_fit: prepare_vae_decode_retry_tiling now defaults rel_size to 0.5 when enabling spatial tiling. get_tile_sizes() defaulted to rel_size=1.0 (factor branch wins) which produced a full-latent tile - tiling was a no-op and 512px decode could exceed device buffer limits (Adreno 740 ~1.94GB -> ~416MB with half tiles). - stable-diffusion: retry VAE decode with tiling on decode failure (empty result, typically OOM) regardless of --auto-fit, so small-buffer mobile GPUs recover automatically. Fires only on failure; happy path unchanged. Evidence (Pocket Chick, K Pad Mali-G925 / Adreno 740): - 512px decode: 1.94GB -> 416MB buffer, renders green channel correctly (prior NaN/white-image on SD3.5 OpenCL); 10-step ~45.8s (0.4% accuracy loss vs non-tiled on K90), 13 tiles; Z-Image K90 39.7s. --- src/core/backend_fit.cpp | 7 +++++++ src/stable-diffusion.cpp | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/core/backend_fit.cpp b/src/core/backend_fit.cpp index 0ba2df14f..18ea5c572 100644 --- a/src/core/backend_fit.cpp +++ b/src/core/backend_fit.cpp @@ -370,6 +370,13 @@ namespace sd::backend_fit { retry_mode = tiling_params.enabled ? "spatial+temporal" : "temporal"; } else if (!tiling_params.enabled) { tiling_params.enabled = true; + // 512px VAE decode memory fix: get_tile_sizes() defaults to + // rel_size=1.0 (factor branch wins), which yields a full-size tile + // (tiling effectively disabled) and can exceed device buffer limits + // (Adreno 740: 512px decode graph ~1.94GB). Half-relative tiles make + // tiling effective (~416MB), no quality impact (VAE tiling overlaps). + tiling_params.rel_size_x = 0.5f; + tiling_params.rel_size_y = 0.5f; if (tiling_params.tile_size_x <= 0) { tiling_params.tile_size_x = 256; } diff --git a/src/stable-diffusion.cpp b/src/stable-diffusion.cpp index d2193e14c..57887650a 100644 --- a/src/stable-diffusion.cpp +++ b/src/stable-diffusion.cpp @@ -3085,8 +3085,11 @@ class StableDiffusionGGML { auto latents = first_stage_model->diffusion_to_vae_latents(x); auto decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y); const bool prefer_temporal_tiling = decode_video && first_stage_model->can_temporal_tile_decode(); + // 512px VAE decode memory fix: retry with tiling unconditionally on + // decode failure (OOM) instead of gating on --auto-fit (default off), + // so mobile GPUs with small buffers recover automatically. Only fires + // when decode actually failed, no side effects on the happy path. while (decoded.empty() && - auto_fit_enabled && sd::backend_fit::prepare_vae_decode_retry_tiling(vae_tiling_params, prefer_temporal_tiling)) { first_stage_model->free_compute_buffer(); decoded = first_stage_model->decode(n_threads, latents, vae_tiling_params, decode_video, circular_x, circular_y);