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);