Skip to content

Commit 2c9b1e2

Browse files
stduhpfwbrunaleejet
authored
feat: add VAE encoding tiling support and adaptive overlap (#484)
* implement tiling vae encode support * Tiling (vae/upscale): adaptative overlap * Tiling: fix edge case * Tiling: fix crash when less than 2 tiles per dim * remove extra dot * Tiling: fix edge cases for adaptative overlap * tiling: fix edge case * set vae tile size via env var * vae tiling: refactor again, base on smaller buffer for alignment * Use bigger tiles for encode (to match compute buffer size) * Fix edge case when tile is bigger than latent * non-square VAE tiling (#3) * refactor tile number calculation * support non-square tiles * add env var to change tile overlap * add safeguards and better error messages for SD_TILE_OVERLAP * add safeguards and include overlapping factor for SD_TILE_SIZE * avoid rounding issues when specifying SD_TILE_SIZE as a factor * lower SD_TILE_OVERLAP limit * zero-init empty output buffer * Fix decode latent size * fix encode * tile size params instead of env * Tiled vae parameter validation (#6) * avoid crash with invalid tile sizes, use 0 for default * refactor default tile size, limit overlap factor * remove explicit parameter for relative tile size * limit encoding tile to latent size * unify code style and format code * update docs * fix get_tile_sizes in decode_first_stage --------- Co-authored-by: Wagner Bruna <[email protected]> Co-authored-by: leejet <[email protected]>
1 parent 288e2d6 commit 2c9b1e2

File tree

5 files changed

+303
-58
lines changed

5 files changed

+303
-58
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,9 @@ arguments:
356356
--clip-skip N ignore last_dot_pos layers of CLIP network; 1 ignores none, 2 ignores one layer (default: -1)
357357
<= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x
358358
--vae-tiling process vae in tiles to reduce memory usage
359+
--vae-tile-size [X]x[Y] tile size for vae tiling (default: 32x32)
360+
--vae-relative-tile-size [X]x[Y] relative tile size for vae tiling, in fraction of image size if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)
361+
--vae-tile-overlap OVERLAP tile overlap for vae tiling, in fraction of tile size (default: 0.5)
359362
--vae-on-cpu keep vae in cpu (for low vram)
360363
--clip-on-cpu keep clip in cpu (for low vram)
361364
--diffusion-fa use flash attention in the diffusion model (for low vram)

examples/cli/main.cpp

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ struct SDParams {
101101
rng_type_t rng_type = CUDA_RNG;
102102
int64_t seed = 42;
103103
bool verbose = false;
104-
bool vae_tiling = false;
105104
bool offload_params_to_cpu = false;
106105
bool control_net_cpu = false;
107106
bool normalize_input = false;
@@ -119,6 +118,8 @@ struct SDParams {
119118
int chroma_t5_mask_pad = 1;
120119
float flow_shift = INFINITY;
121120

121+
sd_tiling_params_t vae_tiling_params = {false, 0, 0, 0.5f, 0.0f, 0.0f};
122+
122123
SDParams() {
123124
sd_sample_params_init(&sample_params);
124125
sd_sample_params_init(&high_noise_sample_params);
@@ -180,7 +181,7 @@ void print_params(SDParams params) {
180181
printf(" rng: %s\n", sd_rng_type_name(params.rng_type));
181182
printf(" seed: %ld\n", params.seed);
182183
printf(" batch_count: %d\n", params.batch_count);
183-
printf(" vae_tiling: %s\n", params.vae_tiling ? "true" : "false");
184+
printf(" vae_tiling: %s\n", params.vae_tiling_params.enabled ? "true" : "false");
184185
printf(" upscale_repeats: %d\n", params.upscale_repeats);
185186
printf(" chroma_use_dit_mask: %s\n", params.chroma_use_dit_mask ? "true" : "false");
186187
printf(" chroma_use_t5_mask: %s\n", params.chroma_use_t5_mask ? "true" : "false");
@@ -268,6 +269,9 @@ void print_usage(int argc, const char* argv[]) {
268269
printf(" --clip-skip N ignore last_dot_pos layers of CLIP network; 1 ignores none, 2 ignores one layer (default: -1)\n");
269270
printf(" <= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x\n");
270271
printf(" --vae-tiling process vae in tiles to reduce memory usage\n");
272+
printf(" --vae-tile-size [X]x[Y] tile size for vae tiling (default: 32x32)\n");
273+
printf(" --vae-relative-tile-size [X]x[Y] relative tile size for vae tiling, in fraction of image size if < 1, in number of tiles per dim if >=1 (overrides --vae-tile-size)\n");
274+
printf(" --vae-tile-overlap OVERLAP tile overlap for vae tiling, in fraction of tile size (default: 0.5)\n");
271275
printf(" --vae-on-cpu keep vae in cpu (for low vram)\n");
272276
printf(" --clip-on-cpu keep clip in cpu (for low vram)\n");
273277
printf(" --diffusion-fa use flash attention in the diffusion model (for low vram)\n");
@@ -485,7 +489,6 @@ void parse_args(int argc, const char** argv, SDParams& params) {
485489
{"-o", "--output", "", &params.output_path},
486490
{"-p", "--prompt", "", &params.prompt},
487491
{"-n", "--negative-prompt", "", &params.negative_prompt},
488-
489492
{"", "--upscale-model", "", &params.esrgan_path},
490493
};
491494

@@ -523,10 +526,11 @@ void parse_args(int argc, const char** argv, SDParams& params) {
523526
{"", "--control-strength", "", &params.control_strength},
524527
{"", "--moe-boundary", "", &params.moe_boundary},
525528
{"", "--flow-shift", "", &params.flow_shift},
529+
{"", "--vae-tile-overlap", "", &params.vae_tiling_params.target_overlap},
526530
};
527531

528532
options.bool_options = {
529-
{"", "--vae-tiling", "", true, &params.vae_tiling},
533+
{"", "--vae-tiling", "", true, &params.vae_tiling_params.enabled},
530534
{"", "--offload-to-cpu", "", true, &params.offload_params_to_cpu},
531535
{"", "--control-net-cpu", "", true, &params.control_net_cpu},
532536
{"", "--normalize-input", "", true, &params.normalize_input},
@@ -726,6 +730,52 @@ void parse_args(int argc, const char** argv, SDParams& params) {
726730
return 1;
727731
};
728732

733+
auto on_tile_size_arg = [&](int argc, const char** argv, int index) {
734+
if (++index >= argc) {
735+
return -1;
736+
}
737+
std::string tile_size_str = argv[index];
738+
size_t x_pos = tile_size_str.find('x');
739+
try {
740+
if (x_pos != std::string::npos) {
741+
std::string tile_x_str = tile_size_str.substr(0, x_pos);
742+
std::string tile_y_str = tile_size_str.substr(x_pos + 1);
743+
params.vae_tiling_params.tile_size_x = std::stoi(tile_x_str);
744+
params.vae_tiling_params.tile_size_y = std::stoi(tile_y_str);
745+
} else {
746+
params.vae_tiling_params.tile_size_x = params.vae_tiling_params.tile_size_y = std::stoi(tile_size_str);
747+
}
748+
} catch (const std::invalid_argument& e) {
749+
return -1;
750+
} catch (const std::out_of_range& e) {
751+
return -1;
752+
}
753+
return 1;
754+
};
755+
756+
auto on_relative_tile_size_arg = [&](int argc, const char** argv, int index) {
757+
if (++index >= argc) {
758+
return -1;
759+
}
760+
std::string rel_size_str = argv[index];
761+
size_t x_pos = rel_size_str.find('x');
762+
try {
763+
if (x_pos != std::string::npos) {
764+
std::string rel_x_str = rel_size_str.substr(0, x_pos);
765+
std::string rel_y_str = rel_size_str.substr(x_pos + 1);
766+
params.vae_tiling_params.rel_size_x = std::stof(rel_x_str);
767+
params.vae_tiling_params.rel_size_y = std::stof(rel_y_str);
768+
} else {
769+
params.vae_tiling_params.rel_size_x = params.vae_tiling_params.rel_size_y = std::stof(rel_size_str);
770+
}
771+
} catch (const std::invalid_argument& e) {
772+
return -1;
773+
} catch (const std::out_of_range& e) {
774+
return -1;
775+
}
776+
return 1;
777+
};
778+
729779
options.manual_options = {
730780
{"-M", "--mode", "", on_mode_arg},
731781
{"", "--type", "", on_type_arg},
@@ -739,6 +789,8 @@ void parse_args(int argc, const char** argv, SDParams& params) {
739789
{"", "--high-noise-skip-layers", "", on_high_noise_skip_layers_arg},
740790
{"-r", "--ref-image", "", on_ref_image_arg},
741791
{"-h", "--help", "", on_help_arg},
792+
{"", "--vae-tile-size", "", on_tile_size_arg},
793+
{"", "--vae-relative-tile-size", "", on_relative_tile_size_arg},
742794
};
743795

744796
if (!parse_options(argc, argv, options)) {
@@ -1176,7 +1228,6 @@ int main(int argc, const char* argv[]) {
11761228
params.embedding_dir.c_str(),
11771229
params.stacked_id_embed_dir.c_str(),
11781230
vae_decode_only,
1179-
params.vae_tiling,
11801231
true,
11811232
params.n_threads,
11821233
params.wtype,
@@ -1229,6 +1280,7 @@ int main(int argc, const char* argv[]) {
12291280
params.style_ratio,
12301281
params.normalize_input,
12311282
params.input_id_images_path.c_str(),
1283+
params.vae_tiling_params,
12321284
};
12331285

12341286
results = generate_image(sd_ctx, &img_gen_params);

ggml_extend.hpp

Lines changed: 139 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,10 @@ __STATIC_INLINE__ void ggml_merge_tensor_2d(struct ggml_tensor* input,
494494
struct ggml_tensor* output,
495495
int x,
496496
int y,
497-
int overlap) {
497+
int overlap_x,
498+
int overlap_y,
499+
int x_skip = 0,
500+
int y_skip = 0) {
498501
int64_t width = input->ne[0];
499502
int64_t height = input->ne[1];
500503
int64_t channels = input->ne[2];
@@ -503,17 +506,17 @@ __STATIC_INLINE__ void ggml_merge_tensor_2d(struct ggml_tensor* input,
503506
int64_t img_height = output->ne[1];
504507

505508
GGML_ASSERT(input->type == GGML_TYPE_F32 && output->type == GGML_TYPE_F32);
506-
for (int iy = 0; iy < height; iy++) {
507-
for (int ix = 0; ix < width; ix++) {
509+
for (int iy = y_skip; iy < height; iy++) {
510+
for (int ix = x_skip; ix < width; ix++) {
508511
for (int k = 0; k < channels; k++) {
509512
float new_value = ggml_tensor_get_f32(input, ix, iy, k);
510-
if (overlap > 0) { // blend colors in overlapped area
513+
if (overlap_x > 0 || overlap_y > 0) { // blend colors in overlapped area
511514
float old_value = ggml_tensor_get_f32(output, x + ix, y + iy, k);
512515

513-
const float x_f_0 = (x > 0) ? ix / float(overlap) : 1;
514-
const float x_f_1 = (x < (img_width - width)) ? (width - ix) / float(overlap) : 1;
515-
const float y_f_0 = (y > 0) ? iy / float(overlap) : 1;
516-
const float y_f_1 = (y < (img_height - height)) ? (height - iy) / float(overlap) : 1;
516+
const float x_f_0 = (overlap_x > 0 && x > 0) ? (ix - x_skip) / float(overlap_x) : 1;
517+
const float x_f_1 = (overlap_x > 0 && x < (img_width - width)) ? (width - ix) / float(overlap_x) : 1;
518+
const float y_f_0 = (overlap_y > 0 && y > 0) ? (iy - y_skip) / float(overlap_y) : 1;
519+
const float y_f_1 = (overlap_y > 0 && y < (img_height - height)) ? (height - iy) / float(overlap_y) : 1;
517520

518521
const float x_f = std::min(std::min(x_f_0, x_f_1), 1.f);
519522
const float y_f = std::min(std::min(y_f_0, y_f_1), 1.f);
@@ -745,22 +748,102 @@ __STATIC_INLINE__ std::vector<struct ggml_tensor*> ggml_chunk(struct ggml_contex
745748

746749
typedef std::function<void(ggml_tensor*, ggml_tensor*, bool)> on_tile_process;
747750

751+
__STATIC_INLINE__ void sd_tiling_calc_tiles(int& num_tiles_dim,
752+
float& tile_overlap_factor_dim,
753+
int small_dim,
754+
int tile_size,
755+
const float tile_overlap_factor) {
756+
int tile_overlap = (tile_size * tile_overlap_factor);
757+
int non_tile_overlap = tile_size - tile_overlap;
758+
759+
num_tiles_dim = (small_dim - tile_overlap) / non_tile_overlap;
760+
int overshoot_dim = ((num_tiles_dim + 1) * non_tile_overlap + tile_overlap) % small_dim;
761+
762+
if ((overshoot_dim != non_tile_overlap) && (overshoot_dim <= num_tiles_dim * (tile_size / 2 - tile_overlap))) {
763+
// if tiles don't fit perfectly using the desired overlap
764+
// and there is enough room to squeeze an extra tile without overlap becoming >0.5
765+
num_tiles_dim++;
766+
}
767+
768+
tile_overlap_factor_dim = (float)(tile_size * num_tiles_dim - small_dim) / (float)(tile_size * (num_tiles_dim - 1));
769+
if (num_tiles_dim <= 2) {
770+
if (small_dim <= tile_size) {
771+
num_tiles_dim = 1;
772+
tile_overlap_factor_dim = 0;
773+
} else {
774+
num_tiles_dim = 2;
775+
tile_overlap_factor_dim = (2 * tile_size - small_dim) / (float)tile_size;
776+
}
777+
}
778+
}
779+
748780
// Tiling
749-
__STATIC_INLINE__ void sd_tiling(ggml_tensor* input, ggml_tensor* output, const int scale, const int tile_size, const float tile_overlap_factor, on_tile_process on_processing) {
781+
__STATIC_INLINE__ void sd_tiling_non_square(ggml_tensor* input,
782+
ggml_tensor* output,
783+
const int scale,
784+
const int p_tile_size_x,
785+
const int p_tile_size_y,
786+
const float tile_overlap_factor,
787+
on_tile_process on_processing) {
750788
output = ggml_set_f32(output, 0);
751789

752790
int input_width = (int)input->ne[0];
753791
int input_height = (int)input->ne[1];
754792
int output_width = (int)output->ne[0];
755793
int output_height = (int)output->ne[1];
794+
795+
GGML_ASSERT(((input_width / output_width) == (input_height / output_height)) &&
796+
((output_width / input_width) == (output_height / input_height)));
797+
GGML_ASSERT(((input_width / output_width) == scale) ||
798+
((output_width / input_width) == scale));
799+
800+
int small_width = output_width;
801+
int small_height = output_height;
802+
803+
bool decode = output_width > input_width;
804+
if (decode) {
805+
small_width = input_width;
806+
small_height = input_height;
807+
}
808+
809+
int num_tiles_x;
810+
float tile_overlap_factor_x;
811+
sd_tiling_calc_tiles(num_tiles_x, tile_overlap_factor_x, small_width, p_tile_size_x, tile_overlap_factor);
812+
813+
int num_tiles_y;
814+
float tile_overlap_factor_y;
815+
sd_tiling_calc_tiles(num_tiles_y, tile_overlap_factor_y, small_height, p_tile_size_y, tile_overlap_factor);
816+
817+
LOG_DEBUG("num tiles : %d, %d ", num_tiles_x, num_tiles_y);
818+
LOG_DEBUG("optimal overlap : %f, %f (targeting %f)", tile_overlap_factor_x, tile_overlap_factor_y, tile_overlap_factor);
819+
756820
GGML_ASSERT(input_width % 2 == 0 && input_height % 2 == 0 && output_width % 2 == 0 && output_height % 2 == 0); // should be multiple of 2
757821

758-
int tile_overlap = (int32_t)(tile_size * tile_overlap_factor);
759-
int non_tile_overlap = tile_size - tile_overlap;
822+
int tile_overlap_x = (int32_t)(p_tile_size_x * tile_overlap_factor_x);
823+
int non_tile_overlap_x = p_tile_size_x - tile_overlap_x;
824+
825+
int tile_overlap_y = (int32_t)(p_tile_size_y * tile_overlap_factor_y);
826+
int non_tile_overlap_y = p_tile_size_y - tile_overlap_y;
827+
828+
int tile_size_x = p_tile_size_x < small_width ? p_tile_size_x : small_width;
829+
int tile_size_y = p_tile_size_y < small_height ? p_tile_size_y : small_height;
830+
831+
int input_tile_size_x = tile_size_x;
832+
int input_tile_size_y = tile_size_y;
833+
int output_tile_size_x = tile_size_x;
834+
int output_tile_size_y = tile_size_y;
835+
836+
if (decode) {
837+
output_tile_size_x *= scale;
838+
output_tile_size_y *= scale;
839+
} else {
840+
input_tile_size_x *= scale;
841+
input_tile_size_y *= scale;
842+
}
760843

761844
struct ggml_init_params params = {};
762-
params.mem_size += tile_size * tile_size * input->ne[2] * sizeof(float); // input chunk
763-
params.mem_size += (tile_size * scale) * (tile_size * scale) * output->ne[2] * sizeof(float); // output chunk
845+
params.mem_size += input_tile_size_x * input_tile_size_y * input->ne[2] * sizeof(float); // input chunk
846+
params.mem_size += output_tile_size_x * output_tile_size_y * output->ne[2] * sizeof(float); // output chunk
764847
params.mem_size += 3 * ggml_tensor_overhead();
765848
params.mem_buffer = NULL;
766849
params.no_alloc = false;
@@ -775,29 +858,50 @@ __STATIC_INLINE__ void sd_tiling(ggml_tensor* input, ggml_tensor* output, const
775858
}
776859

777860
// tiling
778-
ggml_tensor* input_tile = ggml_new_tensor_4d(tiles_ctx, GGML_TYPE_F32, tile_size, tile_size, input->ne[2], 1);
779-
ggml_tensor* output_tile = ggml_new_tensor_4d(tiles_ctx, GGML_TYPE_F32, tile_size * scale, tile_size * scale, output->ne[2], 1);
780-
on_processing(input_tile, NULL, true);
781-
int num_tiles = ceil((float)input_width / non_tile_overlap) * ceil((float)input_height / non_tile_overlap);
861+
ggml_tensor* input_tile = ggml_new_tensor_4d(tiles_ctx, GGML_TYPE_F32, input_tile_size_x, input_tile_size_y, input->ne[2], 1);
862+
ggml_tensor* output_tile = ggml_new_tensor_4d(tiles_ctx, GGML_TYPE_F32, output_tile_size_x, output_tile_size_y, output->ne[2], 1);
863+
int num_tiles = num_tiles_x * num_tiles_y;
782864
LOG_INFO("processing %i tiles", num_tiles);
783-
pretty_progress(1, num_tiles, 0.0f);
865+
pretty_progress(0, num_tiles, 0.0f);
784866
int tile_count = 1;
785867
bool last_y = false, last_x = false;
786868
float last_time = 0.0f;
787-
for (int y = 0; y < input_height && !last_y; y += non_tile_overlap) {
788-
if (y + tile_size >= input_height) {
789-
y = input_height - tile_size;
869+
for (int y = 0; y < small_height && !last_y; y += non_tile_overlap_y) {
870+
int dy = 0;
871+
if (y + tile_size_y >= small_height) {
872+
int _y = y;
873+
y = small_height - tile_size_y;
874+
dy = _y - y;
875+
if (decode) {
876+
dy *= scale;
877+
}
790878
last_y = true;
791879
}
792-
for (int x = 0; x < input_width && !last_x; x += non_tile_overlap) {
793-
if (x + tile_size >= input_width) {
794-
x = input_width - tile_size;
880+
for (int x = 0; x < small_width && !last_x; x += non_tile_overlap_x) {
881+
int dx = 0;
882+
if (x + tile_size_x >= small_width) {
883+
int _x = x;
884+
x = small_width - tile_size_x;
885+
dx = _x - x;
886+
if (decode) {
887+
dx *= scale;
888+
}
795889
last_x = true;
796890
}
891+
892+
int x_in = decode ? x : scale * x;
893+
int y_in = decode ? y : scale * y;
894+
int x_out = decode ? x * scale : x;
895+
int y_out = decode ? y * scale : y;
896+
897+
int overlap_x_out = decode ? tile_overlap_x * scale : tile_overlap_x;
898+
int overlap_y_out = decode ? tile_overlap_y * scale : tile_overlap_y;
899+
797900
int64_t t1 = ggml_time_ms();
798-
ggml_split_tensor_2d(input, input_tile, x, y);
901+
ggml_split_tensor_2d(input, input_tile, x_in, y_in);
799902
on_processing(input_tile, output_tile, false);
800-
ggml_merge_tensor_2d(output_tile, output, x * scale, y * scale, tile_overlap * scale);
903+
ggml_merge_tensor_2d(output_tile, output, x_out, y_out, overlap_x_out, overlap_y_out, dx, dy);
904+
801905
int64_t t2 = ggml_time_ms();
802906
last_time = (t2 - t1) / 1000.0f;
803907
pretty_progress(tile_count, num_tiles, last_time);
@@ -811,6 +915,15 @@ __STATIC_INLINE__ void sd_tiling(ggml_tensor* input, ggml_tensor* output, const
811915
ggml_free(tiles_ctx);
812916
}
813917

918+
__STATIC_INLINE__ void sd_tiling(ggml_tensor* input,
919+
ggml_tensor* output,
920+
const int scale,
921+
const int tile_size,
922+
const float tile_overlap_factor,
923+
on_tile_process on_processing) {
924+
sd_tiling_non_square(input, output, scale, tile_size, tile_size, tile_overlap_factor, on_processing);
925+
}
926+
814927
__STATIC_INLINE__ struct ggml_tensor* ggml_group_norm_32(struct ggml_context* ctx,
815928
struct ggml_tensor* a) {
816929
const float eps = 1e-6f; // default eps parameter

0 commit comments

Comments
 (0)