From 65fa6466848a3711a352bb76d5c6f94070667dce Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 23 Nov 2024 04:15:31 +0100 Subject: [PATCH 001/143] feat: add sd3.5 medium and skip layer guidance support (#451) * mmdit-x * add support for sd3.5 medium * add skip layer guidance support (mmdit only) * ignore slg if slg_scale is zero (optimization) * init out_skip once * slg support for flux (expermiental) * warn if version doesn't support slg * refactor slg cli args * set default slg_scale to 0 (oops) * format code --------- Co-authored-by: leejet --- diffusion_model.hpp | 17 ++- examples/cli/main.cpp | 83 +++++++++++++- flux.hpp | 26 +++-- mmdit.hpp | 252 ++++++++++++++++++++++++++++++++++-------- model.cpp | 3 + model.h | 1 + stable-diffusion.cpp | 101 ++++++++++++++--- stable-diffusion.h | 12 +- vae.hpp | 2 +- 9 files changed, 416 insertions(+), 81 deletions(-) diff --git a/diffusion_model.hpp b/diffusion_model.hpp index 2530f7149..26c619b07 100644 --- a/diffusion_model.hpp +++ b/diffusion_model.hpp @@ -17,7 +17,8 @@ struct DiffusionModel { std::vector controls = {}, float control_strength = 0.f, struct ggml_tensor** output = NULL, - struct ggml_context* output_ctx = NULL) = 0; + struct ggml_context* output_ctx = NULL, + std::vector skip_layers = std::vector()) = 0; virtual void alloc_params_buffer() = 0; virtual void free_params_buffer() = 0; virtual void free_compute_buffer() = 0; @@ -70,7 +71,9 @@ struct UNetModel : public DiffusionModel { std::vector controls = {}, float control_strength = 0.f, struct ggml_tensor** output = NULL, - struct ggml_context* output_ctx = NULL) { + struct ggml_context* output_ctx = NULL, + std::vector skip_layers = std::vector()) { + (void)skip_layers; // SLG doesn't work with UNet models return unet.compute(n_threads, x, timesteps, context, c_concat, y, num_video_frames, controls, control_strength, output, output_ctx); } }; @@ -119,8 +122,9 @@ struct MMDiTModel : public DiffusionModel { std::vector controls = {}, float control_strength = 0.f, struct ggml_tensor** output = NULL, - struct ggml_context* output_ctx = NULL) { - return mmdit.compute(n_threads, x, timesteps, context, y, output, output_ctx); + struct ggml_context* output_ctx = NULL, + std::vector skip_layers = std::vector()) { + return mmdit.compute(n_threads, x, timesteps, context, y, output, output_ctx, skip_layers); } }; @@ -168,8 +172,9 @@ struct FluxModel : public DiffusionModel { std::vector controls = {}, float control_strength = 0.f, struct ggml_tensor** output = NULL, - struct ggml_context* output_ctx = NULL) { - return flux.compute(n_threads, x, timesteps, context, y, guidance, output, output_ctx); + struct ggml_context* output_ctx = NULL, + std::vector skip_layers = std::vector()) { + return flux.compute(n_threads, x, timesteps, context, y, guidance, output, output_ctx, skip_layers); } }; diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index f1bdc698b..1f33547ee 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -119,6 +119,11 @@ struct SDParams { bool canny_preprocess = false; bool color = false; int upscale_repeats = 1; + + std::vector skip_layers = {7, 8, 9}; + float slg_scale = 0.; + float skip_layer_start = 0.01; + float skip_layer_end = 0.2; }; void print_params(SDParams params) { @@ -151,6 +156,7 @@ void print_params(SDParams params) { printf(" negative_prompt: %s\n", params.negative_prompt.c_str()); printf(" min_cfg: %.2f\n", params.min_cfg); printf(" cfg_scale: %.2f\n", params.cfg_scale); + printf(" slg_scale: %.2f\n", params.slg_scale); printf(" guidance: %.2f\n", params.guidance); printf(" clip_skip: %d\n", params.clip_skip); printf(" width: %d\n", params.width); @@ -197,6 +203,12 @@ void print_usage(int argc, const char* argv[]) { printf(" -p, --prompt [PROMPT] the prompt to render\n"); printf(" -n, --negative-prompt PROMPT the negative prompt (default: \"\")\n"); printf(" --cfg-scale SCALE unconditional guidance scale: (default: 7.0)\n"); + printf(" --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0)\n"); + printf(" 0 means disabled, a value of 2.5 is nice for sd3.5 medium\n"); + printf(" --skip_layers LAYERS Layers to skip for SLG steps: (default: [7,8,9])\n"); + printf(" --skip_layer_start START SLG enabling point: (default: 0.01)\n"); + printf(" --skip_layer_end END SLG disabling point: (default: 0.2)\n"); + printf(" SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END])\n"); printf(" --strength STRENGTH strength for noising/unnoising (default: 0.75)\n"); printf(" --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%%)\n"); printf(" --control-strength STRENGTH strength to apply Control Net (default: 0.9)\n"); @@ -534,6 +546,61 @@ void parse_args(int argc, const char** argv, SDParams& params) { params.verbose = true; } else if (arg == "--color") { params.color = true; + } else if (arg == "--slg-scale") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.slg_scale = std::stof(argv[i]); + } else if (arg == "--skip-layers") { + if (++i >= argc) { + invalid_arg = true; + break; + } + if (argv[i][0] != '[') { + invalid_arg = true; + break; + } + std::string layers_str = argv[i]; + while (layers_str.back() != ']') { + if (++i >= argc) { + invalid_arg = true; + break; + } + layers_str += " " + std::string(argv[i]); + } + layers_str = layers_str.substr(1, layers_str.size() - 2); + + std::regex regex("[, ]+"); + std::sregex_token_iterator iter(layers_str.begin(), layers_str.end(), regex, -1); + std::sregex_token_iterator end; + std::vector tokens(iter, end); + std::vector layers; + for (const auto& token : tokens) { + try { + layers.push_back(std::stoi(token)); + } catch (const std::invalid_argument& e) { + invalid_arg = true; + break; + } + } + params.skip_layers = layers; + + if (invalid_arg) { + break; + } + } else if (arg == "--skip-layer-start") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.skip_layer_start = std::stof(argv[i]); + } else if (arg == "--skip-layer-end") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.skip_layer_end = std::stof(argv[i]); } else { fprintf(stderr, "error: unknown argument: %s\n", arg.c_str()); print_usage(argc, argv); @@ -624,6 +691,16 @@ std::string get_image_params(SDParams params, int64_t seed) { } parameter_string += "Steps: " + std::to_string(params.sample_steps) + ", "; parameter_string += "CFG scale: " + std::to_string(params.cfg_scale) + ", "; + if (params.slg_scale != 0 && params.skip_layers.size() != 0) { + parameter_string += "SLG scale: " + std::to_string(params.cfg_scale) + ", "; + parameter_string += "Skip layers: ["; + for (const auto& layer : params.skip_layers) { + parameter_string += std::to_string(layer) + ", "; + } + parameter_string += "], "; + parameter_string += "Skip layer start: " + std::to_string(params.skip_layer_start) + ", "; + parameter_string += "Skip layer end: " + std::to_string(params.skip_layer_end) + ", "; + } parameter_string += "Guidance: " + std::to_string(params.guidance) + ", "; parameter_string += "Seed: " + std::to_string(seed) + ", "; parameter_string += "Size: " + std::to_string(params.width) + "x" + std::to_string(params.height) + ", "; @@ -840,7 +917,11 @@ int main(int argc, const char* argv[]) { params.control_strength, params.style_ratio, params.normalize_input, - params.input_id_images_path.c_str()); + params.input_id_images_path.c_str(), + params.skip_layers, + params.slg_scale, + params.skip_layer_start, + params.skip_layer_end); } else { sd_image_t input_image = {(uint32_t)params.width, (uint32_t)params.height, diff --git a/flux.hpp b/flux.hpp index 73bc345a7..89bf78435 100644 --- a/flux.hpp +++ b/flux.hpp @@ -711,7 +711,8 @@ namespace Flux { struct ggml_tensor* timesteps, struct ggml_tensor* y, struct ggml_tensor* guidance, - struct ggml_tensor* pe) { + struct ggml_tensor* pe, + std::vector skip_layers = std::vector()) { auto img_in = std::dynamic_pointer_cast(blocks["img_in"]); auto time_in = std::dynamic_pointer_cast(blocks["time_in"]); auto vector_in = std::dynamic_pointer_cast(blocks["vector_in"]); @@ -733,6 +734,10 @@ namespace Flux { txt = txt_in->forward(ctx, txt); for (int i = 0; i < params.depth; i++) { + if (skip_layers.size() > 0 && std::find(skip_layers.begin(), skip_layers.end(), i) != skip_layers.end()) { + continue; + } + auto block = std::dynamic_pointer_cast(blocks["double_blocks." + std::to_string(i)]); auto img_txt = block->forward(ctx, img, txt, vec, pe); @@ -742,6 +747,9 @@ namespace Flux { auto txt_img = ggml_concat(ctx, txt, img, 1); // [N, n_txt_token + n_img_token, hidden_size] for (int i = 0; i < params.depth_single_blocks; i++) { + if (skip_layers.size() > 0 && std::find(skip_layers.begin(), skip_layers.end(), i + params.depth) != skip_layers.end()) { + continue; + } auto block = std::dynamic_pointer_cast(blocks["single_blocks." + std::to_string(i)]); txt_img = block->forward(ctx, txt_img, vec, pe); @@ -769,7 +777,8 @@ namespace Flux { struct ggml_tensor* context, struct ggml_tensor* y, struct ggml_tensor* guidance, - struct ggml_tensor* pe) { + struct ggml_tensor* pe, + std::vector skip_layers = std::vector()) { // Forward pass of DiT. // x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images) // timestep: (N,) tensor of diffusion timesteps @@ -791,7 +800,7 @@ namespace Flux { // img = rearrange(x, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=patch_size, pw=patch_size) auto img = patchify(ctx, x, patch_size); // [N, h*w, C * patch_size * patch_size] - auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe); // [N, h*w, C * patch_size * patch_size] + auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, skip_layers); // [N, h*w, C * patch_size * patch_size] // rearrange(out, "b (h w) (c ph pw) -> b c (h ph) (w pw)", h=h_len, w=w_len, ph=2, pw=2) out = unpatchify(ctx, out, (H + pad_h) / patch_size, (W + pad_w) / patch_size, patch_size); // [N, C, H + pad_h, W + pad_w] @@ -829,7 +838,8 @@ namespace Flux { struct ggml_tensor* timesteps, struct ggml_tensor* context, struct ggml_tensor* y, - struct ggml_tensor* guidance) { + struct ggml_tensor* guidance, + std::vector skip_layers = std::vector()) { GGML_ASSERT(x->ne[3] == 1); struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, FLUX_GRAPH_SIZE, false); @@ -856,7 +866,8 @@ namespace Flux { context, y, guidance, - pe); + pe, + skip_layers); ggml_build_forward_expand(gf, out); @@ -870,14 +881,15 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor** output = NULL, - struct ggml_context* output_ctx = NULL) { + struct ggml_context* output_ctx = NULL, + std::vector skip_layers = std::vector()) { // x: [N, in_channels, h, w] // timesteps: [N, ] // context: [N, max_position, hidden_size] // y: [N, adm_in_channels] or [1, adm_in_channels] // guidance: [N, ] auto get_graph = [&]() -> struct ggml_cgraph* { - return build_graph(x, timesteps, context, y, guidance); + return build_graph(x, timesteps, context, y, guidance, skip_layers); }; GGMLRunner::compute(get_graph, n_threads, false, output, output_ctx); diff --git a/mmdit.hpp b/mmdit.hpp index 3a278dac7..35810bad9 100644 --- a/mmdit.hpp +++ b/mmdit.hpp @@ -252,6 +252,7 @@ struct DismantledBlock : public GGMLBlock { public: int64_t num_heads; bool pre_only; + bool self_attn; public: DismantledBlock(int64_t hidden_size, @@ -259,14 +260,19 @@ struct DismantledBlock : public GGMLBlock { float mlp_ratio = 4.0, std::string qk_norm = "", bool qkv_bias = false, - bool pre_only = false) - : num_heads(num_heads), pre_only(pre_only) { + bool pre_only = false, + bool self_attn = false) + : num_heads(num_heads), pre_only(pre_only), self_attn(self_attn) { // rmsnorm is always Flase // scale_mod_only is always Flase // swiglu is always Flase blocks["norm1"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-06f, false)); blocks["attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qk_norm, qkv_bias, pre_only)); + if (self_attn) { + blocks["attn2"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qk_norm, qkv_bias, false)); + } + if (!pre_only) { blocks["norm2"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-06f, false)); int64_t mlp_hidden_dim = (int64_t)(hidden_size * mlp_ratio); @@ -277,9 +283,52 @@ struct DismantledBlock : public GGMLBlock { if (pre_only) { n_mods = 2; } + if (self_attn) { + n_mods = 9; + } blocks["adaLN_modulation.1"] = std::shared_ptr(new Linear(hidden_size, n_mods * hidden_size)); } + std::tuple, std::vector, std::vector> pre_attention_x(struct ggml_context* ctx, + struct ggml_tensor* x, + struct ggml_tensor* c) { + GGML_ASSERT(self_attn); + // x: [N, n_token, hidden_size] + // c: [N, hidden_size] + auto norm1 = std::dynamic_pointer_cast(blocks["norm1"]); + auto attn = std::dynamic_pointer_cast(blocks["attn"]); + auto attn2 = std::dynamic_pointer_cast(blocks["attn2"]); + auto adaLN_modulation_1 = std::dynamic_pointer_cast(blocks["adaLN_modulation.1"]); + + int64_t n_mods = 9; + auto m = adaLN_modulation_1->forward(ctx, ggml_silu(ctx, c)); // [N, n_mods * hidden_size] + m = ggml_reshape_3d(ctx, m, c->ne[0], n_mods, c->ne[1]); // [N, n_mods, hidden_size] + m = ggml_cont(ctx, ggml_permute(ctx, m, 0, 2, 1, 3)); // [n_mods, N, hidden_size] + + int64_t offset = m->nb[1] * m->ne[1]; + auto shift_msa = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 0); // [N, hidden_size] + auto scale_msa = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 1); // [N, hidden_size] + auto gate_msa = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 2); // [N, hidden_size] + + auto shift_mlp = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 3); // [N, hidden_size] + auto scale_mlp = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 4); // [N, hidden_size] + auto gate_mlp = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 5); // [N, hidden_size] + + auto shift_msa2 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 6); // [N, hidden_size] + auto scale_msa2 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 7); // [N, hidden_size] + auto gate_msa2 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 8); // [N, hidden_size] + + auto x_norm = norm1->forward(ctx, x); + + auto attn_in = modulate(ctx, x_norm, shift_msa, scale_msa); + auto qkv = attn->pre_attention(ctx, attn_in); + + auto attn2_in = modulate(ctx, x_norm, shift_msa2, scale_msa2); + auto qkv2 = attn2->pre_attention(ctx, attn2_in); + + return {qkv, qkv2, {x, gate_msa, shift_mlp, scale_mlp, gate_mlp, gate_msa2}}; + } + std::pair, std::vector> pre_attention(struct ggml_context* ctx, struct ggml_tensor* x, struct ggml_tensor* c) { @@ -319,6 +368,44 @@ struct DismantledBlock : public GGMLBlock { } } + struct ggml_tensor* post_attention_x(struct ggml_context* ctx, + struct ggml_tensor* attn_out, + struct ggml_tensor* attn2_out, + struct ggml_tensor* x, + struct ggml_tensor* gate_msa, + struct ggml_tensor* shift_mlp, + struct ggml_tensor* scale_mlp, + struct ggml_tensor* gate_mlp, + struct ggml_tensor* gate_msa2) { + // attn_out: [N, n_token, hidden_size] + // x: [N, n_token, hidden_size] + // gate_msa: [N, hidden_size] + // shift_mlp: [N, hidden_size] + // scale_mlp: [N, hidden_size] + // gate_mlp: [N, hidden_size] + // return: [N, n_token, hidden_size] + GGML_ASSERT(!pre_only); + + auto attn = std::dynamic_pointer_cast(blocks["attn"]); + auto attn2 = std::dynamic_pointer_cast(blocks["attn2"]); + auto norm2 = std::dynamic_pointer_cast(blocks["norm2"]); + auto mlp = std::dynamic_pointer_cast(blocks["mlp"]); + + gate_msa = ggml_reshape_3d(ctx, gate_msa, gate_msa->ne[0], 1, gate_msa->ne[1]); // [N, 1, hidden_size] + gate_mlp = ggml_reshape_3d(ctx, gate_mlp, gate_mlp->ne[0], 1, gate_mlp->ne[1]); // [N, 1, hidden_size] + gate_msa2 = ggml_reshape_3d(ctx, gate_msa2, gate_msa2->ne[0], 1, gate_msa2->ne[1]); // [N, 1, hidden_size] + + attn_out = attn->post_attention(ctx, attn_out); + attn2_out = attn2->post_attention(ctx, attn2_out); + + x = ggml_add(ctx, x, ggml_mul(ctx, attn_out, gate_msa)); + x = ggml_add(ctx, x, ggml_mul(ctx, attn2_out, gate_msa2)); + auto mlp_out = mlp->forward(ctx, modulate(ctx, norm2->forward(ctx, x), shift_mlp, scale_mlp)); + x = ggml_add(ctx, x, ggml_mul(ctx, mlp_out, gate_mlp)); + + return x; + } + struct ggml_tensor* post_attention(struct ggml_context* ctx, struct ggml_tensor* attn_out, struct ggml_tensor* x, @@ -357,29 +444,52 @@ struct DismantledBlock : public GGMLBlock { // return: [N, n_token, hidden_size] auto attn = std::dynamic_pointer_cast(blocks["attn"]); - - auto qkv_intermediates = pre_attention(ctx, x, c); - auto qkv = qkv_intermediates.first; - auto intermediates = qkv_intermediates.second; - - auto attn_out = ggml_nn_attention_ext(ctx, qkv[0], qkv[1], qkv[2], num_heads); // [N, n_token, dim] - x = post_attention(ctx, - attn_out, - intermediates[0], - intermediates[1], - intermediates[2], - intermediates[3], - intermediates[4]); - return x; // [N, n_token, dim] + if (self_attn) { + auto qkv_intermediates = pre_attention_x(ctx, x, c); + // auto qkv = qkv_intermediates.first; + // auto intermediates = qkv_intermediates.second; + // no longer a pair, but a tuple + auto qkv = std::get<0>(qkv_intermediates); + auto qkv2 = std::get<1>(qkv_intermediates); + auto intermediates = std::get<2>(qkv_intermediates); + + auto attn_out = ggml_nn_attention_ext(ctx, qkv[0], qkv[1], qkv[2], num_heads); // [N, n_token, dim] + auto attn2_out = ggml_nn_attention_ext(ctx, qkv2[0], qkv2[1], qkv2[2], num_heads); // [N, n_token, dim] + x = post_attention_x(ctx, + attn_out, + attn2_out, + intermediates[0], + intermediates[1], + intermediates[2], + intermediates[3], + intermediates[4], + intermediates[5]); + return x; // [N, n_token, dim] + } else { + auto qkv_intermediates = pre_attention(ctx, x, c); + auto qkv = qkv_intermediates.first; + auto intermediates = qkv_intermediates.second; + + auto attn_out = ggml_nn_attention_ext(ctx, qkv[0], qkv[1], qkv[2], num_heads); // [N, n_token, dim] + x = post_attention(ctx, + attn_out, + intermediates[0], + intermediates[1], + intermediates[2], + intermediates[3], + intermediates[4]); + return x; // [N, n_token, dim] + } } }; -__STATIC_INLINE__ std::pair block_mixing(struct ggml_context* ctx, - struct ggml_tensor* context, - struct ggml_tensor* x, - struct ggml_tensor* c, - std::shared_ptr context_block, - std::shared_ptr x_block) { +__STATIC_INLINE__ std::pair +block_mixing(struct ggml_context* ctx, + struct ggml_tensor* context, + struct ggml_tensor* x, + struct ggml_tensor* c, + std::shared_ptr context_block, + std::shared_ptr x_block) { // context: [N, n_context, hidden_size] // x: [N, n_token, hidden_size] // c: [N, hidden_size] @@ -387,10 +497,18 @@ __STATIC_INLINE__ std::pair block_mixi auto context_qkv = context_qkv_intermediates.first; auto context_intermediates = context_qkv_intermediates.second; - auto x_qkv_intermediates = x_block->pre_attention(ctx, x, c); - auto x_qkv = x_qkv_intermediates.first; - auto x_intermediates = x_qkv_intermediates.second; + std::vector x_qkv, x_qkv2, x_intermediates; + if (x_block->self_attn) { + auto x_qkv_intermediates = x_block->pre_attention_x(ctx, x, c); + x_qkv = std::get<0>(x_qkv_intermediates); + x_qkv2 = std::get<1>(x_qkv_intermediates); + x_intermediates = std::get<2>(x_qkv_intermediates); + } else { + auto x_qkv_intermediates = x_block->pre_attention(ctx, x, c); + x_qkv = x_qkv_intermediates.first; + x_intermediates = x_qkv_intermediates.second; + } std::vector qkv; for (int i = 0; i < 3; i++) { qkv.push_back(ggml_concat(ctx, context_qkv[i], x_qkv[i], 1)); @@ -429,13 +547,27 @@ __STATIC_INLINE__ std::pair block_mixi context = NULL; } - x = x_block->post_attention(ctx, - x_attn, - x_intermediates[0], - x_intermediates[1], - x_intermediates[2], - x_intermediates[3], - x_intermediates[4]); + if (x_block->self_attn) { + auto attn2 = ggml_nn_attention_ext(ctx, x_qkv2[0], x_qkv2[1], x_qkv2[2], x_block->num_heads); // [N, n_token, hidden_size] + + x = x_block->post_attention_x(ctx, + x_attn, + attn2, + x_intermediates[0], + x_intermediates[1], + x_intermediates[2], + x_intermediates[3], + x_intermediates[4], + x_intermediates[5]); + } else { + x = x_block->post_attention(ctx, + x_attn, + x_intermediates[0], + x_intermediates[1], + x_intermediates[2], + x_intermediates[3], + x_intermediates[4]); + } return {context, x}; } @@ -447,9 +579,10 @@ struct JointBlock : public GGMLBlock { float mlp_ratio = 4.0, std::string qk_norm = "", bool qkv_bias = false, - bool pre_only = false) { + bool pre_only = false, + bool self_attn_x = false) { blocks["context_block"] = std::shared_ptr(new DismantledBlock(hidden_size, num_heads, mlp_ratio, qk_norm, qkv_bias, pre_only)); - blocks["x_block"] = std::shared_ptr(new DismantledBlock(hidden_size, num_heads, mlp_ratio, qk_norm, qkv_bias, false)); + blocks["x_block"] = std::shared_ptr(new DismantledBlock(hidden_size, num_heads, mlp_ratio, qk_norm, qkv_bias, false, self_attn_x)); } std::pair forward(struct ggml_context* ctx, @@ -507,6 +640,7 @@ struct MMDiT : public GGMLBlock { int64_t input_size = -1; int64_t patch_size = 2; int64_t in_channels = 16; + int64_t d_self = -1; // >=0 for MMdiT-X int64_t depth = 24; float mlp_ratio = 4.0f; int64_t adm_in_channels = 2048; @@ -561,6 +695,20 @@ struct MMDiT : public GGMLBlock { context_size = 4096; context_embedder_out_dim = 2432; qk_norm = "rms"; + } else if (version == VERSION_SD3_5_2B) { + input_size = -1; + patch_size = 2; + in_channels = 16; + depth = 24; + d_self = 12; + mlp_ratio = 4.0f; + adm_in_channels = 2048; + out_channels = 16; + pos_embed_max_size = 384; + num_patchs = 147456; + context_size = 4096; + context_embedder_out_dim = 1536; + qk_norm = "rms"; } int64_t default_out_channels = in_channels; hidden_size = 64 * depth; @@ -581,15 +729,17 @@ struct MMDiT : public GGMLBlock { mlp_ratio, qk_norm, true, - i == depth - 1)); + i == depth - 1, + i <= d_self)); } blocks["final_layer"] = std::shared_ptr(new FinalLayer(hidden_size, patch_size, out_channels)); } - struct ggml_tensor* cropped_pos_embed(struct ggml_context* ctx, - int64_t h, - int64_t w) { + struct ggml_tensor* + cropped_pos_embed(struct ggml_context* ctx, + int64_t h, + int64_t w) { auto pos_embed = params["pos_embed"]; h = (h + 1) / patch_size; @@ -651,7 +801,8 @@ struct MMDiT : public GGMLBlock { struct ggml_tensor* forward_core_with_concat(struct ggml_context* ctx, struct ggml_tensor* x, struct ggml_tensor* c_mod, - struct ggml_tensor* context) { + struct ggml_tensor* context, + std::vector skip_layers = std::vector()) { // x: [N, H*W, hidden_size] // context: [N, n_context, d_context] // c: [N, hidden_size] @@ -659,6 +810,11 @@ struct MMDiT : public GGMLBlock { auto final_layer = std::dynamic_pointer_cast(blocks["final_layer"]); for (int i = 0; i < depth; i++) { + // skip iteration if i is in skip_layers + if (skip_layers.size() > 0 && std::find(skip_layers.begin(), skip_layers.end(), i) != skip_layers.end()) { + continue; + } + auto block = std::dynamic_pointer_cast(blocks["joint_blocks." + std::to_string(i)]); auto context_x = block->forward(ctx, context, x, c_mod); @@ -674,8 +830,9 @@ struct MMDiT : public GGMLBlock { struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x, struct ggml_tensor* t, - struct ggml_tensor* y = NULL, - struct ggml_tensor* context = NULL) { + struct ggml_tensor* y = NULL, + struct ggml_tensor* context = NULL, + std::vector skip_layers = std::vector()) { // Forward pass of DiT. // x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images) // t: (N,) tensor of diffusion timesteps @@ -706,7 +863,7 @@ struct MMDiT : public GGMLBlock { context = context_embedder->forward(ctx, context); // [N, L, D] aka [N, L, 1536] } - x = forward_core_with_concat(ctx, x, c, context); // (N, H*W, patch_size ** 2 * out_channels) + x = forward_core_with_concat(ctx, x, c, context, skip_layers); // (N, H*W, patch_size ** 2 * out_channels) x = unpatchify(ctx, x, h, w); // [N, C, H, W] @@ -735,7 +892,8 @@ struct MMDiTRunner : public GGMLRunner { struct ggml_cgraph* build_graph(struct ggml_tensor* x, struct ggml_tensor* timesteps, struct ggml_tensor* context, - struct ggml_tensor* y) { + struct ggml_tensor* y, + std::vector skip_layers = std::vector()) { struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, MMDIT_GRAPH_SIZE, false); x = to_backend(x); @@ -747,7 +905,8 @@ struct MMDiTRunner : public GGMLRunner { x, timesteps, y, - context); + context, + skip_layers); ggml_build_forward_expand(gf, out); @@ -760,13 +919,14 @@ struct MMDiTRunner : public GGMLRunner { struct ggml_tensor* context, struct ggml_tensor* y, struct ggml_tensor** output = NULL, - struct ggml_context* output_ctx = NULL) { + struct ggml_context* output_ctx = NULL, + std::vector skip_layers = std::vector()) { // x: [N, in_channels, h, w] // timesteps: [N, ] // context: [N, max_position, hidden_size]([N, 154, 4096]) or [1, max_position, hidden_size] // y: [N, adm_in_channels] or [1, adm_in_channels] auto get_graph = [&]() -> struct ggml_cgraph* { - return build_graph(x, timesteps, context, y); + return build_graph(x, timesteps, context, y, skip_layers); }; GGMLRunner::compute(get_graph, n_threads, false, output, output_ctx); diff --git a/model.cpp b/model.cpp index 26451cdc5..3da1b3a4e 100644 --- a/model.cpp +++ b/model.cpp @@ -1373,6 +1373,9 @@ SDVersion ModelLoader::get_sd_version() { if (tensor_storage.name.find("model.diffusion_model.double_blocks.") != std::string::npos) { is_flux = true; } + if (tensor_storage.name.find("joint_blocks.0.x_block.attn2.ln_q.weight") != std::string::npos) { + return VERSION_SD3_5_2B; + } if (tensor_storage.name.find("joint_blocks.37.x_block.attn.ln_q.weight") != std::string::npos) { return VERSION_SD3_5_8B; } diff --git a/model.h b/model.h index 4efbdf813..041245e37 100644 --- a/model.h +++ b/model.h @@ -26,6 +26,7 @@ enum SDVersion { VERSION_FLUX_DEV, VERSION_FLUX_SCHNELL, VERSION_SD3_5_8B, + VERSION_SD3_5_2B, VERSION_COUNT, }; diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 4d28a147b..079daa041 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -32,7 +32,8 @@ const char* model_version_to_str[] = { "SD3 2B", "Flux Dev", "Flux Schnell", - "SD3.5 8B"}; + "SD3.5 8B", + "SD3.5 2B"}; const char* sampling_methods_str[] = { "Euler A", @@ -288,7 +289,7 @@ class StableDiffusionGGML { "try specifying SDXL VAE FP16 Fix with the --vae parameter. " "You can find it here: https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/blob/main/sdxl_vae.safetensors"); } - } else if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B) { + } else if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { scale_factor = 1.5305f; } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { scale_factor = 0.3611; @@ -311,7 +312,7 @@ class StableDiffusionGGML { } else { clip_backend = backend; bool use_t5xxl = false; - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { use_t5xxl = true; } if (!ggml_backend_is_cpu(backend) && use_t5xxl && conditioner_wtype != GGML_TYPE_F32) { @@ -322,7 +323,7 @@ class StableDiffusionGGML { LOG_INFO("CLIP: Using CPU backend"); clip_backend = ggml_backend_cpu_init(); } - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { @@ -520,7 +521,7 @@ class StableDiffusionGGML { is_using_v_parameterization = true; } - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { LOG_INFO("running in FLOW mode"); denoiser = std::make_shared(); } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { @@ -771,7 +772,11 @@ class StableDiffusionGGML { sample_method_t method, const std::vector& sigmas, int start_merge_step, - SDCondition id_cond) { + SDCondition id_cond, + std::vector skip_layers = {}, + float slg_scale = 2.5, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2) { size_t steps = sigmas.size() - 1; // noise = load_tensor_from_file(work_ctx, "./rand0.bin"); // print_ggml_tensor(noise); @@ -782,13 +787,24 @@ class StableDiffusionGGML { struct ggml_tensor* noised_input = ggml_dup_tensor(work_ctx, noise); bool has_unconditioned = cfg_scale != 1.0 && uncond.c_crossattn != NULL; + bool has_skiplayer = slg_scale != 0.0 && skip_layers.size() > 0; // denoise wrapper struct ggml_tensor* out_cond = ggml_dup_tensor(work_ctx, x); struct ggml_tensor* out_uncond = NULL; + struct ggml_tensor* out_skip = NULL; + if (has_unconditioned) { out_uncond = ggml_dup_tensor(work_ctx, x); } + if (has_skiplayer) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_2B || version == VERSION_SD3_5_8B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + out_skip = ggml_dup_tensor(work_ctx, x); + } else { + has_skiplayer = false; + LOG_WARN("SLG is incompatible with %s models", model_version_to_str[version]); + } + } struct ggml_tensor* denoised = ggml_dup_tensor(work_ctx, x); auto denoise = [&](ggml_tensor* input, float sigma, int step) -> ggml_tensor* { @@ -869,6 +885,28 @@ class StableDiffusionGGML { &out_uncond); negative_data = (float*)out_uncond->data; } + + int step_count = sigmas.size(); + bool is_skiplayer_step = has_skiplayer && step > (int)(skip_layer_start * step_count) && step < (int)(skip_layer_end * step_count); + float* skip_layer_data = NULL; + if (is_skiplayer_step) { + LOG_DEBUG("Skipping layers at step %d\n", step); + // skip layer (same as conditionned) + diffusion_model->compute(n_threads, + noised_input, + timesteps, + cond.c_crossattn, + cond.c_concat, + cond.c_vector, + guidance_tensor, + -1, + controls, + control_strength, + &out_skip, + NULL, + skip_layers); + skip_layer_data = (float*)out_skip->data; + } float* vec_denoised = (float*)denoised->data; float* vec_input = (float*)input->data; float* positive_data = (float*)out_cond->data; @@ -885,6 +923,9 @@ class StableDiffusionGGML { latent_result = negative_data[i] + cfg_scale * (positive_data[i] - negative_data[i]); } } + if (is_skiplayer_step) { + latent_result = latent_result + (positive_data[i] - skip_layer_data[i]) * slg_scale; + } // v = latent_result, eps = latent_result // denoised = (v * c_out + input * c_skip) or (input + eps * c_out) vec_denoised[i] = latent_result * c_out + vec_input[i] * c_skip; @@ -948,7 +989,7 @@ class StableDiffusionGGML { if (use_tiny_autoencoder) { C = 4; } else { - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { C = 32; } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { C = 32; @@ -1111,7 +1152,11 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, float control_strength, float style_ratio, bool normalize_input, - std::string input_id_images_path) { + std::string input_id_images_path, + std::vector skip_layers = {}, + float slg_scale = 2.5, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2) { if (seed < 0) { // Generally, when using the provided command line, the seed is always >0. // However, to prevent potential issues if 'stable-diffusion.cpp' is invoked as a library @@ -1281,7 +1326,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, // Sample std::vector final_latents; // collect latents to decode int C = 4; - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B) { + if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { C = 16; } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { C = 16; @@ -1320,7 +1365,11 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, sample_method, sigmas, start_merge_step, - id_cond); + id_cond, + skip_layers, + slg_scale, + skip_layer_start, + skip_layer_end); // struct ggml_tensor* x_0 = load_tensor_from_file(ctx, "samples_ddim.bin"); // print_ggml_tensor(x_0); int64_t sampling_end = ggml_time_ms(); @@ -1386,7 +1435,11 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, float control_strength, float style_ratio, bool normalize_input, - const char* input_id_images_path_c_str) { + const char* input_id_images_path_c_str, + std::vector skip_layers, + float slg_scale, + float skip_layer_start, + float skip_layer_end) { LOG_DEBUG("txt2img %dx%d", width, height); if (sd_ctx == NULL) { return NULL; @@ -1394,7 +1447,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, struct ggml_init_params params; params.mem_size = static_cast(10 * 1024 * 1024); // 10 MB - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B) { + if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { params.mem_size *= 3; } if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { @@ -1420,7 +1473,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); int C = 4; - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B) { + if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { C = 16; } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { C = 16; @@ -1428,7 +1481,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, int W = width / 8; int H = height / 8; ggml_tensor* init_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, 1); - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B) { + if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { ggml_set_f32(init_latent, 0.0609f); } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { ggml_set_f32(init_latent, 0.1159f); @@ -1454,7 +1507,11 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, control_strength, style_ratio, normalize_input, - input_id_images_path_c_str); + input_id_images_path_c_str, + skip_layers, + slg_scale, + skip_layer_start, + skip_layer_end); size_t t1 = ggml_time_ms(); @@ -1481,7 +1538,11 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, float control_strength, float style_ratio, bool normalize_input, - const char* input_id_images_path_c_str) { + const char* input_id_images_path_c_str, + std::vector skip_layers, + float slg_scale, + float skip_layer_start, + float skip_layer_end) { LOG_DEBUG("img2img %dx%d", width, height); if (sd_ctx == NULL) { return NULL; @@ -1489,7 +1550,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, struct ggml_init_params params; params.mem_size = static_cast(10 * 1024 * 1024); // 10 MB - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B) { + if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { params.mem_size *= 2; } if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { @@ -1555,7 +1616,11 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, control_strength, style_ratio, normalize_input, - input_id_images_path_c_str); + input_id_images_path_c_str, + skip_layers, + slg_scale, + skip_layer_start, + skip_layer_end); size_t t2 = ggml_time_ms(); diff --git a/stable-diffusion.h b/stable-diffusion.h index 812e8fc94..b310ee595 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -162,7 +162,11 @@ SD_API sd_image_t* txt2img(sd_ctx_t* sd_ctx, float control_strength, float style_strength, bool normalize_input, - const char* input_id_images_path); + const char* input_id_images_path, + std::vector skip_layers = {}, + float slg_scale = 2.5, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2); SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, sd_image_t init_image, @@ -182,7 +186,11 @@ SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, float control_strength, float style_strength, bool normalize_input, - const char* input_id_images_path); + const char* input_id_images_path, + std::vector skip_layers = {}, + float slg_scale = 2.5, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2); SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, sd_image_t init_image, diff --git a/vae.hpp b/vae.hpp index 42b694cd5..50ddf7529 100644 --- a/vae.hpp +++ b/vae.hpp @@ -457,7 +457,7 @@ class AutoencodingEngine : public GGMLBlock { bool use_video_decoder = false, SDVersion version = VERSION_SD1) : decode_only(decode_only), use_video_decoder(use_video_decoder) { - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { dd_config.z_channels = 16; use_quant = false; } From 9b1d90bc238a678a9a63cd243a5d38403f37a081 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 23 Nov 2024 04:19:27 +0100 Subject: [PATCH 002/143] fix: improve clip text_projection support (#397) --- clip.hpp | 8 +++-- conditioner.hpp | 79 +++++++++++++++++++++---------------------------- 2 files changed, 40 insertions(+), 47 deletions(-) diff --git a/clip.hpp b/clip.hpp index f9ac631a8..bf2a8c149 100644 --- a/clip.hpp +++ b/clip.hpp @@ -711,8 +711,12 @@ class CLIPTextModel : public GGMLBlock { if (return_pooled) { auto text_projection = params["text_projection"]; ggml_tensor* pooled = ggml_view_1d(ctx, x, hidden_size, x->nb[1] * max_token_idx); - pooled = ggml_mul_mat(ctx, ggml_cont(ctx, ggml_transpose(ctx, text_projection)), pooled); - return pooled; + if (text_projection != NULL) { + pooled = ggml_nn_linear(ctx, pooled, text_projection, NULL); + } else { + LOG_DEBUG("Missing text_projection matrix, assuming identity..."); + } + return pooled; // [hidden_size, 1, 1] } return x; // [N, n_token, hidden_size] diff --git a/conditioner.hpp b/conditioner.hpp index ac2ab7ebf..9f9d5ae1f 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -798,21 +798,17 @@ struct SD3CLIPEmbedder : public Conditioner { } if (chunk_idx == 0) { - // auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); - // max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); - // clip_l->compute(n_threads, - // input_ids, - // 0, - // NULL, - // max_token_idx, - // true, - // &pooled_l, - // work_ctx); - - // clip_l.transformer.text_model.text_projection no in file, ignore - // TODO: use torch.eye(embed_dim) as default clip_l.transformer.text_model.text_projection - pooled_l = ggml_new_tensor_1d(work_ctx, GGML_TYPE_F32, 768); - ggml_set_f32(pooled_l, 0.f); + auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); + max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); + clip_l->compute(n_threads, + input_ids, + 0, + NULL, + max_token_idx, + true, + &pooled_l, + work_ctx); + } } @@ -852,21 +848,17 @@ struct SD3CLIPEmbedder : public Conditioner { } if (chunk_idx == 0) { - // auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_g_tokenizer.EOS_TOKEN_ID); - // max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); - // clip_g->compute(n_threads, - // input_ids, - // 0, - // NULL, - // max_token_idx, - // true, - // &pooled_g, - // work_ctx); - // clip_l.transformer.text_model.text_projection no in file, ignore pooled_g too - - // TODO: fix pooled_g - pooled_g = ggml_new_tensor_1d(work_ctx, GGML_TYPE_F32, 1280); - ggml_set_f32(pooled_g, 0.f); + auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_g_tokenizer.EOS_TOKEN_ID); + max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); + clip_g->compute(n_threads, + input_ids, + 0, + NULL, + max_token_idx, + true, + &pooled_g, + work_ctx); + } } @@ -1104,21 +1096,18 @@ struct FluxCLIPEmbedder : public Conditioner { auto input_ids = vector_to_ggml_tensor_i32(work_ctx, chunk_tokens); size_t max_token_idx = 0; - // auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); - // max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); - // clip_l->compute(n_threads, - // input_ids, - // 0, - // NULL, - // max_token_idx, - // true, - // &pooled, - // work_ctx); - - // clip_l.transformer.text_model.text_projection no in file, ignore - // TODO: use torch.eye(embed_dim) as default clip_l.transformer.text_model.text_projection - pooled = ggml_new_tensor_1d(work_ctx, GGML_TYPE_F32, 768); - ggml_set_f32(pooled, 0.f); + auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); + max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); + + clip_l->compute(n_threads, + input_ids, + 0, + NULL, + max_token_idx, + true, + &pooled, + work_ctx); + } // t5 From 6ea812256ef5f42f745ced310c43475b6c04aa61 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 23 Nov 2024 04:41:30 +0100 Subject: [PATCH 003/143] feat: add flux 1 lite 8B (freepik) support (#474) * Flux Lite (Freepik) support * format code --------- Co-authored-by: leejet --- clip.hpp | 2 +- conditioner.hpp | 11 ++++------- flux.hpp | 3 +++ model.cpp | 20 ++++++++++++++++---- model.h | 1 + stable-diffusion.cpp | 23 ++++++++++++----------- vae.hpp | 2 +- 7 files changed, 38 insertions(+), 24 deletions(-) diff --git a/clip.hpp b/clip.hpp index bf2a8c149..e0d846aa8 100644 --- a/clip.hpp +++ b/clip.hpp @@ -712,7 +712,7 @@ class CLIPTextModel : public GGMLBlock { auto text_projection = params["text_projection"]; ggml_tensor* pooled = ggml_view_1d(ctx, x, hidden_size, x->nb[1] * max_token_idx); if (text_projection != NULL) { - pooled = ggml_nn_linear(ctx, pooled, text_projection, NULL); + pooled = ggml_nn_linear(ctx, pooled, text_projection, NULL); } else { LOG_DEBUG("Missing text_projection matrix, assuming identity..."); } diff --git a/conditioner.hpp b/conditioner.hpp index 9f9d5ae1f..ea02d377f 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -798,7 +798,7 @@ struct SD3CLIPEmbedder : public Conditioner { } if (chunk_idx == 0) { - auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); + auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); clip_l->compute(n_threads, input_ids, @@ -808,7 +808,6 @@ struct SD3CLIPEmbedder : public Conditioner { true, &pooled_l, work_ctx); - } } @@ -848,7 +847,7 @@ struct SD3CLIPEmbedder : public Conditioner { } if (chunk_idx == 0) { - auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_g_tokenizer.EOS_TOKEN_ID); + auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_g_tokenizer.EOS_TOKEN_ID); max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); clip_g->compute(n_threads, input_ids, @@ -858,7 +857,6 @@ struct SD3CLIPEmbedder : public Conditioner { true, &pooled_g, work_ctx); - } } @@ -1096,9 +1094,9 @@ struct FluxCLIPEmbedder : public Conditioner { auto input_ids = vector_to_ggml_tensor_i32(work_ctx, chunk_tokens); size_t max_token_idx = 0; - auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); + auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), clip_l_tokenizer.EOS_TOKEN_ID); max_token_idx = std::min(std::distance(chunk_tokens.begin(), it), chunk_tokens.size() - 1); - + clip_l->compute(n_threads, input_ids, 0, @@ -1107,7 +1105,6 @@ struct FluxCLIPEmbedder : public Conditioner { true, &pooled, work_ctx); - } // t5 diff --git a/flux.hpp b/flux.hpp index 89bf78435..faea59a4d 100644 --- a/flux.hpp +++ b/flux.hpp @@ -822,6 +822,9 @@ namespace Flux { if (version == VERSION_FLUX_SCHNELL) { flux_params.guidance_embed = false; } + if (version == VERSION_FLUX_LITE) { + flux_params.depth = 8; + } flux = Flux(flux_params); flux.init(params_ctx, wtype); } diff --git a/model.cpp b/model.cpp index 3da1b3a4e..64b57b1de 100644 --- a/model.cpp +++ b/model.cpp @@ -1364,15 +1364,20 @@ bool ModelLoader::init_from_ckpt_file(const std::string& file_path, const std::s SDVersion ModelLoader::get_sd_version() { TensorStorage token_embedding_weight; - bool is_flux = false; - bool is_sd3 = false; + bool is_flux = false; + bool is_schnell = true; + bool is_lite = true; + bool is_sd3 = false; for (auto& tensor_storage : tensor_storages) { if (tensor_storage.name.find("model.diffusion_model.guidance_in.in_layer.weight") != std::string::npos) { - return VERSION_FLUX_DEV; + is_schnell = false; } if (tensor_storage.name.find("model.diffusion_model.double_blocks.") != std::string::npos) { is_flux = true; } + if (tensor_storage.name.find("model.diffusion_model.double_blocks.8") != std::string::npos) { + is_lite = false; + } if (tensor_storage.name.find("joint_blocks.0.x_block.attn2.ln_q.weight") != std::string::npos) { return VERSION_SD3_5_2B; } @@ -1403,7 +1408,14 @@ SDVersion ModelLoader::get_sd_version() { } } if (is_flux) { - return VERSION_FLUX_SCHNELL; + if (is_schnell) { + GGML_ASSERT(!is_lite); + return VERSION_FLUX_SCHNELL; + } else if (is_lite) { + return VERSION_FLUX_LITE; + } else { + return VERSION_FLUX_DEV; + } } if (is_sd3) { return VERSION_SD3_2B; diff --git a/model.h b/model.h index 041245e37..8a1ab4149 100644 --- a/model.h +++ b/model.h @@ -27,6 +27,7 @@ enum SDVersion { VERSION_FLUX_SCHNELL, VERSION_SD3_5_8B, VERSION_SD3_5_2B, + VERSION_FLUX_LITE, VERSION_COUNT, }; diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 079daa041..2297cd377 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -33,7 +33,8 @@ const char* model_version_to_str[] = { "Flux Dev", "Flux Schnell", "SD3.5 8B", - "SD3.5 2B"}; + "SD3.5 2B", + "Flux Lite 8B"}; const char* sampling_methods_str[] = { "Euler A", @@ -291,7 +292,7 @@ class StableDiffusionGGML { } } else if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { scale_factor = 1.5305f; - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { scale_factor = 0.3611; // TODO: shift_factor } @@ -312,7 +313,7 @@ class StableDiffusionGGML { } else { clip_backend = backend; bool use_t5xxl = false; - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { use_t5xxl = true; } if (!ggml_backend_is_cpu(backend) && use_t5xxl && conditioner_wtype != GGML_TYPE_F32) { @@ -326,7 +327,7 @@ class StableDiffusionGGML { if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); } else { @@ -524,7 +525,7 @@ class StableDiffusionGGML { if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { LOG_INFO("running in FLOW mode"); denoiser = std::make_shared(); - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { LOG_INFO("running in Flux FLOW mode"); float shift = 1.15f; if (version == VERSION_FLUX_SCHNELL) { @@ -991,7 +992,7 @@ class StableDiffusionGGML { } else { if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { C = 32; - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { C = 32; } } @@ -1328,7 +1329,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, int C = 4; if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { C = 16; - } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { + } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { C = 16; } int W = width / 8; @@ -1450,7 +1451,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { params.mem_size *= 3; } - if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { + if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { params.mem_size *= 4; } if (sd_ctx->sd->stacked_id) { @@ -1475,7 +1476,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, int C = 4; if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { C = 16; - } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { + } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { C = 16; } int W = width / 8; @@ -1483,7 +1484,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, ggml_tensor* init_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, 1); if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { ggml_set_f32(init_latent, 0.0609f); - } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { + } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { ggml_set_f32(init_latent, 0.1159f); } else { ggml_set_f32(init_latent, 0.f); @@ -1553,7 +1554,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { params.mem_size *= 2; } - if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL) { + if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { params.mem_size *= 3; } if (sd_ctx->sd->stacked_id) { diff --git a/vae.hpp b/vae.hpp index 50ddf7529..8642375f8 100644 --- a/vae.hpp +++ b/vae.hpp @@ -457,7 +457,7 @@ class AutoencodingEngine : public GGMLBlock { bool use_video_decoder = false, SDVersion version = VERSION_SD1) : decode_only(decode_only), use_video_decoder(use_video_decoder) { - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { dd_config.z_channels = 16; use_quant = false; } From 07585448adecdc41705d8423a1dfb603c6f23dea Mon Sep 17 00:00:00 2001 From: fszontagh <51741446+fszontagh@users.noreply.github.com> Date: Sat, 23 Nov 2024 04:42:12 +0100 Subject: [PATCH 004/143] docs: update readme (#462) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c1ba396fe..60f718191 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,7 @@ These projects wrap `stable-diffusion.cpp` for easier use in other languages/fra These projects use `stable-diffusion.cpp` as a backend for their image generation. - [Jellybox](https://jellybox.com) +- [Stable Diffusion GUI](https://github.com/fszontagh/sd.cpp.gui.wx) ## Contributors From 8f94efafa349df5abea3badbacf58cceff158197 Mon Sep 17 00:00:00 2001 From: LostRuins Concedo <39025047+LostRuins@users.noreply.github.com> Date: Sat, 23 Nov 2024 11:45:11 +0800 Subject: [PATCH 005/143] feat: add support for loading F8_E5M2 weights (#460) --- model.cpp | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ model.h | 5 ++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/model.cpp b/model.cpp index 64b57b1de..ae1c097a1 100644 --- a/model.cpp +++ b/model.cpp @@ -614,6 +614,48 @@ uint16_t f8_e4m3_to_f16(uint8_t f8) { return ggml_fp32_to_fp16(*reinterpret_cast(&result)); } + +uint16_t f8_e5m2_to_f16(uint8_t fp8) { + uint8_t sign = (fp8 >> 7) & 0x1; + uint8_t exponent = (fp8 >> 2) & 0x1F; + uint8_t mantissa = fp8 & 0x3; + + uint16_t fp16_sign = sign << 15; + uint16_t fp16_exponent; + uint16_t fp16_mantissa; + + if (exponent == 0 && mantissa == 0) { //zero + return fp16_sign; + } + + if (exponent == 0x1F) { //NAN and INF + fp16_exponent = 0x1F; + fp16_mantissa = mantissa ? (mantissa << 8) : 0; + return fp16_sign | (fp16_exponent << 10) | fp16_mantissa; + } + + if (exponent == 0) { //subnormal numbers + fp16_exponent = 0; + fp16_mantissa = (mantissa << 8); + return fp16_sign | fp16_mantissa; + } + + //normal numbers + int16_t true_exponent = (int16_t)exponent - 15 + 15; + if (true_exponent <= 0) { + fp16_exponent = 0; + fp16_mantissa = (mantissa << 8); + } else if (true_exponent >= 0x1F) { + fp16_exponent = 0x1F; + fp16_mantissa = 0; + } else { + fp16_exponent = (uint16_t)true_exponent; + fp16_mantissa = mantissa << 8; + } + + return fp16_sign | (fp16_exponent << 10) | fp16_mantissa; +} + void bf16_to_f32_vec(uint16_t* src, float* dst, int64_t n) { // support inplace op for (int64_t i = n - 1; i >= 0; i--) { @@ -627,6 +669,12 @@ void f8_e4m3_to_f16_vec(uint8_t* src, uint16_t* dst, int64_t n) { dst[i] = f8_e4m3_to_f16(src[i]); } } +void f8_e5m2_to_f16_vec(uint8_t* src, uint16_t* dst, int64_t n) { + // support inplace op + for (int64_t i = n - 1; i >= 0; i--) { + dst[i] = f8_e5m2_to_f16(src[i]); + } +} void convert_tensor(void* src, ggml_type src_type, @@ -863,6 +911,8 @@ ggml_type str_to_ggml_type(const std::string& dtype) { ttype = GGML_TYPE_F32; } else if (dtype == "F8_E4M3") { ttype = GGML_TYPE_F16; + } else if (dtype == "F8_E5M2") { + ttype = GGML_TYPE_F16; } return ttype; } @@ -976,6 +1026,10 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const tensor_storage.is_f8_e4m3 = true; // f8 -> f16 GGML_ASSERT(tensor_storage.nbytes() == tensor_data_size * 2); + } else if (dtype == "F8_E5M2") { + tensor_storage.is_f8_e5m2 = true; + // f8 -> f16 + GGML_ASSERT(tensor_storage.nbytes() == tensor_data_size * 2); } else { GGML_ASSERT(tensor_storage.nbytes() == tensor_data_size); } @@ -1644,6 +1698,9 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend } else if (tensor_storage.is_f8_e4m3) { // inplace op f8_e4m3_to_f16_vec((uint8_t*)dst_tensor->data, (uint16_t*)dst_tensor->data, tensor_storage.nelements()); + } else if (tensor_storage.is_f8_e5m2) { + // inplace op + f8_e5m2_to_f16_vec((uint8_t*)dst_tensor->data, (uint16_t*)dst_tensor->data, tensor_storage.nelements()); } } else { read_buffer.resize(tensor_storage.nbytes()); @@ -1655,6 +1712,9 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend } else if (tensor_storage.is_f8_e4m3) { // inplace op f8_e4m3_to_f16_vec((uint8_t*)read_buffer.data(), (uint16_t*)read_buffer.data(), tensor_storage.nelements()); + } else if (tensor_storage.is_f8_e5m2) { + // inplace op + f8_e5m2_to_f16_vec((uint8_t*)read_buffer.data(), (uint16_t*)read_buffer.data(), tensor_storage.nelements()); } convert_tensor((void*)read_buffer.data(), tensor_storage.type, dst_tensor->data, @@ -1670,6 +1730,9 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend } else if (tensor_storage.is_f8_e4m3) { // inplace op f8_e4m3_to_f16_vec((uint8_t*)read_buffer.data(), (uint16_t*)read_buffer.data(), tensor_storage.nelements()); + } else if (tensor_storage.is_f8_e5m2) { + // inplace op + f8_e5m2_to_f16_vec((uint8_t*)read_buffer.data(), (uint16_t*)read_buffer.data(), tensor_storage.nelements()); } if (tensor_storage.type == dst_tensor->type) { diff --git a/model.h b/model.h index 8a1ab4149..924d69786 100644 --- a/model.h +++ b/model.h @@ -36,6 +36,7 @@ struct TensorStorage { ggml_type type = GGML_TYPE_F32; bool is_bf16 = false; bool is_f8_e4m3 = false; + bool is_f8_e5m2 = false; int64_t ne[SD_MAX_DIMS] = {1, 1, 1, 1, 1}; int n_dims = 0; @@ -65,7 +66,7 @@ struct TensorStorage { } int64_t nbytes_to_read() const { - if (is_bf16 || is_f8_e4m3) { + if (is_bf16 || is_f8_e4m3 || is_f8_e5m2) { return nbytes() / 2; } else { return nbytes(); @@ -115,6 +116,8 @@ struct TensorStorage { type_name = "bf16"; } else if (is_f8_e4m3) { type_name = "f8_e4m3"; + } else if (is_f8_e5m2) { + type_name = "f8_e5m2"; } ss << name << " | " << type_name << " | "; ss << n_dims << " ["; From 8c7719fe9ae7d201b7aa32958e3fdd36905d3545 Mon Sep 17 00:00:00 2001 From: Plamen Minev Date: Sat, 23 Nov 2024 05:46:00 +0200 Subject: [PATCH 006/143] fix: typo in clip-g encoder arg (#472) --- examples/cli/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 1f33547ee..869b99110 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -183,7 +183,7 @@ void print_usage(int argc, const char* argv[]) { printf(" -m, --model [MODEL] path to full model\n"); printf(" --diffusion-model path to the standalone diffusion model\n"); printf(" --clip_l path to the clip-l text encoder\n"); - printf(" --clip_g path to the clip-l text encoder\n"); + printf(" --clip_g path to the clip-g text encoder\n"); printf(" --t5xxl path to the the t5xxl text encoder\n"); printf(" --vae [VAE] path to vae\n"); printf(" --taesd [TAESD_PATH] path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)\n"); From b99cbfe4dc095bd0a462439ba02f70328f0f793a Mon Sep 17 00:00:00 2001 From: Flavio Bizzarri <33379291+newfla@users.noreply.github.com> Date: Sat, 23 Nov 2024 04:46:50 +0100 Subject: [PATCH 007/143] docs: update README.md (#452) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 60f718191..62b93ff52 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,7 @@ These projects wrap `stable-diffusion.cpp` for easier use in other languages/fra * Golang: [seasonjs/stable-diffusion](https://github.com/seasonjs/stable-diffusion) * C#: [DarthAffe/StableDiffusion.NET](https://github.com/DarthAffe/StableDiffusion.NET) +* Rust: [newfla/diffusion-rs](https://github.com/newfla/diffusion-rs) ## UIs From 2b1bc064776c45a32268c21069a04e91933a1eae Mon Sep 17 00:00:00 2001 From: bssrdf Date: Fri, 22 Nov 2024 22:50:14 -0500 Subject: [PATCH 008/143] feat: add PhotoMaker Version 2 support (#358) * first attempt at updating to photomaker v2 * continue adding photomaker v2 modules * finishing the last few pieces for photomaker v2; id_embeds need to be done by a manual step and pass as an input file * added a name converter for Photomaker V2; build ok * more debugging underway * failing at cuda mat_mul * updated chunk_half to be more efficient; redo feedforward * fixed a bug: carefully using ggml_view_4d to get chunks of a tensor; strides need to be recalculated or set properly; still failing at soft_max cuda op * redo weight calculation and weight*v * fixed a bug now Photomaker V2 kinds of working * add python script for face detection (Photomaker V2 needs) * updated readme for photomaker * fixed a bug causing PMV1 crashing; both V1 and V2 work * fixed clean_input_ids for PMV2 * fixed a double counting bug in tokenize_with_trigger_token * updated photomaker readme * removed some commented code * improved reconstructing class word free prompt * changed reading id_embed to raw binary using existing load tensor function; this is more efficient than using model load and also makes it easier to work with sd server * minor clean up --------- Co-authored-by: bssrdf --- clip.hpp | 27 +- conditioner.hpp | 24 +- docs/photo_maker.md | 24 +- face_detect.py | 88 ++++++ ggml_extend.hpp | 8 +- model.cpp | 42 ++- model.h | 6 + pmid.hpp | 626 +++++++++++++++++++++++++++++++++++++++++-- stable-diffusion.cpp | 37 ++- util.cpp | 18 ++ util.h | 2 +- 11 files changed, 845 insertions(+), 57 deletions(-) create mode 100644 face_detect.py diff --git a/clip.hpp b/clip.hpp index e0d846aa8..7c2705873 100644 --- a/clip.hpp +++ b/clip.hpp @@ -343,6 +343,14 @@ class CLIPTokenizer { } } + std::string clean_up_tokenization(std::string &text){ + + std::regex pattern(R"( ,)"); + // Replace " ," with "," + std::string result = std::regex_replace(text, pattern, ","); + return result; + } + std::string decode(const std::vector& tokens) { std::string text = ""; for (int t : tokens) { @@ -351,8 +359,12 @@ class CLIPTokenizer { std::u32string ts = decoder[t]; // printf("%d, %s \n", t, utf32_to_utf8(ts).c_str()); std::string s = utf32_to_utf8(ts); - if (s.length() >= 4 && ends_with(s, "")) { - text += " " + s.replace(s.length() - 4, s.length() - 1, ""); + if (s.length() >= 4 ){ + if(ends_with(s, "")) { + text += s.replace(s.length() - 4, s.length() - 1, "") + " "; + }else{ + text += s; + } } else { text += " " + s; } @@ -364,6 +376,7 @@ class CLIPTokenizer { // std::string s((char *)bytes.data()); // std::string s = ""; + text = clean_up_tokenization(text); return trim(text); } @@ -755,7 +768,8 @@ class CLIPVisionModel : public GGMLBlock { blocks["post_layernorm"] = std::shared_ptr(new LayerNorm(hidden_size)); } - struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* pixel_values, bool return_pooled = true) { + struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* pixel_values, + bool return_pooled = true) { // pixel_values: [N, num_channels, image_size, image_size] auto embeddings = std::dynamic_pointer_cast(blocks["embeddings"]); auto pre_layernorm = std::dynamic_pointer_cast(blocks["pre_layernorm"]); @@ -765,14 +779,17 @@ class CLIPVisionModel : public GGMLBlock { auto x = embeddings->forward(ctx, pixel_values); // [N, num_positions, embed_dim] x = pre_layernorm->forward(ctx, x); x = encoder->forward(ctx, x, -1, false); + // print_ggml_tensor(x, true, "ClipVisionModel x: "); + auto last_hidden_state = x; x = post_layernorm->forward(ctx, x); // [N, n_token, hidden_size] - GGML_ASSERT(x->ne[3] == 1); + GGML_ASSERT(x->ne[3] == 1); if (return_pooled) { ggml_tensor* pooled = ggml_cont(ctx, ggml_view_2d(ctx, x, x->ne[0], x->ne[2], x->nb[2], 0)); return pooled; // [N, hidden_size] } else { - return x; // [N, n_token, hidden_size] + // return x; // [N, n_token, hidden_size] + return last_hidden_state; // [N, n_token, hidden_size] } } }; diff --git a/conditioner.hpp b/conditioner.hpp index ea02d377f..065f352ec 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -4,6 +4,7 @@ #include "clip.hpp" #include "t5.hpp" + struct SDCondition { struct ggml_tensor* c_crossattn = NULL; // aka context struct ggml_tensor* c_vector = NULL; // aka y @@ -44,6 +45,7 @@ struct Conditioner { // Ref: https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cad87bf4e3e0b0a759afa94e933527c3123d59bc/modules/sd_hijack_clip.py#L283 struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { SDVersion version = VERSION_SD1; + PMVersion pm_version = VERSION_1; CLIPTokenizer tokenizer; ggml_type wtype; std::shared_ptr text_model; @@ -59,8 +61,9 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { ggml_type wtype, const std::string& embd_dir, SDVersion version = VERSION_SD1, + PMVersion pv = VERSION_1, int clip_skip = -1) - : version(version), tokenizer(version == VERSION_SD2 ? 0 : 49407), embd_dir(embd_dir), wtype(wtype) { + : version(version), pm_version(pv), tokenizer(version == VERSION_SD2 ? 0 : 49407), embd_dir(embd_dir), wtype(wtype) { if (clip_skip <= 0) { clip_skip = 1; if (version == VERSION_SD2 || version == VERSION_SDXL) { @@ -159,7 +162,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { tokenize_with_trigger_token(std::string text, int num_input_imgs, int32_t image_token, - bool padding = false) { + bool padding = false){ return tokenize_with_trigger_token(text, num_input_imgs, image_token, text_model->model.n_token, padding); } @@ -268,7 +271,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { std::vector clean_input_ids_tmp; for (uint32_t i = 0; i < class_token_index[0]; i++) clean_input_ids_tmp.push_back(clean_input_ids[i]); - for (uint32_t i = 0; i < num_input_imgs; i++) + for (uint32_t i = 0; i < (pm_version == VERSION_2 ? 2*num_input_imgs: num_input_imgs); i++) clean_input_ids_tmp.push_back(class_token); for (uint32_t i = class_token_index[0] + 1; i < clean_input_ids.size(); i++) clean_input_ids_tmp.push_back(clean_input_ids[i]); @@ -279,13 +282,16 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { tokens.insert(tokens.end(), clean_input_ids.begin(), clean_input_ids.end()); weights.insert(weights.end(), clean_input_ids.size(), curr_weight); } - tokens.insert(tokens.begin(), tokenizer.BOS_TOKEN_ID); - weights.insert(weights.begin(), 1.0); + // BUG!! double couting, pad_tokens will add BOS at the beginning + // tokens.insert(tokens.begin(), tokenizer.BOS_TOKEN_ID); + // weights.insert(weights.begin(), 1.0); tokenizer.pad_tokens(tokens, weights, max_length, padding); - + int offset = pm_version == VERSION_2 ? 2*num_input_imgs: num_input_imgs; for (uint32_t i = 0; i < tokens.size(); i++) { - if (class_idx + 1 <= i && i < class_idx + 1 + num_input_imgs) + // if (class_idx + 1 <= i && i < class_idx + 1 + 2*num_input_imgs) // photomaker V2 has num_tokens(=2)*num_input_imgs + if (class_idx + 1 <= i && i < class_idx + 1 + offset) // photomaker V2 has num_tokens(=2)*num_input_imgs + // hardcode for now class_token_mask.push_back(true); else class_token_mask.push_back(false); @@ -530,7 +536,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { int height, int num_input_imgs, int adm_in_channels = -1, - bool force_zero_embeddings = false) { + bool force_zero_embeddings = false){ auto image_tokens = convert_token_to_id(trigger_word); // if(image_tokens.size() == 1){ // printf(" image token id is: %d \n", image_tokens[0]); @@ -958,7 +964,7 @@ struct SD3CLIPEmbedder : public Conditioner { int height, int num_input_imgs, int adm_in_channels = -1, - bool force_zero_embeddings = false) { + bool force_zero_embeddings = false){ GGML_ASSERT(0 && "Not implemented yet!"); } diff --git a/docs/photo_maker.md b/docs/photo_maker.md index b69ad97d9..8305a33bd 100644 --- a/docs/photo_maker.md +++ b/docs/photo_maker.md @@ -29,4 +29,26 @@ Example: ```bash bin/sd -m ../models/sdxlUnstableDiffusers_v11.safetensors --vae ../models/sdxl_vae.safetensors --stacked-id-embd-dir ../models/photomaker-v1.safetensors --input-id-images-dir ../assets/photomaker_examples/scarletthead_woman -p "a girl img, retro futurism, retro game art style but extremely beautiful, intricate details, masterpiece, best quality, space-themed, cosmic, celestial, stars, galaxies, nebulas, planets, science fiction, highly detailed" -n "realistic, photo-realistic, worst quality, greyscale, bad anatomy, bad hands, error, text" --cfg-scale 5.0 --sampling-method euler -H 1024 -W 1024 --style-ratio 10 --vae-on-cpu -o output.png -``` \ No newline at end of file +``` + +## PhotoMaker Version 2 + +[PhotoMaker Version 2 (PMV2)](https://github.com/TencentARC/PhotoMaker/blob/main/README_pmv2.md) has some key improvements. Unfortunately it has a very heavy dependency which makes running it a bit involved in ```SD.cpp```. + +Running PMV2 is now a two-step process: + +- Run a python script ```face_detect.py``` to obtain **id_embeds** for the given input images +``` +python face_detect.py input_image_dir +``` +An ```id_embeds.safetensors``` file will be generated in ```input_images_dir``` + +**Note: this step is only needed to run once; the same ```id_embeds``` can be reused** + +- Run the same command as in version 1 but replacing ```photomaker-v1.safetensors``` with ```photomaker-v2.safetensors```. + + You can download ```photomaker-v2.safetensors``` from [here](https://huggingface.co/bssrdf/PhotoMakerV2) + +- All the command line parameters from Version 1 remain the same for Version 2 + + diff --git a/face_detect.py b/face_detect.py new file mode 100644 index 000000000..7131af31f --- /dev/null +++ b/face_detect.py @@ -0,0 +1,88 @@ +import os +import sys + +import numpy as np +import torch +from diffusers.utils import load_image +# pip install insightface==0.7.3 +from insightface.app import FaceAnalysis +from insightface.data import get_image as ins_get_image +from safetensors.torch import save_file + +### +# https://github.com/cubiq/ComfyUI_IPAdapter_plus/issues/165#issue-2055829543 +### +class FaceAnalysis2(FaceAnalysis): + # NOTE: allows setting det_size for each detection call. + # the model allows it but the wrapping code from insightface + # doesn't show it, and people end up loading duplicate models + # for different sizes where there is absolutely no need to + def get(self, img, max_num=0, det_size=(640, 640)): + if det_size is not None: + self.det_model.input_size = det_size + + return super().get(img, max_num) + +def analyze_faces(face_analysis: FaceAnalysis, img_data: np.ndarray, det_size=(640, 640)): + # NOTE: try detect faces, if no faces detected, lower det_size until it does + detection_sizes = [None] + [(size, size) for size in range(640, 256, -64)] + [(256, 256)] + + for size in detection_sizes: + faces = face_analysis.get(img_data, det_size=size) + if len(faces) > 0: + return faces + + return [] + +if __name__ == "__main__": + #face_detector = FaceAnalysis2(providers=['CUDAExecutionProvider'], allowed_modules=['detection', 'recognition']) + face_detector = FaceAnalysis2(providers=['CPUExecutionProvider'], allowed_modules=['detection', 'recognition']) + face_detector.prepare(ctx_id=0, det_size=(640, 640)) + #input_folder_name = './scarletthead_woman' + input_folder_name = sys.argv[1] + image_basename_list = os.listdir(input_folder_name) + image_path_list = sorted([os.path.join(input_folder_name, basename) for basename in image_basename_list]) + + input_id_images = [] + for image_path in image_path_list: + input_id_images.append(load_image(image_path)) + + id_embed_list = [] + + for img in input_id_images: + img = np.array(img) + img = img[:, :, ::-1] + faces = analyze_faces(face_detector, img) + if len(faces) > 0: + id_embed_list.append(torch.from_numpy((faces[0]['embedding']))) + + if len(id_embed_list) == 0: + raise ValueError(f"No face detected in input image pool") + + id_embeds = torch.stack(id_embed_list) + + # for r in id_embeds: + # print(r) + # #torch.save(id_embeds, input_folder_name+'/id_embeds.pt'); + # weights = dict() + # weights["id_embeds"] = id_embeds + # save_file(weights, input_folder_name+'/id_embeds.safetensors') + + binary_data = id_embeds.numpy().tobytes() + two = 4 + zero = 0 + one = 1 + tensor_name = "id_embeds" +# Write binary data to a file + with open(input_folder_name+'/id_embeds.bin', "wb") as f: + f.write(two.to_bytes(4, byteorder='little')) + f.write((len(tensor_name)).to_bytes(4, byteorder='little')) + f.write(zero.to_bytes(4, byteorder='little')) + f.write((id_embeds.shape[1]).to_bytes(4, byteorder='little')) + f.write((id_embeds.shape[0]).to_bytes(4, byteorder='little')) + f.write(one.to_bytes(4, byteorder='little')) + f.write(one.to_bytes(4, byteorder='little')) + f.write(tensor_name.encode('ascii')) + f.write(binary_data) + + \ No newline at end of file diff --git a/ggml_extend.hpp b/ggml_extend.hpp index e50137d5e..8dea410ef 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -1047,6 +1047,11 @@ struct GGMLRunner { params_buffer_size / (1024.0 * 1024.0), ggml_backend_is_cpu(backend) ? "RAM" : "VRAM", num_tensors); + // printf("%s params backend buffer size = % 6.2f MB(%s) (%i tensors)\n", + // get_desc().c_str(), + // params_buffer_size / (1024.0 * 1024.0), + // ggml_backend_is_cpu(backend) ? "RAM" : "VRAM", + // num_tensors); return true; } @@ -1216,7 +1221,8 @@ class Linear : public UnaryBlock { params["weight"] = ggml_new_tensor_2d(ctx, wtype, in_features, out_features); if (bias) { params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_features); - } + } + } public: diff --git a/model.cpp b/model.cpp index ae1c097a1..5f1e6e160 100644 --- a/model.cpp +++ b/model.cpp @@ -146,6 +146,33 @@ std::unordered_map vae_decoder_name_map = { {"first_stage_model.decoder.mid.attn_1.to_v.weight", "first_stage_model.decoder.mid.attn_1.v.weight"}, }; +std::unordered_map pmid_v2_name_map = { + {"pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.fc1.weight"}, + {"pmid.qformer_perceiver.perceiver_resampler.layers.0.1.3.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.fc2.weight"}, + {"pmid.qformer_perceiver.perceiver_resampler.layers.1.1.1.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.1.1.1.fc1.weight"}, + {"pmid.qformer_perceiver.perceiver_resampler.layers.1.1.3.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.1.1.1.fc2.weight"}, + {"pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.fc1.weight"}, + {"pmid.qformer_perceiver.perceiver_resampler.layers.2.1.3.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.fc2.weight"}, + {"pmid.qformer_perceiver.perceiver_resampler.layers.3.1.1.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.3.1.1.fc1.weight"}, + {"pmid.qformer_perceiver.perceiver_resampler.layers.3.1.3.weight", + "pmid.qformer_perceiver.perceiver_resampler.layers.3.1.1.fc2.weight"}, + {"pmid.qformer_perceiver.token_proj.0.bias", + "pmid.qformer_perceiver.token_proj.fc1.bias"}, + {"pmid.qformer_perceiver.token_proj.2.bias", + "pmid.qformer_perceiver.token_proj.fc2.bias"}, + {"pmid.qformer_perceiver.token_proj.0.weight", + "pmid.qformer_perceiver.token_proj.fc1.weight"}, + {"pmid.qformer_perceiver.token_proj.2.weight", + "pmid.qformer_perceiver.token_proj.fc2.weight"}, +}; + std::string convert_open_clip_to_hf_clip(const std::string& name) { std::string new_name = name; std::string prefix; @@ -212,6 +239,13 @@ std::string convert_vae_decoder_name(const std::string& name) { return name; } +std::string convert_pmid_v2_name(const std::string& name) { + if (pmid_v2_name_map.find(name) != pmid_v2_name_map.end()) { + return pmid_v2_name_map[name]; + } + return name; +} + /* If not a SDXL LoRA the unet" prefix will have already been replaced by this * point and "te2" and "te1" don't seem to appear in non-SDXL only "te_" */ std::string convert_sdxl_lora_name(std::string tensor_name) { @@ -443,6 +477,8 @@ std::string convert_tensor_name(std::string name) { new_name = convert_open_clip_to_hf_clip(name); } else if (starts_with(name, "first_stage_model.decoder")) { new_name = convert_vae_decoder_name(name); + } else if (starts_with(name, "pmid.qformer_perceiver")) { + new_name = convert_pmid_v2_name(name); } else if (starts_with(name, "control_model.")) { // for controlnet pth models size_t pos = name.find('.'); if (pos != std::string::npos) { @@ -1015,7 +1051,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const } TensorStorage tensor_storage(prefix + name, type, ne, n_dims, file_index, ST_HEADER_SIZE_LEN + header_size_ + begin); - tensor_storage.reverse_ne(); + tensor_storage.reverse_ne(); size_t tensor_data_size = end - begin; @@ -1362,7 +1398,7 @@ bool ModelLoader::parse_data_pkl(uint8_t* buffer, reader.tensor_storage.reverse_ne(); reader.tensor_storage.file_index = file_index; // if(strcmp(prefix.c_str(), "scarlett") == 0) - // printf(" got tensor %s \n ", reader.tensor_storage.name.c_str()); + // printf(" ZIP got tensor %s \n ", reader.tensor_storage.name.c_str()); reader.tensor_storage.name = prefix + reader.tensor_storage.name; tensor_storages.push_back(reader.tensor_storage); // LOG_DEBUG("%s", reader.tensor_storage.name.c_str()); @@ -1398,7 +1434,9 @@ bool ModelLoader::init_from_ckpt_file(const std::string& file_path, const std::s std::string name = zip_entry_name(zip); size_t pos = name.find("data.pkl"); if (pos != std::string::npos) { + std::string dir = name.substr(0, pos); + printf("ZIP %d, name = %s, dir = %s \n", i, name.c_str(), dir.c_str()); void* pkl_data = NULL; size_t pkl_size; zip_entry_read(zip, &pkl_data, &pkl_size); diff --git a/model.h b/model.h index 924d69786..77841e82c 100644 --- a/model.h +++ b/model.h @@ -31,6 +31,11 @@ enum SDVersion { VERSION_COUNT, }; +enum PMVersion { + VERSION_1, + VERSION_2, +}; + struct TensorStorage { std::string name; ggml_type type = GGML_TYPE_F32; @@ -162,6 +167,7 @@ class ModelLoader { bool load_tensors(std::map& tensors, ggml_backend_t backend, std::set ignore_tensors = {}); + bool save_to_gguf_file(const std::string& file_path, ggml_type type); bool tensor_should_be_converted(const TensorStorage& tensor_storage, ggml_type type); int64_t get_params_mem_size(ggml_backend_t backend, ggml_type type = GGML_TYPE_COUNT); diff --git a/pmid.hpp b/pmid.hpp index 381050fef..bde03cc92 100644 --- a/pmid.hpp +++ b/pmid.hpp @@ -6,6 +6,7 @@ #include "clip.hpp" #include "lora.hpp" + struct FuseBlock : public GGMLBlock { // network hparams int in_dim; @@ -42,6 +43,383 @@ struct FuseBlock : public GGMLBlock { } }; +/* +class QFormerPerceiver(nn.Module): + def __init__(self, id_embeddings_dim, cross_attention_dim, num_tokens, embedding_dim=1024, use_residual=True, ratio=4): + super().__init__() + + self.num_tokens = num_tokens + self.cross_attention_dim = cross_attention_dim + self.use_residual = use_residual + print(cross_attention_dim*num_tokens) + self.token_proj = nn.Sequential( + nn.Linear(id_embeddings_dim, id_embeddings_dim*ratio), + nn.GELU(), + nn.Linear(id_embeddings_dim*ratio, cross_attention_dim*num_tokens), + ) + self.token_norm = nn.LayerNorm(cross_attention_dim) + self.perceiver_resampler = FacePerceiverResampler( + dim=cross_attention_dim, + depth=4, + dim_head=128, + heads=cross_attention_dim // 128, + embedding_dim=embedding_dim, + output_dim=cross_attention_dim, + ff_mult=4, + ) + + def forward(self, x, last_hidden_state): + x = self.token_proj(x) + x = x.reshape(-1, self.num_tokens, self.cross_attention_dim) + x = self.token_norm(x) # cls token + out = self.perceiver_resampler(x, last_hidden_state) # retrieve from patch tokens + if self.use_residual: # TODO: if use_residual is not true + out = x + 1.0 * out + return out +*/ + + +struct PMFeedForward : public GGMLBlock { + // network hparams + int dim; + +public: + PMFeedForward(int d, int multi=4) + : dim(d) { + int inner_dim = dim * multi; + blocks["0"] = std::shared_ptr(new LayerNorm(dim)); + blocks["1"] = std::shared_ptr(new Mlp(dim, inner_dim, dim, false)); + } + + struct ggml_tensor* forward(struct ggml_context* ctx, + struct ggml_tensor* x){ + + auto norm = std::dynamic_pointer_cast(blocks["0"]); + auto ff = std::dynamic_pointer_cast(blocks["1"]); + + x = norm->forward(ctx, x); + x = ff->forward(ctx, x); + return x; + } + +}; + +struct PerceiverAttention : public GGMLBlock { + // network hparams + float scale; // = dim_head**-0.5 + int dim_head; // = dim_head + int heads; // = heads +public: + PerceiverAttention(int dim, int dim_h=64, int h=8) + : scale(powf(dim_h, -0.5)), dim_head(dim_h), heads(h) { + + int inner_dim = dim_head * heads; + blocks["norm1"] = std::shared_ptr(new LayerNorm(dim)); + blocks["norm2"] = std::shared_ptr(new LayerNorm(dim)); + blocks["to_q"] = std::shared_ptr(new Linear(dim, inner_dim, false)); + blocks["to_kv"] = std::shared_ptr(new Linear(dim, inner_dim*2, false)); + blocks["to_out"] = std::shared_ptr(new Linear(inner_dim, dim, false)); + } + + struct ggml_tensor* reshape_tensor(struct ggml_context* ctx, + struct ggml_tensor* x, + int heads) { + int64_t ne[4]; + for(int i = 0; i < 4; ++i) + ne[i] = x->ne[i]; + // print_ggml_tensor(x, true, "PerceiverAttention reshape x 0: "); + // printf("heads = %d \n", heads); + // x = ggml_view_4d(ctx, x, x->ne[0], x->ne[1], heads, x->ne[2]/heads, + // x->nb[1], x->nb[2], x->nb[3], 0); + x = ggml_reshape_4d(ctx, x, x->ne[0]/heads, heads, x->ne[1], x->ne[2]); + // x = ggml_view_4d(ctx, x, x->ne[0]/heads, heads, x->ne[1], x->ne[2], + // x->nb[1], x->nb[2], x->nb[3], 0); + // x = ggml_cont(ctx, x); + x = ggml_cont(ctx, ggml_permute(ctx, x, 0, 2, 1, 3)); + // print_ggml_tensor(x, true, "PerceiverAttention reshape x 1: "); + // x = ggml_reshape_4d(ctx, x, ne[0], heads, ne[1], ne[2]/heads); + return x; + } + + std::vector chunk_half(struct ggml_context* ctx, + struct ggml_tensor* x){ + + auto tlo = ggml_view_4d(ctx, x, x->ne[0]/2, x->ne[1], x->ne[2], x->ne[3], x->nb[1], x->nb[2], x->nb[3], 0); + auto tli = ggml_view_4d(ctx, x, x->ne[0]/2, x->ne[1], x->ne[2], x->ne[3], x->nb[1], x->nb[2], x->nb[3], x->nb[0]*x->ne[0]/2); + return {ggml_cont(ctx, tlo), + ggml_cont(ctx, tli)}; + + } + + struct ggml_tensor* forward(struct ggml_context* ctx, + struct ggml_tensor* x, + struct ggml_tensor* latents){ + + // x (torch.Tensor): image features + // shape (b, n1, D) + // latent (torch.Tensor): latent features + // shape (b, n2, D) + int64_t ne[4]; + for(int i = 0; i < 4; ++i) + ne[i] = latents->ne[i]; + + auto norm1 = std::dynamic_pointer_cast(blocks["norm1"]); + auto norm2 = std::dynamic_pointer_cast(blocks["norm2"]); + x = norm1->forward(ctx, x); + latents = norm2->forward(ctx, latents); + auto to_q = std::dynamic_pointer_cast(blocks["to_q"]); + auto q = to_q->forward(ctx, latents); + + auto kv_input = ggml_concat(ctx, x, latents, 1); + auto to_kv = std::dynamic_pointer_cast(blocks["to_kv"]); + auto kv = to_kv->forward(ctx, kv_input); + auto k = ggml_view_4d(ctx, kv, kv->ne[0]/2, kv->ne[1], kv->ne[2], kv->ne[3], kv->nb[1]/2, kv->nb[2]/2, kv->nb[3]/2, 0); + auto v = ggml_view_4d(ctx, kv, kv->ne[0]/2, kv->ne[1], kv->ne[2], kv->ne[3], kv->nb[1]/2, kv->nb[2]/2, kv->nb[3]/2, kv->nb[0]*(kv->ne[0]/2)); + k = ggml_cont(ctx, k); + v = ggml_cont(ctx, v); + q = reshape_tensor(ctx, q, heads); + k = reshape_tensor(ctx, k, heads); + v = reshape_tensor(ctx, v, heads); + scale = 1.f / sqrt(sqrt((float)dim_head)); + k = ggml_scale_inplace(ctx, k, scale); + q = ggml_scale_inplace(ctx, q, scale); + // auto weight = ggml_mul_mat(ctx, q, k); + auto weight = ggml_mul_mat(ctx, k, q); // NOTE order of mul is opposite to pytorch + + // GGML's softmax() is equivalent to pytorch's softmax(x, dim=-1) + // in this case, dimension along which Softmax will be computed is the last dim + // in torch and the first dim in GGML, consistent with the convention that pytorch's + // last dimension (varying most rapidly) corresponds to GGML's first (varying most rapidly). + // weight = ggml_soft_max(ctx, weight); + weight = ggml_soft_max_inplace(ctx, weight); + v = ggml_cont(ctx, ggml_transpose(ctx, v)); + // auto out = ggml_mul_mat(ctx, weight, v); + auto out = ggml_mul_mat(ctx, v, weight); // NOTE order of mul is opposite to pytorch + out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); + out = ggml_reshape_3d(ctx, out, ne[0], ne[1], ggml_nelements(out)/(ne[0]*ne[1])); + auto to_out = std::dynamic_pointer_cast(blocks["to_out"]); + out = to_out->forward(ctx, out); + return out; + } +}; + +struct FacePerceiverResampler : public GGMLBlock { + // network hparams + int depth; +public: + FacePerceiverResampler( int dim=768, + int d=4, + int dim_head=64, + int heads=16, + int embedding_dim=1280, + int output_dim=768, + int ff_mult=4) + : depth(d) { + blocks["proj_in"] = std::shared_ptr(new Linear(embedding_dim, dim, true)); + blocks["proj_out"] = std::shared_ptr(new Linear(dim, output_dim, true)); + blocks["norm_out"] = std::shared_ptr(new LayerNorm(output_dim)); + + for (int i = 0; i < depth; i++) { + std::string name = "layers." + std::to_string(i) + ".0"; + blocks[name] = std::shared_ptr(new PerceiverAttention(dim, dim_head, heads)); + name = "layers." + std::to_string(i) + ".1"; + blocks[name] = std::shared_ptr(new PMFeedForward(dim, ff_mult)); + } + } + + struct ggml_tensor* forward(struct ggml_context* ctx, + struct ggml_tensor* latents, + struct ggml_tensor* x){ + // x: [N, channels, h, w] + auto proj_in = std::dynamic_pointer_cast(blocks["proj_in"]); + auto proj_out = std::dynamic_pointer_cast(blocks["proj_out"]); + auto norm_out = std::dynamic_pointer_cast(blocks["norm_out"]); + + x = proj_in->forward(ctx, x); + for (int i = 0; i < depth; i++) { + std::string name = "layers." + std::to_string(i) + ".0"; + auto attn = std::dynamic_pointer_cast(blocks[name]); + name = "layers." + std::to_string(i) + ".1"; + auto ff = std::dynamic_pointer_cast(blocks[name]); + auto t = attn->forward(ctx, x, latents); + latents = ggml_add(ctx, t, latents); + t = ff->forward(ctx, latents); + latents = ggml_add(ctx, t, latents); + } + latents = proj_out->forward(ctx, latents); + latents = norm_out->forward(ctx, latents); + return latents; + } +}; + +struct QFormerPerceiver : public GGMLBlock { + // network hparams + int num_tokens; + int cross_attention_dim; + bool use_residul; + + +public: + QFormerPerceiver(int id_embeddings_dim, int cross_attention_d, int num_t, int embedding_dim=1024, + bool use_r=true, int ratio=4) + : cross_attention_dim(cross_attention_d), num_tokens(num_t), use_residul(use_r) { + blocks["token_proj"] = std::shared_ptr(new Mlp(id_embeddings_dim, + id_embeddings_dim*ratio, + cross_attention_dim*num_tokens, + true)); + blocks["token_norm"] = std::shared_ptr(new LayerNorm(cross_attention_d)); + blocks["perceiver_resampler"] = std::shared_ptr(new FacePerceiverResampler( + cross_attention_dim, + 4, + 128, + cross_attention_dim / 128, + embedding_dim, + cross_attention_dim, + 4)); + } + + /* + def forward(self, x, last_hidden_state): + x = self.token_proj(x) + x = x.reshape(-1, self.num_tokens, self.cross_attention_dim) + x = self.token_norm(x) # cls token + out = self.perceiver_resampler(x, last_hidden_state) # retrieve from patch tokens + if self.use_residual: # TODO: if use_residual is not true + out = x + 1.0 * out + return out + */ + + struct ggml_tensor* forward(struct ggml_context* ctx, + struct ggml_tensor* x, + struct ggml_tensor* last_hidden_state){ + // x: [N, channels, h, w] + auto token_proj = std::dynamic_pointer_cast(blocks["token_proj"]); + auto token_norm = std::dynamic_pointer_cast(blocks["token_norm"]); + auto perceiver_resampler = std::dynamic_pointer_cast(blocks["perceiver_resampler"]); + + x = token_proj->forward(ctx, x); + int64_t nel = ggml_nelements(x); + x = ggml_reshape_3d(ctx, x, cross_attention_dim, num_tokens, nel/(cross_attention_dim*num_tokens)); + x = token_norm->forward(ctx, x); + struct ggml_tensor* out = perceiver_resampler->forward(ctx, x, last_hidden_state); + if(use_residul) + out = ggml_add(ctx, x, out); + return out; + } +}; + +/* +class FacePerceiverResampler(torch.nn.Module): + def __init__( + self, + *, + dim=768, + depth=4, + dim_head=64, + heads=16, + embedding_dim=1280, + output_dim=768, + ff_mult=4, + ): + super().__init__() + + self.proj_in = torch.nn.Linear(embedding_dim, dim) + self.proj_out = torch.nn.Linear(dim, output_dim) + self.norm_out = torch.nn.LayerNorm(output_dim) + self.layers = torch.nn.ModuleList([]) + for _ in range(depth): + self.layers.append( + torch.nn.ModuleList( + [ + PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads), + FeedForward(dim=dim, mult=ff_mult), + ] + ) + ) + + def forward(self, latents, x): + x = self.proj_in(x) + for attn, ff in self.layers: + latents = attn(x, latents) + latents + latents = ff(latents) + latents + latents = self.proj_out(latents) + return self.norm_out(latents) +*/ + + + +/* + +def FeedForward(dim, mult=4): + inner_dim = int(dim * mult) + return nn.Sequential( + nn.LayerNorm(dim), + nn.Linear(dim, inner_dim, bias=False), + nn.GELU(), + nn.Linear(inner_dim, dim, bias=False), + ) + +def reshape_tensor(x, heads): + bs, length, width = x.shape + # (bs, length, width) --> (bs, length, n_heads, dim_per_head) + x = x.view(bs, length, heads, -1) + # (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head) + x = x.transpose(1, 2) + # (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head) + x = x.reshape(bs, heads, length, -1) + return x + +class PerceiverAttention(nn.Module): + def __init__(self, *, dim, dim_head=64, heads=8): + super().__init__() + self.scale = dim_head**-0.5 + self.dim_head = dim_head + self.heads = heads + inner_dim = dim_head * heads + + self.norm1 = nn.LayerNorm(dim) + self.norm2 = nn.LayerNorm(dim) + + self.to_q = nn.Linear(dim, inner_dim, bias=False) + self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False) + self.to_out = nn.Linear(inner_dim, dim, bias=False) + + def forward(self, x, latents): + """ + Args: + x (torch.Tensor): image features + shape (b, n1, D) + latent (torch.Tensor): latent features + shape (b, n2, D) + """ + x = self.norm1(x) + latents = self.norm2(latents) + + b, l, _ = latents.shape + + q = self.to_q(latents) + kv_input = torch.cat((x, latents), dim=-2) + k, v = self.to_kv(kv_input).chunk(2, dim=-1) + + q = reshape_tensor(q, self.heads) + k = reshape_tensor(k, self.heads) + v = reshape_tensor(v, self.heads) + + # attention + scale = 1 / math.sqrt(math.sqrt(self.dim_head)) + weight = (q * scale) @ (k * scale).transpose(-2, -1) # More stable with f16 than dividing afterwards + weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype) + out = weight @ v + + out = out.permute(0, 2, 1, 3).reshape(b, l, -1) + + return self.to_out(out) + +*/ + + + + struct FuseModule : public GGMLBlock { // network hparams int embed_dim; @@ -61,12 +439,19 @@ struct FuseModule : public GGMLBlock { auto mlp2 = std::dynamic_pointer_cast(blocks["mlp2"]); auto layer_norm = std::dynamic_pointer_cast(blocks["layer_norm"]); - auto prompt_embeds0 = ggml_cont(ctx, ggml_permute(ctx, prompt_embeds, 2, 0, 1, 3)); - auto id_embeds0 = ggml_cont(ctx, ggml_permute(ctx, id_embeds, 2, 0, 1, 3)); - // concat is along dim 2 - auto stacked_id_embeds = ggml_concat(ctx, prompt_embeds0, id_embeds0, 2); - stacked_id_embeds = ggml_cont(ctx, ggml_permute(ctx, stacked_id_embeds, 1, 2, 0, 3)); + // print_ggml_tensor(id_embeds, true, "Fuseblock id_embeds: "); + // print_ggml_tensor(prompt_embeds, true, "Fuseblock prompt_embeds: "); + // auto prompt_embeds0 = ggml_cont(ctx, ggml_permute(ctx, prompt_embeds, 2, 0, 1, 3)); + // auto id_embeds0 = ggml_cont(ctx, ggml_permute(ctx, id_embeds, 2, 0, 1, 3)); + // print_ggml_tensor(id_embeds0, true, "Fuseblock id_embeds0: "); + // print_ggml_tensor(prompt_embeds0, true, "Fuseblock prompt_embeds0: "); + // concat is along dim 2 + // auto stacked_id_embeds = ggml_concat(ctx, prompt_embeds0, id_embeds0, 2); + auto stacked_id_embeds = ggml_concat(ctx, prompt_embeds, id_embeds, 0); + // print_ggml_tensor(stacked_id_embeds, true, "Fuseblock stacked_id_embeds 0: "); + // stacked_id_embeds = ggml_cont(ctx, ggml_permute(ctx, stacked_id_embeds, 1, 2, 0, 3)); + // print_ggml_tensor(stacked_id_embeds, true, "Fuseblock stacked_id_embeds 1: "); // stacked_id_embeds = mlp1.forward(ctx, stacked_id_embeds); // stacked_id_embeds = ggml_add(ctx, stacked_id_embeds, prompt_embeds); // stacked_id_embeds = mlp2.forward(ctx, stacked_id_embeds); @@ -77,6 +462,8 @@ struct FuseModule : public GGMLBlock { stacked_id_embeds = mlp2->forward(ctx, stacked_id_embeds); stacked_id_embeds = layer_norm->forward(ctx, stacked_id_embeds); + // print_ggml_tensor(stacked_id_embeds, true, "Fuseblock stacked_id_embeds 1: "); + return stacked_id_embeds; } @@ -98,23 +485,31 @@ struct FuseModule : public GGMLBlock { // print_ggml_tensor(class_tokens_mask_pos, true, "class_tokens_mask_pos"); struct ggml_tensor* image_token_embeds = ggml_get_rows(ctx, prompt_embeds, class_tokens_mask_pos); ggml_set_name(image_token_embeds, "image_token_embeds"); - struct ggml_tensor* stacked_id_embeds = fuse_fn(ctx, image_token_embeds, valid_id_embeds); - - stacked_id_embeds = ggml_cont(ctx, ggml_permute(ctx, stacked_id_embeds, 0, 2, 1, 3)); + valid_id_embeds = ggml_reshape_2d(ctx, valid_id_embeds, valid_id_embeds->ne[0], + ggml_nelements(valid_id_embeds)/valid_id_embeds->ne[0]); + struct ggml_tensor* stacked_id_embeds = fuse_fn(ctx, image_token_embeds, valid_id_embeds); + + // stacked_id_embeds = ggml_cont(ctx, ggml_permute(ctx, stacked_id_embeds, 0, 2, 1, 3)); + // print_ggml_tensor(stacked_id_embeds, true, "AA stacked_id_embeds"); + // print_ggml_tensor(left, true, "AA left"); + // print_ggml_tensor(right, true, "AA right"); if (left && right) { - stacked_id_embeds = ggml_concat(ctx, left, stacked_id_embeds, 2); - stacked_id_embeds = ggml_concat(ctx, stacked_id_embeds, right, 2); + stacked_id_embeds = ggml_concat(ctx, left, stacked_id_embeds, 1); + stacked_id_embeds = ggml_concat(ctx, stacked_id_embeds, right, 1); } else if (left) { - stacked_id_embeds = ggml_concat(ctx, left, stacked_id_embeds, 2); + stacked_id_embeds = ggml_concat(ctx, left, stacked_id_embeds, 1); } else if (right) { - stacked_id_embeds = ggml_concat(ctx, stacked_id_embeds, right, 2); + stacked_id_embeds = ggml_concat(ctx, stacked_id_embeds, right, 1); } - stacked_id_embeds = ggml_cont(ctx, ggml_permute(ctx, stacked_id_embeds, 0, 2, 1, 3)); + // print_ggml_tensor(stacked_id_embeds, true, "BB stacked_id_embeds"); + // stacked_id_embeds = ggml_cont(ctx, ggml_permute(ctx, stacked_id_embeds, 0, 2, 1, 3)); + // print_ggml_tensor(stacked_id_embeds, true, "CC stacked_id_embeds"); class_tokens_mask = ggml_cont(ctx, ggml_transpose(ctx, class_tokens_mask)); class_tokens_mask = ggml_repeat(ctx, class_tokens_mask, prompt_embeds); prompt_embeds = ggml_mul(ctx, prompt_embeds, class_tokens_mask); struct ggml_tensor* updated_prompt_embeds = ggml_add(ctx, prompt_embeds, stacked_id_embeds); ggml_set_name(updated_prompt_embeds, "updated_prompt_embeds"); + // print_ggml_tensor(updated_prompt_embeds, true, "updated_prompt_embeds: "); return updated_prompt_embeds; } }; @@ -159,10 +554,79 @@ struct PhotoMakerIDEncoderBlock : public CLIPVisionModelProjection { } }; +struct PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock : public CLIPVisionModelProjection { + + int cross_attention_dim; + int num_tokens; + + PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock(int id_embeddings_dim=512) + : CLIPVisionModelProjection(OPENAI_CLIP_VIT_L_14), + cross_attention_dim (2048), + num_tokens(2) { + blocks["visual_projection_2"] = std::shared_ptr(new Linear(1024, 1280, false)); + blocks["fuse_module"] = std::shared_ptr(new FuseModule(2048)); + /* + cross_attention_dim = 2048 + # projection + self.num_tokens = 2 + self.cross_attention_dim = cross_attention_dim + self.qformer_perceiver = QFormerPerceiver( + id_embeddings_dim, + cross_attention_dim, + self.num_tokens, + )*/ + blocks["qformer_perceiver"] = std::shared_ptr(new QFormerPerceiver(id_embeddings_dim, + cross_attention_dim, + num_tokens)); + + } + + /* + def forward(self, id_pixel_values, prompt_embeds, class_tokens_mask, id_embeds): + b, num_inputs, c, h, w = id_pixel_values.shape + id_pixel_values = id_pixel_values.view(b * num_inputs, c, h, w) + + last_hidden_state = self.vision_model(id_pixel_values)[0] + id_embeds = id_embeds.view(b * num_inputs, -1) + + id_embeds = self.qformer_perceiver(id_embeds, last_hidden_state) + id_embeds = id_embeds.view(b, num_inputs, self.num_tokens, -1) + updated_prompt_embeds = self.fuse_module(prompt_embeds, id_embeds, class_tokens_mask) + */ + + struct ggml_tensor* forward(struct ggml_context* ctx, + struct ggml_tensor* id_pixel_values, + struct ggml_tensor* prompt_embeds, + struct ggml_tensor* class_tokens_mask, + struct ggml_tensor* class_tokens_mask_pos, + struct ggml_tensor* id_embeds, + struct ggml_tensor* left, + struct ggml_tensor* right) { + // x: [N, channels, h, w] + auto vision_model = std::dynamic_pointer_cast(blocks["vision_model"]); + auto fuse_module = std::dynamic_pointer_cast(blocks["fuse_module"]); + auto qformer_perceiver = std::dynamic_pointer_cast(blocks["qformer_perceiver"]); + + // struct ggml_tensor* last_hidden_state = vision_model->forward(ctx, id_pixel_values); // [N, hidden_size] + struct ggml_tensor* last_hidden_state = vision_model->forward(ctx, id_pixel_values, false); // [N, hidden_size] + id_embeds = qformer_perceiver->forward(ctx, id_embeds, last_hidden_state); + + struct ggml_tensor* updated_prompt_embeds = fuse_module->forward(ctx, + prompt_embeds, + id_embeds, + class_tokens_mask, + class_tokens_mask_pos, + left, right); + return updated_prompt_embeds; + } +}; + struct PhotoMakerIDEncoder : public GGMLRunner { public: SDVersion version = VERSION_SDXL; + PMVersion pm_version = VERSION_1; PhotoMakerIDEncoderBlock id_encoder; + PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock id_encoder2; float style_strength; std::vector ctm; @@ -175,25 +639,41 @@ struct PhotoMakerIDEncoder : public GGMLRunner { std::vector zeros_right; public: - PhotoMakerIDEncoder(ggml_backend_t backend, ggml_type wtype, SDVersion version = VERSION_SDXL, float sty = 20.f) + PhotoMakerIDEncoder(ggml_backend_t backend, ggml_type wtype, SDVersion version = VERSION_SDXL, + PMVersion pm_v = VERSION_1, float sty = 20.f) : GGMLRunner(backend, wtype), version(version), + pm_version(pm_v), style_strength(sty) { - id_encoder.init(params_ctx, wtype); + if(pm_version == VERSION_1){ + id_encoder.init(params_ctx, wtype); + }else if(pm_version == VERSION_2){ + id_encoder2.init(params_ctx, wtype); + } } std::string get_desc() { return "pmid"; } + PMVersion get_version() const{ + return pm_version; + } + + void get_param_tensors(std::map& tensors, const std::string prefix) { - id_encoder.get_param_tensors(tensors, prefix); + if(pm_version == VERSION_1) + id_encoder.get_param_tensors(tensors, prefix); + else if(pm_version == VERSION_2) + id_encoder2.get_param_tensors(tensors, prefix); + } struct ggml_cgraph* build_graph( // struct ggml_allocr* allocr, struct ggml_tensor* id_pixel_values, struct ggml_tensor* prompt_embeds, - std::vector& class_tokens_mask) { + std::vector& class_tokens_mask, + struct ggml_tensor* id_embeds) { ctm.clear(); ctmf16.clear(); ctmpos.clear(); @@ -214,25 +694,32 @@ struct PhotoMakerIDEncoder : public GGMLRunner { struct ggml_tensor* id_pixel_values_d = to_backend(id_pixel_values); struct ggml_tensor* prompt_embeds_d = to_backend(prompt_embeds); + struct ggml_tensor* id_embeds_d = to_backend(id_embeds); struct ggml_tensor* left = NULL; struct ggml_tensor* right = NULL; for (int i = 0; i < class_tokens_mask.size(); i++) { if (class_tokens_mask[i]) { + // printf(" 1,"); ctm.push_back(0.f); // here use 0.f instead of 1.f to make a scale mask ctmf16.push_back(ggml_fp32_to_fp16(0.f)); // here use 0.f instead of 1.f to make a scale mask ctmpos.push_back(i); } else { + // printf(" 0,"); ctm.push_back(1.f); // here use 1.f instead of 0.f to make a scale mask ctmf16.push_back(ggml_fp32_to_fp16(1.f)); // here use 0.f instead of 1.f to make a scale mask } } + // printf("\n"); if (ctmpos[0] > 0) { - left = ggml_new_tensor_3d(ctx0, type, hidden_size, 1, ctmpos[0]); + // left = ggml_new_tensor_3d(ctx0, type, hidden_size, 1, ctmpos[0]); + left = ggml_new_tensor_3d(ctx0, type, hidden_size, ctmpos[0], 1); } if (ctmpos[ctmpos.size() - 1] < seq_length - 1) { + // right = ggml_new_tensor_3d(ctx0, type, + // hidden_size, 1, seq_length - ctmpos[ctmpos.size() - 1] - 1); right = ggml_new_tensor_3d(ctx0, type, - hidden_size, 1, seq_length - ctmpos[ctmpos.size() - 1] - 1); + hidden_size, seq_length - ctmpos[ctmpos.size() - 1] - 1, 1); } struct ggml_tensor* class_tokens_mask_pos = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, ctmpos.size()); @@ -265,12 +752,23 @@ struct PhotoMakerIDEncoder : public GGMLRunner { } } } - struct ggml_tensor* updated_prompt_embeds = id_encoder.forward(ctx0, - id_pixel_values_d, - prompt_embeds_d, - class_tokens_mask_d, - class_tokens_mask_pos, - left, right); + struct ggml_tensor* updated_prompt_embeds = NULL; + if(pm_version == VERSION_1) + updated_prompt_embeds = id_encoder.forward(ctx0, + id_pixel_values_d, + prompt_embeds_d, + class_tokens_mask_d, + class_tokens_mask_pos, + left, right); + else if(pm_version == VERSION_2) + updated_prompt_embeds = id_encoder2.forward(ctx0, + id_pixel_values_d, + prompt_embeds_d, + class_tokens_mask_d, + class_tokens_mask_pos, + id_embeds_d, + left, right); + ggml_build_forward_expand(gf, updated_prompt_embeds); return gf; @@ -279,12 +777,13 @@ struct PhotoMakerIDEncoder : public GGMLRunner { void compute(const int n_threads, struct ggml_tensor* id_pixel_values, struct ggml_tensor* prompt_embeds, + struct ggml_tensor* id_embeds, std::vector& class_tokens_mask, struct ggml_tensor** updated_prompt_embeds, ggml_context* output_ctx) { auto get_graph = [&]() -> struct ggml_cgraph* { // return build_graph(compute_allocr, id_pixel_values, prompt_embeds, class_tokens_mask); - return build_graph(id_pixel_values, prompt_embeds, class_tokens_mask); + return build_graph(id_pixel_values, prompt_embeds, class_tokens_mask, id_embeds); }; // GGMLRunner::compute(get_graph, n_threads, updated_prompt_embeds); @@ -292,4 +791,79 @@ struct PhotoMakerIDEncoder : public GGMLRunner { } }; + +struct PhotoMakerIDEmbed : public GGMLRunner { + + std::map tensors; + std::string file_path; + ModelLoader *model_loader; + bool load_failed = false; + bool applied = false; + + PhotoMakerIDEmbed(ggml_backend_t backend, + ggml_type wtype, + ModelLoader *ml, + const std::string& file_path = "", + const std::string& prefix = "") + : file_path(file_path), GGMLRunner(backend, wtype), + model_loader(ml) { + if (!model_loader->init_from_file(file_path, prefix)) { + load_failed = true; + } + } + + std::string get_desc() { + return "id_embeds"; + } + + bool load_from_file(bool filter_tensor = false) { + LOG_INFO("loading PhotoMaker ID Embeds from '%s'", file_path.c_str()); + + if (load_failed) { + LOG_ERROR("init photomaker id embed from file failed: '%s'", file_path.c_str()); + return false; + } + + bool dry_run = true; + auto on_new_tensor_cb = [&](const TensorStorage& tensor_storage, ggml_tensor** dst_tensor) -> bool { + const std::string& name = tensor_storage.name; + + if (filter_tensor && !contains(name, "pmid.id_embeds")) { + // LOG_INFO("skipping LoRA tesnor '%s'", name.c_str()); + return true; + } + if (dry_run) { + struct ggml_tensor* real = ggml_new_tensor(params_ctx, + tensor_storage.type, + tensor_storage.n_dims, + tensor_storage.ne); + tensors[name] = real; + } else { + auto real = tensors[name]; + *dst_tensor = real; + } + + return true; + }; + + model_loader->load_tensors(on_new_tensor_cb, backend); + alloc_params_buffer(); + + dry_run = false; + model_loader->load_tensors(on_new_tensor_cb, backend); + + LOG_DEBUG("finished loading PhotoMaker ID Embeds "); + return true; + } + + + struct ggml_tensor* get(){ + std::map::iterator pos; + pos = tensors.find("pmid.id_embeds"); + if(pos != tensors.end()) + return pos->second; + return NULL; + } +}; + #endif // __PMI_HPP__ diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 2297cd377..d70dab1ce 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -95,6 +95,7 @@ class StableDiffusionGGML { std::shared_ptr control_net; std::shared_ptr pmid_model; std::shared_ptr pmid_lora; + std::shared_ptr pmid_id_embeds; std::string taesd_path; bool use_tiny_autoencoder = false; @@ -331,7 +332,11 @@ class StableDiffusionGGML { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); } else { - cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version); + if(id_embeddings_path.find("v2") != std::string::npos) { + cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version, VERSION_2); + }else{ + cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version); + } diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); } cond_stage_model->alloc_params_buffer(); @@ -366,7 +371,12 @@ class StableDiffusionGGML { control_net = std::make_shared(controlnet_backend, diffusion_model_wtype, version); } - pmid_model = std::make_shared(clip_backend, model_wtype, version); + if(id_embeddings_path.find("v2") != std::string::npos) { + pmid_model = std::make_shared(backend, model_wtype, version, VERSION_2); + LOG_INFO("using PhotoMaker Version 2"); + } else { + pmid_model = std::make_shared(backend, model_wtype, version); + } if (id_embeddings_path.size() > 0) { pmid_lora = std::make_shared(backend, model_wtype, id_embeddings_path, ""); if (!pmid_lora->load_from_file(true)) { @@ -385,14 +395,8 @@ class StableDiffusionGGML { LOG_ERROR(" pmid model params buffer allocation failed"); return false; } - // LOG_INFO("pmid param memory buffer size = %.2fMB ", - // pmid_model->params_buffer_size / 1024.0 / 1024.0); pmid_model->get_param_tensors(tensors, "pmid"); } - // if(stacked_id){ - // pmid_model.init_params(GGML_TYPE_F32); - // pmid_model.map_by_name(tensors, "pmid."); - // } } struct ggml_init_params params; @@ -675,10 +679,10 @@ class StableDiffusionGGML { ggml_tensor* id_encoder(ggml_context* work_ctx, ggml_tensor* init_img, ggml_tensor* prompts_embeds, + ggml_tensor* id_embeds, std::vector& class_tokens_mask) { ggml_tensor* res = NULL; - pmid_model->compute(n_threads, init_img, prompts_embeds, class_tokens_mask, &res, work_ctx); - + pmid_model->compute(n_threads, init_img, prompts_embeds, id_embeds, class_tokens_mask, &res, work_ctx); return res; } @@ -1207,11 +1211,15 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, } // preprocess input id images std::vector input_id_images; + bool pmv2 = sd_ctx->sd->pmid_model->get_version() == VERSION_2; if (sd_ctx->sd->pmid_model && input_id_images_path.size() > 0) { std::vector img_files = get_files_from_dir(input_id_images_path); for (std::string img_file : img_files) { int c = 0; int width, height; + if(ends_with(img_file, "safetensors")){ + continue; + } uint8_t* input_image_buffer = stbi_load(img_file.c_str(), &width, &height, &c, 3); if (input_image_buffer == NULL) { LOG_ERROR("PhotoMaker load image from '%s' failed", img_file.c_str()); @@ -1259,8 +1267,13 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, sd_ctx->sd->diffusion_model->get_adm_in_channels()); id_cond = std::get<0>(cond_tup); class_tokens_mask = std::get<1>(cond_tup); // - - id_cond.c_crossattn = sd_ctx->sd->id_encoder(work_ctx, init_img, id_cond.c_crossattn, class_tokens_mask); + struct ggml_tensor* id_embeds = NULL; + if(pmv2){ + // id_embeds = sd_ctx->sd->pmid_id_embeds->get(); + id_embeds = load_tensor_from_file(work_ctx, path_join(input_id_images_path, "id_embeds.bin")); + // print_ggml_tensor(id_embeds, true, "id_embeds:"); + } + id_cond.c_crossattn = sd_ctx->sd->id_encoder(work_ctx, init_img, id_cond.c_crossattn, id_embeds, class_tokens_mask); t1 = ggml_time_ms(); LOG_INFO("Photomaker ID Stacking, taking %" PRId64 " ms", t1 - t0); if (sd_ctx->sd->free_params_immediately) { diff --git a/util.cpp b/util.cpp index 5de5ce26e..cd058bb07 100644 --- a/util.cpp +++ b/util.cpp @@ -276,6 +276,24 @@ std::string path_join(const std::string& p1, const std::string& p2) { return p1 + "/" + p2; } +std::vector splitString(const std::string& str, char delimiter) { + std::vector result; + size_t start = 0; + size_t end = str.find(delimiter); + + while (end != std::string::npos) { + result.push_back(str.substr(start, end - start)); + start = end + 1; + end = str.find(delimiter, start); + } + + // Add the last segment after the last delimiter + result.push_back(str.substr(start)); + + return result; +} + + sd_image_t* preprocess_id_image(sd_image_t* img) { int shortest_edge = 224; int size = shortest_edge; diff --git a/util.h b/util.h index 9b1e6734f..14fa812e5 100644 --- a/util.h +++ b/util.h @@ -45,7 +45,7 @@ sd_image_f32_t resize_sd_image_f32_t(sd_image_f32_t image, int target_width, int sd_image_f32_t clip_preprocess(sd_image_f32_t image, int size); std::string path_join(const std::string& p1, const std::string& p2); - +std::vector splitString(const std::string& str, char delimiter); void pretty_progress(int step, int steps, float time); void log_printf(sd_log_level_t level, const char* file, int line, const char* format, ...); From ea9b647080aae818608606d81ab6abc92193ecfe Mon Sep 17 00:00:00 2001 From: William Murray <79180571+william-murray1204@users.noreply.github.com> Date: Sat, 23 Nov 2024 14:52:33 +1100 Subject: [PATCH 009/143] docs: update readme, add python bindings (#423) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 62b93ff52..147b64712 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,7 @@ These projects wrap `stable-diffusion.cpp` for easier use in other languages/fra * Golang: [seasonjs/stable-diffusion](https://github.com/seasonjs/stable-diffusion) * C#: [DarthAffe/StableDiffusion.NET](https://github.com/DarthAffe/StableDiffusion.NET) +* Python: [william-murray1204/stable-diffusion-cpp-python](https://github.com/william-murray1204/stable-diffusion-cpp-python) * Rust: [newfla/diffusion-rs](https://github.com/newfla/diffusion-rs) ## UIs From 1c168d98a5a47aaf6d9b2c7f3a23e3166c59a6ec Mon Sep 17 00:00:00 2001 From: Erik Scholz Date: Sat, 23 Nov 2024 05:39:08 +0100 Subject: [PATCH 010/143] fix: repair flash attention support (#386) * repair flash attention in _ext this does not fix the currently broken fa behind the define, which is only used by VAE Co-authored-by: FSSRepo * make flash attention in the diffusion model a runtime flag no support for sd3 or video * remove old flash attention option and switch vae over to attn_ext * update docs * format code --------- Co-authored-by: FSSRepo Co-authored-by: leejet --- CMakeLists.txt | 6 - README.md | 21 ++- clip.hpp | 18 ++- common.hpp | 23 ++-- conditioner.hpp | 19 ++- diffusion_model.hpp | 12 +- examples/cli/main.cpp | 10 +- flux.hpp | 49 ++++--- ggml_extend.hpp | 77 +++++------ model.cpp | 28 ++-- model.h | 2 +- pmid.hpp | 293 +++++++++++++++++++----------------------- stable-diffusion.cpp | 61 ++++----- stable-diffusion.h | 3 +- unet.hpp | 11 +- util.cpp | 5 +- vae.hpp | 10 +- 17 files changed, 334 insertions(+), 314 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c993e7c96..8466ed5d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,6 @@ option(SD_HIPBLAS "sd: rocm backend" OFF) option(SD_METAL "sd: metal backend" OFF) option(SD_VULKAN "sd: vulkan backend" OFF) option(SD_SYCL "sd: sycl backend" OFF) -option(SD_FLASH_ATTN "sd: use flash attention for x4 less memory usage" OFF) option(SD_FAST_SOFTMAX "sd: x1.5 faster softmax, indeterministic (sometimes, same seed don't generate same image), cuda only" OFF) option(SD_BUILD_SHARED_LIBS "sd: build shared libs" OFF) #option(SD_BUILD_SERVER "sd: build server example" ON) @@ -61,11 +60,6 @@ if (SD_HIPBLAS) endif() endif () -if(SD_FLASH_ATTN) - message("-- Use Flash Attention for memory optimization") - add_definitions(-DSD_USE_FLASH_ATTENTION) -endif() - set(SD_LIB stable-diffusion) file(GLOB SD_LIB_SOURCES diff --git a/README.md b/README.md index 147b64712..a17ef7e11 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Inference of Stable Diffusion and Flux in pure C/C++ - Full CUDA, Metal, Vulkan and SYCL backend for GPU acceleration. - Can load ckpt, safetensors and diffusers models/checkpoints. Standalone VAEs models - No need to convert to `.ggml` or `.gguf` anymore! -- Flash Attention for memory usage optimization (only cpu for now) +- Flash Attention for memory usage optimization - Original `txt2img` and `img2img` mode - Negative prompt - [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui) style tokenizer (not all the features, only token weighting for now) @@ -182,11 +182,21 @@ Example of text2img by using SYCL backend: ##### Using Flash Attention -Enabling flash attention reduces memory usage by at least 400 MB. At the moment, it is not supported when CUBLAS is enabled because the kernel implementation is missing. +Enabling flash attention for the diffusion model reduces memory usage by varying amounts of MB. +eg.: + - flux 768x768 ~600mb + - SD2 768x768 ~1400mb +For most backends, it slows things down, but for cuda it generally speeds it up too. +At the moment, it is only supported for some models and some backends (like cpu, cuda/rocm, metal). + +Run by adding `--diffusion-fa` to the arguments and watch for: ``` -cmake .. -DSD_FLASH_ATTN=ON -cmake --build . --config Release +[INFO ] stable-diffusion.cpp:312 - Using flash attention in the diffusion model +``` +and the compute buffer shrink in the debug log: +``` +[DEBUG] ggml_extend.hpp:1004 - flux compute buffer size: 650.00 MB(VRAM) ``` ### Run @@ -240,6 +250,9 @@ arguments: --vae-tiling process vae in tiles to reduce memory usage --vae-on-cpu keep vae in cpu (for low vram) --clip-on-cpu keep clip in cpu (for low vram) + --diffusion-fa use flash attention in the diffusion model (for low vram) + Might lower quality, since it implies converting k and v to f16. + This might crash if it is not supported by the backend. --control-net-cpu keep controlnet in cpu (for low vram) --canny apply canny preprocessor (edge detection) --color Colors the logging tags according to level diff --git a/clip.hpp b/clip.hpp index 7c2705873..46e52ada4 100644 --- a/clip.hpp +++ b/clip.hpp @@ -343,8 +343,7 @@ class CLIPTokenizer { } } - std::string clean_up_tokenization(std::string &text){ - + std::string clean_up_tokenization(std::string& text) { std::regex pattern(R"( ,)"); // Replace " ," with "," std::string result = std::regex_replace(text, pattern, ","); @@ -359,10 +358,10 @@ class CLIPTokenizer { std::u32string ts = decoder[t]; // printf("%d, %s \n", t, utf32_to_utf8(ts).c_str()); std::string s = utf32_to_utf8(ts); - if (s.length() >= 4 ){ - if(ends_with(s, "")) { + if (s.length() >= 4) { + if (ends_with(s, "")) { text += s.replace(s.length() - 4, s.length() - 1, "") + " "; - }else{ + } else { text += s; } } else { @@ -768,8 +767,7 @@ class CLIPVisionModel : public GGMLBlock { blocks["post_layernorm"] = std::shared_ptr(new LayerNorm(hidden_size)); } - struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* pixel_values, - bool return_pooled = true) { + struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* pixel_values, bool return_pooled = true) { // pixel_values: [N, num_channels, image_size, image_size] auto embeddings = std::dynamic_pointer_cast(blocks["embeddings"]); auto pre_layernorm = std::dynamic_pointer_cast(blocks["pre_layernorm"]); @@ -779,11 +777,11 @@ class CLIPVisionModel : public GGMLBlock { auto x = embeddings->forward(ctx, pixel_values); // [N, num_positions, embed_dim] x = pre_layernorm->forward(ctx, x); x = encoder->forward(ctx, x, -1, false); - // print_ggml_tensor(x, true, "ClipVisionModel x: "); + // print_ggml_tensor(x, true, "ClipVisionModel x: "); auto last_hidden_state = x; - x = post_layernorm->forward(ctx, x); // [N, n_token, hidden_size] + x = post_layernorm->forward(ctx, x); // [N, n_token, hidden_size] - GGML_ASSERT(x->ne[3] == 1); + GGML_ASSERT(x->ne[3] == 1); if (return_pooled) { ggml_tensor* pooled = ggml_cont(ctx, ggml_view_2d(ctx, x, x->ne[0], x->ne[2], x->nb[2], 0)); return pooled; // [N, hidden_size] diff --git a/common.hpp b/common.hpp index b18ee51f5..1ca6b8d0d 100644 --- a/common.hpp +++ b/common.hpp @@ -245,16 +245,19 @@ class CrossAttention : public GGMLBlock { int64_t context_dim; int64_t n_head; int64_t d_head; + bool flash_attn; public: CrossAttention(int64_t query_dim, int64_t context_dim, int64_t n_head, - int64_t d_head) + int64_t d_head, + bool flash_attn = false) : n_head(n_head), d_head(d_head), query_dim(query_dim), - context_dim(context_dim) { + context_dim(context_dim), + flash_attn(flash_attn) { int64_t inner_dim = d_head * n_head; blocks["to_q"] = std::shared_ptr(new Linear(query_dim, inner_dim, false)); @@ -283,7 +286,7 @@ class CrossAttention : public GGMLBlock { auto k = to_k->forward(ctx, context); // [N, n_context, inner_dim] auto v = to_v->forward(ctx, context); // [N, n_context, inner_dim] - x = ggml_nn_attention_ext(ctx, q, k, v, n_head, NULL, false); // [N, n_token, inner_dim] + x = ggml_nn_attention_ext(ctx, q, k, v, n_head, NULL, false, false, flash_attn); // [N, n_token, inner_dim] x = to_out_0->forward(ctx, x); // [N, n_token, query_dim] return x; @@ -301,15 +304,16 @@ class BasicTransformerBlock : public GGMLBlock { int64_t n_head, int64_t d_head, int64_t context_dim, - bool ff_in = false) + bool ff_in = false, + bool flash_attn = false) : n_head(n_head), d_head(d_head), ff_in(ff_in) { // disable_self_attn is always False // disable_temporal_crossattention is always False // switch_temporal_ca_to_sa is always False // inner_dim is always None or equal to dim // gated_ff is always True - blocks["attn1"] = std::shared_ptr(new CrossAttention(dim, dim, n_head, d_head)); - blocks["attn2"] = std::shared_ptr(new CrossAttention(dim, context_dim, n_head, d_head)); + blocks["attn1"] = std::shared_ptr(new CrossAttention(dim, dim, n_head, d_head, flash_attn)); + blocks["attn2"] = std::shared_ptr(new CrossAttention(dim, context_dim, n_head, d_head, flash_attn)); blocks["ff"] = std::shared_ptr(new FeedForward(dim, dim)); blocks["norm1"] = std::shared_ptr(new LayerNorm(dim)); blocks["norm2"] = std::shared_ptr(new LayerNorm(dim)); @@ -374,7 +378,8 @@ class SpatialTransformer : public GGMLBlock { int64_t n_head, int64_t d_head, int64_t depth, - int64_t context_dim) + int64_t context_dim, + bool flash_attn = false) : in_channels(in_channels), n_head(n_head), d_head(d_head), @@ -388,7 +393,7 @@ class SpatialTransformer : public GGMLBlock { for (int i = 0; i < depth; i++) { std::string name = "transformer_blocks." + std::to_string(i); - blocks[name] = std::shared_ptr(new BasicTransformerBlock(inner_dim, n_head, d_head, context_dim)); + blocks[name] = std::shared_ptr(new BasicTransformerBlock(inner_dim, n_head, d_head, context_dim, false, flash_attn)); } blocks["proj_out"] = std::shared_ptr(new Conv2d(inner_dim, in_channels, {1, 1})); @@ -511,4 +516,4 @@ class VideoResBlock : public ResBlock { } }; -#endif // __COMMON_HPP__ \ No newline at end of file +#endif // __COMMON_HPP__ diff --git a/conditioner.hpp b/conditioner.hpp index 065f352ec..47fd3eb6e 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -4,7 +4,6 @@ #include "clip.hpp" #include "t5.hpp" - struct SDCondition { struct ggml_tensor* c_crossattn = NULL; // aka context struct ggml_tensor* c_vector = NULL; // aka y @@ -44,7 +43,7 @@ struct Conditioner { // ldm.modules.encoders.modules.FrozenCLIPEmbedder // Ref: https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cad87bf4e3e0b0a759afa94e933527c3123d59bc/modules/sd_hijack_clip.py#L283 struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { - SDVersion version = VERSION_SD1; + SDVersion version = VERSION_SD1; PMVersion pm_version = VERSION_1; CLIPTokenizer tokenizer; ggml_type wtype; @@ -61,7 +60,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { ggml_type wtype, const std::string& embd_dir, SDVersion version = VERSION_SD1, - PMVersion pv = VERSION_1, + PMVersion pv = VERSION_1, int clip_skip = -1) : version(version), pm_version(pv), tokenizer(version == VERSION_SD2 ? 0 : 49407), embd_dir(embd_dir), wtype(wtype) { if (clip_skip <= 0) { @@ -162,7 +161,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { tokenize_with_trigger_token(std::string text, int num_input_imgs, int32_t image_token, - bool padding = false){ + bool padding = false) { return tokenize_with_trigger_token(text, num_input_imgs, image_token, text_model->model.n_token, padding); } @@ -271,7 +270,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { std::vector clean_input_ids_tmp; for (uint32_t i = 0; i < class_token_index[0]; i++) clean_input_ids_tmp.push_back(clean_input_ids[i]); - for (uint32_t i = 0; i < (pm_version == VERSION_2 ? 2*num_input_imgs: num_input_imgs); i++) + for (uint32_t i = 0; i < (pm_version == VERSION_2 ? 2 * num_input_imgs : num_input_imgs); i++) clean_input_ids_tmp.push_back(class_token); for (uint32_t i = class_token_index[0] + 1; i < clean_input_ids.size(); i++) clean_input_ids_tmp.push_back(clean_input_ids[i]); @@ -287,11 +286,11 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { // weights.insert(weights.begin(), 1.0); tokenizer.pad_tokens(tokens, weights, max_length, padding); - int offset = pm_version == VERSION_2 ? 2*num_input_imgs: num_input_imgs; + int offset = pm_version == VERSION_2 ? 2 * num_input_imgs : num_input_imgs; for (uint32_t i = 0; i < tokens.size(); i++) { // if (class_idx + 1 <= i && i < class_idx + 1 + 2*num_input_imgs) // photomaker V2 has num_tokens(=2)*num_input_imgs - if (class_idx + 1 <= i && i < class_idx + 1 + offset) // photomaker V2 has num_tokens(=2)*num_input_imgs - // hardcode for now + if (class_idx + 1 <= i && i < class_idx + 1 + offset) // photomaker V2 has num_tokens(=2)*num_input_imgs + // hardcode for now class_token_mask.push_back(true); else class_token_mask.push_back(false); @@ -536,7 +535,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { int height, int num_input_imgs, int adm_in_channels = -1, - bool force_zero_embeddings = false){ + bool force_zero_embeddings = false) { auto image_tokens = convert_token_to_id(trigger_word); // if(image_tokens.size() == 1){ // printf(" image token id is: %d \n", image_tokens[0]); @@ -964,7 +963,7 @@ struct SD3CLIPEmbedder : public Conditioner { int height, int num_input_imgs, int adm_in_channels = -1, - bool force_zero_embeddings = false){ + bool force_zero_embeddings = false) { GGML_ASSERT(0 && "Not implemented yet!"); } diff --git a/diffusion_model.hpp b/diffusion_model.hpp index 26c619b07..eb433b614 100644 --- a/diffusion_model.hpp +++ b/diffusion_model.hpp @@ -32,8 +32,9 @@ struct UNetModel : public DiffusionModel { UNetModel(ggml_backend_t backend, ggml_type wtype, - SDVersion version = VERSION_SD1) - : unet(backend, wtype, version) { + SDVersion version = VERSION_SD1, + bool flash_attn = false) + : unet(backend, wtype, version, flash_attn) { } void alloc_params_buffer() { @@ -133,8 +134,9 @@ struct FluxModel : public DiffusionModel { FluxModel(ggml_backend_t backend, ggml_type wtype, - SDVersion version = VERSION_FLUX_DEV) - : flux(backend, wtype, version) { + SDVersion version = VERSION_FLUX_DEV, + bool flash_attn = false) + : flux(backend, wtype, version, flash_attn) { } void alloc_params_buffer() { @@ -178,4 +180,4 @@ struct FluxModel : public DiffusionModel { } }; -#endif \ No newline at end of file +#endif diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 869b99110..9f25245e3 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -116,6 +116,7 @@ struct SDParams { bool normalize_input = false; bool clip_on_cpu = false; bool vae_on_cpu = false; + bool diffusion_flash_attn = false; bool canny_preprocess = false; bool color = false; int upscale_repeats = 1; @@ -151,6 +152,7 @@ void print_params(SDParams params) { printf(" clip on cpu: %s\n", params.clip_on_cpu ? "true" : "false"); printf(" controlnet cpu: %s\n", params.control_net_cpu ? "true" : "false"); printf(" vae decoder on cpu:%s\n", params.vae_on_cpu ? "true" : "false"); + printf(" diffusion flash attention:%s\n", params.diffusion_flash_attn ? "true" : "false"); printf(" strength(control): %.2f\n", params.control_strength); printf(" prompt: %s\n", params.prompt.c_str()); printf(" negative_prompt: %s\n", params.negative_prompt.c_str()); @@ -227,6 +229,9 @@ void print_usage(int argc, const char* argv[]) { printf(" --vae-tiling process vae in tiles to reduce memory usage\n"); printf(" --vae-on-cpu keep vae in cpu (for low vram)\n"); printf(" --clip-on-cpu keep clip in cpu (for low vram)\n"); + printf(" --diffusion-fa use flash attention in the diffusion model (for low vram)\n"); + printf(" Might lower quality, since it implies converting k and v to f16.\n"); + printf(" This might crash if it is not supported by the backend.\n"); printf(" --control-net-cpu keep controlnet in cpu (for low vram)\n"); printf(" --canny apply canny preprocessor (edge detection)\n"); printf(" --color Colors the logging tags according to level\n"); @@ -477,6 +482,8 @@ void parse_args(int argc, const char** argv, SDParams& params) { params.clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs } else if (arg == "--vae-on-cpu") { params.vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs + } else if (arg == "--diffusion-fa") { + params.diffusion_flash_attn = true; // can reduce MEM significantly } else if (arg == "--canny") { params.canny_preprocess = true; } else if (arg == "-b" || arg == "--batch-count") { @@ -868,7 +875,8 @@ int main(int argc, const char* argv[]) { params.schedule, params.clip_on_cpu, params.control_net_cpu, - params.vae_on_cpu); + params.vae_on_cpu, + params.diffusion_flash_attn); if (sd_ctx == NULL) { printf("new_sd_ctx_t failed\n"); diff --git a/flux.hpp b/flux.hpp index faea59a4d..b2d0f57c2 100644 --- a/flux.hpp +++ b/flux.hpp @@ -115,25 +115,28 @@ namespace Flux { struct ggml_tensor* q, struct ggml_tensor* k, struct ggml_tensor* v, - struct ggml_tensor* pe) { + struct ggml_tensor* pe, + bool flash_attn) { // q,k,v: [N, L, n_head, d_head] // pe: [L, d_head/2, 2, 2] // return: [N, L, n_head*d_head] q = apply_rope(ctx, q, pe); // [N*n_head, L, d_head] k = apply_rope(ctx, k, pe); // [N*n_head, L, d_head] - auto x = ggml_nn_attention_ext(ctx, q, k, v, v->ne[1], NULL, false, true); // [N, L, n_head*d_head] + auto x = ggml_nn_attention_ext(ctx, q, k, v, v->ne[1], NULL, false, true, flash_attn); // [N, L, n_head*d_head] return x; } struct SelfAttention : public GGMLBlock { public: int64_t num_heads; + bool flash_attn; public: SelfAttention(int64_t dim, int64_t num_heads = 8, - bool qkv_bias = false) + bool qkv_bias = false, + bool flash_attn = false) : num_heads(num_heads) { int64_t head_dim = dim / num_heads; blocks["qkv"] = std::shared_ptr(new Linear(dim, dim * 3, qkv_bias)); @@ -167,9 +170,9 @@ namespace Flux { // x: [N, n_token, dim] // pe: [n_token, d_head/2, 2, 2] // return [N, n_token, dim] - auto qkv = pre_attention(ctx, x); // q,k,v: [N, n_token, n_head, d_head] - x = attention(ctx, qkv[0], qkv[1], qkv[2], pe); // [N, n_token, dim] - x = post_attention(ctx, x); // [N, n_token, dim] + auto qkv = pre_attention(ctx, x); // q,k,v: [N, n_token, n_head, d_head] + x = attention(ctx, qkv[0], qkv[1], qkv[2], pe, flash_attn); // [N, n_token, dim] + x = post_attention(ctx, x); // [N, n_token, dim] return x; } }; @@ -237,15 +240,19 @@ namespace Flux { } struct DoubleStreamBlock : public GGMLBlock { + bool flash_attn; + public: DoubleStreamBlock(int64_t hidden_size, int64_t num_heads, float mlp_ratio, - bool qkv_bias = false) { + bool qkv_bias = false, + bool flash_attn = false) + : flash_attn(flash_attn) { int64_t mlp_hidden_dim = hidden_size * mlp_ratio; blocks["img_mod"] = std::shared_ptr(new Modulation(hidden_size, true)); blocks["img_norm1"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); - blocks["img_attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qkv_bias)); + blocks["img_attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qkv_bias, flash_attn)); blocks["img_norm2"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); blocks["img_mlp.0"] = std::shared_ptr(new Linear(hidden_size, mlp_hidden_dim)); @@ -254,7 +261,7 @@ namespace Flux { blocks["txt_mod"] = std::shared_ptr(new Modulation(hidden_size, true)); blocks["txt_norm1"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); - blocks["txt_attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qkv_bias)); + blocks["txt_attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qkv_bias, flash_attn)); blocks["txt_norm2"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); blocks["txt_mlp.0"] = std::shared_ptr(new Linear(hidden_size, mlp_hidden_dim)); @@ -316,7 +323,7 @@ namespace Flux { auto k = ggml_concat(ctx, txt_k, img_k, 2); // [N, n_txt_token + n_img_token, n_head, d_head] auto v = ggml_concat(ctx, txt_v, img_v, 2); // [N, n_txt_token + n_img_token, n_head, d_head] - auto attn = attention(ctx, q, k, v, pe); // [N, n_txt_token + n_img_token, n_head*d_head] + auto attn = attention(ctx, q, k, v, pe, flash_attn); // [N, n_txt_token + n_img_token, n_head*d_head] attn = ggml_cont(ctx, ggml_permute(ctx, attn, 0, 2, 1, 3)); // [n_txt_token + n_img_token, N, hidden_size] auto txt_attn_out = ggml_view_3d(ctx, attn, @@ -364,13 +371,15 @@ namespace Flux { int64_t num_heads; int64_t hidden_size; int64_t mlp_hidden_dim; + bool flash_attn; public: SingleStreamBlock(int64_t hidden_size, int64_t num_heads, float mlp_ratio = 4.0f, - float qk_scale = 0.f) - : hidden_size(hidden_size), num_heads(num_heads) { + float qk_scale = 0.f, + bool flash_attn = false) + : hidden_size(hidden_size), num_heads(num_heads), flash_attn(flash_attn) { int64_t head_dim = hidden_size / num_heads; float scale = qk_scale; if (scale <= 0.f) { @@ -433,7 +442,7 @@ namespace Flux { auto v = ggml_reshape_4d(ctx, qkv_vec[2], head_dim, num_heads, qkv_vec[2]->ne[1], qkv_vec[2]->ne[2]); // [N, n_token, n_head, d_head] q = norm->query_norm(ctx, q); k = norm->key_norm(ctx, k); - auto attn = attention(ctx, q, k, v, pe); // [N, n_token, hidden_size] + auto attn = attention(ctx, q, k, v, pe, flash_attn); // [N, n_token, hidden_size] auto attn_mlp = ggml_concat(ctx, attn, ggml_gelu_inplace(ctx, mlp), 0); // [N, n_token, hidden_size + mlp_hidden_dim] auto output = linear2->forward(ctx, attn_mlp); // [N, n_token, hidden_size] @@ -492,6 +501,7 @@ namespace Flux { int theta = 10000; bool qkv_bias = true; bool guidance_embed = true; + bool flash_attn = true; }; struct Flux : public GGMLBlock { @@ -646,13 +656,16 @@ namespace Flux { blocks["double_blocks." + std::to_string(i)] = std::shared_ptr(new DoubleStreamBlock(params.hidden_size, params.num_heads, params.mlp_ratio, - params.qkv_bias)); + params.qkv_bias, + params.flash_attn)); } for (int i = 0; i < params.depth_single_blocks; i++) { blocks["single_blocks." + std::to_string(i)] = std::shared_ptr(new SingleStreamBlock(params.hidden_size, params.num_heads, - params.mlp_ratio)); + params.mlp_ratio, + 0.f, + params.flash_attn)); } blocks["final_layer"] = std::shared_ptr(new LastLayer(params.hidden_size, 1, out_channels)); @@ -817,8 +830,10 @@ namespace Flux { FluxRunner(ggml_backend_t backend, ggml_type wtype, - SDVersion version = VERSION_FLUX_DEV) + SDVersion version = VERSION_FLUX_DEV, + bool flash_attn = false) : GGMLRunner(backend, wtype) { + flux_params.flash_attn = flash_attn; if (version == VERSION_FLUX_SCHNELL) { flux_params.guidance_embed = false; } @@ -973,4 +988,4 @@ namespace Flux { } // namespace Flux -#endif // __FLUX_HPP__ \ No newline at end of file +#endif // __FLUX_HPP__ diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 8dea410ef..75ad04142 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -666,32 +666,6 @@ __STATIC_INLINE__ std::vector split_qkv(struct ggml_context return {q, k, v}; } -// q: [N * n_head, n_token, d_head] -// k: [N * n_head, n_k, d_head] -// v: [N * n_head, d_head, n_k] -// return: [N * n_head, n_token, d_head] -__STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention(struct ggml_context* ctx, - struct ggml_tensor* q, - struct ggml_tensor* k, - struct ggml_tensor* v, - bool mask = false) { -#if defined(SD_USE_FLASH_ATTENTION) && !defined(SD_USE_CUBLAS) && !defined(SD_USE_METAL) && !defined(SD_USE_VULKAN) && !defined(SD_USE_SYCL) - struct ggml_tensor* kqv = ggml_flash_attn(ctx, q, k, v, false); // [N * n_head, n_token, d_head] -#else - float d_head = (float)q->ne[0]; - - struct ggml_tensor* kq = ggml_mul_mat(ctx, k, q); // [N * n_head, n_token, n_k] - kq = ggml_scale_inplace(ctx, kq, 1.0f / sqrt(d_head)); - if (mask) { - kq = ggml_diag_mask_inf_inplace(ctx, kq, 0); - } - kq = ggml_soft_max_inplace(ctx, kq); - - struct ggml_tensor* kqv = ggml_mul_mat(ctx, v, kq); // [N * n_head, n_token, d_head] -#endif - return kqv; -} - // q: [N, L_q, C] or [N*n_head, L_q, d_head] // k: [N, L_k, C] or [N*n_head, L_k, d_head] // v: [N, L_k, C] or [N, L_k, n_head, d_head] @@ -703,7 +677,8 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention_ext(struct ggml_context* int64_t n_head, struct ggml_tensor* mask = NULL, bool diag_mask_inf = false, - bool skip_reshape = false) { + bool skip_reshape = false, + bool flash_attn = false) { int64_t L_q; int64_t L_k; int64_t C; @@ -734,13 +709,42 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention_ext(struct ggml_context* float scale = (1.0f / sqrt((float)d_head)); - bool use_flash_attn = false; - ggml_tensor* kqv = NULL; - if (use_flash_attn) { + // if (flash_attn) { + // LOG_DEBUG("attention_ext L_q:%d L_k:%d n_head:%d C:%d d_head:%d N:%d", L_q, L_k, n_head, C, d_head, N); + // } + // is there anything oddly shaped?? ping Green-Sky if you can trip this assert + GGML_ASSERT(((L_k % 256 == 0) && L_q == L_k) || !(L_k % 256 == 0)); + + bool can_use_flash_attn = true; + can_use_flash_attn = can_use_flash_attn && L_k % 256 == 0; + can_use_flash_attn = can_use_flash_attn && d_head % 64 == 0; // double check + + // cuda max d_head seems to be 256, cpu does seem to work with 512 + can_use_flash_attn = can_use_flash_attn && d_head <= 256; // double check + + if (mask != nullptr) { + // TODO(Green-Sky): figure out if we can bend t5 to work too + can_use_flash_attn = can_use_flash_attn && mask->ne[2] == 1; + can_use_flash_attn = can_use_flash_attn && mask->ne[3] == 1; + } + + // TODO(Green-Sky): more pad or disable for funny tensor shapes + + ggml_tensor* kqv = nullptr; + // GGML_ASSERT((flash_attn && can_use_flash_attn) || !flash_attn); + if (can_use_flash_attn && flash_attn) { + // LOG_DEBUG("using flash attention"); + k = ggml_cast(ctx, k, GGML_TYPE_F16); + v = ggml_cont(ctx, ggml_permute(ctx, v, 0, 2, 1, 3)); // [N, n_head, L_k, d_head] v = ggml_reshape_3d(ctx, v, d_head, L_k, n_head * N); // [N * n_head, L_k, d_head] - LOG_DEBUG("k->ne[1] == %d", k->ne[1]); + v = ggml_cast(ctx, v, GGML_TYPE_F16); + kqv = ggml_flash_attn_ext(ctx, q, k, v, mask, scale, 0, 0); + ggml_flash_attn_ext_set_prec(kqv, GGML_PREC_F32); + + // kqv = ggml_view_3d(ctx, kqv, d_head, n_head, L_k, kqv->nb[1], kqv->nb[2], 0); + kqv = ggml_view_3d(ctx, kqv, d_head, n_head, L_q, kqv->nb[1], kqv->nb[2], 0); } else { v = ggml_cont(ctx, ggml_permute(ctx, v, 1, 2, 0, 3)); // [N, n_head, d_head, L_k] v = ggml_reshape_3d(ctx, v, L_k, d_head, n_head * N); // [N * n_head, d_head, L_k] @@ -756,10 +760,12 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention_ext(struct ggml_context* kq = ggml_soft_max_inplace(ctx, kq); kqv = ggml_mul_mat(ctx, v, kq); // [N * n_head, L_q, d_head] + + kqv = ggml_reshape_4d(ctx, kqv, d_head, L_q, n_head, N); // [N, n_head, L_q, d_head] + kqv = ggml_permute(ctx, kqv, 0, 2, 1, 3); // [N, L_q, n_head, d_head] } - kqv = ggml_reshape_4d(ctx, kqv, d_head, L_q, n_head, N); // [N, n_head, L_q, d_head] - kqv = ggml_cont(ctx, ggml_permute(ctx, kqv, 0, 2, 1, 3)); // [N, L_q, n_head, d_head] + kqv = ggml_cont(ctx, kqv); kqv = ggml_reshape_3d(ctx, kqv, d_head * n_head, L_q, N); // [N, L_q, C] return kqv; @@ -1051,7 +1057,7 @@ struct GGMLRunner { // get_desc().c_str(), // params_buffer_size / (1024.0 * 1024.0), // ggml_backend_is_cpu(backend) ? "RAM" : "VRAM", - // num_tensors); + // num_tensors); return true; } @@ -1221,8 +1227,7 @@ class Linear : public UnaryBlock { params["weight"] = ggml_new_tensor_2d(ctx, wtype, in_features, out_features); if (bias) { params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_features); - } - + } } public: diff --git a/model.cpp b/model.cpp index 5f1e6e160..2719f63c0 100644 --- a/model.cpp +++ b/model.cpp @@ -148,19 +148,19 @@ std::unordered_map vae_decoder_name_map = { std::unordered_map pmid_v2_name_map = { {"pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.weight", - "pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.fc1.weight"}, + "pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.fc1.weight"}, {"pmid.qformer_perceiver.perceiver_resampler.layers.0.1.3.weight", - "pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.fc2.weight"}, + "pmid.qformer_perceiver.perceiver_resampler.layers.0.1.1.fc2.weight"}, {"pmid.qformer_perceiver.perceiver_resampler.layers.1.1.1.weight", - "pmid.qformer_perceiver.perceiver_resampler.layers.1.1.1.fc1.weight"}, + "pmid.qformer_perceiver.perceiver_resampler.layers.1.1.1.fc1.weight"}, {"pmid.qformer_perceiver.perceiver_resampler.layers.1.1.3.weight", "pmid.qformer_perceiver.perceiver_resampler.layers.1.1.1.fc2.weight"}, {"pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.weight", - "pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.fc1.weight"}, + "pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.fc1.weight"}, {"pmid.qformer_perceiver.perceiver_resampler.layers.2.1.3.weight", - "pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.fc2.weight"}, + "pmid.qformer_perceiver.perceiver_resampler.layers.2.1.1.fc2.weight"}, {"pmid.qformer_perceiver.perceiver_resampler.layers.3.1.1.weight", - "pmid.qformer_perceiver.perceiver_resampler.layers.3.1.1.fc1.weight"}, + "pmid.qformer_perceiver.perceiver_resampler.layers.3.1.1.fc1.weight"}, {"pmid.qformer_perceiver.perceiver_resampler.layers.3.1.3.weight", "pmid.qformer_perceiver.perceiver_resampler.layers.3.1.1.fc2.weight"}, {"pmid.qformer_perceiver.token_proj.0.bias", @@ -650,9 +650,8 @@ uint16_t f8_e4m3_to_f16(uint8_t f8) { return ggml_fp32_to_fp16(*reinterpret_cast(&result)); } - uint16_t f8_e5m2_to_f16(uint8_t fp8) { - uint8_t sign = (fp8 >> 7) & 0x1; + uint8_t sign = (fp8 >> 7) & 0x1; uint8_t exponent = (fp8 >> 2) & 0x1F; uint8_t mantissa = fp8 & 0x3; @@ -660,23 +659,23 @@ uint16_t f8_e5m2_to_f16(uint8_t fp8) { uint16_t fp16_exponent; uint16_t fp16_mantissa; - if (exponent == 0 && mantissa == 0) { //zero + if (exponent == 0 && mantissa == 0) { // zero return fp16_sign; } - if (exponent == 0x1F) { //NAN and INF + if (exponent == 0x1F) { // NAN and INF fp16_exponent = 0x1F; fp16_mantissa = mantissa ? (mantissa << 8) : 0; return fp16_sign | (fp16_exponent << 10) | fp16_mantissa; } - if (exponent == 0) { //subnormal numbers + if (exponent == 0) { // subnormal numbers fp16_exponent = 0; fp16_mantissa = (mantissa << 8); return fp16_sign | fp16_mantissa; } - //normal numbers + // normal numbers int16_t true_exponent = (int16_t)exponent - 15 + 15; if (true_exponent <= 0) { fp16_exponent = 0; @@ -1051,7 +1050,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const } TensorStorage tensor_storage(prefix + name, type, ne, n_dims, file_index, ST_HEADER_SIZE_LEN + header_size_ + begin); - tensor_storage.reverse_ne(); + tensor_storage.reverse_ne(); size_t tensor_data_size = end - begin; @@ -1434,10 +1433,9 @@ bool ModelLoader::init_from_ckpt_file(const std::string& file_path, const std::s std::string name = zip_entry_name(zip); size_t pos = name.find("data.pkl"); if (pos != std::string::npos) { - std::string dir = name.substr(0, pos); printf("ZIP %d, name = %s, dir = %s \n", i, name.c_str(), dir.c_str()); - void* pkl_data = NULL; + void* pkl_data = NULL; size_t pkl_size; zip_entry_read(zip, &pkl_data, &pkl_size); diff --git a/model.h b/model.h index 77841e82c..552a2ccd8 100644 --- a/model.h +++ b/model.h @@ -167,7 +167,7 @@ class ModelLoader { bool load_tensors(std::map& tensors, ggml_backend_t backend, std::set ignore_tensors = {}); - + bool save_to_gguf_file(const std::string& file_path, ggml_type type); bool tensor_should_be_converted(const TensorStorage& tensor_storage, ggml_type type); int64_t get_params_mem_size(ggml_backend_t backend, ggml_type type = GGML_TYPE_COUNT); diff --git a/pmid.hpp b/pmid.hpp index bde03cc92..b8555eb68 100644 --- a/pmid.hpp +++ b/pmid.hpp @@ -6,7 +6,6 @@ #include "clip.hpp" #include "lora.hpp" - struct FuseBlock : public GGMLBlock { // network hparams int in_dim; @@ -74,26 +73,24 @@ class QFormerPerceiver(nn.Module): x = self.token_norm(x) # cls token out = self.perceiver_resampler(x, last_hidden_state) # retrieve from patch tokens if self.use_residual: # TODO: if use_residual is not true - out = x + 1.0 * out + out = x + 1.0 * out return out */ - struct PMFeedForward : public GGMLBlock { // network hparams int dim; public: - PMFeedForward(int d, int multi=4) - : dim(d) { + PMFeedForward(int d, int multi = 4) + : dim(d) { int inner_dim = dim * multi; blocks["0"] = std::shared_ptr(new LayerNorm(dim)); blocks["1"] = std::shared_ptr(new Mlp(dim, inner_dim, dim, false)); } struct ggml_tensor* forward(struct ggml_context* ctx, - struct ggml_tensor* x){ - + struct ggml_tensor* x) { auto norm = std::dynamic_pointer_cast(blocks["0"]); auto ff = std::dynamic_pointer_cast(blocks["1"]); @@ -101,37 +98,35 @@ struct PMFeedForward : public GGMLBlock { x = ff->forward(ctx, x); return x; } - }; struct PerceiverAttention : public GGMLBlock { // network hparams - float scale; // = dim_head**-0.5 - int dim_head; // = dim_head - int heads; // = heads + float scale; // = dim_head**-0.5 + int dim_head; // = dim_head + int heads; // = heads public: - PerceiverAttention(int dim, int dim_h=64, int h=8) - : scale(powf(dim_h, -0.5)), dim_head(dim_h), heads(h) { - - int inner_dim = dim_head * heads; + PerceiverAttention(int dim, int dim_h = 64, int h = 8) + : scale(powf(dim_h, -0.5)), dim_head(dim_h), heads(h) { + int inner_dim = dim_head * heads; blocks["norm1"] = std::shared_ptr(new LayerNorm(dim)); blocks["norm2"] = std::shared_ptr(new LayerNorm(dim)); blocks["to_q"] = std::shared_ptr(new Linear(dim, inner_dim, false)); - blocks["to_kv"] = std::shared_ptr(new Linear(dim, inner_dim*2, false)); + blocks["to_kv"] = std::shared_ptr(new Linear(dim, inner_dim * 2, false)); blocks["to_out"] = std::shared_ptr(new Linear(inner_dim, dim, false)); } struct ggml_tensor* reshape_tensor(struct ggml_context* ctx, - struct ggml_tensor* x, - int heads) { + struct ggml_tensor* x, + int heads) { int64_t ne[4]; - for(int i = 0; i < 4; ++i) - ne[i] = x->ne[i]; + for (int i = 0; i < 4; ++i) + ne[i] = x->ne[i]; // print_ggml_tensor(x, true, "PerceiverAttention reshape x 0: "); // printf("heads = %d \n", heads); // x = ggml_view_4d(ctx, x, x->ne[0], x->ne[1], heads, x->ne[2]/heads, // x->nb[1], x->nb[2], x->nb[3], 0); - x = ggml_reshape_4d(ctx, x, x->ne[0]/heads, heads, x->ne[1], x->ne[2]); + x = ggml_reshape_4d(ctx, x, x->ne[0] / heads, heads, x->ne[1], x->ne[2]); // x = ggml_view_4d(ctx, x, x->ne[0]/heads, heads, x->ne[1], x->ne[2], // x->nb[1], x->nb[2], x->nb[3], 0); // x = ggml_cont(ctx, x); @@ -142,49 +137,46 @@ struct PerceiverAttention : public GGMLBlock { } std::vector chunk_half(struct ggml_context* ctx, - struct ggml_tensor* x){ - - auto tlo = ggml_view_4d(ctx, x, x->ne[0]/2, x->ne[1], x->ne[2], x->ne[3], x->nb[1], x->nb[2], x->nb[3], 0); - auto tli = ggml_view_4d(ctx, x, x->ne[0]/2, x->ne[1], x->ne[2], x->ne[3], x->nb[1], x->nb[2], x->nb[3], x->nb[0]*x->ne[0]/2); + struct ggml_tensor* x) { + auto tlo = ggml_view_4d(ctx, x, x->ne[0] / 2, x->ne[1], x->ne[2], x->ne[3], x->nb[1], x->nb[2], x->nb[3], 0); + auto tli = ggml_view_4d(ctx, x, x->ne[0] / 2, x->ne[1], x->ne[2], x->ne[3], x->nb[1], x->nb[2], x->nb[3], x->nb[0] * x->ne[0] / 2); return {ggml_cont(ctx, tlo), ggml_cont(ctx, tli)}; - } struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x, - struct ggml_tensor* latents){ - + struct ggml_tensor* latents) { // x (torch.Tensor): image features // shape (b, n1, D) // latent (torch.Tensor): latent features // shape (b, n2, D) int64_t ne[4]; - for(int i = 0; i < 4; ++i) - ne[i] = latents->ne[i]; + for (int i = 0; i < 4; ++i) + ne[i] = latents->ne[i]; - auto norm1 = std::dynamic_pointer_cast(blocks["norm1"]); - auto norm2 = std::dynamic_pointer_cast(blocks["norm2"]); - x = norm1->forward(ctx, x); - latents = norm2->forward(ctx, latents); - auto to_q = std::dynamic_pointer_cast(blocks["to_q"]); - auto q = to_q->forward(ctx, latents); + auto norm1 = std::dynamic_pointer_cast(blocks["norm1"]); + auto norm2 = std::dynamic_pointer_cast(blocks["norm2"]); + x = norm1->forward(ctx, x); + latents = norm2->forward(ctx, latents); + auto to_q = std::dynamic_pointer_cast(blocks["to_q"]); + auto q = to_q->forward(ctx, latents); auto kv_input = ggml_concat(ctx, x, latents, 1); auto to_kv = std::dynamic_pointer_cast(blocks["to_kv"]); - auto kv = to_kv->forward(ctx, kv_input); - auto k = ggml_view_4d(ctx, kv, kv->ne[0]/2, kv->ne[1], kv->ne[2], kv->ne[3], kv->nb[1]/2, kv->nb[2]/2, kv->nb[3]/2, 0); - auto v = ggml_view_4d(ctx, kv, kv->ne[0]/2, kv->ne[1], kv->ne[2], kv->ne[3], kv->nb[1]/2, kv->nb[2]/2, kv->nb[3]/2, kv->nb[0]*(kv->ne[0]/2)); - k = ggml_cont(ctx, k); - v = ggml_cont(ctx, v); - q = reshape_tensor(ctx, q, heads); - k = reshape_tensor(ctx, k, heads); - v = reshape_tensor(ctx, v, heads); - scale = 1.f / sqrt(sqrt((float)dim_head)); - k = ggml_scale_inplace(ctx, k, scale); - q = ggml_scale_inplace(ctx, q, scale); + auto kv = to_kv->forward(ctx, kv_input); + auto k = ggml_view_4d(ctx, kv, kv->ne[0] / 2, kv->ne[1], kv->ne[2], kv->ne[3], kv->nb[1] / 2, kv->nb[2] / 2, kv->nb[3] / 2, 0); + auto v = ggml_view_4d(ctx, kv, kv->ne[0] / 2, kv->ne[1], kv->ne[2], kv->ne[3], kv->nb[1] / 2, kv->nb[2] / 2, kv->nb[3] / 2, kv->nb[0] * (kv->ne[0] / 2)); + k = ggml_cont(ctx, k); + v = ggml_cont(ctx, v); + q = reshape_tensor(ctx, q, heads); + k = reshape_tensor(ctx, k, heads); + v = reshape_tensor(ctx, v, heads); + scale = 1.f / sqrt(sqrt((float)dim_head)); + k = ggml_scale_inplace(ctx, k, scale); + q = ggml_scale_inplace(ctx, q, scale); // auto weight = ggml_mul_mat(ctx, q, k); - auto weight = ggml_mul_mat(ctx, k, q); // NOTE order of mul is opposite to pytorch + auto weight = ggml_mul_mat(ctx, k, q); // NOTE order of mul is opposite to pytorch // GGML's softmax() is equivalent to pytorch's softmax(x, dim=-1) // in this case, dimension along which Softmax will be computed is the last dim @@ -192,13 +184,13 @@ struct PerceiverAttention : public GGMLBlock { // last dimension (varying most rapidly) corresponds to GGML's first (varying most rapidly). // weight = ggml_soft_max(ctx, weight); weight = ggml_soft_max_inplace(ctx, weight); - v = ggml_cont(ctx, ggml_transpose(ctx, v)); + v = ggml_cont(ctx, ggml_transpose(ctx, v)); // auto out = ggml_mul_mat(ctx, weight, v); - auto out = ggml_mul_mat(ctx, v, weight); // NOTE order of mul is opposite to pytorch - out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); - out = ggml_reshape_3d(ctx, out, ne[0], ne[1], ggml_nelements(out)/(ne[0]*ne[1])); - auto to_out = std::dynamic_pointer_cast(blocks["to_out"]); - out = to_out->forward(ctx, out); + auto out = ggml_mul_mat(ctx, v, weight); // NOTE order of mul is opposite to pytorch + out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); + out = ggml_reshape_3d(ctx, out, ne[0], ne[1], ggml_nelements(out) / (ne[0] * ne[1])); + auto to_out = std::dynamic_pointer_cast(blocks["to_out"]); + out = to_out->forward(ctx, out); return out; } }; @@ -206,45 +198,46 @@ struct PerceiverAttention : public GGMLBlock { struct FacePerceiverResampler : public GGMLBlock { // network hparams int depth; + public: - FacePerceiverResampler( int dim=768, - int d=4, - int dim_head=64, - int heads=16, - int embedding_dim=1280, - int output_dim=768, - int ff_mult=4) - : depth(d) { - blocks["proj_in"] = std::shared_ptr(new Linear(embedding_dim, dim, true)); + FacePerceiverResampler(int dim = 768, + int d = 4, + int dim_head = 64, + int heads = 16, + int embedding_dim = 1280, + int output_dim = 768, + int ff_mult = 4) + : depth(d) { + blocks["proj_in"] = std::shared_ptr(new Linear(embedding_dim, dim, true)); blocks["proj_out"] = std::shared_ptr(new Linear(dim, output_dim, true)); blocks["norm_out"] = std::shared_ptr(new LayerNorm(output_dim)); for (int i = 0; i < depth; i++) { std::string name = "layers." + std::to_string(i) + ".0"; blocks[name] = std::shared_ptr(new PerceiverAttention(dim, dim_head, heads)); - name = "layers." + std::to_string(i) + ".1"; + name = "layers." + std::to_string(i) + ".1"; blocks[name] = std::shared_ptr(new PMFeedForward(dim, ff_mult)); } } struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* latents, - struct ggml_tensor* x){ + struct ggml_tensor* x) { // x: [N, channels, h, w] - auto proj_in = std::dynamic_pointer_cast(blocks["proj_in"]); - auto proj_out = std::dynamic_pointer_cast(blocks["proj_out"]); - auto norm_out = std::dynamic_pointer_cast(blocks["norm_out"]); + auto proj_in = std::dynamic_pointer_cast(blocks["proj_in"]); + auto proj_out = std::dynamic_pointer_cast(blocks["proj_out"]); + auto norm_out = std::dynamic_pointer_cast(blocks["norm_out"]); x = proj_in->forward(ctx, x); for (int i = 0; i < depth; i++) { std::string name = "layers." + std::to_string(i) + ".0"; - auto attn = std::dynamic_pointer_cast(blocks[name]); - name = "layers." + std::to_string(i) + ".1"; - auto ff = std::dynamic_pointer_cast(blocks[name]); - auto t = attn->forward(ctx, x, latents); - latents = ggml_add(ctx, t, latents); - t = ff->forward(ctx, latents); - latents = ggml_add(ctx, t, latents); + auto attn = std::dynamic_pointer_cast(blocks[name]); + name = "layers." + std::to_string(i) + ".1"; + auto ff = std::dynamic_pointer_cast(blocks[name]); + auto t = attn->forward(ctx, x, latents); + latents = ggml_add(ctx, t, latents); + t = ff->forward(ctx, latents); + latents = ggml_add(ctx, t, latents); } latents = proj_out->forward(ctx, latents); latents = norm_out->forward(ctx, latents); @@ -258,51 +251,49 @@ struct QFormerPerceiver : public GGMLBlock { int cross_attention_dim; bool use_residul; - public: - QFormerPerceiver(int id_embeddings_dim, int cross_attention_d, int num_t, int embedding_dim=1024, - bool use_r=true, int ratio=4) - : cross_attention_dim(cross_attention_d), num_tokens(num_t), use_residul(use_r) { - blocks["token_proj"] = std::shared_ptr(new Mlp(id_embeddings_dim, - id_embeddings_dim*ratio, - cross_attention_dim*num_tokens, - true)); - blocks["token_norm"] = std::shared_ptr(new LayerNorm(cross_attention_d)); + QFormerPerceiver(int id_embeddings_dim, int cross_attention_d, int num_t, int embedding_dim = 1024, bool use_r = true, int ratio = 4) + : cross_attention_dim(cross_attention_d), num_tokens(num_t), use_residul(use_r) { + blocks["token_proj"] = std::shared_ptr(new Mlp(id_embeddings_dim, + id_embeddings_dim * ratio, + cross_attention_dim * num_tokens, + true)); + blocks["token_norm"] = std::shared_ptr(new LayerNorm(cross_attention_d)); blocks["perceiver_resampler"] = std::shared_ptr(new FacePerceiverResampler( - cross_attention_dim, - 4, - 128, - cross_attention_dim / 128, - embedding_dim, - cross_attention_dim, - 4)); + cross_attention_dim, + 4, + 128, + cross_attention_dim / 128, + embedding_dim, + cross_attention_dim, + 4)); } - /* + /* def forward(self, x, last_hidden_state): x = self.token_proj(x) x = x.reshape(-1, self.num_tokens, self.cross_attention_dim) x = self.token_norm(x) # cls token out = self.perceiver_resampler(x, last_hidden_state) # retrieve from patch tokens if self.use_residual: # TODO: if use_residual is not true - out = x + 1.0 * out + out = x + 1.0 * out return out */ struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x, - struct ggml_tensor* last_hidden_state){ + struct ggml_tensor* last_hidden_state) { // x: [N, channels, h, w] - auto token_proj = std::dynamic_pointer_cast(blocks["token_proj"]); - auto token_norm = std::dynamic_pointer_cast(blocks["token_norm"]); + auto token_proj = std::dynamic_pointer_cast(blocks["token_proj"]); + auto token_norm = std::dynamic_pointer_cast(blocks["token_norm"]); auto perceiver_resampler = std::dynamic_pointer_cast(blocks["perceiver_resampler"]); - x = token_proj->forward(ctx, x); - int64_t nel = ggml_nelements(x); - x = ggml_reshape_3d(ctx, x, cross_attention_dim, num_tokens, nel/(cross_attention_dim*num_tokens)); - x = token_norm->forward(ctx, x); + x = token_proj->forward(ctx, x); + int64_t nel = ggml_nelements(x); + x = ggml_reshape_3d(ctx, x, cross_attention_dim, num_tokens, nel / (cross_attention_dim * num_tokens)); + x = token_norm->forward(ctx, x); struct ggml_tensor* out = perceiver_resampler->forward(ctx, x, last_hidden_state); - if(use_residul) + if (use_residul) out = ggml_add(ctx, x, out); return out; } @@ -322,7 +313,7 @@ class FacePerceiverResampler(torch.nn.Module): ff_mult=4, ): super().__init__() - + self.proj_in = torch.nn.Linear(embedding_dim, dim) self.proj_out = torch.nn.Linear(dim, output_dim) self.norm_out = torch.nn.LayerNorm(output_dim) @@ -346,8 +337,6 @@ class FacePerceiverResampler(torch.nn.Module): return self.norm_out(latents) */ - - /* def FeedForward(dim, mult=4): @@ -417,9 +406,6 @@ class PerceiverAttention(nn.Module): */ - - - struct FuseModule : public GGMLBlock { // network hparams int embed_dim; @@ -485,9 +471,9 @@ struct FuseModule : public GGMLBlock { // print_ggml_tensor(class_tokens_mask_pos, true, "class_tokens_mask_pos"); struct ggml_tensor* image_token_embeds = ggml_get_rows(ctx, prompt_embeds, class_tokens_mask_pos); ggml_set_name(image_token_embeds, "image_token_embeds"); - valid_id_embeds = ggml_reshape_2d(ctx, valid_id_embeds, valid_id_embeds->ne[0], - ggml_nelements(valid_id_embeds)/valid_id_embeds->ne[0]); - struct ggml_tensor* stacked_id_embeds = fuse_fn(ctx, image_token_embeds, valid_id_embeds); + valid_id_embeds = ggml_reshape_2d(ctx, valid_id_embeds, valid_id_embeds->ne[0], + ggml_nelements(valid_id_embeds) / valid_id_embeds->ne[0]); + struct ggml_tensor* stacked_id_embeds = fuse_fn(ctx, image_token_embeds, valid_id_embeds); // stacked_id_embeds = ggml_cont(ctx, ggml_permute(ctx, stacked_id_embeds, 0, 2, 1, 3)); // print_ggml_tensor(stacked_id_embeds, true, "AA stacked_id_embeds"); @@ -555,14 +541,13 @@ struct PhotoMakerIDEncoderBlock : public CLIPVisionModelProjection { }; struct PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock : public CLIPVisionModelProjection { - int cross_attention_dim; int num_tokens; - PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock(int id_embeddings_dim=512) + PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock(int id_embeddings_dim = 512) : CLIPVisionModelProjection(OPENAI_CLIP_VIT_L_14), - cross_attention_dim (2048), - num_tokens(2) { + cross_attention_dim(2048), + num_tokens(2) { blocks["visual_projection_2"] = std::shared_ptr(new Linear(1024, 1280, false)); blocks["fuse_module"] = std::shared_ptr(new FuseModule(2048)); /* @@ -571,14 +556,13 @@ struct PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock : public CLIPVisionMo self.num_tokens = 2 self.cross_attention_dim = cross_attention_dim self.qformer_perceiver = QFormerPerceiver( - id_embeddings_dim, - cross_attention_dim, + id_embeddings_dim, + cross_attention_dim, self.num_tokens, )*/ - blocks["qformer_perceiver"] = std::shared_ptr(new QFormerPerceiver(id_embeddings_dim, - cross_attention_dim, - num_tokens)); - + blocks["qformer_perceiver"] = std::shared_ptr(new QFormerPerceiver(id_embeddings_dim, + cross_attention_dim, + num_tokens)); } /* @@ -603,14 +587,14 @@ struct PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock : public CLIPVisionMo struct ggml_tensor* left, struct ggml_tensor* right) { // x: [N, channels, h, w] - auto vision_model = std::dynamic_pointer_cast(blocks["vision_model"]); - auto fuse_module = std::dynamic_pointer_cast(blocks["fuse_module"]); - auto qformer_perceiver = std::dynamic_pointer_cast(blocks["qformer_perceiver"]); + auto vision_model = std::dynamic_pointer_cast(blocks["vision_model"]); + auto fuse_module = std::dynamic_pointer_cast(blocks["fuse_module"]); + auto qformer_perceiver = std::dynamic_pointer_cast(blocks["qformer_perceiver"]); // struct ggml_tensor* last_hidden_state = vision_model->forward(ctx, id_pixel_values); // [N, hidden_size] - struct ggml_tensor* last_hidden_state = vision_model->forward(ctx, id_pixel_values, false); // [N, hidden_size] - id_embeds = qformer_perceiver->forward(ctx, id_embeds, last_hidden_state); - + struct ggml_tensor* last_hidden_state = vision_model->forward(ctx, id_pixel_values, false); // [N, hidden_size] + id_embeds = qformer_perceiver->forward(ctx, id_embeds, last_hidden_state); + struct ggml_tensor* updated_prompt_embeds = fuse_module->forward(ctx, prompt_embeds, id_embeds, @@ -623,7 +607,7 @@ struct PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock : public CLIPVisionMo struct PhotoMakerIDEncoder : public GGMLRunner { public: - SDVersion version = VERSION_SDXL; + SDVersion version = VERSION_SDXL; PMVersion pm_version = VERSION_1; PhotoMakerIDEncoderBlock id_encoder; PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock id_encoder2; @@ -639,15 +623,14 @@ struct PhotoMakerIDEncoder : public GGMLRunner { std::vector zeros_right; public: - PhotoMakerIDEncoder(ggml_backend_t backend, ggml_type wtype, SDVersion version = VERSION_SDXL, - PMVersion pm_v = VERSION_1, float sty = 20.f) + PhotoMakerIDEncoder(ggml_backend_t backend, ggml_type wtype, SDVersion version = VERSION_SDXL, PMVersion pm_v = VERSION_1, float sty = 20.f) : GGMLRunner(backend, wtype), version(version), pm_version(pm_v), style_strength(sty) { - if(pm_version == VERSION_1){ + if (pm_version == VERSION_1) { id_encoder.init(params_ctx, wtype); - }else if(pm_version == VERSION_2){ + } else if (pm_version == VERSION_2) { id_encoder2.init(params_ctx, wtype); } } @@ -656,17 +639,15 @@ struct PhotoMakerIDEncoder : public GGMLRunner { return "pmid"; } - PMVersion get_version() const{ + PMVersion get_version() const { return pm_version; } - void get_param_tensors(std::map& tensors, const std::string prefix) { - if(pm_version == VERSION_1) + if (pm_version == VERSION_1) id_encoder.get_param_tensors(tensors, prefix); - else if(pm_version == VERSION_2) + else if (pm_version == VERSION_2) id_encoder2.get_param_tensors(tensors, prefix); - } struct ggml_cgraph* build_graph( // struct ggml_allocr* allocr, @@ -753,14 +734,14 @@ struct PhotoMakerIDEncoder : public GGMLRunner { } } struct ggml_tensor* updated_prompt_embeds = NULL; - if(pm_version == VERSION_1) + if (pm_version == VERSION_1) updated_prompt_embeds = id_encoder.forward(ctx0, - id_pixel_values_d, - prompt_embeds_d, - class_tokens_mask_d, - class_tokens_mask_pos, - left, right); - else if(pm_version == VERSION_2) + id_pixel_values_d, + prompt_embeds_d, + class_tokens_mask_d, + class_tokens_mask_pos, + left, right); + else if (pm_version == VERSION_2) updated_prompt_embeds = id_encoder2.forward(ctx0, id_pixel_values_d, prompt_embeds_d, @@ -791,22 +772,19 @@ struct PhotoMakerIDEncoder : public GGMLRunner { } }; - struct PhotoMakerIDEmbed : public GGMLRunner { - std::map tensors; std::string file_path; - ModelLoader *model_loader; + ModelLoader* model_loader; bool load_failed = false; bool applied = false; PhotoMakerIDEmbed(ggml_backend_t backend, - ggml_type wtype, - ModelLoader *ml, - const std::string& file_path = "", - const std::string& prefix = "") - : file_path(file_path), GGMLRunner(backend, wtype), - model_loader(ml) { + ggml_type wtype, + ModelLoader* ml, + const std::string& file_path = "", + const std::string& prefix = "") + : file_path(file_path), GGMLRunner(backend, wtype), model_loader(ml) { if (!model_loader->init_from_file(file_path, prefix)) { load_failed = true; } @@ -831,13 +809,13 @@ struct PhotoMakerIDEmbed : public GGMLRunner { if (filter_tensor && !contains(name, "pmid.id_embeds")) { // LOG_INFO("skipping LoRA tesnor '%s'", name.c_str()); return true; - } + } if (dry_run) { struct ggml_tensor* real = ggml_new_tensor(params_ctx, tensor_storage.type, tensor_storage.n_dims, tensor_storage.ne); - tensors[name] = real; + tensors[name] = real; } else { auto real = tensors[name]; *dst_tensor = real; @@ -856,13 +834,12 @@ struct PhotoMakerIDEmbed : public GGMLRunner { return true; } - - struct ggml_tensor* get(){ + struct ggml_tensor* get() { std::map::iterator pos; pos = tensors.find("pmid.id_embeds"); - if(pos != tensors.end()) + if (pos != tensors.end()) return pos->second; - return NULL; + return NULL; } }; diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index d70dab1ce..c722b6539 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -156,7 +156,8 @@ class StableDiffusionGGML { schedule_t schedule, bool clip_on_cpu, bool control_net_cpu, - bool vae_on_cpu) { + bool vae_on_cpu, + bool diffusion_flash_attn) { use_tiny_autoencoder = taesd_path.size() > 0; #ifdef SD_USE_CUBLAS LOG_DEBUG("Using CUDA backend"); @@ -185,13 +186,7 @@ class StableDiffusionGGML { LOG_DEBUG("Using CPU backend"); backend = ggml_backend_cpu_init(); } -#ifdef SD_USE_FLASH_ATTENTION -#if defined(SD_USE_CUBLAS) || defined(SD_USE_METAL) || defined(SD_USE_SYCL) || defined(SD_USE_VULKAN) - LOG_WARN("Flash Attention not supported with GPU Backend"); -#else - LOG_INFO("Flash Attention enabled"); -#endif -#endif + ModelLoader model_loader; vae_tiling = vae_tiling_; @@ -325,19 +320,25 @@ class StableDiffusionGGML { LOG_INFO("CLIP: Using CPU backend"); clip_backend = ggml_backend_cpu_init(); } + if (diffusion_flash_attn) { + LOG_INFO("Using flash attention in the diffusion model"); + } if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { + if (diffusion_flash_attn) { + LOG_WARN("flash attention in this diffusion model is currently unsupported!"); + } cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); - diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); + diffusion_model = std::make_shared(backend, diffusion_model_wtype, version, diffusion_flash_attn); } else { - if(id_embeddings_path.find("v2") != std::string::npos) { + if (id_embeddings_path.find("v2") != std::string::npos) { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version, VERSION_2); - }else{ + } else { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version); - } - diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); + } + diffusion_model = std::make_shared(backend, diffusion_model_wtype, version, diffusion_flash_attn); } cond_stage_model->alloc_params_buffer(); cond_stage_model->get_param_tensors(tensors); @@ -371,7 +372,7 @@ class StableDiffusionGGML { control_net = std::make_shared(controlnet_backend, diffusion_model_wtype, version); } - if(id_embeddings_path.find("v2") != std::string::npos) { + if (id_embeddings_path.find("v2") != std::string::npos) { pmid_model = std::make_shared(backend, model_wtype, version, VERSION_2); LOG_INFO("using PhotoMaker Version 2"); } else { @@ -1081,7 +1082,8 @@ sd_ctx_t* new_sd_ctx(const char* model_path_c_str, enum schedule_t s, bool keep_clip_on_cpu, bool keep_control_net_cpu, - bool keep_vae_on_cpu) { + bool keep_vae_on_cpu, + bool diffusion_flash_attn) { sd_ctx_t* sd_ctx = (sd_ctx_t*)malloc(sizeof(sd_ctx_t)); if (sd_ctx == NULL) { return NULL; @@ -1122,7 +1124,8 @@ sd_ctx_t* new_sd_ctx(const char* model_path_c_str, s, keep_clip_on_cpu, keep_control_net_cpu, - keep_vae_on_cpu)) { + keep_vae_on_cpu, + diffusion_flash_attn)) { delete sd_ctx->sd; sd_ctx->sd = NULL; free(sd_ctx); @@ -1217,9 +1220,9 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, for (std::string img_file : img_files) { int c = 0; int width, height; - if(ends_with(img_file, "safetensors")){ + if (ends_with(img_file, "safetensors")) { continue; - } + } uint8_t* input_image_buffer = stbi_load(img_file.c_str(), &width, &height, &c, 3); if (input_image_buffer == NULL) { LOG_ERROR("PhotoMaker load image from '%s' failed", img_file.c_str()); @@ -1257,18 +1260,18 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, else sd_mul_images_to_tensor(init_image->data, init_img, i, NULL, NULL); } - t0 = ggml_time_ms(); - auto cond_tup = sd_ctx->sd->cond_stage_model->get_learned_condition_with_trigger(work_ctx, - sd_ctx->sd->n_threads, prompt, - clip_skip, - width, - height, - num_input_images, - sd_ctx->sd->diffusion_model->get_adm_in_channels()); - id_cond = std::get<0>(cond_tup); - class_tokens_mask = std::get<1>(cond_tup); // + t0 = ggml_time_ms(); + auto cond_tup = sd_ctx->sd->cond_stage_model->get_learned_condition_with_trigger(work_ctx, + sd_ctx->sd->n_threads, prompt, + clip_skip, + width, + height, + num_input_images, + sd_ctx->sd->diffusion_model->get_adm_in_channels()); + id_cond = std::get<0>(cond_tup); + class_tokens_mask = std::get<1>(cond_tup); // struct ggml_tensor* id_embeds = NULL; - if(pmv2){ + if (pmv2) { // id_embeds = sd_ctx->sd->pmid_id_embeds->get(); id_embeds = load_tensor_from_file(work_ctx, path_join(input_id_images_path, "id_embeds.bin")); // print_ggml_tensor(id_embeds, true, "id_embeds:"); diff --git a/stable-diffusion.h b/stable-diffusion.h index b310ee595..f3e71beff 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -142,7 +142,8 @@ SD_API sd_ctx_t* new_sd_ctx(const char* model_path, enum schedule_t s, bool keep_clip_on_cpu, bool keep_control_net_cpu, - bool keep_vae_on_cpu); + bool keep_vae_on_cpu, + bool diffusion_flash_attn); SD_API void free_sd_ctx(sd_ctx_t* sd_ctx); diff --git a/unet.hpp b/unet.hpp index 94a8ba46a..79f702c4d 100644 --- a/unet.hpp +++ b/unet.hpp @@ -183,7 +183,7 @@ class UnetModelBlock : public GGMLBlock { int model_channels = 320; int adm_in_channels = 2816; // only for VERSION_SDXL/SVD - UnetModelBlock(SDVersion version = VERSION_SD1) + UnetModelBlock(SDVersion version = VERSION_SD1, bool flash_attn = false) : version(version) { if (version == VERSION_SD2) { context_dim = 1024; @@ -242,7 +242,7 @@ class UnetModelBlock : public GGMLBlock { if (version == VERSION_SVD) { return new SpatialVideoTransformer(in_channels, n_head, d_head, depth, context_dim); } else { - return new SpatialTransformer(in_channels, n_head, d_head, depth, context_dim); + return new SpatialTransformer(in_channels, n_head, d_head, depth, context_dim, flash_attn); } }; @@ -533,8 +533,9 @@ struct UNetModelRunner : public GGMLRunner { UNetModelRunner(ggml_backend_t backend, ggml_type wtype, - SDVersion version = VERSION_SD1) - : GGMLRunner(backend, wtype), unet(version) { + SDVersion version = VERSION_SD1, + bool flash_attn = false) + : GGMLRunner(backend, wtype), unet(version, flash_attn) { unet.init(params_ctx, wtype); } @@ -649,4 +650,4 @@ struct UNetModelRunner : public GGMLRunner { } }; -#endif // __UNET_HPP__ \ No newline at end of file +#endif // __UNET_HPP__ diff --git a/util.cpp b/util.cpp index cd058bb07..b8a65e7d9 100644 --- a/util.cpp +++ b/util.cpp @@ -279,12 +279,12 @@ std::string path_join(const std::string& p1, const std::string& p2) { std::vector splitString(const std::string& str, char delimiter) { std::vector result; size_t start = 0; - size_t end = str.find(delimiter); + size_t end = str.find(delimiter); while (end != std::string::npos) { result.push_back(str.substr(start, end - start)); start = end + 1; - end = str.find(delimiter, start); + end = str.find(delimiter, start); } // Add the last segment after the last delimiter @@ -293,7 +293,6 @@ std::vector splitString(const std::string& str, char delimiter) { return result; } - sd_image_t* preprocess_id_image(sd_image_t* img) { int shortest_edge = 224; int size = shortest_edge; diff --git a/vae.hpp b/vae.hpp index 8642375f8..c32846a30 100644 --- a/vae.hpp +++ b/vae.hpp @@ -99,10 +99,12 @@ class AttnBlock : public UnaryBlock { k = ggml_cont(ctx, ggml_permute(ctx, k, 1, 2, 0, 3)); // [N, h, w, in_channels] k = ggml_reshape_3d(ctx, k, c, h * w, n); // [N, h * w, in_channels] - auto v = v_proj->forward(ctx, h_); // [N, in_channels, h, w] - v = ggml_reshape_3d(ctx, v, h * w, c, n); // [N, in_channels, h * w] + auto v = v_proj->forward(ctx, h_); // [N, in_channels, h, w] + v = ggml_cont(ctx, ggml_permute(ctx, v, 1, 2, 0, 3)); // [N, h, w, in_channels] + v = ggml_reshape_3d(ctx, v, c, h * w, n); // [N, h * w, in_channels] - h_ = ggml_nn_attention(ctx, q, k, v, false); // [N, h * w, in_channels] + // h_ = ggml_nn_attention(ctx, q, k, v, false); // [N, h * w, in_channels] + h_ = ggml_nn_attention_ext(ctx, q, k, v, 1, nullptr, false, true, false); h_ = ggml_cont(ctx, ggml_permute(ctx, h_, 1, 0, 2, 3)); // [N, in_channels, h * w] h_ = ggml_reshape_4d(ctx, h_, w, h, c, n); // [N, in_channels, h, w] @@ -612,4 +614,4 @@ struct AutoEncoderKL : public GGMLRunner { }; }; -#endif \ No newline at end of file +#endif From b5f493269609fd7e52db56cbb7e757ebf549bc20 Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 23 Nov 2024 13:02:44 +0800 Subject: [PATCH 011/143] refactor: add some sd vesion helper functions --- conditioner.hpp | 8 ++++---- model.h | 25 ++++++++++++++++++++++-- pmid.hpp | 16 +++++++-------- stable-diffusion.cpp | 46 ++++++++++++++++++++++---------------------- vae.hpp | 2 +- 5 files changed, 59 insertions(+), 38 deletions(-) diff --git a/conditioner.hpp b/conditioner.hpp index 47fd3eb6e..9a6300997 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -44,7 +44,7 @@ struct Conditioner { // Ref: https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cad87bf4e3e0b0a759afa94e933527c3123d59bc/modules/sd_hijack_clip.py#L283 struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { SDVersion version = VERSION_SD1; - PMVersion pm_version = VERSION_1; + PMVersion pm_version = PM_VERSION_1; CLIPTokenizer tokenizer; ggml_type wtype; std::shared_ptr text_model; @@ -60,7 +60,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { ggml_type wtype, const std::string& embd_dir, SDVersion version = VERSION_SD1, - PMVersion pv = VERSION_1, + PMVersion pv = PM_VERSION_1, int clip_skip = -1) : version(version), pm_version(pv), tokenizer(version == VERSION_SD2 ? 0 : 49407), embd_dir(embd_dir), wtype(wtype) { if (clip_skip <= 0) { @@ -270,7 +270,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { std::vector clean_input_ids_tmp; for (uint32_t i = 0; i < class_token_index[0]; i++) clean_input_ids_tmp.push_back(clean_input_ids[i]); - for (uint32_t i = 0; i < (pm_version == VERSION_2 ? 2 * num_input_imgs : num_input_imgs); i++) + for (uint32_t i = 0; i < (pm_version == PM_VERSION_2 ? 2 * num_input_imgs : num_input_imgs); i++) clean_input_ids_tmp.push_back(class_token); for (uint32_t i = class_token_index[0] + 1; i < clean_input_ids.size(); i++) clean_input_ids_tmp.push_back(clean_input_ids[i]); @@ -286,7 +286,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { // weights.insert(weights.begin(), 1.0); tokenizer.pad_tokens(tokens, weights, max_length, padding); - int offset = pm_version == VERSION_2 ? 2 * num_input_imgs : num_input_imgs; + int offset = pm_version == PM_VERSION_2 ? 2 * num_input_imgs : num_input_imgs; for (uint32_t i = 0; i < tokens.size(); i++) { // if (class_idx + 1 <= i && i < class_idx + 1 + 2*num_input_imgs) // photomaker V2 has num_tokens(=2)*num_input_imgs if (class_idx + 1 <= i && i < class_idx + 1 + offset) // photomaker V2 has num_tokens(=2)*num_input_imgs diff --git a/model.h b/model.h index 552a2ccd8..b7e3b3a2e 100644 --- a/model.h +++ b/model.h @@ -31,9 +31,30 @@ enum SDVersion { VERSION_COUNT, }; +static inline bool sd_version_is_flux(SDVersion version) { + if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + return true; + } + return false; +} + +static inline bool sd_version_is_sd3(SDVersion version) { + if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { + return true; + } + return false; +} + +static inline bool sd_version_is_dit(SDVersion version) { + if (sd_version_is_flux(version) || sd_version_is_sd3(version)) { + return true; + } + return false; +} + enum PMVersion { - VERSION_1, - VERSION_2, + PM_VERSION_1, + PM_VERSION_2, }; struct TensorStorage { diff --git a/pmid.hpp b/pmid.hpp index b8555eb68..defb4f05a 100644 --- a/pmid.hpp +++ b/pmid.hpp @@ -608,7 +608,7 @@ struct PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock : public CLIPVisionMo struct PhotoMakerIDEncoder : public GGMLRunner { public: SDVersion version = VERSION_SDXL; - PMVersion pm_version = VERSION_1; + PMVersion pm_version = PM_VERSION_1; PhotoMakerIDEncoderBlock id_encoder; PhotoMakerIDEncoder_CLIPInsightfaceExtendtokenBlock id_encoder2; float style_strength; @@ -623,14 +623,14 @@ struct PhotoMakerIDEncoder : public GGMLRunner { std::vector zeros_right; public: - PhotoMakerIDEncoder(ggml_backend_t backend, ggml_type wtype, SDVersion version = VERSION_SDXL, PMVersion pm_v = VERSION_1, float sty = 20.f) + PhotoMakerIDEncoder(ggml_backend_t backend, ggml_type wtype, SDVersion version = VERSION_SDXL, PMVersion pm_v = PM_VERSION_1, float sty = 20.f) : GGMLRunner(backend, wtype), version(version), pm_version(pm_v), style_strength(sty) { - if (pm_version == VERSION_1) { + if (pm_version == PM_VERSION_1) { id_encoder.init(params_ctx, wtype); - } else if (pm_version == VERSION_2) { + } else if (pm_version == PM_VERSION_2) { id_encoder2.init(params_ctx, wtype); } } @@ -644,9 +644,9 @@ struct PhotoMakerIDEncoder : public GGMLRunner { } void get_param_tensors(std::map& tensors, const std::string prefix) { - if (pm_version == VERSION_1) + if (pm_version == PM_VERSION_1) id_encoder.get_param_tensors(tensors, prefix); - else if (pm_version == VERSION_2) + else if (pm_version == PM_VERSION_2) id_encoder2.get_param_tensors(tensors, prefix); } @@ -734,14 +734,14 @@ struct PhotoMakerIDEncoder : public GGMLRunner { } } struct ggml_tensor* updated_prompt_embeds = NULL; - if (pm_version == VERSION_1) + if (pm_version == PM_VERSION_1) updated_prompt_embeds = id_encoder.forward(ctx0, id_pixel_values_d, prompt_embeds_d, class_tokens_mask_d, class_tokens_mask_pos, left, right); - else if (pm_version == VERSION_2) + else if (pm_version == PM_VERSION_2) updated_prompt_embeds = id_encoder2.forward(ctx0, id_pixel_values_d, prompt_embeds_d, diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index c722b6539..5024b5f27 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -286,9 +286,9 @@ class StableDiffusionGGML { "try specifying SDXL VAE FP16 Fix with the --vae parameter. " "You can find it here: https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/blob/main/sdxl_vae.safetensors"); } - } else if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { + } else if (sd_version_is_sd3(version)) { scale_factor = 1.5305f; - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + } else if (sd_version_is_flux(version)) { scale_factor = 0.3611; // TODO: shift_factor } @@ -309,7 +309,7 @@ class StableDiffusionGGML { } else { clip_backend = backend; bool use_t5xxl = false; - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + if (sd_version_is_dit(version)) { use_t5xxl = true; } if (!ggml_backend_is_cpu(backend) && use_t5xxl && conditioner_wtype != GGML_TYPE_F32) { @@ -323,18 +323,18 @@ class StableDiffusionGGML { if (diffusion_flash_attn) { LOG_INFO("Using flash attention in the diffusion model"); } - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(version)) { if (diffusion_flash_attn) { LOG_WARN("flash attention in this diffusion model is currently unsupported!"); } cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + } else if (sd_version_is_flux(version)) { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); diffusion_model = std::make_shared(backend, diffusion_model_wtype, version, diffusion_flash_attn); } else { if (id_embeddings_path.find("v2") != std::string::npos) { - cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version, VERSION_2); + cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version, PM_VERSION_2); } else { cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version); } @@ -373,7 +373,7 @@ class StableDiffusionGGML { } if (id_embeddings_path.find("v2") != std::string::npos) { - pmid_model = std::make_shared(backend, model_wtype, version, VERSION_2); + pmid_model = std::make_shared(backend, model_wtype, version, PM_VERSION_2); LOG_INFO("using PhotoMaker Version 2"); } else { pmid_model = std::make_shared(backend, model_wtype, version); @@ -527,10 +527,10 @@ class StableDiffusionGGML { is_using_v_parameterization = true; } - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(version)) { LOG_INFO("running in FLOW mode"); denoiser = std::make_shared(); - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + } else if (sd_version_is_flux(version)) { LOG_INFO("running in Flux FLOW mode"); float shift = 1.15f; if (version == VERSION_FLUX_SCHNELL) { @@ -804,7 +804,7 @@ class StableDiffusionGGML { out_uncond = ggml_dup_tensor(work_ctx, x); } if (has_skiplayer) { - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_2B || version == VERSION_SD3_5_8B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL) { + if (sd_version_is_dit(version)) { out_skip = ggml_dup_tensor(work_ctx, x); } else { has_skiplayer = false; @@ -995,9 +995,9 @@ class StableDiffusionGGML { if (use_tiny_autoencoder) { C = 4; } else { - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(version)) { C = 32; - } else if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + } else if (sd_version_is_flux(version)) { C = 32; } } @@ -1214,7 +1214,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, } // preprocess input id images std::vector input_id_images; - bool pmv2 = sd_ctx->sd->pmid_model->get_version() == VERSION_2; + bool pmv2 = sd_ctx->sd->pmid_model->get_version() == PM_VERSION_2; if (sd_ctx->sd->pmid_model && input_id_images_path.size() > 0) { std::vector img_files = get_files_from_dir(input_id_images_path); for (std::string img_file : img_files) { @@ -1343,9 +1343,9 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, // Sample std::vector final_latents; // collect latents to decode int C = 4; - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(sd_ctx->sd->version)) { C = 16; - } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { + } else if (sd_version_is_flux(sd_ctx->sd->version)) { C = 16; } int W = width / 8; @@ -1464,10 +1464,10 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, struct ggml_init_params params; params.mem_size = static_cast(10 * 1024 * 1024); // 10 MB - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(sd_ctx->sd->version)) { params.mem_size *= 3; } - if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { + if (sd_version_is_flux(sd_ctx->sd->version)) { params.mem_size *= 4; } if (sd_ctx->sd->stacked_id) { @@ -1490,17 +1490,17 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); int C = 4; - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(sd_ctx->sd->version)) { C = 16; - } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { + } else if (sd_version_is_flux(sd_ctx->sd->version)) { C = 16; } int W = width / 8; int H = height / 8; ggml_tensor* init_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, 1); - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(sd_ctx->sd->version)) { ggml_set_f32(init_latent, 0.0609f); - } else if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { + } else if (sd_version_is_flux(sd_ctx->sd->version)) { ggml_set_f32(init_latent, 0.1159f); } else { ggml_set_f32(init_latent, 0.f); @@ -1567,10 +1567,10 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, struct ggml_init_params params; params.mem_size = static_cast(10 * 1024 * 1024); // 10 MB - if (sd_ctx->sd->version == VERSION_SD3_2B || sd_ctx->sd->version == VERSION_SD3_5_8B || sd_ctx->sd->version == VERSION_SD3_5_2B) { + if (sd_version_is_sd3(sd_ctx->sd->version)) { params.mem_size *= 2; } - if (sd_ctx->sd->version == VERSION_FLUX_DEV || sd_ctx->sd->version == VERSION_FLUX_SCHNELL || sd_ctx->sd->version == VERSION_FLUX_LITE) { + if (sd_version_is_flux(sd_ctx->sd->version)) { params.mem_size *= 3; } if (sd_ctx->sd->stacked_id) { diff --git a/vae.hpp b/vae.hpp index c32846a30..0c7d84f9f 100644 --- a/vae.hpp +++ b/vae.hpp @@ -459,7 +459,7 @@ class AutoencodingEngine : public GGMLBlock { bool use_video_decoder = false, SDVersion version = VERSION_SD1) : decode_only(decode_only), use_video_decoder(use_video_decoder) { - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B || version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + if (sd_version_is_dit(version)) { dd_config.z_channels = 16; use_quant = false; } From c3eeb669cd9b942ec4b66ed3d0bf28b42b1f9c28 Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 23 Nov 2024 13:29:32 +0800 Subject: [PATCH 012/143] sync: update ggml --- ggml | 2 +- ggml_extend.hpp | 19 +++++++------------ model.cpp | 13 +++++++------ stable-diffusion.h | 2 ++ util.cpp | 2 +- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/ggml b/ggml index 21d3a308f..6fcbd60bc 160000 --- a/ggml +++ b/ggml @@ -1 +1 @@ -Subproject commit 21d3a308fcb7f31cb9beceaeebad4fb622f3c337 +Subproject commit 6fcbd60bc72ac3f7ad43f78c87e535f2e6206f58 diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 75ad04142..fc679e704 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -22,6 +22,7 @@ #include "ggml-alloc.h" #include "ggml-backend.h" +#include "ggml-cpu.h" #include "ggml.h" #ifdef SD_USE_CUBLAS @@ -100,17 +101,11 @@ __STATIC_INLINE__ ggml_fp16_t ggml_tensor_get_f16(const ggml_tensor* tensor, int static struct ggml_tensor* get_tensor_from_graph(struct ggml_cgraph* gf, const char* name) { struct ggml_tensor* res = NULL; - for (int i = 0; i < gf->n_nodes; i++) { - // printf("%d, %s \n", i, gf->nodes[i]->name); - if (strcmp(ggml_get_name(gf->nodes[i]), name) == 0) { - res = gf->nodes[i]; - break; - } - } - for (int i = 0; i < gf->n_leafs; i++) { - // printf("%d, %s \n", i, gf->leafs[i]->name); - if (strcmp(ggml_get_name(gf->leafs[i]), name) == 0) { - res = gf->leafs[i]; + for (int i = 0; i < ggml_graph_n_nodes(gf); i++) { + struct ggml_tensor* node = ggml_graph_node(gf, i); + // printf("%d, %s \n", i, ggml_get_name(node)); + if (strcmp(ggml_get_name(node), name) == 0) { + res = node; break; } } @@ -1129,7 +1124,7 @@ struct GGMLRunner { ggml_graph_print(gf); #endif if (output != NULL) { - auto result = gf->nodes[gf->n_nodes - 1]; + auto result = ggml_graph_node(gf, -1); if (*output == NULL && output_ctx != NULL) { *output = ggml_dup_tensor(output_ctx, result); } diff --git a/model.cpp b/model.cpp index 2719f63c0..dba8187da 100644 --- a/model.cpp +++ b/model.cpp @@ -13,6 +13,7 @@ #include "ggml-alloc.h" #include "ggml-backend.h" +#include "ggml-cpu.h" #include "ggml.h" #include "stable-diffusion.h" @@ -733,25 +734,25 @@ void convert_tensor(void* src, if (src_type == GGML_TYPE_F16) { ggml_fp16_to_fp32_row((ggml_fp16_t*)src, (float*)dst, n); } else { - auto qtype = ggml_internal_get_type_traits(src_type); - if (qtype.to_float == NULL) { + auto qtype = ggml_get_type_traits(src_type); + if (qtype->to_float == NULL) { throw std::runtime_error(format("type %s unsupported for integer quantization: no dequantization available", ggml_type_name(src_type))); } - qtype.to_float(src, (float*)dst, n); + qtype->to_float(src, (float*)dst, n); } } else { // src_type == GGML_TYPE_F16 => dst_type is quantized // src_type is quantized => dst_type == GGML_TYPE_F16 or dst_type is quantized - auto qtype = ggml_internal_get_type_traits(src_type); - if (qtype.to_float == NULL) { + auto qtype = ggml_get_type_traits(src_type); + if (qtype->to_float == NULL) { throw std::runtime_error(format("type %s unsupported for integer quantization: no dequantization available", ggml_type_name(src_type))); } std::vector buf; buf.resize(sizeof(float) * n); char* src_data_f32 = buf.data(); - qtype.to_float(src, (float*)src_data_f32, n); + qtype->to_float(src, (float*)src_data_f32, n); if (dst_type == GGML_TYPE_F16) { ggml_fp32_to_fp16_row((float*)src_data_f32, (ggml_fp16_t*)dst, n); } else { diff --git a/stable-diffusion.h b/stable-diffusion.h index f3e71beff..3604e716b 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -93,6 +93,8 @@ enum sd_type_t { SD_TYPE_Q4_0_4_4 = 31, SD_TYPE_Q4_0_4_8 = 32, SD_TYPE_Q4_0_8_8 = 33, + SD_TYPE_TQ1_0 = 34, + SD_TYPE_TQ2_0 = 35, SD_TYPE_COUNT, }; diff --git a/util.cpp b/util.cpp index b8a65e7d9..3bcee0946 100644 --- a/util.cpp +++ b/util.cpp @@ -22,6 +22,7 @@ #include #endif +#include "ggml-cpu.h" #include "ggml.h" #include "stable-diffusion.h" @@ -410,7 +411,6 @@ const char* sd_get_system_info() { static char buffer[1024]; std::stringstream ss; ss << "System Info: \n"; - ss << " BLAS = " << ggml_cpu_has_blas() << std::endl; ss << " SSE3 = " << ggml_cpu_has_sse3() << std::endl; ss << " AVX = " << ggml_cpu_has_avx() << std::endl; ss << " AVX2 = " << ggml_cpu_has_avx2() << std::endl; From 53b415f78771836b743927f3944cfb5613dd1bb2 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sun, 24 Nov 2024 11:10:25 +0100 Subject: [PATCH 013/143] fix: remove default variables in c headers (#478) --- examples/cli/main.cpp | 10 ++++++++-- stable-diffusion.cpp | 28 ++++++++++++++++------------ stable-diffusion.h | 18 ++++++++++-------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 9f25245e3..59b325504 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -926,7 +926,8 @@ int main(int argc, const char* argv[]) { params.style_ratio, params.normalize_input, params.input_id_images_path.c_str(), - params.skip_layers, + params.skip_layers.data(), + params.skip_layers.size(), params.slg_scale, params.skip_layer_start, params.skip_layer_end); @@ -991,7 +992,12 @@ int main(int argc, const char* argv[]) { params.control_strength, params.style_ratio, params.normalize_input, - params.input_id_images_path.c_str()); + params.input_id_images_path.c_str(), + params.skip_layers.data(), + params.skip_layers.size(), + params.slg_scale, + params.skip_layer_start, + params.skip_layer_end); } } diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 5024b5f27..a276bff5c 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -780,7 +780,7 @@ class StableDiffusionGGML { int start_merge_step, SDCondition id_cond, std::vector skip_layers = {}, - float slg_scale = 2.5, + float slg_scale = 0, float skip_layer_start = 0.01, float skip_layer_end = 0.2) { size_t steps = sigmas.size() - 1; @@ -1162,7 +1162,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, bool normalize_input, std::string input_id_images_path, std::vector skip_layers = {}, - float slg_scale = 2.5, + float slg_scale = 0, float skip_layer_start = 0.01, float skip_layer_end = 0.2) { if (seed < 0) { @@ -1453,10 +1453,12 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, float style_ratio, bool normalize_input, const char* input_id_images_path_c_str, - std::vector skip_layers, - float slg_scale, - float skip_layer_start, - float skip_layer_end) { + int* skip_layers = NULL, + size_t skip_layers_count = 0, + float slg_scale = 0, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2) { + std::vector skip_layers_vec(skip_layers, skip_layers + skip_layers_count); LOG_DEBUG("txt2img %dx%d", width, height); if (sd_ctx == NULL) { return NULL; @@ -1525,7 +1527,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, style_ratio, normalize_input, input_id_images_path_c_str, - skip_layers, + skip_layers_vec, slg_scale, skip_layer_start, skip_layer_end); @@ -1556,10 +1558,12 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, float style_ratio, bool normalize_input, const char* input_id_images_path_c_str, - std::vector skip_layers, - float slg_scale, - float skip_layer_start, - float skip_layer_end) { + int* skip_layers = NULL, + size_t skip_layers_count = 0, + float slg_scale = 0, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2) { + std::vector skip_layers_vec(skip_layers, skip_layers + skip_layers_count); LOG_DEBUG("img2img %dx%d", width, height); if (sd_ctx == NULL) { return NULL; @@ -1634,7 +1638,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, style_ratio, normalize_input, input_id_images_path_c_str, - skip_layers, + skip_layers_vec, slg_scale, skip_layer_start, skip_layer_end); diff --git a/stable-diffusion.h b/stable-diffusion.h index 3604e716b..1fa328570 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -166,10 +166,11 @@ SD_API sd_image_t* txt2img(sd_ctx_t* sd_ctx, float style_strength, bool normalize_input, const char* input_id_images_path, - std::vector skip_layers = {}, - float slg_scale = 2.5, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2); + int* skip_layers, + size_t skip_layers_count, + float slg_scale, + float skip_layer_start, + float skip_layer_end); SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, sd_image_t init_image, @@ -190,10 +191,11 @@ SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, float style_strength, bool normalize_input, const char* input_id_images_path, - std::vector skip_layers = {}, - float slg_scale = 2.5, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2); + int* skip_layers, + size_t skip_layers_count, + float slg_scale, + float skip_layer_start, + float skip_layer_end); SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, sd_image_t init_image, From 4570715727f35e5a07a76796d823824c8f42206c Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 24 Nov 2024 18:21:31 +0800 Subject: [PATCH 014/143] fix: use ggml_nn_attention in vae --- ggml_extend.hpp | 24 ++++++++++++++++++++++++ vae.hpp | 8 +++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/ggml_extend.hpp b/ggml_extend.hpp index fc679e704..e944deb69 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -661,6 +661,30 @@ __STATIC_INLINE__ std::vector split_qkv(struct ggml_context return {q, k, v}; } +// q: [N * n_head, n_token, d_head] +// k: [N * n_head, n_k, d_head] +// v: [N * n_head, d_head, n_k] +// return: [N * n_head, n_token, d_head] +__STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention(struct ggml_context* ctx, + struct ggml_tensor* q, + struct ggml_tensor* k, + struct ggml_tensor* v, + bool mask = false) { +#if defined(SD_USE_FLASH_ATTENTION) && !defined(SD_USE_CUBLAS) && !defined(SD_USE_METAL) && !defined(SD_USE_VULKAN) && !defined(SD_USE_SYCL) + struct ggml_tensor* kqv = ggml_flash_attn(ctx, q, k, v, false); // [N * n_head, n_token, d_head] +#else + float d_head = (float)q->ne[0]; + struct ggml_tensor* kq = ggml_mul_mat(ctx, k, q); // [N * n_head, n_token, n_k] + kq = ggml_scale_inplace(ctx, kq, 1.0f / sqrt(d_head)); + if (mask) { + kq = ggml_diag_mask_inf_inplace(ctx, kq, 0); + } + kq = ggml_soft_max_inplace(ctx, kq); + struct ggml_tensor* kqv = ggml_mul_mat(ctx, v, kq); // [N * n_head, n_token, d_head] +#endif + return kqv; +} + // q: [N, L_q, C] or [N*n_head, L_q, d_head] // k: [N, L_k, C] or [N*n_head, L_k, d_head] // v: [N, L_k, C] or [N, L_k, n_head, d_head] diff --git a/vae.hpp b/vae.hpp index 0c7d84f9f..2985aadd3 100644 --- a/vae.hpp +++ b/vae.hpp @@ -99,12 +99,10 @@ class AttnBlock : public UnaryBlock { k = ggml_cont(ctx, ggml_permute(ctx, k, 1, 2, 0, 3)); // [N, h, w, in_channels] k = ggml_reshape_3d(ctx, k, c, h * w, n); // [N, h * w, in_channels] - auto v = v_proj->forward(ctx, h_); // [N, in_channels, h, w] - v = ggml_cont(ctx, ggml_permute(ctx, v, 1, 2, 0, 3)); // [N, h, w, in_channels] - v = ggml_reshape_3d(ctx, v, c, h * w, n); // [N, h * w, in_channels] + auto v = v_proj->forward(ctx, h_); // [N, in_channels, h, w] + v = ggml_reshape_3d(ctx, v, h * w, c, n); // [N, in_channels, h * w] - // h_ = ggml_nn_attention(ctx, q, k, v, false); // [N, h * w, in_channels] - h_ = ggml_nn_attention_ext(ctx, q, k, v, 1, nullptr, false, true, false); + h_ = ggml_nn_attention(ctx, q, k, v, false); // [N, h * w, in_channels] h_ = ggml_cont(ctx, ggml_permute(ctx, h_, 1, 0, 2, 3)); // [N, in_channels, h * w] h_ = ggml_reshape_4d(ctx, h_, w, h, c, n); // [N, in_channels, h, w] From 7ce63e740cc01715d075b5520880712da3a9f9aa Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 30 Nov 2024 07:18:53 +0100 Subject: [PATCH 015/143] feat: flexible model architecture for dit models (Flux & SD3) (#490) * Refactor: wtype per tensor * Fix default args * refactor: fix flux * Refactor photmaker v2 support * unet: refactor the refactoring * Refactor: fix controlnet and tae * refactor: upscaler * Refactor: fix runtime type override * upscaler: use fp16 again * Refactor: Flexible sd3 arch * Refactor: Flexible Flux arch * format code --------- Co-authored-by: leejet --- clip.hpp | 46 +++++++++++-------- common.hpp | 14 +++--- conditioner.hpp | 44 +++++++++--------- control.hpp | 6 +-- diffusion_model.hpp | 16 +++---- esrgan.hpp | 7 ++- examples/cli/main.cpp | 3 +- flux.hpp | 57 ++++++++++++++++++------ ggml_extend.hpp | 71 +++++++++++++++++------------ lora.hpp | 5 +-- mmdit.hpp | 101 ++++++++++++++++++++---------------------- model.cpp | 57 ++++++++++-------------- model.h | 17 ++++--- pmid.hpp | 11 +++-- stable-diffusion.cpp | 62 +++++++++++++------------- stable-diffusion.h | 3 +- t5.hpp | 31 +++++++------ tae.hpp | 7 +-- unet.hpp | 7 +-- upscaler.cpp | 11 +++-- vae.hpp | 12 ++--- 21 files changed, 317 insertions(+), 271 deletions(-) diff --git a/clip.hpp b/clip.hpp index 46e52ada4..cfc4cb38c 100644 --- a/clip.hpp +++ b/clip.hpp @@ -545,9 +545,12 @@ class CLIPEmbeddings : public GGMLBlock { int64_t vocab_size; int64_t num_positions; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["token_embedding.weight"] = ggml_new_tensor_2d(ctx, wtype, embed_dim, vocab_size); - params["position_embedding.weight"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, embed_dim, num_positions); + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type token_wtype = (tensor_types.find(prefix + "token_embedding.weight") != tensor_types.end()) ? tensor_types[prefix + "token_embedding.weight"] : GGML_TYPE_F32; + enum ggml_type position_wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "position_embedding.weight") != tensor_types.end()) ? tensor_types[prefix + "position_embedding.weight"] : GGML_TYPE_F32; + + params["token_embedding.weight"] = ggml_new_tensor_2d(ctx, token_wtype, embed_dim, vocab_size); + params["position_embedding.weight"] = ggml_new_tensor_2d(ctx, position_wtype, embed_dim, num_positions); } public: @@ -591,11 +594,14 @@ class CLIPVisionEmbeddings : public GGMLBlock { int64_t image_size; int64_t num_patches; int64_t num_positions; + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type patch_wtype = GGML_TYPE_F16; // tensor_types.find(prefix + "patch_embedding.weight") != tensor_types.end() ? tensor_types[prefix + "patch_embedding.weight"] : GGML_TYPE_F16; + enum ggml_type class_wtype = GGML_TYPE_F32; // tensor_types.find(prefix + "class_embedding") != tensor_types.end() ? tensor_types[prefix + "class_embedding"] : GGML_TYPE_F32; + enum ggml_type position_wtype = GGML_TYPE_F32; // tensor_types.find(prefix + "position_embedding.weight") != tensor_types.end() ? tensor_types[prefix + "position_embedding.weight"] : GGML_TYPE_F32; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["patch_embedding.weight"] = ggml_new_tensor_4d(ctx, GGML_TYPE_F16, patch_size, patch_size, num_channels, embed_dim); - params["class_embedding"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, embed_dim); - params["position_embedding.weight"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, embed_dim, num_positions); + params["patch_embedding.weight"] = ggml_new_tensor_4d(ctx, patch_wtype, patch_size, patch_size, num_channels, embed_dim); + params["class_embedding"] = ggml_new_tensor_1d(ctx, class_wtype, embed_dim); + params["position_embedding.weight"] = ggml_new_tensor_2d(ctx, position_wtype, embed_dim, num_positions); } public: @@ -651,9 +657,10 @@ enum CLIPVersion { class CLIPTextModel : public GGMLBlock { protected: - void init_params(struct ggml_context* ctx, ggml_type wtype) { + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { if (version == OPEN_CLIP_VIT_BIGG_14) { - params["text_projection"] = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, projection_dim, hidden_size); + enum ggml_type wtype = GGML_TYPE_F32; // tensor_types.find(prefix + "text_projection") != tensor_types.end() ? tensor_types[prefix + "text_projection"] : GGML_TYPE_F32; + params["text_projection"] = ggml_new_tensor_2d(ctx, wtype, projection_dim, hidden_size); } } @@ -798,9 +805,9 @@ class CLIPProjection : public UnaryBlock { int64_t out_features; bool transpose_weight; - void init_params(struct ggml_context* ctx, ggml_type wtype) { + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type wtype = tensor_types.find(prefix + "weight") != tensor_types.end() ? tensor_types[prefix + "weight"] : GGML_TYPE_F32; if (transpose_weight) { - LOG_ERROR("transpose_weight"); params["weight"] = ggml_new_tensor_2d(ctx, wtype, out_features, in_features); } else { params["weight"] = ggml_new_tensor_2d(ctx, wtype, in_features, out_features); @@ -861,12 +868,13 @@ struct CLIPTextModelRunner : public GGMLRunner { CLIPTextModel model; CLIPTextModelRunner(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, + const std::string prefix, CLIPVersion version = OPENAI_CLIP_VIT_L_14, int clip_skip_value = 1, bool with_final_ln = true) - : GGMLRunner(backend, wtype), model(version, clip_skip_value, with_final_ln) { - model.init(params_ctx, wtype); + : GGMLRunner(backend), model(version, clip_skip_value, with_final_ln) { + model.init(params_ctx, tensor_types, prefix); } std::string get_desc() { @@ -908,13 +916,13 @@ struct CLIPTextModelRunner : public GGMLRunner { struct ggml_tensor* embeddings = NULL; if (num_custom_embeddings > 0 && custom_embeddings_data != NULL) { - auto custom_embeddings = ggml_new_tensor_2d(compute_ctx, - wtype, - model.hidden_size, - num_custom_embeddings); + auto token_embed_weight = model.get_token_embed_weight(); + auto custom_embeddings = ggml_new_tensor_2d(compute_ctx, + token_embed_weight->type, + model.hidden_size, + num_custom_embeddings); set_backend_tensor_data(custom_embeddings, custom_embeddings_data); - auto token_embed_weight = model.get_token_embed_weight(); // concatenate custom embeddings embeddings = ggml_concat(compute_ctx, token_embed_weight, custom_embeddings, 1); } diff --git a/common.hpp b/common.hpp index 1ca6b8d0d..337b4a0c4 100644 --- a/common.hpp +++ b/common.hpp @@ -182,9 +182,11 @@ class GEGLU : public GGMLBlock { int64_t dim_in; int64_t dim_out; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["proj.weight"] = ggml_new_tensor_2d(ctx, wtype, dim_in, dim_out * 2); - params["proj.bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, dim_out * 2); + void init_params(struct ggml_context* ctx, std::map& tensor_types, std::string prefix = "") { + enum ggml_type wtype = (tensor_types.find(prefix + "proj.weight") != tensor_types.end()) ? tensor_types[prefix + "proj.weight"] : GGML_TYPE_F32; + enum ggml_type bias_wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "proj.bias") != tensor_types.end()) ? tensor_types[prefix + "proj.bias"] : GGML_TYPE_F32; + params["proj.weight"] = ggml_new_tensor_2d(ctx, wtype, dim_in, dim_out * 2); + params["proj.bias"] = ggml_new_tensor_1d(ctx, bias_wtype, dim_out * 2); } public: @@ -438,8 +440,10 @@ class SpatialTransformer : public GGMLBlock { class AlphaBlender : public GGMLBlock { protected: - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["mix_factor"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); + void init_params(struct ggml_context* ctx, std::map& tensor_types, std::string prefix = "") { + // Get the type of the "mix_factor" tensor from the input tensors map with the specified prefix + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.ypes.find(prefix + "mix_factor") != tensor_types.end()) ? tensor_types[prefix + "mix_factor"] : GGML_TYPE_F32; + params["mix_factor"] = ggml_new_tensor_1d(ctx, wtype, 1); } float get_alpha() { diff --git a/conditioner.hpp b/conditioner.hpp index 9a6300997..5b3f20dd1 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -46,7 +46,6 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { SDVersion version = VERSION_SD1; PMVersion pm_version = PM_VERSION_1; CLIPTokenizer tokenizer; - ggml_type wtype; std::shared_ptr text_model; std::shared_ptr text_model2; @@ -57,12 +56,12 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { std::vector readed_embeddings; FrozenCLIPEmbedderWithCustomWords(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, const std::string& embd_dir, SDVersion version = VERSION_SD1, PMVersion pv = PM_VERSION_1, int clip_skip = -1) - : version(version), pm_version(pv), tokenizer(version == VERSION_SD2 ? 0 : 49407), embd_dir(embd_dir), wtype(wtype) { + : version(version), pm_version(pv), tokenizer(version == VERSION_SD2 ? 0 : 49407), embd_dir(embd_dir) { if (clip_skip <= 0) { clip_skip = 1; if (version == VERSION_SD2 || version == VERSION_SDXL) { @@ -70,12 +69,12 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { } } if (version == VERSION_SD1) { - text_model = std::make_shared(backend, wtype, OPENAI_CLIP_VIT_L_14, clip_skip); + text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip); } else if (version == VERSION_SD2) { - text_model = std::make_shared(backend, wtype, OPEN_CLIP_VIT_H_14, clip_skip); + text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPEN_CLIP_VIT_H_14, clip_skip); } else if (version == VERSION_SDXL) { - text_model = std::make_shared(backend, wtype, OPENAI_CLIP_VIT_L_14, clip_skip, false); - text_model2 = std::make_shared(backend, wtype, OPEN_CLIP_VIT_BIGG_14, clip_skip, false); + text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip, false); + text_model2 = std::make_shared(backend, tensor_types, "cond_stage_model.1.transformer.text_model", OPEN_CLIP_VIT_BIGG_14, clip_skip, false); } } @@ -138,14 +137,14 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { LOG_DEBUG("embedding wrong hidden size, got %i, expected %i", tensor_storage.ne[0], hidden_size); return false; } - embd = ggml_new_tensor_2d(embd_ctx, wtype, hidden_size, tensor_storage.n_dims > 1 ? tensor_storage.ne[1] : 1); + embd = ggml_new_tensor_2d(embd_ctx, tensor_storage.type, hidden_size, tensor_storage.n_dims > 1 ? tensor_storage.ne[1] : 1); *dst_tensor = embd; return true; }; model_loader.load_tensors(on_load, NULL); readed_embeddings.push_back(embd_name); token_embed_custom.resize(token_embed_custom.size() + ggml_nbytes(embd)); - memcpy((void*)(token_embed_custom.data() + num_custom_embeddings * hidden_size * ggml_type_size(wtype)), + memcpy((void*)(token_embed_custom.data() + num_custom_embeddings * hidden_size * ggml_type_size(embd->type)), embd->data, ggml_nbytes(embd)); for (int i = 0; i < embd->ne[1]; i++) { @@ -590,9 +589,9 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { struct FrozenCLIPVisionEmbedder : public GGMLRunner { CLIPVisionModelProjection vision_model; - FrozenCLIPVisionEmbedder(ggml_backend_t backend, ggml_type wtype) - : vision_model(OPEN_CLIP_VIT_H_14, true), GGMLRunner(backend, wtype) { - vision_model.init(params_ctx, wtype); + FrozenCLIPVisionEmbedder(ggml_backend_t backend, std::map& tensor_types) + : vision_model(OPEN_CLIP_VIT_H_14, true), GGMLRunner(backend) { + vision_model.init(params_ctx, tensor_types, "cond_stage_model.transformer"); } std::string get_desc() { @@ -627,7 +626,6 @@ struct FrozenCLIPVisionEmbedder : public GGMLRunner { }; struct SD3CLIPEmbedder : public Conditioner { - ggml_type wtype; CLIPTokenizer clip_l_tokenizer; CLIPTokenizer clip_g_tokenizer; T5UniGramTokenizer t5_tokenizer; @@ -636,15 +634,15 @@ struct SD3CLIPEmbedder : public Conditioner { std::shared_ptr t5; SD3CLIPEmbedder(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, int clip_skip = -1) - : wtype(wtype), clip_g_tokenizer(0) { + : clip_g_tokenizer(0) { if (clip_skip <= 0) { clip_skip = 2; } - clip_l = std::make_shared(backend, wtype, OPENAI_CLIP_VIT_L_14, clip_skip, false); - clip_g = std::make_shared(backend, wtype, OPEN_CLIP_VIT_BIGG_14, clip_skip, false); - t5 = std::make_shared(backend, wtype); + clip_l = std::make_shared(backend, tensor_types, "text_encoders.clip_l.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip, false); + clip_g = std::make_shared(backend, tensor_types, "text_encoders.clip_g.transformer.text_model", OPEN_CLIP_VIT_BIGG_14, clip_skip, false); + t5 = std::make_shared(backend, tensor_types, "text_encoders.t5xxl.transformer"); } void set_clip_skip(int clip_skip) { @@ -974,21 +972,19 @@ struct SD3CLIPEmbedder : public Conditioner { }; struct FluxCLIPEmbedder : public Conditioner { - ggml_type wtype; CLIPTokenizer clip_l_tokenizer; T5UniGramTokenizer t5_tokenizer; std::shared_ptr clip_l; std::shared_ptr t5; FluxCLIPEmbedder(ggml_backend_t backend, - ggml_type wtype, - int clip_skip = -1) - : wtype(wtype) { + std::map& tensor_types, + int clip_skip = -1) { if (clip_skip <= 0) { clip_skip = 2; } - clip_l = std::make_shared(backend, wtype, OPENAI_CLIP_VIT_L_14, clip_skip, true); - t5 = std::make_shared(backend, wtype); + clip_l = std::make_shared(backend, tensor_types, "text_encoders.clip_l.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip, true); + t5 = std::make_shared(backend, tensor_types, "text_encoders.t5xxl.transformer"); } void set_clip_skip(int clip_skip) { diff --git a/control.hpp b/control.hpp index 41f31acb7..ed36db280 100644 --- a/control.hpp +++ b/control.hpp @@ -317,10 +317,10 @@ struct ControlNet : public GGMLRunner { bool guided_hint_cached = false; ControlNet(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, SDVersion version = VERSION_SD1) - : GGMLRunner(backend, wtype), control_net(version) { - control_net.init(params_ctx, wtype); + : GGMLRunner(backend), control_net(version) { + control_net.init(params_ctx, tensor_types, ""); } ~ControlNet() { diff --git a/diffusion_model.hpp b/diffusion_model.hpp index eb433b614..cbc0cd4c1 100644 --- a/diffusion_model.hpp +++ b/diffusion_model.hpp @@ -31,10 +31,10 @@ struct UNetModel : public DiffusionModel { UNetModelRunner unet; UNetModel(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, SDVersion version = VERSION_SD1, bool flash_attn = false) - : unet(backend, wtype, version, flash_attn) { + : unet(backend, tensor_types, "model.diffusion_model", version, flash_attn) { } void alloc_params_buffer() { @@ -83,9 +83,8 @@ struct MMDiTModel : public DiffusionModel { MMDiTRunner mmdit; MMDiTModel(ggml_backend_t backend, - ggml_type wtype, - SDVersion version = VERSION_SD3_2B) - : mmdit(backend, wtype, version) { + std::map& tensor_types) + : mmdit(backend, tensor_types, "model.diffusion_model") { } void alloc_params_buffer() { @@ -133,10 +132,9 @@ struct FluxModel : public DiffusionModel { Flux::FluxRunner flux; FluxModel(ggml_backend_t backend, - ggml_type wtype, - SDVersion version = VERSION_FLUX_DEV, - bool flash_attn = false) - : flux(backend, wtype, version, flash_attn) { + std::map& tensor_types, + bool flash_attn = false) + : flux(backend, tensor_types, "model.diffusion_model", flash_attn) { } void alloc_params_buffer() { diff --git a/esrgan.hpp b/esrgan.hpp index 33fcf09a4..989d15fee 100644 --- a/esrgan.hpp +++ b/esrgan.hpp @@ -142,10 +142,9 @@ struct ESRGAN : public GGMLRunner { int scale = 4; int tile_size = 128; // avoid cuda OOM for 4gb VRAM - ESRGAN(ggml_backend_t backend, - ggml_type wtype) - : GGMLRunner(backend, wtype) { - rrdb_net.init(params_ctx, wtype); + ESRGAN(ggml_backend_t backend, std::map& tensor_types) + : GGMLRunner(backend) { + rrdb_net.init(params_ctx, tensor_types, ""); } std::string get_desc() { diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 59b325504..93e6d3e43 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -1010,8 +1010,7 @@ int main(int argc, const char* argv[]) { int upscale_factor = 4; // unused for RealESRGAN_x4plus_anime_6B.pth if (params.esrgan_path.size() > 0 && params.upscale_repeats > 0) { upscaler_ctx_t* upscaler_ctx = new_upscaler_ctx(params.esrgan_path.c_str(), - params.n_threads, - params.wtype); + params.n_threads); if (upscaler_ctx == NULL) { printf("new_upscaler_ctx failed\n"); diff --git a/flux.hpp b/flux.hpp index b2d0f57c2..fdd00ebcb 100644 --- a/flux.hpp +++ b/flux.hpp @@ -35,8 +35,9 @@ namespace Flux { int64_t hidden_size; float eps; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["scale"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, hidden_size); + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + ggml_type wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "scale") != tensor_types.end()) ? tensor_types[prefix + "scale"] : GGML_TYPE_F32; + params["scale"] = ggml_new_tensor_1d(ctx, wtype, hidden_size); } public: @@ -823,25 +824,55 @@ namespace Flux { }; struct FluxRunner : public GGMLRunner { + static std::map empty_tensor_types; + public: FluxParams flux_params; Flux flux; std::vector pe_vec; // for cache FluxRunner(ggml_backend_t backend, - ggml_type wtype, - SDVersion version = VERSION_FLUX_DEV, - bool flash_attn = false) - : GGMLRunner(backend, wtype) { - flux_params.flash_attn = flash_attn; - if (version == VERSION_FLUX_SCHNELL) { - flux_params.guidance_embed = false; + std::map& tensor_types = empty_tensor_types, + const std::string prefix = "", + bool flash_attn = false) + : GGMLRunner(backend) { + flux_params.flash_attn = flash_attn; + flux_params.guidance_embed = false; + flux_params.depth = 0; + flux_params.depth_single_blocks = 0; + for (auto pair : tensor_types) { + std::string tensor_name = pair.first; + if (tensor_name.find("model.diffusion_model.") == std::string::npos) + continue; + if (tensor_name.find("guidance_in.in_layer.weight") != std::string::npos) { + // not schnell + flux_params.guidance_embed = true; + } + size_t db = tensor_name.find("double_blocks."); + if (db != std::string::npos) { + tensor_name = tensor_name.substr(db); // remove prefix + int block_depth = atoi(tensor_name.substr(14, tensor_name.find(".", 14)).c_str()); + if (block_depth + 1 > flux_params.depth) { + flux_params.depth = block_depth + 1; + } + } + size_t sb = tensor_name.find("single_blocks."); + if (sb != std::string::npos) { + tensor_name = tensor_name.substr(sb); // remove prefix + int block_depth = atoi(tensor_name.substr(14, tensor_name.find(".", 14)).c_str()); + if (block_depth + 1 > flux_params.depth_single_blocks) { + flux_params.depth_single_blocks = block_depth + 1; + } + } } - if (version == VERSION_FLUX_LITE) { - flux_params.depth = 8; + + LOG_INFO("Flux blocks: %d double, %d single", flux_params.depth, flux_params.depth_single_blocks); + if (!flux_params.guidance_embed) { + LOG_INFO("Flux guidance is disabled (Schnell mode)"); } + flux = Flux(flux_params); - flux.init(params_ctx, wtype); + flux.init(params_ctx, tensor_types, prefix); } std::string get_desc() { @@ -959,7 +990,7 @@ namespace Flux { // ggml_backend_t backend = ggml_backend_cuda_init(0); ggml_backend_t backend = ggml_backend_cpu_init(); ggml_type model_data_type = GGML_TYPE_Q8_0; - std::shared_ptr flux = std::shared_ptr(new FluxRunner(backend, model_data_type)); + std::shared_ptr flux = std::shared_ptr(new FluxRunner(backend)); { LOG_INFO("loading from '%s'", file_path.c_str()); diff --git a/ggml_extend.hpp b/ggml_extend.hpp index e944deb69..8afcd367c 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -25,6 +25,8 @@ #include "ggml-cpu.h" #include "ggml.h" +#include "model.h" + #ifdef SD_USE_CUBLAS #include "ggml-cuda.h" #endif @@ -673,13 +675,13 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention(struct ggml_context* ctx #if defined(SD_USE_FLASH_ATTENTION) && !defined(SD_USE_CUBLAS) && !defined(SD_USE_METAL) && !defined(SD_USE_VULKAN) && !defined(SD_USE_SYCL) struct ggml_tensor* kqv = ggml_flash_attn(ctx, q, k, v, false); // [N * n_head, n_token, d_head] #else - float d_head = (float)q->ne[0]; + float d_head = (float)q->ne[0]; struct ggml_tensor* kq = ggml_mul_mat(ctx, k, q); // [N * n_head, n_token, n_k] kq = ggml_scale_inplace(ctx, kq, 1.0f / sqrt(d_head)); if (mask) { kq = ggml_diag_mask_inf_inplace(ctx, kq, 0); } - kq = ggml_soft_max_inplace(ctx, kq); + kq = ggml_soft_max_inplace(ctx, kq); struct ggml_tensor* kqv = ggml_mul_mat(ctx, v, kq); // [N * n_head, n_token, d_head] #endif return kqv; @@ -964,7 +966,6 @@ struct GGMLRunner { std::map backend_tensor_data_map; - ggml_type wtype = GGML_TYPE_F32; ggml_backend_t backend = NULL; void alloc_params_ctx() { @@ -1040,8 +1041,8 @@ struct GGMLRunner { public: virtual std::string get_desc() = 0; - GGMLRunner(ggml_backend_t backend, ggml_type wtype = GGML_TYPE_F32) - : backend(backend), wtype(wtype) { + GGMLRunner(ggml_backend_t backend) + : backend(backend) { alloc_params_ctx(); } @@ -1170,20 +1171,22 @@ class GGMLBlock { GGMLBlockMap blocks; ParameterMap params; - void init_blocks(struct ggml_context* ctx, ggml_type wtype) { + void init_blocks(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { for (auto& pair : blocks) { auto& block = pair.second; - - block->init(ctx, wtype); + block->init(ctx, tensor_types, prefix + pair.first); } } - virtual void init_params(struct ggml_context* ctx, ggml_type wtype) {} + virtual void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") {} public: - void init(struct ggml_context* ctx, ggml_type wtype) { - init_blocks(ctx, wtype); - init_params(ctx, wtype); + void init(struct ggml_context* ctx, std::map& tensor_types, std::string prefix = "") { + if (prefix.size() > 0) { + prefix = prefix + "."; + } + init_blocks(ctx, tensor_types, prefix); + init_params(ctx, tensor_types, prefix); } size_t get_params_num() { @@ -1239,13 +1242,15 @@ class Linear : public UnaryBlock { bool bias; bool force_f32; - void init_params(struct ggml_context* ctx, ggml_type wtype) { + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type wtype = (tensor_types.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F32; if (in_features % ggml_blck_size(wtype) != 0 || force_f32) { wtype = GGML_TYPE_F32; } params["weight"] = ggml_new_tensor_2d(ctx, wtype, in_features, out_features); if (bias) { - params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_features); + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.ypes.find(prefix + "bias") != tensor_types.end()) ? tensor_types[prefix + "bias"] : GGML_TYPE_F32; + params["bias"] = ggml_new_tensor_1d(ctx, wtype, out_features); } } @@ -1273,9 +1278,9 @@ class Embedding : public UnaryBlock { protected: int64_t embedding_dim; int64_t num_embeddings; - - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["weight"] = ggml_new_tensor_2d(ctx, wtype, embedding_dim, num_embeddings); + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type wtype = (tensor_types.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F32; + params["weight"] = ggml_new_tensor_2d(ctx, wtype, embedding_dim, num_embeddings); } public: @@ -1313,10 +1318,12 @@ class Conv2d : public UnaryBlock { std::pair dilation; bool bias; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["weight"] = ggml_new_tensor_4d(ctx, GGML_TYPE_F16, kernel_size.second, kernel_size.first, in_channels, out_channels); + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type wtype = GGML_TYPE_F16; //(tensor_types.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F16; + params["weight"] = ggml_new_tensor_4d(ctx, wtype, kernel_size.second, kernel_size.first, in_channels, out_channels); if (bias) { - params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_channels); + enum ggml_type wtype = GGML_TYPE_F32; // (tensor_types.find(prefix + "bias") != tensor_types.end()) ? tensor_types[prefix + "bias"] : GGML_TYPE_F32; + params["bias"] = ggml_new_tensor_1d(ctx, wtype, out_channels); } } @@ -1356,10 +1363,12 @@ class Conv3dnx1x1 : public UnaryBlock { int64_t dilation; bool bias; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["weight"] = ggml_new_tensor_4d(ctx, GGML_TYPE_F16, 1, kernel_size, in_channels, out_channels); // 5d => 4d + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type wtype = GGML_TYPE_F16; //(tensor_types.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F16; + params["weight"] = ggml_new_tensor_4d(ctx, wtype, 1, kernel_size, in_channels, out_channels); // 5d => 4d if (bias) { - params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, out_channels); + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "bias") != tensor_types.end()) ? tensor_types[prefix + "bias"] : GGML_TYPE_F32; + params["bias"] = ggml_new_tensor_1d(ctx, wtype, out_channels); } } @@ -1398,11 +1407,13 @@ class LayerNorm : public UnaryBlock { bool elementwise_affine; bool bias; - void init_params(struct ggml_context* ctx, ggml_type wtype) { + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { if (elementwise_affine) { - params["weight"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, normalized_shape); + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.ypes.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F32; + params["weight"] = ggml_new_tensor_1d(ctx, wtype, normalized_shape); if (bias) { - params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, normalized_shape); + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.ypes.find(prefix + "bias") != tensor_types.end()) ? tensor_types[prefix + "bias"] : GGML_TYPE_F32; + params["bias"] = ggml_new_tensor_1d(ctx, wtype, normalized_shape); } } } @@ -1438,10 +1449,12 @@ class GroupNorm : public GGMLBlock { float eps; bool affine; - void init_params(struct ggml_context* ctx, ggml_type wtype) { + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { if (affine) { - params["weight"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, num_channels); - params["bias"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, num_channels); + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F32; + enum ggml_type bias_wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "bias") != tensor_types.end()) ? tensor_types[prefix + "bias"] : GGML_TYPE_F32; + params["weight"] = ggml_new_tensor_1d(ctx, wtype, num_channels); + params["bias"] = ggml_new_tensor_1d(ctx, bias_wtype, num_channels); } } diff --git a/lora.hpp b/lora.hpp index c44db7698..5f458faee 100644 --- a/lora.hpp +++ b/lora.hpp @@ -16,10 +16,9 @@ struct LoraModel : public GGMLRunner { ggml_tensor* zero_index = NULL; LoraModel(ggml_backend_t backend, - ggml_type wtype, const std::string& file_path = "", - const std::string& prefix = "") - : file_path(file_path), GGMLRunner(backend, wtype) { + const std::string prefix = "") + : file_path(file_path), GGMLRunner(backend) { if (!model_loader.init_from_file(file_path, prefix)) { load_failed = true; } diff --git a/mmdit.hpp b/mmdit.hpp index 35810bad9..dee7b1c49 100644 --- a/mmdit.hpp +++ b/mmdit.hpp @@ -147,8 +147,9 @@ class RMSNorm : public UnaryBlock { int64_t hidden_size; float eps; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["weight"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, hidden_size); + void init_params(struct ggml_context* ctx, std::map& tensor_types, std::string prefix = "") { + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F32; + params["weight"] = ggml_new_tensor_1d(ctx, wtype, hidden_size); } public: @@ -636,7 +637,6 @@ struct FinalLayer : public GGMLBlock { struct MMDiT : public GGMLBlock { // Diffusion model with a Transformer backbone. protected: - SDVersion version = VERSION_SD3_2B; int64_t input_size = -1; int64_t patch_size = 2; int64_t in_channels = 16; @@ -652,13 +652,13 @@ struct MMDiT : public GGMLBlock { int64_t hidden_size; std::string qk_norm; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["pos_embed"] = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, hidden_size, num_patchs, 1); + void init_params(struct ggml_context* ctx, std::map& tensor_types, std::string prefix = "") { + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "pos_embed") != tensor_types.end()) ? tensor_types[prefix + "pos_embed"] : GGML_TYPE_F32; + params["pos_embed"] = ggml_new_tensor_3d(ctx, wtype, hidden_size, num_patchs, 1); } public: - MMDiT(SDVersion version = VERSION_SD3_2B) - : version(version) { + MMDiT(std::map& tensor_types) { // input_size is always None // learn_sigma is always False // register_length is alwalys 0 @@ -670,48 +670,44 @@ struct MMDiT : public GGMLBlock { // pos_embed_scaling_factor is not used // pos_embed_offset is not used // context_embedder_config is always {'target': 'torch.nn.Linear', 'params': {'in_features': 4096, 'out_features': 1536}} - if (version == VERSION_SD3_2B) { - input_size = -1; - patch_size = 2; - in_channels = 16; - depth = 24; - mlp_ratio = 4.0f; - adm_in_channels = 2048; - out_channels = 16; - pos_embed_max_size = 192; - num_patchs = 36864; // 192 * 192 - context_size = 4096; - context_embedder_out_dim = 1536; - } else if (version == VERSION_SD3_5_8B) { - input_size = -1; - patch_size = 2; - in_channels = 16; - depth = 38; - mlp_ratio = 4.0f; - adm_in_channels = 2048; - out_channels = 16; - pos_embed_max_size = 192; - num_patchs = 36864; // 192 * 192 - context_size = 4096; - context_embedder_out_dim = 2432; - qk_norm = "rms"; - } else if (version == VERSION_SD3_5_2B) { - input_size = -1; - patch_size = 2; - in_channels = 16; - depth = 24; - d_self = 12; - mlp_ratio = 4.0f; - adm_in_channels = 2048; - out_channels = 16; - pos_embed_max_size = 384; - num_patchs = 147456; - context_size = 4096; - context_embedder_out_dim = 1536; - qk_norm = "rms"; + + // read tensors from tensor_types + for (auto pair : tensor_types) { + std::string tensor_name = pair.first; + if (tensor_name.find("model.diffusion_model.") == std::string::npos) + continue; + size_t jb = tensor_name.find("joint_blocks."); + if (jb != std::string::npos) { + tensor_name = tensor_name.substr(jb); // remove prefix + int block_depth = atoi(tensor_name.substr(13, tensor_name.find(".", 13)).c_str()); + if (block_depth + 1 > depth) { + depth = block_depth + 1; + } + if (tensor_name.find("attn.ln") != std::string::npos) { + if (tensor_name.find(".bias") != std::string::npos) { + qk_norm = "ln"; + } else { + qk_norm = "rms"; + } + } + if (tensor_name.find("attn2") != std::string::npos) { + if (block_depth > d_self) { + d_self = block_depth; + } + } + } + } + + if (d_self >= 0) { + pos_embed_max_size *= 2; + num_patchs *= 4; } + + LOG_INFO("MMDiT layers: %d (including %d MMDiT-x layers)", depth, d_self + 1); + int64_t default_out_channels = in_channels; hidden_size = 64 * depth; + context_embedder_out_dim = 64 * depth; int64_t num_heads = depth; blocks["x_embedder"] = std::shared_ptr(new PatchEmbed(input_size, patch_size, in_channels, hidden_size, true)); @@ -870,15 +866,16 @@ struct MMDiT : public GGMLBlock { return x; } }; - struct MMDiTRunner : public GGMLRunner { MMDiT mmdit; + static std::map empty_tensor_types; + MMDiTRunner(ggml_backend_t backend, - ggml_type wtype, - SDVersion version = VERSION_SD3_2B) - : GGMLRunner(backend, wtype), mmdit(version) { - mmdit.init(params_ctx, wtype); + std::map& tensor_types = empty_tensor_types, + const std::string prefix = "") + : GGMLRunner(backend), mmdit(tensor_types) { + mmdit.init(params_ctx, tensor_types, prefix); } std::string get_desc() { @@ -975,7 +972,7 @@ struct MMDiTRunner : public GGMLRunner { // ggml_backend_t backend = ggml_backend_cuda_init(0); ggml_backend_t backend = ggml_backend_cpu_init(); ggml_type model_data_type = GGML_TYPE_F16; - std::shared_ptr mmdit = std::shared_ptr(new MMDiTRunner(backend, model_data_type)); + std::shared_ptr mmdit = std::shared_ptr(new MMDiTRunner(backend)); { LOG_INFO("loading from '%s'", file_path.c_str()); diff --git a/model.cpp b/model.cpp index dba8187da..c90918ad2 100644 --- a/model.cpp +++ b/model.cpp @@ -927,6 +927,7 @@ bool ModelLoader::init_from_gguf_file(const std::string& file_path, const std::s GGML_ASSERT(ggml_nbytes(dummy) == tensor_storage.nbytes()); tensor_storages.push_back(tensor_storage); + tensor_storages_types[tensor_storage.name] = tensor_storage.type; } gguf_free(ctx_gguf_); @@ -1071,6 +1072,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const } tensor_storages.push_back(tensor_storage); + tensor_storages_types[tensor_storage.name] = tensor_storage.type; // LOG_DEBUG("%s %s", tensor_storage.to_string().c_str(), dtype.c_str()); } @@ -1296,7 +1298,7 @@ bool ModelLoader::parse_data_pkl(uint8_t* buffer, zip_t* zip, std::string dir, size_t file_index, - const std::string& prefix) { + const std::string prefix) { uint8_t* buffer_end = buffer + buffer_size; if (buffer[0] == 0x80) { // proto if (buffer[1] != 2) { @@ -1401,6 +1403,8 @@ bool ModelLoader::parse_data_pkl(uint8_t* buffer, // printf(" ZIP got tensor %s \n ", reader.tensor_storage.name.c_str()); reader.tensor_storage.name = prefix + reader.tensor_storage.name; tensor_storages.push_back(reader.tensor_storage); + tensor_storages_types[reader.tensor_storage.name] = reader.tensor_storage.type; + // LOG_DEBUG("%s", reader.tensor_storage.name.c_str()); // reset reader = PickleTensorReader(); @@ -1455,28 +1459,12 @@ bool ModelLoader::init_from_ckpt_file(const std::string& file_path, const std::s SDVersion ModelLoader::get_sd_version() { TensorStorage token_embedding_weight; - bool is_flux = false; - bool is_schnell = true; - bool is_lite = true; - bool is_sd3 = false; for (auto& tensor_storage : tensor_storages) { - if (tensor_storage.name.find("model.diffusion_model.guidance_in.in_layer.weight") != std::string::npos) { - is_schnell = false; - } if (tensor_storage.name.find("model.diffusion_model.double_blocks.") != std::string::npos) { - is_flux = true; - } - if (tensor_storage.name.find("model.diffusion_model.double_blocks.8") != std::string::npos) { - is_lite = false; - } - if (tensor_storage.name.find("joint_blocks.0.x_block.attn2.ln_q.weight") != std::string::npos) { - return VERSION_SD3_5_2B; - } - if (tensor_storage.name.find("joint_blocks.37.x_block.attn.ln_q.weight") != std::string::npos) { - return VERSION_SD3_5_8B; + return VERSION_FLUX; } - if (tensor_storage.name.find("model.diffusion_model.joint_blocks.23.") != std::string::npos) { - is_sd3 = true; + if (tensor_storage.name.find("model.diffusion_model.joint_blocks.") != std::string::npos) { + return VERSION_SD3; } if (tensor_storage.name.find("conditioner.embedders.1") != std::string::npos) { return VERSION_SDXL; @@ -1498,19 +1486,7 @@ SDVersion ModelLoader::get_sd_version() { // break; } } - if (is_flux) { - if (is_schnell) { - GGML_ASSERT(!is_lite); - return VERSION_FLUX_SCHNELL; - } else if (is_lite) { - return VERSION_FLUX_LITE; - } else { - return VERSION_FLUX_DEV; - } - } - if (is_sd3) { - return VERSION_SD3_2B; - } + if (token_embedding_weight.ne[0] == 768) { return VERSION_SD1; } else if (token_embedding_weight.ne[0] == 1024) { @@ -1603,6 +1579,21 @@ ggml_type ModelLoader::get_vae_wtype() { return GGML_TYPE_COUNT; } +void ModelLoader::set_wtype_override(ggml_type wtype, std::string prefix) { + for (auto& pair : tensor_storages_types) { + if (prefix.size() < 1 || pair.first.substr(0, prefix.size()) == prefix) { + for (auto& tensor_storage : tensor_storages) { + if (tensor_storage.name == pair.first) { + if (tensor_should_be_converted(tensor_storage, wtype)) { + pair.second = wtype; + } + break; + } + } + } + } +} + std::string ModelLoader::load_merges() { std::string merges_utf8_str(reinterpret_cast(merges_utf8_c_str), sizeof(merges_utf8_c_str)); return merges_utf8_str; diff --git a/model.h b/model.h index b7e3b3a2e..29d46c192 100644 --- a/model.h +++ b/model.h @@ -22,24 +22,20 @@ enum SDVersion { VERSION_SD2, VERSION_SDXL, VERSION_SVD, - VERSION_SD3_2B, - VERSION_FLUX_DEV, - VERSION_FLUX_SCHNELL, - VERSION_SD3_5_8B, - VERSION_SD3_5_2B, - VERSION_FLUX_LITE, + VERSION_SD3, + VERSION_FLUX, VERSION_COUNT, }; static inline bool sd_version_is_flux(SDVersion version) { - if (version == VERSION_FLUX_DEV || version == VERSION_FLUX_SCHNELL || version == VERSION_FLUX_LITE) { + if (version == VERSION_FLUX) { return true; } return false; } static inline bool sd_version_is_sd3(SDVersion version) { - if (version == VERSION_SD3_2B || version == VERSION_SD3_5_8B || version == VERSION_SD3_5_2B) { + if (version == VERSION_SD3) { return true; } return false; @@ -170,7 +166,7 @@ class ModelLoader { zip_t* zip, std::string dir, size_t file_index, - const std::string& prefix); + const std::string prefix); bool init_from_gguf_file(const std::string& file_path, const std::string& prefix = ""); bool init_from_safetensors_file(const std::string& file_path, const std::string& prefix = ""); @@ -178,12 +174,15 @@ class ModelLoader { bool init_from_diffusers_file(const std::string& file_path, const std::string& prefix = ""); public: + std::map tensor_storages_types; + bool init_from_file(const std::string& file_path, const std::string& prefix = ""); SDVersion get_sd_version(); ggml_type get_sd_wtype(); ggml_type get_conditioner_wtype(); ggml_type get_diffusion_model_wtype(); ggml_type get_vae_wtype(); + void set_wtype_override(ggml_type wtype, std::string prefix = ""); bool load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend_t backend); bool load_tensors(std::map& tensors, ggml_backend_t backend, diff --git a/pmid.hpp b/pmid.hpp index defb4f05a..ea9f02eb6 100644 --- a/pmid.hpp +++ b/pmid.hpp @@ -623,15 +623,15 @@ struct PhotoMakerIDEncoder : public GGMLRunner { std::vector zeros_right; public: - PhotoMakerIDEncoder(ggml_backend_t backend, ggml_type wtype, SDVersion version = VERSION_SDXL, PMVersion pm_v = PM_VERSION_1, float sty = 20.f) - : GGMLRunner(backend, wtype), + PhotoMakerIDEncoder(ggml_backend_t backend, std::map& tensor_types, const std::string prefix, SDVersion version = VERSION_SDXL, PMVersion pm_v = PM_VERSION_1, float sty = 20.f) + : GGMLRunner(backend), version(version), pm_version(pm_v), style_strength(sty) { if (pm_version == PM_VERSION_1) { - id_encoder.init(params_ctx, wtype); + id_encoder.init(params_ctx, tensor_types, prefix); } else if (pm_version == PM_VERSION_2) { - id_encoder2.init(params_ctx, wtype); + id_encoder2.init(params_ctx, tensor_types, prefix); } } @@ -780,11 +780,10 @@ struct PhotoMakerIDEmbed : public GGMLRunner { bool applied = false; PhotoMakerIDEmbed(ggml_backend_t backend, - ggml_type wtype, ModelLoader* ml, const std::string& file_path = "", const std::string& prefix = "") - : file_path(file_path), GGMLRunner(backend, wtype), model_loader(ml) { + : file_path(file_path), GGMLRunner(backend), model_loader(ml) { if (!model_loader->init_from_file(file_path, prefix)) { load_failed = true; } diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index a276bff5c..5abc29507 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -29,12 +29,8 @@ const char* model_version_to_str[] = { "SD 2.x", "SDXL", "SVD", - "SD3 2B", - "Flux Dev", - "Flux Schnell", - "SD3.5 8B", - "SD3.5 2B", - "Flux Lite 8B"}; + "SD3.x", + "Flux"}; const char* sampling_methods_str[] = { "Euler A", @@ -264,16 +260,18 @@ class StableDiffusionGGML { conditioner_wtype = wtype; diffusion_model_wtype = wtype; vae_wtype = wtype; + model_loader.set_wtype_override(wtype); } if (version == VERSION_SDXL) { vae_wtype = GGML_TYPE_F32; + model_loader.set_wtype_override(GGML_TYPE_F32, "vae."); } - LOG_INFO("Weight type: %s", ggml_type_name(model_wtype)); - LOG_INFO("Conditioner weight type: %s", ggml_type_name(conditioner_wtype)); - LOG_INFO("Diffusion model weight type: %s", ggml_type_name(diffusion_model_wtype)); - LOG_INFO("VAE weight type: %s", ggml_type_name(vae_wtype)); + LOG_INFO("Weight type: %s", model_wtype != SD_TYPE_COUNT ? ggml_type_name(model_wtype) : "??"); + LOG_INFO("Conditioner weight type: %s", conditioner_wtype != SD_TYPE_COUNT ? ggml_type_name(conditioner_wtype) : "??"); + LOG_INFO("Diffusion model weight type: %s", diffusion_model_wtype != SD_TYPE_COUNT ? ggml_type_name(diffusion_model_wtype) : "??"); + LOG_INFO("VAE weight type: %s", vae_wtype != SD_TYPE_COUNT ? ggml_type_name(vae_wtype) : "??"); LOG_DEBUG("ggml tensor size = %d bytes", (int)sizeof(ggml_tensor)); @@ -294,15 +292,15 @@ class StableDiffusionGGML { } if (version == VERSION_SVD) { - clip_vision = std::make_shared(backend, conditioner_wtype); + clip_vision = std::make_shared(backend, model_loader.tensor_storages_types); clip_vision->alloc_params_buffer(); clip_vision->get_param_tensors(tensors); - diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); + diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, version); diffusion_model->alloc_params_buffer(); diffusion_model->get_param_tensors(tensors); - first_stage_model = std::make_shared(backend, vae_wtype, vae_decode_only, true, version); + first_stage_model = std::make_shared(backend, model_loader.tensor_storages_types, "first_stage_model", vae_decode_only, true, version); LOG_DEBUG("vae_decode_only %d", vae_decode_only); first_stage_model->alloc_params_buffer(); first_stage_model->get_param_tensors(tensors, "first_stage_model"); @@ -327,19 +325,20 @@ class StableDiffusionGGML { if (diffusion_flash_attn) { LOG_WARN("flash attention in this diffusion model is currently unsupported!"); } - cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); - diffusion_model = std::make_shared(backend, diffusion_model_wtype, version); + cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); + diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types); } else if (sd_version_is_flux(version)) { - cond_stage_model = std::make_shared(clip_backend, conditioner_wtype); - diffusion_model = std::make_shared(backend, diffusion_model_wtype, version, diffusion_flash_attn); + cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); + diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, diffusion_flash_attn); } else { if (id_embeddings_path.find("v2") != std::string::npos) { - cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version, PM_VERSION_2); + cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, embeddings_path, version, PM_VERSION_2); } else { - cond_stage_model = std::make_shared(clip_backend, conditioner_wtype, embeddings_path, version); + cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, embeddings_path, version); } - diffusion_model = std::make_shared(backend, diffusion_model_wtype, version, diffusion_flash_attn); + diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, version, diffusion_flash_attn); } + cond_stage_model->alloc_params_buffer(); cond_stage_model->get_param_tensors(tensors); @@ -353,11 +352,11 @@ class StableDiffusionGGML { } else { vae_backend = backend; } - first_stage_model = std::make_shared(vae_backend, vae_wtype, vae_decode_only, false, version); + first_stage_model = std::make_shared(vae_backend, model_loader.tensor_storages_types, "first_stage_model", vae_decode_only, false, version); first_stage_model->alloc_params_buffer(); first_stage_model->get_param_tensors(tensors, "first_stage_model"); } else { - tae_first_stage = std::make_shared(backend, vae_wtype, vae_decode_only); + tae_first_stage = std::make_shared(backend, model_loader.tensor_storages_types, "decoder.layers", vae_decode_only); } // first_stage_model->get_param_tensors(tensors, "first_stage_model."); @@ -369,17 +368,17 @@ class StableDiffusionGGML { } else { controlnet_backend = backend; } - control_net = std::make_shared(controlnet_backend, diffusion_model_wtype, version); + control_net = std::make_shared(controlnet_backend, model_loader.tensor_storages_types, version); } if (id_embeddings_path.find("v2") != std::string::npos) { - pmid_model = std::make_shared(backend, model_wtype, version, PM_VERSION_2); + pmid_model = std::make_shared(backend, model_loader.tensor_storages_types, "pmid", version, PM_VERSION_2); LOG_INFO("using PhotoMaker Version 2"); } else { - pmid_model = std::make_shared(backend, model_wtype, version); + pmid_model = std::make_shared(backend, model_loader.tensor_storages_types, "pmid", version); } if (id_embeddings_path.size() > 0) { - pmid_lora = std::make_shared(backend, model_wtype, id_embeddings_path, ""); + pmid_lora = std::make_shared(backend, id_embeddings_path, ""); if (!pmid_lora->load_from_file(true)) { LOG_WARN("load photomaker lora tensors from %s failed", id_embeddings_path.c_str()); return false; @@ -532,9 +531,12 @@ class StableDiffusionGGML { denoiser = std::make_shared(); } else if (sd_version_is_flux(version)) { LOG_INFO("running in Flux FLOW mode"); - float shift = 1.15f; - if (version == VERSION_FLUX_SCHNELL) { - shift = 1.0f; // TODO: validate + float shift = 1.0f; // TODO: validate + for (auto pair : model_loader.tensor_storages_types) { + if (pair.first.find("model.diffusion_model.guidance_in.in_layer.weight") != std::string::npos) { + shift = 1.15f; + break; + } } denoiser = std::make_shared(shift); } else if (is_using_v_parameterization) { @@ -633,7 +635,7 @@ class StableDiffusionGGML { LOG_WARN("can not find %s or %s for lora %s", st_file_path.c_str(), ckpt_file_path.c_str(), lora_name.c_str()); return; } - LoraModel lora(backend, model_wtype, file_path); + LoraModel lora(backend, file_path); if (!lora.load_from_file()) { LOG_WARN("load lora tensors from %s failed", file_path.c_str()); return; diff --git a/stable-diffusion.h b/stable-diffusion.h index 1fa328570..c67bc8a32 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -215,8 +215,7 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, typedef struct upscaler_ctx_t upscaler_ctx_t; SD_API upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path, - int n_threads, - enum sd_type_t wtype); + int n_threads); SD_API void free_upscaler_ctx(upscaler_ctx_t* upscaler_ctx); SD_API sd_image_t upscale(upscaler_ctx_t* upscaler_ctx, sd_image_t input_image, uint32_t upscale_factor); diff --git a/t5.hpp b/t5.hpp index 79109e34b..2a53e2743 100644 --- a/t5.hpp +++ b/t5.hpp @@ -441,8 +441,9 @@ class T5LayerNorm : public UnaryBlock { int64_t hidden_size; float eps; - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["weight"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, hidden_size); + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "weight") != tensor_types.end()) ? tensor_types[prefix + "weight"] : GGML_TYPE_F32; + params["weight"] = ggml_new_tensor_1d(ctx, wtype, hidden_size); } public: @@ -717,14 +718,15 @@ struct T5Runner : public GGMLRunner { std::vector relative_position_bucket_vec; T5Runner(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, + const std::string prefix, int64_t num_layers = 24, int64_t model_dim = 4096, int64_t ff_dim = 10240, int64_t num_heads = 64, int64_t vocab_size = 32128) - : GGMLRunner(backend, wtype), model(num_layers, model_dim, ff_dim, num_heads, vocab_size) { - model.init(params_ctx, wtype); + : GGMLRunner(backend), model(num_layers, model_dim, ff_dim, num_heads, vocab_size) { + model.init(params_ctx, tensor_types, prefix); } std::string get_desc() { @@ -854,14 +856,17 @@ struct T5Embedder { T5UniGramTokenizer tokenizer; T5Runner model; + static std::map empty_tensor_types; + T5Embedder(ggml_backend_t backend, - ggml_type wtype, - int64_t num_layers = 24, - int64_t model_dim = 4096, - int64_t ff_dim = 10240, - int64_t num_heads = 64, - int64_t vocab_size = 32128) - : model(backend, wtype, num_layers, model_dim, ff_dim, num_heads, vocab_size) { + std::map& tensor_types = empty_tensor_types, + const std::string prefix = "", + int64_t num_layers = 24, + int64_t model_dim = 4096, + int64_t ff_dim = 10240, + int64_t num_heads = 64, + int64_t vocab_size = 32128) + : model(backend, tensor_types, prefix, num_layers, model_dim, ff_dim, num_heads, vocab_size) { } void get_param_tensors(std::map& tensors, const std::string prefix) { @@ -951,7 +956,7 @@ struct T5Embedder { // ggml_backend_t backend = ggml_backend_cuda_init(0); ggml_backend_t backend = ggml_backend_cpu_init(); ggml_type model_data_type = GGML_TYPE_F32; - std::shared_ptr t5 = std::shared_ptr(new T5Embedder(backend, model_data_type)); + std::shared_ptr t5 = std::shared_ptr(new T5Embedder(backend)); { LOG_INFO("loading from '%s'", file_path.c_str()); diff --git a/tae.hpp b/tae.hpp index 0e03b884e..fee5e8328 100644 --- a/tae.hpp +++ b/tae.hpp @@ -188,12 +188,13 @@ struct TinyAutoEncoder : public GGMLRunner { bool decode_only = false; TinyAutoEncoder(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, + const std::string prefix, bool decoder_only = true) : decode_only(decoder_only), taesd(decode_only), - GGMLRunner(backend, wtype) { - taesd.init(params_ctx, wtype); + GGMLRunner(backend) { + taesd.init(params_ctx, tensor_types, prefix); } std::string get_desc() { diff --git a/unet.hpp b/unet.hpp index 79f702c4d..2a7adb3d2 100644 --- a/unet.hpp +++ b/unet.hpp @@ -532,11 +532,12 @@ struct UNetModelRunner : public GGMLRunner { UnetModelBlock unet; UNetModelRunner(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, + const std::string prefix, SDVersion version = VERSION_SD1, bool flash_attn = false) - : GGMLRunner(backend, wtype), unet(version, flash_attn) { - unet.init(params_ctx, wtype); + : GGMLRunner(backend), unet(version, flash_attn) { + unet.init(params_ctx, tensor_types, prefix); } std::string get_desc() { diff --git a/upscaler.cpp b/upscaler.cpp index 096352993..1cf34c1a3 100644 --- a/upscaler.cpp +++ b/upscaler.cpp @@ -32,13 +32,17 @@ struct UpscalerGGML { LOG_DEBUG("Using SYCL backend"); backend = ggml_backend_sycl_init(0); #endif - + ModelLoader model_loader; + if (!model_loader.init_from_file(esrgan_path)) { + LOG_ERROR("init model loader from file failed: '%s'", esrgan_path.c_str()); + } + model_loader.set_wtype_override(model_data_type); if (!backend) { LOG_DEBUG("Using CPU backend"); backend = ggml_backend_cpu_init(); } LOG_INFO("Upscaler weight type: %s", ggml_type_name(model_data_type)); - esrgan_upscaler = std::make_shared(backend, model_data_type); + esrgan_upscaler = std::make_shared(backend, model_loader.tensor_storages_types); if (!esrgan_upscaler->load_from_file(esrgan_path)) { return false; } @@ -96,8 +100,7 @@ struct upscaler_ctx_t { }; upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path_c_str, - int n_threads, - enum sd_type_t wtype) { + int n_threads) { upscaler_ctx_t* upscaler_ctx = (upscaler_ctx_t*)malloc(sizeof(upscaler_ctx_t)); if (upscaler_ctx == NULL) { return NULL; diff --git a/vae.hpp b/vae.hpp index 2985aadd3..4add881f6 100644 --- a/vae.hpp +++ b/vae.hpp @@ -163,8 +163,9 @@ class AE3DConv : public Conv2d { class VideoResnetBlock : public ResnetBlock { protected: - void init_params(struct ggml_context* ctx, ggml_type wtype) { - params["mix_factor"] = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); + void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { + enum ggml_type wtype = (tensor_types.find(prefix + "mix_factor") != tensor_types.end()) ? tensor_types[prefix + "mix_factor"] : GGML_TYPE_F32; + params["mix_factor"] = ggml_new_tensor_1d(ctx, wtype, 1); } float get_alpha() { @@ -524,12 +525,13 @@ struct AutoEncoderKL : public GGMLRunner { AutoencodingEngine ae; AutoEncoderKL(ggml_backend_t backend, - ggml_type wtype, + std::map& tensor_types, + const std::string prefix, bool decode_only = false, bool use_video_decoder = false, SDVersion version = VERSION_SD1) - : decode_only(decode_only), ae(decode_only, use_video_decoder, version), GGMLRunner(backend, wtype) { - ae.init(params_ctx, wtype); + : decode_only(decode_only), ae(decode_only, use_video_decoder, version), GGMLRunner(backend) { + ae.init(params_ctx, tensor_types, prefix); } std::string get_desc() { From 9148b980bed0071d1cd221f53228bc01de8c24f4 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 30 Nov 2024 07:22:15 +0100 Subject: [PATCH 016/143] feat: remove type restrictions (#489) --- examples/cli/main.cpp | 50 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 93e6d3e43..4b47286f4 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -196,7 +196,7 @@ void print_usage(int argc, const char* argv[]) { printf(" --normalize-input normalize PHOTOMAKER input id images\n"); printf(" --upscale-model [ESRGAN_PATH] path to esrgan model. Upscale images after generate, just RealESRGAN_x4plus_anime_6B supported by now\n"); printf(" --upscale-repeats Run the ESRGAN upscaler this many times (default 1)\n"); - printf(" --type [TYPE] weight type (f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_k, q3_k, q4_k)\n"); + printf(" --type [TYPE] weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K)\n"); printf(" If not specified, the default is the type of the weight file\n"); printf(" --lora-model-dir [DIR] lora model directory\n"); printf(" -i, --init-img [IMAGE] path to the input image, required by img2img\n"); @@ -346,30 +346,30 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - std::string type = argv[i]; - if (type == "f32") { - params.wtype = SD_TYPE_F32; - } else if (type == "f16") { - params.wtype = SD_TYPE_F16; - } else if (type == "q4_0") { - params.wtype = SD_TYPE_Q4_0; - } else if (type == "q4_1") { - params.wtype = SD_TYPE_Q4_1; - } else if (type == "q5_0") { - params.wtype = SD_TYPE_Q5_0; - } else if (type == "q5_1") { - params.wtype = SD_TYPE_Q5_1; - } else if (type == "q8_0") { - params.wtype = SD_TYPE_Q8_0; - } else if (type == "q2_k") { - params.wtype = SD_TYPE_Q2_K; - } else if (type == "q3_k") { - params.wtype = SD_TYPE_Q3_K; - } else if (type == "q4_k") { - params.wtype = SD_TYPE_Q4_K; - } else { - fprintf(stderr, "error: invalid weight format %s, must be one of [f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_k, q3_k, q4_k]\n", - type.c_str()); + std::string type = argv[i]; + bool found = false; + std::string valid_types = ""; + for (size_t i = 0; i < SD_TYPE_COUNT; i++) { + auto trait = ggml_get_type_traits((ggml_type)i); + std::string name(trait->type_name); + if (name == "f32" || trait->to_float && trait->type_size) { + if (i) + valid_types += ", "; + valid_types += name; + if (type == name) { + if (ggml_quantize_requires_imatrix((ggml_type)i)) { + printf("\033[35;1m[WARNING]\033[0m: type %s requires imatrix to work properly. A dummy imatrix will be used, expect poor quality.\n", trait->type_name); + } + params.wtype = (enum sd_type_t)i; + found = true; + break; + } + } + } + if (!found) { + fprintf(stderr, "error: invalid weight format %s, must be one of [%s]\n", + type.c_str(), + valid_types.c_str()); exit(1); } } else if (arg == "--lora-model-dir") { From 9578fdcc4632dc3de5565f28e2fb16b7c18f8d48 Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 30 Nov 2024 14:26:29 +0800 Subject: [PATCH 017/143] chore: remove rocm5.5 build temporarily --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe1410891..c0eeb4f4a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -164,8 +164,8 @@ jobs: defines: "-DGGML_AVX512=ON -DSD_BUILD_SHARED_LIBS=ON" - build: "cuda12" defines: "-DSD_CUBLAS=ON -DSD_BUILD_SHARED_LIBS=ON" - - build: "rocm5.5" - defines: '-G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_HIPBLAS=ON -DCMAKE_BUILD_TYPE=Release -DAMDGPU_TARGETS="gfx1100;gfx1102;gfx1030" -DSD_BUILD_SHARED_LIBS=ON' + # - build: "rocm5.5" + # defines: '-G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_HIPBLAS=ON -DCMAKE_BUILD_TYPE=Release -DAMDGPU_TARGETS="gfx1100;gfx1102;gfx1030" -DSD_BUILD_SHARED_LIBS=ON' - build: 'vulkan' defines: "-DSD_VULKAN=ON -DSD_BUILD_SHARED_LIBS=ON" steps: From cc92a6a1b3d819670711a573c7211731050d8c60 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 28 Dec 2024 05:56:44 +0100 Subject: [PATCH 018/143] feat: support more LoRA models (#520) --- lora.hpp | 632 +++++++++++++++++++++++++++++++++++++------ stable-diffusion.cpp | 5 +- 2 files changed, 558 insertions(+), 79 deletions(-) diff --git a/lora.hpp b/lora.hpp index 5f458faee..83181837c 100644 --- a/lora.hpp +++ b/lora.hpp @@ -6,6 +6,90 @@ #define LORA_GRAPH_SIZE 10240 struct LoraModel : public GGMLRunner { + enum lora_t { + REGULAR = 0, + DIFFUSERS = 1, + DIFFUSERS_2 = 2, + DIFFUSERS_3 = 3, + TRANSFORMERS = 4, + LORA_TYPE_COUNT + }; + + const std::string lora_ups[LORA_TYPE_COUNT] = { + ".lora_up", + "_lora.up", + ".lora_B", + ".lora.up", + ".lora_linear_layer.up", + }; + + const std::string lora_downs[LORA_TYPE_COUNT] = { + ".lora_down", + "_lora.down", + ".lora_A", + ".lora.down", + ".lora_linear_layer.down", + }; + + const std::string lora_pre[LORA_TYPE_COUNT] = { + "lora.", + "", + "", + "", + "", + }; + + const std::map alt_names = { + // mmdit + {"final_layer.adaLN_modulation.1", "norm_out.linear"}, + {"pos_embed", "pos_embed.proj"}, + {"final_layer.linear", "proj_out"}, + {"y_embedder.mlp.0", "time_text_embed.text_embedder.linear_1"}, + {"y_embedder.mlp.2", "time_text_embed.text_embedder.linear_2"}, + {"t_embedder.mlp.0", "time_text_embed.timestep_embedder.linear_1"}, + {"t_embedder.mlp.2", "time_text_embed.timestep_embedder.linear_2"}, + {"x_block.mlp.fc1", "ff.net.0.proj"}, + {"x_block.mlp.fc2", "ff.net.2"}, + {"context_block.mlp.fc1", "ff_context.net.0.proj"}, + {"context_block.mlp.fc2", "ff_context.net.2"}, + {"x_block.adaLN_modulation.1", "norm1.linear"}, + {"context_block.adaLN_modulation.1", "norm1_context.linear"}, + {"context_block.attn.proj", "attn.to_add_out"}, + {"x_block.attn.proj", "attn.to_out.0"}, + {"x_block.attn2.proj", "attn2.to_out.0"}, + // flux + // singlestream + {"linear2", "proj_out"}, + {"modulation.lin", "norm.linear"}, + // doublestream + {"txt_attn.proj", "attn.to_add_out"}, + {"img_attn.proj", "attn.to_out.0"}, + {"txt_mlp.0", "ff_context.net.0.proj"}, + {"txt_mlp.2", "ff_context.net.2"}, + {"img_mlp.0", "ff.net.0.proj"}, + {"img_mlp.2", "ff.net.2"}, + {"txt_mod.lin", "norm1_context.linear"}, + {"img_mod.lin", "norm1.linear"}, + }; + + const std::map qkv_prefixes = { + // mmdit + {"context_block.attn.qkv", "attn.add_"}, // suffix "_proj" + {"x_block.attn.qkv", "attn.to_"}, + {"x_block.attn2.qkv", "attn2.to_"}, + // flux + // doublestream + {"txt_attn.qkv", "attn.add_"}, // suffix "_proj" + {"img_attn.qkv", "attn.to_"}, + }; + const std::map qkvm_prefixes = { + // flux + // singlestream + {"linear1", ""}, + }; + + const std::string* type_fingerprints = lora_ups; + float multiplier = 1.0f; std::map lora_tensors; std::string file_path; @@ -14,6 +98,7 @@ struct LoraModel : public GGMLRunner { bool applied = false; std::vector zero_index_vec = {0}; ggml_tensor* zero_index = NULL; + enum lora_t type = REGULAR; LoraModel(ggml_backend_t backend, const std::string& file_path = "", @@ -44,6 +129,13 @@ struct LoraModel : public GGMLRunner { // LOG_INFO("skipping LoRA tesnor '%s'", name.c_str()); return true; } + // LOG_INFO("%s", name.c_str()); + for (int i = 0; i < LORA_TYPE_COUNT; i++) { + if (name.find(type_fingerprints[i]) != std::string::npos) { + type = (lora_t)i; + break; + } + } if (dry_run) { struct ggml_tensor* real = ggml_new_tensor(params_ctx, @@ -61,10 +153,12 @@ struct LoraModel : public GGMLRunner { model_loader.load_tensors(on_new_tensor_cb, backend); alloc_params_buffer(); - + // exit(0); dry_run = false; model_loader.load_tensors(on_new_tensor_cb, backend); + LOG_DEBUG("lora type: \"%s\"/\"%s\"", lora_downs[type].c_str(), lora_ups[type].c_str()); + LOG_DEBUG("finished loaded lora"); return true; } @@ -76,7 +170,66 @@ struct LoraModel : public GGMLRunner { return out; } - struct ggml_cgraph* build_lora_graph(std::map model_tensors) { + std::vector to_lora_keys(std::string blk_name, SDVersion version) { + std::vector keys; + // if (!sd_version_is_sd3(version) || blk_name != "model.diffusion_model.pos_embed") { + size_t k_pos = blk_name.find(".weight"); + if (k_pos == std::string::npos) { + return keys; + } + blk_name = blk_name.substr(0, k_pos); + // } + keys.push_back(blk_name); + keys.push_back("lora." + blk_name); + if (sd_version_is_dit(version)) { + if (blk_name.find("model.diffusion_model") != std::string::npos) { + blk_name.replace(blk_name.find("model.diffusion_model"), sizeof("model.diffusion_model") - 1, "transformer"); + } + + if (blk_name.find(".single_blocks") != std::string::npos) { + blk_name.replace(blk_name.find(".single_blocks"), sizeof(".single_blocks") - 1, ".single_transformer_blocks"); + } + if (blk_name.find(".double_blocks") != std::string::npos) { + blk_name.replace(blk_name.find(".double_blocks"), sizeof(".double_blocks") - 1, ".transformer_blocks"); + } + + if (blk_name.find(".joint_blocks") != std::string::npos) { + blk_name.replace(blk_name.find(".joint_blocks"), sizeof(".joint_blocks") - 1, ".transformer_blocks"); + } + + for (const auto& item : alt_names) { + size_t match = blk_name.find(item.first); + if (match != std::string::npos) { + blk_name = blk_name.substr(0, match) + item.second; + } + } + for (const auto& prefix : qkv_prefixes) { + size_t match = blk_name.find(prefix.first); + if (match != std::string::npos) { + std::string split_blk = "SPLIT|" + blk_name.substr(0, match) + prefix.second; + keys.push_back(split_blk); + } + } + for (const auto& prefix : qkvm_prefixes) { + size_t match = blk_name.find(prefix.first); + if (match != std::string::npos) { + std::string split_blk = "SPLIT_L|" + blk_name.substr(0, match) + prefix.second; + keys.push_back(split_blk); + } + } + } + keys.push_back(blk_name); + + std::vector ret; + for (std::string& key : keys) { + ret.push_back(key); + replace_all_chars(key, '.', '_'); + ret.push_back(key); + } + return ret; + } + + struct ggml_cgraph* build_lora_graph(std::map model_tensors, SDVersion version) { struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, LORA_GRAPH_SIZE, false); zero_index = ggml_new_tensor_1d(compute_ctx, GGML_TYPE_I32, 1); @@ -88,91 +241,416 @@ struct LoraModel : public GGMLRunner { std::string k_tensor = it.first; struct ggml_tensor* weight = model_tensors[it.first]; - size_t k_pos = k_tensor.find(".weight"); - if (k_pos == std::string::npos) { + std::vector keys = to_lora_keys(k_tensor, version); + if (keys.size() == 0) continue; - } - k_tensor = k_tensor.substr(0, k_pos); - replace_all_chars(k_tensor, '.', '_'); - // LOG_DEBUG("k_tensor %s", k_tensor.c_str()); - std::string lora_up_name = "lora." + k_tensor + ".lora_up.weight"; - if (lora_tensors.find(lora_up_name) == lora_tensors.end()) { - if (k_tensor == "model_diffusion_model_output_blocks_2_2_conv") { - // fix for some sdxl lora, like lcm-lora-xl - k_tensor = "model_diffusion_model_output_blocks_2_1_conv"; - lora_up_name = "lora." + k_tensor + ".lora_up.weight"; - } - } - - std::string lora_down_name = "lora." + k_tensor + ".lora_down.weight"; - std::string alpha_name = "lora." + k_tensor + ".alpha"; - std::string scale_name = "lora." + k_tensor + ".scale"; - ggml_tensor* lora_up = NULL; ggml_tensor* lora_down = NULL; + for (auto& key : keys) { + std::string alpha_name = ""; + std::string scale_name = ""; + std::string split_q_scale_name = ""; + std::string lora_down_name = ""; + std::string lora_up_name = ""; + + if (starts_with(key, "SPLIT|")) { + key = key.substr(sizeof("SPLIT|") - 1); + // TODO: Handle alphas + std::string suffix = ""; + auto split_q_d_name = lora_pre[type] + key + "q" + suffix + lora_downs[type] + ".weight"; + + if (lora_tensors.find(split_q_d_name) == lora_tensors.end()) { + suffix = "_proj"; + split_q_d_name = lora_pre[type] + key + "q" + suffix + lora_downs[type] + ".weight"; + } + if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { + // print_ggml_tensor(it.second, true); //[3072, 21504, 1, 1] + // find qkv and mlp up parts in LoRA model + auto split_k_d_name = lora_pre[type] + key + "k" + suffix + lora_downs[type] + ".weight"; + auto split_v_d_name = lora_pre[type] + key + "v" + suffix + lora_downs[type] + ".weight"; + + auto split_q_u_name = lora_pre[type] + key + "q" + suffix + lora_ups[type] + ".weight"; + auto split_k_u_name = lora_pre[type] + key + "k" + suffix + lora_ups[type] + ".weight"; + auto split_v_u_name = lora_pre[type] + key + "v" + suffix + lora_ups[type] + ".weight"; + + auto split_q_scale_name = lora_pre[type] + key + "q" + suffix + ".scale"; + auto split_k_scale_name = lora_pre[type] + key + "k" + suffix + ".scale"; + auto split_v_scale_name = lora_pre[type] + key + "v" + suffix + ".scale"; + + auto split_q_alpha_name = lora_pre[type] + key + "q" + suffix + ".alpha"; + auto split_k_alpha_name = lora_pre[type] + key + "k" + suffix + ".alpha"; + auto split_v_alpha_name = lora_pre[type] + key + "v" + suffix + ".alpha"; + + ggml_tensor* lora_q_down = NULL; + ggml_tensor* lora_q_up = NULL; + ggml_tensor* lora_k_down = NULL; + ggml_tensor* lora_k_up = NULL; + ggml_tensor* lora_v_down = NULL; + ggml_tensor* lora_v_up = NULL; + + lora_q_down = to_f32(compute_ctx, lora_tensors[split_q_d_name]); + + if (lora_tensors.find(split_q_u_name) != lora_tensors.end()) { + lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); + } + + if (lora_tensors.find(split_k_d_name) != lora_tensors.end()) { + lora_k_down = to_f32(compute_ctx, lora_tensors[split_k_d_name]); + } + + if (lora_tensors.find(split_k_u_name) != lora_tensors.end()) { + lora_k_up = to_f32(compute_ctx, lora_tensors[split_k_u_name]); + } + + if (lora_tensors.find(split_v_d_name) != lora_tensors.end()) { + lora_v_down = to_f32(compute_ctx, lora_tensors[split_v_d_name]); + } + + if (lora_tensors.find(split_v_u_name) != lora_tensors.end()) { + lora_v_up = to_f32(compute_ctx, lora_tensors[split_v_u_name]); + } + + float q_rank = lora_q_up->ne[0]; + float k_rank = lora_k_up->ne[0]; + float v_rank = lora_v_up->ne[0]; + + float lora_q_scale = 1; + float lora_k_scale = 1; + float lora_v_scale = 1; + + if (lora_tensors.find(split_q_scale_name) != lora_tensors.end()) { + lora_q_scale = ggml_backend_tensor_get_f32(lora_tensors[split_q_scale_name]); + applied_lora_tensors.insert(split_q_scale_name); + } + if (lora_tensors.find(split_k_scale_name) != lora_tensors.end()) { + lora_k_scale = ggml_backend_tensor_get_f32(lora_tensors[split_k_scale_name]); + applied_lora_tensors.insert(split_k_scale_name); + } + if (lora_tensors.find(split_v_scale_name) != lora_tensors.end()) { + lora_v_scale = ggml_backend_tensor_get_f32(lora_tensors[split_v_scale_name]); + applied_lora_tensors.insert(split_v_scale_name); + } + + if (lora_tensors.find(split_q_alpha_name) != lora_tensors.end()) { + float lora_q_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_q_alpha_name]); + applied_lora_tensors.insert(split_q_alpha_name); + lora_q_scale = lora_q_alpha / q_rank; + } + if (lora_tensors.find(split_k_alpha_name) != lora_tensors.end()) { + float lora_k_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_k_alpha_name]); + applied_lora_tensors.insert(split_k_alpha_name); + lora_k_scale = lora_k_alpha / k_rank; + } + if (lora_tensors.find(split_v_alpha_name) != lora_tensors.end()) { + float lora_v_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_v_alpha_name]); + applied_lora_tensors.insert(split_v_alpha_name); + lora_v_scale = lora_v_alpha / v_rank; + } + + ggml_scale_inplace(compute_ctx, lora_q_down, lora_q_scale); + ggml_scale_inplace(compute_ctx, lora_k_down, lora_k_scale); + ggml_scale_inplace(compute_ctx, lora_v_down, lora_v_scale); + + // print_ggml_tensor(lora_q_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_k_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_v_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_q_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_k_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_v_up, true); //[R, 3072, 1, 1] + + // these need to be stitched together this way: + // |q_up,0 ,0 | + // |0 ,k_up,0 | + // |0 ,0 ,v_up| + // (q_down,k_down,v_down) . (q ,k ,v) + + // up_concat will be [9216, R*3, 1, 1] + // down_concat will be [R*3, 3072, 1, 1] + ggml_tensor* lora_down_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_down, lora_k_down, 1), lora_v_down, 1); + + ggml_tensor* z = ggml_dup_tensor(compute_ctx, lora_q_up); + ggml_scale(compute_ctx, z, 0); + ggml_tensor* zz = ggml_concat(compute_ctx, z, z, 1); + + ggml_tensor* q_up = ggml_concat(compute_ctx, lora_q_up, zz, 1); + ggml_tensor* k_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, z, lora_k_up, 1), z, 1); + ggml_tensor* v_up = ggml_concat(compute_ctx, zz, lora_v_up, 1); + // print_ggml_tensor(q_up, true); //[R, 9216, 1, 1] + // print_ggml_tensor(k_up, true); //[R, 9216, 1, 1] + // print_ggml_tensor(v_up, true); //[R, 9216, 1, 1] + ggml_tensor* lora_up_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, q_up, k_up, 0), v_up, 0); + // print_ggml_tensor(lora_up_concat, true); //[R*3, 9216, 1, 1] + + lora_down = ggml_cont(compute_ctx, lora_down_concat); + lora_up = ggml_cont(compute_ctx, lora_up_concat); + + applied_lora_tensors.insert(split_q_u_name); + applied_lora_tensors.insert(split_k_u_name); + applied_lora_tensors.insert(split_v_u_name); + + applied_lora_tensors.insert(split_q_d_name); + applied_lora_tensors.insert(split_k_d_name); + applied_lora_tensors.insert(split_v_d_name); + } + } + if (starts_with(key, "SPLIT_L|")) { + key = key.substr(sizeof("SPLIT_L|") - 1); + + auto split_q_d_name = lora_pre[type] + key + "attn.to_q" + lora_downs[type] + ".weight"; + if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { + // print_ggml_tensor(it.second, true); //[3072, 21504, 1, 1] + // find qkv and mlp up parts in LoRA model + auto split_k_d_name = lora_pre[type] + key + "attn.to_k" + lora_downs[type] + ".weight"; + auto split_v_d_name = lora_pre[type] + key + "attn.to_v" + lora_downs[type] + ".weight"; + + auto split_q_u_name = lora_pre[type] + key + "attn.to_q" + lora_ups[type] + ".weight"; + auto split_k_u_name = lora_pre[type] + key + "attn.to_k" + lora_ups[type] + ".weight"; + auto split_v_u_name = lora_pre[type] + key + "attn.to_v" + lora_ups[type] + ".weight"; + + auto split_m_d_name = lora_pre[type] + key + "proj_mlp" + lora_downs[type] + ".weight"; + auto split_m_u_name = lora_pre[type] + key + "proj_mlp" + lora_ups[type] + ".weight"; + + auto split_q_scale_name = lora_pre[type] + key + "attn.to_q" + ".scale"; + auto split_k_scale_name = lora_pre[type] + key + "attn.to_k" + ".scale"; + auto split_v_scale_name = lora_pre[type] + key + "attn.to_v" + ".scale"; + auto split_m_scale_name = lora_pre[type] + key + "proj_mlp" + ".scale"; + + auto split_q_alpha_name = lora_pre[type] + key + "attn.to_q" + ".alpha"; + auto split_k_alpha_name = lora_pre[type] + key + "attn.to_k" + ".alpha"; + auto split_v_alpha_name = lora_pre[type] + key + "attn.to_v" + ".alpha"; + auto split_m_alpha_name = lora_pre[type] + key + "proj_mlp" + ".alpha"; + + ggml_tensor* lora_q_down = NULL; + ggml_tensor* lora_q_up = NULL; + ggml_tensor* lora_k_down = NULL; + ggml_tensor* lora_k_up = NULL; + ggml_tensor* lora_v_down = NULL; + ggml_tensor* lora_v_up = NULL; + + ggml_tensor* lora_m_down = NULL; + ggml_tensor* lora_m_up = NULL; + + lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); + + if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { + lora_q_down = to_f32(compute_ctx, lora_tensors[split_q_d_name]); + } + + if (lora_tensors.find(split_q_u_name) != lora_tensors.end()) { + lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); + } + + if (lora_tensors.find(split_k_d_name) != lora_tensors.end()) { + lora_k_down = to_f32(compute_ctx, lora_tensors[split_k_d_name]); + } + + if (lora_tensors.find(split_k_u_name) != lora_tensors.end()) { + lora_k_up = to_f32(compute_ctx, lora_tensors[split_k_u_name]); + } + + if (lora_tensors.find(split_v_d_name) != lora_tensors.end()) { + lora_v_down = to_f32(compute_ctx, lora_tensors[split_v_d_name]); + } + + if (lora_tensors.find(split_v_u_name) != lora_tensors.end()) { + lora_v_up = to_f32(compute_ctx, lora_tensors[split_v_u_name]); + } + + if (lora_tensors.find(split_m_d_name) != lora_tensors.end()) { + lora_m_down = to_f32(compute_ctx, lora_tensors[split_m_d_name]); + } + + if (lora_tensors.find(split_m_u_name) != lora_tensors.end()) { + lora_m_up = to_f32(compute_ctx, lora_tensors[split_m_u_name]); + } + + float q_rank = lora_q_up->ne[0]; + float k_rank = lora_k_up->ne[0]; + float v_rank = lora_v_up->ne[0]; + float m_rank = lora_v_up->ne[0]; + + float lora_q_scale = 1; + float lora_k_scale = 1; + float lora_v_scale = 1; + float lora_m_scale = 1; + + if (lora_tensors.find(split_q_scale_name) != lora_tensors.end()) { + lora_q_scale = ggml_backend_tensor_get_f32(lora_tensors[split_q_scale_name]); + applied_lora_tensors.insert(split_q_scale_name); + } + if (lora_tensors.find(split_k_scale_name) != lora_tensors.end()) { + lora_k_scale = ggml_backend_tensor_get_f32(lora_tensors[split_k_scale_name]); + applied_lora_tensors.insert(split_k_scale_name); + } + if (lora_tensors.find(split_v_scale_name) != lora_tensors.end()) { + lora_v_scale = ggml_backend_tensor_get_f32(lora_tensors[split_v_scale_name]); + applied_lora_tensors.insert(split_v_scale_name); + } + if (lora_tensors.find(split_m_scale_name) != lora_tensors.end()) { + lora_m_scale = ggml_backend_tensor_get_f32(lora_tensors[split_m_scale_name]); + applied_lora_tensors.insert(split_m_scale_name); + } + + if (lora_tensors.find(split_q_alpha_name) != lora_tensors.end()) { + float lora_q_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_q_alpha_name]); + applied_lora_tensors.insert(split_q_alpha_name); + lora_q_scale = lora_q_alpha / q_rank; + } + if (lora_tensors.find(split_k_alpha_name) != lora_tensors.end()) { + float lora_k_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_k_alpha_name]); + applied_lora_tensors.insert(split_k_alpha_name); + lora_k_scale = lora_k_alpha / k_rank; + } + if (lora_tensors.find(split_v_alpha_name) != lora_tensors.end()) { + float lora_v_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_v_alpha_name]); + applied_lora_tensors.insert(split_v_alpha_name); + lora_v_scale = lora_v_alpha / v_rank; + } + if (lora_tensors.find(split_m_alpha_name) != lora_tensors.end()) { + float lora_m_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_m_alpha_name]); + applied_lora_tensors.insert(split_m_alpha_name); + lora_m_scale = lora_m_alpha / m_rank; + } + + ggml_scale_inplace(compute_ctx, lora_q_down, lora_q_scale); + ggml_scale_inplace(compute_ctx, lora_k_down, lora_k_scale); + ggml_scale_inplace(compute_ctx, lora_v_down, lora_v_scale); + ggml_scale_inplace(compute_ctx, lora_m_down, lora_m_scale); + + // print_ggml_tensor(lora_q_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_k_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_v_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_m_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_q_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_k_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_v_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_m_up, true); //[R, 12288, 1, 1] + + // these need to be stitched together this way: + // |q_up,0 ,0 ,0 | + // |0 ,k_up,0 ,0 | + // |0 ,0 ,v_up,0 | + // |0 ,0 ,0 ,m_up| + // (q_down,k_down,v_down,m_down) . (q ,k ,v ,m) + + // up_concat will be [21504, R*4, 1, 1] + // down_concat will be [R*4, 3072, 1, 1] + + ggml_tensor* lora_down_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_down, lora_k_down, 1), ggml_concat(compute_ctx, lora_v_down, lora_m_down, 1), 1); + // print_ggml_tensor(lora_down_concat, true); //[3072, R*4, 1, 1] + + // this also means that if rank is bigger than 672, it is less memory efficient to do it this way (should be fine) + // print_ggml_tensor(lora_q_up, true); //[3072, R, 1, 1] + ggml_tensor* z = ggml_dup_tensor(compute_ctx, lora_q_up); + ggml_tensor* mlp_z = ggml_dup_tensor(compute_ctx, lora_m_up); + ggml_scale(compute_ctx, z, 0); + ggml_scale(compute_ctx, mlp_z, 0); + ggml_tensor* zz = ggml_concat(compute_ctx, z, z, 1); + + ggml_tensor* q_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_up, zz, 1), mlp_z, 1); + ggml_tensor* k_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, z, lora_k_up, 1), ggml_concat(compute_ctx, z, mlp_z, 1), 1); + ggml_tensor* v_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, zz, lora_v_up, 1), mlp_z, 1); + ggml_tensor* m_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, zz, z, 1), lora_m_up, 1); + // print_ggml_tensor(q_up, true); //[R, 21504, 1, 1] + // print_ggml_tensor(k_up, true); //[R, 21504, 1, 1] + // print_ggml_tensor(v_up, true); //[R, 21504, 1, 1] + // print_ggml_tensor(m_up, true); //[R, 21504, 1, 1] + + ggml_tensor* lora_up_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, q_up, k_up, 0), ggml_concat(compute_ctx, v_up, m_up, 0), 0); + // print_ggml_tensor(lora_up_concat, true); //[R*4, 21504, 1, 1] + + lora_down = ggml_cont(compute_ctx, lora_down_concat); + lora_up = ggml_cont(compute_ctx, lora_up_concat); + + applied_lora_tensors.insert(split_q_u_name); + applied_lora_tensors.insert(split_k_u_name); + applied_lora_tensors.insert(split_v_u_name); + applied_lora_tensors.insert(split_m_u_name); + + applied_lora_tensors.insert(split_q_d_name); + applied_lora_tensors.insert(split_k_d_name); + applied_lora_tensors.insert(split_v_d_name); + applied_lora_tensors.insert(split_m_d_name); + } + } + if (lora_up == NULL || lora_down == NULL) { + lora_up_name = lora_pre[type] + key + lora_ups[type] + ".weight"; + if (lora_tensors.find(lora_up_name) == lora_tensors.end()) { + if (key == "model_diffusion_model_output_blocks_2_2_conv") { + // fix for some sdxl lora, like lcm-lora-xl + key = "model_diffusion_model_output_blocks_2_1_conv"; + lora_up_name = lora_pre[type] + key + lora_ups[type] + ".weight"; + } + } + + lora_down_name = lora_pre[type] + key + lora_downs[type] + ".weight"; + alpha_name = lora_pre[type] + key + ".alpha"; + scale_name = lora_pre[type] + key + ".scale"; + + if (lora_tensors.find(lora_up_name) != lora_tensors.end()) { + lora_up = lora_tensors[lora_up_name]; + } + + if (lora_tensors.find(lora_down_name) != lora_tensors.end()) { + lora_down = lora_tensors[lora_down_name]; + } + applied_lora_tensors.insert(lora_up_name); + applied_lora_tensors.insert(lora_down_name); + applied_lora_tensors.insert(alpha_name); + applied_lora_tensors.insert(scale_name); + } - if (lora_tensors.find(lora_up_name) != lora_tensors.end()) { - lora_up = lora_tensors[lora_up_name]; - } - - if (lora_tensors.find(lora_down_name) != lora_tensors.end()) { - lora_down = lora_tensors[lora_down_name]; - } - - if (lora_up == NULL || lora_down == NULL) { - continue; - } - - applied_lora_tensors.insert(lora_up_name); - applied_lora_tensors.insert(lora_down_name); - applied_lora_tensors.insert(alpha_name); - applied_lora_tensors.insert(scale_name); - - // calc_cale - int64_t dim = lora_down->ne[ggml_n_dims(lora_down) - 1]; - float scale_value = 1.0f; - if (lora_tensors.find(scale_name) != lora_tensors.end()) { - scale_value = ggml_backend_tensor_get_f32(lora_tensors[scale_name]); - } else if (lora_tensors.find(alpha_name) != lora_tensors.end()) { - float alpha = ggml_backend_tensor_get_f32(lora_tensors[alpha_name]); - scale_value = alpha / dim; - } - scale_value *= multiplier; - - // flat lora tensors to multiply it - int64_t lora_up_rows = lora_up->ne[ggml_n_dims(lora_up) - 1]; - lora_up = ggml_reshape_2d(compute_ctx, lora_up, ggml_nelements(lora_up) / lora_up_rows, lora_up_rows); - int64_t lora_down_rows = lora_down->ne[ggml_n_dims(lora_down) - 1]; - lora_down = ggml_reshape_2d(compute_ctx, lora_down, ggml_nelements(lora_down) / lora_down_rows, lora_down_rows); - - // ggml_mul_mat requires tensor b transposed - lora_down = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, lora_down)); - struct ggml_tensor* updown = ggml_mul_mat(compute_ctx, lora_up, lora_down); - updown = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, updown)); - updown = ggml_reshape(compute_ctx, updown, weight); - GGML_ASSERT(ggml_nelements(updown) == ggml_nelements(weight)); - updown = ggml_scale_inplace(compute_ctx, updown, scale_value); - ggml_tensor* final_weight; - if (weight->type != GGML_TYPE_F32 && weight->type != GGML_TYPE_F16) { - // final_weight = ggml_new_tensor(compute_ctx, GGML_TYPE_F32, ggml_n_dims(weight), weight->ne); - // final_weight = ggml_cpy(compute_ctx, weight, final_weight); - final_weight = to_f32(compute_ctx, weight); - final_weight = ggml_add_inplace(compute_ctx, final_weight, updown); - final_weight = ggml_cpy(compute_ctx, final_weight, weight); - } else { - final_weight = ggml_add_inplace(compute_ctx, weight, updown); + if (lora_up == NULL || lora_down == NULL) { + continue; + } + // calc_scale + int64_t dim = lora_down->ne[ggml_n_dims(lora_down) - 1]; + float scale_value = 1.0f; + if (lora_tensors.find(scale_name) != lora_tensors.end()) { + scale_value = ggml_backend_tensor_get_f32(lora_tensors[scale_name]); + } else if (lora_tensors.find(alpha_name) != lora_tensors.end()) { + float alpha = ggml_backend_tensor_get_f32(lora_tensors[alpha_name]); + scale_value = alpha / dim; + } + scale_value *= multiplier; + + // flat lora tensors to multiply it + int64_t lora_up_rows = lora_up->ne[ggml_n_dims(lora_up) - 1]; + lora_up = ggml_reshape_2d(compute_ctx, lora_up, ggml_nelements(lora_up) / lora_up_rows, lora_up_rows); + int64_t lora_down_rows = lora_down->ne[ggml_n_dims(lora_down) - 1]; + lora_down = ggml_reshape_2d(compute_ctx, lora_down, ggml_nelements(lora_down) / lora_down_rows, lora_down_rows); + + // ggml_mul_mat requires tensor b transposed + lora_down = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, lora_down)); + struct ggml_tensor* updown = ggml_mul_mat(compute_ctx, lora_up, lora_down); + updown = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, updown)); + updown = ggml_reshape(compute_ctx, updown, weight); + GGML_ASSERT(ggml_nelements(updown) == ggml_nelements(weight)); + updown = ggml_scale_inplace(compute_ctx, updown, scale_value); + ggml_tensor* final_weight; + if (weight->type != GGML_TYPE_F32 && weight->type != GGML_TYPE_F16) { + // final_weight = ggml_new_tensor(compute_ctx, GGML_TYPE_F32, ggml_n_dims(weight), weight->ne); + // final_weight = ggml_cpy(compute_ctx, weight, final_weight); + final_weight = to_f32(compute_ctx, weight); + final_weight = ggml_add_inplace(compute_ctx, final_weight, updown); + final_weight = ggml_cpy(compute_ctx, final_weight, weight); + } else { + final_weight = ggml_add_inplace(compute_ctx, weight, updown); + } + // final_weight = ggml_add_inplace(compute_ctx, weight, updown); // apply directly + ggml_build_forward_expand(gf, final_weight); + break; } - // final_weight = ggml_add_inplace(compute_ctx, weight, updown); // apply directly - ggml_build_forward_expand(gf, final_weight); } - size_t total_lora_tensors_count = 0; size_t applied_lora_tensors_count = 0; for (auto& kv : lora_tensors) { total_lora_tensors_count++; if (applied_lora_tensors.find(kv.first) == applied_lora_tensors.end()) { - LOG_WARN("unused lora tensor %s", kv.first.c_str()); + LOG_WARN("unused lora tensor |%s|", kv.first.c_str()); + print_ggml_tensor(kv.second, true); + // exit(0); } else { applied_lora_tensors_count++; } @@ -191,9 +669,9 @@ struct LoraModel : public GGMLRunner { return gf; } - void apply(std::map model_tensors, int n_threads) { + void apply(std::map model_tensors, SDVersion version, int n_threads) { auto get_graph = [&]() -> struct ggml_cgraph* { - return build_lora_graph(model_tensors); + return build_lora_graph(model_tensors, version); }; GGMLRunner::compute(get_graph, n_threads, true); } diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 5abc29507..4d5a7d9b6 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -642,7 +642,8 @@ class StableDiffusionGGML { } lora.multiplier = multiplier; - lora.apply(tensors, n_threads); + // TODO: send version? + lora.apply(tensors, version, n_threads); lora.free_params_buffer(); int64_t t1 = ggml_time_ms(); @@ -1206,7 +1207,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, if (sd_ctx->sd->stacked_id) { if (!sd_ctx->sd->pmid_lora->applied) { t0 = ggml_time_ms(); - sd_ctx->sd->pmid_lora->apply(sd_ctx->sd->tensors, sd_ctx->sd->n_threads); + sd_ctx->sd->pmid_lora->apply(sd_ctx->sd->tensors, sd_ctx->sd->version, sd_ctx->sd->n_threads); t1 = ggml_time_ms(); sd_ctx->sd->pmid_lora->applied = true; LOG_INFO("pmid_lora apply completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); From 8f4ab9add3788b15e928ee8e3d7d0568423abf1f Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 28 Dec 2024 06:04:49 +0100 Subject: [PATCH 019/143] feat: support Inpaint models (#511) --- conditioner.hpp | 26 +++---- control.hpp | 6 +- diffusion_model.hpp | 7 +- examples/cli/main.cpp | 23 ++++++ flux.hpp | 40 ++++++++-- ggml_extend.hpp | 37 ++++++++- model.cpp | 85 +++++++++++++++++---- model.h | 34 ++++++++- stable-diffusion.cpp | 170 ++++++++++++++++++++++++++++++++++++++---- stable-diffusion.h | 1 + unet.hpp | 16 ++-- 11 files changed, 382 insertions(+), 63 deletions(-) diff --git a/conditioner.hpp b/conditioner.hpp index 5b3f20dd1..8d1ec31bc 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -61,18 +61,18 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { SDVersion version = VERSION_SD1, PMVersion pv = PM_VERSION_1, int clip_skip = -1) - : version(version), pm_version(pv), tokenizer(version == VERSION_SD2 ? 0 : 49407), embd_dir(embd_dir) { + : version(version), pm_version(pv), tokenizer(sd_version_is_sd2(version) ? 0 : 49407), embd_dir(embd_dir) { if (clip_skip <= 0) { clip_skip = 1; - if (version == VERSION_SD2 || version == VERSION_SDXL) { + if (sd_version_is_sd2(version) || sd_version_is_sdxl(version)) { clip_skip = 2; } } - if (version == VERSION_SD1) { + if (sd_version_is_sd1(version)) { text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip); - } else if (version == VERSION_SD2) { + } else if (sd_version_is_sd2(version)) { text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPEN_CLIP_VIT_H_14, clip_skip); - } else if (version == VERSION_SDXL) { + } else if (sd_version_is_sdxl(version)) { text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip, false); text_model2 = std::make_shared(backend, tensor_types, "cond_stage_model.1.transformer.text_model", OPEN_CLIP_VIT_BIGG_14, clip_skip, false); } @@ -80,35 +80,35 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { void set_clip_skip(int clip_skip) { text_model->set_clip_skip(clip_skip); - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { text_model2->set_clip_skip(clip_skip); } } void get_param_tensors(std::map& tensors) { text_model->get_param_tensors(tensors, "cond_stage_model.transformer.text_model"); - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { text_model2->get_param_tensors(tensors, "cond_stage_model.1.transformer.text_model"); } } void alloc_params_buffer() { text_model->alloc_params_buffer(); - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { text_model2->alloc_params_buffer(); } } void free_params_buffer() { text_model->free_params_buffer(); - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { text_model2->free_params_buffer(); } } size_t get_params_buffer_size() { size_t buffer_size = text_model->get_params_buffer_size(); - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { buffer_size += text_model2->get_params_buffer_size(); } return buffer_size; @@ -402,7 +402,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { auto input_ids = vector_to_ggml_tensor_i32(work_ctx, chunk_tokens); struct ggml_tensor* input_ids2 = NULL; size_t max_token_idx = 0; - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { auto it = std::find(chunk_tokens.begin(), chunk_tokens.end(), tokenizer.EOS_TOKEN_ID); if (it != chunk_tokens.end()) { std::fill(std::next(it), chunk_tokens.end(), 0); @@ -427,7 +427,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { false, &chunk_hidden_states1, work_ctx); - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { text_model2->compute(n_threads, input_ids2, 0, @@ -486,7 +486,7 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { ggml_nelements(hidden_states) / chunk_hidden_states->ne[0]); ggml_tensor* vec = NULL; - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { int out_dim = 256; vec = ggml_new_tensor_1d(work_ctx, GGML_TYPE_F32, adm_in_channels); // [0:1280] diff --git a/control.hpp b/control.hpp index ed36db280..23b75feff 100644 --- a/control.hpp +++ b/control.hpp @@ -34,11 +34,11 @@ class ControlNetBlock : public GGMLBlock { ControlNetBlock(SDVersion version = VERSION_SD1) : version(version) { - if (version == VERSION_SD2) { + if (sd_version_is_sd2(version)) { context_dim = 1024; num_head_channels = 64; num_heads = -1; - } else if (version == VERSION_SDXL) { + } else if (sd_version_is_sdxl(version)) { context_dim = 2048; attention_resolutions = {4, 2}; channel_mult = {1, 2, 4}; @@ -58,7 +58,7 @@ class ControlNetBlock : public GGMLBlock { // time_embed_1 is nn.SiLU() blocks["time_embed.2"] = std::shared_ptr(new Linear(time_embed_dim, time_embed_dim)); - if (version == VERSION_SDXL || version == VERSION_SVD) { + if (sd_version_is_sdxl(version) || version == VERSION_SVD) { blocks["label_emb.0.0"] = std::shared_ptr(new Linear(adm_in_channels, time_embed_dim)); // label_emb_1 is nn.SiLU() blocks["label_emb.0.2"] = std::shared_ptr(new Linear(time_embed_dim, time_embed_dim)); diff --git a/diffusion_model.hpp b/diffusion_model.hpp index cbc0cd4c1..ee4d88f0c 100644 --- a/diffusion_model.hpp +++ b/diffusion_model.hpp @@ -133,8 +133,9 @@ struct FluxModel : public DiffusionModel { FluxModel(ggml_backend_t backend, std::map& tensor_types, - bool flash_attn = false) - : flux(backend, tensor_types, "model.diffusion_model", flash_attn) { + SDVersion version = VERSION_FLUX, + bool flash_attn = false) + : flux(backend, tensor_types, "model.diffusion_model", version, flash_attn) { } void alloc_params_buffer() { @@ -174,7 +175,7 @@ struct FluxModel : public DiffusionModel { struct ggml_tensor** output = NULL, struct ggml_context* output_ctx = NULL, std::vector skip_layers = std::vector()) { - return flux.compute(n_threads, x, timesteps, context, y, guidance, output, output_ctx, skip_layers); + return flux.compute(n_threads, x, timesteps, context, c_concat, y, guidance, output, output_ctx, skip_layers); } }; diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 4b47286f4..5a48b3d61 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -85,6 +85,7 @@ struct SDParams { std::string lora_model_dir; std::string output_path = "output.png"; std::string input_path; + std::string mask_path; std::string control_image_path; std::string prompt; @@ -148,6 +149,7 @@ void print_params(SDParams params) { printf(" normalize input image : %s\n", params.normalize_input ? "true" : "false"); printf(" output_path: %s\n", params.output_path.c_str()); printf(" init_img: %s\n", params.input_path.c_str()); + printf(" mask_img: %s\n", params.mask_path.c_str()); printf(" control_image: %s\n", params.control_image_path.c_str()); printf(" clip on cpu: %s\n", params.clip_on_cpu ? "true" : "false"); printf(" controlnet cpu: %s\n", params.control_net_cpu ? "true" : "false"); @@ -384,6 +386,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.input_path = argv[i]; + } else if (arg == "--mask") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.mask_path = argv[i]; } else if (arg == "--control-image") { if (++i >= argc) { invalid_arg = true; @@ -803,6 +811,8 @@ int main(int argc, const char* argv[]) { bool vae_decode_only = true; uint8_t* input_image_buffer = NULL; uint8_t* control_image_buffer = NULL; + uint8_t* mask_image_buffer = NULL; + if (params.mode == IMG2IMG || params.mode == IMG2VID) { vae_decode_only = false; @@ -907,6 +917,18 @@ int main(int argc, const char* argv[]) { } } + if (params.mask_path != "") { + int c = 0; + mask_image_buffer = stbi_load(params.mask_path.c_str(), ¶ms.width, ¶ms.height, &c, 1); + } else { + std::vector arr(params.width * params.height, 255); + mask_image_buffer = arr.data(); + } + sd_image_t mask_image = {(uint32_t)params.width, + (uint32_t)params.height, + 1, + mask_image_buffer}; + sd_image_t* results; if (params.mode == TXT2IMG) { results = txt2img(sd_ctx, @@ -976,6 +998,7 @@ int main(int argc, const char* argv[]) { } else { results = img2img(sd_ctx, input_image, + mask_image, params.prompt.c_str(), params.negative_prompt.c_str(), params.clip_skip, diff --git a/flux.hpp b/flux.hpp index fdd00ebcb..20ff41096 100644 --- a/flux.hpp +++ b/flux.hpp @@ -490,6 +490,7 @@ namespace Flux { struct FluxParams { int64_t in_channels = 64; + int64_t out_channels = 64; int64_t vec_in_dim = 768; int64_t context_in_dim = 4096; int64_t hidden_size = 3072; @@ -642,8 +643,7 @@ namespace Flux { Flux() {} Flux(FluxParams params) : params(params) { - int64_t out_channels = params.in_channels; - int64_t pe_dim = params.hidden_size / params.num_heads; + int64_t pe_dim = params.hidden_size / params.num_heads; blocks["img_in"] = std::shared_ptr(new Linear(params.in_channels, params.hidden_size, true)); blocks["time_in"] = std::shared_ptr(new MLPEmbedder(256, params.hidden_size)); @@ -669,7 +669,7 @@ namespace Flux { params.flash_attn)); } - blocks["final_layer"] = std::shared_ptr(new LastLayer(params.hidden_size, 1, out_channels)); + blocks["final_layer"] = std::shared_ptr(new LastLayer(params.hidden_size, 1, params.out_channels)); } struct ggml_tensor* patchify(struct ggml_context* ctx, @@ -789,6 +789,7 @@ namespace Flux { struct ggml_tensor* x, struct ggml_tensor* timestep, struct ggml_tensor* context, + struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor* pe, @@ -797,6 +798,7 @@ namespace Flux { // x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images) // timestep: (N,) tensor of diffusion timesteps // context: (N, L, D) + // c_concat: NULL, or for (N,C+M, H, W) for Fill // y: (N, adm_in_channels) tensor of class labels // guidance: (N,) // pe: (L, d_head/2, 2, 2) @@ -806,6 +808,7 @@ namespace Flux { int64_t W = x->ne[0]; int64_t H = x->ne[1]; + int64_t C = x->ne[2]; int64_t patch_size = 2; int pad_h = (patch_size - H % patch_size) % patch_size; int pad_w = (patch_size - W % patch_size) % patch_size; @@ -814,6 +817,19 @@ namespace Flux { // img = rearrange(x, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=patch_size, pw=patch_size) auto img = patchify(ctx, x, patch_size); // [N, h*w, C * patch_size * patch_size] + if (c_concat != NULL) { + ggml_tensor* masked = ggml_view_4d(ctx, c_concat, c_concat->ne[0], c_concat->ne[1], C, 1, c_concat->nb[1], c_concat->nb[2], c_concat->nb[3], 0); + ggml_tensor* mask = ggml_view_4d(ctx, c_concat, c_concat->ne[0], c_concat->ne[1], 8 * 8, 1, c_concat->nb[1], c_concat->nb[2], c_concat->nb[3], c_concat->nb[2] * C); + + masked = ggml_pad(ctx, masked, pad_w, pad_h, 0, 0); + mask = ggml_pad(ctx, mask, pad_w, pad_h, 0, 0); + + masked = patchify(ctx, masked, patch_size); + mask = patchify(ctx, mask, patch_size); + + img = ggml_concat(ctx, img, ggml_concat(ctx, masked, mask, 0), 0); + } + auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, skip_layers); // [N, h*w, C * patch_size * patch_size] // rearrange(out, "b (h w) (c ph pw) -> b c (h ph) (w pw)", h=h_len, w=w_len, ph=2, pw=2) @@ -834,12 +850,16 @@ namespace Flux { FluxRunner(ggml_backend_t backend, std::map& tensor_types = empty_tensor_types, const std::string prefix = "", + SDVersion version = VERSION_FLUX, bool flash_attn = false) : GGMLRunner(backend) { flux_params.flash_attn = flash_attn; flux_params.guidance_embed = false; flux_params.depth = 0; flux_params.depth_single_blocks = 0; + if (version == VERSION_FLUX_FILL) { + flux_params.in_channels = 384; + } for (auto pair : tensor_types) { std::string tensor_name = pair.first; if (tensor_name.find("model.diffusion_model.") == std::string::npos) @@ -886,14 +906,18 @@ namespace Flux { struct ggml_cgraph* build_graph(struct ggml_tensor* x, struct ggml_tensor* timesteps, struct ggml_tensor* context, + struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, std::vector skip_layers = std::vector()) { GGML_ASSERT(x->ne[3] == 1); struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, FLUX_GRAPH_SIZE, false); - x = to_backend(x); - context = to_backend(context); + x = to_backend(x); + context = to_backend(context); + if (c_concat != NULL) { + c_concat = to_backend(c_concat); + } y = to_backend(y); timesteps = to_backend(timesteps); if (flux_params.guidance_embed) { @@ -913,6 +937,7 @@ namespace Flux { x, timesteps, context, + c_concat, y, guidance, pe, @@ -927,6 +952,7 @@ namespace Flux { struct ggml_tensor* x, struct ggml_tensor* timesteps, struct ggml_tensor* context, + struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor** output = NULL, @@ -938,7 +964,7 @@ namespace Flux { // y: [N, adm_in_channels] or [1, adm_in_channels] // guidance: [N, ] auto get_graph = [&]() -> struct ggml_cgraph* { - return build_graph(x, timesteps, context, y, guidance, skip_layers); + return build_graph(x, timesteps, context, c_concat, y, guidance, skip_layers); }; GGMLRunner::compute(get_graph, n_threads, false, output, output_ctx); @@ -978,7 +1004,7 @@ namespace Flux { struct ggml_tensor* out = NULL; int t0 = ggml_time_ms(); - compute(8, x, timesteps, context, y, guidance, &out, work_ctx); + compute(8, x, timesteps, context, NULL, y, guidance, &out, work_ctx); int t1 = ggml_time_ms(); print_ggml_tensor(out); diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 8afcd367c..5f1db9152 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -290,6 +290,42 @@ __STATIC_INLINE__ void sd_image_to_tensor(const uint8_t* image_data, } } +__STATIC_INLINE__ void sd_mask_to_tensor(const uint8_t* image_data, + struct ggml_tensor* output, + bool scale = true) { + int64_t width = output->ne[0]; + int64_t height = output->ne[1]; + int64_t channels = output->ne[2]; + GGML_ASSERT(channels == 1 && output->type == GGML_TYPE_F32); + for (int iy = 0; iy < height; iy++) { + for (int ix = 0; ix < width; ix++) { + float value = *(image_data + iy * width * channels + ix); + if (scale) { + value /= 255.f; + } + ggml_tensor_set_f32(output, value, ix, iy); + } + } +} + +__STATIC_INLINE__ void sd_apply_mask(struct ggml_tensor* image_data, + struct ggml_tensor* mask, + struct ggml_tensor* output) { + int64_t width = output->ne[0]; + int64_t height = output->ne[1]; + int64_t channels = output->ne[2]; + GGML_ASSERT(output->type == GGML_TYPE_F32); + for (int ix = 0; ix < width; ix++) { + for (int iy = 0; iy < height; iy++) { + float m = ggml_tensor_get_f32(mask, ix, iy); + for (int k = 0; k < channels; k++) { + float value = ((float)(m < 254.5/255)) * (ggml_tensor_get_f32(image_data, ix, iy, k) - .5) + .5; + ggml_tensor_set_f32(output, value, ix, iy, k); + } + } + } +} + __STATIC_INLINE__ void sd_mul_images_to_tensor(const uint8_t* image_data, struct ggml_tensor* output, int idx, @@ -1144,7 +1180,6 @@ struct GGMLRunner { } #endif ggml_backend_graph_compute(backend, gf); - #ifdef GGML_PERF ggml_graph_print(gf); #endif diff --git a/model.cpp b/model.cpp index c90918ad2..dae1e0d56 100644 --- a/model.cpp +++ b/model.cpp @@ -1458,24 +1458,49 @@ bool ModelLoader::init_from_ckpt_file(const std::string& file_path, const std::s } SDVersion ModelLoader::get_sd_version() { - TensorStorage token_embedding_weight; + TensorStorage token_embedding_weight, input_block_weight; + bool input_block_checked = false; + + bool has_multiple_encoders = false; + bool is_unet = false; + + bool is_xl = false; + bool is_flux = false; + +#define found_family (is_xl || is_flux) for (auto& tensor_storage : tensor_storages) { - if (tensor_storage.name.find("model.diffusion_model.double_blocks.") != std::string::npos) { - return VERSION_FLUX; - } - if (tensor_storage.name.find("model.diffusion_model.joint_blocks.") != std::string::npos) { - return VERSION_SD3; - } - if (tensor_storage.name.find("conditioner.embedders.1") != std::string::npos) { - return VERSION_SDXL; - } - if (tensor_storage.name.find("cond_stage_model.1") != std::string::npos) { - return VERSION_SDXL; - } - if (tensor_storage.name.find("model.diffusion_model.input_blocks.8.0.time_mixer.mix_factor") != std::string::npos) { - return VERSION_SVD; + if (!found_family) { + if (tensor_storage.name.find("model.diffusion_model.double_blocks.") != std::string::npos) { + is_flux = true; + if (input_block_checked) { + break; + } + } + if (tensor_storage.name.find("model.diffusion_model.joint_blocks.") != std::string::npos) { + return VERSION_SD3; + } + if (tensor_storage.name.find("model.diffusion_model.input_blocks.") != std::string::npos) { + is_unet = true; + if(has_multiple_encoders){ + is_xl = true; + if (input_block_checked) { + break; + } + } + } + if (tensor_storage.name.find("conditioner.embedders.1") != std::string::npos || tensor_storage.name.find("cond_stage_model.1") != std::string::npos) { + has_multiple_encoders = true; + if(is_unet){ + is_xl = true; + if (input_block_checked) { + break; + } + } + } + if (tensor_storage.name.find("model.diffusion_model.input_blocks.8.0.time_mixer.mix_factor") != std::string::npos) { + return VERSION_SVD; + } } - if (tensor_storage.name == "cond_stage_model.transformer.text_model.embeddings.token_embedding.weight" || tensor_storage.name == "cond_stage_model.model.token_embedding.weight" || tensor_storage.name == "text_model.embeddings.token_embedding.weight" || @@ -1485,11 +1510,39 @@ SDVersion ModelLoader::get_sd_version() { token_embedding_weight = tensor_storage; // break; } + if (tensor_storage.name == "model.diffusion_model.input_blocks.0.0.weight" || tensor_storage.name == "model.diffusion_model.img_in.weight") { + input_block_weight = tensor_storage; + input_block_checked = true; + if (found_family) { + break; + } + } + } + bool is_inpaint = input_block_weight.ne[2] == 9; + if (is_xl) { + if (is_inpaint) { + return VERSION_SDXL_INPAINT; + } + return VERSION_SDXL; + } + + if (is_flux) { + is_inpaint = input_block_weight.ne[0] == 384; + if (is_inpaint) { + return VERSION_FLUX_FILL; + } + return VERSION_FLUX; } if (token_embedding_weight.ne[0] == 768) { + if (is_inpaint) { + return VERSION_SD1_INPAINT; + } return VERSION_SD1; } else if (token_embedding_weight.ne[0] == 1024) { + if (is_inpaint) { + return VERSION_SD2_INPAINT; + } return VERSION_SD2; } return VERSION_COUNT; diff --git a/model.h b/model.h index 29d46c192..95bbf1da2 100644 --- a/model.h +++ b/model.h @@ -19,16 +19,20 @@ enum SDVersion { VERSION_SD1, + VERSION_SD1_INPAINT, VERSION_SD2, + VERSION_SD2_INPAINT, VERSION_SDXL, + VERSION_SDXL_INPAINT, VERSION_SVD, VERSION_SD3, VERSION_FLUX, + VERSION_FLUX_FILL, VERSION_COUNT, }; static inline bool sd_version_is_flux(SDVersion version) { - if (version == VERSION_FLUX) { + if (version == VERSION_FLUX || version == VERSION_FLUX_FILL) { return true; } return false; @@ -41,6 +45,34 @@ static inline bool sd_version_is_sd3(SDVersion version) { return false; } +static inline bool sd_version_is_sd1(SDVersion version) { + if (version == VERSION_SD1 || version == VERSION_SD1_INPAINT) { + return true; + } + return false; +} + +static inline bool sd_version_is_sd2(SDVersion version) { + if (version == VERSION_SD2 || version == VERSION_SD2_INPAINT) { + return true; + } + return false; +} + +static inline bool sd_version_is_sdxl(SDVersion version) { + if (version == VERSION_SDXL || version == VERSION_SDXL_INPAINT) { + return true; + } + return false; +} + +static inline bool sd_version_is_inpaint(SDVersion version) { + if (version == VERSION_SD1_INPAINT || version == VERSION_SD2_INPAINT || version == VERSION_SDXL_INPAINT || version == VERSION_FLUX_FILL) { + return true; + } + return false; +} + static inline bool sd_version_is_dit(SDVersion version) { if (sd_version_is_flux(version) || sd_version_is_sd3(version)) { return true; diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 4d5a7d9b6..e62b2ca49 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -26,11 +26,15 @@ const char* model_version_to_str[] = { "SD 1.x", + "SD 1.x Inpaint", "SD 2.x", + "SD 2.x Inpaint", "SDXL", + "SDXL Inpaint", "SVD", "SD3.x", - "Flux"}; + "Flux", + "Flux Fill"}; const char* sampling_methods_str[] = { "Euler A", @@ -263,7 +267,7 @@ class StableDiffusionGGML { model_loader.set_wtype_override(wtype); } - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { vae_wtype = GGML_TYPE_F32; model_loader.set_wtype_override(GGML_TYPE_F32, "vae."); } @@ -275,7 +279,7 @@ class StableDiffusionGGML { LOG_DEBUG("ggml tensor size = %d bytes", (int)sizeof(ggml_tensor)); - if (version == VERSION_SDXL) { + if (sd_version_is_sdxl(version)) { scale_factor = 0.13025f; if (vae_path.size() == 0 && taesd_path.size() == 0) { LOG_WARN( @@ -329,7 +333,7 @@ class StableDiffusionGGML { diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types); } else if (sd_version_is_flux(version)) { cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); - diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, diffusion_flash_attn); + diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, version, diffusion_flash_attn); } else { if (id_embeddings_path.find("v2") != std::string::npos) { cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, embeddings_path, version, PM_VERSION_2); @@ -517,8 +521,8 @@ class StableDiffusionGGML { // check is_using_v_parameterization_for_sd2 bool is_using_v_parameterization = false; - if (version == VERSION_SD2) { - if (is_using_v_parameterization_for_sd2(ctx)) { + if (sd_version_is_sd2(version)) { + if (is_using_v_parameterization_for_sd2(ctx, sd_version_is_inpaint(version))) { is_using_v_parameterization = true; } } else if (version == VERSION_SVD) { @@ -592,7 +596,7 @@ class StableDiffusionGGML { return true; } - bool is_using_v_parameterization_for_sd2(ggml_context* work_ctx) { + bool is_using_v_parameterization_for_sd2(ggml_context* work_ctx, bool is_inpaint = false) { struct ggml_tensor* x_t = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, 8, 8, 4, 1); ggml_set_f32(x_t, 0.5); struct ggml_tensor* c = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, 1024, 2, 1, 1); @@ -600,9 +604,13 @@ class StableDiffusionGGML { struct ggml_tensor* timesteps = ggml_new_tensor_1d(work_ctx, GGML_TYPE_F32, 1); ggml_set_f32(timesteps, 999); + + struct ggml_tensor* concat = is_inpaint ? ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, 8, 8, 5, 1) : NULL; + ggml_set_f32(concat, 0); + int64_t t0 = ggml_time_ms(); struct ggml_tensor* out = ggml_dup_tensor(work_ctx, x_t); - diffusion_model->compute(n_threads, x_t, timesteps, c, NULL, NULL, NULL, -1, {}, 0.f, &out); + diffusion_model->compute(n_threads, x_t, timesteps, c, concat, NULL, NULL, -1, {}, 0.f, &out); diffusion_model->free_compute_buffer(); double result = 0.f; @@ -785,7 +793,20 @@ class StableDiffusionGGML { std::vector skip_layers = {}, float slg_scale = 0, float skip_layer_start = 0.01, - float skip_layer_end = 0.2) { + float skip_layer_end = 0.2, + ggml_tensor* noise_mask = nullptr) { + LOG_DEBUG("Sample"); + struct ggml_init_params params; + size_t data_size = ggml_row_size(init_latent->type, init_latent->ne[0]); + for (int i = 1; i < 4; i++) { + data_size *= init_latent->ne[i]; + } + data_size += 1024; + params.mem_size = data_size * 3; + params.mem_buffer = NULL; + params.no_alloc = false; + ggml_context* tmp_ctx = ggml_init(params); + size_t steps = sigmas.size() - 1; // noise = load_tensor_from_file(work_ctx, "./rand0.bin"); // print_ggml_tensor(noise); @@ -944,6 +965,19 @@ class StableDiffusionGGML { pretty_progress(step, (int)steps, (t1 - t0) / 1000000.f); // LOG_INFO("step %d sampling completed taking %.2fs", step, (t1 - t0) * 1.0f / 1000000); } + if (noise_mask != nullptr) { + for (int64_t x = 0; x < denoised->ne[0]; x++) { + for (int64_t y = 0; y < denoised->ne[1]; y++) { + float mask = ggml_tensor_get_f32(noise_mask, x, y); + for (int64_t k = 0; k < denoised->ne[2]; k++) { + float init = ggml_tensor_get_f32(init_latent, x, y, k); + float den = ggml_tensor_get_f32(denoised, x, y, k); + ggml_tensor_set_f32(denoised, init + mask * (den - init), x, y, k); + } + } + } + } + return denoised; }; @@ -1167,7 +1201,8 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, std::vector skip_layers = {}, float slg_scale = 0, float skip_layer_start = 0.01, - float skip_layer_end = 0.2) { + float skip_layer_end = 0.2, + ggml_tensor* masked_image = NULL) { if (seed < 0) { // Generally, when using the provided command line, the seed is always >0. // However, to prevent potential issues if 'stable-diffusion.cpp' is invoked as a library @@ -1317,7 +1352,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, SDCondition uncond; if (cfg_scale != 1.0) { bool force_zero_embeddings = false; - if (sd_ctx->sd->version == VERSION_SDXL && negative_prompt.size() == 0) { + if (sd_version_is_sdxl(sd_ctx->sd->version) && negative_prompt.size() == 0) { force_zero_embeddings = true; } uncond = sd_ctx->sd->cond_stage_model->get_learned_condition(work_ctx, @@ -1354,6 +1389,39 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, int W = width / 8; int H = height / 8; LOG_INFO("sampling using %s method", sampling_methods_str[sample_method]); + ggml_tensor* noise_mask = nullptr; + if (sd_version_is_inpaint(sd_ctx->sd->version)) { + if (masked_image == NULL) { + int64_t mask_channels = 1; + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + mask_channels = 8 * 8; // flatten the whole mask + } + // no mask, set the whole image as masked + masked_image = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, init_latent->ne[0], init_latent->ne[1], mask_channels + init_latent->ne[2], 1); + for (int64_t x = 0; x < masked_image->ne[0]; x++) { + for (int64_t y = 0; y < masked_image->ne[1]; y++) { + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + // TODO: this might be wrong + for (int64_t c = 0; c < init_latent->ne[2]; c++) { + ggml_tensor_set_f32(masked_image, 0, x, y, c); + } + for (int64_t c = init_latent->ne[2]; c < masked_image->ne[2]; c++) { + ggml_tensor_set_f32(masked_image, 1, x, y, c); + } + } else { + ggml_tensor_set_f32(masked_image, 1, x, y, 0); + for (int64_t c = 1; c < masked_image->ne[2]; c++) { + ggml_tensor_set_f32(masked_image, 0, x, y, c); + } + } + } + } + } + cond.c_concat = masked_image; + uncond.c_concat = masked_image; + } else { + noise_mask = masked_image; + } for (int b = 0; b < batch_count; b++) { int64_t sampling_start = ggml_time_ms(); int64_t cur_seed = seed + b; @@ -1389,7 +1457,9 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, skip_layers, slg_scale, skip_layer_start, - skip_layer_end); + skip_layer_end, + noise_mask); + // struct ggml_tensor* x_0 = load_tensor_from_file(ctx, "samples_ddim.bin"); // print_ggml_tensor(x_0); int64_t sampling_end = ggml_time_ms(); @@ -1511,6 +1581,10 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, ggml_set_f32(init_latent, 0.f); } + if (sd_version_is_inpaint(sd_ctx->sd->version)) { + LOG_WARN("This is an inpainting model, this should only be used in img2img mode with a mask"); + } + sd_image_t* result_images = generate_image(sd_ctx, work_ctx, init_latent, @@ -1544,6 +1618,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, sd_image_t* img2img(sd_ctx_t* sd_ctx, sd_image_t init_image, + sd_image_t mask, const char* prompt_c_str, const char* negative_prompt_c_str, int clip_skip, @@ -1583,7 +1658,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, if (sd_ctx->sd->stacked_id) { params.mem_size += static_cast(10 * 1024 * 1024); // 10 MB } - params.mem_size += width * height * 3 * sizeof(float) * 2; + params.mem_size += width * height * 3 * sizeof(float) * 3; params.mem_size *= batch_count; params.mem_buffer = NULL; params.no_alloc = false; @@ -1604,7 +1679,70 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, sd_ctx->sd->rng->manual_seed(seed); ggml_tensor* init_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 3, 1); + ggml_tensor* mask_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 1, 1); + + sd_mask_to_tensor(mask.data, mask_img); + sd_image_to_tensor(init_image.data, init_img); + + ggml_tensor* masked_image; + + if (sd_version_is_inpaint(sd_ctx->sd->version)) { + int64_t mask_channels = 1; + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + mask_channels = 8 * 8; // flatten the whole mask + } + ggml_tensor* masked_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 3, 1); + sd_apply_mask(init_img, mask_img, masked_img); + ggml_tensor* masked_image_0 = NULL; + if (!sd_ctx->sd->use_tiny_autoencoder) { + ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); + masked_image_0 = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); + } else { + masked_image_0 = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); + } + masked_image = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, masked_image_0->ne[0], masked_image_0->ne[1], mask_channels + masked_image_0->ne[2], 1); + for (int ix = 0; ix < masked_image_0->ne[0]; ix++) { + for (int iy = 0; iy < masked_image_0->ne[1]; iy++) { + int mx = ix * 8; + int my = iy * 8; + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + for (int k = 0; k < masked_image_0->ne[2]; k++) { + float v = ggml_tensor_get_f32(masked_image_0, ix, iy, k); + ggml_tensor_set_f32(masked_image, v, ix, iy, k); + } + // "Encode" 8x8 mask chunks into a flattened 1x64 vector, and concatenate to masked image + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + float m = ggml_tensor_get_f32(mask_img, mx + x, my + y); + // TODO: check if the way the mask is flattened is correct (is it supposed to be x*8+y or x+8*y?) + // python code was using "b (h 8) (w 8) -> b (8 8) h w" + ggml_tensor_set_f32(masked_image, m, ix, iy, masked_image_0->ne[2] + x * 8 + y); + } + } + } else { + float m = ggml_tensor_get_f32(mask_img, mx, my); + ggml_tensor_set_f32(masked_image, m, ix, iy, 0); + for (int k = 0; k < masked_image_0->ne[2]; k++) { + float v = ggml_tensor_get_f32(masked_image_0, ix, iy, k); + ggml_tensor_set_f32(masked_image, v, ix, iy, k + mask_channels); + } + } + } + } + } else { + // LOG_WARN("Inpainting with a base model is not great"); + masked_image = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width / 8, height / 8, 1, 1); + for (int ix = 0; ix < masked_image->ne[0]; ix++) { + for (int iy = 0; iy < masked_image->ne[1]; iy++) { + int mx = ix * 8; + int my = iy * 8; + float m = ggml_tensor_get_f32(mask_img, mx, my); + ggml_tensor_set_f32(masked_image, m, ix, iy); + } + } + } + ggml_tensor* init_latent = NULL; if (!sd_ctx->sd->use_tiny_autoencoder) { ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, init_img); @@ -1612,12 +1750,15 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, } else { init_latent = sd_ctx->sd->encode_first_stage(work_ctx, init_img); } + print_ggml_tensor(init_latent, true); size_t t1 = ggml_time_ms(); LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); size_t t_enc = static_cast(sample_steps * strength); + if (t_enc == sample_steps) + t_enc--; LOG_INFO("target t_enc is %zu steps", t_enc); std::vector sigma_sched; sigma_sched.assign(sigmas.begin() + sample_steps - t_enc - 1, sigmas.end()); @@ -1644,7 +1785,8 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, skip_layers_vec, slg_scale, skip_layer_start, - skip_layer_end); + skip_layer_end, + masked_image); size_t t2 = ggml_time_ms(); diff --git a/stable-diffusion.h b/stable-diffusion.h index c67bc8a32..5a758df66 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -174,6 +174,7 @@ SD_API sd_image_t* txt2img(sd_ctx_t* sd_ctx, SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, sd_image_t init_image, + sd_image_t mask_image, const char* prompt, const char* negative_prompt, int clip_skip, diff --git a/unet.hpp b/unet.hpp index 2a7adb3d2..31b7fe986 100644 --- a/unet.hpp +++ b/unet.hpp @@ -166,6 +166,7 @@ class SpatialVideoTransformer : public SpatialTransformer { // ldm.modules.diffusionmodules.openaimodel.UNetModel class UnetModelBlock : public GGMLBlock { protected: + static std::map empty_tensor_types; SDVersion version = VERSION_SD1; // network hparams int in_channels = 4; @@ -183,13 +184,13 @@ class UnetModelBlock : public GGMLBlock { int model_channels = 320; int adm_in_channels = 2816; // only for VERSION_SDXL/SVD - UnetModelBlock(SDVersion version = VERSION_SD1, bool flash_attn = false) + UnetModelBlock(SDVersion version = VERSION_SD1, std::map& tensor_types = empty_tensor_types, bool flash_attn = false) : version(version) { - if (version == VERSION_SD2) { + if (sd_version_is_sd2(version)) { context_dim = 1024; num_head_channels = 64; num_heads = -1; - } else if (version == VERSION_SDXL) { + } else if (sd_version_is_sdxl(version)) { context_dim = 2048; attention_resolutions = {4, 2}; channel_mult = {1, 2, 4}; @@ -204,6 +205,10 @@ class UnetModelBlock : public GGMLBlock { num_head_channels = 64; num_heads = -1; } + if (sd_version_is_inpaint(version)) { + in_channels = 9; + } + // dims is always 2 // use_temporal_attention is always True for SVD @@ -211,7 +216,7 @@ class UnetModelBlock : public GGMLBlock { // time_embed_1 is nn.SiLU() blocks["time_embed.2"] = std::shared_ptr(new Linear(time_embed_dim, time_embed_dim)); - if (version == VERSION_SDXL || version == VERSION_SVD) { + if (sd_version_is_sdxl(version) || version == VERSION_SVD) { blocks["label_emb.0.0"] = std::shared_ptr(new Linear(adm_in_channels, time_embed_dim)); // label_emb_1 is nn.SiLU() blocks["label_emb.0.2"] = std::shared_ptr(new Linear(time_embed_dim, time_embed_dim)); @@ -536,7 +541,7 @@ struct UNetModelRunner : public GGMLRunner { const std::string prefix, SDVersion version = VERSION_SD1, bool flash_attn = false) - : GGMLRunner(backend), unet(version, flash_attn) { + : GGMLRunner(backend), unet(version, tensor_types, flash_attn) { unet.init(params_ctx, tensor_types, prefix); } @@ -566,6 +571,7 @@ struct UNetModelRunner : public GGMLRunner { context = to_backend(context); y = to_backend(y); timesteps = to_backend(timesteps); + c_concat = to_backend(c_concat); for (int i = 0; i < controls.size(); i++) { controls[i] = to_backend(controls[i]); From 0d9d6659a7ebb7fc51902d6a96f2ea60bd0e82d6 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 28 Dec 2024 06:06:17 +0100 Subject: [PATCH 020/143] fix: fix metal build (#513) --- ggml_extend.hpp | 5 ----- stable-diffusion.cpp | 2 +- upscaler.cpp | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 5f1db9152..4aea85850 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -1174,11 +1174,6 @@ struct GGMLRunner { ggml_backend_cpu_set_n_threads(backend, n_threads); } -#ifdef SD_USE_METAL - if (ggml_backend_is_metal(backend)) { - ggml_backend_metal_set_n_cb(backend, n_threads); - } -#endif ggml_backend_graph_compute(backend, gf); #ifdef GGML_PERF ggml_graph_print(gf); diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index e62b2ca49..7025df8da 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -165,7 +165,7 @@ class StableDiffusionGGML { #endif #ifdef SD_USE_METAL LOG_DEBUG("Using Metal backend"); - ggml_backend_metal_log_set_callback(ggml_log_callback_default, nullptr); + ggml_log_set(ggml_log_callback_default, nullptr); backend = ggml_backend_metal_init(); #endif #ifdef SD_USE_VULKAN diff --git a/upscaler.cpp b/upscaler.cpp index 1cf34c1a3..86e5e9b48 100644 --- a/upscaler.cpp +++ b/upscaler.cpp @@ -21,7 +21,7 @@ struct UpscalerGGML { #endif #ifdef SD_USE_METAL LOG_DEBUG("Using Metal backend"); - ggml_backend_metal_log_set_callback(ggml_log_callback_default, nullptr); + ggml_log_set(ggml_log_callback_default, nullptr); backend = ggml_backend_metal_init(); #endif #ifdef SD_USE_VULKAN From 5cc74d1f09b3aca81ff948757cf16a1bf7589e9f Mon Sep 17 00:00:00 2001 From: R0CKSTAR Date: Sat, 28 Dec 2024 13:08:36 +0800 Subject: [PATCH 021/143] feat: support Moore Threads GPU (#529) Signed-off-by: Xiaodong Ye --- CMakeLists.txt | 12 +++++++++++- Dockerfile.musa | 19 +++++++++++++++++++ README.md | 8 ++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.musa diff --git a/CMakeLists.txt b/CMakeLists.txt index 8466ed5d9..455de266a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ option(SD_HIPBLAS "sd: rocm backend" OFF) option(SD_METAL "sd: metal backend" OFF) option(SD_VULKAN "sd: vulkan backend" OFF) option(SD_SYCL "sd: sycl backend" OFF) +option(SD_MUSA "sd: musa backend" OFF) option(SD_FAST_SOFTMAX "sd: x1.5 faster softmax, indeterministic (sometimes, same seed don't generate same image), cuda only" OFF) option(SD_BUILD_SHARED_LIBS "sd: build shared libs" OFF) #option(SD_BUILD_SERVER "sd: build server example" ON) @@ -60,9 +61,18 @@ if (SD_HIPBLAS) endif() endif () +if(SD_MUSA) + message("-- Use MUSA as backend stable-diffusion") + set(GGML_MUSA ON) + add_definitions(-DSD_USE_CUBLAS) + if(SD_FAST_SOFTMAX) + set(GGML_CUDA_FAST_SOFTMAX ON) + endif() +endif() + set(SD_LIB stable-diffusion) -file(GLOB SD_LIB_SOURCES +file(GLOB SD_LIB_SOURCES "*.h" "*.cpp" "*.hpp" diff --git a/Dockerfile.musa b/Dockerfile.musa new file mode 100644 index 000000000..0126e6854 --- /dev/null +++ b/Dockerfile.musa @@ -0,0 +1,19 @@ +ARG MUSA_VERSION=rc3.1.0 + +FROM mthreads/musa:${MUSA_VERSION}-devel-ubuntu22.04 as build + +RUN apt-get update && apt-get install -y cmake + +WORKDIR /sd.cpp + +COPY . . + +RUN mkdir build && cd build && \ + cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_MUSA=ON -DCMAKE_BUILD_TYPE=Release && \ + cmake --build . --config Release + +FROM mthreads/musa:${MUSA_VERSION}-runtime-ubuntu22.04 as runtime + +COPY --from=build /sd.cpp/build/bin/sd /sd + +ENTRYPOINT [ "/sd" ] \ No newline at end of file diff --git a/README.md b/README.md index a17ef7e11..72b465f18 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,14 @@ cmake .. -G "Ninja" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_H cmake --build . --config Release ``` +##### Using MUSA + +This provides BLAS acceleration using the MUSA cores of your Moore Threads GPU. Make sure to have the MUSA toolkit installed. + +```bash +cmake .. -DCMAKE_C_COMPILER=/usr/local/musa/bin/clang -DCMAKE_CXX_COMPILER=/usr/local/musa/bin/clang++ -DSD_MUSA=ON -DCMAKE_BUILD_TYPE=Release +cmake --build . --config Release +``` ##### Using Metal From b5cc1422dacb3c03b6c9a68d5d2ec9628e7fcd8d Mon Sep 17 00:00:00 2001 From: piallai <59734081+piallai@users.noreply.github.com> Date: Sat, 28 Dec 2024 06:12:08 +0100 Subject: [PATCH 022/143] fix: fix typo for skip layers parameters (#492) --- README.md | 4 ++++ examples/cli/main.cpp | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 72b465f18..5ea36b621 100644 --- a/README.md +++ b/README.md @@ -240,6 +240,10 @@ arguments: -p, --prompt [PROMPT] the prompt to render -n, --negative-prompt PROMPT the negative prompt (default: "") --cfg-scale SCALE unconditional guidance scale: (default: 7.0) + --skip-layers LAYERS Layers to skip for SLG steps: (default: [7,8,9]) + --skip-layer-start START SLG enabling point: (default: 0.01) + --skip-layer-end END SLG disabling point: (default: 0.2) + SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END]) --strength STRENGTH strength for noising/unnoising (default: 0.75) --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%) --control-strength STRENGTH strength to apply Control Net (default: 0.9) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 5a48b3d61..3c35b1032 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -209,9 +209,9 @@ void print_usage(int argc, const char* argv[]) { printf(" --cfg-scale SCALE unconditional guidance scale: (default: 7.0)\n"); printf(" --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0)\n"); printf(" 0 means disabled, a value of 2.5 is nice for sd3.5 medium\n"); - printf(" --skip_layers LAYERS Layers to skip for SLG steps: (default: [7,8,9])\n"); - printf(" --skip_layer_start START SLG enabling point: (default: 0.01)\n"); - printf(" --skip_layer_end END SLG disabling point: (default: 0.2)\n"); + printf(" --skip-layers LAYERS Layers to skip for SLG steps: (default: [7,8,9])\n"); + printf(" --skip-layer-start START SLG enabling point: (default: 0.01)\n"); + printf(" --skip-layer-end END SLG disabling point: (default: 0.2)\n"); printf(" SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END])\n"); printf(" --strength STRENGTH strength for noising/unnoising (default: 0.75)\n"); printf(" --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%%)\n"); From d50473dc49e88dc692f20bccd52a84fab374b556 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 28 Dec 2024 06:13:48 +0100 Subject: [PATCH 023/143] feat: support 16 channel tae (taesd/taef1) (#527) --- stable-diffusion.cpp | 2 +- tae.hpp | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 7025df8da..35d49af24 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -360,7 +360,7 @@ class StableDiffusionGGML { first_stage_model->alloc_params_buffer(); first_stage_model->get_param_tensors(tensors, "first_stage_model"); } else { - tae_first_stage = std::make_shared(backend, model_loader.tensor_storages_types, "decoder.layers", vae_decode_only); + tae_first_stage = std::make_shared(backend, model_loader.tensor_storages_types, "decoder.layers", vae_decode_only, version); } // first_stage_model->get_param_tensors(tensors, "first_stage_model."); diff --git a/tae.hpp b/tae.hpp index fee5e8328..683059822 100644 --- a/tae.hpp +++ b/tae.hpp @@ -62,7 +62,8 @@ class TinyEncoder : public UnaryBlock { int num_blocks = 3; public: - TinyEncoder() { + TinyEncoder(int z_channels = 4) + : z_channels(z_channels) { int index = 0; blocks[std::to_string(index++)] = std::shared_ptr(new Conv2d(in_channels, channels, {3, 3}, {1, 1}, {1, 1})); blocks[std::to_string(index++)] = std::shared_ptr(new TAEBlock(channels, channels)); @@ -106,7 +107,10 @@ class TinyDecoder : public UnaryBlock { int num_blocks = 3; public: - TinyDecoder(int index = 0) { + TinyDecoder(int z_channels = 4) + : z_channels(z_channels) { + int index = 0; + blocks[std::to_string(index++)] = std::shared_ptr(new Conv2d(z_channels, channels, {3, 3}, {1, 1}, {1, 1})); index++; // nn.ReLU() @@ -163,12 +167,16 @@ class TAESD : public GGMLBlock { bool decode_only; public: - TAESD(bool decode_only = true) + TAESD(bool decode_only = true, SDVersion version = VERSION_SD1) : decode_only(decode_only) { - blocks["decoder.layers"] = std::shared_ptr(new TinyDecoder()); + int z_channels = 4; + if (sd_version_is_dit(version)) { + z_channels = 16; + } + blocks["decoder.layers"] = std::shared_ptr(new TinyDecoder(z_channels)); if (!decode_only) { - blocks["encoder.layers"] = std::shared_ptr(new TinyEncoder()); + blocks["encoder.layers"] = std::shared_ptr(new TinyEncoder(z_channels)); } } @@ -190,9 +198,10 @@ struct TinyAutoEncoder : public GGMLRunner { TinyAutoEncoder(ggml_backend_t backend, std::map& tensor_types, const std::string prefix, - bool decoder_only = true) + bool decoder_only = true, + SDVersion version = VERSION_SD1) : decode_only(decoder_only), - taesd(decode_only), + taesd(decode_only, version), GGMLRunner(backend) { taesd.init(params_ctx, tensor_types, prefix); } From 348a54e34a2b3dea0e781f58260532a335757416 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 28 Dec 2024 06:14:52 +0100 Subject: [PATCH 024/143] feat: use pretty-progress for tensor loading (#516) --- model.cpp | 8 +++++++- util.cpp | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/model.cpp b/model.cpp index dae1e0d56..dcbaae5bc 100644 --- a/model.cpp +++ b/model.cpp @@ -1748,9 +1748,11 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend } return true; }; - + int tensor_count = 0; + int64_t t1 = ggml_time_ms(); for (auto& tensor_storage : processed_tensor_storages) { if (tensor_storage.file_index != file_index) { + ++tensor_count; continue; } ggml_tensor* dst_tensor = NULL; @@ -1762,6 +1764,7 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend } if (dst_tensor == NULL) { + ++tensor_count; continue; } @@ -1828,6 +1831,9 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend ggml_backend_tensor_set(dst_tensor, convert_buffer.data(), 0, ggml_nbytes(dst_tensor)); } } + int64_t t2 = ggml_time_ms(); + pretty_progress(++tensor_count, processed_tensor_storages.size(), (t2 - t1) / 1000.0f); + t1 = t2; } if (zip != NULL) { diff --git a/util.cpp b/util.cpp index 3bcee0946..01c01200e 100644 --- a/util.cpp +++ b/util.cpp @@ -348,7 +348,7 @@ void pretty_progress(int step, int steps, float time) { } } progress += "|"; - printf(time > 1.0f ? "\r%s %i/%i - %.2fs/it" : "\r%s %i/%i - %.2fit/s", + printf(time > 1.0f ? "\r%s %i/%i - %.2fs/it" : "\r%s %i/%i - %.2fit/s\033[K", progress.c_str(), step, steps, time > 1.0f || time == 0 ? time : (1.0f / time)); fflush(stdout); // for linux From dcf91f9e0f2cbf9da472ee2a556751ed4bab2d2a Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 28 Dec 2024 13:27:51 +0800 Subject: [PATCH 025/143] chore: change SD_CUBLAS/SD_USE_CUBLAS to SD_CUDA/SD_USE_CUDA --- .github/workflows/build.yml | 2 +- CMakeLists.txt | 10 +++++----- README.md | 4 ++-- ggml_extend.hpp | 6 +++--- stable-diffusion.cpp | 2 +- upscaler.cpp | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0eeb4f4a..8569ccfd5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -163,7 +163,7 @@ jobs: - build: "avx512" defines: "-DGGML_AVX512=ON -DSD_BUILD_SHARED_LIBS=ON" - build: "cuda12" - defines: "-DSD_CUBLAS=ON -DSD_BUILD_SHARED_LIBS=ON" + defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON" # - build: "rocm5.5" # defines: '-G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_HIPBLAS=ON -DCMAKE_BUILD_TYPE=Release -DAMDGPU_TARGETS="gfx1100;gfx1102;gfx1030" -DSD_BUILD_SHARED_LIBS=ON' - build: 'vulkan' diff --git a/CMakeLists.txt b/CMakeLists.txt index 455de266a..6a60b8c46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ endif() # general #option(SD_BUILD_TESTS "sd: build tests" ${SD_STANDALONE}) option(SD_BUILD_EXAMPLES "sd: build examples" ${SD_STANDALONE}) -option(SD_CUBLAS "sd: cuda backend" OFF) +option(SD_CUDA "sd: cuda backend" OFF) option(SD_HIPBLAS "sd: rocm backend" OFF) option(SD_METAL "sd: metal backend" OFF) option(SD_VULKAN "sd: vulkan backend" OFF) @@ -34,10 +34,10 @@ option(SD_FAST_SOFTMAX "sd: x1.5 faster softmax, indeterministic ( option(SD_BUILD_SHARED_LIBS "sd: build shared libs" OFF) #option(SD_BUILD_SERVER "sd: build server example" ON) -if(SD_CUBLAS) - message("-- Use CUBLAS as backend stable-diffusion") +if(SD_CUDA) + message("-- Use CUDA as backend stable-diffusion") set(GGML_CUDA ON) - add_definitions(-DSD_USE_CUBLAS) + add_definitions(-DSD_USE_CUDA) endif() if(SD_METAL) @@ -55,7 +55,7 @@ endif () if (SD_HIPBLAS) message("-- Use HIPBLAS as backend stable-diffusion") set(GGML_HIPBLAS ON) - add_definitions(-DSD_USE_CUBLAS) + add_definitions(-DSD_USE_CUDA) if(SD_FAST_SOFTMAX) set(GGML_CUDA_FAST_SOFTMAX ON) endif() diff --git a/README.md b/README.md index 5ea36b621..01b1fa4ae 100644 --- a/README.md +++ b/README.md @@ -113,12 +113,12 @@ cmake .. -DGGML_OPENBLAS=ON cmake --build . --config Release ``` -##### Using CUBLAS +##### Using CUDA This provides BLAS acceleration using the CUDA cores of your Nvidia GPU. Make sure to have the CUDA toolkit installed. You can download it from your Linux distro's package manager (e.g. `apt install nvidia-cuda-toolkit`) or from here: [CUDA Toolkit](https://developer.nvidia.com/cuda-downloads). Recommended to have at least 4 GB of VRAM. ``` -cmake .. -DSD_CUBLAS=ON +cmake .. -DSD_CUDA=ON cmake --build . --config Release ``` diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 4aea85850..035f088f4 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -27,7 +27,7 @@ #include "model.h" -#ifdef SD_USE_CUBLAS +#ifdef SD_USE_CUDA #include "ggml-cuda.h" #endif @@ -708,7 +708,7 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention(struct ggml_context* ctx struct ggml_tensor* k, struct ggml_tensor* v, bool mask = false) { -#if defined(SD_USE_FLASH_ATTENTION) && !defined(SD_USE_CUBLAS) && !defined(SD_USE_METAL) && !defined(SD_USE_VULKAN) && !defined(SD_USE_SYCL) +#if defined(SD_USE_FLASH_ATTENTION) && !defined(SD_USE_CUDA) && !defined(SD_USE_METAL) && !defined(SD_USE_VULKAN) && !defined(SD_USE_SYCL) struct ggml_tensor* kqv = ggml_flash_attn(ctx, q, k, v, false); // [N * n_head, n_token, d_head] #else float d_head = (float)q->ne[0]; @@ -864,7 +864,7 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_group_norm(struct ggml_context* ct } __STATIC_INLINE__ void ggml_backend_tensor_get_and_sync(ggml_backend_t backend, const struct ggml_tensor* tensor, void* data, size_t offset, size_t size) { -#if defined(SD_USE_CUBLAS) || defined(SD_USE_SYCL) +#if defined(SD_USE_CUDA) || defined(SD_USE_SYCL) if (!ggml_backend_is_cpu(backend)) { ggml_backend_tensor_get_async(backend, tensor, data, offset, size); ggml_backend_synchronize(backend); diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 35d49af24..e2daf5764 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -159,7 +159,7 @@ class StableDiffusionGGML { bool vae_on_cpu, bool diffusion_flash_attn) { use_tiny_autoencoder = taesd_path.size() > 0; -#ifdef SD_USE_CUBLAS +#ifdef SD_USE_CUDA LOG_DEBUG("Using CUDA backend"); backend = ggml_backend_cuda_init(0); #endif diff --git a/upscaler.cpp b/upscaler.cpp index 86e5e9b48..0c11b666e 100644 --- a/upscaler.cpp +++ b/upscaler.cpp @@ -15,7 +15,7 @@ struct UpscalerGGML { } bool load_from_file(const std::string& esrgan_path) { -#ifdef SD_USE_CUBLAS +#ifdef SD_USE_CUDA LOG_DEBUG("Using CUDA backend"); backend = ggml_backend_cuda_init(0); #endif From 27edb765a5811562f8cb713cd155f1dc9c98483d Mon Sep 17 00:00:00 2001 From: idostyle Date: Sat, 18 Jan 2025 06:09:22 +0100 Subject: [PATCH 026/143] chore: fix CI windows release artifacts (#532) --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8569ccfd5..043ee4b36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -155,13 +155,13 @@ jobs: matrix: include: - build: "noavx" - defines: "-DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DSD_BUILD_SHARED_LIBS=ON" + defines: "-DGGML_NATIVE=OFF -DGGML_AVX=OFF -DGGML_AVX2=OFF -DGGML_FMA=OFF -DSD_BUILD_SHARED_LIBS=ON" - build: "avx2" - defines: "-DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON" + defines: "-DGGML_NATIVE=OFF -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON" - build: "avx" - defines: "-DGGML_AVX2=OFF -DSD_BUILD_SHARED_LIBS=ON" + defines: "-DGGML_NATIVE=OFF -DGGML_AVX=ON -DGGML_AVX2=OFF -DSD_BUILD_SHARED_LIBS=ON" - build: "avx512" - defines: "-DGGML_AVX512=ON -DSD_BUILD_SHARED_LIBS=ON" + defines: "-DGGML_NATIVE=OFF -DGGML_AVX512=ON -DGGML_AVX=ON -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON" - build: "cuda12" defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON" # - build: "rocm5.5" From b70aaa672a17505c3dd4cc4eb3c486164fb3608f Mon Sep 17 00:00:00 2001 From: null-define <61399676+null-define@users.noreply.github.com> Date: Sat, 18 Jan 2025 13:11:39 +0800 Subject: [PATCH 027/143] chore: fix amd rocm build (#571) --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a60b8c46..f82cb5f0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ endif () if (SD_HIPBLAS) message("-- Use HIPBLAS as backend stable-diffusion") - set(GGML_HIPBLAS ON) + set(GGML_HIP ON) add_definitions(-DSD_USE_CUDA) if(SD_FAST_SOFTMAX) set(GGML_CUDA_FAST_SOFTMAX ON) From 4fe83d52cf4506ea4fb87ccd5ed02efe69dc4167 Mon Sep 17 00:00:00 2001 From: ag2s20150909 <19373730+ag2s20150909@users.noreply.github.com> Date: Sat, 18 Jan 2025 13:12:26 +0800 Subject: [PATCH 028/143] chore: fix CUDA on GitHub Action (#567) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 043ee4b36..bb69fff75 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -163,7 +163,7 @@ jobs: - build: "avx512" defines: "-DGGML_NATIVE=OFF -DGGML_AVX512=ON -DGGML_AVX=ON -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON" - build: "cuda12" - defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON" + defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES=60;61;70;75" # - build: "rocm5.5" # defines: '-G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_HIPBLAS=ON -DCMAKE_BUILD_TYPE=Release -DAMDGPU_TARGETS="gfx1100;gfx1102;gfx1030" -DSD_BUILD_SHARED_LIBS=ON' - build: 'vulkan' From 587a37b2e20da2c4c494401e4a77c7900ecd529e Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 18 Jan 2025 06:13:34 +0100 Subject: [PATCH 029/143] fix: avoid sd2((non inpaint) crash on v-pred check (#537) --- stable-diffusion.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index e2daf5764..b5424ad2b 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -606,7 +606,9 @@ class StableDiffusionGGML { ggml_set_f32(timesteps, 999); struct ggml_tensor* concat = is_inpaint ? ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, 8, 8, 5, 1) : NULL; - ggml_set_f32(concat, 0); + if (concat != NULL) { + ggml_set_f32(concat, 0); + } int64_t t0 = ggml_time_ms(); struct ggml_tensor* out = ggml_dup_tensor(work_ctx, x_t); From d9b5942d988ee36c2f2d8a2d79820e90110947c3 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 18 Jan 2025 06:15:54 +0100 Subject: [PATCH 030/143] feat: add sdxl v-pred suppport (#536) --- stable-diffusion.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index b5424ad2b..cea12e6f2 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -525,6 +525,10 @@ class StableDiffusionGGML { if (is_using_v_parameterization_for_sd2(ctx, sd_version_is_inpaint(version))) { is_using_v_parameterization = true; } + } else if (sd_version_is_sdxl(version)) { + if (model_loader.tensor_storages_types.find("v_pred") != model_loader.tensor_storages_types.end()) { + is_using_v_parameterization = true; + } } else if (version == VERSION_SVD) { // TODO: V_PREDICTION_EDM is_using_v_parameterization = true; From 5eb15ef4d022bef4a391de4f5f6556e81fbb5024 Mon Sep 17 00:00:00 2001 From: piallai <59734081+piallai@users.noreply.github.com> Date: Sat, 18 Jan 2025 06:16:54 +0100 Subject: [PATCH 031/143] docs: add CLI-GUI to list (#546) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 01b1fa4ae..ee92ed859 100644 --- a/README.md +++ b/README.md @@ -326,6 +326,7 @@ These projects use `stable-diffusion.cpp` as a backend for their image generatio - [Jellybox](https://jellybox.com) - [Stable Diffusion GUI](https://github.com/fszontagh/sd.cpp.gui.wx) +- [Stable Diffusion CLI-GUI] (https://github.com/piallai/stable-diffusion.cpp) ## Contributors From a3cbdf6dcba96b9f662e33df01baaf67631c8bc3 Mon Sep 17 00:00:00 2001 From: R0CKSTAR Date: Wed, 5 Feb 2025 16:11:26 +0800 Subject: [PATCH 032/143] chore: SD_USE_CUBLAS => SD_USE_CUDA for MUSA backend (#578) Signed-off-by: Xiaodong Ye --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f82cb5f0a..7b7cc6c47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ endif () if(SD_MUSA) message("-- Use MUSA as backend stable-diffusion") set(GGML_MUSA ON) - add_definitions(-DSD_USE_CUBLAS) + add_definitions(-DSD_USE_CUDA) if(SD_FAST_SOFTMAX) set(GGML_CUDA_FAST_SOFTMAX ON) endif() From e500d95abd225667796707da7a6adf6ae75979e4 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Wed, 5 Feb 2025 09:13:17 +0100 Subject: [PATCH 033/143] fix: fix rank 1 loras (#575) --- lora.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lora.hpp b/lora.hpp index 83181837c..ea1d03e02 100644 --- a/lora.hpp +++ b/lora.hpp @@ -615,9 +615,12 @@ struct LoraModel : public GGMLRunner { scale_value *= multiplier; // flat lora tensors to multiply it - int64_t lora_up_rows = lora_up->ne[ggml_n_dims(lora_up) - 1]; - lora_up = ggml_reshape_2d(compute_ctx, lora_up, ggml_nelements(lora_up) / lora_up_rows, lora_up_rows); - int64_t lora_down_rows = lora_down->ne[ggml_n_dims(lora_down) - 1]; + int64_t lora_up_rows = lora_up->ne[ggml_n_dims(lora_up) - 1]; + lora_up = ggml_reshape_2d(compute_ctx, lora_up, ggml_nelements(lora_up) / lora_up_rows, lora_up_rows); + auto lora_down_n_dims = ggml_n_dims(lora_down); + // assume n_dims should always be a multiple of 2 (otherwise rank 1 doesn't work) + lora_down_n_dims = (lora_down_n_dims + lora_down_n_dims % 2); + int64_t lora_down_rows = lora_down->ne[lora_down_n_dims - 1]; lora_down = ggml_reshape_2d(compute_ctx, lora_down, ggml_nelements(lora_down) / lora_down_rows, lora_down_rows); // ggml_mul_mat requires tensor b transposed From 2535ad5a437478da00bc9b6d08dc6b36d1fc2e44 Mon Sep 17 00:00:00 2001 From: ag2s20150909 <19373730+ag2s20150909@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:15:41 +0800 Subject: [PATCH 034/143] chore: fix cuda on github action (#580) --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb69fff75..a16e692ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -163,7 +163,7 @@ jobs: - build: "avx512" defines: "-DGGML_NATIVE=OFF -DGGML_AVX512=ON -DGGML_AVX=ON -DGGML_AVX2=ON -DSD_BUILD_SHARED_LIBS=ON" - build: "cuda12" - defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES=60;61;70;75" + defines: "-DSD_CUDA=ON -DSD_BUILD_SHARED_LIBS=ON -DCMAKE_CUDA_ARCHITECTURES=90;89;80;75" # - build: "rocm5.5" # defines: '-G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_HIPBLAS=ON -DCMAKE_BUILD_TYPE=Release -DAMDGPU_TARGETS="gfx1100;gfx1102;gfx1030" -DSD_BUILD_SHARED_LIBS=ON' - build: 'vulkan' @@ -178,9 +178,9 @@ jobs: - name: Install cuda-toolkit id: cuda-toolkit if: ${{ matrix.build == 'cuda12' }} - uses: Jimver/cuda-toolkit@v0.2.11 + uses: Jimver/cuda-toolkit@v0.2.19 with: - cuda: "12.2.0" + cuda: "12.6.2" method: "network" sub-packages: '["nvcc", "cudart", "cublas", "cublas_dev", "thrust", "visual_studio_integration"]' From d46ed5e184b97c2018dc2e8105925bdb8775e02c Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Wed, 5 Feb 2025 11:18:02 +0300 Subject: [PATCH 035/143] feat: support JPEG compression (#583) --- examples/cli/main.cpp | 37 ++++++++++++++++++++++++++++++------ thirdparty/stb_image_write.h | 22 +++++++++++++++++---- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 3c35b1032..cfadf4342 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -1057,16 +1057,41 @@ int main(int argc, const char* argv[]) { } } - size_t last = params.output_path.find_last_of("."); - std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path; + std::string dummy_name, ext, lc_ext; + bool is_jpg; + size_t last = params.output_path.find_last_of("."); + size_t last_path = std::min(params.output_path.find_last_of("/"), + params.output_path.find_last_of("\\")); + if (last != std::string::npos // filename has extension + && (last_path == std::string::npos || last > last_path)) { + dummy_name = params.output_path.substr(0, last); + ext = lc_ext = params.output_path.substr(last); + std::transform(ext.begin(), ext.end(), lc_ext.begin(), ::tolower); + is_jpg = lc_ext == ".jpg" || lc_ext == ".jpeg" || lc_ext == ".jpe"; + } else { + dummy_name = params.output_path; + ext = lc_ext = ""; + is_jpg = false; + } + // appending ".png" to absent or unknown extension + if (!is_jpg && lc_ext != ".png") { + dummy_name += ext; + ext = ".png"; + } for (int i = 0; i < params.batch_count; i++) { if (results[i].data == NULL) { continue; } - std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1) + ".png" : dummy_name + ".png"; - stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, - results[i].data, 0, get_image_params(params, params.seed + i).c_str()); - printf("save result image to '%s'\n", final_image_path.c_str()); + std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1) + ext : dummy_name + ext; + if(is_jpg) { + stbi_write_jpg(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, + results[i].data, 90, get_image_params(params, params.seed + i).c_str()); + printf("save result JPEG image to '%s'\n", final_image_path.c_str()); + } else { + stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, + results[i].data, 0, get_image_params(params, params.seed + i).c_str()); + printf("save result PNG image to '%s'\n", final_image_path.c_str()); + } free(results[i].data); results[i].data = NULL; } diff --git a/thirdparty/stb_image_write.h b/thirdparty/stb_image_write.h index 5589a7ec2..a9139bec0 100644 --- a/thirdparty/stb_image_write.h +++ b/thirdparty/stb_image_write.h @@ -1412,7 +1412,7 @@ static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt return DU[0]; } -static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality, const char* parameters) { // Constants that don't pollute global namespace static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; @@ -1521,6 +1521,20 @@ static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, in s->func(s->context, (void*)YTable, sizeof(YTable)); stbiw__putc(s, 1); s->func(s->context, UVTable, sizeof(UVTable)); + + // comment block with parameters of generation + if(parameters != NULL) { + stbiw__putc(s, 0xFF /* comnent */ ); + stbiw__putc(s, 0xFE /* marker */ ); + size_t param_length = std::min(2 + strlen("parameters") + 1 + strlen(parameters) + 1, (size_t) 0xFFFF); + stbiw__putc(s, param_length >> 8); // no need to mask, length < 65536 + stbiw__putc(s, param_length & 0xFF); + s->func(s->context, (void*)"parameters", strlen("parameters") + 1); // std::string is zero-terminated + s->func(s->context, (void*)parameters, std::min(param_length, (size_t) 65534) - 2 - strlen("parameters") - 1); + if(param_length > 65534) stbiw__putc(s, 0); // always zero-terminate for safety + if(param_length & 1) stbiw__putc(s, 0xFF); // pad to even length + } + s->func(s->context, (void*)head1, sizeof(head1)); s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); @@ -1625,16 +1639,16 @@ STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, { stbi__write_context s = { 0 }; stbi__start_write_callbacks(&s, func, context); - return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality, NULL); } #ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality, const char* parameters) { stbi__write_context s = { 0 }; if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality, parameters); stbi__end_write_file(&s); return r; } else From 59ca2b0f168d74ce1946e9c7e8b2f9f980fd7a3f Mon Sep 17 00:00:00 2001 From: R0CKSTAR Date: Sat, 22 Feb 2025 21:14:26 +0800 Subject: [PATCH 036/143] chore: bump MUSA SDK version to rc3.1.1 (#599) Signed-off-by: Xiaodong Ye --- Dockerfile.musa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.musa b/Dockerfile.musa index 0126e6854..0adcb7ee5 100644 --- a/Dockerfile.musa +++ b/Dockerfile.musa @@ -1,4 +1,4 @@ -ARG MUSA_VERSION=rc3.1.0 +ARG MUSA_VERSION=rc3.1.1 FROM mthreads/musa:${MUSA_VERSION}-devel-ubuntu22.04 as build From 37532239825f806c6f7f6c98d63d0655b7fbdd39 Mon Sep 17 00:00:00 2001 From: Matti Pulkkinen <123762392+mpulukkinen@users.noreply.github.com> Date: Sat, 22 Feb 2025 15:16:50 +0200 Subject: [PATCH 037/143] fix: make get_files_from_dir works with absolute path (#598) Co-authored-by: Matti Pulkkinen --- util.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/util.cpp b/util.cpp index 01c01200e..da11a14d6 100644 --- a/util.cpp +++ b/util.cpp @@ -113,18 +113,31 @@ std::vector get_files_from_dir(const std::string& dir) { // Find the first file in the directory hFind = FindFirstFile(directoryPath, &findFileData); - + bool isAbsolutePath = false; // Check if the directory was found if (hFind == INVALID_HANDLE_VALUE) { - printf("Unable to find directory.\n"); - return files; + printf("Unable to find directory. Try with original path \n"); + + char directoryPathAbsolute[MAX_PATH]; + sprintf(directoryPathAbsolute, "%s*", dir.c_str()); + + hFind = FindFirstFile(directoryPathAbsolute, &findFileData); + isAbsolutePath = true; + if (hFind == INVALID_HANDLE_VALUE) { + printf("Absolute path was also wrong.\n"); + return files; + } } // Loop through all files in the directory do { // Check if the found file is a regular file (not a directory) if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { - files.push_back(std::string(currentDirectory) + "\\" + dir + "\\" + std::string(findFileData.cFileName)); + if (isAbsolutePath) { + files.push_back(dir + "\\" + std::string(findFileData.cFileName)); + } else { + files.push_back(std::string(currentDirectory) + "\\" + dir + "\\" + std::string(findFileData.cFileName)); + } } } while (FindNextFile(hFind, &findFileData) != 0); From 1be2491dcf5e696e620840dc9149fb071047b500 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 22 Feb 2025 14:19:26 +0100 Subject: [PATCH 038/143] feat: partial LyCORIS support (tucker decomposition for LoCon + LoHa + LoKr) (#577) --- ggml_extend.hpp | 71 +++- lora.hpp | 847 +++++++++++++++++++++++++++++------------------- 2 files changed, 573 insertions(+), 345 deletions(-) diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 035f088f4..6a187ca3e 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -52,6 +52,71 @@ #define __STATIC_INLINE__ static inline #endif +// n-mode trensor-matrix product +// example: 2-mode product +// A: [ne03, k, ne01, ne00] +// B: k rows, m columns => [k, m] +// result is [ne03, m, ne01, ne00] +__STATIC_INLINE__ struct ggml_tensor* ggml_mul_n_mode(struct ggml_context* ctx, struct ggml_tensor* a, struct ggml_tensor* b, int mode = 0) { + // reshape A + // swap 0th and nth axis + a = ggml_cont(ctx, ggml_permute(ctx, a, mode, mode != 1 ? 1 : 0, mode != 2 ? 2 : 0, mode != 3 ? 3 : 0)); + int ne1 = a->ne[1]; + int ne2 = a->ne[2]; + int ne3 = a->ne[3]; + // make 2D + a = ggml_cont(ctx, ggml_reshape_2d(ctx, a, a->ne[0], (ne3 * ne2 * ne1))); + + struct ggml_tensor* result = ggml_cont(ctx, ggml_transpose(ctx, ggml_mul_mat(ctx, a, b))); + + // reshape output (same shape as a after permutation except first dim) + result = ggml_reshape_4d(ctx, result, result->ne[0], ne1, ne2, ne3); + // swap back 0th and nth axis + result = ggml_permute(ctx, result, mode, mode != 1 ? 1 : 0, mode != 2 ? 2 : 0, mode != 3 ? 3 : 0); + return result; +} + +__STATIC_INLINE__ struct ggml_tensor* ggml_merge_lora(ggml_context* ctx, struct ggml_tensor* lora_down, struct ggml_tensor* lora_up, struct ggml_tensor* lora_mid = NULL) { + struct ggml_tensor* updown; + // flat lora tensors to multiply it + int64_t lora_up_rows = lora_up->ne[ggml_n_dims(lora_up) - 1]; + lora_up = ggml_reshape_2d(ctx, lora_up, ggml_nelements(lora_up) / lora_up_rows, lora_up_rows); + auto lora_down_n_dims = ggml_n_dims(lora_down); + // assume n_dims should always be a multiple of 2 (otherwise rank 1 doesn't work) + lora_down_n_dims = (lora_down_n_dims + lora_down_n_dims % 2); + int64_t lora_down_rows = lora_down->ne[lora_down_n_dims - 1]; + lora_down = ggml_reshape_2d(ctx, lora_down, ggml_nelements(lora_down) / lora_down_rows, lora_down_rows); + + // ggml_mul_mat requires tensor b transposed + lora_down = ggml_cont(ctx, ggml_transpose(ctx, lora_down)); + if (lora_mid == NULL) { + updown = ggml_mul_mat(ctx, lora_up, lora_down); + updown = ggml_cont(ctx, ggml_transpose(ctx, updown)); + } else { + // undoing tucker decomposition for conv layers. + // lora_mid has shape (3, 3, Rank, Rank) + // lora_down has shape (Rank, In, 1, 1) + // lora_up has shape (Rank, Out, 1, 1) + // conv layer shape is (3, 3, Out, In) + updown = ggml_mul_n_mode(ctx, ggml_mul_n_mode(ctx, lora_mid, lora_down, 3), lora_up, 2); + updown = ggml_cont(ctx, updown); + } + return updown; +} + +// Kronecker product +// [ne03,ne02,ne01,ne00] x [ne13,ne12,ne11,ne10] => [ne03*ne13,ne02*ne12,ne01*ne11,ne00*ne10] +__STATIC_INLINE__ struct ggml_tensor* ggml_kronecker(ggml_context* ctx, struct ggml_tensor* a, struct ggml_tensor* b) { + return ggml_mul(ctx, + ggml_upscale_ext(ctx, + a, + a->ne[0] * b->ne[0], + a->ne[1] * b->ne[1], + a->ne[2] * b->ne[2], + a->ne[3] * b->ne[3]), + b); +} + __STATIC_INLINE__ void ggml_log_callback_default(ggml_log_level level, const char* text, void* user_data) { (void)level; (void)user_data; @@ -319,7 +384,7 @@ __STATIC_INLINE__ void sd_apply_mask(struct ggml_tensor* image_data, for (int iy = 0; iy < height; iy++) { float m = ggml_tensor_get_f32(mask, ix, iy); for (int k = 0; k < channels; k++) { - float value = ((float)(m < 254.5/255)) * (ggml_tensor_get_f32(image_data, ix, iy, k) - .5) + .5; + float value = ((float)(m < 254.5 / 255)) * (ggml_tensor_get_f32(image_data, ix, iy, k) - .5) + .5; ggml_tensor_set_f32(output, value, ix, iy, k); } } @@ -987,8 +1052,8 @@ __STATIC_INLINE__ size_t ggml_tensor_num(ggml_context* ctx) { } /* SDXL with LoRA requires more space */ -#define MAX_PARAMS_TENSOR_NUM 15360 -#define MAX_GRAPH_SIZE 15360 +#define MAX_PARAMS_TENSOR_NUM 32768 +#define MAX_GRAPH_SIZE 32768 struct GGMLRunner { protected: diff --git a/lora.hpp b/lora.hpp index ea1d03e02..d38c7116f 100644 --- a/lora.hpp +++ b/lora.hpp @@ -197,6 +197,10 @@ struct LoraModel : public GGMLRunner { blk_name.replace(blk_name.find(".joint_blocks"), sizeof(".joint_blocks") - 1, ".transformer_blocks"); } + if (blk_name.find("text_encoders.clip_l") != std::string::npos) { + blk_name.replace(blk_name.find("text_encoders.clip_l"), sizeof("text_encoders.clip_l") - 1, "cond_stage_model"); + } + for (const auto& item : alt_names) { size_t match = blk_name.find(item.first); if (match != std::string::npos) { @@ -217,13 +221,17 @@ struct LoraModel : public GGMLRunner { keys.push_back(split_blk); } } + keys.push_back(blk_name); } - keys.push_back(blk_name); std::vector ret; for (std::string& key : keys) { ret.push_back(key); replace_all_chars(key, '.', '_'); + // fix for some sdxl lora, like lcm-lora-xl + if (key == "model_diffusion_model_output_blocks_2_2_conv") { + ret.push_back("model_diffusion_model_output_blocks_2_1_conv"); + } ret.push_back(key); } return ret; @@ -244,390 +252,545 @@ struct LoraModel : public GGMLRunner { std::vector keys = to_lora_keys(k_tensor, version); if (keys.size() == 0) continue; - ggml_tensor* lora_up = NULL; - ggml_tensor* lora_down = NULL; - for (auto& key : keys) { - std::string alpha_name = ""; - std::string scale_name = ""; - std::string split_q_scale_name = ""; - std::string lora_down_name = ""; - std::string lora_up_name = ""; - if (starts_with(key, "SPLIT|")) { + for (auto& key : keys) { + bool is_qkv_split = starts_with(key, "SPLIT|"); + if (is_qkv_split) { key = key.substr(sizeof("SPLIT|") - 1); - // TODO: Handle alphas - std::string suffix = ""; - auto split_q_d_name = lora_pre[type] + key + "q" + suffix + lora_downs[type] + ".weight"; - - if (lora_tensors.find(split_q_d_name) == lora_tensors.end()) { - suffix = "_proj"; - split_q_d_name = lora_pre[type] + key + "q" + suffix + lora_downs[type] + ".weight"; + } + bool is_qkvm_split = starts_with(key, "SPLIT_L|"); + if (is_qkvm_split) { + key = key.substr(sizeof("SPLIT_L|") - 1); + } + struct ggml_tensor* updown = NULL; + float scale_value = 1.0f; + std::string fk = lora_pre[type] + key; + if (lora_tensors.find(fk + ".hada_w1_a") != lora_tensors.end()) { + // LoHa mode + + // TODO: split qkv convention for LoHas (is it ever used?) + if (is_qkv_split || is_qkvm_split) { + LOG_ERROR("Split qkv isn't supported for LoHa models."); + break; } - if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { - // print_ggml_tensor(it.second, true); //[3072, 21504, 1, 1] - // find qkv and mlp up parts in LoRA model - auto split_k_d_name = lora_pre[type] + key + "k" + suffix + lora_downs[type] + ".weight"; - auto split_v_d_name = lora_pre[type] + key + "v" + suffix + lora_downs[type] + ".weight"; - - auto split_q_u_name = lora_pre[type] + key + "q" + suffix + lora_ups[type] + ".weight"; - auto split_k_u_name = lora_pre[type] + key + "k" + suffix + lora_ups[type] + ".weight"; - auto split_v_u_name = lora_pre[type] + key + "v" + suffix + lora_ups[type] + ".weight"; - - auto split_q_scale_name = lora_pre[type] + key + "q" + suffix + ".scale"; - auto split_k_scale_name = lora_pre[type] + key + "k" + suffix + ".scale"; - auto split_v_scale_name = lora_pre[type] + key + "v" + suffix + ".scale"; - - auto split_q_alpha_name = lora_pre[type] + key + "q" + suffix + ".alpha"; - auto split_k_alpha_name = lora_pre[type] + key + "k" + suffix + ".alpha"; - auto split_v_alpha_name = lora_pre[type] + key + "v" + suffix + ".alpha"; - - ggml_tensor* lora_q_down = NULL; - ggml_tensor* lora_q_up = NULL; - ggml_tensor* lora_k_down = NULL; - ggml_tensor* lora_k_up = NULL; - ggml_tensor* lora_v_down = NULL; - ggml_tensor* lora_v_up = NULL; - - lora_q_down = to_f32(compute_ctx, lora_tensors[split_q_d_name]); - - if (lora_tensors.find(split_q_u_name) != lora_tensors.end()) { - lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); - } + std::string alpha_name = ""; - if (lora_tensors.find(split_k_d_name) != lora_tensors.end()) { - lora_k_down = to_f32(compute_ctx, lora_tensors[split_k_d_name]); - } + ggml_tensor* hada_1_mid = NULL; // tau for tucker decomposition + ggml_tensor* hada_1_up = NULL; + ggml_tensor* hada_1_down = NULL; - if (lora_tensors.find(split_k_u_name) != lora_tensors.end()) { - lora_k_up = to_f32(compute_ctx, lora_tensors[split_k_u_name]); - } + ggml_tensor* hada_2_mid = NULL; // tau for tucker decomposition + ggml_tensor* hada_2_up = NULL; + ggml_tensor* hada_2_down = NULL; - if (lora_tensors.find(split_v_d_name) != lora_tensors.end()) { - lora_v_down = to_f32(compute_ctx, lora_tensors[split_v_d_name]); - } - - if (lora_tensors.find(split_v_u_name) != lora_tensors.end()) { - lora_v_up = to_f32(compute_ctx, lora_tensors[split_v_u_name]); - } + std::string hada_1_mid_name = ""; + std::string hada_1_down_name = ""; + std::string hada_1_up_name = ""; - float q_rank = lora_q_up->ne[0]; - float k_rank = lora_k_up->ne[0]; - float v_rank = lora_v_up->ne[0]; + std::string hada_2_mid_name = ""; + std::string hada_2_down_name = ""; + std::string hada_2_up_name = ""; - float lora_q_scale = 1; - float lora_k_scale = 1; - float lora_v_scale = 1; - if (lora_tensors.find(split_q_scale_name) != lora_tensors.end()) { - lora_q_scale = ggml_backend_tensor_get_f32(lora_tensors[split_q_scale_name]); - applied_lora_tensors.insert(split_q_scale_name); - } - if (lora_tensors.find(split_k_scale_name) != lora_tensors.end()) { - lora_k_scale = ggml_backend_tensor_get_f32(lora_tensors[split_k_scale_name]); - applied_lora_tensors.insert(split_k_scale_name); - } - if (lora_tensors.find(split_v_scale_name) != lora_tensors.end()) { - lora_v_scale = ggml_backend_tensor_get_f32(lora_tensors[split_v_scale_name]); - applied_lora_tensors.insert(split_v_scale_name); - } - - if (lora_tensors.find(split_q_alpha_name) != lora_tensors.end()) { - float lora_q_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_q_alpha_name]); - applied_lora_tensors.insert(split_q_alpha_name); - lora_q_scale = lora_q_alpha / q_rank; - } - if (lora_tensors.find(split_k_alpha_name) != lora_tensors.end()) { - float lora_k_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_k_alpha_name]); - applied_lora_tensors.insert(split_k_alpha_name); - lora_k_scale = lora_k_alpha / k_rank; - } - if (lora_tensors.find(split_v_alpha_name) != lora_tensors.end()) { - float lora_v_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_v_alpha_name]); - applied_lora_tensors.insert(split_v_alpha_name); - lora_v_scale = lora_v_alpha / v_rank; - } - - ggml_scale_inplace(compute_ctx, lora_q_down, lora_q_scale); - ggml_scale_inplace(compute_ctx, lora_k_down, lora_k_scale); - ggml_scale_inplace(compute_ctx, lora_v_down, lora_v_scale); - - // print_ggml_tensor(lora_q_down, true); //[3072, R, 1, 1] - // print_ggml_tensor(lora_k_down, true); //[3072, R, 1, 1] - // print_ggml_tensor(lora_v_down, true); //[3072, R, 1, 1] - // print_ggml_tensor(lora_q_up, true); //[R, 3072, 1, 1] - // print_ggml_tensor(lora_k_up, true); //[R, 3072, 1, 1] - // print_ggml_tensor(lora_v_up, true); //[R, 3072, 1, 1] - - // these need to be stitched together this way: - // |q_up,0 ,0 | - // |0 ,k_up,0 | - // |0 ,0 ,v_up| - // (q_down,k_down,v_down) . (q ,k ,v) - - // up_concat will be [9216, R*3, 1, 1] - // down_concat will be [R*3, 3072, 1, 1] - ggml_tensor* lora_down_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_down, lora_k_down, 1), lora_v_down, 1); - - ggml_tensor* z = ggml_dup_tensor(compute_ctx, lora_q_up); - ggml_scale(compute_ctx, z, 0); - ggml_tensor* zz = ggml_concat(compute_ctx, z, z, 1); - - ggml_tensor* q_up = ggml_concat(compute_ctx, lora_q_up, zz, 1); - ggml_tensor* k_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, z, lora_k_up, 1), z, 1); - ggml_tensor* v_up = ggml_concat(compute_ctx, zz, lora_v_up, 1); - // print_ggml_tensor(q_up, true); //[R, 9216, 1, 1] - // print_ggml_tensor(k_up, true); //[R, 9216, 1, 1] - // print_ggml_tensor(v_up, true); //[R, 9216, 1, 1] - ggml_tensor* lora_up_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, q_up, k_up, 0), v_up, 0); - // print_ggml_tensor(lora_up_concat, true); //[R*3, 9216, 1, 1] - - lora_down = ggml_cont(compute_ctx, lora_down_concat); - lora_up = ggml_cont(compute_ctx, lora_up_concat); - - applied_lora_tensors.insert(split_q_u_name); - applied_lora_tensors.insert(split_k_u_name); - applied_lora_tensors.insert(split_v_u_name); - - applied_lora_tensors.insert(split_q_d_name); - applied_lora_tensors.insert(split_k_d_name); - applied_lora_tensors.insert(split_v_d_name); + hada_1_down_name = fk + ".hada_w1_b"; + hada_1_up_name = fk + ".hada_w1_a"; + hada_1_mid_name = fk + ".hada_t1"; + if (lora_tensors.find(hada_1_down_name) != lora_tensors.end()) { + hada_1_down = to_f32(compute_ctx, lora_tensors[hada_1_down_name]); + } + if (lora_tensors.find(hada_1_up_name) != lora_tensors.end()) { + hada_1_up = to_f32(compute_ctx, lora_tensors[hada_1_up_name]); + } + if (lora_tensors.find(hada_1_mid_name) != lora_tensors.end()) { + hada_1_mid = to_f32(compute_ctx, lora_tensors[hada_1_mid_name]); + applied_lora_tensors.insert(hada_1_mid_name); + hada_1_up = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, hada_1_up)); } - } - if (starts_with(key, "SPLIT_L|")) { - key = key.substr(sizeof("SPLIT_L|") - 1); - - auto split_q_d_name = lora_pre[type] + key + "attn.to_q" + lora_downs[type] + ".weight"; - if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { - // print_ggml_tensor(it.second, true); //[3072, 21504, 1, 1] - // find qkv and mlp up parts in LoRA model - auto split_k_d_name = lora_pre[type] + key + "attn.to_k" + lora_downs[type] + ".weight"; - auto split_v_d_name = lora_pre[type] + key + "attn.to_v" + lora_downs[type] + ".weight"; - auto split_q_u_name = lora_pre[type] + key + "attn.to_q" + lora_ups[type] + ".weight"; - auto split_k_u_name = lora_pre[type] + key + "attn.to_k" + lora_ups[type] + ".weight"; - auto split_v_u_name = lora_pre[type] + key + "attn.to_v" + lora_ups[type] + ".weight"; + hada_2_down_name = fk + ".hada_w2_b"; + hada_2_up_name = fk + ".hada_w2_a"; + hada_2_mid_name = fk + ".hada_t2"; + if (lora_tensors.find(hada_2_down_name) != lora_tensors.end()) { + hada_2_down = to_f32(compute_ctx, lora_tensors[hada_2_down_name]); + } + if (lora_tensors.find(hada_2_up_name) != lora_tensors.end()) { + hada_2_up = to_f32(compute_ctx, lora_tensors[hada_2_up_name]); + } + if (lora_tensors.find(hada_2_mid_name) != lora_tensors.end()) { + hada_2_mid = to_f32(compute_ctx, lora_tensors[hada_2_mid_name]); + applied_lora_tensors.insert(hada_2_mid_name); + hada_2_up = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, hada_2_up)); + } - auto split_m_d_name = lora_pre[type] + key + "proj_mlp" + lora_downs[type] + ".weight"; - auto split_m_u_name = lora_pre[type] + key + "proj_mlp" + lora_ups[type] + ".weight"; + alpha_name = fk + ".alpha"; - auto split_q_scale_name = lora_pre[type] + key + "attn.to_q" + ".scale"; - auto split_k_scale_name = lora_pre[type] + key + "attn.to_k" + ".scale"; - auto split_v_scale_name = lora_pre[type] + key + "attn.to_v" + ".scale"; - auto split_m_scale_name = lora_pre[type] + key + "proj_mlp" + ".scale"; + applied_lora_tensors.insert(hada_1_down_name); + applied_lora_tensors.insert(hada_1_up_name); + applied_lora_tensors.insert(hada_2_down_name); + applied_lora_tensors.insert(hada_2_up_name); - auto split_q_alpha_name = lora_pre[type] + key + "attn.to_q" + ".alpha"; - auto split_k_alpha_name = lora_pre[type] + key + "attn.to_k" + ".alpha"; - auto split_v_alpha_name = lora_pre[type] + key + "attn.to_v" + ".alpha"; - auto split_m_alpha_name = lora_pre[type] + key + "proj_mlp" + ".alpha"; + applied_lora_tensors.insert(alpha_name); + if (hada_1_up == NULL || hada_1_down == NULL || hada_2_up == NULL || hada_2_down == NULL) { + continue; + } - ggml_tensor* lora_q_down = NULL; - ggml_tensor* lora_q_up = NULL; - ggml_tensor* lora_k_down = NULL; - ggml_tensor* lora_k_up = NULL; - ggml_tensor* lora_v_down = NULL; - ggml_tensor* lora_v_up = NULL; + struct ggml_tensor* updown_1 = ggml_merge_lora(compute_ctx, hada_1_down, hada_1_up, hada_1_mid); + struct ggml_tensor* updown_2 = ggml_merge_lora(compute_ctx, hada_2_down, hada_2_up, hada_2_mid); + updown = ggml_mul_inplace(compute_ctx, updown_1, updown_2); - ggml_tensor* lora_m_down = NULL; - ggml_tensor* lora_m_up = NULL; + // calc_scale + // TODO: .dora_scale? + int64_t rank = hada_1_down->ne[ggml_n_dims(hada_1_down) - 1]; + if (lora_tensors.find(alpha_name) != lora_tensors.end()) { + float alpha = ggml_backend_tensor_get_f32(lora_tensors[alpha_name]); + scale_value = alpha / rank; + } + } else if (lora_tensors.find(fk + ".lokr_w1") != lora_tensors.end() || lora_tensors.find(fk + ".lokr_w1_a") != lora_tensors.end()) { + // LoKr mode - lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); + // TODO: split qkv convention for LoKrs (is it ever used?) + if (is_qkv_split || is_qkvm_split) { + LOG_ERROR("Split qkv isn't supported for LoKr models."); + break; + } - if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { - lora_q_down = to_f32(compute_ctx, lora_tensors[split_q_d_name]); + std::string alpha_name = fk + ".alpha"; + + ggml_tensor* lokr_w1 = NULL; + ggml_tensor* lokr_w2 = NULL; + + std::string lokr_w1_name = ""; + std::string lokr_w2_name = ""; + + lokr_w1_name = fk + ".lokr_w1"; + lokr_w2_name = fk + ".lokr_w2"; + + if (lora_tensors.find(lokr_w1_name) != lora_tensors.end()) { + lokr_w1 = to_f32(compute_ctx, lora_tensors[lokr_w1_name]); + applied_lora_tensors.insert(lokr_w1_name); + } else { + ggml_tensor* down = NULL; + ggml_tensor* up = NULL; + std::string down_name = lokr_w1_name + "_b"; + std::string up_name = lokr_w1_name + "_a"; + if (lora_tensors.find(down_name) != lora_tensors.end()) { + // w1 should not be low rank normally, sometimes w1 and w2 are swapped + down = to_f32(compute_ctx, lora_tensors[down_name]); + applied_lora_tensors.insert(down_name); + + int64_t rank = down->ne[ggml_n_dims(down) - 1]; + if (lora_tensors.find(alpha_name) != lora_tensors.end()) { + float alpha = ggml_backend_tensor_get_f32(lora_tensors[alpha_name]); + scale_value = alpha / rank; + } } - - if (lora_tensors.find(split_q_u_name) != lora_tensors.end()) { - lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); + if (lora_tensors.find(up_name) != lora_tensors.end()) { + up = to_f32(compute_ctx, lora_tensors[up_name]); + applied_lora_tensors.insert(up_name); } - - if (lora_tensors.find(split_k_d_name) != lora_tensors.end()) { - lora_k_down = to_f32(compute_ctx, lora_tensors[split_k_d_name]); + lokr_w1 = ggml_merge_lora(compute_ctx, down, up); + } + if (lora_tensors.find(lokr_w2_name) != lora_tensors.end()) { + lokr_w2 = to_f32(compute_ctx, lora_tensors[lokr_w2_name]); + applied_lora_tensors.insert(lokr_w2_name); + } else { + ggml_tensor* down = NULL; + ggml_tensor* up = NULL; + std::string down_name = lokr_w2_name + "_b"; + std::string up_name = lokr_w2_name + "_a"; + if (lora_tensors.find(down_name) != lora_tensors.end()) { + down = to_f32(compute_ctx, lora_tensors[down_name]); + applied_lora_tensors.insert(down_name); + + int64_t rank = down->ne[ggml_n_dims(down) - 1]; + if (lora_tensors.find(alpha_name) != lora_tensors.end()) { + float alpha = ggml_backend_tensor_get_f32(lora_tensors[alpha_name]); + scale_value = alpha / rank; + } } - - if (lora_tensors.find(split_k_u_name) != lora_tensors.end()) { - lora_k_up = to_f32(compute_ctx, lora_tensors[split_k_u_name]); + if (lora_tensors.find(up_name) != lora_tensors.end()) { + up = to_f32(compute_ctx, lora_tensors[up_name]); + applied_lora_tensors.insert(up_name); } + lokr_w2 = ggml_merge_lora(compute_ctx, down, up); + } + + // Technically it might be unused, but I believe it's the expected behavior + applied_lora_tensors.insert(alpha_name); - if (lora_tensors.find(split_v_d_name) != lora_tensors.end()) { - lora_v_down = to_f32(compute_ctx, lora_tensors[split_v_d_name]); - } + updown = ggml_kronecker(compute_ctx, lokr_w1, lokr_w2); - if (lora_tensors.find(split_v_u_name) != lora_tensors.end()) { - lora_v_up = to_f32(compute_ctx, lora_tensors[split_v_u_name]); + } else { + // LoRA mode + ggml_tensor* lora_mid = NULL; // tau for tucker decomposition + ggml_tensor* lora_up = NULL; + ggml_tensor* lora_down = NULL; + + std::string alpha_name = ""; + std::string scale_name = ""; + std::string split_q_scale_name = ""; + std::string lora_mid_name = ""; + std::string lora_down_name = ""; + std::string lora_up_name = ""; + + if (is_qkv_split) { + std::string suffix = ""; + auto split_q_d_name = fk + "q" + suffix + lora_downs[type] + ".weight"; + + if (lora_tensors.find(split_q_d_name) == lora_tensors.end()) { + suffix = "_proj"; + split_q_d_name = fk + "q" + suffix + lora_downs[type] + ".weight"; } + if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { + // print_ggml_tensor(it.second, true); //[3072, 21504, 1, 1] + // find qkv and mlp up parts in LoRA model + auto split_k_d_name = fk + "k" + suffix + lora_downs[type] + ".weight"; + auto split_v_d_name = fk + "v" + suffix + lora_downs[type] + ".weight"; + + auto split_q_u_name = fk + "q" + suffix + lora_ups[type] + ".weight"; + auto split_k_u_name = fk + "k" + suffix + lora_ups[type] + ".weight"; + auto split_v_u_name = fk + "v" + suffix + lora_ups[type] + ".weight"; + + auto split_q_scale_name = fk + "q" + suffix + ".scale"; + auto split_k_scale_name = fk + "k" + suffix + ".scale"; + auto split_v_scale_name = fk + "v" + suffix + ".scale"; + + auto split_q_alpha_name = fk + "q" + suffix + ".alpha"; + auto split_k_alpha_name = fk + "k" + suffix + ".alpha"; + auto split_v_alpha_name = fk + "v" + suffix + ".alpha"; + + ggml_tensor* lora_q_down = NULL; + ggml_tensor* lora_q_up = NULL; + ggml_tensor* lora_k_down = NULL; + ggml_tensor* lora_k_up = NULL; + ggml_tensor* lora_v_down = NULL; + ggml_tensor* lora_v_up = NULL; - if (lora_tensors.find(split_m_d_name) != lora_tensors.end()) { - lora_m_down = to_f32(compute_ctx, lora_tensors[split_m_d_name]); - } + lora_q_down = to_f32(compute_ctx, lora_tensors[split_q_d_name]); - if (lora_tensors.find(split_m_u_name) != lora_tensors.end()) { - lora_m_up = to_f32(compute_ctx, lora_tensors[split_m_u_name]); + if (lora_tensors.find(split_q_u_name) != lora_tensors.end()) { + lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); + } + + if (lora_tensors.find(split_k_d_name) != lora_tensors.end()) { + lora_k_down = to_f32(compute_ctx, lora_tensors[split_k_d_name]); + } + + if (lora_tensors.find(split_k_u_name) != lora_tensors.end()) { + lora_k_up = to_f32(compute_ctx, lora_tensors[split_k_u_name]); + } + + if (lora_tensors.find(split_v_d_name) != lora_tensors.end()) { + lora_v_down = to_f32(compute_ctx, lora_tensors[split_v_d_name]); + } + + if (lora_tensors.find(split_v_u_name) != lora_tensors.end()) { + lora_v_up = to_f32(compute_ctx, lora_tensors[split_v_u_name]); + } + + float q_rank = lora_q_up->ne[0]; + float k_rank = lora_k_up->ne[0]; + float v_rank = lora_v_up->ne[0]; + + float lora_q_scale = 1; + float lora_k_scale = 1; + float lora_v_scale = 1; + + if (lora_tensors.find(split_q_scale_name) != lora_tensors.end()) { + lora_q_scale = ggml_backend_tensor_get_f32(lora_tensors[split_q_scale_name]); + applied_lora_tensors.insert(split_q_scale_name); + } + if (lora_tensors.find(split_k_scale_name) != lora_tensors.end()) { + lora_k_scale = ggml_backend_tensor_get_f32(lora_tensors[split_k_scale_name]); + applied_lora_tensors.insert(split_k_scale_name); + } + if (lora_tensors.find(split_v_scale_name) != lora_tensors.end()) { + lora_v_scale = ggml_backend_tensor_get_f32(lora_tensors[split_v_scale_name]); + applied_lora_tensors.insert(split_v_scale_name); + } + + if (lora_tensors.find(split_q_alpha_name) != lora_tensors.end()) { + float lora_q_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_q_alpha_name]); + applied_lora_tensors.insert(split_q_alpha_name); + lora_q_scale = lora_q_alpha / q_rank; + } + if (lora_tensors.find(split_k_alpha_name) != lora_tensors.end()) { + float lora_k_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_k_alpha_name]); + applied_lora_tensors.insert(split_k_alpha_name); + lora_k_scale = lora_k_alpha / k_rank; + } + if (lora_tensors.find(split_v_alpha_name) != lora_tensors.end()) { + float lora_v_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_v_alpha_name]); + applied_lora_tensors.insert(split_v_alpha_name); + lora_v_scale = lora_v_alpha / v_rank; + } + + ggml_scale_inplace(compute_ctx, lora_q_down, lora_q_scale); + ggml_scale_inplace(compute_ctx, lora_k_down, lora_k_scale); + ggml_scale_inplace(compute_ctx, lora_v_down, lora_v_scale); + + // print_ggml_tensor(lora_q_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_k_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_v_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_q_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_k_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_v_up, true); //[R, 3072, 1, 1] + + // these need to be stitched together this way: + // |q_up,0 ,0 | + // |0 ,k_up,0 | + // |0 ,0 ,v_up| + // (q_down,k_down,v_down) . (q ,k ,v) + + // up_concat will be [9216, R*3, 1, 1] + // down_concat will be [R*3, 3072, 1, 1] + ggml_tensor* lora_down_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_down, lora_k_down, 1), lora_v_down, 1); + + ggml_tensor* z = ggml_dup_tensor(compute_ctx, lora_q_up); + ggml_scale(compute_ctx, z, 0); + ggml_tensor* zz = ggml_concat(compute_ctx, z, z, 1); + + ggml_tensor* q_up = ggml_concat(compute_ctx, lora_q_up, zz, 1); + ggml_tensor* k_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, z, lora_k_up, 1), z, 1); + ggml_tensor* v_up = ggml_concat(compute_ctx, zz, lora_v_up, 1); + // print_ggml_tensor(q_up, true); //[R, 9216, 1, 1] + // print_ggml_tensor(k_up, true); //[R, 9216, 1, 1] + // print_ggml_tensor(v_up, true); //[R, 9216, 1, 1] + ggml_tensor* lora_up_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, q_up, k_up, 0), v_up, 0); + // print_ggml_tensor(lora_up_concat, true); //[R*3, 9216, 1, 1] + + lora_down = ggml_cont(compute_ctx, lora_down_concat); + lora_up = ggml_cont(compute_ctx, lora_up_concat); + + applied_lora_tensors.insert(split_q_u_name); + applied_lora_tensors.insert(split_k_u_name); + applied_lora_tensors.insert(split_v_u_name); + + applied_lora_tensors.insert(split_q_d_name); + applied_lora_tensors.insert(split_k_d_name); + applied_lora_tensors.insert(split_v_d_name); } + } else if (is_qkvm_split) { + auto split_q_d_name = fk + "attn.to_q" + lora_downs[type] + ".weight"; + if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { + // print_ggml_tensor(it.second, true); //[3072, 21504, 1, 1] + // find qkv and mlp up parts in LoRA model + auto split_k_d_name = fk + "attn.to_k" + lora_downs[type] + ".weight"; + auto split_v_d_name = fk + "attn.to_v" + lora_downs[type] + ".weight"; + + auto split_q_u_name = fk + "attn.to_q" + lora_ups[type] + ".weight"; + auto split_k_u_name = fk + "attn.to_k" + lora_ups[type] + ".weight"; + auto split_v_u_name = fk + "attn.to_v" + lora_ups[type] + ".weight"; + + auto split_m_d_name = fk + "proj_mlp" + lora_downs[type] + ".weight"; + auto split_m_u_name = fk + "proj_mlp" + lora_ups[type] + ".weight"; + + auto split_q_scale_name = fk + "attn.to_q" + ".scale"; + auto split_k_scale_name = fk + "attn.to_k" + ".scale"; + auto split_v_scale_name = fk + "attn.to_v" + ".scale"; + auto split_m_scale_name = fk + "proj_mlp" + ".scale"; + + auto split_q_alpha_name = fk + "attn.to_q" + ".alpha"; + auto split_k_alpha_name = fk + "attn.to_k" + ".alpha"; + auto split_v_alpha_name = fk + "attn.to_v" + ".alpha"; + auto split_m_alpha_name = fk + "proj_mlp" + ".alpha"; + + ggml_tensor* lora_q_down = NULL; + ggml_tensor* lora_q_up = NULL; + ggml_tensor* lora_k_down = NULL; + ggml_tensor* lora_k_up = NULL; + ggml_tensor* lora_v_down = NULL; + ggml_tensor* lora_v_up = NULL; + + ggml_tensor* lora_m_down = NULL; + ggml_tensor* lora_m_up = NULL; - float q_rank = lora_q_up->ne[0]; - float k_rank = lora_k_up->ne[0]; - float v_rank = lora_v_up->ne[0]; - float m_rank = lora_v_up->ne[0]; - - float lora_q_scale = 1; - float lora_k_scale = 1; - float lora_v_scale = 1; - float lora_m_scale = 1; + lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); - if (lora_tensors.find(split_q_scale_name) != lora_tensors.end()) { - lora_q_scale = ggml_backend_tensor_get_f32(lora_tensors[split_q_scale_name]); - applied_lora_tensors.insert(split_q_scale_name); - } - if (lora_tensors.find(split_k_scale_name) != lora_tensors.end()) { - lora_k_scale = ggml_backend_tensor_get_f32(lora_tensors[split_k_scale_name]); - applied_lora_tensors.insert(split_k_scale_name); - } - if (lora_tensors.find(split_v_scale_name) != lora_tensors.end()) { - lora_v_scale = ggml_backend_tensor_get_f32(lora_tensors[split_v_scale_name]); - applied_lora_tensors.insert(split_v_scale_name); - } - if (lora_tensors.find(split_m_scale_name) != lora_tensors.end()) { - lora_m_scale = ggml_backend_tensor_get_f32(lora_tensors[split_m_scale_name]); - applied_lora_tensors.insert(split_m_scale_name); + if (lora_tensors.find(split_q_d_name) != lora_tensors.end()) { + lora_q_down = to_f32(compute_ctx, lora_tensors[split_q_d_name]); + } + + if (lora_tensors.find(split_q_u_name) != lora_tensors.end()) { + lora_q_up = to_f32(compute_ctx, lora_tensors[split_q_u_name]); + } + + if (lora_tensors.find(split_k_d_name) != lora_tensors.end()) { + lora_k_down = to_f32(compute_ctx, lora_tensors[split_k_d_name]); + } + + if (lora_tensors.find(split_k_u_name) != lora_tensors.end()) { + lora_k_up = to_f32(compute_ctx, lora_tensors[split_k_u_name]); + } + + if (lora_tensors.find(split_v_d_name) != lora_tensors.end()) { + lora_v_down = to_f32(compute_ctx, lora_tensors[split_v_d_name]); + } + + if (lora_tensors.find(split_v_u_name) != lora_tensors.end()) { + lora_v_up = to_f32(compute_ctx, lora_tensors[split_v_u_name]); + } + + if (lora_tensors.find(split_m_d_name) != lora_tensors.end()) { + lora_m_down = to_f32(compute_ctx, lora_tensors[split_m_d_name]); + } + + if (lora_tensors.find(split_m_u_name) != lora_tensors.end()) { + lora_m_up = to_f32(compute_ctx, lora_tensors[split_m_u_name]); + } + + float q_rank = lora_q_up->ne[0]; + float k_rank = lora_k_up->ne[0]; + float v_rank = lora_v_up->ne[0]; + float m_rank = lora_v_up->ne[0]; + + float lora_q_scale = 1; + float lora_k_scale = 1; + float lora_v_scale = 1; + float lora_m_scale = 1; + + if (lora_tensors.find(split_q_scale_name) != lora_tensors.end()) { + lora_q_scale = ggml_backend_tensor_get_f32(lora_tensors[split_q_scale_name]); + applied_lora_tensors.insert(split_q_scale_name); + } + if (lora_tensors.find(split_k_scale_name) != lora_tensors.end()) { + lora_k_scale = ggml_backend_tensor_get_f32(lora_tensors[split_k_scale_name]); + applied_lora_tensors.insert(split_k_scale_name); + } + if (lora_tensors.find(split_v_scale_name) != lora_tensors.end()) { + lora_v_scale = ggml_backend_tensor_get_f32(lora_tensors[split_v_scale_name]); + applied_lora_tensors.insert(split_v_scale_name); + } + if (lora_tensors.find(split_m_scale_name) != lora_tensors.end()) { + lora_m_scale = ggml_backend_tensor_get_f32(lora_tensors[split_m_scale_name]); + applied_lora_tensors.insert(split_m_scale_name); + } + + if (lora_tensors.find(split_q_alpha_name) != lora_tensors.end()) { + float lora_q_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_q_alpha_name]); + applied_lora_tensors.insert(split_q_alpha_name); + lora_q_scale = lora_q_alpha / q_rank; + } + if (lora_tensors.find(split_k_alpha_name) != lora_tensors.end()) { + float lora_k_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_k_alpha_name]); + applied_lora_tensors.insert(split_k_alpha_name); + lora_k_scale = lora_k_alpha / k_rank; + } + if (lora_tensors.find(split_v_alpha_name) != lora_tensors.end()) { + float lora_v_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_v_alpha_name]); + applied_lora_tensors.insert(split_v_alpha_name); + lora_v_scale = lora_v_alpha / v_rank; + } + if (lora_tensors.find(split_m_alpha_name) != lora_tensors.end()) { + float lora_m_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_m_alpha_name]); + applied_lora_tensors.insert(split_m_alpha_name); + lora_m_scale = lora_m_alpha / m_rank; + } + + ggml_scale_inplace(compute_ctx, lora_q_down, lora_q_scale); + ggml_scale_inplace(compute_ctx, lora_k_down, lora_k_scale); + ggml_scale_inplace(compute_ctx, lora_v_down, lora_v_scale); + ggml_scale_inplace(compute_ctx, lora_m_down, lora_m_scale); + + // print_ggml_tensor(lora_q_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_k_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_v_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_m_down, true); //[3072, R, 1, 1] + // print_ggml_tensor(lora_q_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_k_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_v_up, true); //[R, 3072, 1, 1] + // print_ggml_tensor(lora_m_up, true); //[R, 12288, 1, 1] + + // these need to be stitched together this way: + // |q_up,0 ,0 ,0 | + // |0 ,k_up,0 ,0 | + // |0 ,0 ,v_up,0 | + // |0 ,0 ,0 ,m_up| + // (q_down,k_down,v_down,m_down) . (q ,k ,v ,m) + + // up_concat will be [21504, R*4, 1, 1] + // down_concat will be [R*4, 3072, 1, 1] + + ggml_tensor* lora_down_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_down, lora_k_down, 1), ggml_concat(compute_ctx, lora_v_down, lora_m_down, 1), 1); + // print_ggml_tensor(lora_down_concat, true); //[3072, R*4, 1, 1] + + // this also means that if rank is bigger than 672, it is less memory efficient to do it this way (should be fine) + // print_ggml_tensor(lora_q_up, true); //[3072, R, 1, 1] + ggml_tensor* z = ggml_dup_tensor(compute_ctx, lora_q_up); + ggml_tensor* mlp_z = ggml_dup_tensor(compute_ctx, lora_m_up); + ggml_scale(compute_ctx, z, 0); + ggml_scale(compute_ctx, mlp_z, 0); + ggml_tensor* zz = ggml_concat(compute_ctx, z, z, 1); + + ggml_tensor* q_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_up, zz, 1), mlp_z, 1); + ggml_tensor* k_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, z, lora_k_up, 1), ggml_concat(compute_ctx, z, mlp_z, 1), 1); + ggml_tensor* v_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, zz, lora_v_up, 1), mlp_z, 1); + ggml_tensor* m_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, zz, z, 1), lora_m_up, 1); + // print_ggml_tensor(q_up, true); //[R, 21504, 1, 1] + // print_ggml_tensor(k_up, true); //[R, 21504, 1, 1] + // print_ggml_tensor(v_up, true); //[R, 21504, 1, 1] + // print_ggml_tensor(m_up, true); //[R, 21504, 1, 1] + + ggml_tensor* lora_up_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, q_up, k_up, 0), ggml_concat(compute_ctx, v_up, m_up, 0), 0); + // print_ggml_tensor(lora_up_concat, true); //[R*4, 21504, 1, 1] + + lora_down = ggml_cont(compute_ctx, lora_down_concat); + lora_up = ggml_cont(compute_ctx, lora_up_concat); + + applied_lora_tensors.insert(split_q_u_name); + applied_lora_tensors.insert(split_k_u_name); + applied_lora_tensors.insert(split_v_u_name); + applied_lora_tensors.insert(split_m_u_name); + + applied_lora_tensors.insert(split_q_d_name); + applied_lora_tensors.insert(split_k_d_name); + applied_lora_tensors.insert(split_v_d_name); + applied_lora_tensors.insert(split_m_d_name); } + } else { + lora_up_name = fk + lora_ups[type] + ".weight"; + lora_down_name = fk + lora_downs[type] + ".weight"; + lora_mid_name = fk + ".lora_mid.weight"; - if (lora_tensors.find(split_q_alpha_name) != lora_tensors.end()) { - float lora_q_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_q_alpha_name]); - applied_lora_tensors.insert(split_q_alpha_name); - lora_q_scale = lora_q_alpha / q_rank; - } - if (lora_tensors.find(split_k_alpha_name) != lora_tensors.end()) { - float lora_k_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_k_alpha_name]); - applied_lora_tensors.insert(split_k_alpha_name); - lora_k_scale = lora_k_alpha / k_rank; - } - if (lora_tensors.find(split_v_alpha_name) != lora_tensors.end()) { - float lora_v_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_v_alpha_name]); - applied_lora_tensors.insert(split_v_alpha_name); - lora_v_scale = lora_v_alpha / v_rank; - } - if (lora_tensors.find(split_m_alpha_name) != lora_tensors.end()) { - float lora_m_alpha = ggml_backend_tensor_get_f32(lora_tensors[split_m_alpha_name]); - applied_lora_tensors.insert(split_m_alpha_name); - lora_m_scale = lora_m_alpha / m_rank; + alpha_name = fk + ".alpha"; + scale_name = fk + ".scale"; + + if (lora_tensors.find(lora_up_name) != lora_tensors.end()) { + lora_up = to_f32(compute_ctx, lora_tensors[lora_up_name]); } - ggml_scale_inplace(compute_ctx, lora_q_down, lora_q_scale); - ggml_scale_inplace(compute_ctx, lora_k_down, lora_k_scale); - ggml_scale_inplace(compute_ctx, lora_v_down, lora_v_scale); - ggml_scale_inplace(compute_ctx, lora_m_down, lora_m_scale); - - // print_ggml_tensor(lora_q_down, true); //[3072, R, 1, 1] - // print_ggml_tensor(lora_k_down, true); //[3072, R, 1, 1] - // print_ggml_tensor(lora_v_down, true); //[3072, R, 1, 1] - // print_ggml_tensor(lora_m_down, true); //[3072, R, 1, 1] - // print_ggml_tensor(lora_q_up, true); //[R, 3072, 1, 1] - // print_ggml_tensor(lora_k_up, true); //[R, 3072, 1, 1] - // print_ggml_tensor(lora_v_up, true); //[R, 3072, 1, 1] - // print_ggml_tensor(lora_m_up, true); //[R, 12288, 1, 1] - - // these need to be stitched together this way: - // |q_up,0 ,0 ,0 | - // |0 ,k_up,0 ,0 | - // |0 ,0 ,v_up,0 | - // |0 ,0 ,0 ,m_up| - // (q_down,k_down,v_down,m_down) . (q ,k ,v ,m) - - // up_concat will be [21504, R*4, 1, 1] - // down_concat will be [R*4, 3072, 1, 1] - - ggml_tensor* lora_down_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_down, lora_k_down, 1), ggml_concat(compute_ctx, lora_v_down, lora_m_down, 1), 1); - // print_ggml_tensor(lora_down_concat, true); //[3072, R*4, 1, 1] - - // this also means that if rank is bigger than 672, it is less memory efficient to do it this way (should be fine) - // print_ggml_tensor(lora_q_up, true); //[3072, R, 1, 1] - ggml_tensor* z = ggml_dup_tensor(compute_ctx, lora_q_up); - ggml_tensor* mlp_z = ggml_dup_tensor(compute_ctx, lora_m_up); - ggml_scale(compute_ctx, z, 0); - ggml_scale(compute_ctx, mlp_z, 0); - ggml_tensor* zz = ggml_concat(compute_ctx, z, z, 1); - - ggml_tensor* q_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, lora_q_up, zz, 1), mlp_z, 1); - ggml_tensor* k_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, z, lora_k_up, 1), ggml_concat(compute_ctx, z, mlp_z, 1), 1); - ggml_tensor* v_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, zz, lora_v_up, 1), mlp_z, 1); - ggml_tensor* m_up = ggml_concat(compute_ctx, ggml_concat(compute_ctx, zz, z, 1), lora_m_up, 1); - // print_ggml_tensor(q_up, true); //[R, 21504, 1, 1] - // print_ggml_tensor(k_up, true); //[R, 21504, 1, 1] - // print_ggml_tensor(v_up, true); //[R, 21504, 1, 1] - // print_ggml_tensor(m_up, true); //[R, 21504, 1, 1] - - ggml_tensor* lora_up_concat = ggml_concat(compute_ctx, ggml_concat(compute_ctx, q_up, k_up, 0), ggml_concat(compute_ctx, v_up, m_up, 0), 0); - // print_ggml_tensor(lora_up_concat, true); //[R*4, 21504, 1, 1] - - lora_down = ggml_cont(compute_ctx, lora_down_concat); - lora_up = ggml_cont(compute_ctx, lora_up_concat); - - applied_lora_tensors.insert(split_q_u_name); - applied_lora_tensors.insert(split_k_u_name); - applied_lora_tensors.insert(split_v_u_name); - applied_lora_tensors.insert(split_m_u_name); - - applied_lora_tensors.insert(split_q_d_name); - applied_lora_tensors.insert(split_k_d_name); - applied_lora_tensors.insert(split_v_d_name); - applied_lora_tensors.insert(split_m_d_name); - } - } - if (lora_up == NULL || lora_down == NULL) { - lora_up_name = lora_pre[type] + key + lora_ups[type] + ".weight"; - if (lora_tensors.find(lora_up_name) == lora_tensors.end()) { - if (key == "model_diffusion_model_output_blocks_2_2_conv") { - // fix for some sdxl lora, like lcm-lora-xl - key = "model_diffusion_model_output_blocks_2_1_conv"; - lora_up_name = lora_pre[type] + key + lora_ups[type] + ".weight"; + if (lora_tensors.find(lora_down_name) != lora_tensors.end()) { + lora_down = to_f32(compute_ctx, lora_tensors[lora_down_name]); } - } - lora_down_name = lora_pre[type] + key + lora_downs[type] + ".weight"; - alpha_name = lora_pre[type] + key + ".alpha"; - scale_name = lora_pre[type] + key + ".scale"; + if (lora_tensors.find(lora_mid_name) != lora_tensors.end()) { + lora_mid = to_f32(compute_ctx, lora_tensors[lora_mid_name]); + applied_lora_tensors.insert(lora_mid_name); + } - if (lora_tensors.find(lora_up_name) != lora_tensors.end()) { - lora_up = lora_tensors[lora_up_name]; + applied_lora_tensors.insert(lora_up_name); + applied_lora_tensors.insert(lora_down_name); + applied_lora_tensors.insert(alpha_name); + applied_lora_tensors.insert(scale_name); } - if (lora_tensors.find(lora_down_name) != lora_tensors.end()) { - lora_down = lora_tensors[lora_down_name]; + if (lora_up == NULL || lora_down == NULL) { + continue; + } + // calc_scale + // TODO: .dora_scale? + int64_t rank = lora_down->ne[ggml_n_dims(lora_down) - 1]; + if (lora_tensors.find(scale_name) != lora_tensors.end()) { + scale_value = ggml_backend_tensor_get_f32(lora_tensors[scale_name]); + } else if (lora_tensors.find(alpha_name) != lora_tensors.end()) { + float alpha = ggml_backend_tensor_get_f32(lora_tensors[alpha_name]); + scale_value = alpha / rank; } - applied_lora_tensors.insert(lora_up_name); - applied_lora_tensors.insert(lora_down_name); - applied_lora_tensors.insert(alpha_name); - applied_lora_tensors.insert(scale_name); - } - if (lora_up == NULL || lora_down == NULL) { - continue; - } - // calc_scale - int64_t dim = lora_down->ne[ggml_n_dims(lora_down) - 1]; - float scale_value = 1.0f; - if (lora_tensors.find(scale_name) != lora_tensors.end()) { - scale_value = ggml_backend_tensor_get_f32(lora_tensors[scale_name]); - } else if (lora_tensors.find(alpha_name) != lora_tensors.end()) { - float alpha = ggml_backend_tensor_get_f32(lora_tensors[alpha_name]); - scale_value = alpha / dim; + updown = ggml_merge_lora(compute_ctx, lora_down, lora_up, lora_mid); } scale_value *= multiplier; - - // flat lora tensors to multiply it - int64_t lora_up_rows = lora_up->ne[ggml_n_dims(lora_up) - 1]; - lora_up = ggml_reshape_2d(compute_ctx, lora_up, ggml_nelements(lora_up) / lora_up_rows, lora_up_rows); - auto lora_down_n_dims = ggml_n_dims(lora_down); - // assume n_dims should always be a multiple of 2 (otherwise rank 1 doesn't work) - lora_down_n_dims = (lora_down_n_dims + lora_down_n_dims % 2); - int64_t lora_down_rows = lora_down->ne[lora_down_n_dims - 1]; - lora_down = ggml_reshape_2d(compute_ctx, lora_down, ggml_nelements(lora_down) / lora_down_rows, lora_down_rows); - - // ggml_mul_mat requires tensor b transposed - lora_down = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, lora_down)); - struct ggml_tensor* updown = ggml_mul_mat(compute_ctx, lora_up, lora_down); - updown = ggml_cont(compute_ctx, ggml_transpose(compute_ctx, updown)); - updown = ggml_reshape(compute_ctx, updown, weight); + updown = ggml_reshape(compute_ctx, updown, weight); GGML_ASSERT(ggml_nelements(updown) == ggml_nelements(weight)); updown = ggml_scale_inplace(compute_ctx, updown, scale_value); ggml_tensor* final_weight; From f23b803a6bd1a6a3b71093188485262250e91931 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 22 Feb 2025 14:22:22 +0100 Subject: [PATCH 039/143] fix:: unapply current loras properly (#590) --- stable-diffusion.cpp | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index cea12e6f2..3284edbda 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -673,19 +673,20 @@ class StableDiffusionGGML { for (auto& kv : lora_state) { const std::string& lora_name = kv.first; float multiplier = kv.second; - - if (curr_lora_state.find(lora_name) != curr_lora_state.end()) { - float curr_multiplier = curr_lora_state[lora_name]; - float multiplier_diff = multiplier - curr_multiplier; - if (multiplier_diff != 0.f) { - lora_state_diff[lora_name] = multiplier_diff; - } - } else { - lora_state_diff[lora_name] = multiplier; - } + lora_state_diff[lora_name] += multiplier; + } + for (auto& kv : curr_lora_state) { + const std::string& lora_name = kv.first; + float curr_multiplier = kv.second; + lora_state_diff[lora_name] -= curr_multiplier; + } + + size_t rm = lora_state_diff.size() - lora_state.size(); + if (rm != 0) { + LOG_INFO("Attempting to apply %lu LoRAs (removing %lu applied LoRAs)", lora_state.size(), rm); + } else { + LOG_INFO("Attempting to apply %lu LoRAs", lora_state.size()); } - - LOG_INFO("Attempting to apply %lu LoRAs", lora_state.size()); for (auto& kv : lora_state_diff) { apply_lora(kv.first, kv.second); From 838beb9b5e4ecf58e5415537c5e5f793fb40caf0 Mon Sep 17 00:00:00 2001 From: "Meng, Hengyu" Date: Sat, 22 Feb 2025 21:23:58 +0800 Subject: [PATCH 040/143] chore: add global SYCL compile flags (#597) --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7b7cc6c47..782a893e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ endif() if(SD_SYCL) message("-- Use SYCL as backend stable-diffusion") set(GGML_SYCL ON) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-narrowing -fsycl") add_definitions(-DSD_USE_SYCL) # disable fast-math on host, see: # https://www.intel.com/content/www/us/en/docs/cpp-compiler/developer-guide-reference/2021-10/fp-model-fp.html From 69c73789fee317ffada90de54dddbd83dae92dca Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 22 Feb 2025 14:29:57 +0100 Subject: [PATCH 041/143] fix: force binary mask for inpaint models (#589) Co-authored-by: leejet --- ggml_extend.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 6a187ca3e..c5913be4d 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -383,8 +383,10 @@ __STATIC_INLINE__ void sd_apply_mask(struct ggml_tensor* image_data, for (int ix = 0; ix < width; ix++) { for (int iy = 0; iy < height; iy++) { float m = ggml_tensor_get_f32(mask, ix, iy); + m = round(m); // inpaint models need binary masks + ggml_tensor_set_f32(mask, m, ix, iy); for (int k = 0; k < channels; k++) { - float value = ((float)(m < 254.5 / 255)) * (ggml_tensor_get_f32(image_data, ix, iy, k) - .5) + .5; + float value = (1 - m) * (ggml_tensor_get_f32(image_data, ix, iy, k) - .5) + .5; ggml_tensor_set_f32(output, value, ix, iy, k); } } From 99609761dc67b2b8f0cc147923f10cfc9ddf48c4 Mon Sep 17 00:00:00 2001 From: piallai <59734081+piallai@users.noreply.github.com> Date: Sat, 22 Feb 2025 14:30:28 +0100 Subject: [PATCH 042/143] docs: fix typo in readme (#574) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ee92ed859..553fb7f8f 100644 --- a/README.md +++ b/README.md @@ -326,7 +326,7 @@ These projects use `stable-diffusion.cpp` as a backend for their image generatio - [Jellybox](https://jellybox.com) - [Stable Diffusion GUI](https://github.com/fszontagh/sd.cpp.gui.wx) -- [Stable Diffusion CLI-GUI] (https://github.com/piallai/stable-diffusion.cpp) +- [Stable Diffusion CLI-GUI](https://github.com/piallai/stable-diffusion.cpp) ## Contributors From f27f2b2aa2efe212bd9f34f4fe85289678f9cb4b Mon Sep 17 00:00:00 2001 From: lalala Date: Sat, 22 Feb 2025 21:32:37 +0800 Subject: [PATCH 043/143] docs: add missing --mask and --guidance options to print_usage (#572) --- examples/cli/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index cfadf4342..417d438af 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -202,11 +202,13 @@ void print_usage(int argc, const char* argv[]) { printf(" If not specified, the default is the type of the weight file\n"); printf(" --lora-model-dir [DIR] lora model directory\n"); printf(" -i, --init-img [IMAGE] path to the input image, required by img2img\n"); + printf(" --mask [MASK] path to the mask image, required by img2img with mask\n"); printf(" --control-image [IMAGE] path to image condition, control net\n"); printf(" -o, --output OUTPUT path to write result image to (default: ./output.png)\n"); printf(" -p, --prompt [PROMPT] the prompt to render\n"); printf(" -n, --negative-prompt PROMPT the negative prompt (default: \"\")\n"); printf(" --cfg-scale SCALE unconditional guidance scale: (default: 7.0)\n"); + printf(" --guidance SCALE guidance scale for img2img (default: 3.5)\n"); printf(" --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0)\n"); printf(" 0 means disabled, a value of 2.5 is nice for sd3.5 medium\n"); printf(" --skip-layers LAYERS Layers to skip for SLG steps: (default: [7,8,9])\n"); From 19d876ee300a055629926ff836489901f734f2b7 Mon Sep 17 00:00:00 2001 From: yslai Date: Sat, 22 Feb 2025 05:34:22 -0800 Subject: [PATCH 044/143] feat: implement DDIM with the "trailing" timestep spacing and TCD (#568) --- denoiser.hpp | 371 +++++++++++++++++++++++++++++++++++++++++- examples/cli/main.cpp | 16 +- stable-diffusion.cpp | 12 +- stable-diffusion.h | 4 + 4 files changed, 400 insertions(+), 3 deletions(-) diff --git a/denoiser.hpp b/denoiser.hpp index 975699d22..66799109d 100644 --- a/denoiser.hpp +++ b/denoiser.hpp @@ -474,7 +474,8 @@ static void sample_k_diffusion(sample_method_t method, ggml_context* work_ctx, ggml_tensor* x, std::vector sigmas, - std::shared_ptr rng) { + std::shared_ptr rng, + float eta) { size_t steps = sigmas.size() - 1; // sample_euler_ancestral switch (method) { @@ -1005,6 +1006,374 @@ static void sample_k_diffusion(sample_method_t method, } } } break; + case DDIM_TRAILING: // Denoising Diffusion Implicit Models + // with the "trailing" timestep spacing + { + // See J. Song et al., "Denoising Diffusion Implicit + // Models", arXiv:2010.02502 [cs.LG] + // + // DDIM itself needs alphas_cumprod (DDPM, J. Ho et al., + // arXiv:2006.11239 [cs.LG] with k-diffusion's start and + // end beta) (which unfortunately k-diffusion's data + // structure hides from the denoiser), and the sigmas are + // also needed to invert the behavior of CompVisDenoiser + // (k-diffusion's LMSDiscreteScheduler) + float beta_start = 0.00085f; + float beta_end = 0.0120f; + std::vector alphas_cumprod; + std::vector compvis_sigmas; + + alphas_cumprod.reserve(TIMESTEPS); + compvis_sigmas.reserve(TIMESTEPS); + for (int i = 0; i < TIMESTEPS; i++) { + alphas_cumprod[i] = + (i == 0 ? 1.0f : alphas_cumprod[i - 1]) * + (1.0f - + std::pow(sqrtf(beta_start) + + (sqrtf(beta_end) - sqrtf(beta_start)) * + ((float)i / (TIMESTEPS - 1)), 2)); + compvis_sigmas[i] = + std::sqrt((1 - alphas_cumprod[i]) / + alphas_cumprod[i]); + } + + struct ggml_tensor* pred_original_sample = + ggml_dup_tensor(work_ctx, x); + struct ggml_tensor* variance_noise = + ggml_dup_tensor(work_ctx, x); + + for (int i = 0; i < steps; i++) { + // The "trailing" DDIM timestep, see S. Lin et al., + // "Common Diffusion Noise Schedules and Sample Steps + // are Flawed", arXiv:2305.08891 [cs], p. 4, Table + // 2. Most variables below follow Diffusers naming + // + // Diffuser naming vs. Song et al. (2010), p. 5, (12) + // and p. 16, (16) ( -> ): + // + // - pred_noise_t -> epsilon_theta^(t)(x_t) + // - pred_original_sample -> f_theta^(t)(x_t) or x_0 + // - std_dev_t -> sigma_t (not the LMS sigma) + // - eta -> eta (set to 0 at the moment) + // - pred_sample_direction -> "direction pointing to + // x_t" + // - pred_prev_sample -> "x_t-1" + int timestep = + roundf(TIMESTEPS - + i * ((float)TIMESTEPS / steps)) - 1; + // 1. get previous step value (=t-1) + int prev_timestep = timestep - TIMESTEPS / steps; + // The sigma here is chosen to cause the + // CompVisDenoiser to produce t = timestep + float sigma = compvis_sigmas[timestep]; + if (i == 0) { + // The function add_noise intializes x to + // Diffusers' latents * sigma (as in Diffusers' + // pipeline) or sample * sigma (Diffusers' + // scheduler), where this sigma = init_noise_sigma + // in Diffusers. For DDPM and DDIM however, + // init_noise_sigma = 1. But the k-diffusion + // model() also evaluates F_theta(c_in(sigma) x; + // ...) instead of the bare U-net F_theta, with + // c_in = 1 / sqrt(sigma^2 + 1), as defined in + // T. Karras et al., "Elucidating the Design Space + // of Diffusion-Based Generative Models", + // arXiv:2206.00364 [cs.CV], p. 3, Table 1. Hence + // the first call has to be prescaled as x <- x / + // (c_in * sigma) with the k-diffusion pipeline + // and CompVisDenoiser. + float* vec_x = (float*)x->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_x[j] *= std::sqrt(sigma * sigma + 1) / + sigma; + } + } + else { + // For the subsequent steps after the first one, + // at this point x = latents or x = sample, and + // needs to be prescaled with x <- sample / c_in + // to compensate for model() applying the scale + // c_in before the U-net F_theta + float* vec_x = (float*)x->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_x[j] *= std::sqrt(sigma * sigma + 1); + } + } + // Note (also noise_pred in Diffuser's pipeline) + // model_output = model() is the D(x, sigma) as + // defined in Karras et al. (2022), p. 3, Table 1 and + // p. 8 (7), compare also p. 38 (226) therein. + struct ggml_tensor* model_output = + model(x, sigma, i + 1); + // Here model_output is still the k-diffusion denoiser + // output, not the U-net output F_theta(c_in(sigma) x; + // ...) in Karras et al. (2022), whereas Diffusers' + // model_output is F_theta(...). Recover the actual + // model_output, which is also referred to as the + // "Karras ODE derivative" d or d_cur in several + // samplers above. + { + float* vec_x = (float*)x->data; + float* vec_model_output = + (float*)model_output->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_model_output[j] = + (vec_x[j] - vec_model_output[j]) * + (1 / sigma); + } + } + // 2. compute alphas, betas + float alpha_prod_t = alphas_cumprod[timestep]; + // Note final_alpha_cumprod = alphas_cumprod[0] due to + // trailing timestep spacing + float alpha_prod_t_prev = prev_timestep >= 0 ? + alphas_cumprod[prev_timestep] : alphas_cumprod[0]; + float beta_prod_t = 1 - alpha_prod_t; + // 3. compute predicted original sample from predicted + // noise also called "predicted x_0" of formula (12) + // from https://arxiv.org/pdf/2010.02502.pdf + { + float* vec_x = (float*)x->data; + float* vec_model_output = + (float*)model_output->data; + float* vec_pred_original_sample = + (float*)pred_original_sample->data; + // Note the substitution of latents or sample = x + // * c_in = x / sqrt(sigma^2 + 1) + for (int j = 0; j < ggml_nelements(x); j++) { + vec_pred_original_sample[j] = + (vec_x[j] / std::sqrt(sigma * sigma + 1) - + std::sqrt(beta_prod_t) * + vec_model_output[j]) * + (1 / std::sqrt(alpha_prod_t)); + } + } + // Assuming the "epsilon" prediction type, where below + // pred_epsilon = model_output is inserted, and is not + // defined/copied explicitly. + // + // 5. compute variance: "sigma_t(eta)" -> see formula + // (16) + // + // sigma_t = sqrt((1 - alpha_t-1)/(1 - alpha_t)) * + // sqrt(1 - alpha_t/alpha_t-1) + float beta_prod_t_prev = 1 - alpha_prod_t_prev; + float variance = (beta_prod_t_prev / beta_prod_t) * + (1 - alpha_prod_t / alpha_prod_t_prev); + float std_dev_t = eta * std::sqrt(variance); + // 6. compute "direction pointing to x_t" of formula + // (12) from https://arxiv.org/pdf/2010.02502.pdf + // 7. compute x_t without "random noise" of formula + // (12) from https://arxiv.org/pdf/2010.02502.pdf + { + float* vec_model_output = (float*)model_output->data; + float* vec_pred_original_sample = + (float*)pred_original_sample->data; + float* vec_x = (float*)x->data; + for (int j = 0; j < ggml_nelements(x); j++) { + // Two step inner loop without an explicit + // tensor + float pred_sample_direction = + std::sqrt(1 - alpha_prod_t_prev - + std::pow(std_dev_t, 2)) * + vec_model_output[j]; + vec_x[j] = std::sqrt(alpha_prod_t_prev) * + vec_pred_original_sample[j] + + pred_sample_direction; + } + } + if (eta > 0) { + ggml_tensor_set_f32_randn(variance_noise, rng); + float* vec_variance_noise = + (float*)variance_noise->data; + float* vec_x = (float*)x->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_x[j] += std_dev_t * vec_variance_noise[j]; + } + } + // See the note above: x = latents or sample here, and + // is not scaled by the c_in. For the final output + // this is correct, but for subsequent iterations, x + // needs to be prescaled again, since k-diffusion's + // model() differes from the bare U-net F_theta by the + // factor c_in. + } + } break; + case TCD: // Strategic Stochastic Sampling (Algorithm 4) in + // Trajectory Consistency Distillation + { + // See J. Zheng et al., "Trajectory Consistency + // Distillation: Improved Latent Consistency Distillation + // by Semi-Linear Consistency Function with Trajectory + // Mapping", arXiv:2402.19159 [cs.CV] + float beta_start = 0.00085f; + float beta_end = 0.0120f; + std::vector alphas_cumprod; + std::vector compvis_sigmas; + + alphas_cumprod.reserve(TIMESTEPS); + compvis_sigmas.reserve(TIMESTEPS); + for (int i = 0; i < TIMESTEPS; i++) { + alphas_cumprod[i] = + (i == 0 ? 1.0f : alphas_cumprod[i - 1]) * + (1.0f - + std::pow(sqrtf(beta_start) + + (sqrtf(beta_end) - sqrtf(beta_start)) * + ((float)i / (TIMESTEPS - 1)), 2)); + compvis_sigmas[i] = + std::sqrt((1 - alphas_cumprod[i]) / + alphas_cumprod[i]); + } + int original_steps = 50; + + struct ggml_tensor* pred_original_sample = + ggml_dup_tensor(work_ctx, x); + struct ggml_tensor* noise = + ggml_dup_tensor(work_ctx, x); + + for (int i = 0; i < steps; i++) { + // Analytic form for TCD timesteps + int timestep = TIMESTEPS - 1 - + (TIMESTEPS / original_steps) * + (int)floor(i * ((float)original_steps / steps)); + // 1. get previous step value + int prev_timestep = i >= steps - 1 ? 0 : + TIMESTEPS - 1 - (TIMESTEPS / original_steps) * + (int)floor((i + 1) * + ((float)original_steps / steps)); + // Here timestep_s is tau_n' in Algorithm 4. The _s + // notation appears to be that from C. Lu, + // "DPM-Solver: A Fast ODE Solver for Diffusion + // Probabilistic Model Sampling in Around 10 Steps", + // arXiv:2206.00927 [cs.LG], but this notation is not + // continued in Algorithm 4, where _n' is used. + int timestep_s = + (int)floor((1 - eta) * prev_timestep); + // Begin k-diffusion specific workaround for + // evaluating F_theta(x; ...) from D(x, sigma), same + // as in DDIM (and see there for detailed comments) + float sigma = compvis_sigmas[timestep]; + if (i == 0) { + float* vec_x = (float*)x->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_x[j] *= std::sqrt(sigma * sigma + 1) / + sigma; + } + } + else { + float* vec_x = (float*)x->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_x[j] *= std::sqrt(sigma * sigma + 1); + } + } + struct ggml_tensor* model_output = + model(x, sigma, i + 1); + { + float* vec_x = (float*)x->data; + float* vec_model_output = + (float*)model_output->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_model_output[j] = + (vec_x[j] - vec_model_output[j]) * + (1 / sigma); + } + } + // 2. compute alphas, betas + // + // When comparing TCD with DDPM/DDIM note that Zheng + // et al. (2024) follows the DPM-Solver notation for + // alpha. One can find the following comment in the + // original DPM-Solver code + // (https://github.com/LuChengTHU/dpm-solver/): + // "**Important**: Please pay special attention for + // the args for `alphas_cumprod`: The `alphas_cumprod` + // is the \hat{alpha_n} arrays in the notations of + // DDPM. [...] Therefore, the notation \hat{alpha_n} + // is different from the notation alpha_t in + // DPM-Solver. In fact, we have alpha_{t_n} = + // \sqrt{\hat{alpha_n}}, [...]" + float alpha_prod_t = alphas_cumprod[timestep]; + float beta_prod_t = 1 - alpha_prod_t; + // Note final_alpha_cumprod = alphas_cumprod[0] since + // TCD is always "trailing" + float alpha_prod_t_prev = prev_timestep >= 0 ? + alphas_cumprod[prev_timestep] : alphas_cumprod[0]; + // The subscript _s are the only portion in this + // section (2) unique to TCD + float alpha_prod_s = alphas_cumprod[timestep_s]; + float beta_prod_s = 1 - alpha_prod_s; + // 3. Compute the predicted noised sample x_s based on + // the model parameterization + // + // This section is also exactly the same as DDIM + { + float* vec_x = (float*)x->data; + float* vec_model_output = + (float*)model_output->data; + float* vec_pred_original_sample = + (float*)pred_original_sample->data; + for (int j = 0; j < ggml_nelements(x); j++) { + vec_pred_original_sample[j] = + (vec_x[j] / std::sqrt(sigma * sigma + 1) - + std::sqrt(beta_prod_t) * + vec_model_output[j]) * + (1 / std::sqrt(alpha_prod_t)); + } + } + // This consistency function step can be difficult to + // decipher from Algorithm 4, as it is simply stated + // using a consistency function. This step is the + // modified DDIM, i.e. p. 8 (32) in Zheng et + // al. (2024), with eta set to 0 (see the paragraph + // immediately thereafter that states this somewhat + // obliquely). + { + float* vec_pred_original_sample = + (float*)pred_original_sample->data; + float* vec_model_output = + (float*)model_output->data; + float* vec_x = (float*)x->data; + for (int j = 0; j < ggml_nelements(x); j++) { + // Substituting x = pred_noised_sample and + // pred_epsilon = model_output + vec_x[j] = + std::sqrt(alpha_prod_s) * + vec_pred_original_sample[j] + + std::sqrt(beta_prod_s) * + vec_model_output[j]; + } + } + // 4. Sample and inject noise z ~ N(0, I) for + // MultiStep Inference Noise is not used on the final + // timestep of the timestep schedule. This also means + // that noise is not used for one-step sampling. Eta + // (referred to as "gamma" in the paper) was + // introduced to control the stochasticity in every + // step. When eta = 0, it represents deterministic + // sampling, whereas eta = 1 indicates full stochastic + // sampling. + if (eta > 0 && i != steps - 1) { + // In this case, x is still pred_noised_sample, + // continue in-place + ggml_tensor_set_f32_randn(noise, rng); + float* vec_x = (float*)x->data; + float* vec_noise = (float*)noise->data; + for (int j = 0; j < ggml_nelements(x); j++) { + // Corresponding to (35) in Zheng et + // al. (2024), substituting x = + // pred_noised_sample + vec_x[j] = + std::sqrt(alpha_prod_t_prev / + alpha_prod_s) * + vec_x[j] + + std::sqrt(1 - alpha_prod_t_prev / + alpha_prod_s) * + vec_noise[j]; + } + } + } + } break; default: LOG_ERROR("Attempting to sample with nonexisting sample method %i", method); diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 417d438af..cf8f5b130 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -39,6 +39,8 @@ const char* sample_method_str[] = { "ipndm", "ipndm_v", "lcm", + "ddim_trailing", + "tcd", }; // Names of the sigma schedule overrides, same order as sample_schedule in stable-diffusion.h @@ -93,6 +95,7 @@ struct SDParams { float min_cfg = 1.0f; float cfg_scale = 7.0f; float guidance = 3.5f; + float eta = 0.f; float style_ratio = 20.f; int clip_skip = -1; // <= 0 represents unspecified int width = 512; @@ -162,6 +165,7 @@ void print_params(SDParams params) { printf(" cfg_scale: %.2f\n", params.cfg_scale); printf(" slg_scale: %.2f\n", params.slg_scale); printf(" guidance: %.2f\n", params.guidance); + printf(" eta: %.2f\n", params.eta); printf(" clip_skip: %d\n", params.clip_skip); printf(" width: %d\n", params.width); printf(" height: %d\n", params.height); @@ -211,6 +215,7 @@ void print_usage(int argc, const char* argv[]) { printf(" --guidance SCALE guidance scale for img2img (default: 3.5)\n"); printf(" --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0)\n"); printf(" 0 means disabled, a value of 2.5 is nice for sd3.5 medium\n"); + printf(" --eta SCALE eta in DDIM, only for DDIM and TCD: (default: 0)\n"); printf(" --skip-layers LAYERS Layers to skip for SLG steps: (default: [7,8,9])\n"); printf(" --skip-layer-start START SLG enabling point: (default: 0.01)\n"); printf(" --skip-layer-end END SLG disabling point: (default: 0.2)\n"); @@ -221,7 +226,7 @@ void print_usage(int argc, const char* argv[]) { printf(" 1.0 corresponds to full destruction of information in init image\n"); printf(" -H, --height H image height, in pixel space (default: 512)\n"); printf(" -W, --width W image width, in pixel space (default: 512)\n"); - printf(" --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm}\n"); + printf(" --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd}\n"); printf(" sampling method (default: \"euler_a\")\n"); printf(" --steps STEPS number of sample steps (default: 20)\n"); printf(" --rng {std_default, cuda} RNG (default: cuda)\n"); @@ -440,6 +445,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.guidance = std::stof(argv[i]); + } else if (arg == "--eta") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.eta = std::stof(argv[i]); } else if (arg == "--strength") { if (++i >= argc) { invalid_arg = true; @@ -719,6 +730,7 @@ std::string get_image_params(SDParams params, int64_t seed) { parameter_string += "Skip layer end: " + std::to_string(params.skip_layer_end) + ", "; } parameter_string += "Guidance: " + std::to_string(params.guidance) + ", "; + parameter_string += "Eta: " + std::to_string(params.eta) + ", "; parameter_string += "Seed: " + std::to_string(seed) + ", "; parameter_string += "Size: " + std::to_string(params.width) + "x" + std::to_string(params.height) + ", "; parameter_string += "Model: " + sd_basename(params.model_path) + ", "; @@ -939,6 +951,7 @@ int main(int argc, const char* argv[]) { params.clip_skip, params.cfg_scale, params.guidance, + params.eta, params.width, params.height, params.sample_method, @@ -1006,6 +1019,7 @@ int main(int argc, const char* argv[]) { params.clip_skip, params.cfg_scale, params.guidance, + params.eta, params.width, params.height, params.sample_method, diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 3284edbda..a2d33bca2 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -47,6 +47,8 @@ const char* sampling_methods_str[] = { "iPNDM", "iPNDM_v", "LCM", + "DDIM \"trailing\"", + "TCD" }; /*================================================== Helper Functions ================================================*/ @@ -793,6 +795,7 @@ class StableDiffusionGGML { float min_cfg, float cfg_scale, float guidance, + float eta, sample_method_t method, const std::vector& sigmas, int start_merge_step, @@ -988,7 +991,7 @@ class StableDiffusionGGML { return denoised; }; - sample_k_diffusion(method, denoise, work_ctx, x, sigmas, rng); + sample_k_diffusion(method, denoise, work_ctx, x, sigmas, rng, eta); x = denoiser->inverse_noise_scaling(sigmas[sigmas.size() - 1], x); @@ -1194,6 +1197,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, int clip_skip, float cfg_scale, float guidance, + float eta, int width, int height, enum sample_method_t sample_method, @@ -1457,6 +1461,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, cfg_scale, cfg_scale, guidance, + eta, sample_method, sigmas, start_merge_step, @@ -1522,6 +1527,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, int clip_skip, float cfg_scale, float guidance, + float eta, int width, int height, enum sample_method_t sample_method, @@ -1600,6 +1606,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, clip_skip, cfg_scale, guidance, + eta, width, height, sample_method, @@ -1631,6 +1638,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, int clip_skip, float cfg_scale, float guidance, + float eta, int width, int height, sample_method_t sample_method, @@ -1778,6 +1786,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, clip_skip, cfg_scale, guidance, + eta, width, height, sample_method, @@ -1891,6 +1900,7 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, min_cfg, cfg_scale, 0.f, + 0.f, sample_method, sigmas, -1, diff --git a/stable-diffusion.h b/stable-diffusion.h index 5a758df66..8872bbaac 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -44,6 +44,8 @@ enum sample_method_t { IPNDM, IPNDM_V, LCM, + DDIM_TRAILING, + TCD, N_SAMPLE_METHODS }; @@ -155,6 +157,7 @@ SD_API sd_image_t* txt2img(sd_ctx_t* sd_ctx, int clip_skip, float cfg_scale, float guidance, + float eta, int width, int height, enum sample_method_t sample_method, @@ -180,6 +183,7 @@ SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, int clip_skip, float cfg_scale, float guidance, + float eta, int width, int height, enum sample_method_t sample_method, From fbd42b6fc16d14fbd362993fa1d083740a05f113 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 1 Mar 2025 04:45:39 +0100 Subject: [PATCH 045/143] fix: fix embeddings with quantized models (#601) --- clip.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clip.hpp b/clip.hpp index cfc4cb38c..2307ee3c5 100644 --- a/clip.hpp +++ b/clip.hpp @@ -546,7 +546,7 @@ class CLIPEmbeddings : public GGMLBlock { int64_t num_positions; void init_params(struct ggml_context* ctx, std::map& tensor_types, const std::string prefix = "") { - enum ggml_type token_wtype = (tensor_types.find(prefix + "token_embedding.weight") != tensor_types.end()) ? tensor_types[prefix + "token_embedding.weight"] : GGML_TYPE_F32; + enum ggml_type token_wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "token_embedding.weight") != tensor_types.end()) ? tensor_types[prefix + "token_embedding.weight"] : GGML_TYPE_F32; enum ggml_type position_wtype = GGML_TYPE_F32; //(tensor_types.find(prefix + "position_embedding.weight") != tensor_types.end()) ? tensor_types[prefix + "position_embedding.weight"] : GGML_TYPE_F32; params["token_embedding.weight"] = ggml_new_tensor_2d(ctx, token_wtype, embed_dim, vocab_size); From 85e9a129882c7e3a7d037fb6902e3bd68b4b525c Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 1 Mar 2025 04:48:04 +0100 Subject: [PATCH 046/143] fix: preprocess tensor names in tensor types map (#607) Thank you for your contribution --- model.cpp | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/model.cpp b/model.cpp index dcbaae5bc..f3c5ae576 100644 --- a/model.cpp +++ b/model.cpp @@ -558,6 +558,26 @@ std::string convert_tensor_name(std::string name) { return new_name; } +void add_preprocess_tensor_storage_types(std::map& tensor_storages_types, std::string name, enum ggml_type type) { + std::string new_name = convert_tensor_name(name); + + if (new_name.find("cond_stage_model") != std::string::npos && ends_with(new_name, "attn.in_proj_weight")) { + size_t prefix_size = new_name.find("attn.in_proj_weight"); + std::string prefix = new_name.substr(0, prefix_size); + tensor_storages_types[prefix + "self_attn.q_proj.weight"] = type; + tensor_storages_types[prefix + "self_attn.k_proj.weight"] = type; + tensor_storages_types[prefix + "self_attn.v_proj.weight"] = type; + } else if (new_name.find("cond_stage_model") != std::string::npos && ends_with(new_name, "attn.in_proj_bias")) { + size_t prefix_size = new_name.find("attn.in_proj_bias"); + std::string prefix = new_name.substr(0, prefix_size); + tensor_storages_types[prefix + "self_attn.q_proj.bias"] = type; + tensor_storages_types[prefix + "self_attn.k_proj.bias"] = type; + tensor_storages_types[prefix + "self_attn.v_proj.bias"] = type; + } else { + tensor_storages_types[new_name] = type; + } +} + void preprocess_tensor(TensorStorage tensor_storage, std::vector& processed_tensor_storages) { std::vector result; @@ -927,7 +947,7 @@ bool ModelLoader::init_from_gguf_file(const std::string& file_path, const std::s GGML_ASSERT(ggml_nbytes(dummy) == tensor_storage.nbytes()); tensor_storages.push_back(tensor_storage); - tensor_storages_types[tensor_storage.name] = tensor_storage.type; + add_preprocess_tensor_storage_types(tensor_storages_types, tensor_storage.name, tensor_storage.type); } gguf_free(ctx_gguf_); @@ -1072,7 +1092,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const } tensor_storages.push_back(tensor_storage); - tensor_storages_types[tensor_storage.name] = tensor_storage.type; + add_preprocess_tensor_storage_types(tensor_storages_types, tensor_storage.name, tensor_storage.type); // LOG_DEBUG("%s %s", tensor_storage.to_string().c_str(), dtype.c_str()); } @@ -1403,7 +1423,7 @@ bool ModelLoader::parse_data_pkl(uint8_t* buffer, // printf(" ZIP got tensor %s \n ", reader.tensor_storage.name.c_str()); reader.tensor_storage.name = prefix + reader.tensor_storage.name; tensor_storages.push_back(reader.tensor_storage); - tensor_storages_types[reader.tensor_storage.name] = reader.tensor_storage.type; + add_preprocess_tensor_storage_types(tensor_storages_types, reader.tensor_storage.name, reader.tensor_storage.type); // LOG_DEBUG("%s", reader.tensor_storage.name.c_str()); // reset @@ -1461,10 +1481,10 @@ SDVersion ModelLoader::get_sd_version() { TensorStorage token_embedding_weight, input_block_weight; bool input_block_checked = false; - bool has_multiple_encoders = false; - bool is_unet = false; + bool has_multiple_encoders = false; + bool is_unet = false; - bool is_xl = false; + bool is_xl = false; bool is_flux = false; #define found_family (is_xl || is_flux) @@ -1481,7 +1501,7 @@ SDVersion ModelLoader::get_sd_version() { } if (tensor_storage.name.find("model.diffusion_model.input_blocks.") != std::string::npos) { is_unet = true; - if(has_multiple_encoders){ + if (has_multiple_encoders) { is_xl = true; if (input_block_checked) { break; @@ -1490,7 +1510,7 @@ SDVersion ModelLoader::get_sd_version() { } if (tensor_storage.name.find("conditioner.embedders.1") != std::string::npos || tensor_storage.name.find("cond_stage_model.1") != std::string::npos) { has_multiple_encoders = true; - if(is_unet){ + if (is_unet) { is_xl = true; if (input_block_checked) { break; @@ -1635,11 +1655,20 @@ ggml_type ModelLoader::get_vae_wtype() { void ModelLoader::set_wtype_override(ggml_type wtype, std::string prefix) { for (auto& pair : tensor_storages_types) { if (prefix.size() < 1 || pair.first.substr(0, prefix.size()) == prefix) { + bool found = false; for (auto& tensor_storage : tensor_storages) { - if (tensor_storage.name == pair.first) { - if (tensor_should_be_converted(tensor_storage, wtype)) { - pair.second = wtype; + std::map temp; + add_preprocess_tensor_storage_types(temp, tensor_storage.name, tensor_storage.type); + for (auto& preprocessed_name : temp) { + if (preprocessed_name.first == pair.first) { + if (tensor_should_be_converted(tensor_storage, wtype)) { + pair.second = wtype; + } + found = true; + break; } + } + if (found) { break; } } From f50a7f66aaf9de266e373cd8e7886d7b20f2fb86 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 1 Mar 2025 04:49:06 +0100 Subject: [PATCH 047/143] fix: fix race condition causing inconsistent value for `decoder_only` (#609) --- tae.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tae.hpp b/tae.hpp index 683059822..c458b87d2 100644 --- a/tae.hpp +++ b/tae.hpp @@ -201,7 +201,7 @@ struct TinyAutoEncoder : public GGMLRunner { bool decoder_only = true, SDVersion version = VERSION_SD1) : decode_only(decoder_only), - taesd(decode_only, version), + taesd(decoder_only, version), GGMLRunner(backend) { taesd.init(params_ctx, tensor_types, prefix); } From 195d170136752df3b31c9ababe8f41c8594e3a6a Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 1 Mar 2025 12:09:55 +0800 Subject: [PATCH 048/143] sync: update ggml --- ggml | 2 +- model.h | 1 + stable-diffusion.h | 73 ++++++++++++++++++++++++---------------------- 3 files changed, 40 insertions(+), 36 deletions(-) diff --git a/ggml b/ggml index 6fcbd60bc..ff9052988 160000 --- a/ggml +++ b/ggml @@ -1 +1 @@ -Subproject commit 6fcbd60bc72ac3f7ad43f78c87e535f2e6206f58 +Subproject commit ff9052988b76e137bcf92bb335733933ca196ac0 diff --git a/model.h b/model.h index 95bbf1da2..d7f976533 100644 --- a/model.h +++ b/model.h @@ -14,6 +14,7 @@ #include "ggml.h" #include "json.hpp" #include "zip.h" +#include "gguf.h" #define SD_MAX_DIMS 5 diff --git a/stable-diffusion.h b/stable-diffusion.h index 8872bbaac..52dcc848a 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -61,43 +61,46 @@ enum schedule_t { // same as enum ggml_type enum sd_type_t { - SD_TYPE_F32 = 0, - SD_TYPE_F16 = 1, - SD_TYPE_Q4_0 = 2, - SD_TYPE_Q4_1 = 3, + SD_TYPE_F32 = 0, + SD_TYPE_F16 = 1, + SD_TYPE_Q4_0 = 2, + SD_TYPE_Q4_1 = 3, // SD_TYPE_Q4_2 = 4, support has been removed // SD_TYPE_Q4_3 = 5, support has been removed - SD_TYPE_Q5_0 = 6, - SD_TYPE_Q5_1 = 7, - SD_TYPE_Q8_0 = 8, - SD_TYPE_Q8_1 = 9, - SD_TYPE_Q2_K = 10, - SD_TYPE_Q3_K = 11, - SD_TYPE_Q4_K = 12, - SD_TYPE_Q5_K = 13, - SD_TYPE_Q6_K = 14, - SD_TYPE_Q8_K = 15, - SD_TYPE_IQ2_XXS = 16, - SD_TYPE_IQ2_XS = 17, - SD_TYPE_IQ3_XXS = 18, - SD_TYPE_IQ1_S = 19, - SD_TYPE_IQ4_NL = 20, - SD_TYPE_IQ3_S = 21, - SD_TYPE_IQ2_S = 22, - SD_TYPE_IQ4_XS = 23, - SD_TYPE_I8 = 24, - SD_TYPE_I16 = 25, - SD_TYPE_I32 = 26, - SD_TYPE_I64 = 27, - SD_TYPE_F64 = 28, - SD_TYPE_IQ1_M = 29, - SD_TYPE_BF16 = 30, - SD_TYPE_Q4_0_4_4 = 31, - SD_TYPE_Q4_0_4_8 = 32, - SD_TYPE_Q4_0_8_8 = 33, - SD_TYPE_TQ1_0 = 34, - SD_TYPE_TQ2_0 = 35, - SD_TYPE_COUNT, + SD_TYPE_Q5_0 = 6, + SD_TYPE_Q5_1 = 7, + SD_TYPE_Q8_0 = 8, + SD_TYPE_Q8_1 = 9, + SD_TYPE_Q2_K = 10, + SD_TYPE_Q3_K = 11, + SD_TYPE_Q4_K = 12, + SD_TYPE_Q5_K = 13, + SD_TYPE_Q6_K = 14, + SD_TYPE_Q8_K = 15, + SD_TYPE_IQ2_XXS = 16, + SD_TYPE_IQ2_XS = 17, + SD_TYPE_IQ3_XXS = 18, + SD_TYPE_IQ1_S = 19, + SD_TYPE_IQ4_NL = 20, + SD_TYPE_IQ3_S = 21, + SD_TYPE_IQ2_S = 22, + SD_TYPE_IQ4_XS = 23, + SD_TYPE_I8 = 24, + SD_TYPE_I16 = 25, + SD_TYPE_I32 = 26, + SD_TYPE_I64 = 27, + SD_TYPE_F64 = 28, + SD_TYPE_IQ1_M = 29, + SD_TYPE_BF16 = 30, + // SD_TYPE_Q4_0_4_4 = 31, support has been removed from gguf files + // SD_TYPE_Q4_0_4_8 = 32, + // SD_TYPE_Q4_0_8_8 = 33, + SD_TYPE_TQ1_0 = 34, + SD_TYPE_TQ2_0 = 35, + // SD_TYPE_IQ4_NL_4_4 = 36, + // SD_TYPE_IQ4_NL_4_8 = 37, + // SD_TYPE_IQ4_NL_8_8 = 38, + SD_TYPE_COUNT = 39, }; SD_API const char* sd_type_name(enum sd_type_t type); From 30b3ac8e6279c128a7a3f8c2627d31b96c2a1185 Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 1 Mar 2025 16:58:26 +0800 Subject: [PATCH 049/143] fix: avoid potential dangling pointer problem --- examples/cli/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index cf8f5b130..a2f1d1fa4 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -931,12 +931,12 @@ int main(int argc, const char* argv[]) { } } + std::vector default_mask_image_vec(params.width * params.height, 255); if (params.mask_path != "") { int c = 0; mask_image_buffer = stbi_load(params.mask_path.c_str(), ¶ms.width, ¶ms.height, &c, 1); } else { - std::vector arr(params.width * params.height, 255); - mask_image_buffer = arr.data(); + mask_image_buffer = default_mask_image_vec.data(); } sd_image_t mask_image = {(uint32_t)params.width, (uint32_t)params.height, From 3fb275a67b701f49ea6a1179d5b20357cd49b1d0 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sun, 9 Mar 2025 05:21:23 +0100 Subject: [PATCH 050/143] fix: suport sdxl embedddings (#621) --- conditioner.hpp | 60 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/conditioner.hpp b/conditioner.hpp index 8d1ec31bc..6e9acdb19 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -51,7 +51,8 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { std::string trigger_word = "img"; // should be user settable std::string embd_dir; - int32_t num_custom_embeddings = 0; + int32_t num_custom_embeddings = 0; + int32_t num_custom_embeddings_2 = 0; std::vector token_embed_custom; std::vector readed_embeddings; @@ -131,28 +132,55 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { params.no_alloc = false; struct ggml_context* embd_ctx = ggml_init(params); struct ggml_tensor* embd = NULL; - int64_t hidden_size = text_model->model.hidden_size; + struct ggml_tensor* embd2 = NULL; auto on_load = [&](const TensorStorage& tensor_storage, ggml_tensor** dst_tensor) { - if (tensor_storage.ne[0] != hidden_size) { - LOG_DEBUG("embedding wrong hidden size, got %i, expected %i", tensor_storage.ne[0], hidden_size); - return false; + if (tensor_storage.ne[0] != text_model->model.hidden_size) { + if (text_model2) { + if (tensor_storage.ne[0] == text_model2->model.hidden_size) { + embd2 = ggml_new_tensor_2d(embd_ctx, tensor_storage.type, text_model2->model.hidden_size, tensor_storage.n_dims > 1 ? tensor_storage.ne[1] : 1); + *dst_tensor = embd2; + } else { + LOG_DEBUG("embedding wrong hidden size, got %i, expected %i or %i", tensor_storage.ne[0], text_model->model.hidden_size, text_model2->model.hidden_size); + return false; + } + } else { + LOG_DEBUG("embedding wrong hidden size, got %i, expected %i", tensor_storage.ne[0], text_model->model.hidden_size); + return false; + } + } else { + embd = ggml_new_tensor_2d(embd_ctx, tensor_storage.type, text_model->model.hidden_size, tensor_storage.n_dims > 1 ? tensor_storage.ne[1] : 1); + *dst_tensor = embd; } - embd = ggml_new_tensor_2d(embd_ctx, tensor_storage.type, hidden_size, tensor_storage.n_dims > 1 ? tensor_storage.ne[1] : 1); - *dst_tensor = embd; return true; }; model_loader.load_tensors(on_load, NULL); readed_embeddings.push_back(embd_name); - token_embed_custom.resize(token_embed_custom.size() + ggml_nbytes(embd)); - memcpy((void*)(token_embed_custom.data() + num_custom_embeddings * hidden_size * ggml_type_size(embd->type)), - embd->data, - ggml_nbytes(embd)); - for (int i = 0; i < embd->ne[1]; i++) { - bpe_tokens.push_back(text_model->model.vocab_size + num_custom_embeddings); - // LOG_DEBUG("new custom token: %i", text_model.vocab_size + num_custom_embeddings); - num_custom_embeddings++; + if (embd) { + int64_t hidden_size = text_model->model.hidden_size; + token_embed_custom.resize(token_embed_custom.size() + ggml_nbytes(embd)); + memcpy((void*)(token_embed_custom.data() + num_custom_embeddings * hidden_size * ggml_type_size(embd->type)), + embd->data, + ggml_nbytes(embd)); + for (int i = 0; i < embd->ne[1]; i++) { + bpe_tokens.push_back(text_model->model.vocab_size + num_custom_embeddings); + // LOG_DEBUG("new custom token: %i", text_model.vocab_size + num_custom_embeddings); + num_custom_embeddings++; + } + LOG_DEBUG("embedding '%s' applied, custom embeddings: %i", embd_name.c_str(), num_custom_embeddings); + } + if (embd2) { + int64_t hidden_size = text_model2->model.hidden_size; + token_embed_custom.resize(token_embed_custom.size() + ggml_nbytes(embd2)); + memcpy((void*)(token_embed_custom.data() + num_custom_embeddings_2 * hidden_size * ggml_type_size(embd2->type)), + embd2->data, + ggml_nbytes(embd2)); + for (int i = 0; i < embd2->ne[1]; i++) { + bpe_tokens.push_back(text_model2->model.vocab_size + num_custom_embeddings_2); + // LOG_DEBUG("new custom token: %i", text_model.vocab_size + num_custom_embeddings); + num_custom_embeddings_2++; + } + LOG_DEBUG("embedding '%s' applied, custom embeddings: %i (text model 2)", embd_name.c_str(), num_custom_embeddings_2); } - LOG_DEBUG("embedding '%s' applied, custom embeddings: %i", embd_name.c_str(), num_custom_embeddings); return true; } From 81556f3136d7881ef3b34ec2aa650cf760d52601 Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Sun, 9 Mar 2025 07:22:39 +0300 Subject: [PATCH 051/143] chore: silence some warnings about precision loss (#620) --- examples/cli/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index a2f1d1fa4..af6b2bbdb 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -126,9 +126,9 @@ struct SDParams { int upscale_repeats = 1; std::vector skip_layers = {7, 8, 9}; - float slg_scale = 0.; - float skip_layer_start = 0.01; - float skip_layer_end = 0.2; + float slg_scale = 0.f; + float skip_layer_start = 0.01f; + float skip_layer_end = 0.2f; }; void print_params(SDParams params) { From d7c7a3471217d919bdaf1922500e26b6c186155c Mon Sep 17 00:00:00 2001 From: idostyle Date: Sun, 9 Mar 2025 05:23:23 +0100 Subject: [PATCH 052/143] fix: ModelLoader::load_tensors duplicated check (#623) Introduced in 2b6ec97fe244d03c40aa8d70131d40bb086099b0 --- model.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/model.cpp b/model.cpp index f3c5ae576..24da39f6d 100644 --- a/model.cpp +++ b/model.cpp @@ -1929,9 +1929,6 @@ bool ModelLoader::load_tensors(std::map& tenso if (pair.first.find("cond_stage_model.transformer.text_model.encoder.layers.23") != std::string::npos) { continue; } - if (pair.first.find("alphas_cumprod") != std::string::npos) { - continue; - } if (pair.first.find("alphas_cumprod") != std::string::npos) { continue; From 655f8a51697aede33dc61c0215d3006ddbcffa14 Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Sun, 9 Mar 2025 07:26:41 +0300 Subject: [PATCH 053/143] fix: clang complains about needless braces (#618) --- gits_noise.inl | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/gits_noise.inl b/gits_noise.inl index fd4750267..7a10ff76f 100644 --- a/gits_noise.inl +++ b/gits_noise.inl @@ -329,21 +329,21 @@ const std::vector> GITS_NOISE_1_50 = { }; const std::vector>*> GITS_NOISE = { - { &GITS_NOISE_0_80 }, - { &GITS_NOISE_0_85 }, - { &GITS_NOISE_0_90 }, - { &GITS_NOISE_0_95 }, - { &GITS_NOISE_1_00 }, - { &GITS_NOISE_1_05 }, - { &GITS_NOISE_1_10 }, - { &GITS_NOISE_1_15 }, - { &GITS_NOISE_1_20 }, - { &GITS_NOISE_1_25 }, - { &GITS_NOISE_1_30 }, - { &GITS_NOISE_1_35 }, - { &GITS_NOISE_1_40 }, - { &GITS_NOISE_1_45 }, - { &GITS_NOISE_1_50 } + &GITS_NOISE_0_80, + &GITS_NOISE_0_85, + &GITS_NOISE_0_90, + &GITS_NOISE_0_95, + &GITS_NOISE_1_00, + &GITS_NOISE_1_05, + &GITS_NOISE_1_10, + &GITS_NOISE_1_15, + &GITS_NOISE_1_20, + &GITS_NOISE_1_25, + &GITS_NOISE_1_30, + &GITS_NOISE_1_35, + &GITS_NOISE_1_40, + &GITS_NOISE_1_45, + &GITS_NOISE_1_50 }; #endif // GITS_NOISE_INL From 10feacf031cccc19b7f1257048ec32b778a01dbf Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Sun, 9 Mar 2025 07:29:08 +0300 Subject: [PATCH 054/143] fix: correct img2img time (#616) --- stable-diffusion.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index a2d33bca2..e38a6101f 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -1806,7 +1806,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, size_t t2 = ggml_time_ms(); - LOG_INFO("img2img completed in %.2fs", (t1 - t0) * 1.0f / 1000); + LOG_INFO("img2img completed in %.2fs", (t2 - t0) * 1.0f / 1000); return result_images; } From 10c6501bd05a697e014f1bee3a84e5664290c489 Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Sun, 9 Mar 2025 07:30:10 +0300 Subject: [PATCH 055/143] fix missing argument in prototype of stbi_write_jpg (#613) --- thirdparty/stb_image_write.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thirdparty/stb_image_write.h b/thirdparty/stb_image_write.h index a9139bec0..55118853e 100644 --- a/thirdparty/stb_image_write.h +++ b/thirdparty/stb_image_write.h @@ -177,7 +177,7 @@ STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality, const char* parameters = NULL); #ifdef STBIW_WINDOWS_UTF8 STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); From c9b57351168f1176f3457dd0266e80c68397d4f5 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sun, 29 Jun 2025 04:08:53 +0200 Subject: [PATCH 056/143] feat: add FLUX.1 Kontext dev support (#707) * Kontext support * add edit mode --------- Co-authored-by: leejet --- diffusion_model.hpp | 6 +- examples/cli/main.cpp | 86 ++++++++++++++++++++++++-- flux.hpp | 115 ++++++++++++++++++++++++++-------- stable-diffusion.cpp | 140 +++++++++++++++++++++++++++++++++++++++++- stable-diffusion.h | 26 ++++++++ 5 files changed, 342 insertions(+), 31 deletions(-) diff --git a/diffusion_model.hpp b/diffusion_model.hpp index ee4d88f0c..94e9a2678 100644 --- a/diffusion_model.hpp +++ b/diffusion_model.hpp @@ -13,6 +13,7 @@ struct DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, @@ -68,6 +69,7 @@ struct UNetModel : public DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, @@ -118,6 +120,7 @@ struct MMDiTModel : public DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, @@ -169,13 +172,14 @@ struct FluxModel : public DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, struct ggml_tensor** output = NULL, struct ggml_context* output_ctx = NULL, std::vector skip_layers = std::vector()) { - return flux.compute(n_threads, x, timesteps, context, c_concat, y, guidance, output, output_ctx, skip_layers); + return flux.compute(n_threads, x, timesteps, context, c_concat, y, guidance, ref_latents, output, output_ctx, skip_layers); } }; diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index af6b2bbdb..466fe87c2 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -57,6 +57,7 @@ const char* modes_str[] = { "txt2img", "img2img", "img2vid", + "edit", "convert", }; @@ -64,6 +65,7 @@ enum SDMode { TXT2IMG, IMG2IMG, IMG2VID, + EDIT, CONVERT, MODE_COUNT }; @@ -89,6 +91,7 @@ struct SDParams { std::string input_path; std::string mask_path; std::string control_image_path; + std::vector ref_image_paths; std::string prompt; std::string negative_prompt; @@ -154,6 +157,10 @@ void print_params(SDParams params) { printf(" init_img: %s\n", params.input_path.c_str()); printf(" mask_img: %s\n", params.mask_path.c_str()); printf(" control_image: %s\n", params.control_image_path.c_str()); + printf(" ref_images_paths:\n"); + for (auto& path : params.ref_image_paths) { + printf(" %s\n", path.c_str()); + }; printf(" clip on cpu: %s\n", params.clip_on_cpu ? "true" : "false"); printf(" controlnet cpu: %s\n", params.control_net_cpu ? "true" : "false"); printf(" vae decoder on cpu:%s\n", params.vae_on_cpu ? "true" : "false"); @@ -208,6 +215,7 @@ void print_usage(int argc, const char* argv[]) { printf(" -i, --init-img [IMAGE] path to the input image, required by img2img\n"); printf(" --mask [MASK] path to the mask image, required by img2img with mask\n"); printf(" --control-image [IMAGE] path to image condition, control net\n"); + printf(" -r, --ref_image [PATH] reference image for Flux Kontext models (can be used multiple times) \n"); printf(" -o, --output OUTPUT path to write result image to (default: ./output.png)\n"); printf(" -p, --prompt [PROMPT] the prompt to render\n"); printf(" -n, --negative-prompt PROMPT the negative prompt (default: \"\")\n"); @@ -243,7 +251,7 @@ void print_usage(int argc, const char* argv[]) { printf(" This might crash if it is not supported by the backend.\n"); printf(" --control-net-cpu keep controlnet in cpu (for low vram)\n"); printf(" --canny apply canny preprocessor (edge detection)\n"); - printf(" --color Colors the logging tags according to level\n"); + printf(" --color colors the logging tags according to level\n"); printf(" -v, --verbose print extra info\n"); } @@ -629,6 +637,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.skip_layer_end = std::stof(argv[i]); + } else if (arg == "-r" || arg == "--ref-image") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.ref_image_paths.push_back(argv[i]); } else { fprintf(stderr, "error: unknown argument: %s\n", arg.c_str()); print_usage(argc, argv); @@ -657,7 +671,13 @@ void parse_args(int argc, const char** argv, SDParams& params) { } if ((params.mode == IMG2IMG || params.mode == IMG2VID) && params.input_path.length() == 0) { - fprintf(stderr, "error: when using the img2img mode, the following arguments are required: init-img\n"); + fprintf(stderr, "error: when using the img2img/img2vid mode, the following arguments are required: init-img\n"); + print_usage(argc, argv); + exit(1); + } + + if (params.mode == EDIT && params.ref_image_paths.size() == 0) { + fprintf(stderr, "error: when using the edit mode, the following arguments are required: ref-image\n"); print_usage(argc, argv); exit(1); } @@ -826,6 +846,7 @@ int main(int argc, const char* argv[]) { uint8_t* input_image_buffer = NULL; uint8_t* control_image_buffer = NULL; uint8_t* mask_image_buffer = NULL; + std::vector ref_images; if (params.mode == IMG2IMG || params.mode == IMG2VID) { vae_decode_only = false; @@ -877,6 +898,37 @@ int main(int argc, const char* argv[]) { free(input_image_buffer); input_image_buffer = resized_image_buffer; } + } else if (params.mode == EDIT) { + vae_decode_only = false; + for (auto& path : params.ref_image_paths) { + int c = 0; + int width = 0; + int height = 0; + uint8_t* image_buffer = stbi_load(path.c_str(), &width, &height, &c, 3); + if (image_buffer == NULL) { + fprintf(stderr, "load image from '%s' failed\n", path.c_str()); + return 1; + } + if (c < 3) { + fprintf(stderr, "the number of channels for the input image must be >= 3, but got %d channels\n", c); + free(image_buffer); + return 1; + } + if (width <= 0) { + fprintf(stderr, "error: the width of image must be greater than 0\n"); + free(image_buffer); + return 1; + } + if (height <= 0) { + fprintf(stderr, "error: the height of image must be greater than 0\n"); + free(image_buffer); + return 1; + } + ref_images.push_back({(uint32_t)width, + (uint32_t)height, + 3, + image_buffer}); + } } sd_ctx_t* sd_ctx = new_sd_ctx(params.model_path.c_str(), @@ -968,7 +1020,7 @@ int main(int argc, const char* argv[]) { params.slg_scale, params.skip_layer_start, params.skip_layer_end); - } else { + } else if (params.mode == IMG2IMG || params.mode == IMG2VID) { sd_image_t input_image = {(uint32_t)params.width, (uint32_t)params.height, 3, @@ -1038,6 +1090,32 @@ int main(int argc, const char* argv[]) { params.skip_layer_start, params.skip_layer_end); } + } else { // EDIT + results = edit(sd_ctx, + ref_images.data(), + ref_images.size(), + params.prompt.c_str(), + params.negative_prompt.c_str(), + params.clip_skip, + params.cfg_scale, + params.guidance, + params.eta, + params.width, + params.height, + params.sample_method, + params.sample_steps, + params.strength, + params.seed, + params.batch_count, + control_image, + params.control_strength, + params.style_ratio, + params.normalize_input, + params.skip_layers.data(), + params.skip_layers.size(), + params.slg_scale, + params.skip_layer_start, + params.skip_layer_end); } if (results == NULL) { @@ -1117,4 +1195,4 @@ int main(int argc, const char* argv[]) { free(input_image_buffer); return 0; -} +} \ No newline at end of file diff --git a/flux.hpp b/flux.hpp index 20ff41096..289e8554f 100644 --- a/flux.hpp +++ b/flux.hpp @@ -570,17 +570,22 @@ namespace Flux { } // Generate IDs for image patches and text - std::vector> gen_ids(int h, int w, int patch_size, int bs, int context_len) { + std::vector> gen_txt_ids(int bs, int context_len) { + return std::vector>(bs * context_len, std::vector(3, 0.0)); + } + + std::vector> gen_img_ids(int h, int w, int patch_size, int bs, int index = 0, int h_offset = 0, int w_offset = 0) { int h_len = (h + (patch_size / 2)) / patch_size; int w_len = (w + (patch_size / 2)) / patch_size; std::vector> img_ids(h_len * w_len, std::vector(3, 0.0)); - std::vector row_ids = linspace(0, h_len - 1, h_len); - std::vector col_ids = linspace(0, w_len - 1, w_len); + std::vector row_ids = linspace(h_offset, h_len - 1 + h_offset, h_len); + std::vector col_ids = linspace(w_offset, w_len - 1 + w_offset, w_len); for (int i = 0; i < h_len; ++i) { for (int j = 0; j < w_len; ++j) { + img_ids[i * w_len + j][0] = index; img_ids[i * w_len + j][1] = row_ids[i]; img_ids[i * w_len + j][2] = col_ids[j]; } @@ -592,24 +597,54 @@ namespace Flux { img_ids_repeated[i * img_ids.size() + j] = img_ids[j]; } } + return img_ids_repeated; + } - std::vector> txt_ids(bs * context_len, std::vector(3, 0.0)); - std::vector> ids(bs * (context_len + img_ids.size()), std::vector(3)); + std::vector> concat_ids(const std::vector>& a, + const std::vector>& b, + int bs) { + size_t a_len = a.size() / bs; + size_t b_len = b.size() / bs; + std::vector> ids(a.size() + b.size(), std::vector(3)); for (int i = 0; i < bs; ++i) { - for (int j = 0; j < context_len; ++j) { - ids[i * (context_len + img_ids.size()) + j] = txt_ids[j]; + for (int j = 0; j < a_len; ++j) { + ids[i * (a_len + b_len) + j] = a[i * a_len + j]; } - for (int j = 0; j < img_ids.size(); ++j) { - ids[i * (context_len + img_ids.size()) + context_len + j] = img_ids_repeated[i * img_ids.size() + j]; + for (int j = 0; j < b_len; ++j) { + ids[i * (a_len + b_len) + a_len + j] = b[i * b_len + j]; } } + return ids; + } + + std::vector> gen_ids(int h, int w, int patch_size, int bs, int context_len, std::vector ref_latents) { + auto txt_ids = gen_txt_ids(bs, context_len); + auto img_ids = gen_img_ids(h, w, patch_size, bs); + + auto ids = concat_ids(txt_ids, img_ids, bs); + uint64_t curr_h_offset = 0; + uint64_t curr_w_offset = 0; + for (ggml_tensor* ref : ref_latents) { + uint64_t h_offset = 0; + uint64_t w_offset = 0; + if (ref->ne[1] + curr_h_offset > ref->ne[0] + curr_w_offset) { + w_offset = curr_w_offset; + } else { + h_offset = curr_h_offset; + } + auto ref_ids = gen_img_ids(ref->ne[1], ref->ne[0], patch_size, bs, 1, h_offset, w_offset); + ids = concat_ids(ids, ref_ids, bs); + + curr_h_offset = std::max(curr_h_offset, ref->ne[1] + h_offset); + curr_w_offset = std::max(curr_w_offset, ref->ne[0] + w_offset); + } return ids; } // Generate positional embeddings - std::vector gen_pe(int h, int w, int patch_size, int bs, int context_len, int theta, const std::vector& axes_dim) { - std::vector> ids = gen_ids(h, w, patch_size, bs, context_len); + std::vector gen_pe(int h, int w, int patch_size, int bs, int context_len, std::vector ref_latents, int theta, const std::vector& axes_dim) { + std::vector> ids = gen_ids(h, w, patch_size, bs, context_len, ref_latents); std::vector> trans_ids = transpose(ids); size_t pos_len = ids.size(); int num_axes = axes_dim.size(); @@ -726,7 +761,7 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor* pe, - std::vector skip_layers = std::vector()) { + std::vector skip_layers = {}) { auto img_in = std::dynamic_pointer_cast(blocks["img_in"]); auto time_in = std::dynamic_pointer_cast(blocks["time_in"]); auto vector_in = std::dynamic_pointer_cast(blocks["vector_in"]); @@ -785,6 +820,21 @@ namespace Flux { return img; } + struct ggml_tensor* process_img(struct ggml_context* ctx, + struct ggml_tensor* x) { + + int64_t W = x->ne[0]; + int64_t H = x->ne[1]; + int64_t patch_size = 2; + int pad_h = (patch_size - H % patch_size) % patch_size; + int pad_w = (patch_size - W % patch_size) % patch_size; + x = ggml_pad(ctx, x, pad_w, pad_h, 0, 0); // [N, C, H + pad_h, W + pad_w] + + // img = rearrange(x, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=patch_size, pw=patch_size) + auto img = patchify(ctx, x, patch_size); // [N, h*w, C * patch_size * patch_size] + return img; + } + struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x, struct ggml_tensor* timestep, @@ -793,7 +843,8 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor* pe, - std::vector skip_layers = std::vector()) { + std::vector ref_latents = {}, + std::vector skip_layers = {}) { // Forward pass of DiT. // x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images) // timestep: (N,) tensor of diffusion timesteps @@ -812,25 +863,33 @@ namespace Flux { int64_t patch_size = 2; int pad_h = (patch_size - H % patch_size) % patch_size; int pad_w = (patch_size - W % patch_size) % patch_size; - x = ggml_pad(ctx, x, pad_w, pad_h, 0, 0); // [N, C, H + pad_h, W + pad_w] - // img = rearrange(x, "b c (h ph) (w pw) -> b (h w) (c ph pw)", ph=patch_size, pw=patch_size) - auto img = patchify(ctx, x, patch_size); // [N, h*w, C * patch_size * patch_size] + auto img = process_img(ctx, x); + uint64_t img_tokens = img->ne[1]; if (c_concat != NULL) { ggml_tensor* masked = ggml_view_4d(ctx, c_concat, c_concat->ne[0], c_concat->ne[1], C, 1, c_concat->nb[1], c_concat->nb[2], c_concat->nb[3], 0); ggml_tensor* mask = ggml_view_4d(ctx, c_concat, c_concat->ne[0], c_concat->ne[1], 8 * 8, 1, c_concat->nb[1], c_concat->nb[2], c_concat->nb[3], c_concat->nb[2] * C); - masked = ggml_pad(ctx, masked, pad_w, pad_h, 0, 0); - mask = ggml_pad(ctx, mask, pad_w, pad_h, 0, 0); - - masked = patchify(ctx, masked, patch_size); - mask = patchify(ctx, mask, patch_size); + masked = process_img(ctx, masked); + mask = process_img(ctx, mask); img = ggml_concat(ctx, img, ggml_concat(ctx, masked, mask, 0), 0); } - auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, skip_layers); // [N, h*w, C * patch_size * patch_size] + if (ref_latents.size() > 0) { + for (ggml_tensor* ref : ref_latents) { + ref = process_img(ctx, ref); + img = ggml_concat(ctx, img, ref, 1); + } + } + + auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, skip_layers); // [N, num_tokens, C * patch_size * patch_size] + if (out->ne[1] > img_tokens) { + out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); // [num_tokens, N, C * patch_size * patch_size] + out = ggml_view_3d(ctx, out, out->ne[0], out->ne[1], img_tokens, out->nb[1], out->nb[2], 0); + out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); // [N, h*w, C * patch_size * patch_size] + } // rearrange(out, "b (h w) (c ph pw) -> b c (h ph) (w pw)", h=h_len, w=w_len, ph=2, pw=2) out = unpatchify(ctx, out, (H + pad_h) / patch_size, (W + pad_w) / patch_size, patch_size); // [N, C, H + pad_h, W + pad_w] @@ -909,6 +968,7 @@ namespace Flux { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, + std::vector ref_latents = {}, std::vector skip_layers = std::vector()) { GGML_ASSERT(x->ne[3] == 1); struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, FLUX_GRAPH_SIZE, false); @@ -923,8 +983,11 @@ namespace Flux { if (flux_params.guidance_embed) { guidance = to_backend(guidance); } + for (int i = 0; i < ref_latents.size(); i++) { + ref_latents[i] = to_backend(ref_latents[i]); + } - pe_vec = flux.gen_pe(x->ne[1], x->ne[0], 2, x->ne[3], context->ne[1], flux_params.theta, flux_params.axes_dim); + pe_vec = flux.gen_pe(x->ne[1], x->ne[0], 2, x->ne[3], context->ne[1], ref_latents, flux_params.theta, flux_params.axes_dim); int pos_len = pe_vec.size() / flux_params.axes_dim_sum / 2; // LOG_DEBUG("pos_len %d", pos_len); auto pe = ggml_new_tensor_4d(compute_ctx, GGML_TYPE_F32, 2, 2, flux_params.axes_dim_sum / 2, pos_len); @@ -941,6 +1004,7 @@ namespace Flux { y, guidance, pe, + ref_latents, skip_layers); ggml_build_forward_expand(gf, out); @@ -955,6 +1019,7 @@ namespace Flux { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, + std::vector ref_latents = {}, struct ggml_tensor** output = NULL, struct ggml_context* output_ctx = NULL, std::vector skip_layers = std::vector()) { @@ -964,7 +1029,7 @@ namespace Flux { // y: [N, adm_in_channels] or [1, adm_in_channels] // guidance: [N, ] auto get_graph = [&]() -> struct ggml_cgraph* { - return build_graph(x, timesteps, context, c_concat, y, guidance, skip_layers); + return build_graph(x, timesteps, context, c_concat, y, guidance, ref_latents, skip_layers); }; GGMLRunner::compute(get_graph, n_threads, false, output, output_ctx); @@ -1004,7 +1069,7 @@ namespace Flux { struct ggml_tensor* out = NULL; int t0 = ggml_time_ms(); - compute(8, x, timesteps, context, NULL, y, guidance, &out, work_ctx); + compute(8, x, timesteps, context, NULL, y, guidance, {}, &out, work_ctx); int t1 = ggml_time_ms(); print_ggml_tensor(out); diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index e38a6101f..cf52c4f97 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -618,7 +618,7 @@ class StableDiffusionGGML { int64_t t0 = ggml_time_ms(); struct ggml_tensor* out = ggml_dup_tensor(work_ctx, x_t); - diffusion_model->compute(n_threads, x_t, timesteps, c, concat, NULL, NULL, -1, {}, 0.f, &out); + diffusion_model->compute(n_threads, x_t, timesteps, c, concat, NULL, NULL, {}, -1, {}, 0.f, &out); diffusion_model->free_compute_buffer(); double result = 0.f; @@ -800,6 +800,7 @@ class StableDiffusionGGML { const std::vector& sigmas, int start_merge_step, SDCondition id_cond, + std::vector ref_latents = {}, std::vector skip_layers = {}, float slg_scale = 0, float skip_layer_start = 0.01, @@ -887,6 +888,7 @@ class StableDiffusionGGML { cond.c_concat, cond.c_vector, guidance_tensor, + ref_latents, -1, controls, control_strength, @@ -899,6 +901,7 @@ class StableDiffusionGGML { cond.c_concat, id_cond.c_vector, guidance_tensor, + ref_latents, -1, controls, control_strength, @@ -919,6 +922,7 @@ class StableDiffusionGGML { uncond.c_concat, uncond.c_vector, guidance_tensor, + ref_latents, -1, controls, control_strength, @@ -939,6 +943,7 @@ class StableDiffusionGGML { cond.c_concat, cond.c_vector, guidance_tensor, + ref_latents, -1, controls, control_strength, @@ -1209,6 +1214,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, float style_ratio, bool normalize_input, std::string input_id_images_path, + std::vector ref_latents, std::vector skip_layers = {}, float slg_scale = 0, float skip_layer_start = 0.01, @@ -1466,6 +1472,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, sigmas, start_merge_step, id_cond, + ref_latents, skip_layers, slg_scale, skip_layer_start, @@ -1618,6 +1625,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, style_ratio, normalize_input, input_id_images_path_c_str, + {}, skip_layers_vec, slg_scale, skip_layer_start, @@ -1798,6 +1806,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, style_ratio, normalize_input, input_id_images_path_c_str, + {}, skip_layers_vec, slg_scale, skip_layer_start, @@ -1943,3 +1952,132 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, return result_images; } + + +sd_image_t* edit(sd_ctx_t* sd_ctx, + sd_image_t* ref_images, + int ref_images_count, + const char* prompt_c_str, + const char* negative_prompt_c_str, + int clip_skip, + float cfg_scale, + float guidance, + float eta, + int width, + int height, + sample_method_t sample_method, + int sample_steps, + float strength, + int64_t seed, + int batch_count, + const sd_image_t* control_cond, + float control_strength, + float style_ratio, + bool normalize_input, + int* skip_layers = NULL, + size_t skip_layers_count = 0, + float slg_scale = 0, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2) { + std::vector skip_layers_vec(skip_layers, skip_layers + skip_layers_count); + LOG_DEBUG("edit %dx%d", width, height); + if (sd_ctx == NULL) { + return NULL; + } + if (ref_images_count <= 0) { + LOG_ERROR("ref images count should > 0"); + return NULL; + } + + struct ggml_init_params params; + params.mem_size = static_cast(30 * 1024 * 1024); // 10 MB + params.mem_size += width * height * 3 * sizeof(float) * 3 * ref_images_count; + params.mem_size *= batch_count; + params.mem_buffer = NULL; + params.no_alloc = false; + // LOG_DEBUG("mem_size %u ", params.mem_size); + + struct ggml_context* work_ctx = ggml_init(params); + if (!work_ctx) { + LOG_ERROR("ggml_init() failed"); + return NULL; + } + + if (seed < 0) { + srand((int)time(NULL)); + seed = rand(); + } + sd_ctx->sd->rng->manual_seed(seed); + + int C = 4; + if (sd_version_is_sd3(sd_ctx->sd->version)) { + C = 16; + } else if (sd_version_is_flux(sd_ctx->sd->version)) { + C = 16; + } + int W = width / 8; + int H = height / 8; + ggml_tensor* init_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, 1); + if (sd_version_is_sd3(sd_ctx->sd->version)) { + ggml_set_f32(init_latent, 0.0609f); + } else if (sd_version_is_flux(sd_ctx->sd->version)) { + ggml_set_f32(init_latent, 0.1159f); + } else { + ggml_set_f32(init_latent, 0.f); + } + + size_t t0 = ggml_time_ms(); + + std::vector ref_latents; + for (int i = 0; i < ref_images_count; i++) { + ggml_tensor* img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, ref_images[i].width, ref_images[i].height, 3, 1); + sd_image_to_tensor(ref_images[i].data, img); + + ggml_tensor* latent = NULL; + if (!sd_ctx->sd->use_tiny_autoencoder) { + ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, img); + latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); + } else { + latent = sd_ctx->sd->encode_first_stage(work_ctx, img); + } + ref_latents.push_back(latent); + } + + size_t t1 = ggml_time_ms(); + LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); + + std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); + + sd_image_t* result_images = generate_image(sd_ctx, + work_ctx, + init_latent, + prompt_c_str, + negative_prompt_c_str, + clip_skip, + cfg_scale, + guidance, + eta, + width, + height, + sample_method, + sigmas, + seed, + batch_count, + control_cond, + control_strength, + style_ratio, + normalize_input, + "", + ref_latents, + skip_layers_vec, + slg_scale, + skip_layer_start, + skip_layer_end, + NULL); + + size_t t2 = ggml_time_ms(); + + LOG_INFO("edit completed in %.2fs", (t2 - t0) * 1.0f / 1000); + + return result_images; +} \ No newline at end of file diff --git a/stable-diffusion.h b/stable-diffusion.h index 52dcc848a..804dff71f 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -220,6 +220,32 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, float strength, int64_t seed); +SD_API sd_image_t* edit(sd_ctx_t* sd_ctx, + sd_image_t* ref_images, + int ref_images_count, + const char* prompt, + const char* negative_prompt, + int clip_skip, + float cfg_scale, + float guidance, + float eta, + int width, + int height, + enum sample_method_t sample_method, + int sample_steps, + float strength, + int64_t seed, + int batch_count, + const sd_image_t* control_cond, + float control_strength, + float style_strength, + bool normalize_input, + int* skip_layers, + size_t skip_layers_count, + float slg_scale, + float skip_layer_start, + float skip_layer_end); + typedef struct upscaler_ctx_t upscaler_ctx_t; SD_API upscaler_ctx_t* new_upscaler_ctx(const char* esrgan_path, From 884e23eeebe4eb4a2fde9fb6ab097e649c4f742d Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 29 Jun 2025 10:35:31 +0800 Subject: [PATCH 057/143] docs: add kontext doc --- README.md | 18 ++++++++----- assets/flux/kontext1_dev_output.png | Bin 0 -> 507741 bytes docs/kontext.md | 39 ++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 assets/flux/kontext1_dev_output.png create mode 100644 docs/kontext.md diff --git a/README.md b/README.md index 553fb7f8f..e30afe5b9 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Inference of Stable Diffusion and Flux in pure C/C++ - SD1.x, SD2.x, SDXL and [SD3/SD3.5](./docs/sd3.md) support - !!!The VAE in SDXL encounters NaN issues under FP16, but unfortunately, the ggml_conv_2d only operates under FP16. Hence, a parameter is needed to specify the VAE that has fixed the FP16 NaN issue. You can find it here: [SDXL VAE FP16 Fix](https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/blob/main/sdxl_vae.safetensors). - [Flux-dev/Flux-schnell Support](./docs/flux.md) - +- [FLUX.1-Kontext-dev](./docs/kontext.md) - [SD-Turbo](https://huggingface.co/stabilityai/sd-turbo) and [SDXL-Turbo](https://huggingface.co/stabilityai/sdxl-turbo) support - [PhotoMaker](https://github.com/TencentARC/PhotoMaker) support. - 16-bit, 32-bit float support @@ -220,7 +220,7 @@ arguments: -m, --model [MODEL] path to full model --diffusion-model path to the standalone diffusion model --clip_l path to the clip-l text encoder - --clip_g path to the clip-l text encoder + --clip_g path to the clip-g text encoder --t5xxl path to the the t5xxl text encoder --vae [VAE] path to vae --taesd [TAESD_PATH] path to taesd. Using Tiny AutoEncoder for fast decoding (low quality) @@ -231,26 +231,32 @@ arguments: --normalize-input normalize PHOTOMAKER input id images --upscale-model [ESRGAN_PATH] path to esrgan model. Upscale images after generate, just RealESRGAN_x4plus_anime_6B supported by now --upscale-repeats Run the ESRGAN upscaler this many times (default 1) - --type [TYPE] weight type (f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_k, q3_k, q4_k) + --type [TYPE] weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K) If not specified, the default is the type of the weight file --lora-model-dir [DIR] lora model directory -i, --init-img [IMAGE] path to the input image, required by img2img + --mask [MASK] path to the mask image, required by img2img with mask --control-image [IMAGE] path to image condition, control net + -r, --ref_image [PATH] reference image for Flux Kontext models (can be used multiple times) -o, --output OUTPUT path to write result image to (default: ./output.png) -p, --prompt [PROMPT] the prompt to render -n, --negative-prompt PROMPT the negative prompt (default: "") --cfg-scale SCALE unconditional guidance scale: (default: 7.0) + --guidance SCALE guidance scale for img2img (default: 3.5) + --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0) + 0 means disabled, a value of 2.5 is nice for sd3.5 medium + --eta SCALE eta in DDIM, only for DDIM and TCD: (default: 0) --skip-layers LAYERS Layers to skip for SLG steps: (default: [7,8,9]) --skip-layer-start START SLG enabling point: (default: 0.01) --skip-layer-end END SLG disabling point: (default: 0.2) - SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END]) + SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END]) --strength STRENGTH strength for noising/unnoising (default: 0.75) --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%) --control-strength STRENGTH strength to apply Control Net (default: 0.9) 1.0 corresponds to full destruction of information in init image -H, --height H image height, in pixel space (default: 512) -W, --width W image width, in pixel space (default: 512) - --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm} + --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm, ddim_trailing, tcd} sampling method (default: "euler_a") --steps STEPS number of sample steps (default: 20) --rng {std_default, cuda} RNG (default: cuda) @@ -267,7 +273,7 @@ arguments: This might crash if it is not supported by the backend. --control-net-cpu keep controlnet in cpu (for low vram) --canny apply canny preprocessor (edge detection) - --color Colors the logging tags according to level + --color colors the logging tags according to level -v, --verbose print extra info ``` diff --git a/assets/flux/kontext1_dev_output.png b/assets/flux/kontext1_dev_output.png new file mode 100644 index 0000000000000000000000000000000000000000..4fa5e38dde428d9819385a9d09e78621c4a9a665 GIT binary patch literal 507741 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

4$YgUe2(cR2GRclvtT zEDuCw0-+vX3tNe=l?4yrIca-5QegQp-|CAl~0zq9(kh}{=qw0rd)k{3Ffk#IldS=S+EznAIN=Paxlr}6d^0=Y z((KvFSp|rOzpyp?Vo>X-e!Ec5-k+5%sb2b{pa0z2Eij+&hVA6eO%2;gF#abk*LaAk z)~|fF1jfdO19#9pQyoi-gQrk2;`bcyc_*hoZ&#t6ld@pMl)WSy9Jy`c5PKG*SA!~6 z`1xBcFBj08I4+wSrci5bA0I`V#k(XP^|y@ih*XFaa}3OsD>;g0M7)EEgM?!$R-uTV z*Fq1q#i7YaCsj7r*MBu1SvSlt&bQ{Df?QtT^ob2wg6FS|_4-(f+s40A4?}f^qV`6( zE%&SmQt!QL$ZeJ55C*IanMl!?@-tTK79pB^%5S$VzrslRt?m0lBQ9A9IdHm>LZK;D z=iRpS%p=jMNy&PKUhi?S!?yZ)XusJa)RmQd8wL3=rUg<0kZANRBc*>ply%#63_Fpn zMNDb^BS+yF&g(^P4Z*BlaqJ|^mAf;NQ>f@;3rR^Wl-wy|o+&?55pk8jL1a>5xDUsv zN4nVWDYk^-^DqMWIcMb&u%aFUEFw!U>kvWbg3^QZ6^7}AJ9eSL^t(5p1BHmZlPrx& zjwk=tW0u!VlMVge%T>-pM@-UDa`{sHpiaRsVhLAWR>ovtRHp@Uq4Y~KG43@x+BlR` zgd5Z3#>vud=@raEb(EQ@a$Ze|$VfAZPN(!f+7UX@H%~{yv+V91`*~vyKBQn>UZTy*H z$Q!S74i)nUeSK8}Y3=j0Ne978X$h)oA54k9XCnAaAkjS}cU-c}#cpJw?O)o^e<5O~ zvb8zgFa%nKW{p$u4$N2$fo5xKE4(;+>ZC$D46PM^wPN@HtZEX)EVCkA@Vw?P+j4v` zr{wJXzd?NSeK@9D_@LtMv5^G}HBpTVtGa_7fRj-+`$BYF%UeYT{x4fSni?dKr>-%{vrYU7!@#bv|7v8QEP4ImiREkv!uu z&I&6#=1hA@t;c_ zEvf@eB0ei#FQOr{&Bec_62%L>Yz@v{VT8)BUi8;#9a>#O6Q!nLz+vTu#|aD4izS+@ z>Xmj@R4cGQMEts*-_Xs`9z>c4gIu;u()Z)V*NNU&GRe4;T?6%``2Iz9Es7$A2{ji7 zyEe7cpdSX%i6#C41LHN!%E_;>;!@RG$OI|Y^qHIIKF;E3zveXkLO>6oGh30hK_4tv zLdUGN;o@^$Z*N5HhT%~3YL|ingMGAsa9LVpZ1e&7_I$*wXsisP=~Fs4bZ|!m=s~1y zpq{tB^xeVSIzb-6KUrTZyjiIYu!!laUZe>(5;w2+wa_==?jQ-1lN=I4pL}NHsy-1c zzH2a6G$ZBPv{BuFqGXkixsNWP?jjxX(S;6xCsrYvCTSWBXmoBXXvzJEY)V#T!Hnbl zVa-6M`x!v=L?t%}^$rV{?s%=A(wyQ58qK>G19x5;v}nb7wy<$S*FKTN{;EOLw}cQ8 z_c$&nozQD0_>M{5FBdGr?qT=~Oc~GQqB!&|5R=U52bNU{+;}Lz zCpNCBsdXp8uSd~oKV0t()8k!SS6CUAG12?3)?or$2)4xpVR(9qYE1@CD4KrP?^E*X zkzC7TIZvCT=VTF>9tx<#!Ctchoj+Z?T&T;!>N7WejbeaOL&*X|oKb@OH=WH#gP{4~ zNYU^Sm@~4Do4s0m=A%_eEwE+1n%LWhNmvy0Sy>GsSzGsCjZH2XW6^sR@)lPXZ_YnbaGq#q1NPBVeKc@61G*K48o z@0#TZo6@b7jRtIzG7$iWT+-c5Uag|CHo12p)@B;~&F2ug$*#A}z`N~|<;XYeOYEH> z=sq?81p!Z{;b*ZS+qCb;%h8}L9%kG~2Z6WG`-$t;&cp!1-Ut#kMHw|V(jshxzS+5SP8 z+RMEKPfS3>l5gDESLJ_(*0W`Q#}VUeP|yTdz4Gx<`Ajb+n4fSi12jp6Q8@#6%kBlr z-PJB^tiQHZZNeFNY-EtyTZ_8Z@V0TKnK`Kf=y8rGjblY4O) zr1rW40l4o*an;aVe-Jfzjvj;W@smzFG*EHOStX$iUxksY0y*4yT6n0HVfaz`G9&P^ z9zO|Pv(!h6KFCE~sxIB?SsJ&n#~A!nM}U} z{8{BY3>;M8eqnLmhzse1|Ll2!0V4*Yf`1_=#V;AqjwB6N^|*(qA96ZcxP!Pg;ann~ zYl&QK)-g*onOhX28)XwERv|Pn9#;W%x4&6#X+D>qv3bjO1;^V7e}3Mlgs@?Bu%l$& z&WeK6XbCl=N_tUZZ*2P<6giPoLUX+ZvIfn-zj?;YW&1gLYjByy#!Nzcy3V28wO6@Z zl*?3Vbaq|fu9YncdATxVC+OG3j{d{_g-<6ceFOGumJxuKR!KO%XOd_;FcFek+4~B; z<+%93e{JH{AEGl2%kuL0JU(}aBXsK}KlH4qP!qaoV`2U-N5@4W2usva#r(L34Tj`< zP+;-Z`Bupyh52h!LH;^xQEyAn^|Y%wSVxb)i?c{GwWRwcXnwSojZ*8c%`&g3{82qK z&~-`;i9|fw=A?8q#wC$asU-B#VbSfFMQQ<8m(U(vvsNJ-{MH&+(l>KRe5?oQ^MSiD zz}4}kC1;M1P3BkGsBMN?Eu~C5~(= z>COv*q&?6{ZR$kJ4I zt=zTR8ONA$al8Rd0>(0Ttpt*QL@?VSv(rbVdogXuTaTwVBJ#w&il6xf9x5`=J4vXu zvxLGbFdv&6M2T6?X~iNH@{`7f<_8)+svDN^u`AkOk(<`x6-FEDI$D#}q%Z?J$HsY5 zl2LxaytDYK-*7D~*=ExW>OZ5uxf5z}01^2;Peq40|=5k6l0Tgg!{`q zy&-h9z9DEoWngSwKg!r(t{BEq8S#9I3_kCHO^Q|gRP!IGG-T0MVHuIU!q$Veob%5F zN>T3o1Pd;u2=taot?wB;;1;$u%MHImq+44;;XYH%Vk15=S;nKdf)>>nwGnS~DCKIF z_y`}`0dbDRAlzf+A-QnTD=L!)?7YK$<6A)A#I)h14)4Ko1}&ORrIEieZ&8E2oE58C z&^1CF0v3;VVYbL7XtllEuc_R4N|yN0kn2?^ZUbc$;u?#u8;YZ4O|!9FyXSl@Imgzf zH*Y#R@WtAg^KPj8bYSotH%em(uSWz%$<~i$cs)0$ZE3iFulTeofAXxj{sOfP;tgK=-`53XF2F7EbR23m{&gD#eq;u(t1Lpei z5K#e1nRlsx;T>02dcnp@ooWqfCL*PR{^=Kkl*Giu#gRq0BZRG0*md18KG!+q=OJ=HG`Jg;urw$8*wg_XT_=iO-i(3=E~{{NEI?=a zWG^SWmv`562PGvfL2LEmQd(=-%j<1voWaJIXD9nUPftwrJhWcw!&<6fV?Hs$rBY%v zPIAyu)M6k8@5$UfMV8jS|3v|@-MsJ!rYmM`0VZLBVy|q=)xIXy2eYsYy^G&H;+@X%90UB1Pj2&|!V#;LIHMNR$*gR00dPV%a}d=16%uw|?p z2{Q^QD2Y?apz~(8B2C{|o+@ty&FbVJ3?=n7S#oCFb!WJXr@7kI1>6*Krmmi^L3ETA zI2X4^q-YZDq*$(qIX?xS`nUv6Tt%NzV~tYQQECM`&yqh-JVZ))(1mZ*+E*YYa$f%M z0k9H*QB2Q`;F;ZN`T6<#IJ%ut_@@ipCuqUwBGKguBE;*343k_7)mUP19U@ASOAirK zt*j&oBIIlKCc^5Uo7P5rrfFGqi;okNvNCd}a0}Inq zWhQ^vff=U4B<2+eH6-YtCB?Pb$kz79S!izl>*J=JzvRC$LDdSy${p9u zeUGn6287sA>fyu7bg15>+)v=P?V^*&WYoan}NV<<^{ahqDCs*zNM&pBsU9^P;!FrA!2OM5@u&4H!gsO+lj6 zYcMt^j#2Rl$k_*;zs|)qa+OV?1=%dsa7+L3!FKdSKcZCzbNtZxK8l&#hHH_-X7q#rfWsCFMug_0RxG zv87tv-44W9xq|aF&|{|^-H_Pri77-K%Hb(fsuDQoEjeN5m9B4w-TeTteogdv6ryeU z0>!x7Z|I94?bP!^lw`m&H+l-LtioMqe>-pmSDmc%;UCY!ldFM$@ST5)WLOmx!7<*9 zljvD7xlMB0V#H-KO11a?WaEAq)_9_g@O^?Lb|(CZ2Aap zSW-hY_1EP8K;wqapY~kNpc}~vQHmu75oe?Cp^JQFhR-Mrlc%;&<`EBE=crZlE&WYg z!wFj1mHn83gOdL?vfd>hRs!t{t!*JcMWb1_aRLKSCx|XnU{|S;=UBVkGo42(mlf5x zwn~vUW=#DOzW89j@-MuCs$G&#QaF%(8SQf!9o&Hqx`%4p3zsf%!Z7Be&Pn^JucVj< zQur^K<)v;&WWN4w<$DFwJ|aIk31&{P`6`&=bJ(Z-+_Wu#_VEzTwZAl|VX=@NCWBO^ z6%~geY&_IP4C*JWTJC!9LV$*2XWgL-oNMu7=M_U`>}?8q43xCrgt;i`utFH(Im;u% zqR9vG+C8!*KOnu?ni1*t^rxfozF39Yt7rd>l3;|WVV48-waMF+Hc3*xbta?FHBLdZ_?yDp^S(wpXKe3VS+Cc|7@~z?fMRv?z8t`U5VnT5xo)mIjUm&KdN5` zbS~N#N>Xzr9MZ-5}>I&@P|r@Ui>?+x|@Y@62|uEJuftS^g`T&|qr^5|Eg7D=9(U z#&aIy+?JA-iYuUggPvDsP&H(4uRWRbgO#(D*Iy>cej!$z!=wsW!*viq4W`0Lfyw+Q zTlrjbkLJmWtXx-`adUMtew(Eprexg_a$Hvrr0 z;O$;?rYiBg8q13l9^jiSO{?+?-Kb@MmhPtqR?%?-r-qJR6a6y)9Z%S}ks~-8)-#{U zg8_OU%&~*<%^pS`o>Qnmw9|j&qGOtMlY}iEB!q~>(ywGeuW{?I?oSDPunyN z?%js&6kJgHu7-Ms%)Z@*Y0*TQ@E79IzFgXRpONyJEx0Lj-9qW3cwZ-5*c}*?R_&8xf zA)I73O8!tN^fj>_BGuwKQTU26O!nW$g_-YjtO3>;xCAgLLKTJIE?&E~Efc;?&ggremhZDu+jGg1h)R`NlHt-w8V&C)T~=%l3out=>c&ACDq z?d8rt|B2l!M;ZEJVJvF7<}Hv7u~~V^w>FCV4z=-HxAE_2?KcF?-*J{{HCr1q52+RV zYZgGcO3o*-&JV$WZ0_%^5YGvkafJe~+qm2-EohStSMo?Lq;$+v20Y&o6o~K!Rx?c% z)|nWx^1Ip1!cQ<_(l2RNy&IAn zxu=q@3lp~P+B-WlJ=pDBgV@7Cg+J_tIfC#R;J6Z=_!`T&TAaz-;=M&@h`^Z5BvhgC ztZ1E~A-ed41#|&nq;qT*(lV|NIJBPf@KryLbv!~k@D*i}*@L1a$4~xx_u^E&bJ9Vt zRmcU^xRASPGnjwr+-5UWW%knCKeFT};uKRgXyZ@QpnezF)#RIvH0{!qx3@@7i^b8_ z_{G-mDyrMMI{Gx~NSY13a&jInLER_K=UhU$o`F)d4eh@`d2s1!8Qaq(Sqo_YB(9mI z+Lj~ymjL|~OjUFQrvRg6oTT>uMU`b3ze5}n(s6Z8Qst0Q0|~h1#$L8H=N7VFUoCi) zh1wp-F*8fE~j!sZ6{lr=8tSe-8(#ZLS3W`?}j_L)@Vpyhv|ehP1CGP zJInP))!p5MWZ&dI0-Hs-xnar4AKB&i72J6F`2<@;84{y;<0?ksj0e*cWt*q@ zK^qw4=i^38LhBQn%IbX`!H!+ZCG-nhCl@zUk2!!ku&^RORhU~Y8n}w|F$DK1p3Ey z0@J$2X0!_r^t3B*40QAQIg_~?T7Q;A66AiOYz{MW$P)?V$XAz4IrZMn>Mav5fziCtF&8$C|#LLL6?1ve}Wri4vNfwV?TK zx!&4ioo`-`IIu2He{9N$>3NPFy@hg^MMZH`ckO&7T_SYHx8VZe*W|;Vs7*2%rwQe1 zk_cn&D`C|?&gBFp;0DymwF`aKt=+UDBxll;{A6`r8$8Od`QmvW^bD)wxTjIw7sHcHR^y_zRj$m| zJ$2QUQy}1?AGRW4(g_AVvj@&anXy_L<;*HHN>AsZoF4L~6!&AWrpM{V>-iC?;c~d4 zM78uxjnK>JW^=*4P?yvIoK=XykwXO!Vc1=&FuBG$++f;CEC#-MqjAH4s3=K^QvNzTwP3>$8uja=O*m8&#y5= zE6<16ZDc<80Q0I!v^}#C?8HXkdK&B6-JiBSpo%9;q+8lIeBxcQD z`ujT4bIszM-Qrdgv415y$hiiRHPR#oOr66zB|WwM%3|;-ft&x)D^nhARWF^IyeI@H zW?=Mbs@FuYi7mMgOuW|wi?K89pXA4dBwq_&^ZSdrpLPa zH|0>*vnQoZJ_s8i6y?eck^hvv^l#owP$^e@7Uf?Bon=G*GzXLXbm2-tgk*fC>y%Y6 zkS~W)&wljS8f|qcUrCugIXC-m== z$bC)}-@tVTE>Z&No!AcQ_4M-gIcRg@cJY^fGSvkVS@-O;hE$$QGF3EMlG(bd9XNelGf%N9ZK9> zLK5Iw*a7z?RDWht*GD*XJmqt!xqh0v4%#;T0_ywk6pV|Z`F%J|Wo>Rkhn&T^x(t;K z*0!!%K5*SDUe;?{`Z-U5M}W7j+nde$r*S&YB7_99%JZ^>8o8pPnxs3zb;L%mG-NH$ zK`H2?@MgK12HV2vDx%!#y*STbNM^3PUG83&hC%4-bR+x&4^2d3e8@t0TJhA9JBHS* z;Zc5;&!Lx@L`T=v5Vx3;1SgSrka?c|Ws2ij8LuJ}f4>_4;Opu6rGb1(i4eg}Y{go> z?+ag;&FrH@@6RE`$vMyIu*9$T!ZP50l)?={mQ!bdOo|!r!d!nhHN8%qX*)jnbpqNo zkLU(H!nmK&W>>SSjI>P_pLO(J=X_6v?0(7~f|SHxzxw(H7F*v=LCh{7uuT_SO7cNt&`SSI2R0b5qM36R7mAQ9~2f`M<1@26b`iBy=kSav>X*w^D+M z6oe|4d7*&*RR?WCE94#ub$u;m?;+asN8M4fjuGkubj7yp3HrPp^JH4MpWwdO2&Q!F zVty2_G}8Yo`w=f{c3Jr{>7)<%?A^I@a!9$J@W~z-)WU*Kmv_U-0v@nSdpFsZ$HiQ=40W_A82N2$DD_%P++odSW1otX(3eRQBBgxXufPAnPw zY9-z^0l5{Hl+3n|bzS+_j@16+5%pNBdL{Qzh7o3tK0{$%AL^Kxo#}#qNKZRq(lRpw_ocV@*1qJN0 zGE9(>e9)UkqENFSBPH#kdv3nA<3p1D5_#z7nn-aj|M`{iu2Yp%XJJP}-D{v|H)hNQ zA3WSZatW3HetJJ};tYKr{lO7$S%i*y0*mz{l1c3x+f~BjfSIeNfo?oY3beF{Y?ob0 ztNXm#KFnP!Lv%x{T;X{J2emdz-m0O_Ny86xMRZVCjMrBL?C1F77`vtT-P+>iYHQOP zZPq`Zj?0zA!%|a1+gJ%PJj%L3L*@EI3KGtsXXk7X#&s&)D+tYWER!TAD z@fsHn>IZBj*rqAjQYnA_0K0jB_QYYhkh@2vV6srQ6on;kvF1f4a=-Gp+v31A-wHWf z5q{DGcCU*xar$r5Gg#d4nsa-XtRJ{enw~I`+{mpR9j$PNdYq_&y^^_}L*i`=F@ziu z-{87=p!1tPX@WRhL?+TMK0)dq|~b{>>#Er=@fs1&@ON-kc3-+Gf{NG{sy zdO+jDw+M85BpAWDH{g2uUoTriBJYpppq!&#V{9}NUQSJfuvsieHt(FMl0FFT{*G>H z>3NlK=YDD8=o~@ZW&uXCwQuUzN}6V=+)bvy*TF{*ft;4bJGV*`C6JP(cylYPfk5cr zz_ovO?m}kJ(Pj63{)slUFmA`KJxg$GkPn)IXQYoev0ZAz@aL8g|1B?`3BNkeZCTuH zWf@$p5SOP&LkPu#w<}#9KLm3mp(wpByfYc?xI*8ka&Ee#B!}wqxt`5kY~_&8Pt>xPu$D@Uu+Xs3(1G88p7!EQFWf=x zIUB#I#d5O7BQPR^kI07ACq5F0u}heq2dR{t>!t6r4}IluThKLI(T*8t9i=c-_8}Sm zz7II&gqm0nQ-U?IMJsUa$xHZ>V@MVBnCm%94;G#nw9G~ZQ_Ord3~W*m@uR#i^Exlw z+{1%)QSEyQ=U0#iD-~-z$`*Oc=`8~Wj(Qey9J%uUA)UwnOS`PV{E$UJ7PmzC0m_XZfDL-Nn4%QJ3l$Q8{|?hueUXY++D@wtnH@Ajc{ zIpunFC(uKTAm$?8ewEzX_z^0zRF8Q9V9KcmK&acNxzA2;)V`p0v(Zt1IYc}?`vnYh z4TowGQONw|`3bnBtU{>iW(m zRQC|2m>?qun`53&O;ROmw0wJT!P^Pm%f0!Z^(#h0Y7_7Zhk&n7nF0YU!#QjGHo2JI zJW<)p`PdHGjV5DR+}nCv%*scQ4I9o#PO=L}KXuP2B z26Fzg7G)QclRwq=vh`e^SKif!Wk0$)psurfIyYeEk?cM4O8p|TqKvf7Bu^jBQfaa! za*z_KxD?lpws~k{2U_7HF;*_9FQNo=4J5>>eS^9Va~5eXHQ4JL<-1q?8}8^}^Bbt0 z0kypE>7*d%pe5)K->JZ*=8W{;=R*t{2OD=%RoR51K20(Huf!XwE_8_PS+&ZPc6JIQ z+^ghxm0Xd_JvyguuK;Rgz<4c#MdAUHEhlx}bsimb2dT2HlOcK2?3JE5MX2k81}PN# zO1t_gmnyGR^;VL=SlzJdUB)GD-D@l>7vbGauc}D}%)n~2v-?}QSbw)h*RAAoVqN!9mmpDSzhWT)Z~r-Z5k`E+!b>T~%FB^_h&5Lbu}ZnUXC zmk`Zeq&g>|4}7s%N+b8XBCCRPTYcpW$;hQE?tR_*-RZA=5xCSh<>Iuu%bFE2t_lhs z#^ox_*91M7#t4x;=DfX!ucpE(RNWQm?r{(1X3!K*e-9jVlco8K(lL$A>6Z%Vdb~I) zBN)BK%`i>r%fsX$;C?diyr(p6<_bSWPFawP?=7!y27;Y%f!8QO+rS`xL;JgiHt22L zJG}WC*%G+#Kh)v%4>|3l;pkp0y2f_SYUoXN2-Lvh-jvjGzE(X#TMb5r;;(7VQXTc* zVJgs^BmbUJU2Gmta@07Wg9eicQpWMYY7%^vfwIaD{+=jF0-71IFdKphMjX8>pj#`v zp^N&Q`_AGZ8-X5>YQYS^3A;!`7L2`$c|SQn$l?T0z?IXP#7~4QVqZCP<|k;8{Jvg< zpEKBpzAkSJwJy$2TZtdoD{T7$*VckBappVbs;f1f0&XTrcR%QAtZ)1)H5n3VF!7W) z$dbubV5*v}Q#NECMr?gEaBqoUQg>|6*--rTROy6?A#>4Axr;3+vXOjW=lc_Dsf^yX z2HkY0sw!^$2NLRufRol5iRa)BU^oFZdjs=V=X=BSOjT~?KKAiPk6&i30QBln%5UR?s zb}${jYlu;~{bMczU|m68cLl^gX@r8Yf+HN{phANXsE@MgZp;>K_u}1UlHb z_Hh|-ekDRi4f2xr9z+sUGSDREkWL7~r3N6tiLh%>0?fOS^T>WqZ{ z4X>9N8N?(8?@>K{RC9R5;Q^BBO-;+rM8vx%*N9j4hQz2nt)#&&XZ|K%yr>(;un-MH zL4?3Y(yF1l)|-goqp8rf8o0EP^Hco7r|y(T z&wBVN_6=-Yv&t7XGnV34?{*JY=G`V|wobDz!X`Qw^#EOj*ck0q@Wb=BhaQ%6L(IiUYP_TN;U!yMP{b#W7pX1^rHP z2L4%ZP#mRul%;e3ax^qGLd{)3MD@$QE< z6yzI(VmBWXsWi&PvaPM%cHeGTyTX^fl=cmrL4zzuL|3BzB}BwWvx){CrYBBKxB!`9 zdSYi?2M3AU5o3`O#E;Y%!pKENij~HY?D~XHYDa7>N9_?j_qM(caoDJ8Xs@&O0dwM) z@S|p&1##A4^Q;eU%h{Y~jn>BzD*r#JgP}y|%lr~HPwr6#v@S<1fyJkRr(d`uBd0q% z5%Rvmck=~e=K%0vBn%)w=lz=leTGpntAO(ss28|e*F%9a-kVZ-lN;yDO&KctJD9T? z`);}26)Y{8 zTlFS zKB_Gq7=eD*UU~IBmO;t7Jh1TO5M*ak_2k4H7DIJTARTTdo;#P`()tpAWwzuMFQxzp zwrwKSI2L7GEGfy(q!f5_m1gHZnuzza|F`j%AtX#G?#R;O%z`vtd}#vitv$WR384;g zR&j69%1z`(n^AV%v$3>V;90yNBi*xLPiGBF_Vdf-d(gwTa4ijJn&n`r0*ku0ZJ45R zd`t?Uhmtop)>j1_uOt&-Z#F~`tAaVn`vr^hVAT$BohJGzS zFT{{SCRo?EMk|7O&ooNSQZ)MZAc2^bT*^;t{YR!%y3K&)LuiGq@FnoewvNIK2PVGq zvg4Q1o+>NdLY3)~Efx4LRoZTppaNgpB%zK~|1G(IYH=4WR|rO~xAq>#9wn*LWG}3&FjAU%MpDb&5 zlWlNB0yFo*?jAd3?jr%0Ze}7#_ce|I|!~X%piiku2oC}If2vC1fKaXhag;|G!(wZcMb4#ic9{@ zrg586lBVpU#{gwBGuCkxNurY27HMnjhlmI0`8b~c3p6Do45uq=uX{5y-K4G%qwuD; zmNU2r?!D%`m1J(tEdd>C-_^BF$)+8Ui*7EVD~Fv05#thNVGB|O7$1g`HedpC7Zy7P zeW;kZWOl*=f}YsCNDPbsgZt#3CoD~{#b3IS9g!_p*li#=-_ZWLRen@k8itBN&`lTT;Sh25b7*rq zQiRPQPJubm;{B1o`Wfo#N)I1C`tW7Dy0S8E{1NUNj;zxP_Vh}I|L322Rm*N_XrJmE z*&Vw1&L*Fb=(`6{QOPkqFNnnjyFdCy?T?6;{`)wfeTqxkXJ>~%@~nbiVtcf$%q<$) zTad^u#XALB@!Ffe!@ZNc85`gNe$@gT&b&mr^pm_zh|L?prYkC`Ztwq$xsm|wTAwrC zi)6i(1>5#xuKlKXpEO4(Fqj1{GJE|nH#~dWJhlhSFE1>*+ABOZ^X+jIWMC|o-z^$8 zWV?RVuujTM6s=S=WF62HC}OEZg|~eE2eqnyB*(#N%BMswc>`kFraDDo?$zlS1A|r` zo`r;#QE?5{G&^*jR1{k3RG{Q%Z$p1=}^z(})KY15Ww(nEI?Hlij-zURz zV`4O^E#3k34_C^R*hCRk(H?Fc$qAwA5ReoPe*M3SrwWEl`}*{C4K<^~~d5(=J~` zRwVl_*1C>Zb01egp@uOc*BmGhNk{Q@StWB5Ay7oW*p5VBZDc)&{(4NnJGl~7N$JB_ z7)X&h)lZY?t!oWgT~16MT5u1j$Vr5;+pYqq)IpPFPTW5*D%ND_T}wU2`NG`T)k^57 z5&u3rk&RcAXLC)OmKIuGzc#UW7kV(7IsS8_p19>(wp+HD8tnnqv9)K5<^Jjs7%JS->Bi4*o0(mW>KcoowAeA@BbD=?=7yl*T8 zi^ju4rT=sTdXwbWGLE4Y?gEE4F@E5J6&mYbznfO?teu-yW{oQ`KX&)_LK$R6_UvxX z40RBWt-lde;|x1;w@;1S&aG*FiTBZv$ruY$!Ja`ImI?W(nmg^$6TZK~Bd(~aOC^0^ zE8JVZ)a^5l6kv~Tks}TdM-OQq1UGRInEHead=3c17CUmRYG2jBGTHidk{dlGA>5;= znqRuCK)l4h^l{U9)AI6jUS8;?S245%xvT@I1E$*#e4`!K3E|$Ci$PtX_P&xXOv^^P z)GK?W_#Z1VqutivGqP*}J8A@kVx^6;X?H@R$9h8qw;v03Mzh(tFO@l4DvWB%ju&bFj|E0a>F?=94=-Bk!du3A#198DR6Niq<*b+|E z2H+Gjj271Nm7>MmtB{M1Bu-!4rwDBDZnoXk&|R+rR!ipl%ippQk>Ec%PXuFp<^X~?(#qEuJ72N(*EG*-~l6J6rBe~;S#Q@y^BJ3QQOpn1Jd;r&-D4kw1`!Xd31K43 zl`z}evA>u-A0Q(hUFr|TJ>?g2c*aZKv+_4C5XQoH1$b zn4JBJ7wb7+388IZ2|4~XZ0V&mPKp_1nWN&?jpTRqa$g%n!|Frf6YMtr%M&C%b}cls8M<1uCt$8I zylht-cW@R7n2pyptjnR*Ud-L`MC1@Qzc~B3-^9W4I{Vnf8pCzU(Rp~`%F#v2l@NCA zSt-hXur=+}vKu#`=f9Ep{{uv38(J=Yj4QoKNFa>{4-Xrt4eYC0>oUXQv za^zv%F@(}EDy3Mu^HYrfN3JDUjOk|w) z(V53X@&9prW@m59+8n#ibyO5NE0pXyk}l~IC9MdR*zT=lXB-iVQb{RW=~lKk8bZsElv(@DNmzi;45`mY1R(xC#- zz#%d2G2q59b@DS6MI={FYdM@5P+;aoy=EE#6h0keA6?maZv%F3jlRup4a^*E^(dVf z{Hq~WrWpJPkS#`k47WBAE@z-P4Cx392n6jmfOq?Uc7VqR|f{Nd4yCiNTEXS+xJ(7HWl&*H62BMxGrskGCNz_p6epM zvp{_JOwuueV)tvJYsH^8tb9~sN1pnDBiC2H&hkQoMrd2t`acr!MfCwLpC}} zi{;a)*h`}XQ<=)X@!j>ZGGvIRyt~nEE=D(ykExe0@fq!U+z=m$Rl~ooXtP}`#fraOToKo2(T0snS66)c+Zp;DJw3%kc|JKC@(7d(B1PrpSx*kVJ6D>_!js0meP3i&P!slptex9 z1Yj);1=46_?>JCRCFZxyA1BiNEKKr*AWC_+ENgHJ~Sd2;{E=>i;+= z+GUgP`fj1tYoyT`P~90~!4I+j0w()u`Fnv!xSdN<}7U5x|6oY*F$J)DB63tx%IqqG6xR21mR#%8+U|AU8=h zHZjPby1)^NTIV@ffUbgE72qCcaZ9^E($9A&@*LCOv%i`jprfVG8Zxq0Bger=@xDMU z?5fgMxKQd@MOxPbEh>hI7WuaJreXnBp)`QM{s+^1NiO=6(B3I(6+Wq<=vFo1IC^_~ zFk&+;I0&i!c+@s0?UR>(?ni++D!mLj`mFn5!=O$I#8#cFVqzqu*qgQl|I zC>_GH;aiu|Jmfvd8b~D>iIl@WA{3p zYm>4}niaI(BkG2?78BiLxA&v%2PJ1E@YDoeFS9!=-0RdIG$((5*avLWCg=2)> z(u|^6+MM#|PfD(RqdRM+KDvc;Sr`r*vL4gdYave4EK7LsX`@UGqGRpCOqM*cQvj`c z2-Y{y30e(jl@4s86&wZKUdc1SzA@5lE?r=q%`x`~7Y;CA&UMgmw@aRP?bw{1A%>Hd~=`BEjw&ajD5A)RIi<3TeH>!=G5A$k51Qo=}v0lkv_1G&j!bj6E zd%M~DCT!-EsTz0hs*@~Fo@jf2y#e*q6WQmTPSQDejw${~jyOO_-4|daJ^er|l1T3# zUX3Fpv?maD-j3%b>=yyZYKO+@+*0r_@nb=x38koR0#na6Yc{3H*V z?%)J8q=NN&P*kSp>gQ$n;W6-OCU;ll42NAv#w+ukBkVm+)4GLguq9@V<{z=OvvKrs zf^v|VfUCfuT=)p#<$_E+HA9lMzH~6YTTn}Hon=Ul{at|_buliDX(jq&w*b>kQ0cFq zH2H=KYH7-m)926k(74x6Am6K_vJlIu=p$$G6&?8jt$DV`$BGMY(CrET_RBLF7xaQ6 z!8os|u85O*T&&|K@yZkjPqY*HQS@p*x;G6yw5xL=C86x_HCuaj5jA=A0MgI;qYLJ~ z+r1M$z7Ff!tk3Nj9XhHJbV)t-s=w1&e4FenK7HbH5wEUc1?tZhyXjlG+?JUsXUCgj ziht`&herxL1_yVOia%}%8=dv0p7m!TNKmHc;!oh7VS>gGn)ihFLSyGWy4WdwV8D42 zr=;6s7J)BBbAtp*a0J*TrN)j;LLuBLlHp#^ zWiC8tp4gCn(oLPzg(96D54iS7QU>}Z*bwXuzQ-ce? zg=V_LF!#db&#^0idjbUs;#4m01-cIuA0V#A$n+V(aFU1Y?Iu=s#e~}PkqLBrb>l!p zw9SMLQZY6LvBz z3e$MKBU7F@$+sP;crdfZFq+nu|QAPX08=+s`!gOur?`Ax^_BiLYrf9ckysy|XF?)=5M~}xV z{}25812%kZo7p%(Ulm`GZtYuRO@`W~-O-<<)HDt&?I6quH1^t@#A(?8vtq(${6P24 zcQur)wJmj6IQxh>k<2~no zo8Z#&xko*dj&nkfdT+B1I_0#P)jmmEG{sWE0s5JlD)x@m4lfDS82wUj>Zs<7giAY^ zeq!%Khj8-~|JWrFDF{PSrXy}4O8r(E(VJ!|b-Sf+w6mb5k6;r8%!tEYdSJV7fdJWk zPc}+AGhgai_qFrkY=dAo;fB{i%>wBdeIDJ=Rizr%6hCB$y$vjty<>Q$K)4|h?(xMZ zCk9R@o#!gt0EL@|Ye0QPJru$x{pn4NmD29SO;nSOXpo%-Wy?<~77IYiKwhA}a_m1e z+}SvpGN*_f~OfrWXgx%QyTZLs=)tJ{;$ZiwX=9?=nT7J-~>Lu}DRRa2b5|M#+j z7j;YM1L{#)%vsV3VjA`K4b;LzYig@)-Za!&+uIdhojQ5^{9>MB)-X}Aa5BbB(o2ac zra$I3rb4|}b|xQy=WvyoZ&#qcww4-PtE@AnA95eBKqhC;ZPYf<5b(2#N_54MJc-+& zhNv9J`?&s};-m)Bgt3XvrbXDJaovp>2m-Sk;5wh88 z?(FRYW0Sa`6(?CO_9%sh&%ht2%y$-hk4DAX^%|{*+aXkufRv{34ND{=g>)0)ko;y}A$9WF(oO_j;X2GYqF zBcmK~y@&PO;cxGw&Ntmv|I=!oBpoLV7kB4v0plvETH1~dUJa*7t%81Ti_>Ou?p`6O zMAl~CJ@-J+#3(T`|Bu|;hC}v+sS!+Zl4%iZRVZ@5Rp9rTG7b0MmMYCA^#~&D`TvyR z#BbUi_&<%r=Q%Ok_*L!fwap|tfnwjV=)ZDc-z%x4PVb-z&AQHOnMjmezf_faYFVSP z)=dJ~YII(9p(@j%K)56!?HDVwrqaCTDt@Wc+{bvf8)eM^dzEpLi6+DF)WH*A$qtIs zNF^M7>Sg-D8;7%ZY40?&j^0BY7Hn_u%%`aZ%C#Tx^0#8YI>R-$JiR&ug||Rp#tF-v*+~Dwv}lgC`Xt0 zjXEb*F!%K7MfaWo=S7kk_I~q_6uK*kcWH+Ow&ZBpxi4RcRK^J%ZAxo9-Bx8@osTrt zI^TD6xaa&}ko5RB&~#Y^nViJA&9?|5kczE%r)Ce{Uc@e&j2a&$D$@7+1mbPE=a<5{ zd{_2F=lkkSvhPkBM#?_{i??cpnqmWs8}G@aYEoA3n5MfGOY)_TjTxTBQRk}&cL9m( z;q`-Ds(TL1cSN?bg5Cn3(NI(%Vi#OEZKC+im)t_KWpuGxyZJ*@6!^^%2yw4x8M3Lp zPNR@5_XQ4aF~E**Zp^##20cBU7-Ai4V3dANPvyJbAmztU?d&KQ;cAxQ>e1?|*Hx8l z+fT_UuaZAMaZ|%g;tBviBfBc#?_soh%iX)2;(?1oM$0s+F*IRSTS1H2wC#pG`U|ll?XoWqp0fv#ANC+z5_tuKsv` z+npOl-F++UpPW_qi{aiulGah^afc(a=tVQ`JiufETrV(6i-_wHvXGg~7I9s@Y7v~1 zXQJ>wy1_qK%Qd8-zOv#CDMYMMI?%(Fx8Z|5*NMy?(Kv~lz*!$A+P|)AJU&yu@u(U4 zF$5)qbQ~X_;+4(Hp1b(lC?m~a#77BbgL(JxZW1^ZcTt_$%K?dPu0Nmm_SaO%6z zUo*`bU}l3<{to=}2bU%FnCEGK7y}7R68p{xURy7;t^mK(V_;xD1a#L3YW`VUD&_+E z`)uVeFMv~O8`m?w=x-4TS=&7`!hC~Z0>nHkJ4e^x(uL{bNsVGH8OXQ~bvSlkb4vTh zN(+cg{$x$KzRq&K`$Q%`E;TTZ4HE(DB#oocZQ4Ea^S#1Jefhc*b)nQ7^UW77aR0&C z;_zXWUkYS`Bd)+IBDnC%ws|$?MwbJwI~{~Opr#0x_`E0+Z$0iK^Qz}ImLPaPqHx)8SMG_;|YMnQZKXzbcJ)%RaBE}qwHpP+P()!_%pm&0YBRd z|82sTwc^A9bq`WXEPZ=GsM86IC$Ed6&|LT;bQEvp@+Gn$g}81;Z%Cd z!#5hnHniTj(T4I`YK%q(vtL8QAx)zn%pG5vFfq+ys`B5E4YvI0=0CEfjaOfd`5-15 z!5;d)jmEAHUc}nO0KL8p$e}sUcUDG@@L2^xu210ew*>^)h}{ihCM+CrpSy5hFo&_y z&NBfM8cRNnu$2)m*yV;ztpdU^iDaDatnO++|Gp>QCFNof%|r3mMGlc|!(KprYt{W4 zl0;`4!S%~0Zkm36R`mqIYp3W)YPrq6V;a97LhtMB zMax+&*ENfFso}eW5v2Ox;$8RDl-^IHjDJ7ytiD#_tl!sJ$K`)VVznnmdf4)%WaIV| zXUJNs8QX1UEKpZVel&Ap?esYwSZ8@lQD$aSeL?${@c-VulME}(m1g}%|=`hm8vlS#xwt&-(517?&e>xLFD zJ3S;IFlnvI`v}oUpk}aZ{xnMcUTdC7JoS#~j)UaItjug?o8XNW^~N2{8sy^HX@pR>$=RXq7Wf zca0hee5it#*(T)@g}SgZusRj_sAJPz8XAf9v!rOAhN~`0nYWxJnaLJka*uvQ&{tWC zrojR*evX+u0UW-gPR!zvAGIJUQ#86o&1LA(Xw0>SkBNs{1*nci8Q7!-AK!9BDQZ)^u&ThPOe3@*HLyRC{l2l*Lbl6So^>UX6jI!(2iMG_fp~RH$ z2AU?4elp?rQ!{0D9P#p*;@kQKLn_2d={b3FU&Dk-J?f4d6G z&@1DWK9$Jo`PYw|Y>DV>o6>P;;s+zTfUDDVJ+~OPas(RE4)(Vbr$j0A9oP6nPmB!| z{p@US=T6bjJq^`=g&KNs&8IqDeGO$&_;l3@>?FLOK&lSE&Nwi)Mny8yu7F&+PtT zLMY!K2f%l;|$p$^K{SFGm`$ z?C`8M71l+HUz6gjlXQ`9lt4`l5%orRkW6!f1Ty8=Y8z0+8!5Macwt){CHlI?*4cKT zC6s*R)TBH+GP!6wC3X|#j!RUc-s5b)B;7`t0-S#W+^kdjmBJ-I9?LTeg+Fc0He?2N zs%SLYhlQ9~s^o`Y_CajO@bYm|9)r0>vV$}up`4Kdp6u2RJu*&;HZ0PI&aq!in7S__ zX>9eCv(ZJ{orSlJeCh1K?J8RFYHF&@tND0X&JZ4!vD<)$z1K6VgF8t|Yl_=}t&P*y z>Fe8JTPHHn3L{>Sr{(obnTD^h&c@XMS30Q1Nyg^@{k5nME5u-|uP19$W>JCqe$CBo z={2u`rDX-O+77l{Q!6A7+T*T_o{UZW0Y;ipC%qC!CJu7tCcpuxjo0lTR z9_$uYK~1NDhHJDcTe+^NO$2_|WxJo#3W@=e7_JdhYwGIAPiObwS<*82_B&)ZkZ)!M z6wks)7mt(Ok0KoR3-mXdLcScSS`2R>d4Nk@MBgA|F=)DxRx}4rrQDMb5~|CAwDfzV z6)QHki^#EwqA2Lh#Uj-0b3p+P*sq(Q7`Kj7TUuEmTcHq;(K0|?GL$L1c@XOjB+Po^ zI2$(CN}OXTpHve`e2-YI#w9#V%&gfib8hE(J}v^AHiKESJFzZNLfyc~{0ZqbY1tS& zs4Fahf3Il#_5y9U6y{=1B&%3>5eE!s1eMA0E!2%487;b#?d|I#F;w5VOCXaPczO?H zf8B-L)R81x%V~i7<-viKIfu zHe%nX#hXe?_x+useB{UQ3=JKio8uTu7(nWXioLt6Cf^m_+A23|gUl zN3#WP;pkn89y^*i;aD*R3J(}9tr;I>hz!Yf)67|^i( zq4u`Wi1BcpWlqaRmC)l1x->~h#evg(Hy z9R<&a5O_xT9>WK{(t$kgCpBFX?;^x4M>#@!3lWNqd>Z}DF?4QgZo?+mW;e3WD@sZ-Vq=NyuLl-=MLOx?)!lsY zJ+R2gOlKFGXj;NF-6J%*A*UUm3QteM6S*f6wYu;MYVDYY7kV6fqAV7>1>jWqMoDnp?`^J21)kJ7L$)&2rNR?pa1``HWStzM6R043A^DN zTu0(z6z#!x9b||6r5N7;FCt|3-ebtIzYWsqjx&Bu?B_d?9-9Hx-3CDkD(WXY)L&N| za5M#!j8u&DJuYwudn+mP*pgpP@^e3Qv`OC^Y|ZT$A%7YuMIQ`cZaJ=^xua~!PO_zr zxX|A_xWDeYJ-X2pbvnwGEQRJ9$)^B;$Ix$IP!qjY>7-=xcV~`O) z_hxCnu{c)seRBRfyHHZ^XZ@U$PxlTPa; z_RC_19&m7Q5-#eMc_1GIN-zGOleqYb`NFv$#+I@CuFn%P*H8;L>rZJ8sP+zF_WXhG zL?l-k6{aU&HO*#Zra3N>dlTyh6xgwmC3T9cwV z&`l+}&;u$ASMq;ui#|-*J5{H7SDIILxOy`6sg7$2KFGbn0ZO;648Sa1xbko<+n7Ah zXFYjpJ3oMlqsIvBhT2)ncoeKN?Y96;WCOcBSrjmg!&e0cTIjO*`d+*worU zdU^tPQHECH*lsQOBhM(hQd7f9r)QJ?zG0~#fuzF}uc8qT$b5$`a%dJl(N(YsP2eg0 zc3>(;BOCEo;#gRa##6d#?u^8Ox8k2iJ$5n~_oSn&76#3IS=->_zPbMU*4b}@0#niX zUkM2pNjgdnab*2j4o>Vt4_iuw89VXXrRjqV2664|AJ8a0G^FpEJE$Hqa%8;DYV{zH zdL+9HimBg7*VkggJRUOdFad9y>!3uqL8QKsE~rh9YGYKLG_fDzE_cL9)0YKUCSTBS^<@8XhmT7ph==aa ze|#un$U>~5?McvU=hrNuHKtkLh)~mlZ_!O&Y=MnCM)@~k+Lqlx8@TT>lRAQZyWPzd z)ocG%nBN-}^o&=evud9Akw`c;5^*&(ZWJYD&vU}f2VW>Xs&PDQQc zY3rZLmW{Is0TNAg{f4*kKYn0r;*~Vc`q@qn!rJ9Qy66?nDc|KEpDACjq6RIbzf04# zkxPzb_Iq#7$7{j{`SXG(+um0|yo++#_4{7=kX>Tf7Wa~>N%q#0!uOzM^JC)Mkm|lg z6`s5|P~@dOVsV7XQ6CvTBis8&;7jc|$TM`1QK)E7MBF!p-L{z|VEzKw3@WdFdH{=uFu7ZEtBcXkRs3Eue%-ZclcG?81N=tUH% z*h56_vc&pFWvXFrNgdAbi6c@khS(0c4Bo!jGe??_E)PN{%HhK#%=%-TC^s=lLlJt) zNRk5B(B6;mTZ7e*(fyFt1UPm&ZR2R=D9w+w0*{L50M2}otseDU8iUY=fel}9cpmcJ zlqE$p_vm9MDpAjEqU99#>?xA@>ZS&M0wK&gNePV~m~`PgM`t2PQfgjK7Dur=k+Y{5 zPzEi*%-Hh(SnC&4@>?b+`$n4h=$s`|Wd97+cz;h17kf01sxMmbl2JOnfVj;x5aVNP z6Z&L_Y^pf(C;J9H>Icc63RIWD-hE|dh?Xyr10A4ze+uPW;KLce?8W@@xNJZ}e1C#3 zi86>5rKbF~Z~PB^V&L0&!$e2f4K+H($h0&i9R28INJ8T}tS^&J(x1wB5oED5Rjgx| ztC+0}h}4QT=U`K86CAM}Z_#I;6i&|2K9;wQ?Bi_xoVQReBj zem-P{QAc~1J$l-|ECJ71z2=9W?)cE4y?6zBex#2eAZXeqww4~YoH29fpKcCzI)W%j z6(Em=rT9N1*9FQEuG~q7qYYdeiHj4sC*W;g<1kOdOmv1emJkMFjz8k#4b#(?yn9zB z#Yc&t(BvWZ-RB8+imdT5lO95PK1ovTkNL`MNq#p&izfA`HQE$!=I|G^cEF()~!h6qA!pbvlP2zI&o%~Ys9Pp*IAJ^)XJ7`mc8E8IE^H0 zPeV4$mqMv8%AYsjvW2afkUERhMzuQ<)uDrpjhUQ{g*Br^n|@e{C)r{N&DC0Kea-}X z4~s0m0Lfp`>JO2kF2hhOx*CFw9>QJJ|I>mYGvN^GYbk~)L2NLp3|XEKFhzyaB+pC)%_az*rg)UX6Tv!T7>@=g#f42}F_ zJuq84Wp`q$PzQ1Ou?xuR9Am^#pcT_e`m4}(#{~6(24=|QYkL0f$^MBE7vDzu)W$MY zGl(USlI-VAFSlq9v+(*TNwNN5n@L;ZRw}FgDyLf97D%qr<=j1xpU~7i?PflW?!>`fb?yLdree(T0KFe>F@@vae*Py?Vwi zKD}mbYw5v>?*CR#pBszq6dJDoGUeoyB4cB;2kPBaMse?5?i=#^?|GL;`hkz{Zfsh! z`k+c_r~CH?FJxllleQh8b z1+3Pp9cwZ&!2K*BRcG2&pBEZpsbpfNtLvyz{TxjK^5EQBu==0y5P*yvogAa()sJdE z-T#NL2vjw^RB_k5hSs?yeVXkIKkiLR-{>p(HNbC4UudX|OrCq6Cp*Ag`CUb;bMPAY zB=spL((MmzfkAb}H6|gAS3bvO{8M`n$C30;v$iAoh?qpPQGg*(s{%G+h42TM) z`h6@1w#PW5la$+SL`q=J1YW^hP;$g}sso{0rA9B+Db!ziq{5bZn>?!w&@_V2JQung zolK=zLeZVpmXKm-nE%J!kHN~%N!4AE{loCLi?{|k<>{sIV~tOefpGv!xw%hJVi*B5 zg<@Z*G1Gs@(s2T?{%BUo_mnd&HPb8$Ur`RQoqkepGR=3=4M$RZH5B@ZpDIMm59Wz; ze;M#NZJ_27-{^uA1ibW95b;vact$p`IdR(j0Pc{Wq=~e|3=jKAX1Vj|$49(Q^U*E(gYS`A`;I+%Kc?2m28O%T@gZSw6^T=H?^=EM7R_zdU z?`VEZzA@_dx}Xfc-VboqIQTv2CEYM+402z_;!)9IXYs4tj2x(>inr(6%o`sz1IKHC z1~aOEBB_`gPW`S+DxJY>{thi9i9@-rnt*W{@JR=UAD?_c%Cedu&GR3dSm7J!UxS(` z|ItO>2CfTVveFH)d)LvPbR?HAhq)({j<_O8bBXjDIrijcz|?(sctP}`aw}0TrI{}x znZ^O7wEorx{+oCy+Dq2Lfb0Iv!pAE~eN`m=rvKBUR*>w5#)dm-;_D=cSk{Uc|LQ76 zEB2FkPF06g=?&rfmagXU__` z)BoT4!6EmfgapfpBw5S0j6SAAbI^mUGLfWRsOMCwuRrz|MGYX|NX$=o0D61sH^vvMlLeq>ipor^r&l4DuyIYOzi(F9UPb=wr{&yXHRs5 zEUat}^$qWxZ}hc}YyTpCh{=0~&bfvPf!NvMVVi$I7wi8KTFM#9E|*s1`XdkgKo;ji z8?<8{DR2_KDZ+X+wPO7P1|}v({`TxgXY7^DLO(IYRX#yM9i2H*(<37ZkmhLrq>)Z{ z^cw8R6?E`{r=Fd7TRUG;tJ(Yws?UbzFBZV-Rwf>lpqo?1_|Ps8oEZcUt7+eCNzdRM z(7jf(;lA90wW^u$3;djGQ2uy5P|Y@wZO28+eZ+YryEX&#NhTO+X{ng1X3yBcyOi=n zD(=x=KM$DzS@Kvx;bN^ot@RIHk)N3W5vGN_RCYAse`k0t;rL4O*mj*f^MnZDEe5d) zWHnF2JhTaXZKCcLnejh%pbs!hl$dV^nUIrC?ZPiL6$yJR9Jmo~dx|N0;>fNnbKi;%2%v=lr~py z)vUxfo0C)LL1bb);cQl6m5CJsc1Z@8KEZ|nW}q)G7gej1hJT`8+G3M^={Eu*Bj75g zqK~#Xm=)ki?fzmCa1i#j@B?2J6UpUZ3*Q@YGJoAiXgi$7?7O;DVT&Y4U><~RbX_kPwx;xucH{~|)C&N|X zQ_~ECJt^~)2?p4bS5nz-4y@_jxHuH81(VO3U4-=8&|q@o2Rm`lgdjSL=WtK{08Hh0 zkg_sq^|brId?w-bTZ+u5sgd42VWe-L=^=Yw)t8SGhV~2QH|A0AejTGcWhFD>Yr5kC zBeAO_)PlMv*;pAw$Nt$*eE?QyHjHCgK=%8elzmP?!9p4hEM7Z? z_e}^01K{CO9EsWrEh_bZRxBP>_rB$0VNJ%%WP=>IXSdMhgA?2vs{%vr)-nt~MzxWt zPDH`6Z-On`7~yW4i9U3SD-cZd|1y!=srtKU&Jrz5AU9{XECh^il0sh+qNUoGTJwbe z_~NNrA8aRRh6kWt1yE4Ivc07y_53S9wLl}Q9^%yQ3ezaa++9$XA8>jIJ;p)JXNcUG zPgOl%?e(!~xdLhbuKsvj4Y_XnH`K+(v^R_oW_TEsS?R$0o)2PcN_$j$i5Ygl2 z5kdf`#ewaihgL|WU8JPUQTf< zRHSleH+oS*L!2!he>4yoD|FCGu<)RWh@!4#&l(i1p2ErwoRCUZ(m%aK;mxDcBRGq` z&TE8<5ZLt_4rA=U-yEFETmJEF$-C;L(ND-bZmLtqNvpub4BfQ1q9|dy2i4E|zA$5v zHf8Gv@3)Bx^6T>KldN}(0RsZmZub(n=^18GqlTB=5MD_`z?-h*tjiS|!G2GeHyzB% zNd%z`-B_1KUr&v0BI|~T`iP~5jO15G-~pyNp_V5&G5-Y7IpAhCT>6I28pn_7LG_SIf;40x193uK;-k=hE9(HT1tRh;Ih6|Ih1MfV;6K zeW@Qr(-sDjQUg3+ryInlGsRk?d`NjQ7^?woqVLU;mjY-0pr^U$+ONd5`B&d2Jbpr} zREHutYwdi)I%-RM#}-kzs-m7$YIEk;Ccl6XV~K^O5_7=L?ZLivqn+01TqF4}C2_;V zu!h_|LQi+TsJNBH9fHn3K{KW!x1*;{n~Kof9spS_szD=HM~(bnl;dn1yx6Ded|xam zgX>z$H^k?eX)-VoCx9CrWk8!BolTP%Ym1$^e$LjV#Ge^y|M5olpPndXgdKm_vistG zY>ln>98WiTnUsK$9slO-o9H>GN6_fVMRlh0gk4LZkz=5}9K5RpV?)$~8G|n3w{GGO zbcLO%!fkoL8eU5aeGw06m4MGx zlU?ilJz_;-&?*rAM#pZMVx~VE2<(-M8`NWrdBkEK_XVvlj;1reJwF(@VP!8{Y&z7G zMR@x(=>5R{Rlo|f$v2}6B2J|r&w3+!rjJz4mOrOvasqY_^P$Gm=!|Vxk(Sa9A-)?R z+8UVqbdq}P1W=vs3R`u9MlG^uABENbAxk_ODLXTXTvILl91Tv0>kd!UZxmVCyIC&v_iuxg-Dy(Xr6{=g&dvJj>O({C7Gehj92d{P zIu7iLd{_T=!_|mFWawV{xs%85s&n`A`CuP%U~+(vJN&S?jPCYyb8AL+l8jeFrb@6# z;(3LNt_?sducJF&;hwpRfb%*sjXcEiv24~}bgjyaxJM=>NGIy6D#4H_6}Kgk+G{te z8S2HF2mG_|o@avj3o&hDhh^~NDLD1|Nhwy;44&BYS3U^{soi8m1C zaqgEUa?XvIb($8%lV)sAJ`><~^_A3b3-n9sot}oXw_`RYYJa9*hf0CD1*ytA?*K2?+d3>$D8>Shy$LM;FEo46?Ur`RZOV zz$zl~OS2?$w?-zhLRH+;FcE|&w$T5N==_HJy$umx(qy=v)x2?)`)Ye;z!f|RV!uf` zNXuuT*MwiYuzhy6rDng&>k36x-oJ3aFlup)f%Y7*@KpqS`VHQ8!BV`u8n{Wuy^a22 z$E&1gEAT7pbOKhJ{lIPz^7%fOL_K`bITKicFx->WeA9lG!(Ntye)5sEKjnd%mZhhn z;B_+_)%vq9f%EsE>N&(et(MrF&sF~0j5YSvt+PjF&0#QBirVVwy&AwG9{hyv>hzY? zvT=yNrMgK-)}Y?0F!&3+A>1w4HJO~Sb6|3F zSNKmZc^}_Z@uZBPA-l&7@{N@G3j-xb9oINcSaiU_A zJp$eNeC13VQA&RgSA1xel@9UaOXZc+5B~;x)?qM<*cRzn2mb#7`9wd+49L<4Vlzpz zbScxhq9;|Xa}4rd9nu*{I9GlKJo5-gt&WoFS_qcl-?N@QJ9uY<(CyxE#%W~XRGasG zn}mPS|D@PihPZ{_42|{^H1>^}#%sWRT=;q*wCEqA5k(jk>MgB7TMS3)zXMw%#u^6t z*vgvUSwinJDM9Z|q+ZOX=I^E+;VFZN?S>Rt|EdW6Z_YmK-IX!{QiT%?Z_>BO8hXgC zaXrQIwk`qHA89~8E|P6HDFv=i4T}AynEI-Gc+$E1_p3PBGh1BMNel}o*jTz5Hq}9? z`&%l@nO~MPeWh^Shy$+b*HJ|3xQ>ACZh8_GMIKHrVoFJIZM48Se_BvGbFoTOaoTXr zoBO2H|6rGSK=E<%#S6?YeiZk!My|{eLi*rucQLNDOukc2D()rStx~BvbsS7cS$^ZH zq1^}f>5I48D;+$rl^2a%V*V+29a4Anw|P$l<-xR#&NY5@&GSC{_gt{HupmGuY;zD4 zZFzLK%U|^4gY$>nb-A>9ix3YfM^R6cC}z)B>QC^?g4B-mK?dooCOU6~8y(%vE%cgX z$7VTu9zQ4o`T{5g%Yk#6;}fYAiLGAuyZvG=CKwG6QyVWDh$!sRU_(i6hBc}6P8c!02v&);4k0veE^eZ;be^`Fi{>D}MjDKy@UP*&O)$BX^!s+O?DZbQG z1B6vA;$%)gaS_)1v;k+=J;}^o1^m#b`gUuBirFgm)(|h}1!*KMPbM5=*Eh+3oFZ6- zR6PQUqv;V?fdYS99E447UdEb%6vrdT2{6*MIZA0ro#z7$P5JQJIz_**2?g%d3!l4o z|JDik;AwbWe$CNR<8qkBj%&_d->LvjR`kMG^qty%Q#$Ojr3M@XxT9qtI^*QvmFg)B zqXu)J;kVMD|Eg=~;hsy@iiKVqiK)cF4T2B5Mca98Q!OEuYSD3)pN&xw<qtynL2!4&f&7!|3eANd{$-|3tpaQY4S1rR1l!vk3sA8~ zoYt^impdbMZv?W6vp6_0%J+y@u|F?o#LU?#FDQ#|6D*xAp2?H6SZY{VwYz9hvDe3? zNY5{1+MH#hQUF$qUh(=(tr76Dl%=*}+UA!5m20Mh_~O@e;z*%xb<5Q)9b zAiShG0BsvQZMxi=8puXdPN0V>nuCxig(H+DXpan^7$0Nn+(2+61(UpH2I681PZXi! zDSxfR?Y0_Tf|~Z130&RRcxRj?8#{X zUi~<3&c`|MZGYr{6rG7bRDBzV&&-*{GK_s^Y>|DdC^CbRHngD>rmIzN9l{iR*82eae&U@Z}ppU5WyYKt^y)NA`h@8?@oz(WWb7*+r zbVq!blA2m?WPg63zek09)I)}s%vU(L--AAM6L5e@ZvA;aPN;G-W&V_nrpF-=V# z?BIZGaS?65<~L6N=bs_K?-qM%X&#IGaCiJh4fj!5WyF{hD3WH7kNd};N4RiaOA6l2 zLQ&iX8`eee@Qge=&rr|+6mJRxTh&>IQ^8p#qxt7$smXf?%?VQ=#VShkf`!l%<>lYt zr|#>Nt0ZkuW1is-v~JSsMmv*97_SL~5{;!Ma%__LlJn;+Eam!~bLC+-+fZfVM@~=z zA>I%pWj?SD`cnfuJ&*gShjH~+LBHLkb((}5D_pcopBX-M%U*HFV_?-SO@ghNGSLvM z7z=|m-}$fh&OBh4Wm(Zl)*M5gP^$2x#we~>?H=w48Mpnpr=@Qur#HaSSRJGIF&M)< z#_vnxE&kubq~$zi0&IX%|06ulQ`Sjm1~R~YdPQ~}!L^>OvqGsz z%3J1p=^vjBee_$WxG&@_2p&o0ya(HurL6?nJPaRu7}nNr-!r+v&LB-9QFl4e@WBLd z``p_=Ms9D0JynGY>Rs;_rm}Gr-m0A~cw0eC7&8pKE}f+&`T?SL-A2n?)X+fp2glYn zA}s=W@rD9}8E?>e@qnv&31EVmw=G4Tm&>v9PegP-f}MSKTp-$BMa5%l1s4d4b~y8h z)!d@nOM}F*LM$p?2g`i#k=J{P5V|XC;mN7R7!A?tW(tY|*%0^D2lk73?s|QZDT#?w zc%O@{4K-sNCAjW$GF{n3okfy?W)1$|0FN^Q6p@W2i4f`#WotXdUhpqYzaG?E8BIRM zgR!Ef!1Cr@AYVC*d3$H=_uC#E~a zzP2{0v-4kq6z?z1OwLDmpXk8^f@~MA^G-*6`LX%w-nFa*tQw%hK7|jOlM-9cE|Ejx z#p9Ttp7z3{sMHB>RXImCjoaZ4DYOKk1Jy0 z2e-jnw<8DRtKo+JG(y0BSihB%&Wk?5U8z0PT2f74?edMoD{~~(11!Cf!5>9!yMuOf zSE|zO%vqHLk?3<$JoiXld~&ilKKS+Y-yeb63Hw(kuTBranQ2^H`zq=5$x~}NGkbP> zDGYr-g7qK+kG-5-UAeKU0Ht!T8or{Ey#XSIwr(j^Q;F;P?5;iKQ;= zHrsp4WJl}ly~+$0$Obj!1%(x9Zz*dg6e^bKRv9RCJ|aJ|k&=VAFOgHfFJB)0%>{O{ ze)teRve#p-0OyxY{E4T&Uzfj6gc=Eb2K@7vTsq`J`?FgbWLm%zbx_6DTe3?as zJ(?HiW|B&g=~Hgpres*S1+h37`?y|4w-bA0EB~LB^}xPCnk^ip*S@3J1M;RqwH0bo zUfK+;bdM=z#Fl&z`EowO)CO=2$ouy1N z*Ji#=Cc+8wG6bfNM+F$CwcT9>sM zdqX)A($-L5vp;{i$bxS*}VRg?lI-GcINhVGe=%8YfGoAW*TrgF(V#-Q% z&loux3n#BfK3LVN)S^Nm!E_n>?Ets3R6n9hjSo=^qvS}Az73!txWh_ohew0^NYokjD@{9OrBe6TG9wWhF zw{~qE)=yMvGgR{R6fi);xj2CO5e;W@!szkNm5k3&Bpt?dAm{`<$5_YeCax zy+WMesUxPIGYkh5v5Kwl8Sw67?tmIAa1{%ih%U(}C(Oo|RO3=~;HLx7Nco%B;L2HT zuIqc~K&m$66_0YWdHA2?;HbBXFH^AH!`ZpHIXigZWw2f_tfJ8=BpEQ127j+aqFUtM zzsLL+q}F3V+oH0D5;6Wqboa}HuX1xKEc2rKw^s|aO6V~m{~TAbD@UYjvn8HOXaBBt ziSO+j#}*Jjg5Pi2bg}+~Q%oy%;a^;saI*gi405@e6hIdT@37EfVC6YPVU+)$8}fa( z|ELpJqXKAa1k`O{2WMoz3BTqUhu>|^b~1^LH`Emc|I=Qu0e%sIT=wChbyI6&b7e43 z&<0P#EQ5+A;-GBoZk$F&(Jdu~d~05?4!8L)iU}?@ZS@XHmf3%Y_r&0aTU4gH23neB z_E3Ny*v;2Q3NILb+IGb0vqwH&WzU$`czEBW-FVu5NwWz>7XWs z=i2junJb1i%*GtqTb1Mo8%nW&9fMiz^@y>vfWi!vl1EOo^)tMb0wS^h%3uHHB0&)u zS;jfhnFLK!Y5wy+9`8zx2-NM~%Yj9T#tJ6YWP?HMYZsHxUy#`7%yp~I#e3XW`}NNy zNiN0yriTORsf2Dk9hMHI==U9O*GajTE#>{#F4(Cc%k=k=g5!OVscX&#xS8xB31D`E zzCwKV=0iVUfK@4)ogZTc2V;Qia^$#HCx?v1-~8tqtYtKi)nq8Rmyy`PB~Jk5YJ+`C zA6iLWY&kvO-@U5-+qU&Dw83az!N?#j1o7^iqoJI|8-~ji-4go#dwsQyw)PicOpa(k zl8nt{$M|)xJuV(>CgXgc`^AT(OT{N9=&WirQEdovW3dfhW(*stz108XW85;S#Nr$0(VaR?({w}PEqNbp;7AP|t z=1}8dF17I^r`j@?8k6N5Rt;$i^6$ zx4>P$id8!5*WR=m-WLLwS{_4~tr(0Kk~iiK?g4+2g0D51SWkoO{FP3NC1^`EWX1f( z7K+~`XWkBaK|~#%V4pj|NpcVUA$?OMZla`wo(`?DL>Gu}CPp#$4H_-fF?%q#utBqE`zK5n7%lv?P5P8FWh<-$F9+AxSJP` z@$1BYn^TzCzvs&B;HR_hZ#baaDs`8Bu=prCIZ=1D!wxEZ%)LIg7wK)O=1gKCd8_+} zr>2@Lt**Nq@!lFZQqzA|=jF>Ma&nd9UAD`ujxUMl&qXzVh_j*orB{u^+Z+yatIi$qa|0ws;FXS;_eK*{JfJjMM-4M@KL-6vG|t zYpv8-cL;@)d^Pz!I|)toES1sVONyjl`s)NL8Mq2xovu}cqT~DC)FqzF zFx_d0zgLiq)*MV7GFJ21PtiAs$lNKH?|2!vZPbw=x6Vvpz#0L<9Za66_$;F|9Ys`6 z!M9g;`ovNk4F54B2@24fBY={P61H_t&nfq)%?H9V1GFjmhxOtprhSITGW6?YHr_*A zA|f*@JG8a63M}fEA2B>-dit!m z`$0?_flWL*Gvm%lrMju=batb&GgFJs&}L^8D0;p$Pj;&$ zcZNjtMuHkCqwwi!m-%$SnZebl0ylJU7mXMnViOV`V}CNP=sjMOh+C9}!}O2|R~Sr% zBDTWn)9}C}_%emBhzxHuFi2nZ+;vqP?&|{@JuP5cKq4!l>(v1&Yo)SitmXga4eAk6*(}ZgrNrF;sUbw0 z9Q+vxbcf=Rh1s!@Lkdpf1rR}0o!BF;%X^oIGzsPMd;FuHk&%Rb^-awRRr#u&ZA636 ziQ#^8WTpnzw&W`#Iy#-Vcr6DUGxDVP!Iap`_%)w$l@DLh0V}Sx0v~SYN4tZn;>?Yg z428wa%!?oR;e_I*%kWJA*^Z5}HL~A$O)@lnCWP~GCx(WDc1M`Iuizd9^P+-dOYyDM zxvUs$mT$INSI{J5ldTm71Y~-Ac4TDVh4zkUGv;1Od1~gA_CJxa9=L>hdSktc*--dK zQfw1>Kh4SWn%o1k8Y8^Hb0}b&VYNNiw;+u07X&8qzdshWGyW;W&>xQMCPmxe(y9As zY1(oYQaft{zhC+5rA!}=h1FXEj2$6!Z>_Mu$GuIPjsEWUX5Frb0oqI6aCXO(=;REV zZw9!SGJLZyQRKV?@A4h7aYW?kVAQa>#VqJP<%z0Fpb`h;1MEK00yt=dMAv;(TP{TF zD4BO0>}hWQ%#9K9{ta!`V1jQkte|wI$pjRgDd&Za*p_|$S*VMGsk&FE$)5AZp{TwlK zy$hQNWxq)fO{7X>t2&40uF~kzBU4@MrN*w0t)J+u#ospNA#Vb?nEMon(*+T%kk~cFU~cUjbc;@o`2^p(l21Z70#! zZzX#CQY|Ktv}Hd(jSqub*gm2nhmiW*N#uXL{iFk2N;PW_a)^Ck?T-_*;K!7hg!Qhm zZb}u(v#1|)N=5i>elVrIR}BC6h|?9!|K{fHjTEnL4{CqZ$Uj7ucXBlsypwZV33j5u z^+!N(`@-xe&E%s8S1ucBM!6jUGS)G_#l{o0^tVt8t z<9!?P9p4YjO9s@G#nagvGeZgNi*$PhU@n7u*NiPiQyc6oWUFpVN92(`dy$Nd%(Zia zJsq4;_^Q?=_am~{N8ldoQN5Bk>A!miaNbHHZ2k&`ik>au6@F%v?ZGc2uR>nG?SbTD z(Iw)lKOWA{fxy+=u{lEP7SWV^_Zv?3=_1|E5hBN-35QLfs%f@rOZzB z42Ht5>Y?mmjBmZPFn<|j+g-Y+X1=)jn86r_(h^wKRq-SV`+?|-GD}dDBcGE~?2=yW z$AIfPBv==@qbxY!%l7Joi7QW^@^bRqrD=<%QMXIzX-)5%!HV_4>pH);;9Yo$iwlpc z>FIRN0>vc_e7RgaPD73=(mjfZk_fWm?F0dDO)?@w)_*~E$IJ+|VBBqJAq|_*;9U=4 zCmW3O!98{$6`j?s_^#<_N0eh@vny!hSYgE`N{Br?A;(kK6y(jrx$8XG-S_=w~ zHURrOBpX_kASYVILm(!8=$a`LtH{)x@o40$fu9v+sQWS7wYR+OR85wZOTS~rYn;tW zY2B24%Qd9~7@SHiLNMzZ)W!)aU8F_ZfsrX%v1=~kw(n%fg#=l=$7cMUW8A~!&xeCv zG|3z)lV;Cc=hx;kvpo-(ot7IX6}EhNiqMLu)DxaAt0zkt$ISR!U-8`b2T zobc)lbvPm?kZm30_(Ik$iYtn@k?oExKi&bu!}`aibR+7S@{M zbE>`KEfhReyPe{q@8tz4CmM8fl;->X96>Cu^7pSo{_!v>?HLaN{UCH^P?s$n&=egz zh$QOAw*ANPe^L)hzJW~M1yxSE(r$Ix1% z{hEj#M%(cl7$~&aVl&?Tau#Xbr?BrRVkj;ehd-G>fo?e0F%FTKER{qp%2Z`t(`UcZ z5Imiv7Jr4BL$HD}oRa8qI>a#fit5lyUkpBA$s>FD&g z7DnFbyaRtv&A_*#uutyoU;PU&T=35?B$qw^{w(3g)4P6V()d zwTiTQ;Kk)U*()6W%_r@tGTO(0XSUh}7`*N)$_5rYM*Jk$O3{tpPMO5t6239R(idEgb zYh@JTNb5v($JLcUWc^@DO3=! zpoZzKnm-Dkn~3@+sKs(iqQ1+*98;DsbMv~Pkx4->6XAF%SHFkre98hkkO?wM8=T zX>itE?BDx%gQV{L@%gUR3j4OoZu3A&`3UF;&Aqn=bj5C6;qX%!;^Kl`sE3-kj*5|X zdaUH-th-cu=yJ^u8rLL(|I7&8Eron9fcna$O&|D7;+aGT&zDR@-wzHj5w|Rqz=uJ- zR(RHQD9Bi}Dka+Ae+kU8VX@`d3_6QN3ev}2{m6gV{8q;8wn~s9aNu%fdL%KxR*G|$S_hYd2Lnp&=KdWbH!hD*r zPv+8U=n%=SB^-Z!VSf07E)r)cI_`)>uR!?iukvU|k5b~hUYeO&6ROS@`1t^Ay&s=8 z_;HpN6oWTTNq_E*);NYJE5b(B2I+@6e94kIGuFvdR;cFSKeR1_c^g z!W{0!oB4;qka|aK`Somz@^0Ndx%Q4s2z%vf3B{07K80%g63LRS(97tQY2{-($9siT z6QL5ZQeRt{B-t=+u5{5Wn0gVfOWBuBuyNCd&t|FLKx=@V`AZuTK5}AvNjeRrHUrPS z&8W8jj}d=6dw6S>OFzA3Wq#i|<@e+%ewegmfNpIX2Cm4NRQOJkuE9d&Afdw!ZMSItHua(+D}fyao(2_l*XZbG71*{XJmG@$&}FyYnp*G zdhA^UeRhUeHekZzfQq1jUxwT0Q2R+2yd{?v;FQa+V{pkaIJM=3PD#K!3HiHDyrg@p#vaiaC0)_KgvTRsGr22S;g^q zs>XjU!~dqQAyP90MHx>y6!>u@l1?J|9&0};grBx^y@vTwG1hRz8V>I)mGm=}GP(w+ zoRCHkX+&J`E7@RQ>EEdoCV4<}=*c=nyGVL1Tix49`Y zK|vlJY~1Qx{>qFVO!+zmCg#7T`nK;c|HJK6SRcf#|w6vML;DTI_L0mlprLyQz!S+iS+%@y^ruyh`!B zYK1nKm_Acp4M+5+^UgAe2XCn=Yj76F)LD&Wm+7|+ZS#LV=rSG2hX@gb_$Bb@VJ*}F z=l9P7xmYkLo|IgD_;6_?sT?8)0@=?YUA&R%zvUrSzkdA|OZuHPu|ObzZN6-l*(i4H znVJ8zNTWkV$rIq5lYGvpYpyAybMevI7WX^s3;;V2=XxdMZb9s!mniIWZ~Gtu*L)y@-52i0r>lsfJ1?H7mMC z?1uQmDE|l-I^)q^x*mQ~tiRJFBfG$37cO>KJ<&-$ws(=^Kf=3=+qqHjG$St$+;^Tj zEia1wG9FqjAseWZzgbZ%L`pV(vDpTP6D+|vdq{b^4E!GO!EWwnw2b0Tpt(6!ZBQ_c zv1!M;KRCO`hRbF->mK2q^>5 z=S`znYxfxoZxIs)tz6YR#$5N$NuJq>nrjL^VZ;ab8NV6wiEsH-ldfz^{-t@Ux|kYL zEMxP{@TdcLPCplu#FMNf+RFa{`zO3c!eaN~53e%0btto_88y0@jv5VXD?!Ss?zCI` z74G|%>e~kS!#548TGaJk@x9kny+dhNgp6T$N(srTqU5i^=BHa@%HWM6V9XoZl+Hdw zQj`}oNb7FmzSeTB<0IA`TXAm^*pIt-5P3dHzf;Y*J-_}^b!xPDnp?Oxv*1kD-gTly z-7&PsWw#WUw2%Ashv4u8kUhvrXSazL$%3~eTD77(4_NU;g8pb|LkbRgBdOz>%*CSd zhSNpyn991TMLM@w5`TEpBpflIjiO5DmDp3WD@*c)85y- zPJ((B9&VFzz0Ytr7fX4o_!7d!tMKU{D6li5VWSincK{WZ0S#xNA9oneRB&#c1n|9( zPE`C1kvgEo$ZtVHF`IFE^(F2R?T6LrY1q@#iYgj)u`LLNC#-xi8_!odB#)Uo%Jl%- z%@S&`oNSl-Y`?B3BxDu;2ekJg_dndfN2OBY#W1+^pur*Jf~@G5F|zG`k2ot!%YcqI z0Ox4J^ehFJi#1Yeg_i84ThjDo^h$5T)j2-C|Xy$Ep5dGemV~2XlyRrlrUR#BE^gTzd!9=~@-p+~Ze6#f5aENHxzP5ceK=S@| z>WuQXH5ukcO3$b!RmT{XpNIYCPvYh6Jw!M{mIs57cZ@JDKQrR?F5qlQS6b4?>WUFBdD|ykL znYQXThfmAI4#!rf&gi-5wtRR;a??~0?xpNBl@JYHfWfo)t{?9RdWs~!Vfp5v!_dV#1R2m6y;xPG7`UWa;x}mdlnIT1twt`swnT@~(5C?Pacrz6<|D9HtDK z7mH`-oZ*m9m3HyiNgnO*2(7Pvm>ZW`^$=d~v*ZSERfk<&xVmI^?(oMCPQj*yU4 zlE~N^c|%4NKx_<}l0ChtOPH{4-4-R`Th*W<-1=Llh1l@?L9V!f89{!zG__b+YwQVM z$;`{9s->aY96}#DS!M3?MhRL|SJxn#a}|9&$~2d~Nvo?*)D z{AOlUOJlv6iG@Pd-od`Y)8m}f9bb21tY39yW$?Nf?QgDhwhLL3%wvV23kTbutwLcd zcMk=Q2I5X+Hc`7eNo%OZ#KaOOR_`zs!5M}2RIz7f2d!E=J74=$+mtHJKJyrRpg+NiEPL(G1UKGz&luK;Jzau(p{-!4iq;)=@%h9dtxEtM z3UT*0%B|Gco?COAWEkah%*j(O&f8}t-U2P{*2aJhIgYW)>OYms64A(bJSk=ae?~aiAUh51N4)iAmiiX zmnFjECq$U5ffJ4pAwW&pJx~9tLGXwG&+rm@Z{JEhBP}&VZ`)OVQXnS z(PI0p8hX%RIKbI_u?^)c8;C@Ky@hb$9sZOKm$wC8c@*BfA)VX$fLw2;>-4sbzr)AF zSqA!Tg?UY+85;K;aIOn*xXy}`A1Ip&7)kaSs~$M;=4?^&ZL9X-0B_N|Y|iRSs~I|6 zn*`)f0`g@mhcWka3H&n5;eb{d)$})bKVgJ;ab$523!$Zh*AD_!Teqz9%n4*@1>vQG z!1Wv0Tdc!x3!Uzi%9;BskyliUi)@V51J|=>EAh4)61N=D|#JDl5dWCSmk zCI<$c$J47gR6RLW74=AFKcN4${C@gCeN+H8(HeK>Vf__nPtXepl4jHnj)%G%aA`g6 zVD=){^*OY+63tLt7cL>{U?KJ^m|7dH6x`Edt+Zs_(Hl|;N$=e}=nIb9for5+7L#tA z>x226Su8H!M?U6BPhXIExY8pg8gd*AMQYo}=^P8of3X*gqOfjc@$MXYnK2kAzg*5O zc|u9}M^l(i5t8xjk2A04LZPcKxift~oMBgrxUG1t&0WJIgEa>g)++dFe5DPhgPPWl zWgp{GD6ss1-OrcrY^;?GG|}ImHW+x|kbM!U!EtD2I_TBcI-`p*bECvFx>eCCRUw)} zMj`J^b{<){N=^8;NIfxjxhTX1(bfxv4x)i=q3QFYlgh_^3CrtH-dt*!*DF+cwes5~ zOju?7spiRFj?YQx^S|Ni-p}#zq?7VZX{g5rSAFdS6@$DZ zhM-U!&IHPbPE_7Rz4qLswBIo38$)vrtG&V z>6_+^Xc?*-cV6VQyQ@|0!DrQR>>=?VjNm_h4yTulILMiq-Qx-l>lFCISEMGvQrbnt z@aKm7FaC95+sJoH|IE;TEe{mLGu7?Db=2jNj6OrG->)S&uB>yY{WJ0F1aTC@OU1B-67bVJW z460rMdDZ&$rqG!*1ZX59k(jNsqz#qsBbDJ-I1SO*zMc^w_=`8hg-T`9@rk0XG20B|!}GBmANheES;W z1V956t5^}hmbY?;lw}> z^-eV%L(ZWk`*I3)Kwkc8E@yFD&}%nJL>Wg- z*kLm?BqSJtmI)MrL%-6b!!~(2*3NnMqG9!DMP!X{0Q1X?0^7E|yu6L(CSH*C4<^x| ztGsungs8$@!u9V^-q|gE5yxxERnr#>uZ@gn=V9v;{WlIkIcI5J!A0Y3bKT6JpL^SD zG?63zg%#5r33+TSLbT-&LskQBZ}LKYrh9n}@NTlr!Jk-V4Z8RsT_PLYCRfGy+lH3V zy5B6e5nf`|KTDG61b`ggb_g3ZPLPmC@8UzM)YMhUV>11}kOi@#Fm!yarX8I%__fGx z{KbnPEEQmBWTJps&5OI)6H5iT#cp==o=@g>heBVMKe~%;UXtgRqLAd|lw+f=hY@?$ zU%JS)B8fNYzTAY@aja$&=s8Pp~ztxoyAGJg9*#l)0tHQ`E3$>=EP-h>;sbxNTUKg|W$<)W}z8+>O>0 zDb`+CBBxv>er(VF>Xz&C>7te`X|6vW1eW8j)>&6|RlmX8-HJNSMoWvY=&<(5iKo30 z>23pNjbwJ_0P``vF_e}tbVVZ9dlt*9g8r5pb!ow2put))?B*uvjh3%kCOvVie-QNW{TxDSC(9eMF*@kH67zA%li)d+^H zO!|ao1v3d(-r`!l@kR9X@P#RTt>>@-zC^IRGKXOPDJj|@AzH&1QAvM8@44QS`i}JS z=iFR_U3p}uHl+K8aEnV;fyNBTt6p-Qebe!W+9iwfE9`6QtSl{v)*;+wnylREw)2|s zr}J-8mk7ZsbALZ=AQ^-p&m)f=x{gVyoF*h1K*;_28e&%~q4)}L{p+m!&eN%&ZTXV; z_cGS?%U7@Jvz4_(=Tyy*m6O4#=maCwGA1072|8pqcR4ZkChgo)Ep5Z~)}ZxZ&0>S#iSBkw4^lzzi(=&~gUl#3lNHU}SS)!Oljw77 zgH9zDrrE$(|BMKRYDPMry`mi9p>{pJGU2l#%NY)yzWn8my)6# z91L@_HXFU)N1_0`S^@3b0I`+h-8qc|tGak<6ZtySV7}*RYoCG^nWM(HwD4^>#wL#- zb08qbVJW+^qDGNRG`RrUAT`gdvDLeErqZDk8sVaVwQ=eyjdu-WxWBZqY7O4x@com& z;$uJQetZh7r?UApojSC@jWjXM@fp$H^WC#~nQ)}{cLuf*QSb3TQX@-=!&hyCWdd7r zx=L|mMe71jyykara^P`hHn)F(zUi0Iu6)rO>Lwp<%K4mse$gwUfoE!56I<+FHl-Wu zr^U+AXIq-HiZoc!O6<*_L!Y4-`7_lxH#n73vw;T2^YFICy~>4A8cQ`xgXVQ5B|T7J zEzdWFv&cKYx#{g_)2F*V=_sO!Q{NkpW#wy!GM*;GD+V%CuH>@ zxUQS)S_(a-!Z%1kG|+DoP`1d#U0lIo45m3kX0w+)8Y;PprjoiwScaUxJOW2UNnQUD z+ze&*RO8Fo{N6gnL?inHv)%C6}zE7C$8#WXir=Mq-~g{skOI zDh4@-@lM3UB~%!sjm1W5&8?^BMmV-%+6KRSiaj0StIT zeZ1@Cp~!MYx0NzNb@?E`$pI+x#}&;k;jV8K6<*%8$5&a%|1;8%7wXQ5h#=VNNj}sN ztMXLXZ*fCfzQ~^S$W9WgX{{*WEvC0C###W^jfSE(Aqn2OsK=ng($eHqa|~kXhakBE zEtco?nIBlvy$auw3R@-b7O%g}-Oz+PvRu$MJ>ohPY>NC^EohRRHW&2Bx}mrVx1y7~ zeRpHXKU&MfV)z7I^m)5t$RWf~Rus@TUj0UQ?-g)70NcCcuCKH)Hk6P z%AS$|8}2~Q#MyJ>46^l80p2g0bfRl9Y5exBNJ#UA$k8cn2~&a{1;=!O>)*)J?xN>m z=^+smMA1d2F!rk!Cl%y3i&&Ioj2ci!i+8+hJ z0U|{W+y-ck;WPP24om>{$~0&e~+B$*s!`fSqzpU#?(6 zJNCc=eY@`o4P}bGq@84h#d_rLTQTZu)EsNQD;epytArH>$M0$9%3GBgHSfyT`6|F? zDDTgqSd(l%E9)4A)yr7X{jZcG+x3Puy-7Jbv0H%~K^Svd1FYEh9(XVWOH^W;S{tG5 zd$>37k)|dtc&P_IU->^lR3=t1G1fI0Zr^S&%0+WlZp>$gXKa1J$XKEuZZxUI@3fF) z7yk$7TSR0Y*9#`sH2Zsd-`mjsK;xb(>UA?ydCgJEW8A;8#*I0#_f7EGIGqJ0Y&5Z< zXaTomy3ShdX;7xH~SDlCFPgsRgX}z{+g|gLX1eJ#n{=&jszSB9ywdYYX3k5^dK0Cy!r; zFw{w`SfWr#m0fMfx+TL79_)w@6?1v&xeu#7M)opN8lcxo%)g|>;kM8ct}evg+|Rum zivLl???1tqF>apg$+b?s(Nfj7H2Sbw`-MkRBf9XecA2T=ZPB1a{+KWGz_qELn3d(Z z8OB-m8Pp`}e!|Fk;nm#5gFWfeJ5fwZmtb*V~T-;S65zK!cJ@R8us2$^C{?&IIp~il^_CsV5!(dNMfmYj9HYm(L3Z%OV-ma zJvc$<4mzqw!^pJPX@n}vB0qnzuIP6Ljq@ui>RIB-P&bZPy>s89ynznTtx0jykR7^P zf4KysUlanuLa{y`uVDmLR3I$)j zIY0k*&`!^-sv6BWVRy-J?Qz7|!Yuad0@XsB`Qn1?xf=dz+LddDx0mN+uClA~x`Tzf zvu-l?=UCTq)&*o)WnA<;+X6E%;qUTG_Nz*w98Kf@5x6n}6zWW7Lk z$mim2--yio@^AY`l`);phl=3CX>t)SUofAyy}MSNM!-?g)jqf~Q~l<=>TM<43c*H<3u zdBL80{_={(R7V~hKorE`eb3{~)>76L-j%^tl586{C-Lzhw_V;HWcWc@^o$5I$&ZM5-)d==o|l~5F(kJZ`*Q42_%)tcr@6v*nwqG-rafj0 zob@-9OUBqSYhuD8i5epQj!)@N|37&6;tn+|%;>Uc@8O*a4LO1KNGCU2f^)lnzDnQ~6%pCN1o=-T zZSkQ`Cvi4WVR(gJ){?XtHaE4fHk%sw@(F8%rAkE`^Row=H8BqjY}Ev%9N^y7b&xJB zfI=^>Y9i-bD1uxPTyj%&7EQZpAh~4037|~1FDfEUJi3gfrkJ>EtsttkRoef9O5DHR z*GQJ6m_CaJSHDU7rLHzVKR{%8O_q;`a;Fq}=;>;Gz2J2|`8tFAb?Bj+TgG_E>xGH= zn}*eZrY>u7VlZj^jm$}z>SRfGXA5(#Og|N0Tb1Dk?+r}{15Ze!erYNlM}OPck8%%; zaYuG>V-A5{V(b6|em*5KHc&{VYAE(d`x=LOl>e%^<~GqZ)bn%#YqRR-lS|aUUwF%x z>jqaq&7gN|7S3AjlGJ3-J$Ap$fxQ1)oTbPUr7MpNzfrB1Gd%OIZvNC|NPY|CYUW|} z8{3vg!A|OsrWGEuk%j-haFKZE0j{J8&>NM`&iu@-^a~Lzi5u{7pE2M}%OGbr^oqlA znmFtzcwCh}wP#PrpZ<=vN`s%o=e8bK9WgV2q7-;)#>&-Vzt>_NKd+wHB)_}>+`QKe z*=LVkp0Y9Q$OztbqMCd66F^qzWavI&>^m;nPHTS?#Uuj5b6~kSNOq5XoS<8uYF!ibY6x8@X%yRtqO zZrC|GUXE?0s!`D|ch&0A%20HE-7;?7COB$mwbW7T>W|d483KD9f#tD=HKQhqEN!#y zsdm-@z-{;xT|U8DL3z-?q+mLqFMNul(iqsGymmv(%=|UxHUqVQo zh+I-jx^Fwb^ZUPl`eWyLp3mp~e!Zf09LAPy?ZI4{Tbg?w?#$Ndcs1?pH0!`{{U*;n z=slO()gB(*HTyKXR$9((hH9cF8$LaV`4TFgZ*9cc=82jm50nnjS-$#)`rv2M7=}%c z5BWdy7xiM3Z*0>*U)~1|9~0cXMew@+yye-{H!A<+@4I4ctiRxvh*1p;wNtk1P{ww^ zd(pdhMxb%q+I^UjS9;Rc7}~%qK3_seZX}vpYjo|;Udh-)9{coQDB{U}lC;lIFiy2KF=6mq^eIhM$vwEgtDDfCJ0<6G{R}#@q3s@! z(<+kD#81d&_J?97=ZeU!!K5G$v(%hN4vb7cbuCYp4t|tT`t<@j>h;z;iSAld8^eZa zG f_#EfN0ozblH{{No;;h#`nWy$iT;s^n_55|POa3wN6;JXxFDKf#S)kpxlq{H& z)n{6Kmb)7flJV=gI}m{?7vIhRAMQK48eK@8m9$dbY-2P~G*1S^^5D78ES`-&fhKav-%EJHbjevvwn-yF{8pM63;d8m4apL zjHanu?>eIsVdwX2W73V>%`+=pweoGk``nRN#N~PLV}ZrujJ8YM>Tx4Wo#}EB=%!Lz znuh5(W@5CQtBK*7ukhjb zdGO<1;izLR}!8$#OZ!2_8wn^Rg2Yik6=7WMP^L zh)8O0Z=!m)AJ5vjVtIDf2Kd-126L`yL&EsKWo;~w0+}8&S`d< zC#GV(_KYjq{4zrLCeT(OvXOhtHS|2qW^sSd5nUnP&hT8BarzfH^oBCi1_VWHM0=Xz zWAc;2-|ZmjqWsAro6D5rw(_MO=>APkPH9>=eY%;(X7RnUhnz=huLPSD7FWW%EnO0N zY7c0>G(xAdvA~=lxJ@+ie{Od>KASvs`nYoJ``5p7f`n;GaG;own2|{oSZ3z~jEp-H zSK{xOq}0 zUJ<-hJ!Ep4brtn@Q?e_(M6>|E8aZpbj>~*xC@Ti`()+cia$Mz?U3cF9mvB*)?gbzs zwayq9Ssx&i$w|jFoGVogL3xMul!O*<=k4!YKQSIn<@i|Tw zB4Y4ZtX;D-OsI1GWxR*j*%KR9yw3pzF~065{O>4ETQ84%s#5%Y+jEPB35D&7q1N(S zL*YW<^vF-MxeduEOy@tI{i!`UVi|_d?$sPld#qlAkFC^wWaZ~de%kZ5xbl4_hJx`8 z9luR`btLduQ~cd$Ld`pYyxn|->`%|jWyr`s(FqxLzR4#&KNO=!RyL@4|6+9}k=xW= zM9%B7c9BE_7^;`7YRI5=z9YY4f>+$Uyo8Dg_3L&_Znl34V0%-vI09r}yx1+CZ+R$Ci8jUD|t&bVBQ9EeIwzu$Vt?e6WJpz{aPic&XGSh)lmU*K|kr|X@+ zE)uya{`>0xUPGQfq6HZ^I^#bZ_xBEHk3NxXTYYI%=k@cAJ8{3Hz1@6 z)^Y1^27aD&uiLO*yp?E}aqlJbyuX394JKOBK`7Tbei0>Ed2C4U@3YAGi&=iq{pgdh z=yLQohmJr#mm1@Dc4yA(LY=a^b>ICoTmw4eDB;vfPU0dUr%5N?B+i_8yvGP@IN}qo zK3op!!H&_eVWe1hj9o(`|IRk-!J-|vk;#2u%o-u*rNj*_fD`^n#zV0o@`f=(82?c& zwU?(G1zx2LyLzbchKE^Q^-8+==JZASjpcY`7Q9MkXt-=EH1us$mm!D9hEVCDe_D0x zb^O9Hu6e-hP28KJ)%`TFUP5tUteSBpP3D0ROA4}9@2Bzp!wNlsCCT`WW)omI7wgT> z-j%Lu_+qU8FAptQ9^wvm2dZDo@F045ZLCr_R){vZ{N_oVFPt74}J zrEiJb$Cdvk@1DL){_%W|>{2`?N+aeY;4}#VPt0ZYfDe%@kF5~{oCfY*C+ITJ5K@&*f$0vrGMa3dAi-lb}4ym{fg=D_YJX(M4dbiw;G+I*H9zr#PI z5M#(?bHlU@owlP#(i|N(^7w$W0bm}{BEPZp zdSs_HT+|0|?kXtv&B;0Z^uRc|QI4BxcW+O*N7WOCTb=>HhO zk)7ci4q}SQvC&V67Q@|(1<)yz)QI<^Cv>D;hRo6)T-1&BlFj!tf~7s$`lz63T-nNa z&JxpTF9>228w&;@!_yU<-X%__wM}bg6z$tD;t~VQE?V*Ai3EN@1vv~Fgm`jH^F70+drZF;OsoKp0?VM8*s+r zn^UFci)J-@8=R_s`gxvgrcd>3k>LmVJ6|a=TYpwzoncNdR%aTbqBx?IZMVRvBa|Ih zsS13>DLGeIrs1NCjy}gQS>0KzFvBZ)5Lm08M=N}75S|JLIRqpjbysm+)mvJFFg*G> zKyqQbaL(?b<@}=o*%PBy?%RyC`*kmIX8~ z-6uK0G!kzE$l*)jiD7nU7+IDCc8FvvD0kNIhC~Ay&7x{WwsSq$WCg8e*nqCw4Dh;k zXp{?wT*P^rHQ!WsVV>j&Lzhd_7WyqRvTVyZWSP{&wD$O~;Tj$~))!TUqb`%R!otmT zFFC=XSml0K5f`r)x;mcdq>7G*UMPu%cnW3uZ!JEwdOkdE`?aV5V?X^mF#<^!XoPF> z-Bl#54Y)CA3g00Om9t|eMo&%l1bd#w5X+64nxi;fi72vz732m7_qG(gFc6GgAH_s%??lG4Y`ue&E z;Kh}IE7Fl#A%f{EDg6+MPvD6Go|aqr7~ z_Dqza!v3#Sb$HZWLyU}qA~dNUdL1Gjj{FeRPz;BK7{PX3(vs>dcv6Rk$a{!y~`c`7noM5PQ=V=e((=A%wvO zLjE(T-EF_}*IVI_=N3E`)x+LK?RpI^`U;@_$f>{(BQ+;9{U$z>3p^48T-gKPeT=+a zi(0S!K*a5zH~v~@k0}(B$Br2Zmi{w2BxH*|*C!URo~Dj51veP<`M)zBKftlN8l_9V zQ@UN-TU0+AKJB~`4E?5~Gw&ovJtdJR2<`3CeP0c&f1EZ6kX_cq_em{#Wh+048){0B zy$uTRCKK}h*~3D;?ad>7QKuMmtBXVpnqeKH^OE?m@+UhEoFSEv7^%1_;XWg2-GT}S z1*NPb@>Ccx=_O`WEgDwEKdK;-a7A#u(%tO z4K@kM#oE?_M&?N^?d6+)1k#~!Esw3>re*0SQp6OzR#7Z|+`@YXR9gT$EWetecc6~d z3Oa<#+i%2#po+!SjJjupYH@UCHNQlhdxa@BpP~<&L)Uw$RI9?x&CTrN3>S)wA>> znp`bFiCJMMOw_Grh(ZWAF5+LEn5AISXLhrW7_}fhB)|R_zyzERV}3_oVIj7vMEI^M z1vxkHt2fS z|3>@3J%d92rB{HuHGek2aDT1n#3N+ed_*1K&1$ipsoo_WWqG`ymEIsa-6am5QZ^8L z;PLigPxQ8Ck5B>@{G5a$?C_EQgPt#;F>Ehp)IsduIJLF>ig1R(YWSiZ?t*lVT5#&v zG4g$Hk)v;9+8`Twvr(m-R*XC@bivhA$bnRm?Hge6s&M2UKx zGlhd@F2C=lQ$)ojNJAOMX}T;*K5^(J5eU+d<@3-Md6?b?B&F?y<9&Dosqu`(aB>+iN1b(iT<%K zl)}s00h5Q~%_T-&^QX&mv!fP*KXsx8@D?JkinZ zgYqk&;K5Yol%vFm7?JscHehv-GH4u&_Vtp?t0v{IC@8AyE|_8n_%e)*kg9Ig)(F7} zLsHEns@H@17cH&$)%W?c_LpsxnmU@tE)`4q{g*SHN->#Fk?f}VyvZpc39fyPLOm&A zp6I=N7AA37Al-3pP2iz(8HdjnDeJGE7`SeVF9dY7ztmDo!^v%yBS$1|DhA*=I+<1S zA-wx(>W-iF{%bTZ)w0mu26nW-Zf1w`nX{SA!BukGQ3WjZxBL2NEV12Rr z(q6WJFlGb$ZH{7N`g-vPrUw_$oh3a$X+LkWb*z7KWN~u$Q-+}-xLs=k8)EQ-KV(0+ z1y_lSnIB*(2JFl4vZfZU`D%; z(O}+_NtV=Vvj0{OVq(V8s}Y|!m3`!eB-2tkrjik2myv~)rOHG=e~BY*KRLjg)IA2E zQ%(R<3Y$NmV*RYW44G<{mmF`S-MO(IfiL@u0U5*V#xWE$w*b8N|NDb z&HwX-g&wqy7%)w_^ZG(P0qGs358*(hy?um1U7ljRw_!sX`fbQ`06FSEP`VYpord1) zkAmlZBRmuzpBhgVTTlg2LqeHM^9NHPZ$ZQjEUe+U6SXpstOS$`SuX$f;HMI_=Sc>9%%utw(h={3%z|-(QnDYS^&?iVC22 z?WF$GHnC+Su?Y-3TWZEXM*E@vyH@xVytQzjs!w&p1o>_AtWgw{!aaLc1u^arf?or3 z6zfzO+v_Rk4uCWUy4#}^*?7BjE0Q(MPIFJ29vvEeT!43GNZp!1#J9$_w)%#a;lZ~f z7!JL59wUs+kWTuS*G@S&R5@G_U*7b^!scduo%Ptn&$G9BgqxGD?d0D9+TT1Bj@+-z zk>ds*n2`zgXLdh5Xl>!}uNUWWqFs>t@83m;=DNIbRu^gTE))iEcI?}?5A^`}CNAzj z($x}BqfX?u3ctn?ibkIvoG$IfSZHcHZrpsDrXmgy2#krRINhLO`T_EA|2iWhG*FAb z!BXuw^Qf7Ze!9JbBs3}0X7vx6qsbzk%ply}&mH3a!C3TmzqvOb>h?i44RFFG^n zAg20?P7{K5qWWyg+@iu_wObY}Jy{I;q6-smfRPzCQ1xDVTQ|mirIDng(SDF7OBrTY zf1Y5SiNZ-Tg;vxB*;w|k>|*+619-zH?R53jTHT7I82FU!vcj?XjfMgI%{*S_$pG}i z5ZlYL;b_)-t-!%}_?k%HSeud_#Kd<8efo09nIS~w8Fc~G) zu67`2YvR<)xPHTm!2~$?gnPZ`0>59{Wd0_#^`=_ezPY=(AbTNXi(5d&uutI))FKh6 z$}~JtXtXY%f)m5(IGeZZr7T6k_LyK=klZ?+;rO~tuPpGY;i3Dc$l?W}2-57kOs3@l zeU4#zDFMIMjJBzn|40uXDF;8s0R>uc6)4$)jM_s)j=gXAm2A-PlEf}LvqGbCbIwJT z-vyKP*V9cg`sz1|Y0aATeQbMMds`~IpHVj+Rf~~4qeSix&^?Bd#l(oGnp>*~xh9?o zD-y`)tJz43`L5G$5=f;kEyHkn506y_d zFPkChBy0 z`{JW=3Mh9p^rfZhUOaNyCD6R^IW3~O5v(~&V&)BX*MjeA%YB7VcP0>FryfC~;uiS) zmvTP%{wg8Z@*vHd07-|(-#hz84_94r4xj(feP;?5`*ni$vH!WU_wyF?XRxAc*--iC zwA7)&>HY#yZir}YC$wQ}%$J3=DwV#W0XT?${eqGQJD~1>_V#wV4{!wZxr#6GlA@UM z_4`obeeK%j$2dmcjKJGW99#;uX#hJN`C+WoWpg3PunK4Q@$AjL4r}>j8%V9`K6CMF z%2tLKSupj|Xr*g3TIOstZ^^t(SzF%U0vPvax7eF?3bmU7EyZov=~p7?c!5+_)%2?E z8NLR*vRHw*ow8)hP! za>05@KZH`MMK0G!eC`A$S)tLfsL)L?O!~PRSqft^Zj7{+%WZ5%fT(xPz&NEMJT`98 z?C#zM16s&jB_)@$5M{?jdsbhBobJP0luV2K*vxEdg^{LaHAZX3)zp`nMm%2}g>lmz zHpnm#U8;NHm~>2YONE~q_0B(bN-yQVXKIHZVJ=NB^*+?yq&pkx7UpT2HFJU2gs>*H zG4-y~m`sql`V`Mh;stDgZWebE^1Gl^>L9WPIH;Z5eR*;ht@(fasmQC(LmwO;IjmFLO6X9A*CWONJ;bfc9dO*jN|sbWyugNX$jqd zw~mwXP)vQsS#B&#a^iPUyyCR}YVI;86^GX~HE=!b-s)#;FaG@mPHY8-TH<44r=I;C zHR0Eeuu%Q+?7eJJqK5k46!yV?3&T&cm{#Iv=Y?sI^IGzD*}HcSy!Vdg=L&7)ADeq# z;+=kXCBY1w&Nd4F>Z+_s`}Vr1sGtNRB=4Q5YiQLxla@$s)7%w04E>pl}nDwwSGjV62tD3zWEh#;$ z(|zR7={12_KKQck-p2DYIY)?<57kD*rvU5C4_kruFLt_E^ao~W1>VMEeY_25zHc~N zm=}0-ZBlQ#9me(a``iRyX3(z73G~ICwcUpDq|NnA-b_h2$Mt%MJARv^wVErohL%Zs`qrrInmBI)-kXc*LVq zU!X}l$~`13+!q`2bCg~~CYw50aOLYL+KDj1G<0zWp|2+@X@r$PZl265bXmM@6RadH zMXaEGg& z^eDs2n`S_qN8ZI47KWIO)0YtnjzhPuV9-Ym(iwzD+72a9oSJY_F0^<%{NOSWoC+OB z{O2Q~W>UW+B}cPCK`*o1PJYB5<-S4=2Jmi(L)Cw@M{l zTjjM+_77`?mk_R;7b>DB#vqSve}6)fGr*pCzEIF=-V9=CAMxf6LcRsK;l)st5V)UO z3%!4rrg*8f_3cQC3qBENXn{=^i#h$BVAE>Yz(pRj0)41NRvjZ5oSNe$C6zx>OpMyg zIV9A(g^j4*_2cBA`4F6wdtyV_5@pL-x}lSAk4mCQ=mp!RR5-d%HQmm#r6=A z1$GaQO}^P+?kh@0%0PE_d0-;Cy|&1?JU90+7`*HY-g&@<%ce)8iOYWy`p4FhCMLM< z-ae^FH=V!0p5^S!uw_Vko$V}uE-C2UGRl@|1)y_I6A0<}K8Kt?1^$M3Cu@!j|LnE681>~4LS>*dKDZHb* z-bqPWOQ#in_pK>R;g0PHaM+$q?o2W6xI>RfZEz4tfmsgG-+ZA( zMqYRrEn3@>9xHlq0QvHleSHCNB&fnW`;cnt~}Gdj{plOn>y)R8K9TX&-3IOIMo&?E>dm~k6w@-2(3 z*C`W}_5617BZZmAR}G(7S~z|lZrhwdoGen`6{0CmI*<@1Q~&R!_{}9w(NiEuD*+uW zFGq&I2y5YsleJ6?-xVL{t5DI zAb$?nx&{qrOYA7-h3dvP!JDOt-^hb$6Uu@~`VU<-^!vwJ*}ZTvA6oMnsoDq!tz<1o zeeqJQ<~pqI)%9-#$877AA^yveb(_)5E7#6^Ocp<23yC-12uHqK^S!5jj|=g@Jq6z{ zT#tpnen;-zL~_&Xsy3F0{H&14VAR5kztWm9mtu1k%+7@kiVLMgxw$D`n59%7(e`Ku zw`#g946}2&t5QvuXC3XIdi5CjuoBI}8J&ah+ip5q=p*-#;$@}ZWxhB0W$7?-Z=so4kl zxZ*)YnrRr+uZ6nOkUa}r)ZmLw$Pr!B3&^QE$ny0#)M{~>iV#sl{MVQ)i*Lh>V0+2- zu9~*C=aZvv7^uygemYBZ&IAQR7N(~oU+3Zj2R6HgGFHlj@AVAzq$+CYgT6$qtLp0N zPYz&UF&X~6RIH9@a3a=ggj0a`Lw9!U05(`6Z_7=)N)f$rxalhXr8|0Cq$MNg<0s=S z6--wWH;NFowiDR-qth66{t|foMl&w?zCe@IG^shebFT4bFdg2za@-tqs768+&UE^X zL}x|Zu{yFRutmu_{Z@5x?qu|Aqv)eCh3oLgxt6tcDlb!KeX;0@7W4c~Xl@pFYiCS3 zhDIj`1kCK@&u=_PT5uU6CDvWCC4Z@a)md7sCm=_LG_CVod?4*NDm{68S`A_W*{VFm||C8q8#%Y^} zCtLrhWqg8M>3tkY!TFgD@4xxINZ~s|dvM_RHD2cGbouKdoy#L^o4?${IXWwExxj}K znCGMa(YuP_wHIhK`7fs1sdhEZ#K03!M36GOLn{ABG`+wZa^Z%upo$lV{{2C+yeirV z^Uc=ZGP)eSjIGi`g;pn(%{qe+SnND{Lphy`9jT>B1QKF_&AocnX*Ax4It}mVUwYoRBTpz% zDy0H64WUGukZ5yw8bmEyFcD;OPiQPdS2;1)Nd+R?vnulT2;3!+e>USavw@Aq&p}GI zqbnPbZ{A4CDIG6^`W9+eyoME) zJkbArIl9%0uU%>)$kU>j@WV`{%T(WT*S42GA)0RA>c*DS307NYrG_*q7 z;28S-`@yjtfj3g zI(eF~V)F0sFi_M#BIjp>6@#|C2kWQ$B%R+kt*?v+$(kVjOD*uz^vcF$2Avt_WGq5Ef{p{Dg zkV_WE1IC30g|3cC|3NRh@E}})x~BsC?&jjc3&(E{xUKxqkbW$|xtwJ3yfMcPDnZ`7 zN1w?Gr?NpeUMt|JL^c62Z-tc^MsmEAQUg#~-30u4HNG!S1RpMqA zd5PPF3yO=VeHYf_z5HzIW~-!K=IiVHtcgBMihY63U@Ts4zv|9pibu^M$m&VDZI0N? zAs|q5n)UjKh{%@e5^uqoQX>y4u-kE$nqGbEqRlBToUi@tPG9j=jK|ejpG5hXF*-Q9 z1+88n;Aw=T@QQbo{6^sH1!RshvgW*aB|l82tt}U;p-*_F7Z%bZYeQBR7Oh!}(^OjC ztWD0kVEn6RaY>9kW9VmzP8Usa*gjhL+Z*9QH zEzHrDtHq<2D~4#S;xv%a=7NXpjim73PY-Sl^&Auo|M-d3isxKJW)s6>RFoMTWa@@O zq-ryF?j9w0X<5hz70U}A`T03}Z*aM2;OEINBo6NO812Y2LfP(p?6*daW zpfgmapq*hS#w*pjs>d)Ef7q8Ftu6PAMxRZUIv~9%|DOcpE-cyJb?}R^?)c<18~qj` zy>urfTSdgQqPu%`zLOI@l>z1&) zMljv`HPLnD?YoI>&=xx|4b<^JhTA1vMB7QE$fqyC0Zl^g0p5S~Wi~I-8Jw-9$ftMk zi8=>_Ys>ql9DMt`T&(Atmhs$iU9n2Er-);#CE~tp1hyUK-T}N~xyB0%@0V=j*%c4KS=;MuDjrSHouin% zctWQ`r$yQ#X z=!i)mNc5zpzG`LjOEgA@*g0p}2l345) z-zA`U`ekok$Y@@ke4%dK?5%4_9+#0dwoo5>PKb#Y5CZ+j>D|O^il+z7$f&oCH$fkF zv{CU$uGXE)0>bBLMZ<5?kNO|w6vv9wvSyK~7c1l7cj@RC#&zQg!}YV^gBp9RY%U-d z6J7q~HS4IC(GRVm6uO<$+5T6UbZdlteF>nxDsZb`D9x?|xi{ZhIt)D8GW@KyA19-L zZE@*?v!mm)!^c@E@fs7mLd0pjj5CK%K!Px${CfZ{7TC+`xyw&!Zk`1+**%5m{?knp zy{{-vd+_^Yn;H7E%oLfEB|a5_y6!4$=Pqtb4?YdObw?|-e14k=cnQ9PdV^`EsOtx?RyC874+a_Bo*Fp zS^Iu**df+33?9kAit7^9UE$L);8|C@EV?_aOr@7`9SVaTa5Mg$pxOa&HZ@`}_jtrfBuW?^tKSMMG2Qw{yQ27Qu` zOeY(aAd726-pkR?XK7ov4v1HRGiS;|43hGR`g@SuX}svB7Y&*bNzSvKULr)(L4xK* z(4@sp;}?Fq?S8ctwqG-km??7C&cm2e_z*B(9Y?~OEO?L0U2fhfI)2PRR~yK0gQzdv zo3v_$BY&syn@=5wH>{le`aTgauW$Ib{^NSLlsgJS`rhq5@nEDK-IN>imC((X|NNTB z>nSu7JTft{w`EWyZopu~3DV3A+)eEWpq zP9(CYjTgLEpn`7xO(oTg{cY{X?+s7(jT#GXRopppK`Q-mad81W(vKYQze(jCCMCNP#m$&)()l?UlY zxv>5*TF#ws?24lrZOw%-zuB96>k+{5Rh>EMlv?FrH0RoG@twj8+M$4mN|Wd&eH&)2 zziX+1m)D;+1G7#UiML0?C%d($E#pl(?$RX7-!}V_PyNV~byHpz@ya>ZSd#O2p{_e? zDQ8Ql*uuKwu*+J}#z|IaA_t-xm-6mfUR*~jY|bVEaa}pRY<)PxU|>c-pA7=5IW2Nj zpA``u=7!wgZ%r^9@D3pIV&WDp6u0T8+-qzG)V;|P6ZE;8m}uAehhHShDAK9CR69lN;lK{7ynahPI9l-in?pKR#=PRvV)|5}=DfJnvSv znzr=_NJ)q9K1D9i;0yZMNhYQO&l6cBFIqj(dSENICZ@p4U7r3Ccf2;uQ%nqx zPmh;#=4P~&1Mk8S`>!R%2+1oc&RlSMjzpXoAA4d=DDP{pLNz^(bJx!;*eL49Q()60 z&)(P~M;{^=8Qnq!(dTxykyIb=!pxxai_3Ig%i@TU9ltG8=f9|oZN%$kRNHDg~jnmD|`Sy9sR%fIK4`Qla?c&^11#i{UcWEhimP; z<5HV8C1}4q8s%F`!lO}0+JpfH-5z3JL{qG!oeO1X(>=7zq=yU>(?aX%{ zPE-9Ip3m?3+aAR!Iueuv1`hyZ33%-liueOv?}l6V!E=?HH(z7#QxK)w3d^(mdun^c zAASji0;!kioDcfiiRVekzX|rIiIx*knH9f7&iJAIu)W7uNZ#&6zO)4uSTVU%U-AmS z>t8obkJsgw0Gmf$>f(_ZG^>969=q;Z9pLDHegLFy68w7>JOV?jGJ`=hIZ zR1N!QV#lgz%^gnGomCde?yi(1Y^tbRU8cU;Tt9epND`eZ(^E4)QR!{&#qLnt$w zkFK1{ubUoXSDR+Oj}{{t{u;w4tiOF?|b#q}EKuu(~QYvb3Gs+o+bT?gDQlE)SPftGoSWO_&?- z#>n@tIF++0rZadG)QIo&uEZ&m(oT3Y1}t`lcRu4Z(rUcVkpv&z=G*=dzrc`&Iif13 zo+k&!|GuU2hC$Pre>2c(H)>L_ z9@VbJMbz}7*kbx0Q?%I21*Wfv%`B@!fSXQm?858FJ|233#j66PHb0%e3elhE*j(GI z;HP)UA2kdQq#?Vn_ONS|%5QojJyD2RYkBt*)l@I;86wY=`JWIkUxFSyEcRDo%<%tL za)X4f^WL(M21^xw#r2Mlj#FuR*Nr&KF9)CqRc0if=MaJd?;8n4!_Xg!8BC7o)+&8O zT(K~PKLSw3*$1H&zuo@IHJ~fitgeR?2MInkj7-+@`S+zf&qp5Wf=0|Vu@;Qa<$pJ3 z3TkRWG(C{R2@eR=X{6UAErZWa%b$KuEHERPyAyrNGtOnK30V{vwgRuU z>w8H?CO8{EpH>9b0WdG*&BW77fg&Qk8(np%~waM+j`itL_F#V78~Wb){5#w|Ga za;87L>T?Y8G`$c;51+V_Ng{I7z;_>L7`#MHI>SV8=<}^8Hr&9CiK$1-vXI>z1mvKD zzi}C*yB5CC>;b=O2~6mcH9+LraeDO`X2eN2)W=#X;bkT)4ZKQl-hfUrO3PKT-}i>k zqZe^c;<1+)memL=A|77*Tj@9ZYP11umzl-|cAJ`ep?c{CT*K~ey(O1vW6UZ~m%8#PAeC+)1Q@oqmL}qx8kX$()0%^PpP2j9KBHRbH-N8pk&R_n0&j1(_mROgdT3+DpNVHXt15T?Vd{TRaASF%M-28%2bOBDV7X?xAZn|0LB+COt z2#>1pw%}e(pJKhv+{DbT*VL`a5F?6E^jiVlI7u644w`{9vEw?JIE?U7_ErE~7 zYLQEJ$T2JQ+ewT=<%sTD4U{5lB-gtLNSH7GFWPd~cHp|h5Zq*jBd3CIQX+ZNU*2v( zkMBWyJCQpk(zJ6$*-&^4p_ToD4rnhLtUKe&agno~tFX_wQjJB(ErkT6d7u zdH7a$jjllvuqVRzppxKla2}lKblW8;Fy{&0Jn)HZYHoNnf`dPJ3|+xW`dk)W9-<-M zD{9RW1xch21@O3Ux|64Bgd#AV#s8<-*$;f)@?7Il7sg6r4$vjj^{-kd&kxbihYjXGEM5a*TGkk+p{&*DL4mVV&1~>1W#1w)}Iq*v%A=)!~b7d&>ZhW=v)g zBd@Sa!TMM$>eOiYkk@>TX#s>1xi5g?i_BhsMm0>CPmZ_%tA!YhvewtarJA!2sr=67 z%ABB12YhT*+slS7tfcMvC=ACaah}q(NIjg?nBJ5j&YExSGa>%Qkk$|$GDADKN0#PX zWLo$eR9+^TfFst`D@5let&dl7eV8=yHv%Pkq zuD}|t{8`~|q289heVV4}^OYo6xPbpWc-gX(XX(4rT;+EL={>%Len|9q ziO5|Ii9PT^wAdV=j?uYHGZts-4 zDPo~>YyK(cQ>^FnxeNGj+5Bzlk^;i@-LSO_;tdF_=1_{zHxJ4?RjH(#t9wzR;dWCt z<_5hlfrbX~&fq<8v1?KTC>JN7_o6Y;gC+$z%x@V1d3+o9h&CWrmnsznC4XMw3Wfh% zR_wX|V5lc3XepLBLGy_j`EWP!Iyk|Hsj}_%qr6fBf369k4NTKF^_?&lG9dMxm3WlS<6l-O18v zmF${B6h%jrvbyU|r7WpdDVMVxvW`^BC5ciDId8ju*Z21yc+9n3pU?aKdOe?`b5oI) zZ_ap)d@b-06Es?7YBC_HeP(88u`E0h$*1s=jU{v%|FfmO@le}84|L}d3{wS~M-=hy~7`{h!&T9_zo+lmX@cy_+cvY?HyJ_XBNi@k9gEf1q$uOc^2}?dZw=yF5 z)On#{*ym(mV0PvSw1-gkk8s^0xcPf;FV0!{WbfRC!fX}ZGZ!&tQB`b3N-+QyCcq~0 zh;%6&n0E!AmlOk>tOPnKysD$)8ig|9xD1}NCe9ETtix*I{>1QJ)v7&b)iKj3*`wN( z!Q;5g!qzobx4|md+rry>k|Oyr&{NJr1t8goqt65qE(sSnfQhPV-sIY2E7+O#Z>}BT z?o_7#xGWGS<5EBRlw}fIJn}b~N%a_#LP%c^PN`EV_hb{*N}tRzDwu>1kK-Yo z@n24=-OV+3#(O2#oIFSx{B6x=saj<;sW?4nhl&rY|K1)DaQy>T0!9gT%ME*nqcdEd z5f-a{Kf;rxX{kEu)uhP|cz{)@X&JWO2WsAXflDo72i1(Uq?&UyOd4~QYPW@R-pW!# znLcpNuu#|X-@Ck6Sb+Zo|c2G5YnD{2!&m^rI*uTJ)U8E=}ZU;iXbiIT3U%k({e)0k_2 z*vP!`A*MihXKl;hypzOzWiEA*h+#boi%y@~;<~p2Prs>2v*MxpCinLfSqgcTVJ%nR})2hR_&yUE_Htt?l=tH3>5>H#;ZRHrv^`CyQ9-{1N~=hEY&W; zD;YwsFPJM-p|4F&OmdU%f$OPHKcAsjnq(DC&^#Ox8*CPL+&t(4O_=1Ezb$9Lrpx`{ z{;NCTNhc56)e6w*tU`TEnS{Kr5Ing&GixU=lES)Tgz0f!A#f^i3?yYcR zjXLw@S%oq0wVlk~R4-!Va0x>ohJruG1KW1aP)fh+v%DA-FPei3dN!e>Usp%i6o*e@ z3ieIu@#wa)b1690`;pZwj|UIrWYeR68T*nK*tqciGP?iHkgE#+FnILeBMd658ayb+ zdmY2&t9eU8*!@-&r_)Lg_Io#TeLXblq-CjRGtiu^tqpEJ zU!%fsjK*^8etctt&;ZUBMeeHXi@#Z5*~J)W!5!4U+l~`Q0my_ne7kXHt5ZC zuiOZ|50d2z9gQ=|)b^O>o1(&Oktdk=`~|+z!0+0W;*7JMy17 zGIl^?<3{PCS`^ZkEAaP>ai?S`*+q?T80;EO&&3N5991Y<37@(e)KIyd9N7E+NT-nEBAYHtSZ;0I1L%PkySh_V3+Uk#G<0=*M#+NOl=y<_YNp<(LL#reF z8C^RQqT|V@=%6Xcv>J4g%_DHH(sr)FuXIEzQsa^SpF5dp*UA|{-jxJkH%*Uzp0FFm zpXyG3bq#mfXq4=!Ich8+m>Fz``<$Sr<4U~q`9tHqU><HSx*;xQ1ww{Y@}As5c0?N;5RId0KzR2SzHwnU396@IQK_ADz{x zA}T6@sPT+-bRRm?I^Y-XSPWCPe#I;l2V}kDHom^rNX_E5-&O2!L$U>8eCD5yHaHkyU)<9|~{j5QP`j z*;I`}+F$T~wXi2?=NGU;MI*XlyP%3=V6#il(SR`2l6(r)d zjd}02WnmN~^BlY$_;jnO87ThUj{vXUrhpsO;rE;2kB5VP)~dWXY~Wo-Fd>5*gWx48 z@Gd=gM=u9V7wIjM?2E~GM?iZIoxq(@w;)am_{NNP7P$AM<{fpti!yK<-J!jULw%Jr zJFlzyeDkN8;wlwqw)sYXDwwb?0AukuNesDlj=9JNEs@<49tW%KrMWZf$6fED7Io{W$a0rbb4f1g(5d6}Pti)+gu^s94W%L; zEusZB>s8jbxk49*xs8IbpKWQSDR7OtJ;G^Br+5X0`L%ck#ZhnhTHG+KUz5!R4r;NN z&`3esVBqHhw!^v*-IppSO?Vx-xQJ@uzlDq4FZ=gp`59M;K6C^{5|0zo>N~cvic}2Jg>?xV#t`@!DdLsVI;JuM1bza=kO8L zMKC>?`$`Eiy^SPn!aG&MsZd}glWVB<>9yLH4)CvbX)`(aMcO?o{)(J(F z0Zd7_@F7*t9Mwvmwq!4LU*EjHqCn}|t8M5a1NNQ2nDgX3DPmRQG}*hCTBUyae6I`d zpPR1ashcH&d)pSdSl_fZzb9u@j;Kp+T`=^tn4$4C2Ut=}&ipwKdm3x@>qUHJiL`qP zF?*lQar3=H?eQ!ne;mM$Y2&H1^plIDAVW14W=>~Nc$dx#g;Ap(dM%<|) zfD(w50pev;PYyfi@95jP$S0c*L49VhD$P0`nYltuDG>l2Npmx%@795*`s8vj9Ea+6 zHO`J}OERh6^C?wpr(363dCXh?(7qA@uecaxfl1P}N zi_qUbJCL;D%vr_QJr@7Qs-`)<#b8Yb^%h*d2CVM~j1E}2T2sQmNofrtVl z=;2oG+OwR9U|avHe~;u{NwZ%Yz%4nUUK~>hVb`tc$(0*c_q7(}6^B>B#yZfzx1%$& zM9GC4geaVf25FD+n6bhat{wB(2 z)s0pVzNpu~qE6^rO_nRMsmYQ_{{GZkCI1 zYu97Gw;c00t;+EDs0$Y#MDlSYlnt4`H?k^Qnxzk!(}T?GD7$$xj|7F=bfg?2s#tx_fF58a`gn9dQ5ipp^;~3{%pAB zfLrZOY~VM9o_<>|$oMJdWs#U&2XlE3rCKGp&>8m1l>C^&{AeAzIR?GQ6gZsVSUJjw zz`4OY%md~%LE4nkL&`sk$EQY$;YsVBHNn2fp#+w%+k;UY#m-v#Q;k_r6f>hI zdz-xCxtLr|GD+e`;~mBY2ax#|*osKK(4-B{sf7aT6$G~VW#hQF~s(7ZQ9`93)t6V~t5VDD$Hn`%`K)VW5rDBqS zi+oL5u?0S@j%+&)Ymj6S`;cTatWGZb6%p`3P{rI?r8QQPD#ze2pfQ72cHA(fR#RLN zMbM}NIts`WFIX^=T@mMqSW1MX$V;U-;_X{W{rdY|B}XAId(vLhX- zECDJf<k6QSRbS;(j)1v-hiB2DAwSDmlxDGH@pOQx>G0*P^EI^#^9ebXPbIb6~u@m0Q1qT zKA5=G@TPQ%#`1kovDd?0hA(#j%-U2K1R`gO~!qXFKM-|FHHi<8PMIz344yL53ID%Yr;kp<}j_OO0 ztfHcm>H3lwjX{if+fw@3Yd6^O6uv_>9*?j95D!SZ~$3*Q+q8pyb3~W8O_y@wjSt7)}R&$`CrdC0BgGK=LjQ`mr@2aUIQ3 zr4OdK^Va5^`GZ>Z`}y?e@ut|A}UssaHzb4}Y*?+aO%`SjI=<8^S2XYQ1*1 z20x_6K|KhkaiC7Xzfl7ibI5lKp+}xIeZJ-jM;XY;&6pB2#c4r<027#h%@6SBROoGs(I`Rr-hZu`314AQg;6>Fn*6F1hT z-vyp1S)un;Pep|};n%%{q3;Xw5kdAiTO?qkD);R>$+;RFt|HuhM}9O_$TmJ80LoX(=Y~F)j6{YNVn+47ec$oHZpli#sV^Ar z+R+H?{N;ut?}bjkQZJKnvTV}lGDKGIu)@VFEU8gCpX_J_-E;u8EyZGDUOx1Q1m^YN z?)?XveFb*X(-%t9tbZA>$&GHzdI|F2g5o!+OuwvG*cZ{f7Nr_zgEA zQl3urwbHn2ThwniL5kz7+%|aYO#|UN6?nnVHu#|kK7i7YwBktEza0wH2J*gPL(9fj z8q7X~*2h9l2VrCRkQ6^S?q>;0Kn$Lc&nS%d^;_r}88(pSaQ!cBFgN zW$?-mA3t)kjzK@>3mww=?<)62`aYqUG?M;7Rhhnn9d{llrMihzW~cSKUgKV2ubn-} zlASPR9NV7ZO)%B42q@k^GdV>QGwR@1a#{aMLyL%by$mtA2-_&Vy+m>3{Tu)P8 z33PDp_MyS5l*#oQRx-MHq%XJNn$dm~jf_Mq&^b!?y#~8^Hyv&eir13Ant@f@s9i0o zihlB-pC0le8gXoXoMZw!Bo;z}OK=Gy0XPsLq^mLAC+6ll*hw!^M<*BYo?bes%|F1B zogyHgHgX$OP009r$hY3UXwvFoXfmFa`d39#tshnaCZs_>wZn3NtWS`?4}2#a+T_hT zG%Nq}j0Ug0hl=V}6bX;gxgA>G1VZ;Wpvv=aCssg+S;79ZOltTAeqk|QjkbxNWn&-j z2RHa%H^_fK91Hr8PN}Al2;QBpwMJ0>q)+7^ioLmLnOc>M=z;V-Mc1xeea>%!<=N*3 z8)eF(^XV6sS!C*U<)cV|@pWHpgZ?je%-7u#AzcRZI1 zCF}}%`4Tu_Sl^THgAbkW1}zz9G~$SOt1?YemWox`jM}6Y_)c9`K|S28(RDKW4dH7S ziL7CZ^FLvdABCLJ3NMh8R|h;`uUMS#m!Y5Et@`vOO=nfE^qSr;ZyAB;9fbOCw+WKD zvnifDyH~*7I4(B-e+WD*J$AhIVl8TDz}=v~%0iN>>;Ue3J^f3I#Cs3iL%6yaa_Wit zLZXS5xTTj>br{^_FY-~@lIr1Vxf$ci0OgTN|7ReVrXZiq)huB98UM0hIzdh*Sy@f* z-p4cda5di^mYpfP`v>g}MXx?5A4A0!3&S)4-MxGF-Un7*>Zyhb7QRSrhh5+r7mLyH zxxhz}@FN4>9#h`xL6?mP3D;P?GZz+qV4zqjfE;mCqxdB;;t4+dP~G1GQ^_6!iQ##A zC0A$Pym{Il;=5|$D`>?uz_|){w=@=Cu9`9I5QXo#2LwOP&G%o+@jrp!^aG^39i;Aj zX~p&cgzddpU))Uinu(dYJAZnGEE=p*LsXXVicM3r?W7FYdXcyg(8E8zrhJep)t5iSK-APYv4dRPxqW+rKT|1MNeU44{zY9Bb_7#~f# zf+I6aW<11A<0Q#z;pKt4sh&vGRSuZNwam?Z?hwdDPt>`+)9PVWc$b3&Gf%=QqW$)Z z@GNw4E5LsXidBB}=>uO18(V7YXxk5hb+S5rq+iA|R4U@5qUSWiWonLn0x)MC62cO1 zQ}1?ZQ_XckkTwBAq9T4k{6?gA_lWSZKJ!R@O`{rh7uSa7{l(&TsT>PHt+GKD(yZ+a z*K*I|tCXshvy&}Pa^PSv@x2SBH7|)JG^QJL6$+Htx#?Eg$#Jybp#XaPa-8=7JeV)+ zY)AzUT!&U`H{X!LnXw`!x%d$NYb>p7g>bDr@8{J2ZXELvsh;e16r!Rl^IarO^tzUJ68Z1Y;3AgC%LuXIZ|Jg?x}k?yER+VdT{x%B&}bvD1RELwKi) z2If`{+^-n8U1Sol^iM4$TWD4B=l{7)x*s(iqOLXqkEjt>;9rF78I+SigdKEvr>7Wu z^rYB|shv<>j(rTemqitHNAlfoaHdXI4D#s$J-pMe8{B=Rt|e4leO1>@5|Z_*&F@Zh z25MlywIBo=QK|VKal9(Fqms7gJuB#-S}0DQ<-9yC1gS85*^HuV{nd+84QfUw7|au7 zJ6VwdSIy$RcU1wfoGV>VzQr}rd^m!l`9Abd-GE*CdN1>J!avb6w?oL)D<~SZwxt;` zjlpe-bv;}2GoStejEc_i)G=Sdb_0xp6pHkcv&-@?t;;W1t|LiVv)+V1XeI8#eLc_s zkLkbrO*-%t#dj8`B*_*R7=7ha%*ANs(C_=VgoMzXGB z>@x#?8N*Xu&M+B(Uq&J4KyeshSPK|7o2K}r%V@H;?Ezd1+Nb13mD@YncA2VS6>^lu z94Dt7QnixZ)8e|ls#S3WZ>;YD14|m_N4`jzQoglWE}fsjLX~j}a#D$Bxc37?S7>-!02peSBHMVb1CH^+(n3tAX)NV1s>(O95%U$w87CIY zg?EU`J2WSd-Q|}dJ362og)m_ZI#m%Br}(SdwU*mIpr5MvW$FrJ>?At}tBF1@+eGN> zdluBGP|9E8J<+B$$cCRTJyp0zZ-tvQK^Ht|b`>;nnj2M6c%PIkT5D4h`T{p)p*vsg*nFz zBpLNWt4&Eg)e4-Cx(?s}lb5wD)DLkp>=qfie9 z(?=sR61-#>B^e^JFe};i)0u}t>9j7>8q%az~8LFN-bp9n>HkF zA8g;dSc<}SKa=2XpR1L^;AUXHZh-9Jo@{5#a(p1Y@{R!)@FldW*nL)dm_IydXujH) z6c&ebDuOfBr6znK**R0hlKKN5#$uJrp9N5x*g*U0Cvn{|c#^=44l z6gAYGG5)nmb~;gd%&MhO`?^BqR=(0|RaV<}ze^%4$?DkJ3~7aCBf@ib*iD!I@*_ew zEA<~4JL?bvAlg1fuuS9FHX4^2*3&Ut#oc+J+2;%K9d z63#hSOGC1=SJaN>oRfitMO0aUhj&CxaAp@l)xS}IjOTGrtN;23dV{;31iED$R}FuK zX4&Xu*cccZ((lqpyh^MXV`&K(nW0yI0!#gCX=N91|IM%}(=es%Vv^QyJ|87-{)EBf zuOUxW;_F%bHvg&)VAQmGZN4>8x>$72ofkij0S{*n>5 W5Ec~6wZmosu*5bIRM2G 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ÐRl+h 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 z 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

l8mDIB0~!OZjXf1OsY@1_XDt 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

y{s%~s6zM`TlpOptYtu$ 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 *z~#7c$GuP4GC;>n`yR0 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 literal 0 HcmV?d00001 diff --git a/docs/kontext.md b/docs/kontext.md new file mode 100644 index 000000000..519752553 --- /dev/null +++ b/docs/kontext.md @@ -0,0 +1,39 @@ +# How to Use + +You can run Kontext using stable-diffusion.cpp with a GPU that has 6GB or even 4GB of VRAM, without needing to offload to RAM. + +## Download weights + +- Download Kontext + - If you don't want to do the conversion yourself, download the preconverted gguf model from [FLUX.1-Kontext-dev-GGUF](https://huggingface.co/QuantStack/FLUX.1-Kontext-dev-GGUF) + - Otherwise, download FLUX.1-Kontext-dev from https://huggingface.co/black-forest-labs/FLUX.1-Kontext-dev/blob/main/flux1-kontext-dev.safetensors +- Download vae from https://huggingface.co/black-forest-labs/FLUX.1-dev/blob/main/ae.safetensors +- Download clip_l from https://huggingface.co/comfyanonymous/flux_text_encoders/blob/main/clip_l.safetensors +- Download t5xxl from https://huggingface.co/comfyanonymous/flux_text_encoders/blob/main/t5xxl_fp16.safetensors + +## Convert Kontext weights + +You can download the preconverted gguf weights from [FLUX.1-Kontext-dev-GGUF](https://huggingface.co/QuantStack/FLUX.1-Kontext-dev-GGUF), this way you don't have to do the conversion yourself. + +``` +.\bin\Release\sd.exe -M convert -m ..\..\ComfyUI\models\unet\flux1-kontext-dev.safetensors -o ..\models\flux1-kontext-dev-q8_0.gguf -v --type q8_0 +``` + +## Run + +- `--cfg-scale` is recommended to be set to 1. + +### Example +For example: + +``` + .\bin\Release\sd.exe -M edit -r .\flux1-dev-q8_0.png --diffusion-model ..\models\flux1-kontext-dev-q8_0.gguf --vae ..\models\ae.sft --clip_l ..\models\clip_l.safetensors --t5xxl ..\models\t5xxl_fp16.safetensors -p "change 'flux.cpp' to 'kontext.cpp'" --cfg-scale 1.0 --sampling-method euler -v +``` + + +| ref_image | prompt | output | +| ---- | ---- |---- | +| ![](../assets/flux/flux1-dev-q8_0.png) | change 'flux.cpp' to 'kontext.cpp' |![](../assets/flux/kontext1_dev_output.png) | + + + From b1cc40c35cec90420d15e9e25bd34f4f85ac0c5d Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sun, 29 Jun 2025 17:36:42 +0200 Subject: [PATCH 058/143] feat: add Chroma support (#696) --------- Co-authored-by: Green Sky Co-authored-by: leejet --- conditioner.hpp | 211 +++++++++++++++++++++++++- diffusion_model.hpp | 5 +- examples/cli/main.cpp | 25 +++- flux.hpp | 341 +++++++++++++++++++++++++++++++----------- ggml_extend.hpp | 14 +- stable-diffusion.cpp | 30 +++- stable-diffusion.h | 5 +- t5.hpp | 50 +++++-- 8 files changed, 566 insertions(+), 115 deletions(-) diff --git a/conditioner.hpp b/conditioner.hpp index 6e9acdb19..f06f1e25e 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -747,7 +747,7 @@ struct SD3CLIPEmbedder : public Conditioner { clip_l_tokenizer.pad_tokens(clip_l_tokens, clip_l_weights, max_length, padding); clip_g_tokenizer.pad_tokens(clip_g_tokens, clip_g_weights, max_length, padding); - t5_tokenizer.pad_tokens(t5_tokens, t5_weights, max_length, padding); + t5_tokenizer.pad_tokens(t5_tokens, t5_weights, NULL, max_length, padding); // for (int i = 0; i < clip_l_tokens.size(); i++) { // std::cout << clip_l_tokens[i] << ":" << clip_l_weights[i] << ", "; @@ -902,6 +902,7 @@ struct SD3CLIPEmbedder : public Conditioner { t5->compute(n_threads, input_ids, + NULL, &chunk_hidden_states_t5, work_ctx); { @@ -1004,6 +1005,7 @@ struct FluxCLIPEmbedder : public Conditioner { T5UniGramTokenizer t5_tokenizer; std::shared_ptr clip_l; std::shared_ptr t5; + size_t chunk_len = 256; FluxCLIPEmbedder(ggml_backend_t backend, std::map& tensor_types, @@ -1077,7 +1079,7 @@ struct FluxCLIPEmbedder : public Conditioner { } clip_l_tokenizer.pad_tokens(clip_l_tokens, clip_l_weights, 77, padding); - t5_tokenizer.pad_tokens(t5_tokens, t5_weights, max_length, padding); + t5_tokenizer.pad_tokens(t5_tokens, t5_weights, NULL, max_length, padding); // for (int i = 0; i < clip_l_tokens.size(); i++) { // std::cout << clip_l_tokens[i] << ":" << clip_l_weights[i] << ", "; @@ -1109,7 +1111,6 @@ struct FluxCLIPEmbedder : public Conditioner { struct ggml_tensor* pooled = NULL; // [768,] std::vector hidden_states_vec; - size_t chunk_len = 256; size_t chunk_count = t5_tokens.size() / chunk_len; for (int chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) { // clip_l @@ -1147,6 +1148,7 @@ struct FluxCLIPEmbedder : public Conditioner { t5->compute(n_threads, input_ids, + NULL, &chunk_hidden_states, work_ctx); { @@ -1196,7 +1198,208 @@ struct FluxCLIPEmbedder : public Conditioner { int height, int adm_in_channels = -1, bool force_zero_embeddings = false) { - auto tokens_and_weights = tokenize(text, 256, true); + auto tokens_and_weights = tokenize(text, chunk_len, true); + return get_learned_condition_common(work_ctx, n_threads, tokens_and_weights, clip_skip, force_zero_embeddings); + } + + std::tuple> get_learned_condition_with_trigger(ggml_context* work_ctx, + int n_threads, + const std::string& text, + int clip_skip, + int width, + int height, + int num_input_imgs, + int adm_in_channels = -1, + bool force_zero_embeddings = false) { + GGML_ASSERT(0 && "Not implemented yet!"); + } + + std::string remove_trigger_from_prompt(ggml_context* work_ctx, + const std::string& prompt) { + GGML_ASSERT(0 && "Not implemented yet!"); + } +}; + +struct PixArtCLIPEmbedder : public Conditioner { + T5UniGramTokenizer t5_tokenizer; + std::shared_ptr t5; + size_t chunk_len = 512; + bool use_mask = false; + int mask_pad = 1; + + PixArtCLIPEmbedder(ggml_backend_t backend, + std::map& tensor_types, + int clip_skip = -1, + bool use_mask = false, + int mask_pad = 1) : use_mask(use_mask), mask_pad(mask_pad) { + t5 = std::make_shared(backend, tensor_types, "text_encoders.t5xxl.transformer"); + } + + void set_clip_skip(int clip_skip) { + } + + void get_param_tensors(std::map& tensors) { + t5->get_param_tensors(tensors, "text_encoders.t5xxl.transformer"); + } + + void alloc_params_buffer() { + t5->alloc_params_buffer(); + } + + void free_params_buffer() { + t5->free_params_buffer(); + } + + size_t get_params_buffer_size() { + size_t buffer_size = 0; + + buffer_size += t5->get_params_buffer_size(); + + return buffer_size; + } + + std::tuple, std::vector, std::vector> tokenize(std::string text, + size_t max_length = 0, + bool padding = false) { + auto parsed_attention = parse_prompt_attention(text); + + { + std::stringstream ss; + ss << "["; + for (const auto& item : parsed_attention) { + ss << "['" << item.first << "', " << item.second << "], "; + } + ss << "]"; + LOG_DEBUG("parse '%s' to %s", text.c_str(), ss.str().c_str()); + } + + auto on_new_token_cb = [&](std::string& str, std::vector& bpe_tokens) -> bool { + return false; + }; + + std::vector t5_tokens; + std::vector t5_weights; + std::vector t5_mask; + for (const auto& item : parsed_attention) { + const std::string& curr_text = item.first; + float curr_weight = item.second; + + std::vector curr_tokens = t5_tokenizer.Encode(curr_text, true); + t5_tokens.insert(t5_tokens.end(), curr_tokens.begin(), curr_tokens.end()); + t5_weights.insert(t5_weights.end(), curr_tokens.size(), curr_weight); + } + + t5_tokenizer.pad_tokens(t5_tokens, t5_weights, &t5_mask, max_length, padding); + + return {t5_tokens, t5_weights, t5_mask}; + } + + void modify_mask_to_attend_padding(struct ggml_tensor* mask, int max_seq_length, int num_extra_padding = 8) { + float* mask_data = (float*)mask->data; + int num_pad = 0; + for (int64_t i = 0; i < max_seq_length; i++) { + if (num_pad >= num_extra_padding) { + break; + } + if (std::isinf(mask_data[i])) { + mask_data[i] = 0; + ++num_pad; + } + } + // LOG_DEBUG("PAD: %d", num_pad); + } + + SDCondition get_learned_condition_common(ggml_context* work_ctx, + int n_threads, + std::tuple, std::vector, std::vector> token_and_weights, + int clip_skip, + bool force_zero_embeddings = false) { + auto& t5_tokens = std::get<0>(token_and_weights); + auto& t5_weights = std::get<1>(token_and_weights); + auto& t5_attn_mask_vec = std::get<2>(token_and_weights); + + int64_t t0 = ggml_time_ms(); + struct ggml_tensor* hidden_states = NULL; // [N, n_token, 4096] + struct ggml_tensor* chunk_hidden_states = NULL; // [n_token, 4096] + struct ggml_tensor* pooled = NULL; // [768,] + struct ggml_tensor* t5_attn_mask = vector_to_ggml_tensor(work_ctx, t5_attn_mask_vec); // [768,] + + std::vector hidden_states_vec; + + size_t chunk_count = t5_tokens.size() / chunk_len; + + for (int chunk_idx = 0; chunk_idx < chunk_count; chunk_idx++) { + // t5 + std::vector chunk_tokens(t5_tokens.begin() + chunk_idx * chunk_len, + t5_tokens.begin() + (chunk_idx + 1) * chunk_len); + std::vector chunk_weights(t5_weights.begin() + chunk_idx * chunk_len, + t5_weights.begin() + (chunk_idx + 1) * chunk_len); + std::vector chunk_mask(t5_attn_mask_vec.begin() + chunk_idx * chunk_len, + t5_attn_mask_vec.begin() + (chunk_idx + 1) * chunk_len); + + auto input_ids = vector_to_ggml_tensor_i32(work_ctx, chunk_tokens); + auto t5_attn_mask_chunk = use_mask ? vector_to_ggml_tensor(work_ctx, chunk_mask) : NULL; + + t5->compute(n_threads, + input_ids, + t5_attn_mask_chunk, + &chunk_hidden_states, + work_ctx); + { + auto tensor = chunk_hidden_states; + float original_mean = ggml_tensor_mean(tensor); + for (int i2 = 0; i2 < tensor->ne[2]; i2++) { + for (int i1 = 0; i1 < tensor->ne[1]; i1++) { + for (int i0 = 0; i0 < tensor->ne[0]; i0++) { + float value = ggml_tensor_get_f32(tensor, i0, i1, i2); + value *= chunk_weights[i1]; + ggml_tensor_set_f32(tensor, value, i0, i1, i2); + } + } + } + float new_mean = ggml_tensor_mean(tensor); + ggml_tensor_scale(tensor, (original_mean / new_mean)); + } + + int64_t t1 = ggml_time_ms(); + LOG_DEBUG("computing condition graph completed, taking %" PRId64 " ms", t1 - t0); + if (force_zero_embeddings) { + float* vec = (float*)chunk_hidden_states->data; + for (int i = 0; i < ggml_nelements(chunk_hidden_states); i++) { + vec[i] = 0; + } + } + + hidden_states_vec.insert(hidden_states_vec.end(), + (float*)chunk_hidden_states->data, + ((float*)chunk_hidden_states->data) + ggml_nelements(chunk_hidden_states)); + } + + if (hidden_states_vec.size() > 0) { + hidden_states = vector_to_ggml_tensor(work_ctx, hidden_states_vec); + hidden_states = ggml_reshape_2d(work_ctx, + hidden_states, + chunk_hidden_states->ne[0], + ggml_nelements(hidden_states) / chunk_hidden_states->ne[0]); + } else { + hidden_states = ggml_new_tensor_2d(work_ctx, GGML_TYPE_F32, 4096, 256); + ggml_set_f32(hidden_states, 0.f); + } + + modify_mask_to_attend_padding(t5_attn_mask, ggml_nelements(t5_attn_mask), mask_pad); + + return SDCondition(hidden_states, t5_attn_mask, NULL); + } + + SDCondition get_learned_condition(ggml_context* work_ctx, + int n_threads, + const std::string& text, + int clip_skip, + int width, + int height, + int adm_in_channels = -1, + bool force_zero_embeddings = false) { + auto tokens_and_weights = tokenize(text, chunk_len, true); return get_learned_condition_common(work_ctx, n_threads, tokens_and_weights, clip_skip, force_zero_embeddings); } diff --git a/diffusion_model.hpp b/diffusion_model.hpp index 94e9a2678..0f843770e 100644 --- a/diffusion_model.hpp +++ b/diffusion_model.hpp @@ -137,8 +137,9 @@ struct FluxModel : public DiffusionModel { FluxModel(ggml_backend_t backend, std::map& tensor_types, SDVersion version = VERSION_FLUX, - bool flash_attn = false) - : flux(backend, tensor_types, "model.diffusion_model", version, flash_attn) { + bool flash_attn = false, + bool use_mask = false) + : flux(backend, tensor_types, "model.diffusion_model", version, flash_attn, use_mask) { } void alloc_params_buffer() { diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 466fe87c2..0583ff073 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -132,6 +132,10 @@ struct SDParams { float slg_scale = 0.f; float skip_layer_start = 0.01f; float skip_layer_end = 0.2f; + + bool chroma_use_dit_mask = true; + bool chroma_use_t5_mask = false; + int chroma_t5_mask_pad = 1; }; void print_params(SDParams params) { @@ -185,6 +189,9 @@ void print_params(SDParams params) { printf(" batch_count: %d\n", params.batch_count); printf(" vae_tiling: %s\n", params.vae_tiling ? "true" : "false"); printf(" upscale_repeats: %d\n", params.upscale_repeats); + printf(" chroma_use_dit_mask: %s\n", params.chroma_use_dit_mask ? "true" : "false"); + printf(" chroma_use_t5_mask: %s\n", params.chroma_use_t5_mask ? "true" : "false"); + printf(" chroma_t5_mask_pad: %d\n", params.chroma_t5_mask_pad); } void print_usage(int argc, const char* argv[]) { @@ -252,6 +259,9 @@ void print_usage(int argc, const char* argv[]) { printf(" --control-net-cpu keep controlnet in cpu (for low vram)\n"); printf(" --canny apply canny preprocessor (edge detection)\n"); printf(" --color colors the logging tags according to level\n"); + printf(" --chroma-disable-dit-mask disable dit mask for chroma\n"); + printf(" --chroma-enable-t5-mask enable t5 mask for chroma\n"); + printf(" --chroma-t5-mask-pad PAD_SIZE t5 mask pad size of chroma\n"); printf(" -v, --verbose print extra info\n"); } @@ -643,6 +653,16 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.ref_image_paths.push_back(argv[i]); + } else if (arg == "chroma-disable-dit-mask") { + params.chroma_use_dit_mask = false; + } else if (arg == "--chroma-use-t5-mask") { + params.chroma_use_t5_mask = true; + } else if (arg == "--chroma-t5-mask-pad") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.chroma_t5_mask_pad = std::stoi(argv[i]); } else { fprintf(stderr, "error: unknown argument: %s\n", arg.c_str()); print_usage(argc, argv); @@ -952,7 +972,10 @@ int main(int argc, const char* argv[]) { params.clip_on_cpu, params.control_net_cpu, params.vae_on_cpu, - params.diffusion_flash_attn); + params.diffusion_flash_attn, + params.chroma_use_dit_mask, + params.chroma_use_t5_mask, + params.chroma_t5_mask_pad); if (sd_ctx == NULL) { printf("new_sd_ctx_t failed\n"); diff --git a/flux.hpp b/flux.hpp index 289e8554f..a1e88d568 100644 --- a/flux.hpp +++ b/flux.hpp @@ -117,6 +117,7 @@ namespace Flux { struct ggml_tensor* k, struct ggml_tensor* v, struct ggml_tensor* pe, + struct ggml_tensor* mask, bool flash_attn) { // q,k,v: [N, L, n_head, d_head] // pe: [L, d_head/2, 2, 2] @@ -124,7 +125,7 @@ namespace Flux { q = apply_rope(ctx, q, pe); // [N*n_head, L, d_head] k = apply_rope(ctx, k, pe); // [N*n_head, L, d_head] - auto x = ggml_nn_attention_ext(ctx, q, k, v, v->ne[1], NULL, false, true, flash_attn); // [N, L, n_head*d_head] + auto x = ggml_nn_attention_ext(ctx, q, k, v, v->ne[1], mask, false, true, flash_attn); // [N, L, n_head*d_head] return x; } @@ -167,13 +168,13 @@ namespace Flux { return x; } - struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x, struct ggml_tensor* pe) { + struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x, struct ggml_tensor* pe, struct ggml_tensor* mask) { // x: [N, n_token, dim] // pe: [n_token, d_head/2, 2, 2] // return [N, n_token, dim] - auto qkv = pre_attention(ctx, x); // q,k,v: [N, n_token, n_head, d_head] - x = attention(ctx, qkv[0], qkv[1], qkv[2], pe, flash_attn); // [N, n_token, dim] - x = post_attention(ctx, x); // [N, n_token, dim] + auto qkv = pre_attention(ctx, x); // q,k,v: [N, n_token, n_head, d_head] + x = attention(ctx, qkv[0], qkv[1], qkv[2], pe, mask, flash_attn); // [N, n_token, dim] + x = post_attention(ctx, x); // [N, n_token, dim] return x; } }; @@ -185,6 +186,13 @@ namespace Flux { ModulationOut(ggml_tensor* shift = NULL, ggml_tensor* scale = NULL, ggml_tensor* gate = NULL) : shift(shift), scale(scale), gate(gate) {} + + ModulationOut(struct ggml_context* ctx, ggml_tensor* vec, int64_t offset) { + int64_t stride = vec->nb[1] * vec->ne[1]; + shift = ggml_view_2d(ctx, vec, vec->ne[0], vec->ne[1], vec->nb[1], stride * (offset + 0)); // [N, dim] + scale = ggml_view_2d(ctx, vec, vec->ne[0], vec->ne[1], vec->nb[1], stride * (offset + 1)); // [N, dim] + gate = ggml_view_2d(ctx, vec, vec->ne[0], vec->ne[1], vec->nb[1], stride * (offset + 2)); // [N, dim] + } }; struct Modulation : public GGMLBlock { @@ -210,19 +218,12 @@ namespace Flux { auto m = ggml_reshape_3d(ctx, out, vec->ne[0], multiplier, vec->ne[1]); // [N, multiplier, dim] m = ggml_cont(ctx, ggml_permute(ctx, m, 0, 2, 1, 3)); // [multiplier, N, dim] - int64_t offset = m->nb[1] * m->ne[1]; - auto shift_0 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 0); // [N, dim] - auto scale_0 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 1); // [N, dim] - auto gate_0 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 2); // [N, dim] - + ModulationOut m_0 = ModulationOut(ctx, m, 0); if (is_double) { - auto shift_1 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 3); // [N, dim] - auto scale_1 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 4); // [N, dim] - auto gate_1 = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 5); // [N, dim] - return {ModulationOut(shift_0, scale_0, gate_0), ModulationOut(shift_1, scale_1, gate_1)}; + return {m_0, ModulationOut(ctx, m, 3)}; } - return {ModulationOut(shift_0, scale_0, gate_0), ModulationOut()}; + return {m_0, ModulationOut()}; } }; @@ -242,25 +243,33 @@ namespace Flux { struct DoubleStreamBlock : public GGMLBlock { bool flash_attn; + bool prune_mod; + int idx = 0; public: DoubleStreamBlock(int64_t hidden_size, int64_t num_heads, float mlp_ratio, + int idx = 0, bool qkv_bias = false, - bool flash_attn = false) - : flash_attn(flash_attn) { + bool flash_attn = false, + bool prune_mod = false) + : idx(idx), flash_attn(flash_attn), prune_mod(prune_mod) { int64_t mlp_hidden_dim = hidden_size * mlp_ratio; - blocks["img_mod"] = std::shared_ptr(new Modulation(hidden_size, true)); - blocks["img_norm1"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); - blocks["img_attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qkv_bias, flash_attn)); + if (!prune_mod) { + blocks["img_mod"] = std::shared_ptr(new Modulation(hidden_size, true)); + } + blocks["img_norm1"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); + blocks["img_attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qkv_bias, flash_attn)); blocks["img_norm2"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); blocks["img_mlp.0"] = std::shared_ptr(new Linear(hidden_size, mlp_hidden_dim)); // img_mlp.1 is nn.GELU(approximate="tanh") blocks["img_mlp.2"] = std::shared_ptr(new Linear(mlp_hidden_dim, hidden_size)); - blocks["txt_mod"] = std::shared_ptr(new Modulation(hidden_size, true)); + if (!prune_mod) { + blocks["txt_mod"] = std::shared_ptr(new Modulation(hidden_size, true)); + } blocks["txt_norm1"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); blocks["txt_attn"] = std::shared_ptr(new SelfAttention(hidden_size, num_heads, qkv_bias, flash_attn)); @@ -270,17 +279,34 @@ namespace Flux { blocks["txt_mlp.2"] = std::shared_ptr(new Linear(mlp_hidden_dim, hidden_size)); } + std::vector get_distil_img_mod(struct ggml_context* ctx, struct ggml_tensor* vec) { + // TODO: not hardcoded? + const int single_blocks_count = 38; + const int double_blocks_count = 19; + + int64_t offset = 6 * idx + 3 * single_blocks_count; + return {ModulationOut(ctx, vec, offset), ModulationOut(ctx, vec, offset + 3)}; + } + + std::vector get_distil_txt_mod(struct ggml_context* ctx, struct ggml_tensor* vec) { + // TODO: not hardcoded? + const int single_blocks_count = 38; + const int double_blocks_count = 19; + + int64_t offset = 6 * idx + 6 * double_blocks_count + 3 * single_blocks_count; + return {ModulationOut(ctx, vec, offset), ModulationOut(ctx, vec, offset + 3)}; + } + std::pair forward(struct ggml_context* ctx, struct ggml_tensor* img, struct ggml_tensor* txt, struct ggml_tensor* vec, - struct ggml_tensor* pe) { + struct ggml_tensor* pe, + struct ggml_tensor* mask = NULL) { // img: [N, n_img_token, hidden_size] // txt: [N, n_txt_token, hidden_size] // pe: [n_img_token + n_txt_token, d_head/2, 2, 2] // return: ([N, n_img_token, hidden_size], [N, n_txt_token, hidden_size]) - - auto img_mod = std::dynamic_pointer_cast(blocks["img_mod"]); auto img_norm1 = std::dynamic_pointer_cast(blocks["img_norm1"]); auto img_attn = std::dynamic_pointer_cast(blocks["img_attn"]); @@ -288,7 +314,6 @@ namespace Flux { auto img_mlp_0 = std::dynamic_pointer_cast(blocks["img_mlp.0"]); auto img_mlp_2 = std::dynamic_pointer_cast(blocks["img_mlp.2"]); - auto txt_mod = std::dynamic_pointer_cast(blocks["txt_mod"]); auto txt_norm1 = std::dynamic_pointer_cast(blocks["txt_norm1"]); auto txt_attn = std::dynamic_pointer_cast(blocks["txt_attn"]); @@ -296,10 +321,22 @@ namespace Flux { auto txt_mlp_0 = std::dynamic_pointer_cast(blocks["txt_mlp.0"]); auto txt_mlp_2 = std::dynamic_pointer_cast(blocks["txt_mlp.2"]); - auto img_mods = img_mod->forward(ctx, vec); + std::vector img_mods; + if (prune_mod) { + img_mods = get_distil_img_mod(ctx, vec); + } else { + auto img_mod = std::dynamic_pointer_cast(blocks["img_mod"]); + img_mods = img_mod->forward(ctx, vec); + } ModulationOut img_mod1 = img_mods[0]; ModulationOut img_mod2 = img_mods[1]; - auto txt_mods = txt_mod->forward(ctx, vec); + std::vector txt_mods; + if (prune_mod) { + txt_mods = get_distil_txt_mod(ctx, vec); + } else { + auto txt_mod = std::dynamic_pointer_cast(blocks["txt_mod"]); + txt_mods = txt_mod->forward(ctx, vec); + } ModulationOut txt_mod1 = txt_mods[0]; ModulationOut txt_mod2 = txt_mods[1]; @@ -324,7 +361,7 @@ namespace Flux { auto k = ggml_concat(ctx, txt_k, img_k, 2); // [N, n_txt_token + n_img_token, n_head, d_head] auto v = ggml_concat(ctx, txt_v, img_v, 2); // [N, n_txt_token + n_img_token, n_head, d_head] - auto attn = attention(ctx, q, k, v, pe, flash_attn); // [N, n_txt_token + n_img_token, n_head*d_head] + auto attn = attention(ctx, q, k, v, pe, mask, flash_attn); // [N, n_txt_token + n_img_token, n_head*d_head] attn = ggml_cont(ctx, ggml_permute(ctx, attn, 0, 2, 1, 3)); // [n_txt_token + n_img_token, N, hidden_size] auto txt_attn_out = ggml_view_3d(ctx, attn, @@ -373,14 +410,18 @@ namespace Flux { int64_t hidden_size; int64_t mlp_hidden_dim; bool flash_attn; + bool prune_mod; + int idx = 0; public: SingleStreamBlock(int64_t hidden_size, int64_t num_heads, float mlp_ratio = 4.0f, + int idx = 0, float qk_scale = 0.f, - bool flash_attn = false) - : hidden_size(hidden_size), num_heads(num_heads), flash_attn(flash_attn) { + bool flash_attn = false, + bool prune_mod = false) + : hidden_size(hidden_size), num_heads(num_heads), idx(idx), flash_attn(flash_attn), prune_mod(prune_mod) { int64_t head_dim = hidden_size / num_heads; float scale = qk_scale; if (scale <= 0.f) { @@ -393,26 +434,37 @@ namespace Flux { blocks["norm"] = std::shared_ptr(new QKNorm(head_dim)); blocks["pre_norm"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-6f, false)); // mlp_act is nn.GELU(approximate="tanh") - blocks["modulation"] = std::shared_ptr(new Modulation(hidden_size, false)); + if (!prune_mod) { + blocks["modulation"] = std::shared_ptr(new Modulation(hidden_size, false)); + } + } + + ModulationOut get_distil_mod(struct ggml_context* ctx, struct ggml_tensor* vec) { + int64_t offset = 3 * idx; + return ModulationOut(ctx, vec, offset); } struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x, struct ggml_tensor* vec, - struct ggml_tensor* pe) { + struct ggml_tensor* pe, + struct ggml_tensor* mask = NULL) { // x: [N, n_token, hidden_size] // pe: [n_token, d_head/2, 2, 2] // return: [N, n_token, hidden_size] - auto linear1 = std::dynamic_pointer_cast(blocks["linear1"]); - auto linear2 = std::dynamic_pointer_cast(blocks["linear2"]); - auto norm = std::dynamic_pointer_cast(blocks["norm"]); - auto pre_norm = std::dynamic_pointer_cast(blocks["pre_norm"]); - auto modulation = std::dynamic_pointer_cast(blocks["modulation"]); - - auto mods = modulation->forward(ctx, vec); - ModulationOut mod = mods[0]; - + auto linear1 = std::dynamic_pointer_cast(blocks["linear1"]); + auto linear2 = std::dynamic_pointer_cast(blocks["linear2"]); + auto norm = std::dynamic_pointer_cast(blocks["norm"]); + auto pre_norm = std::dynamic_pointer_cast(blocks["pre_norm"]); + ModulationOut mod; + if (prune_mod) { + mod = get_distil_mod(ctx, vec); + } else { + auto modulation = std::dynamic_pointer_cast(blocks["modulation"]); + + mod = modulation->forward(ctx, vec)[0]; + } auto x_mod = Flux::modulate(ctx, pre_norm->forward(ctx, x), mod.shift, mod.scale); auto qkv_mlp = linear1->forward(ctx, x_mod); // [N, n_token, hidden_size * 3 + mlp_hidden_dim] qkv_mlp = ggml_cont(ctx, ggml_permute(ctx, qkv_mlp, 2, 0, 1, 3)); // [hidden_size * 3 + mlp_hidden_dim, N, n_token] @@ -443,7 +495,7 @@ namespace Flux { auto v = ggml_reshape_4d(ctx, qkv_vec[2], head_dim, num_heads, qkv_vec[2]->ne[1], qkv_vec[2]->ne[2]); // [N, n_token, n_head, d_head] q = norm->query_norm(ctx, q); k = norm->key_norm(ctx, k); - auto attn = attention(ctx, q, k, v, pe, flash_attn); // [N, n_token, hidden_size] + auto attn = attention(ctx, q, k, v, pe, mask, flash_attn); // [N, n_token, hidden_size] auto attn_mlp = ggml_concat(ctx, attn, ggml_gelu_inplace(ctx, mlp), 0); // [N, n_token, hidden_size + mlp_hidden_dim] auto output = linear2->forward(ctx, attn_mlp); // [N, n_token, hidden_size] @@ -454,13 +506,27 @@ namespace Flux { }; struct LastLayer : public GGMLBlock { + bool prune_mod; + public: LastLayer(int64_t hidden_size, int64_t patch_size, - int64_t out_channels) { - blocks["norm_final"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-06f, false)); - blocks["linear"] = std::shared_ptr(new Linear(hidden_size, patch_size * patch_size * out_channels)); - blocks["adaLN_modulation.1"] = std::shared_ptr(new Linear(hidden_size, 2 * hidden_size)); + int64_t out_channels, + bool prune_mod = false) : prune_mod(prune_mod) { + blocks["norm_final"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-06f, false)); + blocks["linear"] = std::shared_ptr(new Linear(hidden_size, patch_size * patch_size * out_channels)); + if (!prune_mod) { + blocks["adaLN_modulation.1"] = std::shared_ptr(new Linear(hidden_size, 2 * hidden_size)); + } + } + + ModulationOut get_distil_mod(struct ggml_context* ctx, struct ggml_tensor* vec) { + int64_t offset = vec->ne[2] - 2; + int64_t stride = vec->nb[1] * vec->ne[1]; + auto shift = ggml_view_2d(ctx, vec, vec->ne[0], vec->ne[1], vec->nb[1], stride * (offset + 0)); // [N, dim] + auto scale = ggml_view_2d(ctx, vec, vec->ne[0], vec->ne[1], vec->nb[1], stride * (offset + 1)); // [N, dim] + // No gate + return ModulationOut(shift, scale, NULL); } struct ggml_tensor* forward(struct ggml_context* ctx, @@ -469,17 +535,24 @@ namespace Flux { // x: [N, n_token, hidden_size] // c: [N, hidden_size] // return: [N, n_token, patch_size * patch_size * out_channels] - auto norm_final = std::dynamic_pointer_cast(blocks["norm_final"]); - auto linear = std::dynamic_pointer_cast(blocks["linear"]); - auto adaLN_modulation_1 = std::dynamic_pointer_cast(blocks["adaLN_modulation.1"]); - - auto m = adaLN_modulation_1->forward(ctx, ggml_silu(ctx, c)); // [N, 2 * hidden_size] - m = ggml_reshape_3d(ctx, m, c->ne[0], 2, c->ne[1]); // [N, 2, hidden_size] - m = ggml_cont(ctx, ggml_permute(ctx, m, 0, 2, 1, 3)); // [2, N, hidden_size] - - int64_t offset = m->nb[1] * m->ne[1]; - auto shift = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 0); // [N, hidden_size] - auto scale = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 1); // [N, hidden_size] + auto norm_final = std::dynamic_pointer_cast(blocks["norm_final"]); + auto linear = std::dynamic_pointer_cast(blocks["linear"]); + struct ggml_tensor *shift, *scale; + if (prune_mod) { + auto mod = get_distil_mod(ctx, c); + shift = mod.shift; + scale = mod.scale; + } else { + auto adaLN_modulation_1 = std::dynamic_pointer_cast(blocks["adaLN_modulation.1"]); + + auto m = adaLN_modulation_1->forward(ctx, ggml_silu(ctx, c)); // [N, 2 * hidden_size] + m = ggml_reshape_3d(ctx, m, c->ne[0], 2, c->ne[1]); // [N, 2, hidden_size] + m = ggml_cont(ctx, ggml_permute(ctx, m, 0, 2, 1, 3)); // [2, N, hidden_size] + + int64_t offset = m->nb[1] * m->ne[1]; + shift = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 0); // [N, hidden_size] + scale = ggml_view_2d(ctx, m, m->ne[0], m->ne[1], m->nb[1], offset * 1); // [N, hidden_size] + } x = Flux::modulate(ctx, norm_final->forward(ctx, x), shift, scale); x = linear->forward(ctx, x); @@ -488,6 +561,34 @@ namespace Flux { } }; + struct ChromaApproximator : public GGMLBlock { + int64_t inner_size = 5120; + int64_t n_layers = 5; + ChromaApproximator(int64_t in_channels = 64, int64_t hidden_size = 3072) { + blocks["in_proj"] = std::shared_ptr(new Linear(in_channels, inner_size, true)); + for (int i = 0; i < n_layers; i++) { + blocks["norms." + std::to_string(i)] = std::shared_ptr(new RMSNorm(inner_size)); + blocks["layers." + std::to_string(i)] = std::shared_ptr(new MLPEmbedder(inner_size, inner_size)); + } + blocks["out_proj"] = std::shared_ptr(new Linear(inner_size, hidden_size, true)); + } + + struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* x) { + auto in_proj = std::dynamic_pointer_cast(blocks["in_proj"]); + auto out_proj = std::dynamic_pointer_cast(blocks["out_proj"]); + + x = in_proj->forward(ctx, x); + for (int i = 0; i < n_layers; i++) { + auto norm = std::dynamic_pointer_cast(blocks["norms." + std::to_string(i)]); + auto embed = std::dynamic_pointer_cast(blocks["layers." + std::to_string(i)]); + x = ggml_add_inplace(ctx, x, embed->forward(ctx, norm->forward(ctx, x))); + } + x = out_proj->forward(ctx, x); + + return x; + } + }; + struct FluxParams { int64_t in_channels = 64; int64_t out_channels = 64; @@ -504,6 +605,7 @@ namespace Flux { bool qkv_bias = true; bool guidance_embed = true; bool flash_attn = true; + bool is_chroma = false; }; struct Flux : public GGMLBlock { @@ -642,6 +744,7 @@ namespace Flux { return ids; } + // Generate positional embeddings std::vector gen_pe(int h, int w, int patch_size, int bs, int context_len, std::vector ref_latents, int theta, const std::vector& axes_dim) { std::vector> ids = gen_ids(h, w, patch_size, bs, context_len, ref_latents); @@ -680,11 +783,15 @@ namespace Flux { : params(params) { int64_t pe_dim = params.hidden_size / params.num_heads; - blocks["img_in"] = std::shared_ptr(new Linear(params.in_channels, params.hidden_size, true)); - blocks["time_in"] = std::shared_ptr(new MLPEmbedder(256, params.hidden_size)); - blocks["vector_in"] = std::shared_ptr(new MLPEmbedder(params.vec_in_dim, params.hidden_size)); - if (params.guidance_embed) { - blocks["guidance_in"] = std::shared_ptr(new MLPEmbedder(256, params.hidden_size)); + blocks["img_in"] = std::shared_ptr(new Linear(params.in_channels, params.hidden_size, true)); + if (params.is_chroma) { + blocks["distilled_guidance_layer"] = std::shared_ptr(new ChromaApproximator(params.in_channels, params.hidden_size)); + } else { + blocks["time_in"] = std::shared_ptr(new MLPEmbedder(256, params.hidden_size)); + blocks["vector_in"] = std::shared_ptr(new MLPEmbedder(params.vec_in_dim, params.hidden_size)); + if (params.guidance_embed) { + blocks["guidance_in"] = std::shared_ptr(new MLPEmbedder(256, params.hidden_size)); + } } blocks["txt_in"] = std::shared_ptr(new Linear(params.context_in_dim, params.hidden_size, true)); @@ -692,19 +799,23 @@ namespace Flux { blocks["double_blocks." + std::to_string(i)] = std::shared_ptr(new DoubleStreamBlock(params.hidden_size, params.num_heads, params.mlp_ratio, + i, params.qkv_bias, - params.flash_attn)); + params.flash_attn, + params.is_chroma)); } for (int i = 0; i < params.depth_single_blocks; i++) { blocks["single_blocks." + std::to_string(i)] = std::shared_ptr(new SingleStreamBlock(params.hidden_size, params.num_heads, params.mlp_ratio, + i, 0.f, - params.flash_attn)); + params.flash_attn, + params.is_chroma)); } - blocks["final_layer"] = std::shared_ptr(new LastLayer(params.hidden_size, 1, params.out_channels)); + blocks["final_layer"] = std::shared_ptr(new LastLayer(params.hidden_size, 1, params.out_channels, params.is_chroma)); } struct ggml_tensor* patchify(struct ggml_context* ctx, @@ -761,25 +872,55 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor* pe, + struct ggml_tensor* mod_index_arange = NULL, std::vector skip_layers = {}) { auto img_in = std::dynamic_pointer_cast(blocks["img_in"]); - auto time_in = std::dynamic_pointer_cast(blocks["time_in"]); - auto vector_in = std::dynamic_pointer_cast(blocks["vector_in"]); auto txt_in = std::dynamic_pointer_cast(blocks["txt_in"]); auto final_layer = std::dynamic_pointer_cast(blocks["final_layer"]); - img = img_in->forward(ctx, img); - auto vec = time_in->forward(ctx, ggml_nn_timestep_embedding(ctx, timesteps, 256, 10000, 1000.f)); + img = img_in->forward(ctx, img); + struct ggml_tensor* vec; + struct ggml_tensor* txt_img_mask = NULL; + if (params.is_chroma) { + int64_t mod_index_length = 344; + auto approx = std::dynamic_pointer_cast(blocks["distilled_guidance_layer"]); + auto distill_timestep = ggml_nn_timestep_embedding(ctx, timesteps, 16, 10000, 1000.f); + auto distill_guidance = ggml_nn_timestep_embedding(ctx, guidance, 16, 10000, 1000.f); + + // auto mod_index_arange = ggml_arange(ctx, 0, (float)mod_index_length, 1); + // ggml_arange tot working on a lot of backends, precomputing it on CPU instead + GGML_ASSERT(arange != NULL); + auto modulation_index = ggml_nn_timestep_embedding(ctx, mod_index_arange, 32, 10000, 1000.f); // [1, 344, 32] + + // Batch broadcast (will it ever be useful) + modulation_index = ggml_repeat(ctx, modulation_index, ggml_new_tensor_3d(ctx, GGML_TYPE_F32, modulation_index->ne[0], modulation_index->ne[1], img->ne[2])); // [N, 344, 32] + + auto timestep_guidance = ggml_concat(ctx, distill_timestep, distill_guidance, 0); // [N, 1, 32] + timestep_guidance = ggml_repeat(ctx, timestep_guidance, modulation_index); // [N, 344, 32] + + vec = ggml_concat(ctx, timestep_guidance, modulation_index, 0); // [N, 344, 64] + // Permute for consistency with non-distilled modulation implementation + vec = ggml_cont(ctx, ggml_permute(ctx, vec, 0, 2, 1, 3)); // [344, N, 64] + vec = approx->forward(ctx, vec); // [344, N, hidden_size] + + if (y != NULL) { + txt_img_mask = ggml_pad(ctx, y, img->ne[1], 0, 0, 0); + } + } else { + auto time_in = std::dynamic_pointer_cast(blocks["time_in"]); + auto vector_in = std::dynamic_pointer_cast(blocks["vector_in"]); + vec = time_in->forward(ctx, ggml_nn_timestep_embedding(ctx, timesteps, 256, 10000, 1000.f)); + if (params.guidance_embed) { + GGML_ASSERT(guidance != NULL); + auto guidance_in = std::dynamic_pointer_cast(blocks["guidance_in"]); + // bf16 and fp16 result is different + auto g_in = ggml_nn_timestep_embedding(ctx, guidance, 256, 10000, 1000.f); + vec = ggml_add(ctx, vec, guidance_in->forward(ctx, g_in)); + } - if (params.guidance_embed) { - GGML_ASSERT(guidance != NULL); - auto guidance_in = std::dynamic_pointer_cast(blocks["guidance_in"]); - // bf16 and fp16 result is different - auto g_in = ggml_nn_timestep_embedding(ctx, guidance, 256, 10000, 1000.f); - vec = ggml_add(ctx, vec, guidance_in->forward(ctx, g_in)); + vec = ggml_add(ctx, vec, vector_in->forward(ctx, y)); } - vec = ggml_add(ctx, vec, vector_in->forward(ctx, y)); txt = txt_in->forward(ctx, txt); for (int i = 0; i < params.depth; i++) { @@ -789,7 +930,7 @@ namespace Flux { auto block = std::dynamic_pointer_cast(blocks["double_blocks." + std::to_string(i)]); - auto img_txt = block->forward(ctx, img, txt, vec, pe); + auto img_txt = block->forward(ctx, img, txt, vec, pe, txt_img_mask); img = img_txt.first; // [N, n_img_token, hidden_size] txt = img_txt.second; // [N, n_txt_token, hidden_size] } @@ -801,7 +942,7 @@ namespace Flux { } auto block = std::dynamic_pointer_cast(blocks["single_blocks." + std::to_string(i)]); - txt_img = block->forward(ctx, txt_img, vec, pe); + txt_img = block->forward(ctx, txt_img, vec, pe, txt_img_mask); } txt_img = ggml_cont(ctx, ggml_permute(ctx, txt_img, 0, 2, 1, 3)); // [n_txt_token + n_img_token, N, hidden_size] @@ -816,7 +957,6 @@ namespace Flux { img = ggml_cont(ctx, ggml_permute(ctx, img, 0, 2, 1, 3)); // [N, n_img_token, hidden_size] img = final_layer->forward(ctx, img, vec); // (N, T, patch_size ** 2 * out_channels) - return img; } @@ -843,6 +983,7 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor* pe, + struct ggml_tensor* mod_index_arange = NULL, std::vector ref_latents = {}, std::vector skip_layers = {}) { // Forward pass of DiT. @@ -884,7 +1025,7 @@ namespace Flux { } } - auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, skip_layers); // [N, num_tokens, C * patch_size * patch_size] + auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, mod_index_arange, skip_layers); // [N, num_tokens, C * patch_size * patch_size] if (out->ne[1] > img_tokens) { out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); // [num_tokens, N, C * patch_size * patch_size] out = ggml_view_3d(ctx, out, out->ne[0], out->ne[1], img_tokens, out->nb[1], out->nb[2], 0); @@ -904,14 +1045,18 @@ namespace Flux { public: FluxParams flux_params; Flux flux; - std::vector pe_vec; // for cache + std::vector pe_vec; + std::vector mod_index_arange_vec; // for cache + SDVersion version; + bool use_mask = false; FluxRunner(ggml_backend_t backend, std::map& tensor_types = empty_tensor_types, const std::string prefix = "", SDVersion version = VERSION_FLUX, - bool flash_attn = false) - : GGMLRunner(backend) { + bool flash_attn = false, + bool use_mask = false) + : GGMLRunner(backend), use_mask(use_mask) { flux_params.flash_attn = flash_attn; flux_params.guidance_embed = false; flux_params.depth = 0; @@ -927,6 +1072,10 @@ namespace Flux { // not schnell flux_params.guidance_embed = true; } + if (tensor_name.find("distilled_guidance_layer.in_proj.weight") != std::string::npos) { + // Chroma + flux_params.is_chroma = true; + } size_t db = tensor_name.find("double_blocks."); if (db != std::string::npos) { tensor_name = tensor_name.substr(db); // remove prefix @@ -946,7 +1095,9 @@ namespace Flux { } LOG_INFO("Flux blocks: %d double, %d single", flux_params.depth, flux_params.depth_single_blocks); - if (!flux_params.guidance_embed) { + if (flux_params.is_chroma) { + LOG_INFO("Using pruned modulation (Chroma)"); + } else if (!flux_params.guidance_embed) { LOG_INFO("Flux guidance is disabled (Schnell mode)"); } @@ -969,18 +1120,33 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, std::vector ref_latents = {}, - std::vector skip_layers = std::vector()) { + std::vector skip_layers = {}) { GGML_ASSERT(x->ne[3] == 1); struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, FLUX_GRAPH_SIZE, false); + struct ggml_tensor* mod_index_arange = NULL; + x = to_backend(x); context = to_backend(context); if (c_concat != NULL) { c_concat = to_backend(c_concat); } - y = to_backend(y); + if (flux_params.is_chroma) { + guidance = ggml_set_f32(guidance, 0); + + if (!use_mask) { + y = NULL; + } + + // ggml_arange is not working on some backends, precompute it + mod_index_arange_vec = arange(0, 344); + mod_index_arange = ggml_new_tensor_1d(compute_ctx, GGML_TYPE_F32, mod_index_arange_vec.size()); + set_backend_tensor_data(mod_index_arange, mod_index_arange_vec.data()); + } + y = to_backend(y); + timesteps = to_backend(timesteps); - if (flux_params.guidance_embed) { + if (flux_params.guidance_embed || flux_params.is_chroma) { guidance = to_backend(guidance); } for (int i = 0; i < ref_latents.size(); i++) { @@ -1004,6 +1170,7 @@ namespace Flux { y, guidance, pe, + mod_index_arange, ref_latents, skip_layers); diff --git a/ggml_extend.hpp b/ggml_extend.hpp index c5913be4d..101a5d1f6 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -864,6 +864,18 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention_ext(struct ggml_context* v = ggml_reshape_3d(ctx, v, d_head, L_k, n_head * N); // [N * n_head, L_k, d_head] v = ggml_cast(ctx, v, GGML_TYPE_F16); + if (mask != nullptr) { + mask = ggml_transpose(ctx, mask); + + if (mask->ne[1] < GGML_PAD(q->ne[1], GGML_KQ_MASK_PAD)) { + LOG_DEBUG("mask dims %ld, %ld, %ld, %ld\n", mask->ne[0], mask->ne[1], mask->ne[2], mask->ne[3]); + LOG_DEBUG("needs padding, padding from %ld to %ld\n", mask->ne[1], GGML_PAD(q->ne[1], GGML_KQ_MASK_PAD)); + mask = ggml_pad(ctx, mask, 0, GGML_PAD(q->ne[1], GGML_KQ_MASK_PAD) - mask->ne[1], 0, 0); + } + + mask = ggml_cast(ctx, mask, GGML_TYPE_F16); + } + kqv = ggml_flash_attn_ext(ctx, q, k, v, mask, scale, 0, 0); ggml_flash_attn_ext_set_prec(kqv, GGML_PREC_F32); @@ -876,7 +888,7 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_nn_attention_ext(struct ggml_context* auto kq = ggml_mul_mat(ctx, k, q); // [N * n_head, L_q, L_k] kq = ggml_scale_inplace(ctx, kq, scale); if (mask) { - kq = ggml_add(ctx, kq, mask); + kq = ggml_add_inplace(ctx, kq, mask); } if (diag_mask_inf) { kq = ggml_diag_mask_inf_inplace(ctx, kq, 0); diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index cf52c4f97..cffef3259 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -159,7 +159,10 @@ class StableDiffusionGGML { bool clip_on_cpu, bool control_net_cpu, bool vae_on_cpu, - bool diffusion_flash_attn) { + bool diffusion_flash_attn, + bool chroma_use_dit_mask, + bool chroma_use_t5_mask, + int chroma_t5_mask_pad) { use_tiny_autoencoder = taesd_path.size() > 0; #ifdef SD_USE_CUDA LOG_DEBUG("Using CUDA backend"); @@ -334,8 +337,19 @@ class StableDiffusionGGML { cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types); } else if (sd_version_is_flux(version)) { - cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); - diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, version, diffusion_flash_attn); + bool is_chroma = false; + for (auto pair : model_loader.tensor_storages_types) { + if (pair.first.find("distilled_guidance_layer.in_proj.weight") != std::string::npos) { + is_chroma = true; + break; + } + } + if (is_chroma) { + cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, -1, chroma_use_t5_mask, chroma_t5_mask_pad); + } else { + cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); + } + diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, version, diffusion_flash_attn, chroma_use_dit_mask); } else { if (id_embeddings_path.find("v2") != std::string::npos) { cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, embeddings_path, version, PM_VERSION_2); @@ -1135,7 +1149,10 @@ sd_ctx_t* new_sd_ctx(const char* model_path_c_str, bool keep_clip_on_cpu, bool keep_control_net_cpu, bool keep_vae_on_cpu, - bool diffusion_flash_attn) { + bool diffusion_flash_attn, + bool chroma_use_dit_mask, + bool chroma_use_t5_mask, + int chroma_t5_mask_pad) { sd_ctx_t* sd_ctx = (sd_ctx_t*)malloc(sizeof(sd_ctx_t)); if (sd_ctx == NULL) { return NULL; @@ -1177,7 +1194,10 @@ sd_ctx_t* new_sd_ctx(const char* model_path_c_str, keep_clip_on_cpu, keep_control_net_cpu, keep_vae_on_cpu, - diffusion_flash_attn)) { + diffusion_flash_attn, + chroma_use_dit_mask, + chroma_use_t5_mask, + chroma_t5_mask_pad)) { delete sd_ctx->sd; sd_ctx->sd = NULL; free(sd_ctx); diff --git a/stable-diffusion.h b/stable-diffusion.h index 804dff71f..5dc5a1814 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -150,7 +150,10 @@ SD_API sd_ctx_t* new_sd_ctx(const char* model_path, bool keep_clip_on_cpu, bool keep_control_net_cpu, bool keep_vae_on_cpu, - bool diffusion_flash_attn); + bool diffusion_flash_attn, + bool chroma_use_dit_mask, + bool chroma_use_t5_mask, + int chroma_t5_mask_pad); SD_API void free_sd_ctx(sd_ctx_t* sd_ctx); diff --git a/t5.hpp b/t5.hpp index 2a53e2743..be88007a2 100644 --- a/t5.hpp +++ b/t5.hpp @@ -385,6 +385,7 @@ class T5UniGramTokenizer { void pad_tokens(std::vector& tokens, std::vector& weights, + std::vector* attention_mask, size_t max_length = 0, bool padding = false) { if (max_length > 0 && padding) { @@ -397,11 +398,15 @@ class T5UniGramTokenizer { LOG_DEBUG("token length: %llu", length); std::vector new_tokens; std::vector new_weights; + std::vector new_attention_mask; int token_idx = 0; for (int i = 0; i < length; i++) { if (token_idx >= orig_token_num) { break; } + if (attention_mask != nullptr) { + new_attention_mask.push_back(0.0); + } if (i % max_length == max_length - 1) { new_tokens.push_back(eos_id_); new_weights.push_back(1.0); @@ -414,13 +419,24 @@ class T5UniGramTokenizer { new_tokens.push_back(eos_id_); new_weights.push_back(1.0); + if (attention_mask != nullptr) { + new_attention_mask.push_back(0.0); + } + tokens = new_tokens; weights = new_weights; + if (attention_mask != nullptr) { + *attention_mask = new_attention_mask; + } if (padding) { int pad_token_id = pad_id_; tokens.insert(tokens.end(), length - tokens.size(), pad_token_id); weights.insert(weights.end(), length - weights.size(), 1.0); + if (attention_mask != nullptr) { + // maybe keep some padding tokens unmasked? + attention_mask->insert(attention_mask->end(), length - attention_mask->size(), -HUGE_VALF); + } } } } @@ -579,6 +595,7 @@ class T5Attention : public GGMLBlock { } if (past_bias != NULL) { if (mask != NULL) { + mask = ggml_repeat(ctx, mask, past_bias); mask = ggml_add(ctx, mask, past_bias); } else { mask = past_bias; @@ -739,15 +756,17 @@ struct T5Runner : public GGMLRunner { struct ggml_tensor* forward(struct ggml_context* ctx, struct ggml_tensor* input_ids, - struct ggml_tensor* relative_position_bucket) { + struct ggml_tensor* relative_position_bucket, + struct ggml_tensor* attention_mask = NULL) { size_t N = input_ids->ne[1]; size_t n_token = input_ids->ne[0]; - auto hidden_states = model.forward(ctx, input_ids, NULL, NULL, relative_position_bucket); // [N, n_token, model_dim] + auto hidden_states = model.forward(ctx, input_ids, NULL, attention_mask, relative_position_bucket); // [N, n_token, model_dim] return hidden_states; } - struct ggml_cgraph* build_graph(struct ggml_tensor* input_ids) { + struct ggml_cgraph* build_graph(struct ggml_tensor* input_ids, + struct ggml_tensor* attention_mask = NULL) { struct ggml_cgraph* gf = ggml_new_graph(compute_ctx); input_ids = to_backend(input_ids); @@ -767,7 +786,7 @@ struct T5Runner : public GGMLRunner { input_ids->ne[0]); set_backend_tensor_data(relative_position_bucket, relative_position_bucket_vec.data()); - struct ggml_tensor* hidden_states = forward(compute_ctx, input_ids, relative_position_bucket); + struct ggml_tensor* hidden_states = forward(compute_ctx, input_ids, relative_position_bucket, attention_mask); ggml_build_forward_expand(gf, hidden_states); @@ -776,10 +795,11 @@ struct T5Runner : public GGMLRunner { void compute(const int n_threads, struct ggml_tensor* input_ids, + struct ggml_tensor* attention_mask, ggml_tensor** output, - ggml_context* output_ctx = NULL) { + ggml_context* output_ctx = NULL) { auto get_graph = [&]() -> struct ggml_cgraph* { - return build_graph(input_ids); + return build_graph(input_ids, attention_mask); }; GGMLRunner::compute(get_graph, n_threads, true, output, output_ctx); } @@ -877,9 +897,9 @@ struct T5Embedder { model.alloc_params_buffer(); } - std::pair, std::vector> tokenize(std::string text, - size_t max_length = 0, - bool padding = false) { + std::tuple, std::vector, std::vector> tokenize(std::string text, + size_t max_length = 0, + bool padding = false) { auto parsed_attention = parse_prompt_attention(text); { @@ -906,14 +926,16 @@ struct T5Embedder { tokens.push_back(EOS_TOKEN_ID); weights.push_back(1.0); - tokenizer.pad_tokens(tokens, weights, max_length, padding); + std::vector attention_mask; + + tokenizer.pad_tokens(tokens, weights, &attention_mask, max_length, padding); // for (int i = 0; i < tokens.size(); i++) { // std::cout << tokens[i] << ":" << weights[i] << ", "; // } // std::cout << std::endl; - return {tokens, weights}; + return {tokens, weights, attention_mask}; } void test() { @@ -934,8 +956,8 @@ struct T5Embedder { // TODO: fix cuda nan std::string text("a lovely cat"); auto tokens_and_weights = tokenize(text, 77, true); - std::vector& tokens = tokens_and_weights.first; - std::vector& weights = tokens_and_weights.second; + std::vector& tokens = std::get<0>(tokens_and_weights); + std::vector& weights = std::get<1>(tokens_and_weights); for (auto token : tokens) { printf("%d ", token); } @@ -944,7 +966,7 @@ struct T5Embedder { struct ggml_tensor* out = NULL; int t0 = ggml_time_ms(); - model.compute(8, input_ids, &out, work_ctx); + model.compute(8, input_ids, NULL, &out, work_ctx); int t1 = ggml_time_ms(); print_ggml_tensor(out); From 45d0ebb30c126013ce5a34fc8756eedd8197d71a Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 29 Jun 2025 23:40:55 +0800 Subject: [PATCH 059/143] style: format code --- conditioner.hpp | 7 +++-- denoiser.hpp | 72 ++++++++++++++++++++----------------------- diffusion_model.hpp | 8 ++--- examples/cli/main.cpp | 20 ++++++------ flux.hpp | 39 ++++++++++++----------- lora.hpp | 3 +- model.h | 2 +- stable-diffusion.cpp | 18 +++++------ stable-diffusion.h | 14 ++++----- t5.hpp | 4 +-- util.cpp | 4 +-- 11 files changed, 92 insertions(+), 99 deletions(-) diff --git a/conditioner.hpp b/conditioner.hpp index f06f1e25e..4005fadf7 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -1224,14 +1224,15 @@ struct PixArtCLIPEmbedder : public Conditioner { T5UniGramTokenizer t5_tokenizer; std::shared_ptr t5; size_t chunk_len = 512; - bool use_mask = false; - int mask_pad = 1; + bool use_mask = false; + int mask_pad = 1; PixArtCLIPEmbedder(ggml_backend_t backend, std::map& tensor_types, int clip_skip = -1, bool use_mask = false, - int mask_pad = 1) : use_mask(use_mask), mask_pad(mask_pad) { + int mask_pad = 1) + : use_mask(use_mask), mask_pad(mask_pad) { t5 = std::make_shared(backend, tensor_types, "text_encoders.t5xxl.transformer"); } diff --git a/denoiser.hpp b/denoiser.hpp index 66799109d..5dec109de 100644 --- a/denoiser.hpp +++ b/denoiser.hpp @@ -1019,7 +1019,7 @@ static void sample_k_diffusion(sample_method_t method, // also needed to invert the behavior of CompVisDenoiser // (k-diffusion's LMSDiscreteScheduler) float beta_start = 0.00085f; - float beta_end = 0.0120f; + float beta_end = 0.0120f; std::vector alphas_cumprod; std::vector compvis_sigmas; @@ -1030,8 +1030,9 @@ static void sample_k_diffusion(sample_method_t method, (i == 0 ? 1.0f : alphas_cumprod[i - 1]) * (1.0f - std::pow(sqrtf(beta_start) + - (sqrtf(beta_end) - sqrtf(beta_start)) * - ((float)i / (TIMESTEPS - 1)), 2)); + (sqrtf(beta_end) - sqrtf(beta_start)) * + ((float)i / (TIMESTEPS - 1)), + 2)); compvis_sigmas[i] = std::sqrt((1 - alphas_cumprod[i]) / alphas_cumprod[i]); @@ -1061,7 +1062,8 @@ static void sample_k_diffusion(sample_method_t method, // - pred_prev_sample -> "x_t-1" int timestep = roundf(TIMESTEPS - - i * ((float)TIMESTEPS / steps)) - 1; + i * ((float)TIMESTEPS / steps)) - + 1; // 1. get previous step value (=t-1) int prev_timestep = timestep - TIMESTEPS / steps; // The sigma here is chosen to cause the @@ -1086,10 +1088,9 @@ static void sample_k_diffusion(sample_method_t method, float* vec_x = (float*)x->data; for (int j = 0; j < ggml_nelements(x); j++) { vec_x[j] *= std::sqrt(sigma * sigma + 1) / - sigma; + sigma; } - } - else { + } else { // For the subsequent steps after the first one, // at this point x = latents or x = sample, and // needs to be prescaled with x <- sample / c_in @@ -1127,9 +1128,8 @@ static void sample_k_diffusion(sample_method_t method, float alpha_prod_t = alphas_cumprod[timestep]; // Note final_alpha_cumprod = alphas_cumprod[0] due to // trailing timestep spacing - float alpha_prod_t_prev = prev_timestep >= 0 ? - alphas_cumprod[prev_timestep] : alphas_cumprod[0]; - float beta_prod_t = 1 - alpha_prod_t; + float alpha_prod_t_prev = prev_timestep >= 0 ? alphas_cumprod[prev_timestep] : alphas_cumprod[0]; + float beta_prod_t = 1 - alpha_prod_t; // 3. compute predicted original sample from predicted // noise also called "predicted x_0" of formula (12) // from https://arxiv.org/pdf/2010.02502.pdf @@ -1145,7 +1145,7 @@ static void sample_k_diffusion(sample_method_t method, vec_pred_original_sample[j] = (vec_x[j] / std::sqrt(sigma * sigma + 1) - std::sqrt(beta_prod_t) * - vec_model_output[j]) * + vec_model_output[j]) * (1 / std::sqrt(alpha_prod_t)); } } @@ -1159,8 +1159,8 @@ static void sample_k_diffusion(sample_method_t method, // sigma_t = sqrt((1 - alpha_t-1)/(1 - alpha_t)) * // sqrt(1 - alpha_t/alpha_t-1) float beta_prod_t_prev = 1 - alpha_prod_t_prev; - float variance = (beta_prod_t_prev / beta_prod_t) * - (1 - alpha_prod_t / alpha_prod_t_prev); + float variance = (beta_prod_t_prev / beta_prod_t) * + (1 - alpha_prod_t / alpha_prod_t_prev); float std_dev_t = eta * std::sqrt(variance); // 6. compute "direction pointing to x_t" of formula // (12) from https://arxiv.org/pdf/2010.02502.pdf @@ -1179,8 +1179,8 @@ static void sample_k_diffusion(sample_method_t method, std::pow(std_dev_t, 2)) * vec_model_output[j]; vec_x[j] = std::sqrt(alpha_prod_t_prev) * - vec_pred_original_sample[j] + - pred_sample_direction; + vec_pred_original_sample[j] + + pred_sample_direction; } } if (eta > 0) { @@ -1208,7 +1208,7 @@ static void sample_k_diffusion(sample_method_t method, // by Semi-Linear Consistency Function with Trajectory // Mapping", arXiv:2402.19159 [cs.CV] float beta_start = 0.00085f; - float beta_end = 0.0120f; + float beta_end = 0.0120f; std::vector alphas_cumprod; std::vector compvis_sigmas; @@ -1219,8 +1219,9 @@ static void sample_k_diffusion(sample_method_t method, (i == 0 ? 1.0f : alphas_cumprod[i - 1]) * (1.0f - std::pow(sqrtf(beta_start) + - (sqrtf(beta_end) - sqrtf(beta_start)) * - ((float)i / (TIMESTEPS - 1)), 2)); + (sqrtf(beta_end) - sqrtf(beta_start)) * + ((float)i / (TIMESTEPS - 1)), + 2)); compvis_sigmas[i] = std::sqrt((1 - alphas_cumprod[i]) / alphas_cumprod[i]); @@ -1235,13 +1236,10 @@ static void sample_k_diffusion(sample_method_t method, for (int i = 0; i < steps; i++) { // Analytic form for TCD timesteps int timestep = TIMESTEPS - 1 - - (TIMESTEPS / original_steps) * - (int)floor(i * ((float)original_steps / steps)); + (TIMESTEPS / original_steps) * + (int)floor(i * ((float)original_steps / steps)); // 1. get previous step value - int prev_timestep = i >= steps - 1 ? 0 : - TIMESTEPS - 1 - (TIMESTEPS / original_steps) * - (int)floor((i + 1) * - ((float)original_steps / steps)); + int prev_timestep = i >= steps - 1 ? 0 : TIMESTEPS - 1 - (TIMESTEPS / original_steps) * (int)floor((i + 1) * ((float)original_steps / steps)); // Here timestep_s is tau_n' in Algorithm 4. The _s // notation appears to be that from C. Lu, // "DPM-Solver: A Fast ODE Solver for Diffusion @@ -1258,10 +1256,9 @@ static void sample_k_diffusion(sample_method_t method, float* vec_x = (float*)x->data; for (int j = 0; j < ggml_nelements(x); j++) { vec_x[j] *= std::sqrt(sigma * sigma + 1) / - sigma; + sigma; } - } - else { + } else { float* vec_x = (float*)x->data; for (int j = 0; j < ggml_nelements(x); j++) { vec_x[j] *= std::sqrt(sigma * sigma + 1); @@ -1294,15 +1291,14 @@ static void sample_k_diffusion(sample_method_t method, // DPM-Solver. In fact, we have alpha_{t_n} = // \sqrt{\hat{alpha_n}}, [...]" float alpha_prod_t = alphas_cumprod[timestep]; - float beta_prod_t = 1 - alpha_prod_t; + float beta_prod_t = 1 - alpha_prod_t; // Note final_alpha_cumprod = alphas_cumprod[0] since // TCD is always "trailing" - float alpha_prod_t_prev = prev_timestep >= 0 ? - alphas_cumprod[prev_timestep] : alphas_cumprod[0]; + float alpha_prod_t_prev = prev_timestep >= 0 ? alphas_cumprod[prev_timestep] : alphas_cumprod[0]; // The subscript _s are the only portion in this // section (2) unique to TCD float alpha_prod_s = alphas_cumprod[timestep_s]; - float beta_prod_s = 1 - alpha_prod_s; + float beta_prod_s = 1 - alpha_prod_s; // 3. Compute the predicted noised sample x_s based on // the model parameterization // @@ -1317,7 +1313,7 @@ static void sample_k_diffusion(sample_method_t method, vec_pred_original_sample[j] = (vec_x[j] / std::sqrt(sigma * sigma + 1) - std::sqrt(beta_prod_t) * - vec_model_output[j]) * + vec_model_output[j]) * (1 / std::sqrt(alpha_prod_t)); } } @@ -1339,9 +1335,9 @@ static void sample_k_diffusion(sample_method_t method, // pred_epsilon = model_output vec_x[j] = std::sqrt(alpha_prod_s) * - vec_pred_original_sample[j] + + vec_pred_original_sample[j] + std::sqrt(beta_prod_s) * - vec_model_output[j]; + vec_model_output[j]; } } // 4. Sample and inject noise z ~ N(0, I) for @@ -1357,7 +1353,7 @@ static void sample_k_diffusion(sample_method_t method, // In this case, x is still pred_noised_sample, // continue in-place ggml_tensor_set_f32_randn(noise, rng); - float* vec_x = (float*)x->data; + float* vec_x = (float*)x->data; float* vec_noise = (float*)noise->data; for (int j = 0; j < ggml_nelements(x); j++) { // Corresponding to (35) in Zheng et @@ -1366,10 +1362,10 @@ static void sample_k_diffusion(sample_method_t method, vec_x[j] = std::sqrt(alpha_prod_t_prev / alpha_prod_s) * - vec_x[j] + + vec_x[j] + std::sqrt(1 - alpha_prod_t_prev / - alpha_prod_s) * - vec_noise[j]; + alpha_prod_s) * + vec_noise[j]; } } } diff --git a/diffusion_model.hpp b/diffusion_model.hpp index 0f843770e..5c349439d 100644 --- a/diffusion_model.hpp +++ b/diffusion_model.hpp @@ -13,7 +13,7 @@ struct DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, - std::vector ref_latents = {}, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, @@ -69,7 +69,7 @@ struct UNetModel : public DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, - std::vector ref_latents = {}, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, @@ -120,7 +120,7 @@ struct MMDiTModel : public DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, - std::vector ref_latents = {}, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, @@ -173,7 +173,7 @@ struct FluxModel : public DiffusionModel { struct ggml_tensor* c_concat, struct ggml_tensor* y, struct ggml_tensor* guidance, - std::vector ref_latents = {}, + std::vector ref_latents = {}, int num_video_frames = -1, std::vector controls = {}, float control_strength = 0.f, diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 0583ff073..8f68f53f1 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -133,9 +133,9 @@ struct SDParams { float skip_layer_start = 0.01f; float skip_layer_end = 0.2f; - bool chroma_use_dit_mask = true; - bool chroma_use_t5_mask = false; - int chroma_t5_mask_pad = 1; + bool chroma_use_dit_mask = true; + bool chroma_use_t5_mask = false; + int chroma_t5_mask_pad = 1; }; void print_params(SDParams params) { @@ -919,7 +919,7 @@ int main(int argc, const char* argv[]) { input_image_buffer = resized_image_buffer; } } else if (params.mode == EDIT) { - vae_decode_only = false; + vae_decode_only = false; for (auto& path : params.ref_image_paths) { int c = 0; int width = 0; @@ -1113,7 +1113,7 @@ int main(int argc, const char* argv[]) { params.skip_layer_start, params.skip_layer_end); } - } else { // EDIT + } else { // EDIT results = edit(sd_ctx, ref_images.data(), ref_images.size(), @@ -1176,11 +1176,11 @@ int main(int argc, const char* argv[]) { std::string dummy_name, ext, lc_ext; bool is_jpg; - size_t last = params.output_path.find_last_of("."); + size_t last = params.output_path.find_last_of("."); size_t last_path = std::min(params.output_path.find_last_of("/"), params.output_path.find_last_of("\\")); - if (last != std::string::npos // filename has extension - && (last_path == std::string::npos || last > last_path)) { + if (last != std::string::npos // filename has extension + && (last_path == std::string::npos || last > last_path)) { dummy_name = params.output_path.substr(0, last); ext = lc_ext = params.output_path.substr(last); std::transform(ext.begin(), ext.end(), lc_ext.begin(), ::tolower); @@ -1188,7 +1188,7 @@ int main(int argc, const char* argv[]) { } else { dummy_name = params.output_path; ext = lc_ext = ""; - is_jpg = false; + is_jpg = false; } // appending ".png" to absent or unknown extension if (!is_jpg && lc_ext != ".png") { @@ -1200,7 +1200,7 @@ int main(int argc, const char* argv[]) { continue; } std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1) + ext : dummy_name + ext; - if(is_jpg) { + if (is_jpg) { stbi_write_jpg(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, results[i].data, 90, get_image_params(params, params.seed + i).c_str()); printf("save result JPEG image to '%s'\n", final_image_path.c_str()); diff --git a/flux.hpp b/flux.hpp index a1e88d568..11045918f 100644 --- a/flux.hpp +++ b/flux.hpp @@ -512,7 +512,8 @@ namespace Flux { LastLayer(int64_t hidden_size, int64_t patch_size, int64_t out_channels, - bool prune_mod = false) : prune_mod(prune_mod) { + bool prune_mod = false) + : prune_mod(prune_mod) { blocks["norm_final"] = std::shared_ptr(new LayerNorm(hidden_size, 1e-06f, false)); blocks["linear"] = std::shared_ptr(new Linear(hidden_size, patch_size * patch_size * out_channels)); if (!prune_mod) { @@ -723,7 +724,7 @@ namespace Flux { auto txt_ids = gen_txt_ids(bs, context_len); auto img_ids = gen_img_ids(h, w, patch_size, bs); - auto ids = concat_ids(txt_ids, img_ids, bs); + auto ids = concat_ids(txt_ids, img_ids, bs); uint64_t curr_h_offset = 0; uint64_t curr_w_offset = 0; for (ggml_tensor* ref : ref_latents) { @@ -736,7 +737,7 @@ namespace Flux { } auto ref_ids = gen_img_ids(ref->ne[1], ref->ne[0], patch_size, bs, 1, h_offset, w_offset); - ids = concat_ids(ids, ref_ids, bs); + ids = concat_ids(ids, ref_ids, bs); curr_h_offset = std::max(curr_h_offset, ref->ne[1] + h_offset); curr_w_offset = std::max(curr_w_offset, ref->ne[0] + w_offset); @@ -744,7 +745,6 @@ namespace Flux { return ids; } - // Generate positional embeddings std::vector gen_pe(int h, int w, int patch_size, int bs, int context_len, std::vector ref_latents, int theta, const std::vector& axes_dim) { std::vector> ids = gen_ids(h, w, patch_size, bs, context_len, ref_latents); @@ -872,8 +872,8 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor* pe, - struct ggml_tensor* mod_index_arange = NULL, - std::vector skip_layers = {}) { + struct ggml_tensor* mod_index_arange = NULL, + std::vector skip_layers = {}) { auto img_in = std::dynamic_pointer_cast(blocks["img_in"]); auto txt_in = std::dynamic_pointer_cast(blocks["txt_in"]); auto final_layer = std::dynamic_pointer_cast(blocks["final_layer"]); @@ -887,7 +887,7 @@ namespace Flux { auto distill_timestep = ggml_nn_timestep_embedding(ctx, timesteps, 16, 10000, 1000.f); auto distill_guidance = ggml_nn_timestep_embedding(ctx, guidance, 16, 10000, 1000.f); - // auto mod_index_arange = ggml_arange(ctx, 0, (float)mod_index_length, 1); + // auto mod_index_arange = ggml_arange(ctx, 0, (float)mod_index_length, 1); // ggml_arange tot working on a lot of backends, precomputing it on CPU instead GGML_ASSERT(arange != NULL); auto modulation_index = ggml_nn_timestep_embedding(ctx, mod_index_arange, 32, 10000, 1000.f); // [1, 344, 32] @@ -962,7 +962,6 @@ namespace Flux { struct ggml_tensor* process_img(struct ggml_context* ctx, struct ggml_tensor* x) { - int64_t W = x->ne[0]; int64_t H = x->ne[1]; int64_t patch_size = 2; @@ -983,9 +982,9 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, struct ggml_tensor* pe, - struct ggml_tensor* mod_index_arange = NULL, + struct ggml_tensor* mod_index_arange = NULL, std::vector ref_latents = {}, - std::vector skip_layers = {}) { + std::vector skip_layers = {}) { // Forward pass of DiT. // x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images) // timestep: (N,) tensor of diffusion timesteps @@ -1005,7 +1004,7 @@ namespace Flux { int pad_h = (patch_size - H % patch_size) % patch_size; int pad_w = (patch_size - W % patch_size) % patch_size; - auto img = process_img(ctx, x); + auto img = process_img(ctx, x); uint64_t img_tokens = img->ne[1]; if (c_concat != NULL) { @@ -1013,7 +1012,7 @@ namespace Flux { ggml_tensor* mask = ggml_view_4d(ctx, c_concat, c_concat->ne[0], c_concat->ne[1], 8 * 8, 1, c_concat->nb[1], c_concat->nb[2], c_concat->nb[3], c_concat->nb[2] * C); masked = process_img(ctx, masked); - mask = process_img(ctx, mask); + mask = process_img(ctx, mask); img = ggml_concat(ctx, img, ggml_concat(ctx, masked, mask, 0), 0); } @@ -1027,9 +1026,9 @@ namespace Flux { auto out = forward_orig(ctx, img, context, timestep, y, guidance, pe, mod_index_arange, skip_layers); // [N, num_tokens, C * patch_size * patch_size] if (out->ne[1] > img_tokens) { - out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); // [num_tokens, N, C * patch_size * patch_size] + out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); // [num_tokens, N, C * patch_size * patch_size] out = ggml_view_3d(ctx, out, out->ne[0], out->ne[1], img_tokens, out->nb[1], out->nb[2], 0); - out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); // [N, h*w, C * patch_size * patch_size] + out = ggml_cont(ctx, ggml_permute(ctx, out, 0, 2, 1, 3)); // [N, h*w, C * patch_size * patch_size] } // rearrange(out, "b (h w) (c ph pw) -> b c (h ph) (w pw)", h=h_len, w=w_len, ph=2, pw=2) @@ -1120,7 +1119,7 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, std::vector ref_latents = {}, - std::vector skip_layers = {}) { + std::vector skip_layers = {}) { GGML_ASSERT(x->ne[3] == 1); struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, FLUX_GRAPH_SIZE, false); @@ -1139,8 +1138,8 @@ namespace Flux { } // ggml_arange is not working on some backends, precompute it - mod_index_arange_vec = arange(0, 344); - mod_index_arange = ggml_new_tensor_1d(compute_ctx, GGML_TYPE_F32, mod_index_arange_vec.size()); + mod_index_arange_vec = arange(0, 344); + mod_index_arange = ggml_new_tensor_1d(compute_ctx, GGML_TYPE_F32, mod_index_arange_vec.size()); set_backend_tensor_data(mod_index_arange, mod_index_arange_vec.data()); } y = to_backend(y); @@ -1187,9 +1186,9 @@ namespace Flux { struct ggml_tensor* y, struct ggml_tensor* guidance, std::vector ref_latents = {}, - struct ggml_tensor** output = NULL, - struct ggml_context* output_ctx = NULL, - std::vector skip_layers = std::vector()) { + struct ggml_tensor** output = NULL, + struct ggml_context* output_ctx = NULL, + std::vector skip_layers = std::vector()) { // x: [N, in_channels, h, w] // timesteps: [N, ] // context: [N, max_position, hidden_size] diff --git a/lora.hpp b/lora.hpp index d38c7116f..ee14bce29 100644 --- a/lora.hpp +++ b/lora.hpp @@ -291,7 +291,6 @@ struct LoraModel : public GGMLRunner { std::string hada_2_down_name = ""; std::string hada_2_up_name = ""; - hada_1_down_name = fk + ".hada_w1_b"; hada_1_up_name = fk + ".hada_w1_a"; hada_1_mid_name = fk + ".hada_t1"; @@ -414,7 +413,7 @@ struct LoraModel : public GGMLRunner { } lokr_w2 = ggml_merge_lora(compute_ctx, down, up); } - + // Technically it might be unused, but I believe it's the expected behavior applied_lora_tensors.insert(alpha_name); diff --git a/model.h b/model.h index d7f976533..79c25337c 100644 --- a/model.h +++ b/model.h @@ -12,9 +12,9 @@ #include "ggml-backend.h" #include "ggml.h" +#include "gguf.h" #include "json.hpp" #include "zip.h" -#include "gguf.h" #define SD_MAX_DIMS 5 diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index cffef3259..552228722 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -48,8 +48,7 @@ const char* sampling_methods_str[] = { "iPNDM_v", "LCM", "DDIM \"trailing\"", - "TCD" -}; + "TCD"}; /*================================================== Helper Functions ================================================*/ @@ -696,7 +695,7 @@ class StableDiffusionGGML { float curr_multiplier = kv.second; lora_state_diff[lora_name] -= curr_multiplier; } - + size_t rm = lora_state_diff.size() - lora_state.size(); if (rm != 0) { LOG_INFO("Attempting to apply %lu LoRAs (removing %lu applied LoRAs)", lora_state.size(), rm); @@ -815,11 +814,11 @@ class StableDiffusionGGML { int start_merge_step, SDCondition id_cond, std::vector ref_latents = {}, - std::vector skip_layers = {}, - float slg_scale = 0, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2, - ggml_tensor* noise_mask = nullptr) { + std::vector skip_layers = {}, + float slg_scale = 0, + float skip_layer_start = 0.01, + float skip_layer_end = 0.2, + ggml_tensor* noise_mask = nullptr) { LOG_DEBUG("Sample"); struct ggml_init_params params; size_t data_size = ggml_row_size(init_latent->type, init_latent->ne[0]); @@ -1973,7 +1972,6 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, return result_images; } - sd_image_t* edit(sd_ctx_t* sd_ctx, sd_image_t* ref_images, int ref_images_count, @@ -2062,7 +2060,7 @@ sd_image_t* edit(sd_ctx_t* sd_ctx, } ref_latents.push_back(latent); } - + size_t t1 = ggml_time_ms(); LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); diff --git a/stable-diffusion.h b/stable-diffusion.h index 5dc5a1814..b4d6fc327 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -61,10 +61,10 @@ enum schedule_t { // same as enum ggml_type enum sd_type_t { - SD_TYPE_F32 = 0, - SD_TYPE_F16 = 1, - SD_TYPE_Q4_0 = 2, - SD_TYPE_Q4_1 = 3, + SD_TYPE_F32 = 0, + SD_TYPE_F16 = 1, + SD_TYPE_Q4_0 = 2, + SD_TYPE_Q4_1 = 3, // SD_TYPE_Q4_2 = 4, support has been removed // SD_TYPE_Q4_3 = 5, support has been removed SD_TYPE_Q5_0 = 6, @@ -95,12 +95,12 @@ enum sd_type_t { // SD_TYPE_Q4_0_4_4 = 31, support has been removed from gguf files // SD_TYPE_Q4_0_4_8 = 32, // SD_TYPE_Q4_0_8_8 = 33, - SD_TYPE_TQ1_0 = 34, - SD_TYPE_TQ2_0 = 35, + SD_TYPE_TQ1_0 = 34, + SD_TYPE_TQ2_0 = 35, // SD_TYPE_IQ4_NL_4_4 = 36, // SD_TYPE_IQ4_NL_4_8 = 37, // SD_TYPE_IQ4_NL_8_8 = 38, - SD_TYPE_COUNT = 39, + SD_TYPE_COUNT = 39, }; SD_API const char* sd_type_name(enum sd_type_t type); diff --git a/t5.hpp b/t5.hpp index be88007a2..d511ef24b 100644 --- a/t5.hpp +++ b/t5.hpp @@ -434,7 +434,7 @@ class T5UniGramTokenizer { tokens.insert(tokens.end(), length - tokens.size(), pad_token_id); weights.insert(weights.end(), length - weights.size(), 1.0); if (attention_mask != nullptr) { - // maybe keep some padding tokens unmasked? + // maybe keep some padding tokens unmasked? attention_mask->insert(attention_mask->end(), length - attention_mask->size(), -HUGE_VALF); } } @@ -797,7 +797,7 @@ struct T5Runner : public GGMLRunner { struct ggml_tensor* input_ids, struct ggml_tensor* attention_mask, ggml_tensor** output, - ggml_context* output_ctx = NULL) { + ggml_context* output_ctx = NULL) { auto get_graph = [&]() -> struct ggml_cgraph* { return build_graph(input_ids, attention_mask); }; diff --git a/util.cpp b/util.cpp index da11a14d6..631c12066 100644 --- a/util.cpp +++ b/util.cpp @@ -112,7 +112,7 @@ std::vector get_files_from_dir(const std::string& dir) { sprintf(directoryPath, "%s\\%s\\*", currentDirectory, dir.c_str()); // Find the first file in the directory - hFind = FindFirstFile(directoryPath, &findFileData); + hFind = FindFirstFile(directoryPath, &findFileData); bool isAbsolutePath = false; // Check if the directory was found if (hFind == INVALID_HANDLE_VALUE) { @@ -121,7 +121,7 @@ std::vector get_files_from_dir(const std::string& dir) { char directoryPathAbsolute[MAX_PATH]; sprintf(directoryPathAbsolute, "%s*", dir.c_str()); - hFind = FindFirstFile(directoryPathAbsolute, &findFileData); + hFind = FindFirstFile(directoryPathAbsolute, &findFileData); isAbsolutePath = true; if (hFind == INVALID_HANDLE_VALUE) { printf("Absolute path was also wrong.\n"); From a28d04dd8190f5f61e17f58736eb117d055eb1dd Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 29 Jun 2025 23:52:36 +0800 Subject: [PATCH 060/143] fix: fix the issue in parsing --chroma-disable-dit-mask --- examples/cli/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 8f68f53f1..d06040445 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -653,7 +653,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.ref_image_paths.push_back(argv[i]); - } else if (arg == "chroma-disable-dit-mask") { + } else if (arg == "--chroma-disable-dit-mask") { params.chroma_use_dit_mask = false; } else if (arg == "--chroma-use-t5-mask") { params.chroma_use_t5_mask = true; From d6c87dce5ce22db02d9c41386fc4704f9444df91 Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 29 Jun 2025 23:58:15 +0800 Subject: [PATCH 061/143] docs: add chroma doc --- README.md | 4 ++++ assets/flux/chroma_v40.png | Bin 0 -> 551539 bytes docs/chroma.md | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 assets/flux/chroma_v40.png create mode 100644 docs/chroma.md diff --git a/README.md b/README.md index e30afe5b9..232e8816f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Inference of Stable Diffusion and Flux in pure C/C++ - !!!The VAE in SDXL encounters NaN issues under FP16, but unfortunately, the ggml_conv_2d only operates under FP16. Hence, a parameter is needed to specify the VAE that has fixed the FP16 NaN issue. You can find it here: [SDXL VAE FP16 Fix](https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/blob/main/sdxl_vae.safetensors). - [Flux-dev/Flux-schnell Support](./docs/flux.md) - [FLUX.1-Kontext-dev](./docs/kontext.md) +- [Chroma](./docs/chroma.md) - [SD-Turbo](https://huggingface.co/stabilityai/sd-turbo) and [SDXL-Turbo](https://huggingface.co/stabilityai/sdxl-turbo) support - [PhotoMaker](https://github.com/TencentARC/PhotoMaker) support. - 16-bit, 32-bit float support @@ -274,6 +275,9 @@ arguments: --control-net-cpu keep controlnet in cpu (for low vram) --canny apply canny preprocessor (edge detection) --color colors the logging tags according to level + --chroma-disable-dit-mask disable dit mask for chroma + --chroma-enable-t5-mask enable t5 mask for chroma + --chroma-t5-mask-pad PAD_SIZE t5 mask pad size of chroma -v, --verbose print extra info ``` diff --git a/assets/flux/chroma_v40.png b/assets/flux/chroma_v40.png new file mode 100644 index 0000000000000000000000000000000000000000..4217009dc0e026b674a8094d4f0f27da128075a4 GIT binary patch literal 551539 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

1r~t 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

2x32azm=Am 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 z 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 z zS}qzV8YWgZkev0$^(1u^p};u-`Jh*ZEZl|8s3OpHL3JFmOpa{6^7YFXfk!y<*>S`r z@t@>xbfUo$2Dz$sY6Q%FkwDBxq`geG1TaEBX?uQ~%Ve(gO23|?SCRBjpJ(&&qeks= zUaT{OJD2xjJnbrhb$Yb#ql+m7p+9=EvAVY)fG1a;tEKXo<#;lr2T{+SkH z?R2oLH7_$nRZN^1y<^#A*gkdS__63?K)#$783JW?hKF`1U@Eei2CTG95H7Gjuxr0k zyArWI3mjYv`&QsTTm;vY36`0OG>K7HD-tF~zek6&TIKOh%jXUBgP(h@`&tWZaqY)x zL2B`Tf0=Ox-qv(5T3vyT3)L1#{OFf|7o?A?Nh-L{_E>1!K&(Z~QK~BZl;Zd&`CJ6? z!So*Dvi=u>E0au06Qgpi{Ey<69j{w#Q0M1s<6lT_6MZ{Lo2T*qE)+_>j;oI zQN{2bta1Tk|p#U;*Hy{9%Qm&!z;Os`&IS^X*SSD0tP(*V!gDd(z&aW9y5u{f)0 z26`}-Xrr;|nOp4}MfHAd?mOkb0zrB#QPd*0N8-&ycKVxi1Sz_=@xN`o{>qt}TB_Ao zbDH~|yt3rssgcUNfn`4h9l%|P^MW3l~{ht;+Zck7@a&L#Z_TBZ0q|VA?GaW;(e%;))7};=!&$oLl!w*|- zy0p3M@XXuq8anWdw5|6LviDTNu4E*!OkMd%9C4OkkrXc*UD$Vz=1D~J79pzz2)2&+ zjKbxA&o1eNvkSo$e~~+#d`yrY1%^B63Z9(mgk7?mMg-k8Wv@*-D-uY-{G$8e(*Cbw zAtA0f#|)WNX{8l{E{&Zjzm(hyT-WbtNuzv`8aNM|Q}gI22ZG>y0)F|Klmpkb?kvIVYP zDwS0O?MVdZr=XZ<$eK?m(j-iLS?)FUb21DD|YKVU7qq}c@BU@bJ%E80yf`$Wol z0#mLDrq+548$dnVB(N(~><0YVC^;7taYM;~3O^qc_9*z=jr4^T48-)U@RQCZ;@ zLN=(iEA>5OZ|7!7XpK-3+mfFHn{K9jNZCS`Y@5PjMeA^z^5GAp%cY7N%jm7z#3BYY zYug;V`Qalz`Xt6pU3Ig% zz+1tNP|pFMf4xF3Gt|^~ZGL32N`nyzm=;H*owi@uPwdlGDZboTR|kxwy5CAx+oU0w zBZ;i&Z%l|@OKO!KL@}yTVyINf<|po-`_NHF!X-;_6e*B&508MTYON=%X?JjrZ?{@M{5P|EEu7+Kr%0eGe&Pk zz_3#a;l=r||9T+v7C5{USD&o?&DI7i5r8Ukpyi6-^=}1Jr8!n1H0VP%!I-neo4Hg8*H|JX=O%(!IMh%}7w)>&#iUzU zi_U?Q=LO-4b!Yz7ucgSO)5G6Clm6cyEZ#NJr>Z`0R54dNW!-5bmracRdyrmxt~_ZR zLzs+xl)WkmDd$Ol)yOQGl4LVegYRzX>6CzTy-1h2m6aj-BM$ASP$-+t7*|W13U)g4 zRd|0)Y%(8#;m$L7vEVYvQBjJCSiZUR#jPZs#8z&hCkrH(_HnLKc9hOe*kd@fG<^wW z6z97DQqcrRv95`+ipih$p-I7EciG7O?W_zZY=T4|d-270le*^v{f{pbw{q9-im#nesG%P^hpCp$OauK}M5kjLC{G%JW zKQ0*ZR5|934ldQ`+o6sIkhriCuf>GBbgkbiUDgme=ngG+WA??%1SbMdQke1dcZ)CL zasc^7jd5miwKUk3faHQ9+Ug?V63{MT2DbAkgU}>fPAFdg6%;j2uv!3ob3q>28Acg$ zM(1QBbZ#7xSD|Fn4i_GUH#yl4U{yLpm)}_t{rL?wptn3;GHu4CC0{wYNV1rS*5|Sj z@G)QEnj}>Y;*x{DV)(;z?U^Ol%)`9>A7q`sP-^Gc&6!b5Pj0Z+C=bufmr7?8W2MaS zru@gx9}|On=q^4>k?N}DO!t5SjGdBlSuEPo@c&J%bagX8t#fn$Q`9%NaAkf>wdSG)mb zt>D^1eUd;lVfonE0sm=&_-A>LmZp{~Q1W+XGS494z(UdKah<1H1!VC9y!dh^bZMDe z?a5@TT+m79DLQLy6Nt;y!}aX%GbvYOT>u?gY5TYy4O(93Yn2Y}vvX@W6hS6Nl_u8@ zC^DMw+T0Z4bpA9y9%Ms7Y{(Q!(8b-;-IT9LJLTccYF&VXZWpIy3l(as7i}>sA}E-V zj5?YkR9TmCfi z{Kn@F^zSzANU}g}+gW%=xhP60QkM{XCb=DabROKaP!Kqx+9{~Ms&GGgu;6)k}lv2r+C1`!T9 zUF?Y5u9ib+#r9?sFK$1{Dg80-vP@1=c*Hq)r!>P5L%rJX|iHz8k`tE z3>-d)uI;r(J-E2T;7*7t<;^)o#o1|`rf!u|mXelPra+bkLpg){{~sR~;2(Xnpylo$T|=}ClfDI{h=5f{;Q7x8*}Nt?3? z?mI>&*6^aKRbwGu>(vCJXLISIb1`VlS8!q?bx@y-2gJj6jf{VDTdPXNeoRp$*UUvA z3ERTOoIRxv{4mA6n4vwIO(`@tS!ggi*QbejfT%X1qDG_r5XpZGOy2{aB4CeVuoD(M zErNOG9h}3Gm<^Veu!YG$Fbh1V$bSV277+Mf=kaOy2*!oK*_^-Kly7MvZ8Olo{!WVi z!{G}r?t?MC$Wx~pr2KOvEZE`5 zs6;yYc?3PO>8cmXoM($&*=HS5C8t+q5Cu1+UF>?C3Ans%zoymAoqtBC|8zSN`@j7B zgSLdH;T(sZ2dY!&!mE<6Me+U(|Lu!vIt^*Yx;(AaXD6I3pb0`*=KOGGNG9Q8VzOZ3 z*W}$O^wgcmNY$#@FsD6T zJrj&zC(5$u&*@n#Xk@Z=h#l8aA)8_BKeA!vP)^Oevu7`BT0@DUz)S|Z0|?WznQ7&2XWQ-jzz*gE8$kGF+4!eJwWQ7LIrE74M z#1~8|oR4bwiyRllmnek3gfG9!_M(Md=bSKzoucoUwXO42Gg#zMaW$0@$~hqvpARU= zwp0DBDBj}Zvs87IRT+aSlx|0(ZQ72D{wdx(fLy3oIiD%9I|F*!F&p`ggTrmQ ztEB=n_dPS(^#oQO*e|V6*=h!>zBmwYvjw<0ZV=>6P#++c6kRz@JV;va=l%Ss=%Hq8 z2YyR`>p&~+twLte9H<7lIF>=D6J{dBvSewjvkJa92@NtEumY8bj;zyhuEOgKgbt^ zPcnsbTfpl=?BQ<|+`P>P)28yy4F7Cm;8s1pnp(Re`9fI!t`79vNqFawXVnqP6E8gZ z=8rA?SyYsXKa?n2;mvV-nN)1f(z;71nCe#NhizKBY#CjUZAWYQ@aM6=uFHX;kAHP}hr4R$6gqrb>1Ao~<5&B|W-nHlwfK1orI)UcR1TML| z7bj(Zei|cxb}|?#KNlRRLhf8TKH#XC1YvpvcJeHxP&C;+*Hsj8&;@Z~!8`s9 zb=A&7ufqW+`@LXrC7xonCrrf1aOX2#`Ki!jIs1?H^|2)Zi==x({e!GL{q;fY8VP+$ zt(+vnsfOt1OXi=-Z(5q$ID{bm+r19;KKcd8QE4pkgf z7BFcxJK&iUh%X6X6r``J0J9cPf}V%|HVqY_56`1!U^@Zu|4MjoCQgvc@MHsli*-=> zo0jm`nJ-ppZ$BvDsyJ_I0rPu~%uxmTX5!zZ1Bq~y@6~!W(W#wiX(fDgm?cTDMStmI zb596f^`iX-S~!v6tGjy#>OA*vjv^J?W7?KHP;pm5%Jsz?bCHJz{Cqvr zXMyv+m!BJ=-(TPTkFzf41L8aFE{JzCv-|h%=t)t7S~B7N{Qg6i*}^>M$|>AI6aT%< zHd+6GkY$a9eEjCifIEqu<^PEcqIMNiwhpnyL!(FUo4vfX$NvrbTUoG9i;w7v4<8zN zz8V|uL6@vYPkV~rDu?lb%uQIRIW}B^K3k6l;xub4mJRhcdv6q-`HGP9kR0lor88|~ z(liP)vpZfZY*c&++gZ)x!t~;M^6a%bk~FKb%wsu|Ja19U27{mT%W~2#9?q2FqMh_q z@Z+&?PH(H{Ro*4QGb}xwu=!hrb=#zRWwv&TGY%W@knPr5-4?HTb|VDt6wM zR-x59D3{zCUB*#Yvrfm2J!RTh3SwN^YfroqEv0`MUEBRK-!;om7J}Bdnx0 zmXV-RwLyqAD~XpdcCiQ)W%zt6yZQM;p%OZljq4dylU6C(oQJ+zpjRM_$q`K{Zj<~3 z(X)j3P1e$6W$fB%HgaQkvYhr?6?zQF!EZ_bvJlM&OtRpHtGP7H0L1WuM>zK6Fm`(o zoqqwHCFI|M3jfXge*JX~TPWu5Z!t4fJedK;kgW1+1w$VU3KeL z*@8uuq_AU#1_X=HDH&e%njOA>Bdf6X@Gx1RajG~6$IH2id8F}|$PO8NXaYUAj&t$j zKAHmIKLzJ!FJD^&?r!>mAp*ZkMG`FXzLD3yw6VC|xe~tr5QUmy%XL`0wpeRgHoqc% z+>QTPjsIIs5Q4A&S&J4HBZlM-5_y#!dh#o5pMsL_WQZ5@|ElmUUHDd-T9?rpg0ykP z9({ct*J!c9zp)uj>=}qw0K5Nfize++Rr#(-Hnzg%If+w98fR@!A%8ByXGme=MvIK5#$TKB5=c*ina%;Ej=bkezi$(oY|(S-Y>p-?86C3brj%4HCoNyEnx#1TcAX zbYWv5TS&DC7beMH(ekV2o{jc@reV$5_fNjYXZ=j1^ts?5Y)Qnl+HP-poMM#X6!%qz zy^X@TE(Ai=rpLQZ9?z^!oNz3mPtNI7bTb8w7X4ejG1b(vGHZQyWqfxPonP~;vHxSi zpNN{IAC{)$n5;q4hmc zKf}_*Qn_Yf4};dPwNr96UQ+#D$W5#S;-2E0Liwz07gtf#ml}w%ne?%; z7PEXNVZE$U%mCXA@QvdD8nUcLpJ`0Vs zQ19i^v*%TV8oiq0cs{mXr9^Tp^*PS#s3WPDlpqg-m^`CSCF!`6QM;#)PjR634^3&L zCiXc^F1^2l^W3ZB-w^vpsCm1MGW4K0^?<6uGr3UEQmCr0fa=5gM!V)Ib(C&NyKr2t zx>?PqWTegxv)Phn$xnF&!VM~6&+AVl7>9fyu9LE~jWV!w*A%L&jPREFi14Iey=?-} z`!mei;y&Q#p6z8U@mU(=D4LLYmEVYh52WN8d~MX?)xcjB71v0QuUC>^*`VO39Dfji zejFoY-{JHu!+x1dL`DJXd=GsaOXvm@eltf~@koeLG~gBbJU-Y^Fu!dh$=E+YoAg>0 zHnx_fGIGctR&dsFjuT_e_<`CL5l5L1Al8HM{?<7EAPrmywIC&FSEd~4akRjNNf&Bo zrit&xf{d}hzs=0d>aT9Or)Y<+TbRep!|UMe+Le8?^ zomnhTd@$^KkX9ijSEQ>u7Xo1`7-3tfc#Ey$#q3lMg|~YAPhT8QZ{*hu!%%fE)>%3+ z#0Fxf;QK$&1|brR0l}|pg(DjM?wDGu5VQP?Zs%YL=i)kRp>RE@=JR>g2a{Q+=EsL? zX`1jWsLs{1J$NsM*^C-1^1rKA=m6~tiQ&`r&t8-ALy2e6{~F;JI?gOW_Y4&7Vqnln z$u9y8zH2ksAwRDdb00B#jyK3$n@{{FqwV=jH9SXLBuimv_?aBq{d-{7a>M zC$S*=)vM=?FAp#(?)SX?#))8IoktV#_KX`ZtEkFZ_)ATEvG?b1s{7eP4aXj$rIb~f zt8v~F!QF-)awhd7?u&ZM;{p0X!@s||Vc%q^sv|ZYh5uOaba*>5VJ1#FK!LKdp-VWR z_-&814;Eg9Lv&~((nA$b?%MAMW^~c8%Ba>F7yI4lRQXj28n4W}j02}73f&O_ z;mEEs(xVo6s^%Tw@+_+E!A%UZ@hbP-LR`Jz{yv4{8HC5Wo`lm&=H_nPxzqIQ^{}kr zvM>{?3l;? zr7YKVik_+yb{;Tjvp;jb#N!%($?awK1lw52W2m{;utTbZEJR(3g3F8q|beJR?KIhHhEL-Qh+8YuqcH`fY)_r?g)Kx_}?Vg_?zodoMD z5?zEdp^sote0zTIQu4`lsF z!mAuKz77*_+HKgV9jb`ly>2qWsuqm*j@e^OE;@39x&Gn$N*sZ^%#weVAjp4nKfM(W zegL&fyCE={_mk+pTiH1b3e6p539{=F_ectcQICzYteT|mA3b29wX*w4ra*g9$tk7$ z`Lk4a5+vC8+1d(QJ7i&jb|>>rsHU7dx+I3+N@e! zcjN4?E3Pq; z-2cy#aFkq#6D4rc=b+uuTL|<4DA6)$a+cceN22e5tY3lGm+-F92NvGERqAnYR>9fu z{~2Um@iRj8 z){UX_GA47wLX^d>tNd5Y4UbWzvs7#Ruu_h;E~*CfS16W!fSb+` zKE1hncP~)tie0Gg6U{Vrre0V(3w7qxb$hl#{(>avFGN<(#vMW62ub>TZlz}HiWkdW zD&5#e{z<<+wk{326C518d&kX9HCtTzeSD2^u6cDR`eGhgs(MSbNQ9kh;o#c>4?t2+ z;DY|1doY4M)(G`0aea*U?Ae1o&t;3g4z$CGwN-~k0=)ut+m}J~Y{H!&a`}B8saHK{ z8v)VYQ6tT~QALyUsxylsfGk$r4}yBmey&w5u4v~GY~ z4v_nic6p={{~Nj78P!o>a%>enWh{4b1MsUEE=}aURkBcp-)}+37M6+LAAoP4>dI~) z$n8@e!-ys2Gz2836L7E9Cu^_wtk^e{5*>%+Syq*rey%ZWM3r+kt&#)kn*H%+yp)+4 zR)ucHSPP zW`@{_d%6xK4Gm7LiCB7c>U_=*?kRI@iK&vXt9oXV@!);#X1{sy)5elSbR>`ELEN)= z8ns?1J9%bA_AUKRVXcbByo}7_1pP*GmHUq`Q|57nNrI8@=gu6zS#~xhv#L15=F`)@ ztO$>TGT9>OECKtFEQEWg=t)1>eY<^xJL2!X$+P`b{!)bsyG?#!XHOQfoUIsPWEr!q zhWzm?%2mVmAa_Lj6yi0Tgm@Xw#sj)ITy=PkUSX~!xM$6mX*|$7eoh@1{{bIQ>RO;r z@VcsN0`~LBF7(hT9l=`<=~87M$??Wl9irtuwdf&|^OunshLou9&+8|+Iibpm=(D4! z7UlGBpfp^hz@M)n&IAt36HE$n>k{h-YmNY&8Pr`P(9?P7=6UGf1hj>O1VO&Pk;@wJ z>p(2$jyP9EYT;-c7$Yiw2YLcJ+tH|0*aelgh0R$-Xb*H;%C;pSxmX&qR;LyM%1vZ1;SuRzCTnwN}35 zfdgWGkaJvDprnAi0H1$v%zOX)W$5+m#wRaW9*B<*49t0YWT;apnSXX5LI|v}AY2#{ z3IA?jqF{4vvWxpdE$2FV znV7{sSbXi4xuMa4@mW(1iHCHRs5%#`iwszq!>pQ8YJU?=?3!J$un~Rp(h^m!iq_A? zDH*Y|l(_e#k6Y1o!N}Zy!dgzS2xwKKly6bPdb)dhXXi%Tq)G0)nn{Z1F?t9E4n2ci znH6I1sG*AXDQmJQy>?^8B^R-1_xQ}#~ z)l~!XCzEWl&3j=xpxh&jjZ|R$q->`oE|fK_bW}-I4Lt}SxKrozsP20eCpa z0~LP;PW-IJo{r+RDCECc(VJ8W1;QSX*ONC!kkw)Uy$l6id)4`>#KN{oX8c1V{)mV4fXV^s6DrRV=`|GsM(J`CmzqX z$#@ZAXlQ2g*CICK>r+kZPEVn{m&7mq*C$-1gkITU>auoExT7ldI3uDa-1%@`!Ffu> zs(`f{NDe>hTPeEPgu%7nfAo_7T24bxUg}r;)6hatQn_3PT8GWOZm|UNKW~cy@0=pg znS8trJLrArGn!z4mP1nULS8lL(tP6JB67Ns^vMApLfeJkl>zr#8G7*zmgaX1SS4z6 zb0^aoAzVAcOaxl2Ft8YKVu3w6f+UBMJgU2P&iTLA{p}FeVMLRvkg)RYyvREA6%git zx2OJ(kRzmL!kZp*qF!dM(nZ(JaNd0=J3vD;& zJFF$T8)yqg8Rl+MrEBR~`hZCrlIZ=#?aP}>4<(=6R0Ks|PVM}}IWJiwF}^6ow=N~l z;NUx6-`2yF(3t5L^=+ds-rqp4&M~CCkMdRSVFOM>n<<46^NO@;;+p8#!M3_9-RzoV zO89?_UF)HkP{fcrPyjgL>O3bQLJ0vc0)81MKn9f<4e$J{iqvqE~nXL?vCIc$^~8i~({`DV2Cq29h;$jTA0 z+5ub~UnN9!G&#fc(@Xiil-+%ZgEy1=+L}!Edojfd<2StA74o0{A$=Q&2%m5aa3n?V zjjfcj0^JQ)c=*KjOtS@Hm%w)k@adiQUY9yW@BXkybGG1Zj9Hdl2od*n7}Q^?H27re z6`W(^)e;);x&e5rVEM-2JRMTvoZtowGsf@{GX|PQrM=qhz2ci(a#mvi2j_kuU8#$< zsQ3;#UB6PZg$D-#ycpaG>Sb<5_R0OjeMk`aXzp@-P-Ww1iO9+q+lJF^ufEkx@UVz$mhp2>h!9uOB2 zBZFN3(-mCM<)6C=@DoOFFj#Q96>x`d>-6@Fn(Ey6@Wh9e!9(Akkixbr%p>ZnDDOIb z1pJ1~Twa+Wce0n(Yz(L-g_T0?{>%zME-P?DuUTixwYWEq-`#s;ZcN>|GbiUlqjcue z73eEFSup(P6TWZ{V|5)-J$Giw$iAY2wv4iWjuLd7-GAL7`6)7=fDe)X4fB3gKeVLW8rj2&OIg+%ABB zY6qA|IemEN?Hbul+DX8!>r2w6A|wk`@oka2E6}zAD%X(H++&oTk1{T;CYi)Z+zrJY z6i5F`d?NjtfGPU%a#9A9BZ-6ezE3i~G)`*@7LdeeNse+Pm5r`kAe6~KSKrf+^W=zT zNT!ztjbL&Warz6F&m$LjO|dHMVO0mSCZ#fKWc3OkNzGo#K^XM}0~U$DgCBcP?m}MP z0%HqV?>BRv3o&ezFi-@%*OMmEd6$9sTNE+%B_Xtn?0S&guClEMI8cj3nS~at`c$M)Z#?ct zL6(2GK1Th3KGLQw##v~qVM-iieUR)wC_DV`$CcF7p7g8;_)`PO_%k*B)10rl8nmykIutB4I|F)4d8 zRaAd`_!NjUT22;=wfgrA$k7FpEa{2`ywtg=lfl7O;R=e1$+M#`9_d{k&<%tCb7t4b z56g%3Y;bMW1;fp|vD1;*>Vh!cqz*Z9#Fj@>&|;OLrH*+xK5DFJip5ve# z-%+Av6f^Q~XmL2D-AD{a!Y)+z)8HnRRv?tLwU{!d1tocDYlmZobZHV`RP{0{?I?NF zj~ucLyi=#eYp~1oLjYw5pO8r#FW+OF!gM;X?$$!TNn9~SJFwJ(vmUfYsHr=>x=V1RaGv2`OQO<9;Gn9*yK|OnX2N@kyPfK3Uzv7O$pgw~?*FD20hi zt}3G@lQs&N3W+~~x2ExQ*3qLV^G&T>CoMD!g8O?!)QTIzwHRx63RTRm-7}=U{Mr^NSa^m^y_u@pbQ8{kxX7>nI7-74^x$b3_{{l7gC-zzzMPnOI!x9S9%psR1cZkshIvD4|yl zN9&3^38y_%Pp3}KQ3Rg4;+=%Th*@f80QSO*qt5ZQN37qF3zX`flRY!^+U1kVp-Bp% zDzAunR6sA8dJEKoKd4DKylREA_sF>i=*$hc^mw7?VMAjpYCc2>dcl^@;)M+;Y(`<% zaflL<^I?hA=BIUab;3ViJ~WH7M>Jc(L@PW!={~@ySkUjrUul%Bth<+R$CF3?IEapw zq4?1}{lYDHkD_#MdfHK4w*<9zQxK0a-ZJMS6pK(K{TtLkb$BHhd2YFS^@i~9(~P0c zyYQ!10HcmfZ!B;>sL}pb#zTqZrZq}+Zd zYU>uze+XKWMz*3r{`a9QFT%5V>PGyR+IC7LB`fUt>GwBqu=+F5TBR53jd+}VBygYh zNNv%lxNxX2;U&@YY93T^UNb~}-+p`XJ=xLBrzyBRC^duB7C_iMM(D78L44vr9uT+vWj`d8}E<}5{eh5J#vXGJ+5VV?y%^XS$|56Tzd4f+~5oM zlEF`_lJDTfEbMk8wj@n-%(R^(k6fXp#k2CtdHg9QQ3;r z3uh(DTSRu$9j-==8w^yR<12JUe`MNEDc~ZKNJ#bWV?(2(I?Z%iz-J}X4|VS?Gcg>M z!c@5VL(6d9)=1MbJEG>5RW>B0?0T)hQ(Q(L@-bjlX7&noJRa=;H)I6*+IYR(5_>#T z!G@PhFu56m{s;1!5ECj#Bjrz<9P8HR+rWJhx#&Ymvz7QEHmDPO-Ud>)S)Dq_)Wj&R zkaI6hTal!s?(SMZu1Y^y0L5qEdF3MEEN&*Sd*Hx6 z*jsj?{N3>aw>P>j&qs?kul3oybXD)GpZ2SCYl|19&C5Bj?~ViV^JPD0BCyCR^VR>0Zj0m+1YU?d`}i6Ta!KY_Y*Y zb}HGMdoqXnREj20A*pTvHxk6&ZNg$1k`SINW$-Khfyu=pYO;V z!25cM+4dfEoaSyaLT-Ek6AS%TWC&+R^(!*x3#RE(0u?j;h_BYG4)z=?%glUu?e*+4 zT6^VZZ|{12_X_!7gRjKjMuHiO^W~IF)uiy~ItAA#<%%n0!RuEMyjgL|Ael%n0(TxV zZf-V~R;f5%h%#vf^jPz>4-MCU-u?8^(xquKne6ZX9fB`5kps+CLzL~oI~Xu#4Xays zxTDdb_(`I!mWMPgB(%;F-+Tl$6;u8Dw{7_W_Ttuw0W@H8;v(d1&3JTX{`x$7aN;|- z#+7h7DR~wzA57SkGs-#-oB2$Q;i2$3-oG-nL1GDL0#vq9uCfT4wqh zD9qc+A!Ki%g!1jZZWYNTGKCBRPP)+PFrcBI&ce5keO#V>^cR*1njWh2Pm8!nyJf*! z_YpI;Xx}4IQE7iDf~OffOt~#_=ZZk0)i3T#gCrk6 z6{O=I%OQYr+fEDpXv;M-@H3_U+QC_b+LHC#y|r);cnsbb|!Y~n>3tz6Fjc_`OEU^}qgk1$7Yjn$8LSL7cy;-7-}|FTK(By%HY%aqG1=-Q*K zS~%wKY|==#9^X`l*6b@>x@?)lhYxUFa?kT`6Rc$My%8XI8p3C$%U7H3nc zezT0wVK&yUDh>TNB-G0N`>i_J&gS+vF_ND8bFBL5MESS#7uTLuIrkJV)IZzVH}?9W z@~8tc@jk4E|L0TOc+o4lP&N~}b9c#gGui;pJ!w)wz_@rf?@Zc*#nQ$4STj1rszvVS zps&dJwPefbZIM$`6AUNop{EmZoXbBrJ2`M6FjTN@8#x>gSk`dY8R4s(3aClxb4P>X zo6{H^L%?gVtG#%2jIgx&`hS%84`0|kbtyY`^K&7l^@S~22I2;)0OSLmPbWJE5(kz8 zI=N_);*-@Xf(oO_tFAl`87;}sgg>{KZ=et|L)aui{fI#yM3Ksv$4)l#{x`0S(u0G* zEG@{93%4Y3FCMeE1>??4pZzHQ#9KnBU}qX&MOOQRVPQI zuuem)`ZRjGwH689M{qhg3%ywh{dt6M1%Zn?M+oH-;vT0-oD$fg=&t0obXEVWymf`) z`e*^lIah?UH@XLtyT3o{lEdmAnfu9Y~jW_$N>#iaem1tTVInr zb6~i>GPgnt^B;(5EdpC3N%SwU@p^g6CQs4H7R2-tHI~l57aATq{sBjVKm+)28a{0o zO<45hMl0Oc{4H^W578%vI_`e>)D-N!;qPGeQ?%QTm-v@$Bpq06EIAaxgXIu~92=tL zaF=Fr5P9l@?zY6rBYTAgI!Nh>5SB;M==g`y$p<+FUlVm*_}Oc+TRYljpPlT(c6M!q z^Dd;G?8Ez0uLjV{c*##qtX|n+a)c3o%!=%2(cY{_nxQ)`lPx|s~9_%rf7>Kb!5LtlGT~IqbB*l zpL9o_;r+Ty>?$=$gK1Q}if}OpGmUKKd#E?kr53D;*kkj0vfpV3nc3NSoe1#ZFQ*P_ z!7l^-3||0)8*Df~3>KMJZG#_qa>u!f%f*!eH}GXv+%9jkG`Zv#;3KF#s+zw??!A&t z_Pa~lTuC(j%@kc}Xhs1KMZ)^SUR9}!2MtNKyP_yK+d zBticUZuq^+F`0mar~t(l`RUBR`BbdHPw!)6l^&VdQ4Z*J@7^}Up0(M{lwhK~?t@$W zJ64E3m^wdfIb5DI13gh?#9wux@Way2bemL6(A^uY^{ipdr#1#B& zVM`XPK#;U#q_(z(`fvZvojbr!+=z<3b1zyZ{fh#|PaK^5?Bas-G@~<3jmwD0L@>^2_&$6&vBT@T*Y|z8?z6b2kS*TX)Md?j zMk02B)~^pujW;|V^7?*tu(R`GTp0d45}zpUA9A`=*mbb1JX+ev{`1Se&oepc@4)UG zF(v!Q+m4!F+_U0%3*PI&Gp|?f+&Pz1f}L-{k&s8me}8lGWjJAlYp|`78TM6#tiKXE zk5elfG}^Y33$iG>-IL0?%D@|C*zunYa$y+s)^)G~LYLCq4O7RgrLa41r0D#7XC^J& zl(=h)JZ-%u3NFF7dSSOn9iHS}J7pw224DvYwlHE>oS{AlhT8j~hl+JK)~EPyj^0Lo z)P?v%gQe4RnpL}@5bQ4eW|Vy0DChv4XGXHJCU+8nRohWTlHi_}&1pDlDY}5#F(+f3 zC22et3ZYY!1XcKgEQXvgmz?UD0hAuZ+ZlLvaE+I2w%eY+!CHKfR`v>>AOa@U$RY06 zAecftGN(`#EgSrH7?lz`KVt*CguSx|u>otF_i{l0=J14ONds|JjcDz1-Y2HB(m$Q} zHf^Y<6wmSmtMaA%zg>yw%a>l^E57%R>7aE!*du4G!?Zs$jV(u7@Yg5FgJz>j_;l;{ z_MN!0SX-bpufHS)d%H}uIs@&cH2nSQx%;=vx!ZH8Nxd&4BsUL=xVpSMy6s)nkp3=*hmV~1Pk%3BzijEgBRpoSQsEFu`6(Djh>8Z^{9but`P}4~URyrp{4vgi3_3+aj@hEGSNNS# z+M}7$V&)@Vu}kvX&?n$EWELVvTHzUrnJedJ8W`k(o?PVj0x&bE`}RV}Gs34c4;09c zH=)r>ww_mLH^u6|vjrSVpf7yVySdH0A~sd&pN*GdA~{3fk9%VA=PSz$D;g=96Y_2k zYV0}fFRj4&xJ=BD!!ICd%O@ElU{d}GF#H3T7A2@u-ni^p8T|HFFkP;pZu@#a-6rlCXG zG{H1FtxXq`60QL}9VPsc^zHb|=4OQiYI~nZ4>wwkfEZ{fRc4?cX;OeGeO@Nd-a-m- zP@pP;PtTzpFNx(>nJBFpRaK&{5m%#yOR~2Siu@Hjlcbuu`-OEg`0OAJ{b0z?ofl#O z1cLsacu6vGYD>(Zpt_QU>$vb8#H5dBj5g+A3r3Vhh&z=o_tDKnp zWxh=VLtg5lwh>t6U-bNFN>vAH#Nno|yblMpffu)=l5Zd@F@(~Tp)f23g1f|fZSGYa z8Si~leRx%wg*MsvRan)X2>gkqF*-VEX=)g5tBR*3qSvBpGve4|maBC;2JVEpQ^JC8 zlp9P8{u*Jlb}nD}vh(n4@AN4*cimN2B0bTRvCeaSkN0?d_~3MC{D&!@DtdfJo#EDV z#@n~Z{m4`ws}!F*>QLS{F@|8vdIpZa_rS3A;e!3NTT!^%E3Pl&(pneG*`*09-^bD}&>p~R^ zRYRho(j5}DcuCHTu=ztFy39IXw*yrM(d9=Z*T2^aIVRgvvyf&Nk>;z5&UhMQcbB26X1pcD zRrArmap=n*)(sK7GJ@{~xIl|{PPbo;S4Ro!pgytu^VKQbDjW-Rz)0bEmiGzhyCWAV z9TO64AtQDXAl+ht9b}=$S7Y?8+1G@~d^PjM)py0!g5f}}|F5yHPN&Z1sn%wh@BPiExqQp4`0o9mhbuaN3@u(7J{8(t#7!MMA<2pYn)Gdf7GHh3B8| zPIb4X<`R%FUKS;nBE&Dvm&aI(dtfv34Wwn*m5y5OV;-n8uq*r9Tbu+vXaE*=?${Z} z=ve=-Y)~U5MSiTZ?Tv1m74%f}U^>e1aeS0-T6IBAy*_j;c6W3Lo6l|mLxD`|HZ-&t{X<{J-}}( zdUQy?16X7z$zZnOFBtr7$kDfGQ1HZas~2a}3At05nHixJcN-aF-?k-2@9YA^+j)oO z-hcFBZ{V0k?lXl!61)&>SNY0W z*tU;cau{7gZxH6*m#3N;_{2J$RCRZz;k{m!(N!N;7{{qvlDs(0v?R9407CKg>{;csn~B_ zJl721I-4grHYX_1f1{sIudO&!HkCEU>M1p0ENXr$KyXgdpep2G8}OOcxJmgU-%WdlP?m!RRy#a)Q)b&tFcQoOE%o4)d&; z75r|y+r@^jIe7E37WPi97|wEk*pKCv{%ATGmtus&j$G?)cT{b4Gk!BHKyu{_9)BjW+|3sF~DsX z{H;Qhju14pk5C+yV3UOXBcRi3@MNwK`$Eq>3=bdaes{FLxv|H04LB5aaQOS(^z^xI zZ(roxJJAY-wrtQ1h_5hM{0&t1=zl;q4#EbE&1oh!)r6qw3U$94Yr z+2dqyTe)vck?#|_$-AkoZQy5%<`7PJ{U{O2>o0vl6{%Xza?W*vH(8+TWK$;xEzQVe z)%Q2n6fJB_J22Fqck%rGpWhdWHlS-i_LyXC3b|x<>t$HQp=C=0)tu{S9pvUZ<*E}o zq>4#O$SL^F>up;|R;f{x-HK#?1zWE1NzM^Zk%D>!m)haZP!a~zP&z+PP2jDs<38vM z8%$Mz?p=nn0kF5vZZq;Vn-_@!E)u|^T$=TrkxJqzFZc2 zieRN{csgZ%eL&M(`jQ`VquT~4IhedI!A`KN;5cZf)Bp_I6|Yat`6OpXzKKeU_fh%D zUeCOL={Q)DNZrAMlb#XHUo%m2J?0e;6FzL?wcOZ`)TD4%a|_g%M2^i(dFsX6ASs6+ zXltTv2(zL+I*sh6-mYkzyD<|hgevAU#@&m}jTT>3i z8_e8Bdhf%ZF4}O6z<3rQT^uE{%4Q?$qF#wKbZk<&KAVxfMEAY=?bzH%kDFT$`^f4I z$$JJ*2A3a^$^`ABJCCw_akuw@Eo1%erEX@-@gU=l|FU%S34f0phE zvSl*YLzB7Xm(2L9#~O4JCo3l5Wj`j^UCtyIOnWL`}(w_$3iRj|3$#R1Jb?LHI zVFc$bME8rV3WfR3dN1f58>c6nDjF1bv@k}yudpF+$=$IfWZNNfY_+QQX@k!6N z=5p_VULwl@s9FqEnB!T?W}J2OeXZz4_I7}`NxPzy=Bx#j6~Vobx+IqLp=xr_ZKFj&@Mx;gHd1Q{DyBECh3sswSnmoNSV1^{HwqG1=*KwHN^zx%@ z>OG~4eI#Kbz6w{LiKFVMIA!P?h4+FM`W{-bT3%P6W*o3mU(iPrW7{TafBomkMHwxP z{6EuUK!kjx?+w^jj^KaBUsm>c6ptI9UtxjzQS`Tf?r%UX>$7TsC@@6}{W+}}??AP0 ziIW_hE4$NGlk`t|rJz7hnpuU%$!l3$Yr#6UI>sVkqN4Qgr2g$?`KDbjKN3$DB zZz*AwK5<>pA4_a|I{sxP-Ep0xDS1dwD3g~O>9lPj|BN&4SL5=xrRT6E|5#-y;Jf@8 z#-)DX_E9k1t$%@Y_S$UZhg@uT89ZKx0y-I#$BAbYSkDh93#bO@d3ZQ zDb^Q_qq%g`mrR3dN^^*UQ5lu4|Jpb3Ibfd2(Vu2i8x624Rsau6>XwCVk z-TTSd`D}?ku(*$7zL@Mh$rxPCxzvm!CwOVEU>lk5dQqn2*O)vTBRgHaQa}kgiqCc6 z^%FKYq<-ScFA0!3pKr%VIX1>_fAPLls3fX_t4YOlZoB-<;6H{Sz3F9?KmM8gXyz6Rem zL7?aPD;ehREf%o6rp(`wZyCX}Id7*J!_)Abe%2H&S}_B3kb;~REF!1ad{G2?aS-9q zrNM&&=`RV3^JW7Ij4;^x_s5u4NH?KbEF6L=-UAocE5>Zfwzobf+AqVO zTg_0X5rg*q6pwf;qatR?sioEGMFUs^jVcH7p7{~xJo7`Il7<)8YU%te)GbtuPT;4O z)kltakuph}h0tG2Af{&TDh?@KxL=JWek@5M+^<&|Ssp@yPhU`+N zU?U&0-`5qjrQIE=O&MFWDl9uM@8yTkKR+(_6gr0Wbaz3k>Zdc4u_@*FkGr}^_wbwB zl}C?mWJ49-Yp&Vf#%WqJT@7|ri+3@rOYhy^pP`wdkmnVjgbZzw`#DDEzrbfcAWvsw zoQCHj>+hxEtn-{rGvKf-xN^QLVN;TrkmLs}i-*4*H5A%83I9X~cSI9Rr{$AQ{72^E z_0izl09U_H+nwQrJd$H0cy5z`^A8w1+wJx0|KI*22^#K7@aQ~A387*>B{vb9A+;}T zX{slOrZR$bkuOI2`qEqLQ1dywmrk+GMzPDMnU>d9LN$~r1KHn;rTMxS;l>%9Vzob@ zsZ^j!!=_E$hZyq*g|DB$*`&6EK+e1?BW(ClCuG?r!4Sdl+0edCT@z zisEnA*d{dCiZ|xWs~zYoTwNKOdT~|BVtL{DZ6WIwDrFyHYz{xkh- z2c-yP=EW&~$;q@453B96gf9%+iw>}my)5ZNr{E6q&}wpC7O?I=7V7BEiy)WJ5*m-d zc9!tVpkTIE4Yj1#Gj`m@xfNcq+uS(xIOn)6nQv+0t)}5h`fw}bVTOgns=%#U-Y=;; zpFdMyS+*JLA}ze;u3!vXA+x*k&46n^vQB>Ycc2vRz*|!RG9!xzGzs^JdmkqJ=^`Ro z?aQVP*}0|L*c(~++o4w&Tdw$rK7-3aduU?ewe^_$Y4x*)IxHI3phojC+m4z z5$+mp$?-cnX|X}w{j{O^G7#@=wKTi2zYd}RKG%nz*rS0v;2;z5B;m$B)Tjzs!tm9} z$}445>Fc(YlP^J3??2g8vpHx%C7pZIEj{A|wI<%M<`BJxKkHf6--W6S`8KTH%?dO3 zIScO&2=-L}5sQi$;?XIMuc5@*@24jF3Xj*gGZ}qvijz77Gb%aDPP~J-Wg)oOoX}4| zcX^_I`_LcH_sH_yojVD}3vd!=FdccbGCnfW55r?)9V5yydAQ@jFm97vp~rvgmT~-o ztJp1B-t=bmo=#xXaRQ}tF?d@I<-~`!pZ;%o7Q;j}{)@(>$v>{M5ss0O1}|X_8y>hR zq*%OoaSWZqF}@+V~j)!}n{3kENiRFrsMy)tj6tFcE=rvezWPZyk9;4Ix9H8X-= zB@P~3h0b;26*G7V=&O&k$KJD%Gup651UG^7*eGQF-my_|(m{A(l>p1QF$}LeY9E9Q z#KF)5r1398_2TX%wQGX$IfUjYxM4)pcoA8lR{#D0;)gL~!?Z&}?!!3jg;}iqfd*(2 z+~9|_yM*Qtc>7z?STE=1ULekjTYQS-{u^ItozUmHXzVm6G?<1A668dIMe}oL&wES* zuXFwhAT9VMjC1DP47>-vCi5Oq<45lkjNc(bebE_qToY$0Ll?Tni;e)z95vi07*WTy zyo`zMF?3&Ix`u=V8t%j3UN3P2Ww1?2I=KZhIHin;P9F?bQi9^yebCk+m`S#PA!|l}$+gfQ;NfhqwmPWLQ9-%* zdAk4QcJ8vqPfcXLvRz)i2X1~d|Mg4xG(SyF@V)fg)Y+N+I_A94X7LEH+EH6i52twn zVTM4-Z{S=tqU;ly%;b%{?8#DpwXY&=s5$o{`BTi5$VlX38&bSP7^4E>%%ufkqNTl} z7D~$!B<{B=<0ysQ(-|a6FpFJ-=hI4z+{!q>1r*YZb8!*wU!=<~!Nn{<) z{J>;6W9|Qag|svo&#*yo?66^kl?MdXR2{udd0B7A3J3^UgSSFAMgV=l--z!9`S$VB zi4S5$Bd@B;He#3d78G8e4 ze@gt9ut`sJ{%&U-M>rz)WZGrieK_+uZFF^aXISVmLP@lz z=z8rJPK@l^3s;;YJs9Eqdr3Rd%em|r@Wvg`yX(qHIt>`ShVq=qmyFf7ERc`+@aqlj z_>Y_+-csw>Ap@^cT0D}j<51?2<=z{@c>=(c->H;@Gh`=ECBt4_c-06ogT!RY#ojkE zrtgL4vfkI5q>A}aPvM?$ETdHgymb_6?wJ~g_=vXb^eU0L>=__63dV#vp9E0#Bf|TR z;>ls*x0Sdu)$BEJA&cy&0fk?JeC_qm9u?LEI44JI=|Z58FGcU&pJDc z9Y^78m8ygRBkb=!!HqKv_YvUuH%xTkw#ZKhM-|AP&5j*;Ngs-VCJW(nKL{DC0t3|~ zenQi#y^NYBVkiy%Ifhwm`J12e7XDC1^1RL2y*T&|$lOmhIDjsA6_o$b&C;@Jo7yEi zvVn(8pLH#fc+(~zc4zMTOD^omN3PFizmPU9T<;SfLOgAP`P(J(-l-q(SnHBAk5^AJ zlaMqkh};6a74=t>CRrN+7(Ad$buNMgl^grQ*L-zgF4c%C{NR$m&m{DN-yx#R)gaO^ z@dl)0Ko61~8f8HrLV^wb4>9Ta^MTNN8Rf$HM~QPR%YVI5b9h$2$YcqSVa&or@=8OC z)9f+ia31@dDr^&N$H163D>JKjWgxsPB+!um{=HJ3_guK* zRVGm=UnN}nUlNXp@{2@#_bQ~?3vEXjm6udgf2oP(LpLW8{|sScY3^lhH-pa);;JjR zJ*CFK!=~%YD)5rjk;I9=3)d}L>2G=RNz$-*Y#-R5&OdtMVa3Ev9DZ!~#Oit!ysR2Z zy1Pg)bN1B8B)&cpI&4Nkx*v|Y%V-K|`y$L5gw{=!me#=r5Oh^1@yeIsQbkP){yYL5 zkJI`-t<+xQf+{_b{nv!ePZuSR5)=3R?(9U)O?P(I!6U>9x7z!zcd@By4X-e;y+EU6 zlw|gad?FIvwu`OJU8E7;tK7~8WX}Pf7TM6Ovwo{jkqTZ#CqLBX=&}*x?QqXwl$H+D zZnj~O~h%>b5oE!`MO$3N5F$Z&5Hq4P9W5eM%DJBxIlowq^sl zYM{Y&z@Xa|F!-pL88K+919NS`$-^1VGs2!V$g2SZ9$m}Jl^jpN$lvo+jH)#zd`B&w z+92cFA-P;>99wJ{`@5h(ga3Z+H7~+J6ZpC_ZzryG=q=tjGT6VT;#-6pr1={TUps0mdE`e!n@L?x-5fB}mqh6J-FA1cETp}nLH6K*1mCT8U z2XU@FSkVQ(je=Uoz=GF^+kAG6WqQR{Tzq85i=tP#9x>vtFficV0{U&aYsr`Dh~DgM zY8BMQNn009&Lm){WI2sNQH#AQQ4Rpb{7sM(f)`>iDq72Rl%7Duvd-3^)(a(pke<1?L62=pmY%AT-8%t> zRk*sEPZ}ELtM3f3ZWDX>-u1I11@Mc_bk=h%kpc}#OnTtA6ViX90|IwKr{S~xn2`ut z#(Qw=naNkJ7-C)yd)bGG#e^?(qrC*iL$>KNHBAk2pb?{k0yzF#|Iyr1(o4IfnR%ks z*)N*JU2)Oya;n-fjiCHUgMA@H2ul2*_SKxs7|Sr85`87;)CO{(mmf~#zqe8O|5guK z_!dz!5>YlO#6NwZ_`ze^k#>oXyII%nL;GxEcNy^4YRPnVMRPY9@ITGsT~;gm*xES4 ztU5ud3Z{7dQg894i^|6hLcgbfH8_#~zz9v7*B|;1%5&50e{Ny@;`z9M{DQEV3qnEn zkh^x=*(9e8K()H4i#Awuk65|sVIcR>^Vf}<@uN@@i6$EXJ)3~ZDZ+{+9Q2CG!Q4HixKUdc z0H4`hlC^Gm2o5cd!7;`<+FH|NV>Ysy&T1xWR898!^$UUg@2`oIIesNa2VHExxO;=se1d2 zT=B(+`*UPutfRUQ8QWC(LvPmN+QT<*-X2%|x+>@t%HJ9Woj4J`_;6!u>sZe>n>E@I z++|(gvR2oz1f&j2C-WB+lev_RP@txO&fovz{>@g$FbO^M0oCODW4`jjv14Ar#I|!W zgaZj_;I}*Yo&&n{h3Y+t`tU5eFg)ZAK0;;@2TO$q{~H}4Y^O`kfP5cK-i--^DMz}# z6#r7$8}RAY5XjXAerq_pDakt$t@UNcoyf9C9#=q`Rf!v~WtkSp49cAAcyD4@v>6{E#@quqX}J1(ede47V5AE`O-a%@Cg<3lyVFQE@tlN(qn*AtynbG~bsZ)PRA)X3QzaEdDAKnA+ z)brw!majK&#>hkZX*0j?=H>N$x!;$jj9K$Bc+r6&f^15?2@}8?ThSpc?n|{@yMVmc z;Gd=#^2916)q?x2h?)r4^|$}1%OBT9q6EGF?O=LV4s5DW?uaDj4gi$@;FB8M3evet zbUb~1SvgI?6aEK0>}a|&;yQFZ2`8SH5Fc+-vUm|LQXZE{=ij2pUi^pC6mR`|3)<9A z7>-vcON}IHgTHc;j*@>OPh#MhL}<@m$V1cyUtNcA5J6oLLUQ3*F{CcYJ8%7_I#z9} zbG2BKHcmFC$PT^hM0{|zNwS@t9qt8}ohaRd%wbmD)91I?^ZaM?e#o5oVHy&OPSsys z$ys3i!3;+M4~DbxJzcq;K1j~6C&MG6`VAX zu}S)D>wr|>`x-mu2>nFHw;U09UinSHr~+JB4X=;*E^+MmLK&oEu;R}6*{GnOBa1w^N3^$TWMLWz?=)ltIa zXZC(_{z4)zkbTRUOzw9FqFgEWvJG6#X`$ED;#Da#Q`&9PDat|Hxvd#}+t_K5N9_Be z(vRAuFabzR9c3m?x|eNh1#;&ENK+;S!4tsl-K2-7ir6bsRUF@!^igVT2R_0KHukb< zZg_d+W(A+ZkAExQBM2!H5DJ}Lq3RmK%`8kwFzr^+_&@&Sg=28vg7i;0P+x6`;Uljv zd|_b|l-CSa_(nt$F4a#Lo?7X*e73|zkDqBF_9b2VVqf2i4a-Y!e@dNY<~M9PT(g{`cQ9^VZ5e;jsTxBttH|szE*KN9v7BJ0WwaY(&l5BI?2aOc%0 z46{Z2IgXB27FNlaYLP88gkH1e5@<72Xs76a&#e> z;St;J>7hTyxTf>`zwsW0I}53qbEyiFY~(&6bFRHdEGHj1^E+wD8pF)XA04#sCNZ&ZidCRBNkl}D(UF0 zU^I6#R}ZZ5F-^ozS`A&Lb%-ey@t>OzTKi+@=b=fJ1DZyX-F+a6Cz$-DkO^XY(x0EC z$3+>YURC3?4|as1G+@<$it_Lz{1eF1({rEp(Xe>TyiU@FqXHBEH;^An<5{{LJ$F@zwBK6+B^x%r~@HK!*4$wX1w7nS^;fc9SyeFBNI97Qe#j zp%lV{SdQhi=+i>WSiStk587bP5qy5_= z_Ppk=4bFalFvbMQ($fBiA@>39w-J!boR?_Do9dp`D45B+Vis!W3gaZlHS@``WM2MA zDJJTerz-}>e~xdJYQ7YRP8%6cqL(8BT{Hf^Q29HMDqO+B-V>sX9qhLe=_CiQHj(LN zOl0?+a}LZpRevx&R{|b2m)auln6iJj*R|CO&P46#o|&F(R4xlGFA+u0a;ww+k1t;ieoH{k*uX0ipI>W)J=xnH?K6_N2CokRyuQ9| zcq1S4YfmPMGsj?` z4D2qj`r1#(V|yu(fZWq9YzC^M!n` zjQ+YBgZ$wlr-V>e?p+=)TP+9IWSZdkzFe8FU^+Bli>B9zyuSqeJ1iZZG#Ju6iS6P;cX?Mc@o|RN{4*89$>|6g2rVfG<2C%;h$OZLBoE( z(0>u~Z4KOfS4m=Oga1GR&i*IJ^{jZ&ZbX2ATi>5R*AgY+U|RdfLRLh{2E z)5V_1QD5g%5G@k@^|!Pxg?wWds`fEly0E1jN#!BMbaqa1ni70OkU+WADy4?kds@e8 z1W|9FM4oAukU;(Y$ZxPM2(Mag2Xg!1!iC@yTwfR{ilX|iJ616L>rC_Ud;6G+)1G4e zdqV=zj;HduORja)Osn~hFp4M$?E)w68EmT-vJAv&%|GSJ$*kIkKfihIf%n@8Fc;y$ zY{>Ju64wPsq^brlpQ>%D97Yb0J%VGd5}qtWG&N+B>N?E4_&WQMu>_zQsv(Osg3umM z_5n!uy%Var^{4>3?~Ms3TGPu(N9T$c>!Q8-x<99J*p6Gno>i-wT3NM4=eH_VBg!}U zH?iOb;&z5@up@nSJ<0S|Jv~&1Uc)`n<)r)iqCCmpa!%#}RF~pdI z!2-=1UG)#kDB-4R?WYjBZ|6Vx;eaHMu^v{EVx(C9yHx3Oooi;(6tn%X=4R*guV?Z> z6IntGIS`aH!{CPb6tQ(I6V`S$2OTmmkKLt9>&@^ZaS$*04dBSAf9UF*PK%W~>hgLn zYJbvYR}IzgJO$pOxX)&EWUA}K=U~j@9)?@h-wJr|FlMBl1Wo_D9r1S(s~-bH-QCz# zuBxn0`uVwU1=0ZLz^9a*1r0F^xeiIz;;X8LZ@;y2PBje~A(>v1W6?(n#Aq5|X?d+F zgQyd=p0ZZrXW9z<>sY@zU)x)g@u%9^Pf5#m3W(L99ni&B7Y_-aO)#U&2T+0Rg&uEc z6+2ifQQcSO@f*YHi&Iq%KJ)T<&<4SY{Mq1IPeQGw ztF$W1j{$DtrTA5@wGw%7)gCYSM}>scLY}k~Rr$YHN%k2kZ2I=)vU$&1HEOnLd09>> zzQhGc7akYge$jF#e*K4nM_;gAw_mBtTrriZpfsybb@gTnVb0erzmYh}qvVj`&^meV z`%T*xIV_{})r!sJ>3SkP1%UPG*K zC7e4~J}7cKE*!{(GvtU*fgoe~A10#DDjO!nI}#{Ec*^$A0CaIqGvP!6cLlSI9KwSv znQgw5hi#mjtM2b*57`oT-zVHzqNKI2rhRpkGpjzTxo>;^Y+5lL$30`Q7{oClsVBZ5J?Wpr#0pm3Njutp}1Jh z;6HB(HODC-VuFD!A&*EQ&oZFc$?Ppr98PStn3t_dn|eWeac>hk_t?F3^*XUsDW81W zuFN&{akQOJ3ndUQP|2Dty!472po3}}XCAC3wXbh^r6|o@vuf?c$kk=}y35&xAHlxB z&Fr|{UpN}S>X5e-=SIa0wPyQ8ZGN^#;(ju&)o(ug2 z2(%QYokRY_OX)0$C*#rClT=?DO3iZk4>R^W;KTEy{MQd@yirj+wJj!B32*FIahi5w z!iuD5!m0zu)TkJFw*&Fwb};lYxJBbN-PRN7^}fazmS|2h$)ax=hURxRkmn+5cGZU3 zcl-l*AU^K>hNl6o7FGR-g!MNDuTx=J=l&Cq77Dea=RuMV?vWyjk#5$B^=$P?ERd|s z`V|Ss682;M>LDRtXg|}x-JCq_v{a)z)y9lmRtYnA{O%JD)Mg4M)cxFr2Vx@UadvT zxOwtlHG}tgNlvwk4wHP723Z3FG;A5S0v!4s8Pm%JJm(iJY^zOCswn(P3(7YZnpurc z9^N|Nb%9dHdtQ5@A8ir;0z4nsas~Nl$*N23CSKx?jM?$pND{7|R|`GIZ(cpD!e$61imBHvx5{okA1yTUdLthiw?p^cXzi?3%g*7s z+*eGK+s!7{g)X1X@BaKAmv1Sittzh^>iM(?U*}~{wj<|PqbJFD%R{@1*5{bB;Ge>}H9 zvt~X~vQbYGqVHwuU};54Y+_0KJ<8F5RwAG z3j#wspi4%S5>s#jC_>_jVIkoAiYy_RR-Hrp_GlhNFIkaMv-PVy%(!E^MaS5XZG1dZ zMsd6l8L^$PF&25$yGQh0a01zIA2|(3Cdd*s-B>m8d-4y$O)Yi~?i@-#ew+~F#!A7$ zf=k}N|9acr67n4^2E7g|CRtJ|fdB`q72|UO?|B0TsPjx|Y0Y4$uSvNeP0-iZ1zqBi zg2?IV&yX!-iGR`goR$8|c1(9~KrX$kWOfR-Mz7$$5+g}|M^GGN|TS4j;t1y z4&Th%lQgvG`5OnM{*~=~wfJv}o*&(iqxT`K_rAE?(3p{vbNlGVdO|FfxO*Y~YPuAB61no56@OV93Yiq(^#|#@ zBpmW3dW)=5&=mS)fv8#Loiu?2MdZFiq3vL&;zjg%Rmx+26je ze@-^0Rh|C8KkH8YZ8Jm8dopJ3@0!oCj?I`IYVVppACSe{NDk1S)}PjP{Z$Vvs-kQJ z;ax7rqNOe;F~pm@VUFds9zumX`<#mlXDOnY^|2%TUByChOC=%)^LNp|X5zSN_PPMv^@Kl*e_n)R^-q1syz8V3#_&M_I<9+dH4rw0 zQyI)-P6>bS=k<{#`@kx3AG1H*Cm12<&apA}k?{_QZVFd=prJSu@1}{5zxe5|fy1A_ zs1!iLifQF9inGPNQ6AwNDiA0kvkT;Q!)fLF!)U}?#ON1`CCJdP0JB;*e=$_0FH0BF z;$T{f(D*`a>W+v=%B2vQMpZ2_gyTFmich%@q$yPcy|f7$EsndQOpeaR^U*2b~v^2yuF81VjBedXUmVudM2S?{e@mo4X7d<*EHc`-7Kvyd?ySWqe7#v?8!+zeC6N{zO1@aibt z(UTcx zO!?kUyp88GGyW()JIx$AgrlSMfFa|bT}m*Nir|olv#$^wVeH8rnjTde)A}b|S8?x< z?0du0rsIDPJ!@%(=BL3sOnJ|apBiaxWu>PRO6yWpw6AsKg=XZiV;PSVM1)D!k697H za-H@oB)u*aOpYZ1#-ZSCCFRa6LqpMZ56klBSp{>X)2}hv!Nu2PGmP7ixhWVxJ}L;d z6piM4Lx&Kzz;QwB9pE0uiYhjY>e32ccT@U{bjunU9(sxS-2&+~LfP00wV_+i>3OUu z(j7W<&QWv*>ZnQEd1Zc_W5d`l)$ZgqcPl&LtSL-2IzkiYlRR977vrR#4&v3&v$Lk| zwPVN)Bbp3ZeQ=_ZRR=Yv8zJMbCOZ+kd>mVam%8l_be<8{_*QC?5xmBI~l#L8hc6KbvI7Mxwj#W9lS9wF*b znT|xRCNb+V<<0MUf7o`j5%)5^=8ze`VZNOYuX0}VfPvGL#0&oc1Z-CDAMqCpQkpe= zV@B{hX z$&tS1VWvrnFQO;J(uAwjrbfqX#Mj6b6JW=0;)x1D3({~3&gxZ0KppEQrrn$~q1NuS z$u48ws##82KAiZyn6Kl^3)A{y`uJz{B*%ZTHATR zOw~yB^&<0+)9v0j!ochv1a;kBpxVrc73`5>}86nQwZS{m`JP5`wQ zQgT;4zN2q9W)c;S+0}MDPg>6gY zD>a>e0X?%yl3>qJu#aF^^aUmv@jUh(j{m$ zVS0+>1*_Mj1AH9QL!y3_042`|4;R1}Z8qxXUq_bG%j1!3V!}ob5dFTVq@>)$1ScGx zc5#-;obTON5gn5kCwF&$uB6x1Le=BKgLjl9hb9Fh(TulVzs-eYM(!Kx+1vFaSvn%c zFhZ-Q_AFh{aesH$PlA4osD54mnrP}KPS_q9L%4WfKG2FsQ|xqAQ1e;#AHyaVvdtAa z+0McUkZv138F5oFLN=!nI!Z_63?4JLLvpSm zXka9~a199~Oa=S$@Q;7m-xyDOqV+f@R_vHn6>C9@7G>I(?N%}VdmKe{_;lplBhk36 zbRW)0F24oOra8W;-|>qTxc!p0w_Xd7@ZHUi;xB@hpl`JD*N$MUvX5xa4nqr{?d&ou{c?mp<-s8 z%4>_!z8XP`yz9|imZvz6VPYN~83B1r3l~A$Y`}MpWQF_jKQ+Gm>dMJ&!#krYiQp{IPJUw!j9(w@aDs+_& z<4dQp6Y#vc^wZtG6Nmz|ZLZz5AfgcOW|k z+UCepF5sQ*X*#(T`MP<1=nopN6!iK0xs_?eUUzCRG{v_LQ<5KQ2>o>tp=Japg6lG( zlrQ5hY$Ohb0IkQTDWTf((v#V`?)lFel>?rTF*C2|+B^fEEL|^_;Z!CD-sXr2E4)!M z8J*EVlZTK4rY*?TNqA9S-3erT6*4|rhhY)GL!Qu9%WJX9pyVs2kZY8i3%54l$iaG; zvg<_*LCp0Mxv{{4HGA3v^`nI`d@EqJG>!A>>| zpHg$C?9$BY@Vb3GJa;2{uoU^#JzR$oZnMKyah=p8s&jgLh5_%fUeOc``IMK3Ru6T= zY>$>t{4!=3c#5Yf?MAfFFQgMoP{|8?{Q%1g;6-}qP9hRvg5JVPJK6p;B<$>wvizfu z@xi93SB3a=3fJLN122?2OG^n|oIk>pgs6En5eDbMr+X^H*`@5SIT&Y=DYx_ovtMf+ zs=gf!n&;-m6O$6#h!F3C@qb&uZO0+|^+LPF!Uj3ASXxBLTy2O*i~(g}(>OjzyW<_6fU=QctmV^fop6tS3`D~I~_)k97q zyV-0zPW|g{j?YJdiZJwo6t+n=+BksGQDG9AdPP*fii~a!GC^~Ug@w4MVmKFa@Ftph zf;9%9`!^+{YU!zLZCoL7)J*0agYNp~R2ojmE2!Pu)1k!8Bv51xQoswcM+U6Yl!|)t z1qqj!+m|vmvoyp+vI?X5xLJx9L5mmX{26H7gDl*ikf6!6b#kyH)hslUJQkkG>-l~= zse8^GeB1?g?1V}hfDW7g$K6W{nN?#$%!a<H}$Om(bIL#XHe3SgAqVsWq17?xXdIhUpJvbR-UYTY|6i4h6hy$dP z$NZZGNT%zH)uK5JuP%n|l=;eSd^|X48a=-*AJ^ ze@7`z3yD*6b2T) z+sorYhn1{a!Ov)1sq&`qZ2pw8k0QG;i^~Dpzfo%rnMeqJaB&~TtT1TpI39SMkn|zF zvQ`eos}vEDM4WhIXDjsA#0|cwf4i@j*K`}8;&+9uSeinh`nynks7G%WzRgzrRqEEw z;zd~Df$n(Bkfn};JY}OP$yjENasi_!e3fG~I%j#9S(;fI2j#!Waw?rW*D7IKjs4AA z78Z`0zY{ZVu#K)Id%?>l6+5_)x7u@N4C%Es7nP~szXasL>hZ= zU5}!DP!zW@ZNH2M9e`f%<0g~0{HH1e6K9?cmm;>CrRn<81@%~+68s~UkBxuEd-PN! ziW0vV*i;7G)KR&K?_-oZU$V?312ThZ|48|QlXe( z@YAjF@{En;#|IzRZ`d&W{VC0N!Dwsgp5OSsAYdRI-8aiI*K@ez*71K73fV8*c@2Nr z!_oIJC)L;hUP*)+N3)`}!lZi`@uY^5Xr0+9dd(1I1C^mFg+?2dk4-!kml^i6FO_z} z%^pB=>l9z8r_OYP>|{l8TxcUMgYnvGH)lRQK1_j8=(mOu&Mq{_!p49{A;kW9j4%_V zegWuP+l?F1=xs6!4+KhQ|4WkGRF8Gyb-k+G(@)(*#(Mb?R~=j&Ty_UR6y{X=#YiQT_hVjhu zT;OsKO%IZ6%3eX5v3KzmvN6A1OKd>-+TQT0VqT@}XO9hf#R~<+DJAXfm=P+_+N)|* zFEIud2sq6G6=bST@VzK!hluM~DyL|PPltA`S zH2fb@?}nG(0(XP+ZR#!MbSy$^<2)thk7;c82Cn zu6}qR;xf(5W18_G7xX?xxV6*J+i1|K_KkqU47=AO_KrrG z@(u6^1+IPaTwpf0f%q@uyegylbA(1(Na`r_v1?%$tET4Yr4^09C417jvn}EszFNA5 z<_ol+iv+0YJp5)KVBNn@go)pI$`I`j8;=#^EHmkx0P&$R8zb|=M;V7#qu#J~4O_ix zt4@`njOAFFrmI2_P&5t zXo(k6>|fWV-M#yOFtj*Xf!=My>U_g5ed(VECGGtFct4ZhNurd(m#94V2~F`xW5XM& zZ$f5i&Tvz~7;@)69lfwQC}`%}=hitX3MWpC?S6>n14eM9>b^JzRyh6g+a&hIi@jai z3Hu;L^cH(zPA8L9cgNhJvDH$o#*Mu(EIZ>&ue>@>v}UWmCfZ^q4kCq}rj&@F@8;w| zbtMUJdQt`yL^DTN$LrskG?O$vcJcm5| zjB;ri(Rdvo2qch#}j%Mb`)&_%&t=pqZ}3C_^$rP9sZnNC=)42`TS@!fB|Ff%F{$ zv)*tv{ReIP+dEKQimdxmi=%aW$rJjpmF?8Ma+YX+Ptljsr)idR=91e(?9Ar^z8Pv> z8~+a0>!vN{tdb%$UX5jL?ceXUPir}?qT8#Pj2P>yw1Lvbx-J>m;V!twWB%@z@OtX^exg2j=5yqH5LGrrU&Be``j2%L zGmfA8shIr=$JW}TWUu}sJd|kLAdoEx4nn?n6OalW@p;*}9nWhBKdi7{=?BPsf8+Xa z56K|8;3mfHb*MV8p0L_)hKk-Z*_g}T+9@(UBtCVh|G;y!$x*VDaf^{}{7JNn7)vQz zq2|4ojME1LuA6T+rlUFEMH@6NC45q&0pHue!&q4geqBs?oH@dp3JO{BZT6M|T#-R; zb5R!!sr45f3LK$b*h$d__SJ`C=RxiJ7bQzi>N(XD$x+Rb*;|T>luK9vc zV2xg&N9ol>a$(P&h#lS%-)O>lwF+Is%j33|k}$$Is_m!xWa>U^HwljthoclXsWQ&( z^S`eTJ>QG5-Fd5wydE{%Ae66W)ZEp_7GV(&wD{JuH)f}AKY5C}#mANNhnyuB#I{nZ zo`6v<{d6rfWFlPpYli??^S878*^i-%#o?^9?&#j>vF^KbC|3(Eci&%@H+_Vt_rSd~ zRdv0rLTJ}ZMH{HnnHc)Z|x=|xQ&U}V7mU0Iu*G8OT&)Dmhy-~B5p647`tQ+e^OG%`Q-MQpF zPl>sljF>n>RsGQ(RYSFA`oLcdt@+E`m!l@pCMH}`WLUeuSdLdhsqPSY&^4WG)HSwI z)Nq6o*LivCA9j`_tm7%cG+vcp^>`|p;?xUxGztHmHjvFBpx>i0wgzgQCj5hgiDjl<X(y@|Jv;JsK z4o%WE^*J0Zu@+a3GQCnY;y+{zp9T-E2oYvbk?T0W2fS4-9A6w8RchoFqBMAXwMhFh zUr0^VQ+JySf~J}s%OT%t9e$u|S)&G>Z7n+u1nx zy%hAoBRU<`_#s+~GkTV7fCjXr9~fdy1GLMaud5D2hnO4Bg^OYcg z)KM3m@r;(*p0`Ke%Y^G1k$J#HceRpP(ZF~mXQhjEKu9k6zPS}sV(~wY&c(0A{{Q3G zwQHxg)jGFz+R{;_b2>=diVVplB*{7_ry=ZixOZ(UIfO|_!XkvwO&G$pL{T!yIb4n< z%1G(B>vw&B|3Hr(^|?Nu_u=(=zDQx;b{KV(k|vd!1*OnE0gHo8>V!buKl_d9 z3QaYqe{|y1+z_y^2R`DWA0x(gAkBX8K6l)WKKabr}&QF6vW*V|L_WgqV;^4zSS5^c4(sf3-^POx~8; zwHeX?P$#!$uC`b;-@A4h!6ZjAGar0?4xZu+W!y5=90M2A|1irZ3*tyGGO&fJaGIK6 z9v(>Z6gaH}n5sYo%oEew>d23eM3@VjMU2gWnNPO-9J7`rSHS8jp)r(ZfqSH^TwebN?eEaG9{0{G5 zqqTi!&OEH!IA9w3oKUy9|5fv~JBd!BIDlMvR{j)BoSZMj}463ID0gMBX5gP zH%Roux2wuRr=6l+yC_i|NwzwU%=1*YZ7nd~oHnuPvbsNme@sqWugl$lN^NWO(AC@W zFzdWe)EWtVVJg5mVw}4eSo9aUz;w=Vvi11ouh`vy7tu9DE4jLOg!lPIpvWW*c;9Gz zz0G0ycwHw$b(B5$*+t?-%9z;oaGh@Idvvi-O<%x5j4Tpn$#0k-#~l*4fS(FU(ho+G zX%-UK5uT3ZjpPuHD1bV)Lnx!6iD(;`3Lg0gEI%Cx(yescb?UWa@@p>$v;WH`%sQ?x zCrE~E6F2Dfcu|HdQSY;b8x@K-ON!LHI%F8|csY=A7a=C^8X5b^5&wbC$vNtUK3U|Z zia2$zyF~62=w}KS{*IocX;GP5*7|(l4PKq1-;rt^6VntBCi>wg_wgN}gk3W)HOB-y zOhu=`ypL=N+q|lmbUH#p$Un~)6R*??tc+?7J!%3LyW=ld-Bg$qN_ym4(bP1Wk4vf@ zbO~s1oqD`Tho74pLe{GNmtlh zeE2#mN#ul3C%nWxsb)1!D!Dyb;x)vpE>&EKVH=`f&(huZZ!#4w>g3g)2E0}n?(XQ3 zsqULYm0i)68)J*Aruu72rX&QZe$%sPP-QIcZ9i-JtcXDkG|25zv5MyP++OnDZ3=cq zh%;SdnMpjqA^(cP$<-Iqd@f8NGIZb4$uA`lIZ|@6Yd}QQsn&m-b%tT=~$$?{Xqlt7T zeU7M50Vbs?UjH#YhfAO|IG*;2M_nU@8KPSm9iuCG&C%U+L&*J{l*mZqS-bX~ZUr{D zfY(|suf5c_MiL504{9xGk6;W3Q=?i=Fo?M6xO}uuDx852TXc9wCOm?`GEA z({pMS%sj?qC?|%M+!lZp#GQmjPGR!%bF$`(c95QRBPkQB@<=|6q2?BZkladLJf5E0 z7YO@I8fCU*V93ETuyONC_z5EkrPe)Hk`hk9C(Tk%4MZMHl0ZGa(!e&J(4ASz#S}LWs?8aB z7on>Agtt1Q9ii3|3!{YDqlJI$;b|Yh7iQgEN$62he&jJzL_`e8YyR#w6S+HwI#dCc*L ze{KRYBXueBB;f>#O;>EVwP?vvqq+*yc6$S}I}1!NC0QmGsa8xtGU)b;LAPkMXMRx) zKLhAjb3Zw$aUZvF6ZQ7+4tCumE6W^pPi-u^X#bMBjkpTw?~i*B8aYMOmPL9ogj+^P zY-i~qvzqDng>dPkNuq?C3cMtTbCu z-wbQxc5_ni!V$_DAM~_a{L=cW{B_!KWXq&|ue!vD>Oy1s$)>D!JguWq0I28fDPxR;0qALkyzj$d% zv#JAwl;BNWk6$V+EX&Xj!qZCiQJtnGw&l=+|0M@N;v=r9}q zO9fUT8N_DA8e>f^U|fcrz9PTJsQNm{JdbBdISw(s{&h0RAQqbHe`m(jTox4gd6aUU zhZ&~80Y%XJitR?_>)93i%{=}=mf?$-)sKOt%SWf#P9Pk<*94sT#d30s_F_u<3Cp(N zML|omXaxUZ;`}I)54dVQU~P;{cfuE1>L&4&Hj{N+7pZoZtwoM>p`kF}KBv;?vpIS_2ou~qAlUg8Q~o?dF}XKN+O-|p z8EmaPwMpEK8HeBa%Bh@lAMn&2;`YRgE132U>I7Xghya1TB=T7%cW22PL-ks;IPnR|o_rkw}vzA{uJ z!T2byhmAmc)ZMm0mzcSMW0ccTqSg96MrDIY<2iXtCb^LmL<&^Q3@1yp;DnWk5j9>A z5FH=vF)X+_yA*LZKx6czxlqS+@az`DCHSH>8L$4g?Q+nxng`iaI!2(P+2S9{-c+(r zTqYr;MGlQep<%&>-|(AGwWEo%^CWk>P(8e`?0;b)8}-Ok*I2|g3W>Ck;H=#@L`hUB z8013k{x%9-n<2Psi$`Z%A!yQ#X|u9Kdn_yq)D}AgfiGiKzb!j*vw%E z7eWkAJ!aN1k+2d@BOLsRUZU3Su;(_)Fl`zqzSL=M{qoKhcOH);`FN16`MjfmqM0KgYT~|8B!_|n zE4_i?-@YzumDenlSAFD{nL11K!8_!W8+@)%u=gJ9@sh1QKU<VlcEjiY(;%ArDhtzGS+P;6MIR!wXO}3-BsDkfg#hq?Q3oE3m@Sdg>f}1yTNl zpH@_JHK^8=>9x!01J3JfhUZ0posoL^8KcICNY?BX&y~-eXKE8YQxqfoMvrS&wK-f0 zgU#ChWWlp`BgK}X(*pS)Z7rklTK;2b%#<^uoH=y=sOEKvzf8K%Al)Qk`X8RO-$Y|B z|7(wYFfrP!V)8fm6J8`hZv@EO3M7pl@en$iLSB%mLly1U&^*Ss0H>RD`@8C=H(A`( z(1m@NGDJWKBug@4l9Fcdh58@g0)HfTAW!n<>z;M0uVf;BNXesXCGQ29{SZM zss#x?B!efTnEJm6vE2c7?X7M9kJC>GhgMxA?Kf9v7%$%j_2H?-;1#9YIspZ$Q7k&F zj+-R1vZ&OMTepDBfON38f@1s%j8>A`>qvXL0;1_D;t_hD55rly-%EO7n^D_x@csSY zoB%)L8xiav52Ec7!unBxmscq=XNc40L6~rzVC9O=eTEk+{hwJ#tXtxDARd5cIEj{G zd%#P_QJ#f#x~UW~xd8*3FPxVzv&AHv%ZS0E>$8jKGyGyHFw+Mup-SFci+EC!ql6P& z<&O|9e>8*nXYT?rfmk5W?BM^%}3qZM(x^iycK^B#; z`D6cKh>t^80)fK7!`xhnrBiTiGx0>Lcc}TF63#T>#?=I9g z01pnT`yTPv8C88GxX#f%h(ZgL>In@kIKfw+?^Ly$GM}oW0YS%^D>nm&AF1c=6`Ux= zN|Eg6YgYevONPZH%`p7BJgpkv3XG#@2v+Uxkg*Hl;8FR27x>9BbP1_wt=JaY-;P}0 zBI+g_b?#?Ikib8Y8RH#7y#k}@moRkiGO}bDWPcdbE-W+(Sci+{!5Qn+g==m;84MnJ9sTT|aCr0uiGGjbyasyMJ zVtTwFxS!?Qb#w2&uSqsZ+C_}`{ORv1^&Vuz2!3FOr_`<5otc&eUBKb!qxvC=-xT@S z1a!-^_p(6XMHsN@DU#+;3zCv&j1`t_M~f**XXSP+{9jAd7L)kV$ttOG|6JX#4Kd_I zHfg*qvNlb%-ZSSueNMH1)awCsz73v|UC)sW5q*;JE@!pq5bXk2JJq&jp^7}Asv0Wr z|0I)6ZlJE{jNTbb7QQlayiXiI%p4zDafS4-gRe zyadbkH5r7z4Q#DRHq5x{7Z9~3RO8EJM^g%DntOjaADLvK25B>?oLLVnq7#?hQ=aqD z&=J?)vYZ;lV*Fw~o>ov`zl|Kc0zK=rl$v}{ZquiD{Q}7{q0?*pO?IrlnUp&AD>OR{%K}xf@SDV>Co?a6XRNW=%%@BLFe~T zXcrS?YDfDI9zM1pQK(|qy_pH``QG2>K4X~t6KAQUIG9A((Gy)2F-uu2?R2w*t%OZh z=t|qF2eiohHlf9Rlpxa~W}cO2R}K|5OGg6N3ugT*VjG7?n1==cb<926+2(Q#Id@Rd z$XDP<$)N$x=5Y^sLOMI5gTX$LPIOOIFOexPdP4Ke zOUr;q*~AIwz-Qq|099IKALVUt?f8qvF?6Ts9&Y?E zwnBPWk<^Xc^n88JTz0%W9G;2^!YIiE1Eqka@AlE>QAIRqJ;NennUgflxGM&gx_(+k z7Gb;C9Z{)Z@WexgsD~>>4nRA8@-0By$CLx_Wpdg}fwPfXV5S?i^6P{icLG!WbMC(3 zWvxd&PsmiMxC1$zXHe#(*6Wk`I_hgNE&`wp}68~mPh(RZpQho1MGD)qyYl#_|>3&r!A2(~p8lXozF>e#kW;Y4Y> zafw)d%|`D}1NW?fX2H;8?cg!1IyxnYPK^7Bo|ugV%4eS;4LL@LAqEX~+K9SMkiI4d zc@WBulD_2V-`2ONO&AuMO+@drP6u59I}bHMZr1)QgTQdd_7GjegD6ZpaLjzqa82Bs9J%<(}wMuXv{3*vlEL zB8zN(b2Kw4vG-N`e#7Sg#N9#eqZ0!sr;FzkQuh9(YZiieDc-ms@Hkr|`1teSd*nP_ zx5jkycK@76n`9UN`j|aVN^_hfZy~)tSvbVgEw^&nBq+P8y8(Qhl~_jJV1|4#$htxa zkjsiurPY0gDMIbOAK+KN%(M%B$di0CnK`RS@8l?)K8O#}!!rk&4%6g2?%IlqRGyUN zc*QxU(1__%bBH@1ZeAudS6Fs1Y90$htq3~B+E41ABKk%TLymtp>T`kT*Fmg%(jxq+AII5v) zj8w<(rgS|_j+rkq0WO@PtjM73J_%3yp`H+}UP-gCsLdfA5MWOG5KKGIPf}o5qAMj= zbrlY>+H1+X`rsRV7&5)YEXa+JeBR`Oqape}%)c*xkyJl$*-v5Dq#iLr9`f(fiqr!> zeg$BkuQQ}MMGDT~f8UGthga%}C++TIHCK&tg6sp<5N5=vDz3`VsS8ljWVLRqONwYJ zfdP^wXUGq&`4ssneIl}RJpNTKb4EuAU94v^^+Mx`)>q#b$+%(p>`H6tTdOKFn)e{T zCl%dpi@f>QplofT9BC%LZh|^iHWJDTNxjcU{@O_;c9MO5P|d(+F*s`>u!3^*Je0hV zawD+V>a&;5B{=7syJXvT_9b8QgJ=c*uW*C&tR*58ok};-;`G7>JyH)JXlj)(3?21p5(HaB2 z_pW@|v>05m$2~HUoY+eY-cb>FUp=&1;un2tlL3RCg`@p+^!YQMq;Z>BLw}@^q1H4ZKKn`kfU{Df6YS6hDSo0a8LaoM<|0pPgeQM8z%`| z=^B{qmZ740_z2cnX}H9pKRhUCBb$4Yljq_X!OXLt1*!=x^%%{zPZrIxpvzqbHzJs z1mh%K7)Kk!L?`&`%u2^NmnxyQg0%dEP*wdNQo|4>?yOkJcEWoJTbeOT^xmxF1jeM< zktL}O4Ggxv;15k)_Q9l(fxr5Tjg04R#n2cJXXolxfNWp+#l&Q?Br5;U6el(@VkdnL z{03~v z-P_=pLR{>NN5*3XCBM;ncAbJ=#{k=thyVWM1PKh3i=D)SY6eO+%9&-EgF0%a5bcNc zOIc`?nTQ25hX@N4YIED5+Epk;+Ri096}4tF5Dds)Z6($ES_kQvDdXvpPry7}IWEZs z-Bw9XHnvdh#$}Ox-9U$4N{abCLW(c(PpCdI&JA~a`BhbCl6$zMXZA(td6y#H0cH*U zn(WqEkkwEA>8cu!=OVj3_0Zorx*0rTByHP1q%vX@>oUq-Bv7(pfe&@$$U) z|C`pth{ChUKLfIWnPb@sPATE)F{1!?ntdu1MzCCk9DO6plB^*vBi|$8#(riuZd>?y z9o+xZTNTAl?ndID%SzqQAZyKp2gDTe0H;pkc*(7sCk^l9IahuLr-pkt8=#&DaF&kX ztN=UyC`*hnZKj2kR%X-|4Spwb-uC_V)fBW+<3jQH>&5kW@7*-YtKGnp1ipLomt)uh zInJsd{KT{fVY=ts>wEuRH;z7$VBoaB8>B4V50A#vj?I^9?pe&h+ul7>x zpz2uYbUI$#YN>Xg$RVGc9?ev-TsA1`O)71Z&E#v?wPOze5?T-@BB_eaJ0_EuljYsN zFjY*r#o(Vf(IhBwLx8+7o6YZct<1-mxl85eDKARUSejP|_b}ZM{Ra0w_*P=%+*yj( zH&r@tf3G?UN0NG@QQ+%(^6AZF;TFQBue=1h%faa5#u*&xTbHK5BK!mOm<`{h1i-ZM z_{1!864ZVMD^&~dENp<8km#qJYf)Jw#!<#&7<2U#nMeVW{Vg)=Y+uTYig2NPtlpMv z`-_3;aro54S#W# zB-=>maH^iQGyol^h|6{IVy-04IQcAWy-qQ|4xY9cQ##wU&B|2xFeo;^RW9ICQK0^!&fg_E1aB?RMyM&Ds29afnWHjc@NZ6WFt_=bTyE@;l@8!WPlWvFvVQ>l_&MP^ zR42BK_)dSjfw0D*Y85d_1@z{RLFfI6>sau9PvhL~D*lDEkAq5E#>buMLfihky0SBHalu*)jc?5WVk)XZ^V>#WeQ`v*; z1>maS47l2ge9x$+1v>33;*lbP89p}!m#OO9eS6KyBIRK=9c`n`+&*XpWnPwQTrqC- z^CoP$OMLLS2n|~#4Ee^Ba(toC5P$iyCFr|r%wzAiZ`)RfGlN3U8z+5ZURcUcHBNch z)IeM~+Cpt*s+ZHNFK~aExCojza(g@_s~j#3?*lvB3adaS0i!ICqgvY-;rr4L9LOa} z@J-2%5HTK@U|KdO!A0@L6D_t(Q2HyHr|CLOyiHjw?u6w{^Uuh*J*fXUox)#DHz*G} z1V*){1DTnU?yUTbO?sM_WE{E2qwpO}Zm~mHH#UCTVR{Gs`IsZ${)L&dz{N}N6V{4h z+;g^E<0_e|gXmBH%+T91gsX{<`nj$Qbd7N_$q?NwUM*pD;;6P~iU`(2$i zYx1ql)xYh^m-C(O!wj)&uycoxDo}f5S&c)H>0fxueX39PLgWXgp1MjEs?5k1xk9Tj z|ETlg{P`V4`k09g0&zCH<i z?>^Za5mARs7(v=N;W_l(8?q!%%{uN-G6QwTqrYMZ^cIeO)=_Pbq<0D2FK?DU9zM2E zRAM9LPRc3ld5^I|JJaON4sPyYpzQ#^58QkZxxD9 zxs?fGv;=q;MECUIUZ!H&fUp7R1dGW>H59pK&(tY`b@LhY&HtPg~ymdQdoPZV7-o$5CUxW0& zSKOEBH~;1It`V*+GYWX~huNN#kvhzD^rB34l{%6mPBN>1V#;Qc=sLl+o^p?&$z(Ft zOO%e~OOVXTkjn=7B5QT6af)V$W9?m}N~MXeQ*X0bINam#RerkQT$1+HF0y8B!|@IC|q(SpGe7oa&55YC78`5OF!Jh_pSbH)r?4Cl_1a;s!U4HKe>S{ z91z2?LxP@y;iG1+Z{U&1;sVnqc`W*aJ|hc#rm5jVZYBztVey%h3gJ)nvCX> z%LFX$=>es+3eE%fd;yQ#=;x$4R9X^AAK33{k#29Z!Y1*h;pll z-<)MQbM{=y&5cL);rgIbVyK+lT95RHsSmLHmH@No0Y_DRxB@*3y|SbOAypwyjL<~L zSzmPa81l3orabQ(HH`M2tShpYpxa2bHI$O)L{dKdlq?f`afQ4lh5C-+@kH>VCiDii z-4ApHn~Ec2auT3}=SC?nmZMLfuyMA|gqr2)gwEw;9m@6VkYBOXP?Z_px(H9{347^O zre-p2<%H36cs)^@J{h-6xsud=!<6YpDr=LR2K#omf_6~6f&cx83{`+H9W(OR?{*3f z)tEz_&N=>#RL5Az6~|z@#7NSzfNN`0=LOgP0bl*%nBU`G^e1G`(@wT(QUzC*gV}R+ zXE(>__@W&~J@SLX ztmEJy^NduDqhOWD)0sG&rz}$a%tuEchOY(RPpT*0IBF9Rh%qC|q23?xMhEm`kx_Ao zlbzVfcq%!j%DYy-b-qTuE;Lp(YLsgvr-h2rNRQt5MfX|yg(#l4jE+DNl##xJzDVH6 zZJZoqKXqvAr;T(1&ttpNZGqF9S$yYN4Bcxx`+6jZ;qWsnBfp5pzXVxm?ht&oBGD^V zd!1ErIiFNUZ-90EJEd4nT+2G^vJRZM;{Q%4Y&U33Fm~IR-1Nq zzF3aT_|$pqJ@T}2NWL}z?Z1fbpl8@g|HVyg$?5#sBmsn%v{1Wdh`BUW(Ro+>D3>g` zf>3<|*r}Pel?(nH1Lx!mcH~rA3~BchHD^g)!^~$ptK9G_Y~nko@${r^IFvxFi?>U9 zuDIOK+s1-wyRwPj2bm8-)tjOD#N1}5cENE@(zR&%#u`?Jp#=O$-Uh+TtwlG<9euDV zrk&FQwBBW?Cfa%&lhX+?LOIPdmu(8tvjo@CELRtn_`@I0#{pRp=F~r_{Up~H4EC3Y z7oeev>U+SMt@$>g+i|21ull3Bs9o8hZafViuF}PG%xTz%{R6U`82oiT@9K2(CI2XR zJ{Gw606MT=-M?36xKwj_`t)sYZ>+O>Sx<0`y-S?^mx10Ii&aA*{2|j?ai?$2KOuBG zecgk1=p&`5$_&cxI<(HLp=V?|Q4H#&W0 zSFx2YhoUJ4DI8~MW|J)O7Ipf9BJ=?BlPpGPsmRctTZt6dR!Iu2|Em2LOU4_ImuNq= zqee4hBv%XW`(?1e0O|wk(tiGp-&pE)QZmgznM5eZN7{3UNj9|AK0=`F5IlwBTw4wP zl{uYZRTrA&J+ScT;?s~u;ixj!6!s?X$^oab3@~}8;QKPc!D))eGeI}43|%o3(-w+h zue2spO6U=JXEUbEuEAXr_qf`7O~kOqU%bj{%5_d{KF|S)iF@4fUGcR%{)WW@>#b37 z7sWu&0PteIdWwFlQbWY%SWL{B#(6-CH>S1?hyUkFm6_Mw%7$3#SBUKJ!9d zmEBWBUR%L3d%>rjsy5s_*`+-2MsVt(llv{@WlujFc@&;|dC>RRJ6s%2jJUR^5PErN z1P@EB33d17*mNcN*UW@hQ_7*jqWVC1JpwU0nu9RR#H1CgxkOExuYEX=ED59g zZ?`7ItLAq zm7h<@8K*EiIVC&4U4n+2C(OmcYWqSU?~4KaD6%(gjh%Do{6pLVT2KUrQ|dh=uJj8Y z+E&mZ3L*A1D3YnPkfsi*bHq0|W{&(LhAb=t15Pzik9ZXlT|<4i1$37gSTuoLKGSLO zOkLR&{clHf|85yfd9-ue_P4jTFs0r-Jl(pB-a2^%nT-AlK-OD3hn|zWRl;x1@F)4o zH*6UFfPMnLhD;p5kS$l=M{XhP|0(`l>x7C_V)y-*sQj`%~IG!S6fe%Phz_{}JPSi^Q{) zH*FAr%;d-^Fs{RP_v6q03lEY%y1F&}bBj?^Yhr&4@|i#hPSE0U8jEgokByS7#$np; z3k0sudBUMqYAQBDIkF$xN|S6X{Kl*N(g?hJL2g|Ti+-k7_>1v2XBgGvE`?CC=TL!oef58fyD`i?#Bb-(aNmR#{{3}$av1nU4uL8TFT7KlNsm} zbJb0QAZ{y|JuQYTRN9wqVX0S`ZpBP*B99hJ7^9R8QvOM%R1O6uLe&FAt0`zN$yrm9 zrWlxx+|%K?d)))Pmr}A@j?AX?7iK67^2U_kt#oHOdjD*+{tGAPh@Dg$PVf6P?Xi3( z0lqS_78N|NZb446aleo*0QB`4>mQ?7RPO!7Eap4m2lfg+C|D*OD?CtxIqh1knvE<} zf)Ue!Z-fB;&P<73>AV9>DifTekhjtB#*RYONXd=&WK$S;d=+I(;Kcmq1&>5y6%&?= z#z9spEOn|)En}`PgMqSVjVH{6d zS`B}P9d6hNt*OWobn_(shGSs-@VFU^YM6&uYOjqf6zJF?PY5e2QvEuOseq&L=9Z0)QU~ley&-)R(`-yiI420SL~&i0BNFc4i+w*iZ0QUr=kIY z=&7k$=+!AGDb~BLLnfebv8*p*Qt$My=-08)Uzd+xZT5U#u&QrRwjK1pQvpF8ewv>$6AA z-XhgNP|gz*$mIloZHP;X)6GkKkn}AzFe-NwS-8PC`8!;=4Do+VeRpn@Q}<&(Xg&pn zf}PHXkehDUk|npZh#SPnmGK_C;P3V-Ld_li$w@i&MP}xCXg#YWz9Kjh(!|N1t>u1# zAB=K7*KkEvxIcXo&`H5Vc2j34i3 z^JHr;DKB|Kp{@dRuTUF)4{_ZmL-dS=dM*R1PA1r_H0=z8EyA1DVJz-cr-{bPAKt49NpZpnYzaOuCoW^AfRrqz*EUShvHB!4jW~R8NL# zqLHhCQ8U&EO9S|1D;C#UPB)7Y4gnL?>Zza$DGukr7xQ5GVpA znk7?`|7dSD{zyjq4ak3s^(a+ROUa?-9HdFC9&*U&>$MV@L!#|}z+){X<$eQTs|4OL zTl@Cx>>0U;SAOu6{3-IubLX#gLf`hvWTU{5x&b@sYgZMeZi2Zc7zQ^#H1$}@Z~KB_ zy1nB?a|qryxapSY@E)`B986B*>RMApb6SWgJM^42z;~JIV8vgxym1(p4?p*eT5lWu zks9%eZW5(SG^zQ4H3KWw$=f`dsEOUg&Mm?;AY`NVA6BtcJS!eAD#(s|$C&IL*>Gio zWces%gp|xsY@{(%67tE}k`Y|OfAlo^z}}uBd4$R-!ZfJUNa8a>StgBo{RB^*bVipm zb}rG)NmIFY>K4w^DTXn$|FkOph*4X$GAK}fz<-QvlcH|_!>s$wsoen74gTaTCx8j* zN`+;>$+10Vb-ASEX@(KX>>k-6^_#7N0*bScReYa2FEFruUy&+_T)d9WpJ#nnHi)Nx z37=3GON>_XqXwo03a-AEn-ED~KH<&Wkr=lom4hx)JZ&FX+eON|`W?f>yL8r|GvwXM zLV{g04(v86FXdk^X50QmUAuM5aYg;nHS+e?D$)$(?gQ-+PqO3@BVNx;{-|uVC(W}` z9ep9onE^J2Bz}>-{4LX!YI*u3NMB2An5TZ?qJDv#-VqRWn#1n}A8i#xlM({)1HR#~ zpJGxfa<2_THUUoOCc@L1nnxq|KH1o}{6J2u!Num%4w}>zdiwJ{COB;_ug-w4-eGH3 z60HK!w@=WGy*%ey4P@h{DjjC7v=}Y4xumn!%W_f+glEwdPpI-cVf%bZDMq>Sn16_E z4XJ>@4OzJpSj zhi|g+e2}J${07GaYRPl{JX?`@{Hs7@(lFo38~v5jBo1GGB)I)MLx(`uF0#3wKOn0A7{E)>cr0W2vsiSmTyxUIrnq;eKCf7hOOLL&pr<~%7x$Rvt; zZFS+J=uxG2H{grI_>WRcw)`D{^fYhO_!DyCv3%O-+BG$oRs(zfF|+`6Mmv?!3CW6` zyT(TFQXudCd)~jm`N3bG@Q_15lXPTP0qNyxaaIPH{ZFY4QUUl*^70lqU2KcPgg~nX5^Nb6|cspoJfRGHbL0{NzIboPhakt?69!x$nZ=&^AnX2EN@5Rrg=&xSjsTszRBaZFcvIwiT0O!lW zFC-MKVCdGsX0-xVf^mQ!mYoGx&g^oq!Z(2vJgx=6->2(p$W^zumJI0jPHa*Ta?U&i zjw}Ipc?Se)Y3ag4xZDIJJzG$uI!`S^iN?EAMR+{UydL#9dgw$#q=8vN*S>G|{ z=q65VifrOLHx=P6(lR3Vn-Y9Tns)&ABNnFm>p3N!27z^sqTbZ9g&!tN9ozGZQzs;B zgnmq}*(bQEU?JlzE3JbJ4XG%L1f}xFSD;uz}CLXYX8j0y=v~i@%b} z&obd_!jabBhj;BVuDv1OJM`_YJ5J{_4E&nhwT5NwX0kpD{V%SFcN==#J%}$~W9{0# zNMYNJ?AOHVpmMDdv3!p~5kc}?#+~IaSqk~==lX-Y@!i!LPO?z2(^L4yCcYyOUKED9 z7@zS-&E=k9SM=~S8|9V@(R!njY78r6oC#3SCrFpseq0=*-)NK5Lu%e3`NVeGmyHVX zt#tTq;`n}U0l`@_0N6amv@|3Mi_Tr1v#*QSvW44NWD9@_y-b(g|{|+)CWa zXC_7k3`(z^mi!Gk1XTR{|6_6I8+sxO@hq-^XJyf2)8yw#46mHTuj4 ziF3{Qcl&mBM=^Ar)?Pr_^;dbn8D7ef{ukVp#KegPTYar{C1DZ*+W6+$#XB^Aqu(`w zHJ7$v!%zhA7gNf1(zos1@bjG~Fw@jp_p4t0jIRDZ%y-1-hx|x|p3XVMLmxPmiG*p^ zRsFPZ(CEe&2YUiqX4j76dyXZW_?*HqxpKxID+^7F!ezYV)gZ>pV;wYl?MgTqA-eSt zq53}HR#4;m8AZB8oVG?eXsdo_ttlno>`M#Vi1raqP>M-z1OIsgyf#wPc%RzprH9t{rJ4Dk*Xr8%D$5+*UtY_?BGp8*mj`v5p;;!}Mr6Gt06tlY7>m#c z`xwG|T|CiT%i29Wis=a$v==P6IWa7b;OjHb~O7Q(Fu#RSS*o^UqN zJh;XuL?g}4-!(=4+fGBnn7wRk+2->IH~*-%wGsv zE>{1+<)*)Zf2=I8$^`SC$RxL*Bc{L#0kG;3Y<~gBV`T8$q)&mIQ=H^-ep$R4Nh3b~ zmtVH92;C)DH?P!wAfI(Xo*0)+g013I3e%wX^p)!cGg#`+vaSUC+HufHH{o@#^*1Jf z4xuqFwY`~ewf7)%V!lz(PLm5R{5gO_8hpTjVe&Vv>J)b9=Zfp5c`F<=vEU)3g&KCr zSW0bsfpaTt_O1pmK2A^l_#q9K+Y=zH(-(EkLhQyFE?xd#14U{l^h*5rKxK7C29e3* zPrM(bSSEpqE9@tF7#=JZb& zuRXX(KVVu4uBJNIGRBxa3;{VxO?oX$lrt`YnbQ|{()%HVaw~Pr@qLAwESUO*7&>x zR`LGSihBsaJcHlqCoQ2^l1HilGL5&^Dz<|kC+Bopr&-p;i-{LgfEO{U3-vPL>5n`Y ze6nNWLSS=uoyKJ$7F{VRYKWf+N*>)VDfzQ++8+4=UocywZRWF)=v=j9NX`PYoGw=R zcx`i)d<2454i1gDOUm}_+674^V2I6QO!gl38PX5*-DDmMD}8kv8`Cm7_8b0kBqpd$ zSLApT{n1oW18ia?Cap-L?9L+0oMYYfDw5$R8>1{p!jLccJt5scPI`~z@gHn%7mCWm zx;f4kbz26R0U&++Il)3w!T^6qsAQRK`vB#uznkP#VeQ3=2e!qU4`x(58^wW3Sn9W8 zvx!ag3nIaa8RVCxjL>V!#TdqOsdWl^Ib9t(#=HWRhqBqGL&nA9u;|#d4EWi1oeOyl zSNBP^gIrw7e|iaXdRmGep3Bmv{^I=T!gOrh|CFICw$0#%7IBxFTzbp6ba|&y9jnA_ z;&ysmyY4}rDhnNNQG_mDSELHUIK_KeSbgiAbaDQb)$r3cwcIwQX%JuWG%53LXm8PC zVx*P+{5`{?%nN4`uWHjrZrj3jw&|)TMXD~0>CY@-Am7ihoO;2(N^WzPLB43Y$|91* z-*gRe>SreHS9*F$aINlHEIJM4)m}FF4QjG7^G|NWbie#X7fAs>@ae{JPQpR|`bIzg z3OC^?#SK%-NY&Y6aEsn+;v0HikaMW3{LQNxF}~212^}EgSz9qx{AXUGYr`!D znq->rgm7#4AZUIDi6U!SJD*)XD8r!H!i-N}KboSQnUsNtSRmv}BJ?-Iog>g=QSeN}I(FJ(>W7^@FMd*DS$==&lnwR)$1E!zT7A8m0Ld34WI33%! z+B9Po`_^FCOyM@@;?=D<8t7dZ<+Y%pg;7T%Yq-py$p}KNTWzZ0S!`no4uR4OowHF- zk(TEwlVMFA&m8q|c;=L8=d%!gx{VP8!n5&|X+uDDM6=(CKx9&Cj`j3v` zW`QpffkxLH#Syaz)uRSRoq$>dNWJdT!_HvqqhLKHhkUYkb zB#wD@Hi=f6E5_y&6I{yNtIb8+InhZU(ad_3^)d%OA#p(dc(PUP|R7cGb>Q&?Cc<(WPl!3psX(kDi@PWoM$+#irtYCb%0NX^x zicWCHR>Y8V>J(Gh2EmWae3w+Rbd!0)BL4vYD7y*xR@)Mia=&v1OM#~A%hg0;z%H{S zK*3}UQC>d>%Ri49`kn3c2N>j2ocD&?Tl$QoAaIBK{x9c_AicN<`LdqJ#!I8frhh+R z!stHLKxk#rAhW+oCOI?2yz$uP$1=%G_GU|;+KlLu5LH%iWigOFiA=OhKIDhQPll>- zZJ*^PT+Lg4i0yR7B)-w_c()kNnjviRN1Tnze`+6)>_0GAC~vcfQ~ra^{YTF`?y0yi z2HBXZ>qupfDK?*wbDLP&hsYu0_*#Q9!*Sb$B9(^3boPTS9HvZ8JYeQI!U@<(=`#tn z;vYX`W-h8ZYm#PFB?Jn5f|mOGaSNcTfb@LJGKR3Ck5d*Yc<_anXKTJZsKH1aA|Jk9 zlK?Hu>EqN69%R4!CyVI7#uf;LT|GB9AQu0^Pp|7*aaD7upEIsG9w{~pl2B8wV>Y2N z1Ea6#;(eTi$=DF7h;MCTed<{3G2WI>W6gTQ*fg={=!4%D(ywJOCahG_rBg~3o)vdcHpoo#X*eWy zkNRj6zs*pn!FyYm7Va}L6v`5;SgV^85vv>TfoFGb3zWZkftqHJM{L@@E2Iu3Pxxyg zG{C~LBvuu$(=7P_JUgCnS|YCY7d($pjFhNWldF~)1X*(hJt=NgAxI=~QXlW-Ke4f@ zQ)H;Jc$m5Li(lZl|D)(!{95e)KYm@ic5G{FmD)OOX{GZSA+}XABuSElbxk6 z274`{0(ynDcLCz0JjF!S@qh?>n6D`UQcRoS0bX>bZ%AyOBKNIsqQI3Z{uge})A;$3 z)2l#bcwmFhMFi>#_8g@7H&gP4aD5+$tIk#Ctp~;rc3By>e2DzsUUt(WP&Leo&IALo zSTEcd>hi>KSo8*FTF5p~5+?ilqI;J_MH78upqxoPWRmpu@V8d$r+7YkMk~5wLY!~eEmg{<~bX`J3i?Eu{%G*Ep}EByXzVtT{HBL z-r@QJQUoYWca;^Woj0U621p5#Gflf)nOx8pxRP0s^g|fJdAl|$Thze#A^^kIqwM&Z z0g724_(kd?2Sppejxd>7JA-0M@~b~IuHp)H-hAwfL+M(_5B5kF*Hv)AU*zshmehoV znwCA#9*3uy7FOu~;~*VFvJ>_1m>E+9SHzF)Z_U&}OIi>QD*s8C6p`vkCgxGs8FQMk zFxImB`BaZ^S)8wG8U1si$YVUY$JJfH7Vr4UBZwNFpiava7mO!_ip5Wr_qxR{iqeRD zEHSrE6Gymd?VkvD#S?A(hsRhOEg7H0sv{M)K2F1YFod?6Bl$Vg}$v(EDHl-(KgX{JWv5zo$^sG|%HtN&3Tja%wjiYP!fo-c-jiA1hGRZ+7Zy&p1)ETzSt z{RIxjW=Q;gHMz7{J8(*ACHGgo+^L^qV!73fB{uiT8YJDy0?FBbQHAR2rGuD~O5BOAVYmQre3jQ(elf>O^~7aF*_O%4@9jDtkV zjG!k<525snG-Vev&wn)cq6_qrdYn#7ley>hITJcx5YMi2ibkNL`)JO74E5uPXe+miJ0c#0w%u9$nz1J zs9isftyIK3%hx~ml7%D@CJ@MyIFb2;+61y69y)crbOe28u6KvtuaGQd77+QNL;5*n z_eN@S21B#eYQZMai*fl_d>(8wo1uZSKOB))MZg~OxQVmB2y0iG&RK+gFw>ucTxb3( zTD`pzG04caAjEr(w#X*Jk(e++4)95!Del5mLe(5^6-#3Exad z(-cH+Bq)~S2sQ5NYND*&C<+FoM7&hItdEyqrR$?s-e!HaAI?y&=C73SFKFb%gBeh`kH2F`3I8CX(MH}2ure?n%oR_vW%*yp21(Z zjuUq4%mXTZ=Z5vk4eLXrY(_ym{P=qD`l>J%jT>&b<8PQfOIykfc5WqpHWj4?aiwnN z38{*BR)pSaL@t}x2F+ktlr0e7UMhQpIEYnOz0hULT_mMRgOuf)n8#wsQ+Cn(zyA4w z%ZooL2DU3F+af-9S4y*Ep6hSHHVX2wp$4MO#oXS1RqaBh+Op_hPWil$-Pgny&dBu% z(DI9<7Ov)wTaD`=tDjk!NQvi@j*lp2x>hv7Y3slU?Zmqz!F0v3-#>(cK`Ci`qs)xv$B4ceAhPgA(!bJf z^(GU$SqAHZF5MaX<$7PmuPH&ujwYMzWVuxOxmv6%Wcw{RNgim;ns}*=2N?kG-s0b=zH{6 zffgQKfp;pd{rYP?s3;rHN9h?q>g0W+&*Y~-Q~c!r89%*Gp*;2G;X6pL<3a!39Gz$u zOpz1!?8>S)6qFQuvaUGkv_Xl3p(M(;kgE=rQ=upTZ=b#q; z2bYS;tmbg6moZ~Ace!9RcM-S>Q)Qu)?Hx9?Cz;FZ>EGpww>EZ8SXPRv!w1)e?gQ*Q zc)$fK8&Ocj%?y?G1c-|)L!G{Xs!qK;-94uWn93@VYpU|K6Ul}ki^_>@&>B z@Q*-h_WJ97L9SfKRDbm>n&`tjai;bU;HnOWCw^0Ej@pQivTdGkLzEQa;dtTqF39@o^q(M5Ls*MWm@ED#Svo$r9U zi7t4LaxJ&hO`U4zASaiGr~hP{!xs#&&-yVIAwvJ3X&d(b0d$qsyDi)>Iyq%1C`fF~ zJud{op>AblA2d*}wC3EB|JemnM;G5>Z=uZ(N}hMhp(Ak$@9iV_*O#a3(Dbw{=7G&sy?KdJ5aORy0gmYGK-vk+1!SR_I(l9auPRkeNPN zn_;^LRV`3qYT`J@pr4^*wEkd3(21^M3sC5bCpc8T@5FsSFtDB7!t_VOyf)|@`R@h% zeKVun2J7-D5^4M4-q(Cb!qmoO$KuXEK6n#I6I+o12Bp&sT2&SZ&Gr5^s^+7$Bt%oF9M$|RO-_-8#< z;)pIiv~z68G~<@oBwLR28v+@-hq#7COHe_d_0=rPUV%%~_yyP^RC_Z*Iqj|yv;uy9 z+1*`NCr{1-%`=9^q7z^;KVSka-eDXe&DnrDi_Y8iYHz+)S`e^v5hDB9daZ}k`<`;# zjHUb^w+wW$jf}?=OpidnqIL+5(Q4mLyHv7(V)M=$SinG^lR3w1U#y~O4GLu#IVkHA7d9j)*488oih zJ(qtFnWNFHR7Gew*^@r&hTuY9v0ESS{OvK`)DYGjTdW8U+AB)oIv#dBf1SAACZ%jQ zEn#VZpK_bK_ANQ#kZM0ms^Bo^RcIe)(+aoY>RCh5YitpNcyfBUVYx|alk+`&A?c2n z)%Id@r;QngZn@++vE7@YNnz}t6ut}B?=*MH(jTTpd#hK^BTKv?dt)2*>*g^vlLTO^ zsg)hjw{ud6_pF6_ZV(SW7w^l9{$r$^2)|2Ra-XrhevtQKk~BNtRC(``cncgn0nV%j zCjgM-x@@I~wIegcPVQ~mX;(RXk}ykx+A0ir{I(Wb0jaxPQG0t`*eMqo9z0JI?%mnkX7TO#=S>#M?T?o5p6={no#-J z@W`Kslch6Oejpq_(Af8Nvf-?=OL+Ps%8WL4Q2RZTTV86o?0B&;wAvxi6wG3-PU5c| zm-TaUMMq9ei8#l>sp4`Y3awfmdTJ>L-2_gmx5}AbG3A?lgPN5VJ~0;ADoc!^_h(sx zdadiAisoPO(WRPT=7Is_}M?Hj$*=bDrgh8(x=(jc+tX+xd%?RBGl7KAQ!HgmdvS_oT zz=R=YRp^W9+P8<U?6bX7L}lmPr@BxBQN0}WwM3cTTP2~nGZ^|ND1QD_3bWe zh33bB;MIm~{t{}!3^XsSXXnDGFvV#<)C#8@*{J2@?*FE^Osjp7B!zAg;(Pr?Ne-S! zWHyePP7RkGWWAVS@_J4)w8q{~wfKijJE5{U~camG`t<$K0q<^~q)FUn%JmEqdRs zlL>jokz(<_&tDf4_9v)=bEXS%%NIY;lZ~=XCUa(E9rvK-o_u{D5?l#34fED(jHXU0PqW!&a-Eln1|V1; zA=N9+H zQ$%O#^zZAb-fdiT0nMESj`1~z`HS2e&qz-iBu18LM3zQG|9|YRiTXJ#WSU-YZf^HK zy(KtX-Q?&Y!8+9wzY0TIO7<>aSEBB)Ya=^f<4Jz-+-xEG&AW*|4yg&=rN;u{Rd2;M z0&N%b4hxE(;4eP*i)Yo0eI%z87qjh78VY~&g5|A{cTZvwm|vVCIA*G*?1RrAf}e7w zMC;!ga}C_lF~+*UcbuYTmu_*~!aQ|^4R&X*k>~I{Pkpsr`ZTBCPtD13kZ8UWv<9cV zb~BwiI^;8$r^B&rlRzA6lSz}OWCQE`5q7~_<+Q94(Y4L+!#?7Lzf$322kX&$ZG#^g zwypJAn;CnGwK&os%iFmVr)91C-p!NmgndqukDLDf^{M*@zNEG`==v1-juDcZ58#*{ z$TicR@#n3P?Ex-{Zl&!>Eu?1bQQE`pugxf`^_E#zXigO=$;8u<;d)D6ttohWCUG2{ zf5;_1gy2{Lxo?2L;%1xOTH3y>mqNuzn$X|em+O-hpS@EA4 zNfV;u<{~k_LqDE#Ty|Zq56NL~j5V)aHo_}FQBq*-Q{sKUto@c9CZ@)4-Ww%_TBE&3Ey5xzE4lY4W!wnkCS_mGYt=~eZAP=*?%M^Vsf+J!p zbJ5XChUTj6_$u9<5z1^W!*C06I0*mLew7!aCxyr@Y9euRB}cT>&Zxe|R;*b-mem4U zaN9SAG!0xDW*^r&$6;~M5bf5~9$`9}S@r{c(1UAYeQH5F!YG~Vlxuk(FW*Ggm!OGb z&n_*eZ}p#ikhT!&h+A{F=I_V$sZJ70QR33Lw2VIcz@A|hTWgQ2a z=0U6YWJM?(AErn^W{<-Uu9+vxrN=jg-Uh238iQO(k^b?O*an3fbB8HiW8?`O+4H;5 z-*$pMs@|aio=vtil*_*|CB0)exAx6mc8sI?$hZI{FwiGDHM(gZbAruG6T(o);RE4^ zO&W2XY4YG`|AjNk*Ps4gadFuV-NS}~PoGv`?OPpJ&6e(I1nK9YUujt6zX#Bnt9ox~ zN9HPM-@8}rUMD{3S=%#2*~67xRa(|n?S?7STxmexW`%eTjgltDaL|>LgO(U`8HIRm zo3p}+kP+;APJM5e?xaqhf9ANE&wG(K!aB3qteI(- z!Kiv=&=er0w;V4J!zG)bJdvTdSx%OelfNGheG#s0=@Hf%l;NFnS(L(jYL?q{i;9F) z!nj$|YofV+;czd(0 zd-U8)@$`W3baGC|1pQWemuXJqZIMYZIc_FA@1RzsBHoqvm^ry(j@pPL;KIwxz#}@O%%nq>s|)TX9j9;SDWzU}zpwiq>z-wEakb zU4xq)QNE(hZo^1NamNo{LCvA@WgwvGMCnkEP`>Xc_$aoI{l0sIbwcrnClp#!D3xFT zjPR-!JftS%=ty>)B;@tYQQmfQ3*QrCln3`9`cCOjoD$Pi4ISA-m^`+DFqwWCIr2?; z7SUgw*z`e$zY$aBtd@RK3{TYhvpw%pJ=RGBEo%QsR*>vsIABgi4<%QXSM^yW#3?s$ zgI4;5+UPc=4$VHR*Y+-PV5s_WDqYkOBF|Q@->n)1stB0g#4{5 zr!fnk^Y{D^4kkIdg!fQe<&sNwWdu&t(j!sVlrIlp6{O7hz6z7exY?ho1XjPx`P^2j zpLy9^og=b=Q2Q5jafWo0=SCULe5YnRb3q$*Lkq~c>?OGEQIApR!t{|`w#`L`CLZb~ zMD10>v1(t1+T8M*)*jMl(!pUbdnIw5HS~N498$>~sX3(|y0c^KpL3Ufz5UZVYyR=p z-Mc|;(*B&ows9q+qrYawCA|Oi$;U1sgYZv?e94yUg7=zrQijGHMz5G^qD|?u=k4`3M<+MYQshSvy*Wcp*5jDQZi2ZwW%{yFu z>^G~oAJ^QQF7P#mQl@akS3`oTBA8!Wds8HKjNO2B^kZ59pdgld~2&Cgrp#nJmczLi5L+%B%l- zGZp<6j+d_@#kK^fZi@K_l)YI7jpl z9e!6iS3cD43z1}x<`y!w6C*Wt?NpB|+JB+5J|eHJE`}^tMK<^y)m65Z*Y#J~NSMw0 zdT`F4-1K1$#^miDq)bU>WbH?)Qa(Z6sFZvhyW9WcA=kRjPq<{7 zT|&LcMS~9Lm1E7+*{jS}DAYD(ndlh{ySbPs=e;B@5>yi^S5x1hjX-NY|AM8ecNP3D$YGcm|vA| z$fV@ZIh$oFrImo;3$cl5HvFHw;1=p8Kc|ZQNyKU9g&bV$!IF$mRakk{ zmpoChDdcfL*PQU(R#l;i&gK>C!9z8TT@D7!%bzO>19W*l5;MhtTIka@gB{)Vnd%o~ zV<*Lb4Z|Fy6AC<)7EPE(y>bT0n+n^Tjh%S&Y~Vz+d9+YIw#_g|VhI7fDp#aQpnU{~ z-}iJa#&@j;e16Q>f%oTrVmzF!221*gP)K4auCYu$hW+hFB%f zcb!q)b9Viwgx}1Fxnj(%0I1voCw=;$JS>eA{Z{cZOB3P{YzMd3;)##isN4nQ153mK z;6Kq8KFQ&qIiZA0Gda>nMqVk8zjAU-ep2?+MVQOPS}M*u-ba3ZpE|b?7Y9rdv76qS zgUe_x-}*>+g8CQD##c>Lf}_s(CmYEbf2y)$)$()E;8 z2uRuyT-Ys#FR9ar%gbozsYYdFKlIw+9u(|MtsO>!{O&iBZJ5KXI~zuukHqiLy6@OMB5RrSL~- zxMT}7o?HHf|6OhQ3oo~equZGKO2z4=@DQ_1QvTOn=#+S94{du3KOH5;+2WwgvHUQl z=?2cgu1;v;nLSiolnsqg-X>u!3-o0#yM^^CLiFQ;#TS>oW1B-CQ?cgDaxN$X9{w}Z zdTAX`X>EBMAN}lMOL^EjaAMKM--=HPuriBn&DT`scwT}gI92f+Y4!!dXhswivX>kl zw=SRk*q8*(pRJArtw+&#=x~EZMcTRBA!w>7lRTMX41Mwzm+PMfxK^M)L8|743Y4jK9EQ5~Jm<_Y$$rzfa7C~;Kvr=O2;ricgD8#))oG8%!%FqN+4q;Ya zfh$FGr$}BWDN?z4Bl8wG%n>K7SA%F&)GLa`9U$n-dI4axJ_UurYNpk{Znv|J++?o`VFz2 zioxFLPjgeO$mHV7IvBCa4zvPG(b8UnYhpHYg1_R_dBBof<~h|SQCgK2+?XkyM=;l2 zjD`LvV^`cCR)Sn%oeOBX=Z4>`4d$It`hPgjyh78(YPN{bzp1OMQ+`qtmKrla=v-&Kwe1sYZsiW%NA~$x{WV4Qj5$586Tg zN8k|O@hg4FDbN%XLd+KBuDlX$vN(UYSFW(gYz0hE+g)S%_MZTF41nh_BY5<6XS+tk)`_&!AA1D(r_*l})5 zj^o;IIHtZYI-{gj*X};ZetKb77z(#aDF=_ZNL#ubFGC|CW?}a+)ll6pjv7Igt~lDiDWiJIeCS0pqu}X#P5m<8LnO z_;W?3vh)SiK6c7XlL`wWbDrb@gNb2A3%nO_XUS z@#h0Vs(qXU+qKJ_C5?*6#AIS_kmfEF-$eD%p;wyaO_M^KYNiEwg3R%pY+O?ctcf4s zYPiDOzmMgm=W5rcNz6h_)<<>tiH;$egUGuBw8v3ntX2Pj@Zu7v5uhF^{O8<`_1P&? znk=a)TXU7{r{{-R7ZxhjesZ0<4?F6qbEN;vHeZU_f5fVH!?)u4g+U*=x-;yA&!Y95 znhI(5d6GilW_a#}qrYHmo~Z^H4Dk*y1fk_K;Hj8q{B?li)&w?#Pw^Fp<7n&Q-H-oU$r>!+!v{xIUgupT_(s`!PRR zQM46cZ5jsYdnTGdZeEV#PFEs0+7c+EMILuP03B~qZ9?71v=Mmt*Wc*J-?ZN19}Z}` zBu&OcANwMWv#|FjUiTI8#r(-5p`OW7#Z?n@<&4T}RBsM{`ZltDC=%yHjZ$CDC-#$4 z_K@Qciy%h;S>!Sdd6Z}%cRhL?+chD`QMG|n`I<0pliYFfTd`G~;;2UiHsZ=pg{RZI zzPB=-UobhIF+zF%YWRn+>No!L+s#wE&T6YLn!^@0~T8hsI_cTl?SlGpA+mSqsm zrg9RUVL_PVR&<*TWs=?CqEzj%%i3(J&A6P@HCff7HM!!uq()bl9{#k-;AB;=LFM9_^$+PvlIVZ4F5#ONR|9`P5MY?EzN0Ul9b?SrIGpOGaotI02;Eok;rn0r=kg{{Z?B3!Zh9nMcX z3H)Kvn}K@Ywzv~lugfMcVni^ao~c@QR=wmH$KcFqloNB|8`}ImaKYbb387#Iu7m>w zPjyqqc;`2fVju@`q9VpQzb(JX&5ZK^`){LmJ3WT>Zy7tDd(cgHswP_I2f!tc1CboETnpjo-D)+x=n_{u z(>c!M#cLyr9tA#2KUEAD{+keLo2U84(Vp@U>{CVzv-Y_6WU|djC7wx&z(W2jrtD*= zuv}bReibav&Y%-Q^zlg6&(qgz`+Cj>*Eu!LgIx`T>a9Qp_zlk`YP@d z`OnRI7fEk7QP;SGH1SVS2=v7^JPTFMKc;4o)f0AuRb)eb8)3$+H=r9#c-S&UIqM*A zN*1THpOX1~<)^p9rnfdxvv|~i0L{$IhtJ^_h<>w5c3MIh@Py_WDxD>is53=;CR{ebG$k- z7iW2^Kn%D{exJJPf672#e>9KZ6fI9@(W-OV2_vfU)UL9$r0kUSafb*U&!Nd`(N{Oo zDq6vD(TC4MgTH(FCBpI7;$&A%dvHZ8GSd>NIAOXv2>h5?0Qh*j8{W8+Sm8u>0e9|# zXk5Ll$-sIM0EW0O>1UOKfb=EQHFV|3sB{rtp5tHpQDHxg90};_25-?yyQWLn!4*-B z$j!MLPsHs5e+n`BwjY}J8`s-sFbs2YXf+#|1!AuD8(#v27CZ2 zQ$~1gBbp7rIHH%G=v-6XN=v7TfqT@=<0>Lj2-elO=+G3*6Q!=FTlBbRNTT5zCvxm- zDWAGuKJ!uR^H~nWsb|76$}&{r3Fx#z1$*S(%S#7-2>pIm<8XHn7<0QbJF?P<7j8qZ zKbe%d4c=?H$2{MxI0**?RBX@5FH*G^Co8P>f-&1Pk?u$^VhWFYh3#Sn<@kmKSOh%= zhl`}Ba^#FX`iSMW7=Rl9^;5;4)PDOkc=VxYH(9YGZVifj?J*Ftr zjH8J}UKcr|fv^}?Z!``yv^Un@Z{614OY&asAoWol0@QGduK`*RRUl1FefJ(q#L_-| z(pXs*ER0G`O|&d38Xls&_mG}V%}#J@rLN*AofNRSDegEh86*Ne6O6eL5`X`C3K4yS zyiRVRu0A%Fd+~qj!T`-`_6rB%N?X}F4g2wmlx)xIiHaJD)Y;jo49;_ZUyRHhrCd2` z1Z#k4haD15sm`THT1kZ}vFR1L9t7I<&r>bki#s+D^MQuXFm;V22l@#ju{A@iXS6vN z_5QXog#-)R^aG@~hAreCVu!q`m@H_=1JJzjQ9B67vq*>6VSPQqt}vO7{&~D&qUa+H zy+b;=B(5|W8Yt$E^KD9yIGaYs0U<_Q%~6fnz=a7JMIx>)gjT# z8Ty@Gt~*qVOB6}@@QvG0!Zjm>#H>t$KLxqCNZvs)(V@qP{d##*h)1}gmU#ZyZ(dhs zQg&c4*U_VsqjiHG*ANz$VKPC$sI0|=F}6;gTlv?I!FHawPF;h&aOiqOSn=QJ_R(ngrdwX|$Un@BD|$_JY)Pd0ThA01$AjDRZF6Sh#|l17n5k=jM! zs6oN>RuSm6B{;8E-Uj)>J@!$rTB-fx1+`o=H;0Y|$yEX50fPG74uO zcBl;}q^*(_E9uri4CU6y;s1Fnmz-dKe1@BW`kV(#vxbn*pG70{(81K5CYcZJ)@=T07(rj%}HN#$(++*w$k>YP*Z^VBdjbK@W5fk%TC6f}9+m!g@htbUicX z9xnOZdc+49x#vjsrY|X#Z^cP*t;i3WRQhI+@;a`bc=3Py5`y^z3?kfZ6-p4=<5#I* zUR$CTF-g1zI|OTU{BVDbWf{Jz`7vs~jx1>}iuSebpYq}e$U%+er z*LTd0_Q8sOuuPIbC((Dv`e_&?KZyq>I5Wcis)Uxu%cJtzN@I- z5NP1dP{EN)aQ?qEnm`g207~KZ5FZcy;0;QGdz#G8C#b_GAsN#NffA98QElOsa?ASt zA-TrgeEUvz`g=V8bXgfb3E%yw zV|3ryfscNp_qc${N2^Iia-s6;RDiU7H$+Cn;v6Un2dh53~p(m&dtCRX(n%DB^!{0Ox5G*^sKlN zxaba7I%^uR*rbByKG30>KyWU`HJ=4lrct)y8AAsoeo-rY(e2cBsNPeRu4c~+fM*3I>hiAR+N%s#SKZ!I z%Etix;&rid&ilj5#??1LWecmT{ip5t?ssy{-^smjS z(i=&H*Q9>qlf}}7#H=VHJ^g~eZ-#QmV*Rp<=m@5}OLf|Qv|BFop7|oAo;qdQUikQa zbapoTaVbJ^-Z&nP-=unC5?0DC;EPt8c@E;@bE|rmCx`F;S7X--%{HszkgP58^$u`t zk(BswugIMtV^cg}ZH{*q%OmJX=$!d+KTS7rv$E{{^GvI)qW@8>u?&|y+k60VSey|P}v1yBo1^> zv)mS;ex$i}`~hYEG~FDj#DuW#rmuf}fam!qNOPh#0s#~5=yQG{YFdihJ2~h%nUjob zR|C)1Ubyt3c-D}=a_{*pb+A`s*uOe-QXvYeJU{Yo4FJ96Nr>2LX&dIzclfHe& z^x9c{BSORO@OwudoFr6x9(X zj}j(FA}o01&mRxNoaPnX@~REh$g>}?bvl&xS#hemx=OX3R5paP6Guti#~%>yY%}kH zQuVJMp%wJmXGJT~g||d*wzn?w3J>VEaLu>kxcYP}nwzKW3##3p2wfu@8(JrXSFylr zy`KH_9_h%G1smaDzK= z9L{_u*Q*xluYc6A=SRWx?;(ETH3G7v8oCeiEcSr{R)FaKOr4iF9Xx3j+QA&p)yhJy zK8f|>hWb6y2~jFiNY_u$uBH&)E^{&bwJaa&pI)<7T?iH>&D25&SC9NHov%9NP!WYZ zv(x@Jt*WtEQTP^}lE;2a3YXdD(aL5T)u)e%-U7FB;?B5Pl;EQK(9P+lDL&~}&4R~4 z7iV$HLArUaYaGX4hF(cZ@} zYL=d5)=O@Rk0*C>?#i2d`B`Ph*wL7usKthCsGSJHxjHv8oi+#4L@*xbf zre0(Q{TjdrgjV5Jnr%CA*uh9$B87k3WQE(?b>ih0=1WeEv6hM9`Cc zb3o=H%Zqj~-*N6oj#O-#Sr97F&I<{rn$nnNloxeGdT3r4piupJ{({dv|? zocAIlDf@oCyz*BZe~U$d9UdA@M`Apj&M!mug-6AbPG(9hLE*~ME^MosU6#Q=4L7)N zC&v+>39#f{5nOPLKZnE7?nA;WpK$mk@O$&PP;%wpaY`|^e3W%= zCmy;cdG|Q8XM}erxCNWV=jdoaV1`{U|1g@}|1~$evgydf6?e9e_48_H?uUb${|xw4 z-DaJK6s@$Kh(+6vI`kwhp{%TQ*H(G4QC0m=o{Igc3-0Pg&BsO_o?knL0#WBLqSeYR=vZ;vAI?|DISjab_r&#T7+4y3i;R_^-P`)L-eU0lusEUmJvuh9R|G+e| z1r#lWD#6KsY?q`7qn`mSjdxrNn(8@D zoiN1fofNKLz|eN8)8TF4lMg@+G^9usG1=NEh;=7)!hh&xJ3U7|VpgW&mMuN` zf)bfs3{hLqpC6%w+0!oEaqQv>3Lxl)iOeP@Rw#PBRFIj7uH}ytVkt8)2ZHN zTN9+nC|If3xCbocqG!Qf6+;i92XekG8gM&bpFhTXJI=6`l|F)4`D)hZ!ppsV{T0?& z$eI~Z{}ET^_}8Z<;h|P(&^N+9fE7{fD&dRQ$fvG!*BPtVS0id(*7YZ+>*R`?4MES9 z7viuy{feZJ5cC~i5jwwar*RN*|4+PzqgXW2*1|(ZGt*B7@|U3@+pMFIZ=LGlGWcw2 zD&eVjo2K!>AlQXa3YYS~P13BumG>WT5}$J)6V$1&W`wKrXa%8r+i-~+@gj`wp_I9Z zu7D2s2CCO3{uxf$G5*W^ayO!#ZNA=RO4fhloo-FmQQd|TXTxC)Et}GhCo4>OiVwyH z59e@+1v9}+yqFcBpdh$<07fJ0lR_+C=IhBpfvx_&s0WX%Nz?9_RJAqzkonY!l%;&? zY&s*#z$>aNzd)AFG--a1MG)J4v7$$EiFXo#<)XgqDp0DP*k!f+=G7J}GCdVfEwoaS zmlg5|*Zu|NEtISw*_dG=<6?5hkpI4+KX>Z?S%{r!(uM=4Se@v0$fVc>vwwbFoC)1j zp=Fe^KcpiZTzPs3d3MB4)n1THJbt|89+VMPMa|o@6Ce757PU@{=9ijB6D2K4#C>9Y-;Tm z%EjMYty=-RYg?`$0pRT>7E#yXDL*-d%f*od)&XDfZ{Wr%b`BkeX9l$d2v_2r-!=TgtL4(3>e^_eEgq6>#Y|JoF!M!zzNsnaH2*=n~Tk z`x?tVaCBh6=q&fYK+a{8ZLOUuzf!k)tK>f0t`1M^2UGV?hxyNQXfF;Err^SgMX+}w z_s#3vh;l#CvU^bfh}>}A%QZJ7UXDBFcm~Z0mbaa?M4%#SVbSV`vmo%=)gcTxF?s3BE zZN}eIWRE6QHB#CkuD1IgFUI6A89!+#t&a3XGKNM1ckeog6rH?l%{|5(Yf9NKn| zUjsZeVffdlOWk$40r4ov!+Ovs$W(;d_pInLm_;Gou*`Z)tQsbo5RP;b)`4OON}}TG z4gTF>xqi?c$!fN$yr}qnk|VOFsxwzWms(X;Ss+vvG(}zN$UlZ<&?6C?tGz<1F^sU9 zA7R0Sy!&#EY?w7WRqHpom_SbNC)}i$UDsY%MV3UTD2g^=m8w8J`13jkRt)E!cLE~+ z*H}yo!EGAa%q|QopUo^A(tW3VT9N);s@QzF{K~|NJoUy^Y}+vLDfO)Hd?PXUuH3a5 zioA~VmoP(AEi+_$$GzVqDfu-Zth~%{pustC;9=1d*Csb@1pt_qlHE`6-wE=yH+@8u zo=_7j$vs6IhUZ>!Eq4Yx^Kv+Um6c0TUENNEQXwB5J-H@zws~D@)cG;^ z*lGML(Bpjgfuq*h|0+)Je_p%<45aC|_Stl9lMb>T7`5#ri^^?q>1|cSS+(_T0E0{i zr*UiZ$0Dg>WjtA8VcnaVWjf5O##jkg;ZN>>)&60xJVmZLgXO_x3ZOVSrd?ne-lVDo zDID-t(2NJ_5g)V|jtz;>N|J}^4o1SbF>MI3Fr7~t@AQ7BZDpu<2_4q1v`02NF$IgpD3`Vp(gMij^yRr$ zuNj(+P1M;7^&7`{(Q^Q1d%`!wMxyQ2H9=fuTJ{H-q?zKa<8rjkyJ@cjp)}K|CuRv% zzM|DzB;l&fU&<#b_rW)Ngf#-BzKd+3iMdQGLniK*8h2esUMt`S^OW`WT~c_t{rz?wv#gJ_tNXyE?!ds9b+7Kw zHrgA0i&CS$5;`WT6WmQBBCrjeIJigpjSk)=*fbKnFRvV;+ssZ?d8$FJ=1b6d{}@~o zU)_V;?2GyHrb^n02mTiQ11e;$sv5?Xxu8ql;quBEz#Zy6-f_`%r=UQTve-{KvJ^8L zVST%x-XL^+ExvFMFp(}!wdIi8Ced`*uq)KC`7*A-$RJNLnKExOLtmR+VP#+-HER0u zR{+Ah$;|BYQ_V$j%}jBG=EgpDnWIrXM*3bkrVf84Oh1W253Hp^e-R|OqG(+$*{B9i z`5FVSqlR*%f}yuWX!BgcN)s+rFpH!iV>7vf(H0ijho{;Sxu=I z(IfeLFY&LN#+Vt^74tg--Ll9u^QF><5CU+4+bHpSAf7vHVtHK8NmHDElSn?jS>m2X znD=)`+Ct?l(%An|bS{1^?|&Tse)rwAt=773-L{l2biXOZwj!f22}x?vWp15Gh=YB% zr9?ELo6_>jaSM}>Ypc*rwUb+i4qt?b%(d(G{e910&|^Ke&*$^Lyq-_!`e5}xOsnO} z&kumhMsIdRw%u^yb-!vcb8Bo8Zi~JFl9k3@x5(j-) zZvSewgx0JD_urQ)9fKqbQsHaDCeHxnLDdd335MB=hAYX-zOz3w_w7*C>Agp9E{u~A z>&q$60pN*Sf#|fnD8wzM+K`H0m=5Q<^6eRd5Q4+YN$SnoA@7zyg!+!|dmUuEQuNnb zuJdVd@vey;x3-=M*6jmD#}wv+v^Ma7=HOb7jT+1UX@1*Iu#v~0(@^0nt^uc-W^UH3w7G3cjmPuj!<1)(1vk@0aB$vh zuG8)2G<={_Dd(MK%2Sj(8g>#b(Eb@)-A-LXmIwOFw_p%hC~JjLLqRHhs7baKUsR^K zzCzJV>95Rfb%x{wzaeg|G8y+Lrsc|cypX0E*Kr#zx0BDduwGB~JCh+F;JL)@%(Vzd z@;L%`C75%;^OtJ(A4I_|W(w7*HVd?j($;CKsJC+8)8@*k~O1o$LJ+(ldt*}Y8$pMH|^+Kqzu{a|N5 zr>o1*k8iDAg0=~IzOhGRiJ}PL@f}ezK4?l`$V$f@%s%z&0TC%^PM>V)J=Obmj2pUS zxrE8z_7iTlHrtQBzWzF!rYG zE%-S^m?rzOpwe%si86%SlFf&OmSG{t`52b1NI zA@$2B_Q!I5dvXFT+`!4TlOzXEd|*M##zTsh~f`rOPZ5&u6i<6GVFQC zbewPg5(jwWWfm`K1*X@VY(J|&nu|8ZOY1UQlw{H%#MifzjtY=KR4P6OEqtLGH;!?Z z(blHG^9b3jZ&Ja8$$n~#25Cl=NMS1QYi4aCbe!fs=-9=>FZB ztvx|gE{s#*X#FAbYi6plA@<2dIl*S-$2I>OM#z9=N&#|gD^2|yY*LUUO9#|X3LSg*T2kYIf$22Ds*&G5G%A$}CP^e@`&(6U3fBe~AUc-M>HKOV7MQg@Q?Al3IhNMR6J9CSi6c)0ctvdYhp$2z? z6}Q?CwjHU}>_@ywy2R5}jnd1PuP9!)YF3YELS9Q%AX{>QWaEa54D}aR5}h|Gw#+)S z^2b+h0KhZ_ikg|6FW%0{l2-6G=B2AQ$zwuRc|qm|#ZZ(4JLv)t7H9f?&TIPgNY#bu zgC|Q+Wm-JAKUqxr#=Xak3#F`QIeG9@-Qzt#<-r`{ zpj2ErUIo6HLln6GV9&S3mM`Yf)`M|}C}F;|vn!dJZo;ptj%SPV8Nud1C#=O7XRn4& zncu$Z`d8qE2~E{0k8?c5(GjzeCxse#ZF;Jn6ykpZjN_{_x8S1w)E%_a{E)*j8kvQN zg|`jOt^FV2;8p(nSk*uF8=X_ix}@25n(++LANW_}^`jzZ-a)@jKaD+G(4YqtK4MOi`~0;@(S`B8B^tiqT- zjMaE1K|q2BZ@se%QGzM>E?nj;Gmmq{oC9m&`ddx+y=pJx0}-xAYV)}%0&M3aOaw-q zROV>N`B-=jLu;GY>=zNM{sBy;69sC_&^4dudXwizIwQqRBb7N`@mAG?%O8^6|KNA6 z9n83hVUzWKN`~4L`oN2n?8LbWyNT8)efHikwxc5=*^G7; zXA$VV5-O=yF7h%T71&E!4m0Ar!oR!ry~5-wc@aLmB1SgzAQ(1?JLg==a%>4ik&)__ z=c~}_Rkx~7(HeFe`C&)pS(E%f1pk0%VJxSF;V_LrV<}4da$)bO1E{(NH``0j_#7hH zsi=R!nU8Ih@`&-Oh3`>h=MeTdp(G5z8$t!WgoAN@{!lAC=l;DUbf-zh1R-%qPYTYM zu+5(Q@G9w5BXm4Nqkb)V`zC!2aTozF+OJ*n1~7R8eFf9fKa4`pU$&FOy6wfog;lR) zr<_A|zh_%n2@BNO$^Apn)Sb#FX!Q@CHo@o`?z6M_n{j(Bx{_J_1t&g#1UxG-kZx}^?;V6nJ{71J zfbpw*d2i{@gVyHMtRq?!IvK4Kj}fmL+bOlnwyEDta-&VG`e-^c=IzS2^} zl@52U!T(B!30~r3ZCu?F7dTfuA^BfVKD zELwvrERT496OH)clX5f{tT@NiKo%Gu&=zu{3r)AQJh27M2j$Qp29jM z4D$FT_n#8&9{i7D!$S2c#towiOO_^|Jade;`eY}Nc*badjM5sF71rZNmSFQ%qB4rb z-?R6@tjFyIs%zH4mGEO<%?Ao+dO-D`7FhV?#{s1gGsaD>C<{?1%iQ#>)k_LKO4FGfGt_D4%gnJZDq^%HEH6}OTPW%MWXt2W$ZG2xM|TO^yM z`fv7eIg^uPP{RbZ;SM{%5;)NcRI0%+-2p-$9^V}S|G>3vR-d3lSstYL_x`-`2DHhr z*F_vDo+8U5K~B5=1%d;ej>Re4pbhEpQMIT`^j3A@AgbrbXbz(orgYCmVK-NiIvNqD*b0jB{bDo(ZF3#o+^XO+U^CI;V zB!{!3^b1uy0>?=VknFBu(uuU3n(ibnd^`$n-7B3D_~@48wTF5o81@xxxJ4bRn;>@fjau_Bu3^}CumTfjojac zoq$sv=R+~k@;X+l$<#SN4F`73_O?F=Y4AeR469=MU?F2c$Jx%H_F*?s=B&R>!=- z%g81ZENLw$P)_{mm`{#*9v&sbMNITMZmFcf7fyfTKkpj`7_`wIQ+@sR!MHkk4L&6_u)V{fh1;eZ3a ztF=@ATE9wU`+20Q3YF>y2HxQ^x<81B7e0>u`Wo887G@9^Rii+n(Sv)y?U3(uabKQg z1NrBp)`M`()mtvj8dQ+gCxzw^JDZ2Ld{H7 zXfreU?ix6rSWZa=u3zQ(8a+Us+ZNr@WEcv44r2HC&WKJ)r1)|V*-MJ0D=as1_G*)b zl5u#H@yS~4oORPC)=zfJmd6bz?x+qVi;d|iZwLVd=o;nd^-@)SIi-IXiqt11W_XD@ z9U6oM$0|u#^kDz&3A*{9i4n3yp-G$S-}j6Qcjhqpr|;>2Orh7IMvk|wM{?R zF)L-%Y=NvDSg6p7!PCq4T~(H?jTDc8iRLB_MydEq-(&%cG#iwxrcbQu_xx}mCQ2Sb zNO6s$m^VBkt(!iXH^D5P#+#`+MM<%Aa)W%sT_Bb9S{ZexSs`pZW+&4 z{BX?Hkc!5cEa7)7@c89E+2KqwFc()JBr86ox{IAfUErcm>YwD*SHN4!VeakUa4v=c zDwN$J|1=WVI{&GlZcM|NFH_(xw3<)pDYygZ+R0Ax%aYbMvVEE?ZJOjH^jCD`pvxS= zWx^s8)bY=(GJc?`sN?5{^6N3-0Tg6)6P1^Rs?4s!}E`rMXBA1 z`uXr9)fyS@Q;#o9KR-(P^2A>TxQ)SYs~Ej95x(j<@uI-M--2_yCF}N2&-qLnrRN7~ zzudi)bQk{;q9rIMCuXS4yT6b9pwFH?TNKh|6lQg(a*XbOrkl9wJ+3Nq6bsERkKBgL zTBLdHF497a<)dBWwvRuOZkh#Sp59rdaqyN5-i7Fm8+mL!iT?94+);pHgR8DbezMO* zb4-Is9T14-RZ)Ueq-j%Gj`<(Bw=wOo=STjcJQt3jJUJ!j>TBdnreqCt-@N85*ezGy zGgIDOahX>A-qM;eSNAFs^}-h;IYxF?I(h~^Z$<87cyj_e0P%_+ME?1Tgz z7s&&r^4jTPJG;qeGc_GZ261STL;)l(Nn>(KJY5zDznG`mV_W|n|F3Tsp7n{)RS!=g z2bV$~TvR`Ps`js)LoSlRD%*N#g>ii#GDBU4v57ul?3WHeyFWmg1UpS?HWRhE{it+h zUzyC^wBf~&_nBqw_;~ei`WpxB=tHTF5o}s0UysMv*BWUrDnMQWp6m&8FpEy!m?%Gu$nGCl$rs$^}6A zJ;#6W0~l92xJ2%mW8z>)&#~;FsXya}(&YZ$_l)*=Yc_?rU^YyhoeNYSjemi^aH~wU z11#@#yn$)D-tjX(Nwdw;a%eWZB5uv|HSjU);w&aSR45of8&JkuR`!5hrNM?W0jNe7Zz(nD#Nq7k(D%%gYRttO3m& zk$aQuioY~6%Ft3jd4y%mxWAvgwX&2|ZRrRNyctE+@wO=wTnV-Si`Av~$P_HX?d&PD zquN};rEV7ej~Vb73Tvz~J~~zQY#e7EY)iM*2}z>+q_BEY1N+AWJ2#1Iz@^J&tJ3zM zYk(7$P50V}6=V7zL)PLav&EQ)X~&7rk6WIhc(zK>Yvtro$MYgxe*GmayQ~8k2^H*Pluv2;NQ&Rh zGY@OXN#`wiiE<8O!Qdiu{#j^OA{Fvb_+hjq@wS{)S7JjdOkA}zI0yKfyak`YTe*k9r#_@~zeqtjX`V}88FG>|jj)aCY|Y<~CyI+eSJJ*M z8f+* z6C#Lba~GwtZS zJVnL<(hA!GWtxka>ZOj8kNxD}K&+d+9hHLYpjS$1?%oFNkM+UTUht z${R;X6G4F0O2}(#%-k7~!Qv%PuNq3s=CWzwzZ?a?N?q zRJt~0K*vmwm$RT)Y;d~8FMQg0RI9_ePS-wPC3rQ+&Ds)Fqa&Oa)-==m6VveCe#tol zcTmIzTP9jg+F=~j+zGJMIM>x}Z9w)vJyKn^OLjVV@*940h#L$K?~Pt2KPF2@Sjn?2 zwRKjKOXP&8h;$N*L#IXe68fAyYk&TQ6Gsi{>S+^e%f}c{# z=jiFHV+iiH{5`2S&tci=02i0Rzd+$aygP{1K9(Uhqw-OEyEDvni=SGBc>?a< ze7#v(8dB-AYjT&S^ZJc!jEOX1ob@1YJG&A5N(4;wI*CCf8Xf-dRDd-71=cl| zrOyPYU{KH^;$Kp`goKAk>ZMnA!LOsEl-gggEfjYn*f2RtN*Dn@J_1Hc^q3bwpO>{E zE{-2%&^(%;DLZuB;H}|x_=eT^1xi&8P=vun&50YlnwF9hD37lrGi~EL(7fLYPQKam zY+TZI&>iBN2O&1rkFVoThhife3jLxd$9|#U%3-ko?=)#?g+HCj#S9$Ub^ZexqmhRGZx1M@YWJ-L^BRwU>ch2 zWTo_H@LFvmxRa@J+CxSBnP}5v$n&F&gwgPLY}jAM(XSMC+6By_&JR<*H`#SLET9Co z9Y7uursY?6ZD~uH)etLxCm^=zY@M9h22yIgWEo+l9c`IWugTfK>L%R6X=wGPAfn`B zGkL>wo)NAuYj%q??l$~sk&a{J6wn&}*G!eU11Gg0TKAL@EMlpyd`6C9bE)KsR^8a! z(-@*rv3{567}=w_Omz>PiDb};6grVq+^7ki1!oWrjlsPG z++3`O_Ur+4>D(H_7)m#{rAinKbMZA?_8;aACV2WNumbeOO;|TFHIG1==0!TZj7S~K z7AW<|`TH!@^uL&zwe3in@okXQ{gkGRF)CW9U08fsvi!QM=oAOa*hr{zCxEMng zl;St!<*f0>npgL>Lp0X>}XYd z9_z6qF#L;faZ*n|8)HzISb~Rs2=WhbrQc}TtpRSRaO{!jopY#i%Y2+6Ijq zvQvRqb-=JU7Pwhp8!7$`2NHP6#81`!a>X$QBM?mVs$k-rE)Ra;JDgg5G@JK^!6nzam8lhbV^1^5qZRb&PAyVFds%NAmkWy&{t%FBIV zxu9vPd&2@zj%$$M6YY@@|EQGl%w9EukEnyfpHv8qSe%dtn`bXMG9`ZYq4SY_qc zTrSX>;)q|8gpD~EU$P3zoc5>V3VGND=dVSMQ@=~IL9)Y*G+#(wXRo&S(r`3(NBO3<4QPtC(h=9fXS_&NMZa^l)fz;+X`*TT78 zvv6jdw1n|AU4E^Jh4Hs-D$%BpK`!L6$yfe-ae=xo1*o#8R{qGj7QzzwHUs$_PpT-p*w*Ck5!2MEDz8 zKVZ=Jv_F`t>G?0Z+f&sj$-E5w`F0Yw>+?nrbY*bWZB#$;mY!x}C0f|t-rn8u;1?9U zTKA);5F6R}8}K{LgJ) z+nSjb?B+)B@tJax8NZDG-8cBFd~Gfxeiufn?wtmaTpLo>;O|c@9VO*2%-*>czPgC{ z`s_Z&^8z5%R6^<0v6stR_U}iveXykif`ui@>zmFV!xG>atSdD8{ER+}xJY>_Re2u8 zV1>NZao9$Al)T8Mp&SayW6b{E0s|Ou&AKmMaG2gTLv$FEJ07EH6G-k;M88aHYOtL; z@$eeBWE`1-dvgjkt&*S8D26oQl$b+`t+MNNm&eMw;&jHA{uwanp_|W2 zz}^4?!3fHZL)dDD?!z;>?!w;JM*p(G*Rb6cut-uC-nzc+a46H#2$(S6DW9|JZP07 z^k#IldKHhqgJB`rcNQRuz4Rm2dji0QcCa0ze=O}pn@(|RHm2lQ=A%tpaH_*N0qn!O z_SKNLsKb|M6si6T)q)uHAcwfTsSS49WN^A47;RAzz~bCZfseMTQby>HFOJfO_xQ;+ z>{K5{9Rp3`%|=a(k+h-nzMG^p8=LK+DdpeT(3|LesOffno8`5%a^}nk;$gj{)Ve%izm6du9!^`Wn$osBy|F&=G;QP!%ImSZ%a!TC)u{hv7r* z|8V}Na=N1Rvo@QKOwzLg%_bZjG{@8pR16 zasF}bbI_tjdGKFV4DIvIxFf|_vwg20>?&fhn%-h|#F`%R{7O{-xb35~0dONI(i@m8 z#L^jZKBiC*Vs7J}ZK}t?H@bq6E+DYw-&^ zef=(P#yH&61O~o50{H!z#aan};QA{%B%>szQ>36d4eKc@)%D~r`F_nE=>l)#_~#K(yT$(G-bQ)NCUuCZzh)gVK6w(Q zpHEF*ImYb^kDE`u#vXJR5`aTTP~_$~w`NZ6%tMF4m`vFgd#C!u+}M3cN;|oWuPerI zW01%H;3zj@o64-l0o>~rXK{GA`kF8A9Jw?jQs56|{xoxH7)TQV-o6oM#e5Ucl2c&% zOH&m+;7?`9Tn zH)_mPWsQ5rOxBNG0p>k0=qG<2IDX&&qux^S?L~>VW{UuxXM$VmK$4^!uB4XUE`@K+ zhuy#T(3c}YTaf3DR#rNmmTRTk?5i3E#H!EUDR(R$vsDR%a%?qke}u%BxGq+cyPMgx zFj_bun~KUtKJtt}L41yN8tTZPuJhv+nVUO5@{w8v{Qo19o<<0L)}2efFC9V$2iU%YvMd222sg&jGS72 zSQlP-m|>80jSGkK$4KY9VOC5MMUxR@2lA3K(@BC16Z{ z#D|OgI+ctq+(Euj-E@Od^iJfJ)7MU})TQB#FBufe5sU{iG#wKiEPAHZXQ*Q_GHR4> z_(|O30pJE0g9W5{l1H`)CwQOC2V+BPQt)~1!IvWv$RDrz%UTM+%xyR~7fuUp-%rS0 z!FN%Z+)~gU$e)E_=uKiQ4-%kFfldvc_kkdWMPC8RnJz#hK4nGf5&}{N@f8}_gkZc| z)9sv&r+-(hrhJ*fuM6yRrzu^u(!r-;k{cLqx8FD1Z$v8jn^I>T)`0>!b%^4Q*d=yCRk_XEVIyjqWqDlfmzq#!JAb-;ny+$dFC%*K0t zf`X2~#=8-@Q5$o*#DIMLDy=O*{*|DHy1FYEq}hCM=s(g^C|h+rEDnu0w-m|OAXn|A zWlhFhUH5L~ao&!fTr4&y!(fRYF@?dhDeJvO7JS#Oq8LO1$7)pwh7XHR2!BpOn_wBE ze2(J{V$nv`{V^#q(&fx=RK3Qb{Dx{LKTNP+mao+%SUpDJ7_0FYJEq;&Uth1O15kU^sP-`0v;b0X_1YFs{Naqn<=$49<_`g z<)ZmG>7D)3((V8Um$0wJ>Hwy0TOOyU9}+N%%|!5%pDJuJE7N95;& z$SfLEsX^+QW_AfQrIAIvo&4EuTm(C82*^(rfOP-nh@FeS)DJAad= z*iIce@FpdFf;RIZBQ6q{Cv8N>?sq>hOpJVOL?-@4<;7Y4&?fN2$e8)Af7n#@6(9w$ z9=js<%1M5Q@3samjjkmW?(|b+*rq{?Z2V-`XP~5mLjL}ENuI--wZbh%Gv^RG79qhY zIj$Rtakq#?=QOiC;`YEhu;`4M|Ixz61>4}wSO(w1Ce`^+0>O}1(pez6!csl)4E99x z-cJ=T$azZ9rkEU9E&2Bc`+blgm#~70Ho010&C?lNCg-AJ?ub-)ZJd;0iRogozp>Ge zQ;VOvNWkbJFYYQplo)6BotOArZZ7fU7!X8Q-r^g+1DS*0Y|)a`wuT}a8@J9HcDmgU z9XqD7z}?g_0#=g3#wb~TLq29@vRf^(W3J-Ay>z(kNbzRTRrg}BqCSr;N zCdc((@%P)aYa+LD?=t>!!^a<|Yc-+onJamOf4D?#xllAq_W`Y9H_Gx0^74jSzkapb z=pmJ@=mJZ6z!Jvfm~r<(l*M0%bEo`Xvg`*t%s@pnRS&8d2ouUG5Qa_MQO@&rC`9ldoQz4z}X;Fq)># zU*C4mx@H5=Ax4|9ozaC}0xFd78UC_&j#Wb@T`;GBK`loV}dr!!x&oYL-pVm%rh4xC@p7 zHPnRDaBjcKbrrB0AErL2I8s@8@NLVp?G>73J8K`ZUpA7wS|%`3xz<MAj zd(dHY+p&NUR!<1)WAz1!($?xbfd$dg4>eXCohq5A!XaZ-s)s+>0kchNJ@*)h3r(O< zkq_KH)KePBjU+mVq>yc0H~r7>(FskD1n4lGL0~VhyGQ zDeA@^UQ_!~G}(BGV5wzYJ%+u*{0bs?J&Lj&I%&#cpuZm#E#{Z+=NwdB>1DD+=XZ0C zFXjC?l6+pz!hkT#NCika`_mH?`4Pzrwc=Af;dZlzuMqRB#=uc~-p5~jQOeJ|AwL8Y zi$@a@Dl`*u@T^4S?^hZ;WapKVOgz*F<$-8jUFMz%gJt$VowpW$-DA)bJYLF5_8BI= z9V|RzKj@h3d%46r(Pz?NydLoJQCuvY7&&fVAAIz6Yl(M3qd{k++oS(IH#z~|w-hl~ z0*Ac8!Xlv9>(%VtZ$#+>qdmG&gVWq)f!g*v1<{(dtt(fC@Iq}-*`JQuFXbu^$RtUy zUN&b!>JsN~)Y~n>f0!j`lgBuS^=5HSDF%Gv0Gd}DK1R>KB}EVgZ9>t^Dhid=qR3%8 z$r6H+x$YFlZ-Nq12ogKUxTWQ6ec8?U)V@f|`rJ803fNGj^RZG0&z_ z1E7f85Ac;aIT=&%%U)uje(UV)ql{iF`8|fSxjfBr;gD-?bDC{I-akX z4_A%6ohVFd+(TL@09NDt4Bpy3Na<`hl8@|WexuC1snGy$f9Q*w4!LMI;>^!HC3!g$ zjty$ozwfC^!Wgni{L_uTlN0){Gay|ND+rk_wX$;2Ef9$$@)4@PY#v?4hZ2KHH&e~~ zpUIA1S9aSAfg-7azdl$f&l$)P?4@MPn9M82e7zj_nw`i_Qo)Z;D$pVmJ-`#37ZwGj z&>xJ^J5P$1spS?H&k<-;wASthNJL3 zmM&b4wqst{tmN2hpj3F7F_1CCr>VMLvgq6BhaaxFNsa%YzL)j-me!Wv zUpiM|4 zUE;HJrAIf-K}5RFYpT}gf!S$B;*LYC`mI(+(^*Y(e{tjdKvyr_tof?hB=ed(WAyTH z&K;_D)T)7qGhPBeFv3>h^jHKnTAs@&ZMr|@%q@DxFCWeRM8PXU3MCHI1zbT@#nye> zY1*4$(Qp3LkTZ)2Um;P>Q|kil`>DZIa5PY|08O4XNBdfgymjVZX;S{rn#nux$e{S{ zB}x6tmdwIul=#TE*U7pULDrXa$^S@|E3yzLdc7N*>K{i; z0~VL^9?Rv_-f0u0O=~ca>=@T=R5O&US^J7t2xI6}7oFOrAraj4is0!-bb$naR2&BV z{^|j4MGbLl=7A@+$OoZYnEh!7cpZTM#R|Sa$*@JtVSgVVelJvmT?b{w#H;5y-{_%( z#P~0m$dUfi8CY;%dCf+s^B&zp(5;M9Ei;jFj?9QI(1P9)uTi?Ym&VvyAWz3A{K*Fe z+B-OoIWKR6?30s_10Q;ezbv}}G`wVIvouw&xFLkSa0hSR1s@;v7NB)47dJ08!~h=x zaETysrkPb~WLas`OX8Ew$U8*!POr&MC0@|uU)lp@chHd!t#l-FHW5i%k5uz(H>y^B zkU^2r8e@{Gm?lwC`gY-S9ehxno_tdQBsj*id9KwPcB+<_;$wwY(!s3KuWzmS<2fkut9_+<3D4s2rckYUm^cc_lK>V2O&$}f@ zKzf^tRB~7094zRUk-At-?wG)OMDfC2o!BWAD@^)FM|*15oS_#+DY+U$PBFFsuLve4 zj7O|8=LI=srN|k*Hu5<>PEG;Ge0X5ZYoqo~r6Dv9Y(Vww-c6)uyOj06*nXS3+JO0< z$Ua=x6u1MxEWo7}@am?;RkOcKzQJDWNkKYP4_y} zu6xVQ>fU#)I_YPmxpIov`p_`Z+VPMUy4k)?j1QX8284956We48yT=S5aO3M6xu6|U zo5@V*^$Xt_tJ>z2vL9RKH%XZZ1$^_7lUV$#AF#*zTD)bFs$mU$aE4F#Q@`*}QWxPX zM$sJ3LH|g>e4`E9VY&TH$tc%38SaPNV;t9WmSM_aOres-n>stC*wVQR|A#lP2=CNC zQGnPugvqgeev@tt5-RBE2`W=1M+?gQWWEAOM44WOf!2$vV)wxua1$f>r=xI0Nimp?G#&{X~$ z{Z4@w>@q4fosjX{Xzb&PIRNCWX-Z69lat{JeDIWLHLf=e5f2Vk9}E@zG0Lr(Mkx6g zYlRpH&u$=l`nVDdD{vOxZQOAyTIty->X{_1{!NX4k6)CA?Ygow2lJ)KUoWKs7I3ai zV;`t3dqGn|m|Mb=B%$d^+fQhA6zAttY}Tnd%D^`wp&C`B{zCWdx3elno|D4l_wKdf zDN`eL-Nq6ajEDc$ZfI#%+{ z@Z-8hv9nBbF&*z2i3=HG*SJ%^JG;5U!&?X$|4!;j0eHO0pNPtos^+~Oz`t~S*m2*) z&I7mXl4`1|R+($=T-@YtS#D%dth%Or>2Su5G)8|sQ&w=1_tXHFU_{rQAB2}=wwMz= z@QYON3HRY|R9$3t^iK}vq$b2D;PTG7H2$P}dxvFJWPR@;R!YDiOo_}+mTvK)wthiQ9cq%>9^7_7s zdBey9e5X=5*#!7ndyp6DXv*j=G8LN*#P7wrxsCkZ-bPgO3*Z%k+lN5~GnkK0D25Wo zC#QSNthf z$}JeX3)H{=mS(S}91=@D$%->LfhecRI5dXncY;_h$C0d@vi`Jkj?r9*aM6a_2o<#Y zrQn(&6w@;j&ynDBrJ9eHy|ywJhA=)YMLyHA?#W(O>@6R8$+%Cr5#!940_`aN!&GAA z8E2;)>il)8@m$7VJoN%eWL z(+ZHg)-`7M^c9lBt@wYIGyzct@O7)vW|$>0irn_{sr18q|ahOO?cD>m$o>Lv^=ePbTyLm&!mtr^ll0?x};v9_|;RJ~HIK8s8+QC3yn22xO5ql=QIWDl+GxV9k>Rc&omgY$_vVPjO*5^)$-zZ zlM~;L9FfVOIO3j(=JwH5D^`Zjk14fpSpKth#nRoLLE8|<3C&Cef5Gyc15h6yf9NF8 z`kN(NH~6gZlIF|a3cHP(DGqV3d?tyW=Hnwn*K;fU-o8r8&E3f(-0rvgBk|6perSe7 zLT(rw{qf=J=|fdd{(cG;pR3(itD&YaHOaVtBFL0gE-rH%?~}b}H-W!Vw(|_XrYpXX zQ~h?rbfO3&UlH5hSPI*lb!;0als|3=f9|T$Lfh7BS{q<1f8P4oNFPGR3q8piV`z4* z*Y=r6Jb+%aygBx6JHlSSxI}TzQt})0q=VMWv~}d7LW5%7i>AFZYRc}D&mJ`B=M9`kRaS&rdJ|y8 zQ_7+-jR(0iPo2<`;|x%pvcdLy%e8KJ4l=Yarqe!2aw8y~+B`5!9QgI#`i2t;R%ze|8Bm1OEPn zn@g!VKylauzh5Wm^W(j5Y)T}R#1yEjaV{>p_2;DOgJeV~4{*a8T;AfODZyj%_Zyma zzm%%3^vRyIysBz6>>A5N#{=iOw`2ZU+gn)qZ$i}nXJYWymK8+TvhSjMbf4ZzkxU1K z;oT@(v0D~&-9X6rk&N#-3jan?!mCMq`Nwhk38tcEoyt~ol_1{ZP(Sk8d(`eGs(d?B zvvqd%p~HvE7|YN2@od*)w%TUEy_649FI;DGs`1^!h@3hwRl9m|fv8cIIV6qSkFzDX zzvI8h)|+%pgkoYbru4FK3fB8HpJ!yxr7mzOkjOzIZ?6C2smk@mV8vlWi%nd?zV+|9 zSL$S5HNCQ5PcbL`vT!uH8%$pyNd(g!A|uaC@li0W!c`KH{HZHOKOCSgvMtc{5tlp9OiXwu+?pBg0`AEh563tX$fFgX}KJuKlrweR~k1EOR?#(3)Fw|CQZpZ`D* z7xfGc&Zd4Xwhi{Ik=@w02en$GckS+c&^5Vu>B{)kOUB<0J=46?@6YT_NT}7U%)#dN z;4=Vt_Ir}#jJa8EgDYK7C36gpl#b++~+4FXPmt0LswQ5!ap<^ z=bT`IzPvTTi<@JY9rAbUc6P?TcVXyVZ`yU#m%DXSHSmP(s^&CwyZJo8^VV)JnYjp;?7n#oBr7hP~V`kNN z+<*vv1u)&1$qDop*h9f*;r~G~|3*CBEcevYjh7R}-KjY^AqZ}m3maqg-Ya;>Nh0*e zpyAtt{a|xvHxTXuevR=y>ewlcrXKyAyU^Lap1)*PiP#4U7G?X ze#37}o+QeWIIaeLr_qfj`-Ezrh_{uDSa)BU*F9noVGoMSpb(x;eI;w13;dBsSoB#> zu>ba}WbZ*j{b~3ZE9Ru^T)gg{gT&q_JB7wFGmVoy&$5R1Q{xW^%nnf!HHySw`&5wvUQS<;O@!q}+n&-t+b=~s6raTn~opGMccIG9Y1~>tJ^cbO%i;k-hY+UDBCv8ZxVXt zN&7S=Kj10J$g9VOe#Rbs9ULLQhuocrxX*4FX&Q6L9HKg<_*hq*z%KC%b0gbaD9T9D zb7H!u#2x40HvVIT6kL4;?pX+4_yAO1BZj@fHOx2e|7Vn%7}Wu+PA8WMaThMGNlBTL zQ2>7-7`MrfPokxoC=Mr|P)i$NYBMvnP2Ujrrzr9UA2bTBgD3N*CJ659*w2M{`X|}a zf26u|7U~dWS(0kFNm?4kTMqv94K6P^Xmy(|^Y zcnz1gD?6-WkHMi!ko+v60N!BUS2Tqv!H)LxeZ0$*_(a+wdV$hvYK;-`vaB2EITP_I zg($8BpLF8mhoH}+;5yMn3B}?HLJX!0+jlwr2Ihj&Dzk>L@)rP{P5RtVveh5{l} z)tHt0tsMS8Md#wr1#fT-hkX?% z?jdFX2|IrY0prpuO2xWriO<_q8x_NIKHW1&=VlsT)(=Nlh8Yj)3|Lv`HvZNvwtW=2b0_WB9%QJ9!-rxQ^rJp+pDu zlzI(+b5ia(eb-ic6a{)@tIuW+bzt|UXMl&C0KFhq#H)b7VxWE%*L*t+o0T(+;L4N+?90I54orn=RwGSpOS)ojrU_1 z2|G;$5XMnP0+F>0SBqNwwiT^}FG09x>l3N2f9mS#<*(9CUpcKEdryy}Ul)w*{2*&9 zVnGGBP~tmlL`5-Yru=8%HE0|54iKLCtl`CQ_kGY>wD# zkeC194X*jCPwpyGqzUozfPlMOM$n>yksl}6<^PZapGZ4hmH*pF5^=Jmz{OcC*+rQv zS!WBf>ORt_GJ>tlOYf-}YXP!>-Gs~<)G`eB z`VeZl$&{Y4u!8QZTY%YY9t>Bp{_yhu?jl?#+@WS!gxSl*M0>NTI=aInY28Kv=JA%$ zGc;<6=q177qGjV093(Zpi`lGgUQ>*zi3=xXdvGq;&XFw(<2#)s9rz>3&)!NB84%6; zK&yE^H;6N&MRlX>(&H(t&4Vb|dJ5NnOR$Hrxgg;>6RoP|Aqo1MZze3G%TF`KwXH+! zJUR68q<46!r!Yg3>Zr zx=5j(GV+Q9xe|$dI(-`W@S5zV;LlK?W})8%PJGTu^aZ!kHF4ZrOcIw>aDU{ zY>W)|S>GC4^^<0r@OK+rbb$LlNFqZhj`o)4+)9=e8fD>`59t(KOC&OYW735BEX#eW zDx&*F7H!=^dPywaneCNFc32AEgN$UWi21a)cT*2r2|hap0Q5)ai5IU`oq?YlS#GyH zO>n(PIlU+He}wbjP%I8=dYMqwlCnrQ&|A<2T}F*=#wY(zxDrZb!6DU@ zAS!O%LA2^B|F|_WI$Y(RjuMCPyGdqa^g!`ALtN@E3NzBToyo(lBIjn!5Ob)zdrlLY z9QIGJ*9qbM2M)HD^~(PK${`7)om|iw3>L?eqyLJBzt&7(l)kv>H`Gs?TBd(anXEH+ zK!UzFBD=l6ygX6&>W$s}`NZFA@C@qI-!ZOeW9I2gsoCfLxK{_8uN#cvNucvzQP6AI z8SM`km8_4s*qv9h16RKFYg=7R8VZdiA!qv?kG)QUX8z~-QTmtxU+_WpZcv`m<%qXA z%l~3bZ<&qME_J2x+YrKpTu`?(F;PER@8oowt~5`S$Gamiw% z83}2;yJ=`$w+0>5cN&gYk4$wtTBi(ELS-KhtsXrc@I%!Xf-*jR(Fy!DPLAcg3RWJ} zL-txmc0p@mmDgq|=g|EQ0H@dc&6aW(f}wz?{0id@KI1S0doex;_gMywUqF|KZx&F; zc##j>J?x0&< zhjIXGxUGFfxp4}(LZHj`85?2C5v_ulLpUp;x8DIB69V{D2QFKUlFP=8E^M#P7-xSg z%|{h2-2Au1iGxN}VcOqz1t>Ex88kgL_6J-mNO`77G$TFNrDi=BogQ^Wl=OhkH40;vf|teNN(NnGdX^b+7wChx~8 zssIVRg6skD^;gNNc(R&GK}0PGQUlan!FLKoJ^`d2uUc6L6zBz@*oP9oUw|XgL>y-@ zGKe+12fj#%t1vDNPq`d{TGp^U^qgO%tc1hdd`MnN04Bp$B+^@!e`-*?V(yx4Q^0p? zgS8?_BDIeFRZHR=Fm8xA*DIu%BgXy{gBucFM4}-d3ERKBxX7LXn|&Yif8(Cq%Mz~n zYouqtDMKHN8tO&v)48}1JE!rXX}URcVKa$e{jz0vJH}&1R|%$!3-ut$kHWW!Vxu%E z@O!i9-`~9apEzT!idQD3*rRZ14&e2*zJ8MC;mZj8Uj3Tj^=qhc#xd=FrBOWE*Bf@l z&|q?UnmDg|1%JTRH5*29v z7wzw4BE!*=?vc33nf`fXrU{puYEcZAIaUV0Jf{77jn>)In8E~ToxN+4_KAoq7oYEO zY+R2JW;^{*5x#iTY2|q7&el^+_HgELs~efc(#)olQ`3Js%ZA`>cO}S*_b7ZK3Yw5Z zsWbf3xq5Nul)*}zn-f{!&^pOzFqAFzEHnj=dI@fVKk+E}N*F!kBo0wxY_ygTTv;co z0<2acn)wQ~6ACTggyzM0Y;fN+t?zUt!BG#bhQ~99^?N7gZS>V5+4G>X+snegp^3)7 zVeUm}xW1`=oDp02hUYg3UWrBicOc`ljASCugb91X_$Kxlb7sZX)5Wuau2LK$I$`yS zjMW(Cs*aLfF2p~W3CE8?M*J0^SVzD7AQs`m0R=S7yK#4g5eqgjp6f2XWLfx<`dapo zZe8*a`Oa0mpd*(xY&w6Zmhyjh($W+c>Fpl~McTM63$Uz)K?YV=ye(DH77W9ih%y~R zQ2be+EAG_tNCZ+7Q0K%^=fW}aPjl|_=^q)|w`BLp^IdM*WlZpimU3f4!69ouD{{~Y zM$4g$V-^PkCrp9g76Dir^&AMRd(%-zvR|%*r5^(z~rS`Dp&%nQo0r&-)CVRj|p@}9HG02=A zXh8Vw2x4Ij9mQ2=C7>sJDU*Gg{hG%9k#YPnMm4fxs%}Y61R?r5f$ER%Rk~+@1%RB=N+7vtz0h)vn@?Yy_%0$OYkNzOr;{&z?B*1=`H?2NY@zr`j zZ$9!G7foI^{U8mwPilxk`1ltTH^O+HRBw?DRQT2i=Hw$8_>WcfxCbQ5kk^Fgx%$_7 zr6)xS%eC;@rAW9Bi?`nQ*?)bT^c0w1}|VhnY^f8=!8r7bL6JvP>MT~aCS zsCa z!7D5a31NhV6bw)prJ!J9Y>Jx5G((!3Q0%tcWsuxot_VbK;+>`OA3 zuTB49$d!Htvu6xIY8#Kv^9AH^w)3)z+ zf=HQ`?+gR*T^1-~(v@JesjIWUHE*e8oErPvqpUFNbw@WZ z$?EksX;~Gn&4@-TSqVgGaEqj$t04Q7#8|w=O*(4@*zpJH)s-!AQ&Jqn zktFe^Pccu?ZvMQjAQPvaV(9Q?2jHDi-A3Eu505Vld*yDt|4vyB?%q#J?F9-aZ6BRNx@+j`*Erk#f|vS2 zOR&g_#j-e$pEi!W$E-i@U0=e=Z-yZ!Bd_-ihZu`{G9$|!h8Sv&Gb9UNDtDh|ccmwN z4=UL$ST(!+(s}AGv)^>!RAbdkt4NbS7?vVc&uduQ7{Wj4#MljU=T5jt`$h`*@W}QgoKtZd@3F~`b2}jy~A&a7WdB6Bh)f+ZN}h=mL z!x*18K{CaI4lHt!Sr5|l9q9qCYG7Z&-d|t`irYyHv?FYPgIS&TN_UU5??2NSBXKzS z-@=lv9z_o7Ubcg`aPt}D=>3qYj>6bWitM4Y-b&9TnzWTz1FBoZj47nAl#bpsa0N?G zCAoH6Y;m=5sH(A%FC*Q7Df9TK>U2p7#eMN;UD=Q{;^k7YGK1#2 zvPq-+vK zR5dxQ&vbXBag|T0(z3iSD%H%Tv#FS&X~-8g2%|>@SDeI}g#7V5V(C{15N}whj64&= zmc*Z&xo|zVzilEN=A4H7+g_fW2!irb%{)W|^E_fdAgjxQkBXfW8~1wqGz??rgL5!p zFQO*fdutdAPFsY{^C(Ef(~TL(QjimSm@KZ*u_?)}vNDJaU>TW7q`l^*c9mJ^BQR#s z?odKeinLVND**%C)IG%YJLC`v!IUOi_1t&8&KK7+w zBN8x6oJC`|?6zmV4te1gBTeFZ3!qOc<6l_$=Th}uoV9hpHNxRR!4R=zBYTXR!gq(+;@{d|SNYVDbCg&-ooBO^pST(M z?q%NI?`0c2SL;1XV(qHmvzPPtgOhYf`4!?!tOU&%yV|e zr5_yAf_eypf zmL=O`Nr8Ut9BYsdHKZr8! ztblLQ%@{ZqIE%QFlK&i9;eZ`M?5$h)bBJD4x|`1IZ@$QJdd?RN%5!}ht>SFJcy5s1 zb~Of;eYAvAsz`BNDg1<4kMsh033m-kGBHMo|0z{O(&fF#Wm1uypcYCr?yA8XILQ)o zrQjc#>l=dUiV(oOcY3UmNfnr{v#w?5b+0--A{xapW1P=W&f;FAtY>?Lh4jfMeoITB zQ>O|QWv#)|0@Wy0iA{ZG8q;IoE!F&&con|M6t?|Z0s3pCb&FUZp7GoMK{fGVz0=ii z;M$g2?6U>v>=QjaG)g&$89=ZFMquV5e4bb`LyJm}1FznJ*L;u1txVz9E!0fcOb&i7 z=t#fuGH&GBbrZQ$He;aDF}PxQ_~7!Z3lc_LG4-Z-erVEMY^asyZX>KxTl!RxGw;(C z3yg9~pky;~4iN9+41(enwUT;seTA!;Ut2H|$%^cx75Xbp0)B(!g(yDrA+~k!9ly99 zuKp2^$---<*>d0W7kb!p3t5)x{lZZ&{+nl{iCtkgu*wh>&>}U0T-qM7)3Uclt{TBA z)Alo|Y1WCfT?*xb%@aH&;-WuEm@7PzLN`sMxryd6TzA8#BK4eI5@P(rlS=2HiK)-v ztI}$kmkHIC84x8+DPdVQ&-R&zv!Szg0*pCM%TjGErIgi@w2bTkpIM}%->^09RDop! zOjxYbgW-lWA<*UD@a1NDbhOk~+b@|~{*C<2F5K@FRQp1(+@OS@b0^BkQf;W5tL4!T zxfsjS8GB|gKjiXZKXL@yBu`Y)Z=@%GTOevCPv~*vI6U@R9mGHRDt!vo4GUPr@{cB7 zQWx1cCPU_IE0kzDFc*<7 zL91pQq?WYp39l(P+zCY^ghgOhvh*De#5Rvn^826g(nSO9|Lc}WZ6#SyC-foS`O%|? zs$G~8LmH-|kaefYepw2Bt&}#p!vzw85lOyX(x3mL89Cq{A+~43h&kl^s%>Ea&&FT6$^1iT;dRaO zCsGrd;yHQaGwBI?y5c;8b88ys&(U=N1Gw^4ghk`2`r?E7ogG7Y(lZ~kpn^lV4laI( z5FRI*xcQ*yOq#`u3|*cQb{8gShSd{wj=ZHOr(@N%drWrhz=B1rn=nUH;S4$^^oAZA z{f+@GTF~DTLQN8{d{%C)4OYu;Z;QN@_B<}UBs=O>jKqnvG`MnDt=F}y&_?lvNKJ?e&j}oy=lai|G+ie zg-xpn(I=o5%+0MGYh=ig-&3OOSdI2`D}wy7cB%IOQ|apEO?rwVuXPqALuU&$;E7VW zw^eXe~>I`ESOd zcK#C+kriDW#C)5ay!nqT+*JM$+=Jl>t_MVb54;10^Vi>L3(E1SQyZpc!s9B7s?GkwKs~ z6!X7bN%o5g%w=gAOV}AJb|DGu%Q{tiK+{J}@Oew8{p!S>E>*ao>d_%~Y^}-WZ_;q@ zELcGFU5diJXJpAMGpIhYm%iAHsbVpGbAyvhqA%_v{%+()kB`~>y2YAlmA-snnpbj; z81*Y|O4Zj|*9y}@;?*i`ui2;m@I)t^3LCToi>qo0KNbLvVY?CtM#@D(6>y-M%pEpYtf{$?qQlasjVC zVkpMwx;D$_e+;cFsA53FaM;7TS@0CPHO=F2?*9Oy+u_v`(J4)8DI0E}o8~D}w9CJx z&a?Mv(4OB@6~^^==nNg4UD^w*CS(BYNIfVHJG zOUW*M$Y6UV>>#EUq{DzIW?K%qc{HXQ82Wu>w!zfsC{q;Ho0`XywvC#aMtQ~M1TV$+ z6!<#^gUo^M$_c-nJJV}P*sn9$w$!6TBDhsIZO3e@s})4Cx{-R zj>zfg$_o5?U3{~Pw6&PH)fi4jF_MCnGkho9d)<+BhKA>hyD?ztwTBk14ls4mBvu)iC3JTiL9VKDL!F#H~J^5jGF4 zI!7vbZ29;tQfnD$VIY331r`=-n3wA@0qX%3BPeu)@yM0f-U+zt^K0-*dJBqF4OGgK zZz3t*cz&9xI?Bg%(xPpnyvWZ>@{_>Z^Xo%h#pG8!<*gNw6#&sV^i~4!X>SOjkyU=5raW^?DqjRIhAx&bPzL8+}eA%dlpvIF^ z$|1DG(~(OXL(DQ!DKk{x(_`Svwf{_Qxnr*a9m;9Au=w#bifT&{?QB_ii) zg89o3?8`^`b4eP9kNM+_<)G?T8&zpYb+ZdM3zPb`E0Pw-=O%q~N4C#L)*fq;RcrI# zkvvu|UofjA6&`aCE2ns|p7uT_C4cDmFY*7-UN>orw=oM3piGZrD#q0dmiN;6%|hYg z6l!b-B~sUPv#A>N=OPQ(UZ==+@GN-Aic_7a1_fIwq_fn+O1r!NY(iRev|EQxenCXLnu#~%oO^+FGbNYO?G6-1>g zYj7tlWI=Cvq8OITO{&8bYWc=9^0s#L28`r|#cxVKvn)R^5P5O=w!26^QOn-igMp47 z+Y)zOxBz?-5HmZ|5IEs!4a((z+O5kGuKLZ36|*;o@jLs{GQP50Qkn?vl*>*QNAb(g zGEbdEf~F-O-ukAXCBxQ*3l#+Ipc|A152r8!II!z z!{3`bV|W#xkE!7Hw|t+Zq5gEoZb=+agoO2f$6GV~&I?tHt{!rZ&!nc$L?rz_5zIR# z7Gq!!YS*atq9u6w6)4$2Y>uS!&@YmiriK>vD7j=OdKM#7i{H<~fLumuGHt+N5u#;r zwOfY2O1-QfkM7*f=MB8s zqd2n3+Q*G>uGCqOq7FIi!l~~-La8q=Xl+h$dgYzO2d3E@y;S7BS=ORnJ(mwyHM$S{ z*GODwPy#ejKE*dF$zDXQ1t+D~D+5lH$N5*Z$;@BLCIt+DbTl8%<;!ioi2_EO^e+CkcT%K~pu9kHUJ2 zi(dOT(5)1{_mi(3DSA?ci zuan*6%JgK7YD@bA-nEhpYs13z#J@(|P+=;yU7s9Xp=Rva0uWJXKKxhscw!!aR<>u4a>grEv(TXIy7~(sPM6j2i*dcRD{eH|^_&~+<@~7U> z##&PM{m2me*jBEMTyO~|>$!?5 z*3B;T(~ygXvt>_Axm

?|>wkf6PJgLGxC5sN>j&BbQ)B~kbrcW2n;Kd(u1S>i9f zFDBR$p!g)&{w={e45?^bZ)XS3OhRQI&L~iJJS2tdoUmR*dVdt-XOLASD@$2>aCiwU z@w=|;`kdfUVpLTffGs|Ixodb%p8~QpkDY-nF8Egc-K4?ZDeW(pt4D?yoYE0;#}T@$ z@2+gcBVXSP>Jde|R{r_a_HG#+_`Cu5I5wWAx4m}xq6*&(Pbr#l7n>02;vYC7w^0en z1NtV>o#2%M)zPbjoTGRUZX^Ly6 zU7is*_c$Yq(l2U)6i?3wu?b~eHcbwH{^t}IH0_bw)d@5 z!aaMxTex?&X}XTUA*xfP^p5$5x8(4z6YMY0r{eo?uR&HTn`bb%ziwpaUZGsKL`E`P z$^P;Ztj)M|eF>;9iXZrecJrhwJ)A&)+944@ix57mZfvf`?M(yAXi8u`glicGQ%~~M z0+KhfXBqqwsf-88_Me;??`6_VW?r}#$E!3AwlE{AQZr3`zR6sy0Towc@+$B%Nxt`C zVj(=5pN%ywsIliPUf|}{(U7?I%$V*E*&j3#w08a*a#xa0+6u)NY+^GD2{S<6J@!Rz z8I-=i!RmT6!&v%roCk*f^OYwa(BJ%b`CVTTPuB=j$S<6uFMjw*Qt!*LvCFQyw;XEw zA7(Lq%#qpYG+E0Yt`jcS3iNbZw!HvpUJS2j^^KBf4yn|Qq(zNMRJr%;0DBeiq#aEl zku*R@f;vQ{-IU)XbGeE+C}&l@rnir#OAWG~SZK#7`L87k8Li|C@xlM_TQCq{l3uBe zVZc^sdgOja=>(&+Y0%L|;OrzdVZmFC#9_LHN#IsU0w;E035!d38zQhq3kzRAZ$sH& zz8TKWCmwTIOptDb1A(u!x6@}B*rwCxCat^v{3~?P1z3{t0;;Zs`~KR=I(s48QvP9E zPGsMoZprVA`TLbwvz%xsx%gk;zcZDx`M*%asz)ZBqYyRbNq!@_G8jh5(azErPh{U1 zU6a_Q@Lc6M@QUW9c%!Y*;R$JD?j;+cP_myJLHnVUnU$DESQyQMM+cE(4oV+G#Xne- zUH-7XiDK5a8!|mgiDpQ*zHo6#nAad(2RtZIgV!8!R~wp8n9~PuauOW27oD5W`Ouwl zn(X&M6@m9ZF8fNke4Nib15}T@M_wau4gwo({ahhemxQ7s&`bijjx#`Urd6aAi*ndw z&Wa?1_MHR+rUbT45&SyE;%5%4!Tk-u7AZn96J4FXxf}%!JF}$MIns_ONGEub(UpTg z0}2;b)f3FH_%7L~08a4`UpuAe^^iUYNR7SIGHNMudy2zyD09#SGW1N`u?$lnKaC@M z+j431XeOX&F3!ltPWCVaxwUrde+)ZB{nDZUKWbLEytQeB*)<#Yg-ndT?8JB5FReYE z#qTG8j9HV6=(0rXVp&9}Gp@~5Uql8<_VyqI^KUivq;f~YfFxXc0xoxsmgy)lmx9G8 zZy0LGdjqB6i?Kx>zf^>mpG=S5nt0GWUjh!QoD-VvN5?jQ|1B&G?30q&Q8jq&^|&db7QTmD3syI6m7!egbu<~sbH zxmd71U#jC!#)2P+MK0hkLRll$%j>F99w?7?l+2md9P+a+AOL<<>o?bgd+)IHJD;IH$@_er&5)G+3<{Gp~)mVl-KVVj2p_ueQ|)B>#A#7Xn}{u zhj=CTU`n|dC;vl}lV*GP5^^GmR}Xl_#dTN2h2yX6L@u>eojl|N>bARvK!4A}pt%et zlN}0))9iA*?jl#hX+OlmgDc+#>^-WPs@5ql8szEMNsz;r;S?TMeu3dTj!K-+DT?m` z*bb%`mlM`+(g5ei6ISmOHP99W3Py7z{0<8zRUhtS-FTw3_ZM%l`@U~pHU!>kG7U@% zqz4;SO8SBG_toIK$Dqj#xPmE)Hu}KDeWzcoY{Ju2@RR-|G>WWc#=7rRxLJ;O8+F8dF?Ijdh_U{tzo>DVo z|DlQYQ#hiH*ZUTawz^Arz`S43zq-C?)<=m-f4cR}o7dDu^dVkvx)tGbFx`rFWs(&!q1@1iPiM;Ww0lVw4xaT8i&HeqXjoHy{B2Yd$iU9(h^Iqq-g+ zBxq1F+s{PcJ}+t1r0YfEnabMKp(eBfKa@UAE?-OWxc$1mS|{pue8lAF>AqI?TC`Gd zJqfx)7gyW01^ciw^;}}$*Yu!-zwE$+5XD=xR80#4NjJWd&c{I4meOUeuj%(E{{OED zKaV!$Ag;Lbe=ieVODX22yEUV^**)C0;=KD3X!&p1ahk{F2KaS#Qwuo|HvwkZ1n}Jv z;2v1($j**GN!mIH<7XJ$ejX_v3Gd8fSIJ0|RDg{>!eU(e3W7nE zY^wvO%9=?se1Yr^o6f6_rt_1gCEmdBBn@ zvP&k4=e#Q}ZbIW9IpG;LDw=Pagg;N)83S&h(`xpHA}4I;DtjA@@nC`p>t4e<{Iek| zF2~W4q5Nhb>@ffE0l%eIB7O1eVf#(l1eJ(UiLLM8DbX zX5j>P?1_n!!@yd#DtTxfxm%YMc$8uqLUJ2}3UE#(8lXsnJJJX>Fyyai4@7w*Ka|Mb zSsoRQ^lSq#{jSshH-UCE6SfU$->(t;FlZkoWyYaZFA+4u+0@UHVxPBRKFNoV;kG_< z>Y?`Esk3Eh)s{t^{6yfcp@lymL%F`gNCXDnkth0iu_bze-xIdaAakzjz1;^j8DpST z#(O7dD|Rf0WejTPKVj_AIKc&tMtbX}8P|U&4~=3(t{KwhS7=UINu(9->REMui` zjVadePCzpb$t~LsATnHp7D?8ag`$AA7-y0KhwGQTCkMZKWpC9~S557@);Rrjtg|fU z%bGQ7B&d$v3Es%?C_tNw9Q4)EDJtgQjOmt+u7Lk^!SgxDHZ@G00{|5gi8OKSK74bo zP``hswfHyuuCfaco8y$@WtH%(KWtD5Stv3nC6bx+;m!^={G2ebA=(^3)*f)YQ6A9~ zzPlISvKr{m2!Q%m#i43&a{#y|-1~bmTrJh8^a(-Qpx5?m$;8q!)7WKWk^y^*j2q_p z9pvrj=op{-{4CKh>CAH!wjYokH1jY7Ki#26Db&5w&Gi$hxjilS`s*CJUdGL|T`J?n zYXHLqrsYv_Z*L`#R=XTAq<=LX`WRmkG2K{SH8M2M=>i%;II^{Bak`OUQ#`Wx3Utm{ zST&U{P`SI;U2>GH>wYfp9hkYEE}gfzL^LdF&|2_p$EoLgP9ouI!u?C4Y~aEJp! znO~ypogNudR~;&k;i=HE{&|ICHB;w%FF`N**z=veYx7=70x;zuK6R3JfNV zIy#(WC5tPAyHF#3a|nX8;h?>r1?|`mj z+WU_m13&om6zFzFME_T8=L`SL9AlIW0Y3vElNC+nX;HjlFAa(qG0%wvWEJ@-)uaa5Qb=3}q3m*Ilag4sB z$v(>pu#rTNpLPg1&axEkxtO|TKVOuBXR6XvLKv;CSvO7&^z_#f-v*|svh~(+n;{lD`#j$ z#eow)4%RQgQ1*T3-_#yW=e_F_6)%pT?VDGk6|uV~q<^FCmBX&O?VFA@ObwA2_cD~L z4B+oxwlwhyEFc-;${CpDAdF0$Th;pp8>bTCO@`9jOM371J61}FJ5gHH=~3zk?!>XE zG~eK*7%r-}ztcSigOhl9BdiOs5L~B_{@kbB@gAY*y>Q<>ZH#sk7S zATEr;d2Q~uj~$!RChMN!e(d~h*OYc(a%)3_=G!~lb@4$< z9d494vcbiNV0{(V&6%cooE;q+D$9dGwRFj%b;2jwfeUbf-x_P0weNQdHm#I7xYLxg z1B8#&6Fj5{YV9(O;#I^?kNxV;bnN!@nEY~b+G4%~VE6GOr2k5io%b?sbg+82kjLlq zv(6@`p2KdoSUWC0|0=Cl+swnmW3oPrlpyZ%0Mc;9_l4HibMnH4F&bRpT`!$qRQ z>B+xij-3%2c3n*Fj#6pgdy}U_s8uBzPFk1fgiflbKVLCnXrc`t**Ayye@E+;>cu5% z>!)YjIb0J?=fw5m^z{M7%0DK*^714`QhyuKOJ+!k6Lc_MFEg78Von1Hc_6v=Y5K3&nUa(x)nq{Ap)IVBhY@hNzAGkO%>KK9~KZK>lP6h90X` zRXV@-l{`!+%EE8$My1VhA=dBn7{PzGm;bKL-jR@J^jh=ipgQy`;fvKU`9me3eHf&r zv4>Dq+~NNEXQ{q>-&)TP`8xe|eLy9;3%IcvpW}cZT&*S-O$a;*AN~s`G1lYAar`z7 zTD4pw+b2bVv&X{~uyN$poF5GRLZWiKqv5t~_YHZ|gocU6c=rUOQhPtd$4nQf(?-2c zj!u40{MD(mW}As^>T!|J!%Cr=7!^!=ye7v>1NHsSUk7or=8m4QPMJ^z4-Gt?-s(@0 zM5s$p)y*xPgbHWsNp%hBw@6J+t}e!eYF=^iN4pJvHT~nZ@N|D5FFs+7m0#i@o6{jV zKQys)$@W94AtqwDLm}OmmxY$ko_h6qyxDVI-j9|U+p})vi;v|_)Jd=rEj9NCn z;@>OPlsFv%9XpYpUdhaN?Z|DU$U~+!Ccuyi2@jbX&ovQm?--N}`slNZ-U|59XBkIK|$&NRxW*w-fdQ zpIPQZ%kBApkwGu+6K@xESx0djL@g5u8OySO?MM0AQY{z0F)PhCO?lNb^3_@3mVVc3 zawl+@K?~JQ0Y1eDlaBb_e9|bX53~d~yBDTFb~=d@mKL?D)Ezp%T3}P6%K$G@HT$_8 zYa@fZ>^V2XSj%gG`sgb=@)X9De7k=a?&+(Kf`WtGM+cooOCD7bv|)3nw?OvuGnOu_{N{SJGVdn^3O{aPijzgjUzp(2w-GPYhQEM zOt6g=XwS+jr-XC>cQ!H1@-VLOA6!u~S%>y8;VnlbTmH8#Z@F0;FYjeFnwzbj2hJ2% z+R1)+35zMQfq18fC2CdcRPF|4Z)NaxhoSOG`0&O-Y{V#F+4;(r{Dwh2Ny1&K;`9f20LP=w!2cnCY%OxF3<}wY8#lY3yjP43~{3J!0Oy(>%Gz_B>X}6{*w> z^-Z{_sbLYtS#$vHooa?v>HVF&isGpB{^)1VepjL!=f%st>H&`T` zskMMrc@w|#3iSQEcT~1Mvdvr}o#BY&wAhmQkyX}C?vK=J$Lf~q$#WNm$f8sWk?Mwl znCUvfI5Lc$sa*f^!zYimBOfCgV&FY=3;csxz?xT7EP);`t6SX95@|K==yYK}D|zr} zCBjWqthat2Qz`wo967ui4z~tp+)0|&%Jro!Z>RL;nT5jiGYIz1(k(hP#Hf#6HXt%*b4f-}#dI z+-=Np5+NfN)`hFIu1AyEy^N)`se0|Jf%DYI2TJgrak5Kn6j(2vdV}}U))5<-f}+2p z45sLUZb=>~XD{BogP2A&RQl?13#TaCS%mTdGq1?%*L2oC+_`I?Ez=w$ByH5sH53GfVl+Rvar7&-{#BgMjagy-xua z!3jySfBC{~B}O(3{);&l1<+h0K@Y*%a1LQ3#(gw`UoIDlQ1X5dtp>_7&z{rwkONVh z&OGKu+W6pKq`^NYnqN%74M7OySLXV>N$D1Vp=^IzdH;S6JZ&QWOXEf!n#yB$LuSV@ zKhK9_gim8;Vobm_a#R7Ei}o`HzFWvVyCJd^qt-?pm;T3NNjq`zF5Cn07zRndq+^O4 zyU;Zq$54h(tmX;SUp6?i5Vj_~C zBL~DmTN7R42w!f?ir|Rii8HBEwd1{}^@E2V(1K$Y+m4L$TCom*-aO$Zde#qq*y6(5 z;AZW>cE?#s_HNF<4yKS9`=q;!5*u_!KOczX92Hq6pS zyojO=L*4A(IjhesGTC8mQ!&Mv)Q~&#x@GS%9fSC=ZQ*<35e<}^wb*~~j=Hb=o$I#m z`rPggp6UOuLJi~JOHg=sG%Dz|uhbkYDG7dgbWMW~T83LJvLJdCwrEE45XbMFHbeDd z@tW~wiL@2P=xFo2pwD{3o<_>265#hhqJQV|pIpCJ=mb*?8##h?ZHp(YHg7Sqn$$qG z`z${%^Xtyh>smI=;8^Zk!V+96K`l5BWQIT3);A@l#qN0?Up)PLxU_EfjO*Jh$)X zZ^b7VH-?tljh#EK?J)xGiPjL^SVmQcs%7dgXuPT?tVb^TaqI4#9`;f<#FZ>M04NT3 znPS~M=8(CEy_PANu+>ENZ@Welu9_Gh>%4AqWPW+XHq+3|R#ZmZD7)vM1%v;|CZXudLL_4Mi}>-c z{S$DQMH7$zk|lg>>UtIz)l8+*z?FZb_i}?P(74el6J+mFa|bsE9UVi(UJs$wC{3Qo zJO#aYktMM04S;_Y!@C#Cu&K)@9%K6sQhA7Oz;;~3x-7vw6h}TT|F})==^^D@f_nnZ zb%a=)+|y_?1vq*ch+&5U&1A5jR%QFb2;TI66rK4)OyB><@7y~}%QPb`(>^5%r4^+{ zGb0jG*+Xhd8=^rpNOPx*|6qD55F-M97`Sjeev4=^lkanjEjU87A3Ego)(J=X7PO(Mg#5e$|{xmZNpo7nkjl! z{o--H5L1>@cdNmyXhLzLG!Ci!#p#B?0IHH?-ymA40_b{_Jh~RkN2{g!PY2^q*DsaI zRPwU-(~lR(O^#?gm?WGU?^HZyBCg@0RE>G_*PJ^$tgWdztgV5^C`*MCgcf0US2%;A z2ebt(m}B+6yj-g^(?W8e;Oo=T(k5W|Y4t#>R-qf4K85 z{2hED6^ibuDDrReLYazuY|x?YvbwyUSO^*$!Fb?ET`{8V{a4vZdPgS|1J|x6A{TRng^1NHHgaVBVN`$iC&wt1=Ys#}49T>P z`_tobsz=ZM;@2?uZ&KGHg6$~$;I1(zWn7pYgvpDX&${eCpkhQ%9^3s<$?$ACC{@=D zLy~z+_xN%!cs1C1TWKnIY{RceRxyh%Q#LI|2zKZ!MwGna@}Tf^X}J4mudMu4K&ePa zFtfLPuwE+M@1*_{rG8gV9PM;O3oK3fduQtXP%w{eJG#+J)vX>im+ToawHtA>ITh-M zPDSzjqr<@~>exYSO{?N)$=)k{jK$+|Qm!qps0g@=2noF_&}3gjADQ|;tRA`Odc_f2 z-lmk6@*pe@O{DUNH6(zh`<3y09rxzROPRkYu8L3^O%~z8_sRWht2aWA>dKF;7r(ZI zqfK|Lp1{=^+y^Xf zuZp|lXrNc8x?HZ3Jv(=4OHE=#6lCEAvU1aO1sB}-5hwq;@port{PTgE%3?ZpOr(RU6diKl{a z^{8DbH=B`od6u=lljy}yC(A=b%eSmsZNQ>EJnd^?-YWx*dk|eor_Qc0C#QRwFUce~~yP=)Q@$vpXkg?eARt20E31|IF`FCf> zIO4>iwVYF;)DLR!`#5W40tMG9Q5E$eJ?IP9H`M?h33`$$(|CO*Ux3lsl$85r zGi`g>ykq7P<*6Koz|XX@J0R5kWsv9#k-xvqG0+S+*6s*XAhklr>?n!Wh=BvG(4zqI zqb~%9Pk(rnMhd`4Cj7t(+l5MSvC0cdS9Ig!pI!=sj@_jXie3vvJ9ZF4ytW!=9}5`J z0s}?}7PRv6+FG4$`<3ecI%cR42Zl7SX2Gl0FF&1s>AK2O{tl++i_UEmv~0Z{IlD8{ zK(t^V@7OP9!8G7t7k<+0j`p^d!-MQUxnXi5-p7$bXWa#NJxTU);$|6P40ZL9_~B*c zcSDb`Ay-F!j8RKw6mj2XibJC=(7m>L864^@-ZJv z#8PuEnYP@ZoPVr@;%bOvYeWk+p`W$13)ni_YDdoe9i%%vPsfBqMNbbN$Eua4?#qGQ zB4;>`{%dl)yRG)z`&x^>KIEGf%J~{_1w%ERd3P<07-%M#iyUstk%B@PS}FE-cfv;~ z{#SuK_bQ-J*FAz*R-_K6d@b9O$Nrw^+huLr-Gs%87cVaOi$RhHdJ>;Y*E<=M$}L?| zKP*X8xwCt+QG*p}Dv0QalZ#^029uO!%2CyG*9wu3qp??VV?2?TVY=j;iM828(C?6LMdo#1G`dix&1># zcNVX^ixKm3j;q4)olwe2@PseSR!lG@C6~LQ$_EG2mv||s8dP+_XJxHJ6 zI1~l${R*8|Own7{ zhEF$XnLMKOlK=<3sp<;wyCK!gha7j2@Gy_KU|%FDGzKbc;q1v*rx+fDMx0zDpe47` zY1=+bFk5vZiEeSmR(oTrRr0JJ&gBm!Ra{n?WQ_r`aOr+Zc-5zJQ+Jak5_)I6*FsgjJ8r7u+#=|cjz|ToQ zKQMSaHFp_k_9Tq_We_=JU!SH_DurdLql(En+Me`OOS+qzXVJrV3uY68$5bPvf^(GI zpON4qeNo+WfoKt-=o~qh92Ff!I=@g&3ONb(ol-}I@BH=q4|!7u)epJ!9rmn;?~Y@W zBiil@l>HQWh`u3e%tZ`1VlZO}dn{$=OOIF7HzIY_>Y=*)iTrlebTx@(OLNZ~>FjVr z=Mto1d2@THh_NO~HQDJ_6!89?da8r6+3-;)yR5x5X@gHl2**tFCs`-n`hyVFTFY|@ zC(si_Zn|8>8Sx2>9>3lpW`>5li{|%w%``3+`MJG>DAnjU}@4J>|(m3S8#y@TeVZxRHkK#Lg-4U>7@?DXe_r%wGIyA-WnI$1HH8$99d9|&0dZ!G;+@3|M$ z{3NB`WF@gQ4qEt5MTz%twVoXHlg)Rt>B>)QMW(`J!vyqIt+f3cChP{+B80oadHbDB zzwgHI$VazrW!|cDC)!>2tBC%Dl;>pN5}m)dC;IT6fMk1R*Xrl%%Is%W2-!mR>zJuJ zXv+$C4T+6)0E??I7toeG$yUB84ZoCfiDt^==MwNrcWOv|Iq|FzTm|Qj$ zZ5no@MWKsg*()THIa7JTFXs?ge|oiJs^1Ic?nb6PvV*c&6HKJs(cz9;E;hADzbr{G z(J>VLi0+5HJu5B`3)~zIT4-TdM-y|9?NWOni9mDNj~D6UMHu5kG4# z#23|hhDFiLjdhN#(OnUV-jcmFe;s;VVm&?VNGbYE+8aS$tSi!+g?#ys>bS4Hd;KRh zfodzmH{}IY7)JlNHi{g#7Pe zr8Z3&&;GgK{Bd*9PJN;DxMJ+jPu20ep?R;>mo9eB`H^2$yC7hy44QDk-6U!1L8d%D zf-E!F6ce0ff=M;xme=yE}dn4$p<(2zXy;5x^E0SQ$~4 zyjY-4SvW451LzgKiY9guWO?mujPv4!ONt>wI7;pi_PRlteSYX~vjZVS>swaT`Ps%+-!gQ+`(yO}g-XaVD*3{+lgqLP zw4fWbYZp$sJ>*u(WgBkcKD#Y>t{l?=>Wp)i*9pF>aCM)WY&LIa82r4{IFOtszOE&{ zhtsDsTW`vD`+fMU@GzF28wdEF}GhtIbo;o0hxZnCrP&x=+ekneCLQQg+sLHSs z?1Io5plyxTeN&OBmb#j68Gy48MdCdL+y&31*oh#)qrTR+z)n6?fVAf~6b}_QDE5L6N zzN+_@#M0?WCZ$KiMs_qfFooQLcRO2bCz^@CJ6Jvyla4$oHH^Gm^6Ar}FOe6oL}lo{ zQup4FVZ$sD8yB>^HYV`M{_P{oEYU>?Dt}Q9K*pD&R`QLpMPTuQ`W;Eti z-N&~oL!g~|UC_es~)G96m&3pL&=XElMO2z&fh!c*{zr_4Gzq0_3~=a*hI8GCt3M*ihVI^V8voX5;=K$P4>+!!oYO$ct#>X8ptO~$ zA{th;f0U~iI=6h(5S8+oC)nsqx3I4rGL9|!Ox{+~_k}7(;#<9fgOt9x@YBUm!~@ruC!V*Kld=xuW_Ox# zn}~Lt(=5mPRQ9U9U)f?eZiZ6RPTw#J{HwEcK>NPd_qHf_%m7L8p%y*8qU-UVXtC~u zMzVMMFLvMOI~aymI#TU3)D*!d6UqE!`#?AwkDq_Lt@$MR7ki_%jAkQGHmu2Y4KhrW z=|^}Nt-tFG?{Nt1L^Rr-A{yodkS>IJEoz?8(fg#9$CT><;5G(zcP9aF$fyxjCP`1c zG=3>*A-=rIE={T(9{iygY%fbvsDAylMSV6H?{Y?OeNea!N{$HN9Z15U7jVg3&YOX5 zjb#hpsK+MU z!jNdzvy%`oAm@)jZeS8dF4Xc8V&f}=m;b9RRv?-HFG;0i7N{H<9IvF{pwWizKZJcQ z!1zMpaZD%I7VuX>6F2w64uuhukViC8(tL$f@@(e98a4Bk+>rHH^E*@Pks4WK8?%~W zv^ZIsO=Geafc7=Gb7kQO9j~;b1qa`=x__}#IMLi8OZy^Gb7va-8t+^7k=onySyU;8 zb*YAG?Y|BlsNRO&aSt3ZVO_@88^D$g*t8~#tX;C=gl;PcJiMFIy1E8n9f!>Y%M6Qp zC>D2Szn9moZY`emw-~5q1pcZATXV+qxhJ~mgAG4ZTSTh=+Qozr`B4qD0m91o%HhJg zh=p4OwWC86xsN%yD_w{ssis2g*!|OI&IPl}4hq9?FEo!AH8qmn-`n0xxv*8R8_WTb z$R4DG*lIe-`1tJ99|JNOUH{>6pWovZ>LURTYMVM$#?&JIIe_n?tSmA3FBC7sWcCnE zit?|j6Ms!AR$r@4SYN*73^r&SGOX@Ce6QwP-iL~@b7u^m?xiLE(s9yE5@N=Zf4YKM z8vHP4J{?WCBK1pbpIWpe_wFVqzNWLhIH`Mjw5#byKGlqxmW$jY4m7bN){ZhAmQ+ln zpS+~WG6U{E;5@`tXg87f%!LaygR|v~+$Bbq#^Ayg#D-~f#L%ib{GfADsr&IWBmMLX zQe^K8v3UgYHjN#G9Y>=-`;SJrTuvMvT)=NVX)ICXh%^NwW~QGQfBC&7Mv8Pc8~aTh z@P!6jNf)MF2@L0_E^yqllYUEqH5s9P!m<%3HL0A`Feaf(3`NK zdFZ&RNyE$zTF^*x_!tfhfS3|}oBx7ov;tmBkoD0i{MDqM$3ofpX4JIhY7!w@JZUs# z7P)CSC9W8HUB`w^7jKm+Kl-LEdEGy&+ScYUn#4h0Ee%|xWVl<@4fo^SS&mEeiDfEi za7ayQn@`F!r#w0XF7SiWnUGzeJd7oD56XUdWC0dSxZJG=X+-}-(hO$6K zW_%N;oKSN+7%nI9l^3I%84+XN8lPH%a#z?&;_+IIC&o9# zs7DwN0Uq8Hf1y@6Qu)As=ZF?&BFHFd#pq|ZoyMK1T0rm-czP9jR%0$seJlYN)Pi>* z)Ym4a+ct)$6Z8{I-Hz{%#zRi0Zh%v_rsb?Wot|?(M^!lyigU}5&-D{md#Wb}MU_HC zt$OViDpXHSK|#4f%3_)t-yyJ%O{Jyebd*sWTH?ES9i%Jq_(_V54t2bKfxJJZ*U}AG zEG3qzbW7GF3g3FKg>SJ^Q->k>`y^p1wy2i$+pQT*}_{#3&{fx&B-wnZ`! zFzQmY?5Ssug&ulCWsmAvBR?1OR;t=V9eCH}!#}a+!Ecy%qaoqhDfovUo3DI+HbH17 z&&kebUfsd9I|?i6;J;UMRCQQ;JEjI~_Q<|-1HlNQdE8WLwz*`OUNWX1z) zY@9i-^EfN~#1>0?a$gQAV};iv4sHLvkFc_JtC}z|0lsdlN9Ij3k*6s`{pVNqN!2NP zHuNkxjW~xJuCQ9X{fljGC`Rxki!%qr2QR@tC}qlNo^IcvmwOiR558rhT7EV%6V`LA zK7D|N|1;USvSr@lwa$fDd2<@)f+UQ%_9+xiDm$QNBz(pm&zU?srASyDV#qrqzm&P^ zDmIbRjtB5f58}WEK^+Z*m`z{kPCs;{;@Fwc!4>XhV0v<7WT!4qSwS{=OX(TAzQYT?&7 z`~3DK=)z;jHX8oH46-~P8u*-$S3Y!I$7q2N`C=QBwYb!ZW(9bi&LwkKWXm@^!{x~-4hEP9^)%?nD{2W<&kHmxV*?%SyZ`ep##M~?(? zJa^${Fwb^)O`jQ{q2f1lV+&hjnPrJz;umAj2Nv>=R5E4r<{(c3Wpg!(?6L9D%o&%)Q!)Dt zS0uaKnw^F|Lo;?Xqb4&Gv#CWdp*^uP8cDzihXb50+=n0aw~T`1O)xOt&Vg-KTi)W}^s+lIf3manOz+ikmX+a(?^&I_R}NHVrs zVK3OLYrS0aRvz$0=d=mr;S1gKRdY?+EiZ9K>@sE={az-AU0cHW!V@G`Fll$Lt21}y zZ_7E8?4U&zrwhu|Dd!`J&1RO?Mi+Z$#cZ(1%`Nv*(6+HY?bdC4Xxqhlk&cI4}K_T{pT2RoOraqDLwRn%K@fW5~k>;2)MYU z3b8A1OH{~*-V|hokJM{0t^TP=cb2$xjF$}DFJs#hyCE~r$sN$?UxVjhiybc96I!) zD9krrfZug+ACl!it;Ko`9PNwTVv97?;Dn!9eQey|&v4?tyGlwS7{uw$gnD&w3f(NN z^qN*WeBKEi8$iky@b|hEOp>niNgl4mg|*~=+u>ww)*5IQveF(nb`ve6)<OLV5((ND(#gK-Tm_r6r>4g&*f**_vJVx51yXV-ll|tV+h}wbhE~A!n2(m+X8M^ zkrB7<3T~kVqPcH_%=frHaxPO;mRAp7y8esa&8nowLKH}P>#yWX_CWm^KxD^$J zlP|0g1uP&9yy8t+hwU0Vp9Os`P?9VzLQS?Q+6_{%Yk9Vn>yb1h`xws%bOW585KNck zQju5S!a?BuPvreL!+Ioc-`D&^GvSmwYit{ER0W`^Baiyd7*cL(~-hI>zMD zoxGQdZcp2k%Qq@Ibym7tzVHQYYsyXaa4~u7JDu59#X>*y(s?`*5?+s<-;RusjLWO% z!%c5UWE*R@7nc0dizm+;EX>vNDAI`c!_o&49YgVhoc1>uKvicwZUeG{p{7aj@buWB z_PTST^=7%2Hgnaq~uc>wyb(2Isv7D4W;&D)*O^;coa{QAQ4Ygw)nD8I{!5;^oKICKQJqwvH z*mGxCDlG4`iTS9(@}YUOYZ+Bw$lXfdu5byQy6fOUpvjMLI9s%`6Ic_;yQlc;_($!K z;f+{lp_TP`X73ipt!@!%vpBE+hx`l-+>Oz_4o>ec#UH8}?P5B@EViOpU z84;!Ti*n@y23J1-(|=Op?h~JnFHz(8>XWC%zJ!VKNzC8N0}V)6g@a=q7~B9>VYyDP0@l{|}1a_-RmDirC#?t8^zDMhCwP+9}f8zdy?w zo7#RuY7a`kZ4%Xe7Y->WYl*CjSBD%UcDU=sydvTU4c#n;NV}uLNfQ>C`aS}^$iX!| zx+McTpPR{x|A;pHEEI3kkj($vob@axsG90b{ZCEmLHZygCX~&3}U=;17{2N!1 zX$q*>?uiCp4fM zHep4rV{oe!q0D=d^4uTVvR#soFoWUkO&!btyxWjV6d-UBJGYbb8_P^a&K5B6=_nZXUsn1O98l3!i|Aok`e8)}@^O z?ui%neT9g>7Jt##m1xHA%wC&QNAd6A^@QA+A4-#*2u1cTMS82sF!229BKXmC7-7eR z#{K);OqZf!`nFGCNtWj4h)9C@sHVLc+$;iYjU+E$inN!BRI!2Uv|0T5so9vq$ z36Gi@S*|p9af0v83J6f!tmUhYMdI^lmBOQDm}k! z_`v?FMy92SQ~H9<4Oz@k$s~Mp9yQ&974_@S5tTZ{lG*;_1ru&2NtIZ@>dj`7l&qBW z{Lqb~9k1OI2HNzxx{s^68oJX%MK_OCu)xRdi6CtPn|do(elsP@cB{7hULOuBm55>M zO;64U#ukom-WWs_%yKXKO}G=}5aJ(_gbs12V(T%XO0Dk1N8Y_U!2n2mCKZp|5ou+oazF`}mr+X|hzG$ZojAow7J{91#~X+|Ir)>Di9 z>Ol{ynen$A?W`pX!uPacW@`hH`5O3shs0u#f(`+c-|*}C+)tO%sIsqbg)-*|yaV(WBuQHuWx;#y01{zkDKw>u5Yx|UoI?V!*+7BN^;P@%1;`V!&s?@3D84c*dV zlSF9)6F2Jf4-C?KENFa12UBu#gkF03Z#CV5x0G7{l0zq?mKY=F4jFOxNV%!EoSnfY z9iZ{6l9Z|Ghb)QUDe^n*2~# zF%j=bd_rrDq}+)_>#YmGFwi^((eltpUx(V-)jf!dW;_&>3{rcKk!z)CiO84LLU?;V zyeFPxstGUBD4Grf7aXJXpH@)37O0sqfn>pnnf%Tr{CW!)I;BCHmO%w|p}QJ^-+4S0 zrO#JIehtNc(2;EyZ}1mAFbI-}?p{+<+Sh7a`^r1fY6W@8hMALXM(B~PNT0^5b>=N6 zp|45E&4oyHx+^@Mn7mIE1(^E`)7^#8s3HH6M*J@8K$?3PA^7C`@+xZwgc$ok2puxv z{=ABz3l<`T;W$J!6X}5t&)?d@VJsAp^VQsa={(|`U9z#>V1x{4^QRUSe9_F~17lZ= zFpc-;oHU;*k1O;9KAN-Nf_lb1Ej?^0?h`234CH5MZHuVUSis`$T^_jgEwSvwB|?e# z4^z+9)Rf+v?#TYOWXTbdM5vskwO&$>xXEWrrHRR<2=`Y& zrSV;tZ(J0e&TIQy%y>yU8D#tt?qtUt&N2=PfUudc!DaV13xl`;SXI43jGoNDR z-Ih;^p^5aT;3so_(~_btlHpn8e7<<+9nsXqGsgc5#a(vVmR!4I##XmkqpN2H9*rbs z$uh?JaS5#}5&gLQ>V(@@V{!Hc!ur$6uR$tWVX@I27tuZ&#=N)n z7d>}}f^@1lSN-{CE;wBv+-LMrmeYZIvj_o1Z)(&DW9lenauh5&ruMGpTQ1UHt|P%~ z0=I^0IRuEGXgzY{B-nF~{@~$XQu-OnH_h*-i1!1HMT%0&EcRF^o4@yOplV^{srh7+T3^LoK9bKKe zS^6rv-xxdTsq=ntrRJ1mN_1}Z%WtA{-a(>!#P~UXg>sKmm{|9LP)yMFkp((w$Gp;< zJH$xzBJy)}KmQj7wG>%U zh8dT?I0#?6f^n=bi8U9>)F7VsnVXWl%^>50yEx1bYY~~gQDf1hLr1u;_lp{#DYup>vi{$0K0 zO-{mJ(Uncb$(w6kVgG+b9T$S0Zjnngbe=-U+6L$@5txT=B30#gW*3C;MP1u;9n3)ubLdr!JmKS=4G@XxnZkKg_0X0sVJUM+<;18!v^lPPu(oO%YlE@!kn0P+YG z8e8kBS~~b*$R?LB#ELte~eXpKZg!Q%QW1bhT-jXK^UaiBfbQbYZ-Ar zT|_pxh&DV#I?~~U&CrxsZQ?2s5?;OpTBC?M?aB0P@EVcj&1_jsrY&{G-y3nsJN0`! z(bFDvm@pSl_4E&iiY`uP>AIgGvFh}LXipD73E6~ZJJ_KEKSmdo10FYZTmGjaw63VB zC`Sm}_NeGA7UYr1FghhB{YK7`1bOP-s?M$ow?4@`M+QA7 zm}kyvBriT+T%=U0*K&L_M41!iKSHI#MB&)*#mgFO*?RxO7SX2XI;zu)_aE4nZDO|M z(3R_{bU~S;)9DuO4zDPJRTAp*UwSC4P~pUG5GUszcM-kFK%?}Ck-Mb+edXKQYMt+a znk`Zy;Y1bcggEfrRQ=783a&|a2Jkk=bT95RV?|jc-S7N$>%|j^etUwX=oCqAQb#NAIM=N%XCVj^y5q2i}XqRmj42NO30;Z zovZKQV+T3sA4}r~+J7nL&9b(tsjD1LKi`4jF73aYdD>O)%axuny9gsc@x`G~U1M9X zn11VOco@xok{g^@TG+>uii)?T)z1oN!-~%yh^j3+gN9%(7Chmsh^d zVv4qlM7PbMqRGezrTmu;rKc~f5x<-FQ=hiJzG9f`Ki?MSvrTOuGg% zW~F4U0@7!a7dwcy<`7~RNY2x3I1c37k1)&aYHrp{73JYH_$-)fo-acu&Z z5_fB=C_lSNX1-YUdva-Uwq}X{G<(ueSAKggl2GWA>Drm4K?glY|1N0K5XioZ0Mcr^Ii>4L;$JZg@i#84|M1!JaYhplp z$P6W!<&~kEIF~$IX2gXW>ct%nT1oW!MmCB=4rTNSVOs&T z9d~S`eP)y+40x?e(@OQZub!+w+L)}fBoA(e2G33Kor?z&?HD;-e z@xmIiNho0)_~zbHzF`u*`ROWxOI1JOL04a}!NEn9dSb?{Kn2AtaQbFsIKK~hvZs5H z|I}&KnN>~bTi8_M6D!U^Cal>!9V=dGVDA!i$T}9{Ocd+vL##F zMRy-2`_F(v(#io=UZzfouX)M(G|i0&cl<5KW~RC_>t~9`n9%C2xphf=1ROOla1jt{ zOKjUb&U9YiC};K^=WiNfZj16J2XG$7*w|d=8vCv>-}p5-y33e%o$UKSW8*>P$&#B_ zjl@66MJxQM^2?g5=lSv4L06Ys5W)OU80=r4e9n?A-*g7OMqoiY58oS=?^df;4jV!7 zGok0-DF-OJzDuj-_b8pLBh=dVG>QA<6#^pI4Dg@HdQlvs(9(pY+K_`tuIg2ZF0 zK>zie<#5iW)Dchqp5KmW)IfeKVs3&Qm|&+|dpcXLj!N<(99lHBGGLI<8-jvUOeuC| z)!8!P#L$;>&C8Z8^TUMm=hYscniQD_)!|eiVS)%Yo#^O#J<_AeXISy)V%;y=mnGsM zPZjCH0x;bZ8^WXXSI}y<@c8twg`i2^Ee(~jCT|qX)~x^3WWiQUlLVis3PrFfWg7|nW)r?thas0}p&w=B8J!?Ai0n zRbD+Z6ZcJtim~mS)xYTx@x+Z=Ql^Q>Yn`i7b@)*QNkILcU?_`s){~6Tzh9-jD!-ta zxWS<4xl-HWGmw`?YUKd0MybIEQcN|_J=HK*+w9Ynf^MdkEtoGR{}mR^nEOPT(soB@ zpa4t{B@Iv_30*=o(X&$6(-4`4?tLr17;Gcw>LEM4k?DT7f?XQ_if~DHG~J&s2Ew9A z1_p@EBgBiUU0@FU`*ED?U_*I+E*q}?mqL!>cz?sAVJ@N{y|A_scL}huRJ5f@McMkU zT)yQh4#UUjIQ_<+aQBH*W-pW77SRO)Z z{ZZp($QM)eX}-b2{}BhgA=@h)XekynIXHYh%QL+$QC&T* zV%W`{0w1$^(^aG0DH`2+qvh9=*?G05Y z`?vDQv_;ScPbXVK7P&atkV)8?Y#@-%i|HdtNK}C~XvZ&1)|SL-w5$nJXooDPH0}-- zZuR;pxG&FAEf{el$0SgVywD9{#$k46jk(q^vU3f5h-NqW-xf6NwD=nJb0nY>?<$&} z=_ek;51Bpx8A(-y=ZzwVNV5N^cJV>>t0VY0g#c`3p>{{Glr0zG3j)w$lrX=Xt0)T+ z{ea6AUdEg`mNBjig1Wl5$p3|+n_4g-A*V&0vs0Zs{sc=LpKgyvPdTDTO7NgJqIU`L zuLxbd&jGbk_#s0M@>v{N#cHx|DlL9jeW=_Z4v7WJ?8xyCnwr>{^Rn^UIzn9G-Z<~*VxXY_jC-T`)G)1Zc2HGhO zj;M0-myRsuv1sRVOmufw5!iGVck}_Vb79KotH=&hS&L4=7b=S&cavWZLdomL+mQ|e z+;J57G-fDub18kenKjed$@`JV&g7DZoK^ZBKQ;ZgWP~N)^=s?# z&NuyE;#1C&pVXf(-fHa4^PVR7p##vZget&zUo3fuzOR9`c#D(wD6tGL0FpRU z^rt8m36bzZnpGn)?-j;m?I-EtE4^Coon`a&gI40;<1#ICcK8XZ0!No+zvjoJ!e=fL z+N3AriI%$5M>B66nrSUL4AO3k{8N>bS1U;Zy&vt;U?j&#==?^gn*Q2Ee`lhlWW~Dt zFWj-c^jAdZ@6KPOJjFZPn(78Tte(}6Nkh-x|CrmF=!e+lh*IAT1J%dv zO2s=^$ht$aGc&~(ndow`Xg6e@uhp0!e((hGyeC?^o>wksekOH=M@mb&Ek~-ENXlkz zZ)kaHusVFF5~&>D6zY;#UdU!4H#bU^3e}mDrjnLU2EX5Ht^?QD^m7@wc};tpkYwHt z-p5gZqQV6qeR(64OszsF$dUuOe02rhW|1K2DZ<05w5mc~va>G&?|gRkYShCTSC*s)BmNd$rp2j55-* zxWm2ImcJ}49PA#(#2*~cNqr_={=ejQu_g_j6))RmgmK+oIH@4F6Wx{Dpy|Mx+%h;62`Ob+TM5xq4FV zTwC)V1Fy6r^LRECt_1+f8Dh1dOg1ebItkJ&PWl$}^$l`GfwlytQ!8X@y4S>oq4!qZb}=zFdBtw6*f zUFh@DKwH=3m2cGE|3FLAzJS1$>4d~XP)z+0li$LW1dv*4u>T$v$UDtV*|x3)lVIMH zF@^-Y(m#+=Mw$34;F|s;0X3$H{iYJny z+5GH3P5ErEQU$e@p%@+7xqrW=jUoD;XUGe%jQ zp3Og%ty9uyB8dmDK2`$_%BgeNXEHZzTpk2xFz%v zX%xefCpw}3xLuDiY*kK=m-J}9c|2=g%m=5KhYV=s1tUMnOj^|~p1d|O7+3S%lDrxH zV>99Se2wY&F!*{5<0`oDF1`3fXMW|WG@~4eMo|=byjirNN?86gP`ob9FXLIU_^V?< z${0g{dhst>^8byndrS!AK+kUWEr>S1iBri&^v=T*)lMm072F3qxNiDTQ#(22I<(s- z$*{f=H1zu@_~<8PAQO1BjbPxL7K^Mls1ekSFDSAaN|!|+M(61v zUuW^XhWqb^=9j6kCXDHF5h$yiakWwO`qMXVR@U(;`n400k;?Mf(_7H0lE`$rSrpk} zDKFBqqHLRE)mF!kf4i`Nc8SRIv-<4Tn4e1yZtqdB{dr!*>&}$eJeQ(U#n{oNc^_sJ zGmsFnd`67y#0=SA#O|Ibfn0PN+(RdSov;s;KE+5DC^fhk@SBakp2-z8Q5`O+;q?}J zJ^y1G-Wim#U5cPBn_QmY!oPQ_XvJjjhY#lv^V1n?Mnzk@+MA^8t!fo-%1&&B(Ioh+ ziKS1v|C~F~$o1(J%>!dd-NGwP33&&OQ)rH0R!#D$1)^TdAgMY^IR1BHilS^cg2z0H zK0x=`oP%b5>~Gk?d8>uc0+(eP3kq~>MtQ8O;bk;^x)F>qd1q%vRKw4Z2tgi|xYD6T12%?~5Z6G2H zB7)KaNKKh<$fo?)^Gl zs)gPD8L&4+)aer%3mTi@ZbnojHZhRIQ(4LvNd)Ph~C8-8Se2ZmCBj&SVt(X`$r%%&34@jInG;IDX};q-0*z z5b@DP>zhLYo-TR!fsE}W|F2VWz*=!?cdqynV3V;htM2BoKykwdrIpkKVN3)_E6E|< z1gfVY)}L^;`Gny{eD_z}+bOQSCac9c!yGV8I*~IXtj2Il{Y^_NZ!N32f+4#se~VB0 z3@9zCDCQmUal+&v|E%xAJD5^Dt}}|{lZ6>DffvU_z)!zIG17EFe+)7`IAQN+=L#P~ zH8iCNxK|5R246HYK5xz%f|iTVpd&j+B5$D{-vH-gCrh1Dp$haqr#oVnN^(9wvQ)U? ziSS~P&7Rb5nTk+iH8!kFiqc(Y3B2nMDwQ2Yhfn$Ll@em6AE>VP>-`R57f{vx+?&GE$tgBj+Ja>f8O)b|MmoPWQ#^Oa;hw9BHRrKXUxVZr z+vy{XNeTP*w?pW9ejXV7QTmE23Ai09C z^k~(3nwr^zWC_>?#dj(yx?WRid|_hZd+L^5h`M<~-Tm*==AkMrVb;sksJT~TITwr1 z2Pla3y*GDwkEY|C=EoAe3`F>PXL!+mbM~QPv;)d>6BCvSQ~X2IMHg8E`wkMC=FMYr zGUnb&ww{AUo8f#D3jeoZawpcd3Zw>9<^E?Z54`}dH#Ufo1n9ue7t4Eo3M8B$E*nfn zoMzsvhxgP)DptLa6ceH1Q<8~e!n`f}F@+*F+r_0O93QP#Og>9?kR|!tLQTyRG&ocQrT7$dJGCDg z`{aK4wtP*CfUVKo`n@M@&(GB+o(s;6LiJ-pdm50lTiL8U^Xp~N&CKO@+ydv&N*AJi zwF3xv!b?vu$GV7OtXiQv~? z&`uJYGqDP>irFjy;<%)PsQ#DN+w*l7bA|({r>}j9`0w zk)QC9*9T`Vm|3`PWx&q~q!e0`A^Bn|cUh=Eb8>!gAU76A_d~3%!`YI34(?#(_PU1G zWOEUnPJjQYt@h9_A3DA4_PJY`wSKm|y`Unoy);rd2%;0=sb3#b;6E5%uTG@~e77-8 zMKZP8v~7!KZqh+95NW9sZ;0O?v2W_@6U*9EwO0FiqNTZ_r{4bX2pg)8fb40QJadfl zZ|IOIq|JsNtr?O`{)@UiK5du0y$wZa8Rc$3IG%rigc^U_eH$fb5$Lxb-wjB->8StX z6Cf+;uGBC zhwu_N`R@#%a|N)q6aF_g_%cNL+r;g~VJwn$$lJKC*DILj^5XVwlqM1H;%|>uOmlq* z;SIt;!zMYc3eE~x;kMkz5q4vIR1t&1g||7u3=6&~4ji~5fa7No$=fq1hi+UZn|Pr1 zl7IY;^W2%PQjVz|68~;RC+hzi$8(YM1?RJJi0#T=x}Iukc<@zFq%i{IJdD$&8)Ek@ zCw6^psH>msX(pn_v5S}W`5*p6x+B@{*#C{8^R856ZqAl>(26HAQRF5`QqY^;;)=yb zdR|O2*y)1Q=~Hy(bLe&BtJgRG{Z}!FRD69h;_^WJGhhZi!=2FUGw-l9cMbRrR?af3%KNlk_b_Ls+0cm?hOoCJJ6km*hR3o5lFKKd^Dnw(U;Hy( zNKT=G(Pb{V*A5N1xviEzP;Y0~{?6h%b){`NpK-a*R}o^OxOWwy?&&VD97PjMBeoa) zgQC{`s_2&a)l3hJ+<(~O#aNUU2mgRkyAi1(dzzlUEjX!|yh$ip-R>W=SmJ8VD zv{8GL_LlIBIgl!evy$5-$`j*{2mB`-XYvmHmUY7=-y#<%bfVg#6T|6J@=a@Je~ z_uH@ZDjQAa3l1pc}~_`jXr#!G-sB$v~Bz`~^ z0(zwTYj2akH4Jj6(di(8x8@*5Syj=+YOmd{ z1{F3waH^j4jB?N(VgD@4y0`Gy4s!n5G;9RV=0*Ixfj|3y>Fk3GZbD373y&XOKFQ>z7$u56D0fdk*%;}p`0>BRw6@I5*P@4uMQc`|)$$5{EV0XI zg80!fM!csF@cCTS$ zYSy2$uxo`+wiMr4`EsRk zm*AV1!29d)lkjei?MVT2g09F;hHD&nMys~$KuZ9l%I;AFqM(IkJN`}$TQMI~eyWLN zY9Ec8MpG);l9**C#v&pQ=-+F}uAyywfBMb(+ord}#i=q4qEc#0Z7z-VPX|HQj|$X) zr874CC%LEt!JC)F#q_EsOUeAig_RM?KiYhI*May*9av6pcJ}Z%6)C8Fj;6A|mX$_- z{|*)C!8?P{Jqr0_Ahx)_es22JlkOqtYjeBkVHUKA#XX}dwah1ncw_pX_)lx!7YEX! z#72812TYO|8CQU_>l;|FuFtbSqa`{7+ZgM0Dtg+`@E$aFttijSeuK#k;+vVXf;Eu zuA|*C=-du?>$+Z@WVq=-n8k~sw@L(FV0hy^?VQ8fyHgCeuvZw9^Z3a=|J!vZaP8l6 zT~{c+{0F~VSiXOj-f4s{K8M-U=&sU*=`-WMY#Cn9Zr`f?M{3pkBq+Sdku9=ArYj)u zjmRNH7<##&_ymn<553V0{c?{YFM_{^bF+FF3h^yxnP|syV-wi~8ElKg(*MfX_cu`T zf$YPfN;?Wpa01u;{!};}S*!WcV@UAiyj-D0*g@lh_Y3&DI#KA`E3m}7+I^H(XM_$=hwAI z!aWZm>i_~=$J=wqe6%)AqwV8`mj-Jy0hNB0zi#_f0>Q$L6gEXMO#m!gRrCjwx>YSE z$uFEh{;;`X{gTX58`kfU!%2@Vz>+O3STvDZ?M_Z?`=3n4mem|f*GW9#R&E-5S@Q}~ z_ImMh+<1@2Cr_ljm*u0bM0;1!E2&A{#c9a`7XK*;#aQagqm#LLYUhD`Oz?46vFHDU zrWc@X(@FAoIw*vEpEW`8X8&sW{ako*O7fB%)%GFaE()Wg1G5`*095>JtNg zTMuNX$2Tnm+!xMbvi1oR{gZQDDZb9Ykrm*mq(33HPo*(S{?~yrQxyz{ z6BHqVKV_v-jY&$VAck0G#{2dX+Y`A zXvQbq6$Zqpzv?2{0_=_h-L+}D20h@uRcNWf-M8P%>X=&7o5zrS32hgBL3#DsN&$~% z5wb}=J9`mG{xPpPa+@c-UuYCyAL)ZPHE)E~NffEWA`&DK>wf#1)#7 z%$`PZV~?NfaN#6~>mCtoV^hgn9;vbnrr^h{$}exxPNA2v#KTm2oOshXQzj2P2@JXM zzB0C0tFw}WjGK2QR@f1&lM$qup+y*wd?a}33L`0JfT6g6 zazO7_p~-a_pIq~9?9*3zcWL)uQwMtb@Rq+dw04h~iHp6;SI$VC`$fsWr({r?iY?k) z2Z5>^KW+#?$5OPziC)a{*PyZr2I3}|nLvDLPv-Hq`VGJX2qN@g*?W1(=|g*{vUFDs z#!@u}^3l2gqf}6vGC^*i?fH>j@sEu_-Dm;#6nFapt@m)2Q!hxYox$4zf7fT_K)P{o z9|Y+vrrFb7sztAk0$(vU6-Sf01mmUT{Agp*hyNVSQG6ag9|39kn5p@i45y9iR$_Pw z)o&w_Z)C_jKgN(30f~G4xr?c^VgO`z4S}ST*DazvWr~=U5 z_eLRL3ILg&%uw-_4sjR*UX_^*U+$qwzP)mk*bO4k^xv4x?QHoyEL_P{Y+Mz5u%G~V zd{Xby70%6t{50Lf_w%;kFi&!@yW4WmjwCnzf_s@hcsJXz3=Te zr?)R(lfsLM;fJxTngtKA2;NS!G2Lt-# z7{5_NPQ741kRQ!G)IbeifO3_0i%;Pn#Li=mzL_Cb$w|o+AKq^>reQrH9QCV8Jtw^M z6aVw(M}U_$iX0vb6Sh_g<8d4S7j_d9b_8RN8a8N4A87mCophFdBZpbxSZ@cMLB-G4 zG+FK6M?;F233B=!6kATnBrA3sbMj6CW)#+_Fd^Cr{ueo13%s2fqH%ftOu2rR z&MPpjTrjK5=&#tF{->wJ?!By?DRXrZpL@Z@-Rth3nJhF5$;AY4ac>)i=k({4IfFFC zJuCQaE&2#cIe~&K5_(iGRr<+!riz93@J^Y;Rt=r-)L-Ze+U6(+1%AP2`WWS}w09MW z;=TN*72+#;;3pSsQg%v%CdhV~>i+y!_VWFsko7$Hc8Q<`DltiHwJzV$f~n&+VU+_#6mYx7oNqR1uWUeap;+Knlm%rY9L-oD;$ZADL`0CXP z!e6kJnS0_P5WE9p(XpsC8D1@mgwFghF7Zo$(Moib7D%5Kh_)}qI`79?;(?k`8h_b` zGFqw7HUiq#L2G1;b_uXLpRj8}L>F<>Bj3>52i|g$vgfe?)!-x}YTW0zZ0N2Pw4sK9 zvB#fSX@Y#Ko*r(t=l(|LLJo>rXL*F(YK4|ja2x((6i#~+u!AKjJ}n^&We8`Dg*skE z{`ZM+3KigCyaL^Yd8`c27;sr5wPW||m!t@83+7!H73R1HH(*1HUz7G-;B5FSal*`C zRkq~vTR~l~Pp(@DuuYn=rqNilA|ZiuVo_qxyx93!x*Z#Y+bpT`R#hHml`FEAcA8kJ zGZDDfDfR|w%L?H%Al#nlx_pa&%Pj5QaTDk)M5Gubm zG}x80j*^CqFccH`>9Qf{H0nW;rp)7u%_AcofrzqE^yIVR%t_TOYKQ-&)!F+7>(iyOcS!8B z1^EYU!2$jT?gpU7A8fzK#pK|C!gm5@XsLVZnSt`1=eI6uZJ@GlQKX*?rM)<5Xa@d4 z82M6y2Ba-kz9!Yokok82P!SHdZ4}5G0f0bT_Y9pzqGbSPL;&Wj0d6woyR?H# z%|psSdlQlR_b4KCu;FY^67Nb~&=g`68%qmWj_K>(k9M{X61p7IQ+h04qkpiBI3XqR zi`Cl`vTV6^EId3RndC}5iE?8&AWCr5yZhw+nMapcF=}e{EJb>V^6?wNNW5_m^kF_C zob!OLPS6rfJbCXfkW<~}l@DTe1!I+i(2`GV(W}Sq;=?l0m8y)kEh^&M>)4!Q_!4~` zpUbB0Pl0_?*n0ySaMulYkybjq($#&2a4fi?4%Zn5HDMj;Yz<<)w|s0}?uvQLnwc@L z#3}L?x?3esF)#;ckzfT?k$;_SzL6mf30>)pJXbp^GEgx38WQUa(0&bwxHy^ht9}D~ zp9I;x>6HBMj4-d+T|BMts_NxNfhCm~?`{&mQ^$efTpmZLIdr;jH072o+Vg5W`}(v5S+1-j752`85e4|7BBZ7MMU0*8&)XBn)kNMc zLWs8-EIvsH7vkIpMQ&X=q?gg@>cbpjR{hI8dcOFW(I+(-S&%;EgQz6z$1D zi3U#nx<)?}u*KM^QKgssscBJBk>t@k2unn(ThZ>WY50iuYW3h*vm^Xj=BncjGe_|^ z;$nrf)9?Fshnqt&JB=e{sjuF?#j|p7`~s`Wn{{<9|JXR!y{#M9OysmiihP$LY03$t zo({Jg*jA>DF{(=FHF!o*sEPmA9uuMc2C)w$fb0*bRN!emcQJ}b9^nxK_6x796t4eE z-cBnAsJG?DQQkV?)nc97gh$t}?M>x1P%RDv+E-Md*E19AfMW`X9D5i>*olP}OIMPe zItr``qpbqcn#6w*1~$_8P1;N4XATdTp|V$ExMRkVK{w%6@lT8~_hFn|LrxUZYK(E6 zyD^4Sx&JUWUG%@@Y4*t%G!X*HIcaLy!IVyf6SEMUtJ z{_J?48TbASt*U!XzP|L(hK-qd`pgfr{mv+?E>cur_OT^4T0K78pM^qI&32$CWpdFJ zg0}Tdzl3^+3NIsP=qL&7D5dk5a$?ke0WY5T*;SSze#iEkg&3(d>d^Je_42!g2`4nzeSx4;fclkUO6>qd z1uD)LLzeS-Z2f_?B$(N-v(oZs-PvE+76pv&tWu$Uh9(aG zE(ECXpJ|<$V^kVp&ixIxu9?x^<^51KTTgZz^^dV?fycb!1HZP+O=|(tbeCn2Myv3Hab4G~wsIdf#eoQr)Qk6|(?!xX0 z&DdqkJySR~TjBgB>fHMaT3Plj{WE`ZdcAzVPIvE-asJjx_GuIZAJJnb;sgIh{F}X= z8q3xlm_5Apxq(W%=7AP}p&p%A>@)B(0$#pc{?WYh6t!~!8Zl3lXh%!b!2(3#1~}g) zE{OVB3KgrNfAsi|=nnl!jjfQG%e7b4!hOq?V9^2mHj)1!NNo9W06%<#;1v1;W4Q^Z zg$cezxUx~=L?r4DTskH>2IHF|kA4~26ce;DWPZcTNu)kVu3ZQ%i-oT0wjY>7h|Ko! z!open#F4WL6V$vlncKUK=+kRc`+HL-vxx1r#C=r+Bf|fOF7p1;U@jMhA9&Cz)lf(W zAEwPUlEQajoh*dJ_rNIKeWFq@F=|%X6bmf*5qqq8ZZov*>rUpugRdN;;5ysxkWf+H ze_&1kHm4ceHUVbM3Ih_jQCgj7tT$(_?(N*w-W>4-=0Z==st=5x-QAouo#2@@gr=pW z%F&lERe1gqtowkEH+(r93N#!@&lA9B=RqH7op&L{sFzniW2<%Eg_nhb?gtOYHM7)k zy;(En2rxubN8w8IfyKxD3smeGe-ig}3a!Q!;Irs1H#6R>lzY$kpMdY~l33SuL%zn+ ze9f<>ua_I2_oDH1Y{Z+1Jj3wS*fWo0)Lb#vjZW>jjqxcy4WRnnCt4_1s`CTL+PESj z!l>O7G<*YbwtB|H=@#5yfO!>g7@v;JP5=3F5O&+``j@VU;a2?7zHZ39M`(XiaX^+f zL8$s{!2hF9+e>+_7Qz^jrPHG{R~MH6Z7$rV?7;hqA7dQw~f zypQ1uMF{*_gh$&dZo}oN99_DXT6kQ^R33;w&?{z+`~+X<_jr0c7Y2om*UP2UJ4? z-_Fplz7K=+>4G#RA$&b8{59BVQeQWPyuO%VE?Hl6CWj%tsiY<9cOxKAYF$W!{KmBO7qT+c`d9GCaebXSS}ep92xEej&s747dXt|g&9l7j^p`K z=gMmn#H#qQFKH zjN~JW;3f~n>f6%-q4K&wP3&6Kg1ucX`b8hOLhBaP;!3E*8t*(Z^D<`WXGY*KvCC_G zta*)J@Mc=O6?zvNYde@Z8Yw!nP^5eUwLNnIX~n9E!pZ)Wqww;Rkpkhww4@wgoIX*i zTU|w4x{T6sr=>-8MQxw+NL;#q;*++o(>`BlZ0ZRdv>J{hKtl1JSJ-Mm$}OBSMOcd;yy*&Go+HqFh`Pvp0?QL!@*0i=BMV!H4XZyRV4mtQhVCbdes`X z^>|!f`jeb~BDohkAv_Z|?cmlX&n-0*C)Y(5JSy}r0WRQ3Aw>?ih(z^ye{9jI~ zU)t>4)|DYEH|!`bKGUl%^y;pCP7F)}3TMAx(ChX~OSoX59P2L=lIWEi-n?s!kG(ZL z^lM_rlM%o4KiOxp3JXL5CnMqdXGg+W;>B88XfQbFqKZ`GHIHe8V>RkBFVQx(JKky8 z(+QoQzi;*7DYk@Fk1fK*N8xYLgB^h-diBaW*9~OsoA)5L*^1PebE>6;KI7{moVG!NlbKFH}A7`y>GL{^o zfYQ@yq9v*si-_+NDXEjG43D{#Sw ze$cvYZ>&3#l}wjxHEA+IIB3oJ@8$4XMq(j@_lnLtMv)#hk{-_(>n+QH^l>7k^wUec zhXIc7t>Vj0RG({g+v>i?Ht;#SZHg^s1Nn>MnO6xY{nyDo0pUbV+nhQu}jFNjLQMk+JYABwS)aFoOSm z!wA|Llcq8ad9KEt)x%H{jNq!qq$tlt4&O#ud!P=j5OT%{Gb$cAHu$!bm}D$KL1eQU zq?-oIJ@vt_J2KH~?Of!lkXkqOcfxq7dgky?lUI>fx@9ObpJ=Ip8mIDCeUIlTAF~M3h6hDRzvkz0$z~#-g#o1pJc$E+eAVKKqO=O=#9`#HU0 z`?v)oah>)+Wp%d%eM{~`Jq909kf)llKR)ntkSwxPHa7SDuJd%HN~u8oEH0*B@@I~E z_np91U7k;U6f%ERmu7aZu(y9ZjQPx7eUlbmsDE|UzO)|v;}4?#y(;{-!9SUUqbd5{ zWmDocdCs+d{PAY>lkRT7Obg{DZ>+hX(hD=)HMLr1c*4>>O;g`Y*yW1=C33BrBJJe+ z7=?KLyWVMMgTTw^gY+NFmfMw@7*&H}bU~|geTRYfniW6W7+ob#$7gUtHyn=G zPc}^3s++c#fbZIVhaCattiw*-6@3HY_XPR6UdVa{_d`FMzr})8LfxZ_;62Vjai)od zzC0%jX;!KB$EKNgI=h(IyssiK~wBGLB>^v z7Yh1*PJV?BY+e;$$Op}N5*l;ge5-{`&Wrz8NV})&U$r@-h^!uT#@O0yf9KjYYIwJL zxSzJpdp{Sz-N{2xqR*e#`f!&K_m@_UBXuE(wH?EBSM}5r<@|T0GHCF)aF_(Iaz$Ba zM^yu#rk$Y$uOnf8ZOuP7@|lcEzF>BIvYB2X`b6VBapncjlP)H=hsF1PXCEGali@Wg zY`AaEE3C%&`udtFthD5R{ch1Kgq`43p6A0{us6Mv&@w(kYd=ma-K?E z{Hv7#*Dc<2N06@O5{d_*rn;ACp@R~pjWgd7gHIw6ivNI}k0Amb>2m`AnEP`>P#l5Bb}hxd;RI_5hz-Ah4LzuNI!Gjs|ufo1imY=YFA* zPDFR<`B}!&VW7x0o_Kc^K7gSDcUD1-)UcU3#GUuWI*#}40@70&il@OC9 zUX>p(K7k60>F&H+(LTec9Q9PuGVjpzhib6o94*|7$l80ZSXW9{dsR-wY4Amx2~#a1 z-(F$HF-TnvP5g~$X6@cIHXIsn_=Q58yB*n_gm_HbHgNieTK0B&Lv4GIP1EnMIi<_9 z%~PS2ViD!ANxm@a7bYRO7HEx*RnMXWx1)26EIN(HchR?`pgLN0V~FPdwPIvdeAO<@)uLZ z4@D`|XhK&g4#O9A!owEOWf97|8prG=tyLQHtsNCwW8LiZgAXP|@9N{?z$rqlK$$Z7 zP9KQ6_N27S^}zuae~vJ40De0kT6GZmL#&~i@PF!7-lVnIO0EYmnIbPTcKRlmbzbCk z4p`GDbokf*nX%RM0&$>L9G12+<4Fc^X88i%Ym&D_dy`{E9#=zTq*G0q*hwmM!p$~$ z{}TRfrI ztT+|A&K73>3IgaSSMpcI)3DNLR24Dciz0Y2_@a$p3ZCB7`}esOWE{BiS2Ts zHa>8zjKu%Pd|-qL|IX@`*ml%y&Xm|WK!&#@_1TYXb&~=4c#i$A+U28ejS?aTQdmYM&ksmWg($wzV zKP&dyDqKJ)5P5j;=$~X2lHvo!b4TAXc(#}1(9{6m@^^MR0sOBA^C~#JBIpf5nVgKkTlKy;l;{Q|OF3y-% zxvCXAwOhn}+FWWU67cxKfLYOR-;yvbDD*^K$JS1Z&DU&F~Er~X>R0~($wt@pyP5QsrrlobAy7yEOcC8t;HJIu2{x!2^7VwAAhik!4@1bVtHab;|R&?0#x-)b; zK;W_&615DJKQLU)2TOrt^E~$$Q~bTZV&8oBh z+PUIlU#tDrtv9=&wKHRb!(WfE%)=otS!m%S`6@h{CQrX7fGj;7=yWgMgoQMxZhZJ_ zD;?VD3C#^M{&)Pify;uGD~}76oh-yxC#H#GM?xi5-CG~{^O#DA0kmz-rTSm@BJT|ZL=BodaqZxtL>5n)Xv#S zO?_4&t#sYV4HE|rB1hFm?H0Hxim3NDG}TbnT8e^Y#;{N9v3H5X&xys2zm0B>drl@XUn*f6xs2l0cB_S<4CAVTt8{>ZD9^=x<5fpya+ueDL8pn&l<1;vMEN zMMxiW6}G){0d{Pc`R~+&MZwT1vi$ERXfWPd9RPoJAT)7YAuOpK#_runaC(99vV&|k zN>1#9Vs*Tk`VS1Lmx`=T6#~f~K>2mbn=Z*a?O@|7{b^?h2lpSj&I-c}$#QatC7AYS zx{Yzz^DuWSdBhcbW+?IX5Pq_DpT9(uq>Z)PB0u$6vL57nX)V-t<6Ifa@cQWgxm}#l z=`il8tM1? z3^zAEe~>Y=mKFR7m%Lz-W{54Cf{YVUXpAqkG0=;r{^UvJ>#eaK-i1u`r zV!nY(IJ8A0`k%4Um2UW=zkh!kLR=!p2K>fuwwyVcw04RRVK>Iua>`YFYXzdhqC6Vb z$SA^A%(8Rs7D7>n;RANT`siAk>h7`|pw|FdLnZ~Ho!huCFr^f&t_`@3!#aCjq|Lt7 z3EydlWu^aO4@l!BNPE8j!(9mHNU>0h4&Tp)r1;`EOc;e6)`;oU=v0X=PO3*MpuWG5 zbWvWf=%)+khzZ!rQz5_TUvrS57Pvtu!9S9yKT58Q$QUh>s3q|EPIz_=eA`YT^}Nwn zkFk8o6&4vl7@Y+9zO{1JvPvd!1wDRab>e>tP^E=hg)e8cf7fUReP=o{1VzvlCS@>* z%hr?HOpUVTajBBCBNEC-;q?N^^^EqTs5g|IuAWnj=__LM*xAt8oovx6I|tqb*^Hv- z`0U)xMrLLyA&*aPz&c+}m{sWTuYGKtu805=oDE{_EZJ$4?C%VLe}9;$Of0e{Rr9h6 zv<>KzXPF_;1~*Z?m!!|!KS(@Ma(scyg4F}YO#Sxjm7fY=v6)Zs3tZq6coCo&_M*c7 zm~Kj4-tkEhZP~#}z$M%gAlIJVu^Q$lrpFw=@`ox<2f(GhoFjj8e&- z(7ONih!UzrVQ(cDj+2dC;TSuP1tEC1g~9J^_=REeL5QsgNQVN+wv^`r=EBdjNa+qirFE@IO zz$>`^gSHy?jHG?|Pb;ht7cnKd=|779_56IvBIBx+GhQ>E`{au;l7;0LFmV{g>;WiV z#R&cn>35E8I*(8Oj7{`y7X<$YQe^^o3)Sy|))t~L2#+yxgm-$jzy#ued8lFzE5|Nw zM0+!XpZ(KgMai#CG#M4I=fK)=OJ*gmLyGej8ig~|SyV5C7+H>`2FUMV7p?n@R(4gf&BPJKe*UTE=0DEnZ@}`bj>6`>9WVm|Xe>ogh^%#RVPDuuzRM zZrI>57Y}6bK{+9N-^14&y0=$Fn7M?p(wm)n#dA1GO6MaSV*wD{SbnjGM~d%v>trG`R5R5jKUbh|jJw z#NT-ImSmJQrQ@He$~adGZ8%ano7;ig@*V-d=>Vsr%PH0;e3{KIFSbp)2zOwzsk!$T z5SrG~q(?!$7*Qi`;X7k%&kUb97>tmdph?$@&V{IBgdcZH$k@uKuw5`b2fnAx(6HHZ zyERq`8KeIZD>h?`xBtmHbrzrezn)RX9mr47Du}GD*`Bxc{EwKvzIe@_x#lzd6*EU? zgcIJ-q*C%AOP7D|!P|H@(?*Aht)hC5*2vm;@N@hgNJHYiFkBjr0{wH?GH?HsN#O)pChR{A9CXkh zNM7AE79m+q{0QM62Xg3*;yH@s{U7JpM0_V1WL%_}`eA=^xcd?a-w34N-(B4wh?EK0 zup7mo0~*%}$r5EQ(1^6m6mELK8^xeS?9#*xa$=ArIDqq%U}lEQ<##;Vd_zakv79mf9ZX+drPNX@lD-_7Q0;(2Yl%ml3na~-FpBBgiT zL(WDR|AH#f)9%QO5;~g1N5$et_eF{3m&qiB9LVfce%S?A+n;-NSz z2T4I)-h`_#$5unUdItr2`0oF`jnGOjt~4dhZb*>`i)m4EkljtuQzyAq+4*;mjntcNik6cd&Yb*DO(5$v@XdKp(IheXHCgJQV5Ei)u;sTGNUWhK=iC@hv%QJc!z77*Y~5L3 z#X2)MW(ESNVOg(95ln>k5y57DVMHiu}j_-Xip&K*!7sHX~L$?ShX0 zd_y0zjD-rF`SELAwH+_|+h1FL^%xvX@HS^*o3K80pfMRbZ07P(xX5@RY9qV)T0l@> zH^4o|`~_fZ2f*v8--G2Pa`M;2Quh#Si6ZfSm!vJ7w4QS~Mc^XmEhd zjl&l01_CMI|JK33J4%NjSC0h?V5yV-K)fHEs-u|yuDg>3?Kmg0u!{Ffmuo#2NRHCs zn$JPpIb?RCt-oT_KSgX}a`fp@|A)`nqR*}x&D8W@R%y4WkfzCa{7qy^7S!%Ppz8Y$ z&#L-940=_xe-V1kse68WpT{LcG$EyD3YBjrpSg|KT-~LWQ(7}esMRLqTZl3=&`Get zgZCX}B6heI3AYu(fbhutgC*@j@e{O7 zFZ~0ECR!c0saKB!H$#K5*l9w(F-+NDeSK497-d^h#xoqMpR_ilGzBhD6cL&NV5x(mS0>)4Wj=0!~@y^S8LQb0IjFKq1L>-DTWe*mRKr3*y89LVB` z{xfY!mdnzx;eF(q`VAYyRKKp5 zxgB^PtrtB#!s>W7Exd5`^o`>KXU)7sy+1lT@6?2|AWym%eefB&aJIn1^N{QGASTm4 z<63umI@Zzy*NJx5&Kmvf>*e-fCAh51$hh3YxIrWQ zjuGxKmuxe>u@1jJ6Mk$c-3V;OP$kySqv+h@nfm`Yes<2THkG~y!;jeVlQQF?8Emq zmKKwghqK{9p6ZK!bYPAUxl4WP4%yNtXz?Y+*R`DzY$QTK&wOg2c=zdtha1{j(f#Qm zNL$|K%@ZW%SFp91b_D-W}}xGv3E$*)Ox{#Bj|RkgcM(R#TH-q7Rrci5d6tbsDp2r#Kaa&$V1pU)>E9t zV)O7tC^gHgqQ)q3l$Zt|1K_Cn$bzp1gCqdQMc!;L>8r=F+4=e54Xv%5`Z`L-cm1H7 z@4k9qQ!X)a5U@X+ye3Uv3y7(7ZHKk+D5EaG?DYe~$WR0Bg|I7zXB;=&y?dsYmz%5Y zej{Jh318&h5EC6|CCq&$qw6?bXbNMnU0r$4*|JhU~kcId1>b`va0(tik5_zLve^M51J%(37AR7UL zX@6y19iVH0;3Zi=t8)s`Q3!b`k)r3)!ZV|dEe{_N9b9yj6Z+IK!p*q2x5H@!qOxeB?UbjQL$FbSv}dgB9t(jQxQE*`86sH z`=Pw^lNa4!y|^JDa=chs4cWe0tz!jz;Ej~{#NL$$Wm`L~v#CCH$n=bkSCL7cMsNyf z%vVJDj5K+qiDsZp)L*fBxyvka#VQ7N&GgwcVkrA#ruhB<=dCs^C2#A!(}P6klt7CU z1RM0MotHU$GEx%@-jJFuZI(uMamxEZ<`07cBX$L+pS@4SiLDR4&^OLeqM3}(xuPq? zTkNH?Wbcx=b_UrKfc+t#%koDHK98|NrFcRunJyzIA|yZa5ylGgo-YjSPFVSqAy&cw z{_cg@2oF1DJp{bsBvtUXG^p2WY~Uw;a5}fE6Uo!w37&JY{w{+AW7-H85K8tXuZG{d zQ^KDTstYY%FCT3H<2Mk!BlQ9wKRi1G{c5Mmq!d*spR_3_daw(74A?pWHk*xZAY)su zC=2f4F{KV`ls_9Z@f`cN$}29uSM*4gV@2g7>Xm$CmzlOA1dH?)ZW{UZuX5%|#h~=UX=& zpI!@l?(N7GyLnI5f4;vN`}mZzsnfS=^ByB3u+RAQOaFd? zmVJ&=@jW>O{$eKjC@Wthx@g=JDk_z~NH^G=-b{;?PsqRCkSNd6ef>mNfXPX*%PGfl z>jf9j!Ob={$PPGnU?3|%H+s4J5=u>I8U60;W-fw;Iu~(xAd9+|^u%t0*zsCB-sXe3 z{*7rn+I%|km~-`$#-UHGMXb&{z#@cP%4%+UK(N2Oqp!!(jb8KbzV9d7IKSF&vl6Pk zrg%JP_C&!1TULaVjrf^8E107ahHukP5*2w-#)ABB@NCtwR!>1fE{$yOp7^gKNk;&<)EmX%u`H>-IP;jl7ShSI$ z46=L96MZ7SumFxe%vV{Fpz^(Vc$>D2@Qeo(rs?F2w|p~`5C_?V&0PHdUw_c_SSi<7 z6czo3_0ZNJaL6Rb?IfS@2G0jXy`HvTsWwSJPyLHQbFR)KwQUoY=_xg*Y|wcAz}b9k zDYJBfG_6vwVR3q{Y^hoq!4!j*IsKN9xj4?zej2uwT`>av93Zx?M&9!C4X4S{e$y1u zSI*~HFGWbSRR!?EEH4Gr>hu!VXcGA$&wvin-MeNE(I=oqi)C)30zY!-GW^WWH6Ryb z$lBuhin*qu22Nd;&D&VweWtsypVT8vqn@E00plr&mzm)Kdr8%pWeEE&@#Za~>=F;H z)$AaVoK^Sbn5n%$89^4UHVzCOVCBr7B3>#py|xtNRm>;PL(pVfEW%qecLTQ2vgwU- zsD-Dw9Eiyp4YITW13Wwv0cj->Ht19{?XyK9%aLIp@!Q|Ml?K%-Ppy3_84G-0Q(05F z3OZKt%IX+H>G6@3ZVWy*;CM@Uuk4BnpN$ia9`>r&=TIC`YR{8_tj&ZK7jnnIx&*9q z8@8n|<`Q~rGF{a0po^pQrUsacB2Do;z{j7w9%rKCE)lsSUfz@SU+;%u-|kA$A0U3l z=yPGY1&;b1Zm8J=PRis&?3~bz1_b+PH~yu0?ZjdS$EvGpTH!_j`iClRoUA=`${^v( z6LfmkO(myx0W~S9n6vtSWj98-^!kU`GqYaYuEav)42HEF0ax}*( z(VVc>(?!a6%|4E-sHWYn5}`q5U1N=y5~Ji8w+i^BdeK@Z$P z7c8n5EccZji_hv)5r#JqiiH1OSXZ}m+^BZV_m>y(G3srK_b+0pF(m;YXR4r9*3R4y zUoKR39XXnMV>>c`;0+>|!pD~S`-6+d3BTOQ1-1SCqKtn%K=zPsOgsONKl*S=W|vRm z@}*!0#Hh16+V}GZYw@#Zy`2(5i(mBo{aO84GsM69-S9cLRz$=cEwCuxh1fCCgI0Wm zE@rhCj5vsIGo0z3-M^{Wyk$)6i69?cat;|djLyS+C}HW_soi`5S%Xsy=I!Sxy{*kf zv}xVX)`(>G$3tN^FSz#z6a~;n_)}wP#`Byd>i>Qv#deT4y<9`Q+(9^sD!!MQUi&=U znH2qXt&Vl4j^zJ}`Y&CtK4+x7VQRt3$LwzMmn`E}83buPe7_;~O%Iu=|1p|JkUsK( z#9BSmMFnzR#7b1su#2S(V zHZzpoptJZrzCFl$fno^*EdNC~sbd#?-4fYO;?{7e_U#(r9Z(uHV&IsOl>OEgdn9W4 z6hHpFCFJNWq=!|?_}v5#@GtX<)gT)aG9f5u6(eNPgd7mPB!2q|Uz`&=ND5serv-Ay zIN`G%P=8tJWWRIyTRM_NrQ{7Y#0B|4ZyI2=E5?AV-3f=Tj8UpLX|8e}8}R0$qKtB4 z4q=hGpLA2H{L5-lGsda7(E_Yn4qha>#;yUxUY6ZrBG!ZQTs1j9s@6VAwGI!52bMD9 zllxc}mz0W8mcuA?o+~m2r*Z{-lu|mO?YVNb#fv#36BFa_?P}oR`vd(0KxG>_F@q8w zZc!^|g&2=h#C1eSd&q8gD<=XR{sOUOfZHl$$$w~om?r^B&4(Ps_IAEsMEF~uFfJc` zSJR8uf$dCk_zzA9?#RAi5fXdvQA@vrIOpDQFCOtUGBny&S%zNTnu+dm;&e(ZEj|+y zEp(KSa|1Xp;&c9~qtNj-r0X#tzjG&dtmV;zD_1V{>t1GH%Q{gSS-1!aF(x}^6J5zz zx}B$cnhpH!3x9K;@;c-klEFV=rL-QSlx|rgoMnMwnb3*#Yw`+C=nRd({1ioXGj*Mt zuP^^OJ#$x!B>EtI0j50`T8lgZHh%Good46H$ppOfPEYkC$2|ktJ&NRH(WKomIDwtN z#3!Vgzt2f@7W_30rcWYAMW?0kub&`zk%uhAf6j2u<^taxRY~~P;YAiYW3v+M<5i?; zCt4R_gFnKJ`cczqe+aJBKSsk|BX|Jl2TLH)YS1owCpOMOEhEq`!wmcyv4px48EID7 zfnI##SUaqB6utM;y2N3%9Bs}Dtdx&k^WC~J(KRTD8KFN}(<>IQ1fZg$yG+Hi*=XR@jSb$541DU^gv~a`J&ZF>DGwQmL7M7m!^4*QckklTz+f!OMPqph z9k>g-6$I!%kvgmyI3KA|fl2R&N0vV*P{FaV_^igd|K@vwD=*WcTlml@>Ui9tXJ(?qkkvC zGi#LuL7J%3KG3&O%YJ4l1kO9fN-!Q&J_wu`$m_+&m#kPD#`i=Ww14JXoFaO}gikwV zT9RnvMCz1?KF~KB`~{De#Aeaw^>UNo+?5;ggKY>!g--429W2!bh`cdw?|rI`YVPVq1brPhs7V$&6D%YC9Y5 zQ62oipu>UyFU9PY!cM{NEcAwv1=Zkm*2Nv;&)jTqy0rrxEjH~*k4B{@`WX4X_Z63D z>-GN9w*8UI0|mPm*r0a*+Nf82@PotY7aUOYS~L%|btNzG(m`F*9~k!Rfu=qt{fs7U z?_!DXP6UfQtvs7qL0hp7-{rzxP^c9?K(S4aDI?S|j^ifWX;Nom`@bmw%U60+sSG2) zPQ=hJy}T4RQ&Ekpc6VTu@cEdV=wv7~!e_?<1W$P{mN5(Y1^vp_L!srrdFj!_>UZF? z#AKjGC+PdlPG|<*IQ&xblO3{!REpyr?ZDtgHYcLK>+2VcTzcs)t?miNqtGcLH@0e# zagQ3P>4a)}fm6-*TX5DR`}qp&<_{lq!M+%!Y!$-p_XFFi2;-K@HBEoM5sc5MH~c*? zW^9*!WcRnx!;Lx}h`vjiT<(%opG&`V94=Z0u_!?4Fpv;IWCUZk5f1NmS68y7$>-W( z#}M#0k`(*<$-71TBHhIB`1>C#_$!bfe`t;bh~D{X0?@skxWu;60Q7ngHXa5#Ha$j- z9HS}ciK0i8Hb7Z!Z2MCdQ#o_a5*zM)x4H`Q%f}cs6+U)F^BPR{|& zv0#Yd8~Jt#7BnoWcxZCNz|cxL?F0h*YmBA@+IZTJznLj=3V>c`Y1Ye3)7;As5L%S^ ziW1O!5_jed&4bCct)Pe9aju@+W~!hWlcU+JXO6&>Wa1SX@zfbYOfpa4#!9T;BBzL5 zK#IBDt!k4Ru3IoWJ$0h2vom*s_?3v+##(K~OhfB=Ib+0|adhz(F3wBoL}dW7?bJ1> zFfTP_?N{Q)7bM4Fd^taV?8j);+Pi1Yl!Uim`|A+8Ij@u_VaBvfnluUh<6LhUJcEK6 zWm{p}BXPVP(BTGU7D7vS+IfF?3pNrqdT-b%8#up^&}DZ;B5rJe+9!`t#CuG9Sgj56 zMYHdn{asz%%lo@u_r(r7C_<~I3U2RcIu1aGxjwi$VpILZ*dw1w%=oS3lGkISc4BuE z->fRI)r8``V1#xvu7Hqj+r0^k_EY%_i4Nl!t?F}!vQ3j6{DX1*1BMhvXQwg!3rPo3(@ejkpm*MYcb4(UowwO?HEUNWmo|N&~Lp^fE zel>YJO?>^OPa2jwjzaH{Tb=OBVAT_pc%q$j1FT+ofELe3o(Dh`-~{EA75Sy@CGNL+ z4(|@7pv&6Uu8J`1Y5+Ud5!+3S!>hk@M0r--gGoF%xLiLzHW472?4S*GXKux&pTCsf z9^P562R8%*4*gI1zBDhw#O~S`*20BEGN`o{&Ye3K%k{%*k!lNyPa(RCUKA2C9gnbq zS1v$ikeOQx*p-Ry?(?A{8MMM|=jiVR@UC7T?bk_GLD)T%qhBid?$WeW)5S+>sAQEOl^;Si1ha5b`V z`WL-%#PlYSdT1vqi_Z5o$UH2`*qe~YO2GnG;mK1+4|cWHa8Ny*!tlP<{crirn!PZRiT7zVwwC3!qO+Y`9zU^djlu9~1+ zpJ&ali3cg6g2uyHI;jO75D|lPmB6pY;d%DjD|VuHb_s`|{SEcl_UW++vZAMgeSmAz zCEE}LUTsk)D>BWgaH+5vq15rZd61ZHQ4yC+_Gv&`+Thhi-#CGDWRGS?&nzXJxFy&v z4O%D5$cR;=O~9#TyIg#ttH?_~!D+qLT-TYn7tY^5@?Bs)wl|@P3W(!vm&V~}zYwWK z6_z_yoN0H&s90%f99?jb_BM^eDR0uU{6u&Vr1${6;^>pd#5a3~aKQ*Qd%cs3&xAaM zC2|_#v=8Z6-vVZOSVc2sj62c*Vn`U``DYYaYx554uoQ<=-2sos+!_aXZKmKqy7+fK z!Mkr-sfkA)0dvh`HzC=6dKv4jUS2>6fHx3-$$`SRe#rUs;^Qv3w2R>9h;>uVwR=1? ziuSTiNjCL*i`A4X%hmuKA#F+k{|4T)In(U-aht7AzjSp+kWEPt`2?3ziR4ppE7Z{` zvCW&XS~gwOHNcKqb+8h>Woyk}G0oRsCwIv+D9Q&|p}kbwUSN|vm3-M5a*#3Y2T2{c zg!aNN2XU$`%SW|^6hGUj?jInV0@a@csN+lRsw+_YO;npC{lXaM*?+QG(;;ZJ%CtmbBb;J&$clSATLh*E;TKfp83V^< z%Y@w4_}5FM%BxyR=n9RUN}N|_P>;Tv8ez1wKo)Epxg_WwKX#m1{*jY%(7xS@9Ji6O zk})?=^N5zoO*erFC8O$ZmXk>Bhm67>LE;o!RWqesNel0U#G$$}Lw#z>EY`hSc)QyqFJKeRv zqp?RfD$yMU)`F^+{1sQLq)Z`nC5`eclrWV3x)zTRHzfp@?1kjjwfsgofY0tO`m#iq zJ)ZNhYXmNsIQt>_EspmvC-w{j!L>l)9?Cg)tj9+by_;6OlNJ{xJLc|R#lO$LB@=*H zw={DTIEA#JKl1P0SX2n)Y@U+s*xpUL9;n{`NC4eaJS1tsNqZclv`-4q3+bAOOA;l? zO=(s@J496b(HH)SM{2$T{&9R(L^0A+h8>j$DA9f7#3G}_dg{Eye9dpbBdJ)hpCP#K zRJ#}0aFTB|tzmzv=(+XVYI044dbY27mY&kK8M{Y~fAO0WE-+gEz*m}MG&Rr0X~T-x zm3D!1T8NFmqu5Q1}x51p|x9bkLKh;6+;Ogt7OMVjI2lvh4=+gf%*ofQ@d%7A{K^J_4;~ zqbr$sye=xU=uytb&>daK@J49PLcKK2QfXHxSaJ_jj?i!ZAc#9GDppcGX6f}4_ZbCl znEDO+-yzib!)tz;KXObD1y6@+URLRJx&b@w!T{`#TGT13{Ht4QDX@wdKE{rl2`^{;rIIoa zQ>M#EwSKG+Pwmi8JIrXQ-;{^Jn|Sr(T2i`eddBOvGB5PKF7vTZY51qFc+x!s4Ejx+ zMeLM}P!k+}k2fYc=3*@xA2g8Ivka)Qng9nOc$tx#wkZU=!&@E{ggxcw+uJ`bu}3!& zL`ax<_Y3RT&CDYkkez;3+NV=ByZzliAW=!`i}DY~gxFS-I`<)-i=VIH*Ji29;@EzG zMHkEy#RV8$cXWPf928UH!FWD$DPD7)HDWamdw zDb`XpxTU3%96hO-hryQs+nEr?L5URnYd@Bw3ud4bYcrL`h;l{&y15Bn;Wj~>`h&9N zCPFKinP zAA7KH4l+S>^dT?f7`PWK+noR(f8u`%y_%jHm41}?gw9|noZ1WwO%Sb|g8~^R1Ma4! z+c?<@f?%`h&M~|YR^(2yk+TT*uTC-xaw+1@_2Z|0h=MaB(-)@Vm8Y?DNS)d_S6MR1 zx_J?>aKM%v6c~S!7`SN$XxNiKjhHgTsD2EE=aV}mfNeQ=@&WK~sm0HjA2FYr@VbIX)W60g98y|4~l!X0-_cQ`~bT^*l6{2B zyZH_QR+`23O_SEdI+ATiPWAOt9&~Q9&aF=!8@EasX&pwxhH(S{>8%8Pd=Gf@(YPbH z2`K&t+%>tWFGkq}{mOt!KlM-G3$l53m~^5y=}!0!0}yh^#lzz+Bfg4gTMFIOcj80E zsz3FzV*^knOdJ|THajT$wh3NQCoeZQ??Z ze|YF>C_8m}H^xoV%yss{99mV`tdz4T zD>rI6C-z#SBq}t^+=cCrJ_`?dXpp;!qxQTvS%QvPqg8PRr(L0!glWs4we3n-k-o-k zb;uED{Vjo!Mf6G5p^N{Qbj~2+I>J94j}+Kgb7!n^RfRW&#BOGE`&hXOPcg2X;P3s= zHr$I&0kGIxDwE2(d>3;OKHeQ_DeJ_ws3j9!AHGfueWHtOcy^*LHoL2)68$Hc2Nq_N z9Q(+R-O%SV-W-ReNo5g|f6rbp4qx`Md4=W`K{>Jl+k*}F=78axj-5b@ZZpuvsH)n$ zdGr3gdvqhCM+*xL4-UvV5r#2ZjG&&MAvb`-G=D$djy|1UHV6HS_PTeR33i4|zk#@U z)aOnx#iTwtt9SeM>8a(Dkr}VHZQ_9g-+FmUDMRc-6&=9`-gB7@ZvLNXc4j(3XFY1rF&toq0Gobrgi;Hvs(;h2oyX|?IBn|jg5_D zyZgiYDN%8iEQ<@8&w$x#u(C`(d6z_VQQz>u%0|76nWD%$NO-l2c!Sz5q_7i}$vn|s zV3h@J%1?C29}bQ~R_<3Kq1<6kkFB%l5%iMlOLo618^`Z%ke(1ITSc_B!2unuHm7L^ zXot=n=e~Ck&A5kGrKF>=iU$<)@)fkc>CPc%1v3d2LZzv>s?C}^$}pQ3qmZ&c<0vD+ zhN!sYC)CA;GQ#%&ZNcj3cMd1m=zxRC96!FlgR@t!ASP|ry-DS}W$rSvmoxM@T|}qm zq!ZmgA>XIE?y;!NrF8Blh5Aq}225rcobCwuuN$fJ+zd{)!^nG1s&6n$xcn=G{eIF# z`LZj`+eELeVNcC)|?Y8HAdLa`8+6pTxEB z=WW_w8JZhRyu*5bid>gT74J2t5(bsCxL1|v12xQFH3g!GLoZaW@7delR z-oJZ~w1@QHGi$+R3$2GRN_2|w#L2_}lht~?EO1BharkysJ$i8)^3E2=@lW(U&j>Jo zU(bVwuYhed_zGP=^wTq#(%RUHuH=f5T_LJGI#gF(?$sUBWQak>41@2sN%2+ks$O(8*v-It zrfI1QvE83J*fKUc1Zoqe3wJO=u0tnuU@83(A;GkfSa%JIZ7aGl!7-bOfzpGfB`clge#Y0rryc<}XE|0E=G(;I4uCW0 zd8QP&?e#!zYRJ!E0Y}J7LCM>IH$aj~V)r9JqLd zb0W_g-Hp(H8Wf+KsSC^$L25}<^eEl?KDvXdm#s}z4;nRHFgX37+o)5mT#C7a8`K*n z#r{1(v6X>tK3d^Dd@TWrOYNUI!DH;~eGK<4vfB-S&mM!Ud8+ohS4EE`+De)_ElT9v z%}Y5`LMXk=mklyh4~!wPlSjFZ%-Rs3{Mbl= zV5?gT=XpToj;mp-VFo_iAg6x>EGNfcv!yXYQoc$>PAZ}X9XyEZN?9HkkY>+f^x{c$ z+Mv4nJ+5{c!Srg_0{;@O}=qTHl%!_qrAj8UX4(4iB_R zT9a;1FzrnM4vA_n8Pk37RQ*RvU>LQ3X7)ejY5vGUzaaH^B8fwgZsNQWwib08zTUot zT9);8)P4QbWoGdiD%HAsxDwmd<6oY$tkN){zny5RrS`L*u*&TCe~9x!9LZEj){~0U z*j67YTj%EH7-)}AqsyeqZ_8t~I4l*9WrQ;dOplG)SPNH0Mv>D`w^FmFdpXT~=IZ>~EIJi@azB4hKJ*`e7Z6j~ zLtJ&k79&0Lwk)aylWWx(uLbp zbMahful-0v4*D`;(%nct>#ASXU!p9+qL^_~W{qL=tkQ*5Vl+;@X~TRSxYo+jbm#Ec zG;Peg6+Ve~$So7l{IMkEXE17DS5Q(D&1p5{@C9hX(n1MwS{eipph*QRd1HlUE zE?Rs%LI;}c9r5=vJAE$Yqx7vmcdgVdBl#*b9y9idR{OP5=ejV2Yh`QQ3=MNO^RGZR zDB)qobJypa3@RLf@&s_y5+@Zdp6>iuuDr_*yk$1Jr3q*XI{PxCH~6y^S%51bUjweQT;TkJO1U#yEeS? zBYF>#TkrY8K}m!;dvYgMuT32t9;b9GZ{T}#3E?R}e$;dp zmfq3njKz66LmB!oJuMAOeu~P{kdsFd=j&*|=XEJx&<%6yk>R`m zzpD3jJBv;opw~tu#aW1J1D&eQYYy`A@K4L zcH*}|5_dB>@0@#7`fMW)rH!G%ZU~!2-}sU5AvfT+{0T4hK{fTFuYM-M#Z>z7GY;N; zPAj3SuM!=sQ)4O-3_ z!FgygiYNX@a6P(5C3MYtlXwuS^;l{d3NOeewf1ySuRWyiY<&9eUTQ zcCA%9DB9kd6uSqM5yd7LYpbxT?9_m{R`tZE6Luaisms*q+HtkTp=Aov@)6~ZFnC8; zNG(!SOR($3g;_z<{5}xRxu#huS7RFz(ElEwFITlEw3pg;KaK{u_Y6vMAm&T4-X^zerThsZuAq&rydYhzQMItkBDA+WSnKg-;Wr4 zWj>9-u^x2xr5tQ3?UHh~T6tr(=Af4sHgFFK!{9DPmGoE(k|tN%O)2Jnx~vFDL5*{w zZ`m*YC<`zV4>P0vwE3oK(Q#m^;I?t>ev1>1T$w#Xa1;7*!?lj1}b~>(` zet|HA@Eq&o+$YIV$L*ZfmG_TkPfW&-3J zxr3aqiZXLPLV6TuNZ!D$Sa~`jDqSdLQc`wXsPh?`!AlsB!VesN2MZo=5iB`IbEIN(r5faB)7X;}0InkN2X7}hP zV6g(&Jv|^r^MP01NxV7FF+KtgXPtOX_|Hb^u*@~B=@n6w6W|-U(t~Ss97Hm9zzb%0 zFBIy9;FCv$N_HyY=)b7oMSZ&gHTOc-E?FQD8B{JJ>^&WEsno^>~H{~unRaf&hSB;_@h>IV|o`*Z9Y$wN=Mq7th z09mw<%7D971W3FL5d;$tj!GfeF!2!WbS{5bp}vuOPqr zjHaGK|0#k4uA<-VUR#a~8D~fRXie(eUMs$1&@oG_$OX>{D}S|zC!m3S6F(MV$FSBi z{JR)zYIlF&i7!B+CqAl_T-^vw`&S5dg&;h+a`EDxZ)>pG3iQ)Xc+R7%Kp};+^&?!f z2&{xh$bq)vS0UOQQEsB}D$9P>$B&d>WMNMKNZ zJ;~oww8%zvl5(jFS#h6G*hTE9qlYR@YZ;{K-{!BG$$+B^>4XJv`7ChkoN}%(_O#In z7vSYpg}>bNnu@a6dgfO^-LO`PLKo7cyC;W4p`*sN19_yUzsYT{6nj?Z{idi%!S2`8 zjpovoJ5>dsv$=W9luwfGaN~6CNkV*}KYF6fWWRFPG~D>Q1$<_Nw?@LF#^I-Y{UdGJ zs#id{Chru3y1t)pOI7`bHgbHCW^PpU_dLc!_+*s938r1ULOZ0#b_GLF`6%&G3bt!s zzG5}~rbidAwuy2y((qx!y4X?bx^vR?B#{%ow?CAk-sM+&v4wgi%gNYIn^={wyn6{NsNsuUKjO~ju~f=J>Sjs<8?ZrGt>>1SSZYho)cYFxxHI$4zw|`}Ad8+ZQ03 z;iOIXQA*$yI)WX!HL*@S{^9vH?5TG>f7_qH$pUn@tH_TkdghoN=7*akO|h#awBM*M z#U_x(ymIig6LmGTKR4DGxKt$wutnnu))r_4LA31wPh?@(RS@YPd19yX@NNFfY33p1 zV)TO5(p+D7;G-mZ60YvKgt^=!?mcYKCR&ATb((v?)NC5Y?BwAlqjKtd@nh<;(Xu}u z@`rilqm+fe@>P*i!n4(@^DLI(EtW+sfjH#se%TkVdHR`<*B0rVFzXM~_n3wkFkKvUlv7HM&X{&$M;xxgK26mq%yui8i4(l4uo&4s3yM|HqRc7enjm)V;vkc(^$i zKA#8fUm|RtK1U!Ahl004Z4BU|F?Q1%3wpSSBLNzT62qAO z+w(B&%hq8|#P*4g?}vTr%g>g>ru0yEU6`u!q1HU`?FcLT($tE>#_iT%ydR}*Js|!P z=-G4w9S8K_t$pYe3O4l|I{7q1F*GWPQgkK{uTt?_&QsWtijPML;p@n66|K~hv)AU8 z({Sr+y8I>4aC%LnK`@>3>QE|tz17C%^i^oecWYGsqG6Kb+iPTYCl84;hK0d^D^9DocfVudbq8q10h9%V>KP zS@Zjl~#7qC>!IpHZU{3jTt{ea$rtjoWEoO^ebP z|BK$tcMw11sbpY_BWg+#?LTIjoe^%AzK-MKVL*nB^fT8P%jiOp^B&W>b>wcI3XBqe zmfniT@`w?6241Z>eTqtbkU8&~($po|Li+J5Pn^5o^rjjg)0ib;uL*8mqFH|a{%F+S zHtGwdghR8Oep-{`n(6Hj>@_M(iELSlrL+JKi!||+BS+B1pFe>`0!yvH@G@%1 zPso1f7;fP?ymnJAvdP=h6kIq{3;9@@VW#8)7RU~SSox$NE%NQlKvyM7Ptr>pEJLOd zGH(FG?qE?eu!Dwf3{kPcy6tA-)$YExuEE~S6yhLB*zity&Ax8`DO&adTd|kb@{6*9 z^fa*SIWyh{WNs9x$)^sHSjIDrvRy@{z{90DE8?8jJqwk0RJSh;3W;)>Y=pPfQ&yA9 zi~w)zV*X$G*p$>*e`&Rm(-Zzkw=qsIRD7aA|I?ceKFx!a8ep*ZTl;&JOGbX*J^$PrLh>L9yHqs3oOqHj55a9_q+j3&-CkuFHBXrDhb$-Qp5}i>Jf42=M&zdc!C^e41H^RV%4oLF-wM zC(GEc022F#nYFu#A=4i}0^3_j31&@?9`=2sYd0RRRBi_&A~aXEy&X`q9~RGDH)XDp zp}oVxCc>5b-yXL{@CR%SgI8|=uMB+soC5V;A2$x4Wm}!t%@e6TwAU5&4D4(cDk!g4 zEO>^^jfnn8*g?!bKE2y)8XsUt46N2MgF&{o6SX1OvlX-G1(w(}vqxh$6)|-07N@uT zKN#yg{3Dki_QS!E>qDV(2V4fV1^bQ59hVB>BmUS%-=;ql&loRMqm;VbE;rw;vq!d1 zzDMNUHUM>p1u>EBuV)pWI$#>|b6{e;D7dmC=Wm0l7$a+4ekJiFD&K36rwCi7MBAC+ zYKtBUo`ae`)5v{?p3?CrE7KTZ zlaMH$y6bN}Z9pWUd;ujZ91>4}m+ld_oe(+)aC>F*7m2pB zzV#zNj9n`l_LVQZuan?OBq^Lj<4Azww_~lUGti}EHV1W zUW2TQY^`2$FdsWYm`18rTPL6akus+35(d`6FdympOEhY{u@yON|C%g1E^TJJcF&@R z{Dj}0f2?o?q1r1*H?C<^803UkuEZ7pDNUKtAyWAPeg+FQUZk*2&#=JNSnFP*w-eFp zcy`WY(Nki&_$zK7M*cr`)5g!iF6W?!L+TBBS{=_Qt^{ly7=401_Vm(Yea{O~nKklb z2E2kvaHGJDj|}AZYKm6@vgB_b{H{+jfu-QthCc{n$7MM5K98-s$2K)5bQsYR;>Za- ztoBFH<5K0td$8x6xH#mj90o*|CY80dA);|>Rf=TztFv~jms2kR8w+6b0)7{vuyze_ z;FiBc|J%5>xhJgq)oY$Iz)>(9epOGI_8Q4x3mQFSmE<&V#f^M}Qr_e*cx4?Rm?w$S ze2mbFxaKaa@HI6#)&u+?!6LdPql@Kjq3_>lkb#@7i0(W|0p^azn1<#j-Ru$TeVB;x^-Jq3f(U%q-{k)NMBzn(Yng5 z=02odDurl>N~!q@xg~@lI+GNo=9)|DP!Xaf-P_LZ{QiJ?Jldn3^ZC5ruh;WAf^tP& zPs9-?pv~c0QZe-ztUhLQtW4nwUC@iLAh!AB$}8#iH>Dr$B28!Di-+};Q;bC)=yXeS z&5V!VTG!&P?I-%wp_AiV`dux5mc^88vTD9UdGu&XE)HH?TqN1cf0(X&I{gtTdWQb* z%ed5>N$A7D`o__rSLTSkCF2{tHXUuTYixa{BIXX=A1TEGOaHD|fqMs|!(&dGaCgKg zqrW!2adDHL-oQ5OJ51LX4-U^YccZ(nl@=V?2R+jQxBt`vtvmM({kW=-z!?p#{Kes`dNb>`5|682Qh<{VVB&E0PHVmMOSfiMEh6c2hlos^3ya-9w2&{VWU* zpvB)HeTc|hu}ZP>2e)zkb`7*sKwTTM8=9W3vR)C~7(q_XCw-Y47M1BI0i!x3#Z?sb z)cGjCATR#n|2G$Wr_F$^UQ(qCAAKcz)W_DL%{;(eb zLu-(+!{ADmWS`!2d)2;;MB#Ue%O2JlmrcRFeI9?^_|<^TX8hHM0!j0CXtf^l$^iaMyw^vGcq7^4i1=k)^-0VOrWsfK z#eKpJ(|0~Gv9lkBr!&$%w~N*1eqhi@{@I7ln|!Z*I(h6^w=saR3ZUPluSqx?RMXRc zr*leN96LY^(4LD$gSz_Sw*FdOIy% zj7di)4<9HlD`ve;8FhnOwO!^y%{Mnkb0_mMg_}lyD0Dy9o&-zcg5&d6mZ_p^I*C}A2MLaW(PmN#i!k;tO#2F{2fjyk#u)6v=dA*B=Ef|utPBL{*@~sv!OM`WTkL;C|K;PXm zHd3q3O6z7cD{i}2RKx-;wNUXpx0nZnjs+@;zcTyy!g+p7*eCV%kkz!wF>}#_c5dUvl3YdBE_ix1ify&s@8agLQ#)!&Ei9nUDB`Td zwH;V$pdD*?{!}ko+eD0B`>fHv(G9TTrmU_8(`kSxJ3N2Yf}0y z;ILI-RA;mN@($Hr2Ud~ob9}In&3?_>kM}D7r9rv&ZmRzP|D{cMf*h;a8i_T_3Bj80 zyxg29ApgAW)mg$nvLiEtu<+t`54#A11S10@tkRZ(m0llV5i$>G`#(m?`bqXNEeoFN zT+N|>pX49hP9#PQP?uUN9A-@OknQClTk{wtm9|UTQ29&nbt&$uzqC4%snT-fmr(ho z0j~4spr9OZ3Tfj9NkcG8V7)QqCCRNA1COGdf5C)t{4qAu{m*N$@GSA`R(I-VftJ)J z&Q(9Z6RKWmS{?=Z)|@%*l8k16;j3jg^_8c<=RHzK8{7H*?;Md_a#T#62kUp_ zj<*5^2@c7!aT0qd9=PStcAzFxYQpy$Ei*Obnhwn=v4iC{wz`D6m=XDQ0i9p5a;D4EL{>6JkYS~_Ad6r zeUpD8qXJ()i-=jUWO?M;@aUXn61v~WNLj{EZH{4HVGh#OK$qSb0d6HT^k^tz?IkmF)L^_|6!%@2h*zxfGdC`o(R7*_M1z&R{;67q!_rpN8i{VTh?P7Usr-<)Z?v75;f5d6dS3^vS`xaCVJcQQ7H3FV&fafdo_ z@RyD5(975L7GNmgs`^mlO6M=46h@qz+Nr8iTVZ|wJD zWc=4g!7xfx?wQ1v&_E#XLcGzjeP1jMZ((xlNir@vVkee%M2A=u_m*;|8qovN? zVlKKuhBPIHf^wX}cI0&Ym$*k*7D7@tPpl$c2Q5S3*g)jAIkIRa;!G4Kll`*s&OH9{hZ!YBEDkk1*db(u{_@KHYT+647amhg=b*86j=Aa;|><5)V;be9)F^xS^nh|`0$%qTK=r^ z^YM~zp2P|-?9S|4F^<2mCxl8Gaw|`lZ7<}_jy=3RBp%bp`$s&b=?R5o|+Qy_w^eGTW;W%ZQ?@^WD7ludrrQ)4RQ#i3PESP4fCnM3mHIjFpd5^@!{ zR)%Bc1Jk9icVp94&Qy}}*aV%3DjxVEFVi&n&ElMoOxqqRq})xSS*hp#Mi9a(dz6I? z46$Fn$L;vQ^U_zO45X@A8{lJxeh2htFU`&Z3x7&qRl)I7BtL)fR&C#hp@shZ`h`*D zyabZf8Kn=?woUwwM>r|YedvD~4JeA^J(=Yr=%~CrR^e9WwXI zWSR`td*lYMd4w`+wW9{nF8Xn3BH%g(efwcLd&*+Th9*EzYg1i{%KR7N{i_j6i(THIy6Np!*5^noRV-4E< z2iR#`uIL)QqMdZa6!P+~(9fcrm1awLw}4G!o?dp_S5QLuUo5<9h%LeLFGF0Vm);Ru z;fYq@`Cjzs;ut^6lgm^K@vnafGM7*$vz^CA^V;uapSM?-HqRj5!|v(Mq=UCml(T_l zdjLFjLG`|n5sgTUto^Thz|RRzDBWe3?SXtLBTdBV5+eiqU=BA0>i`P5ZgebZ+KEv^ zS~SIsX;WM{kDgKbg02gN(7Lu+@J}(fQ<)yLVuexRVAec*x^|4}Y%aO56Ig&^3o+>+ z_O)RnpWI*_r|Qk9_MGKWto&#E8X{6BEXw zIJ~glUAS$QW=fIywGNyc5$gv^`lsmVkxvGHf7&Y;&@b78J>|Nvrq>2&m?ph|%^KQ^ zL>RI(ex9QJ`CoS7G39RTa84-Y{_VlYr0_6ZW5i5SyX;a6A=g;kb1LArWnDbv_A=Az z>k$3e08?R*&f9Ia(mC+0q3r8V_%ty%c?P(hoZev*=d=-c%zcgtK5tBkCp0&Dm!Cf{)lixP=Cp8_A6`;RDRu0=?)o zED56he9jT71|b)kElpD0fJ$%Q;{~TOIooRGSs3CB>kIYWMddBd+x_)Fl!FCH^6AlX zUljX6`O(!5xrV+$KlO;CQ>JLrJg;EIki~Vuxy&eN&|`?yAdWkpOX;beOfVQrVB1)iMHq6IA~P$0qOocsrmh}tsgen zFD-aB+4B`4EhXWu9ng+-k%V`us`DvT3r9W=E?BeX(=LnzIT?%pJrAe~|MfSJMdH@v>zQ z4r;OWFj6$m_1Ez(${D9Dd8>VNQX^C2dB7$UWe3pa4s&eR5F_qW+UM_A5=%-s&t2pv zDDu~O*xif8@wd;yLMTVpRp|@A08^up8_|dlCU0DdXxQ%I($Ay6yL^!p8gi@Upl7%Y z2cN5k|H+S(-q&?JoB&opCI7{)i^Gy)q&bd`nkXXD5hW5RyJwrpfC+8#SM+HN#-Ay34*?SbPEtnNb}77zLNDZBuc5wIbn<*Nh8&i_xX~6 zn`S~Qc`<3b-t*(@TBWy~+#gD*!^;Id8EK{#$u0QpCtrvo3{2V= z#R1v+73m3avRiz?MU&7BvsT^}=#~ubYDcVaFUd1@v*68a(L}!9soFy3nTPw8Cm1jJ zpzj)$=~Hn89r*BNoWAB5cnN2?nS(*{tbVT!TPy{`i#-BE!4V3)oOJ0Z-+GDq++-Dt zkzBw&mdcFnf&9<&cVNBLoQ$Z?@{d+bIMRAkpSV|GeI+n1)7~ab;sK|T78zikj0HPw zKhyd@^KZJ_i84u1sh+Y`J9P_QhlV23(_$n`A%AWdiggZ&$+4tM2RQ4Yx7h9ZiHXKF zLXB;l=Tz7=!%tg4=WTolvVyg@`_OdItvD8U@)BJ4Nn)LX*%%aqQm;DRpTUBDIO`K! z*S!yFfBbk1YYQ$`O3j00N#S>uqRt1<5tbOg_On4r1K2S|H#k8@>fKsPxmsLw#mFy9 z65Ubwk*m=ZUW=(cct8W@6;F;q-sOL(sSU=>TEizOJH(7~x?~ar(fWEuh z(J_Lb>*(0Wi9ELC!XwV^4=CMj+Kxy0CRJ(R=+*7aE%vOA`_V=NFP=QY?;GRTyqF(a;*kNnt zHMr^9Ls^tLybrHh4i6+=_33M~sd$qLsXphor?jL)8Zo!v+A??*G$o7lo{+&M@R>fpy&Ox=`LN zTn>G$t`Ydq(+ZQ95mB^mBTnt?o3Q4UIP5V`KSuNEwtUMvHH(C7yHsSmkCJ?e*h6yA z=)XYKD#Jzqi~W`h5;Ci0EO@$ccpX37xmrbA9~~4Gs{UgdVEvq&TE}}f$I!g5P>evM zoP`$Qd9olQiy%aVyGyhRM^!`EKM8}j0myI@?DrXC*{<;pJ2Fb5=?fBoZ>xEBneyev z$p*CTZ;kEzTF?wyJG5qalwNboB+lG>RgmXQ`xBES-yTfj;?B!c5J`p!nFqm|wN}wZ z{F~0*G$FA!L2sVnJq|uP(?e=(^v(^nU1BC2A@P-2!0xTWJuJ;$_% za^!$}Ht494tOMPz0~buuhkHhTkD#^i@hs^aiPR@uOQ|W>7cI7MN?y{~-wA(V`E4CC zeoYu9MTe3@|3P_c=Bh7k)%-|k+-i+Hw}bg{>X)_=*J&4PF)VHxe^0wSaU`@V^D(fw zooCc4PQGtrqLFWvNg1c+ovC*&Kggp*>#|q%x)bCtY#oH{(tjMFnnS?!&wLk}I@mop zIN2pLnJPK%rQG-wcz#R16WgC|(F@45?goUd#f+7ul4hy%G}-Mu-j1p6zr`75e`UBF z!uEqcPQvN#!j~gxS=pi+MwHqB6mb2b)xUSsSTu#MjzV_Nfwx(#^%p;F1Zu8Q{0`L7 zoTp0;&Eq?&v{-=sKWr1N1*1sg#b9+HQN(HIBENpY$BQM!HBv|S135Y9Sd&mU+0#^z zic8%I?!Q;4d^|yCGns|w&h?Z9?8Wt|wfNKeP1JT0x#TIhvLBw0!aY3r>H!RwOFV5k zWYGQq6P+0I*f%A6gy?%L>wnV}9)e)}2&BA+V0X3Tv-=6k9V|aOSbCl~-%;2hBb1Yr zlWfr#(<}KTuExj&BZR_l{Dk(Pe{LgwdNOb3s5to}^+Ppb%@^8wM;URQBFo3l&YtBS zWGdZH6KLlM^`T!&Ni)es@C-h|%x?RE0ddG&`ugv62j>JQ*JTa3CFC-{VD1j<>A8v< ztnlrgflj7jmh2nX4@oh3=Op_jJVEa`3-}&pyvzZ7ck8@(<_l!mfB5@ih0|5!>4}QP zOIZpR8;lIIh@;6N`EfGt8(@Kfq?r}R;xe6xtI4IyuvwJNySws*p^feg(RDjJ5%U1} zAr$uwM@;6BJz2*jXhwlNa0VR<==Z9`Y8z%kkg@VUb&GrB7ZmX{J+-dTc0Pu_GR)pX z>fMq=tlG$_$1C6B+g2i9%@UsdXr&4x!CCp*gtOD2i&`Qq` zeDARoHelJTarW1B$@Rno{81a4kd|e_QZy!J+V2X}1Ml6mWcrEuTK8aK|1rHO27fq@ ziD4yP@N8_-bqwq?(7doyd=A&(XNEz>eru#o24N>O^avB+&Ok<+GwAkn!XQdr_ZZ6% zu10?BMIPA;7P@d899*W;tQ_`?40~10Big7?OvSe^$a=||L}ZNw^X&jM3h+_ZoNER1 z#k8e@Pb%QGft8YEbC1?Or1xPJr6gA{iVoK`Y`pUu82R&X0+R#Zru)hMj*Pgi+?j+R zH-TE)s`J2;v*q~}3PWGV-c9 zPiPs`5>k2KNo&kxa?%FzuG2p?NjUd!SP+{@3ROH<0N;0(Iz>qbEyRL_mNhFak9{iS z)Lm7;lXRC6RlHK35)VU?E@4 zAuHa&WNbRx#*A>j#Qfn2N9oJ=0G?%}Uv`pLRJ-#1fF%@bXTg{M*Uk0Y=D|yYtfi7u zFy7)9F)3a!iyZ0&GyDXHbZ!01ES}xCg6eQ?fM}qvC`X=?nMr2T-7M|AH0}|CV@a`} zCkOKe>M#2uSGyD$MUsY5p#OCo&YJzF;BXdI8B;Kz^ztEH`2isV&?q%I3yX)2_apTgX2`FzZyX@F@6f5=Hq1zp)te9!hA{)Is@9;uUa$ zy|Pzw@T-`jHgl?g*JYlfF0w^AiRO(7VY7fmm6V*%RADxTR(+U#dw|9GwIp~HXIeEM zdBnPc?Z}cBXM!c0utnrmenRHd<-$l6x$`uPw=RmsttTyu4qAN!+3#Ah&{`Max% zz9F{%v%l=ELkRZr)%*AisFcI&^A9lg*fae025y+8?Bvb2uuCv@uuEHQY@8q=zFn~Y ztAYF`8svlvW5Lb%)i3LSEt~|9s5AlyF3N|bJ2&_QZ|@)v51tpJ(ORDFC_)VUgIa*5 zoAMjxE0|t^9}-X%)i$g=h;)PZ#Dl+Juk8lgJ^UhL6B4TGnvXD(Fy){iE|a=ImZ00;jUVrWWGWK_{ zzkrQOR@nmC8zpBA;F}4O#lxAR(TBRx|0*wyjbl%fSSp-GtQk3wb^5sU=+MMSCk$>7 zn3Da|iGF7Xv`!j(lE4}>2o{mj@~wNTrv|IHl)*~@uL>{yO@gY)j4SWKk>{LsP{xM@ z7{XG5@pqA(HeLz21&sEygu|e_N8nDJM<7e#3FU*Fk33HNJdG7;QCn_KPc*7JtVPu} zR^>ahy#0~WK*Yjk@_|^QFe4W{8KtU+?39f}HUV6!QG43N&4g;kFoOB_J?@^okLI~* zR|ETj!0={q@*<*(5mc}c*+g(nVRBsT6-mgcrRf1S45Y`cBzLFJc`ReL`%m{9Y;y6j z_TlBr7Xh~pd`dNR@;36xO8mQH_uoH5s}at%J}bEA44RoWCcX9h>v# zn9%Y*4gK3Q$Q7Q!TYc{m^pI@EGy|_3*9O=ii_m3dVqGGh8j6kECL7w=ozjB9NkWTN z)wV%l1}!H^K%_a#QceJyzfDlv+YQ~zYl-E z*DX-rWsCX6JZTds>B@jt7@Qg>4eP6{V}&hoK*0eRLzn-Ik$lG0;BqqzM6h%3LpQ-w zT6E46+>wnAA;%TuFmJ63!$VC&U5avojV$#H3`pb$lfy4X0CSL{TP$8L1uf`qVGRc{?;#9!S+9^7X?2f+bCl* z!$JkCJOcl-%>PxSFwUeX{uHoY687IF3NDhnmnI2OVm`td1@((`I%owc8OuBQj#)lk~Hj1HX^@b zJtKR;KfErPBE@2R1*If|r=~ z(JrCKx4aIwvB*QVxEH0!pT;JCouL29!g?%DI3^1#W>`03H=Bq=iHWt`q+Y{*>l}|a ze}nShG{l5JY^&wi0gerM%UP-magtZ2cGVu_tw2I6)?;0Qa|vatHXCW&8P&1`$r9_J zlX{ccR(qN;iH?OKHgq4(lP>O;+}zXO`Jku!3)Z^C&DkgBbydQ{rkaDhiRl5zhl6gg zQFO?>(4}iz;1IT72D$k*9Bw08Pp*p*EGStWylB{5Q`6aB%UKiav(rpaY%L;vy^t3Z z87ZL#Am^l^{T=(pbU!=qlp*mRI@B2H**o&pT65*e2)hGZl_?$@uof-0Rqwz8#ezFy zUK;xYsp7Q{(&;t78Ol)WWM|Enh3XeZeI@VAOX$7FxEOCIF7{#N>nfp8)0a>j4)cyxu50E@i3tY>A{U(=?|H`mt<8sO(1{!N{PnBh?oU#989fma+=FWQ~4!H%c zCk5#*#P}=&4dfK&B&i8pn55RlhRv~=eG)TH;Gx$LB~lPGm`NF*QU- zaxLX-do3`3R0Ga1VcBkbVS82;KSvIR3kS&0<55+h`lZ>PnK=6xPfs3c7Mk(Xj2JRB zz&1jV^KRANiForz^39R9S93xnNruFbO8f#V=s&1|yv6n1X=I$xT`qXl!BoWLrOmaC@NB^^klWeXDfw z1lKS4l?Ght#55Gh>z@46({<<@Z)^l6o;-HY%F)e9GrmahVo0O1b6uGNrgIS5=W7^~ zQA@8mPzs9P&k=Gu8}d3OcXmPzyE<_O`;f$C;)u?DqhpiL7a*Gl6c9FB?*5#4h1(6> z?m3a4C|UecY8RbXRx6@^*fcy=w+@j!y3;8}rTeF1Vg=tu+%*=_$9vXYB^E`m>Kz^b zvCzRL!FvlEkI@$~6|2ZPVoA~G}Ym*foxGs4^1>d$4ik%Akz)=BAyJ-NAZ&?@A? zGx8dgI{gL9_MB7s8r;=usz{qngz{}qfVJ7=!_$evDJbc1itx<<%JnxxgO0iR?UIB} z(2MxETMKp49f2c<0xuDf!-Cm{O*|JrVF|jR8snhUEumE9tjn}tL7dl$H9kJX3o?f& zOw|LD=0;GJ7iS+E5xl#0Ek@Gi>>2)YgzYc*+ztJ%A#`Y3n(DOQKi$88fhm^lo}l~h zop9^jf8yJ+&H(XPRA`|7_E^i_D{V3Gp_{)u#m;sYfLtFW?T;IBZgXlUv@vyUK!74u zH%X*5y92(bq&wraqr{S_MCt=5J4EM&b2O0H?dPQeBikp(={3GOx-Z?$eF9i*b*0Zr z(UhUnLLC9b6|nwV5l~W1IN!rotW?Da^Sv@$PsS(VOSyOoTQL)MvQ3w@u)!ye#=T(& zJ8Pw<@tB~CsM+!iKKoj_FGzit2d`emFIX{2a)=X*;HHr)&+>P(yyqHNK=@moMT)8Z z38se14$wFk*$H};Pi@7otS4VH2SUS8$eW^$#3vxpT7gfH;od(Bmb$!=*m zV^;ZFP$~UI=5V`&1I|ccF_u#fgjF3X#6E8IS91As+H@htF!Q2T)z!ndh{B~t{x?VI zp20A%P$(A)GW+6z_rw`fP?VIHlHr6!MoyB_a#)_9o&!Th#`bqJnx=;R#CaZn0t|5M z%I6!DVZoFg3t3FX68oGwMWS^@QeY!*Q9=#_8!t}@uJQ>vL7u*!W%~v{?T`nbn57Gf zCeq?V;?YiV4jT>Hh!vHCL)HM->95~lY`!UK3!GL#zbw7;S&VQ>&{;+eg7^2s zwxUOnte+_F0wv>;Ve%00{c*-m->^dI8=2*Vyt#>dubj}njj(Q4gxo#wwI}Z_3>5}Xw#$S*n~^;_M;U4y$n&>M{|Uu5$k}}dttA{^hx~i zpS#jKncylUEp39KY%)+PAW>3VLu+UHqz?V_>l-~L9di2x*!&@HYQ^0d%+Vh>sJG>i zW*gaH0L(r$Hnw|hP1@MsF(+hpcT6XGy{c+Z^9LHV@D+(Q*(3eC@uQL;9&8*VS-Wp| zd@9MDBy897>(-CnM&3lm)yY}aN@_%*7L4&2rl+;~h-QMx?W6SaSt{V(IingL+20U< zBt|qN%cqpZJo6MRX@L)W3G*{HZjCrbaFW_HLuN~=Llh?<`v<~-et|B{dEm_ z?d|^TfQ%N?Nm~1-2}{u^Tey%}6+TM!|7|LI;w*F}Z!i2A(->%OhM1WmKfDF)8Cxxc zhX609DjdKLYyxWT7^Q6k-R*T!R|bYsE!{-Bcl=ZlOLWzv>TkAl5@_}Y889ZQ&-Kf1 zlo3{NJu_N^NR=Douxfd(-1C%P^$826lN=KghXUJ=(v1_I8hRzy7_J!il4ZxhcaP}Z z2qC^S+kbKZRn6TVX0OXWIStz*pJ!Jr2G{2XZFMXEj8TVP{zuw~Z}={bs0rF-swKIZ zKo_QmY=J7}g|?<+e*Pj8F(At!}XK$4tj+=#c#Io3S1lOh>(@gPdb+z`MHT~-2?WRI8;nm*Wg*w zSbSc%bWR^KZqe>YGChsRY&KaXzr;nk=(4M83VF}xg)G)z@iF6`pL{I6!c?$ZGiGDE zPoDM|81`(wh45{YJ8;%B09qZ2d>|Oj6ZpIft144{m1KX_1=U#_ZKP1Do*kDmwptOC zE~E4)nZ(J3NbIe_*_GeKxe|4CBTkabvyi?Nl4%-pQ~qOuFM}EXwcaA|>v^OOl?iZ+^lyUcw_(r6Ke#9B*TaYCivyn3(8b zhG5v^@g|IiF$Pog`z+B(VswX|6-Ffb`UdlRqZ%_m3JhS-B5d}0bCsK9T^(2;OrIE{ zix#zaVtj?QIQMtE@A?sTN6aUPBpYecss6%-)(7`TYJsNX{ak+bQrxl~I<0|nZExGP zRE*pVy`|Pn`om@n&4%g7`SKVAD=;keq?BRNqS%oO@0iP5|8m*U}qTIib} zOI5rVE;_`hwnVtSadU{mUXMUXKfjQ7;jNeC4^ufyMLsZ$A~Q^*wtK>_X4v6*GSe%> zl?`>5I#Br;bKSx<sy`@N&*O_ukh{D46Gtn zwXHBSLbRyXvThZUbR-6j`|YNAyYzhRqep2lAIskb;MVPz-oKa9SySf-i+es$1$j%b zbO2z{fc`(0$-}2in*eH-c743I4E_t2EF1m%YmV?W!Ml27!cF7144I|lks!DI#J6~O zHo)Y-;B4eBPZkIt;)#WWWZd32Y^fu`^CpnDb%J_tQ!ZBGu?7F+i_8eCf&PZ$0uTFp}BXsoXr5G@3tAo%& zY9gY@P5z+V>Y9lWc1`c`ql9oDY+=@72Cy=3hfTh}Y zL2>yaFKcdjF`lwMnAqEEuPlX3*C;w%8W$k1>;x_hc>${sUc6k@0ug-PEvEb*f&ke^ z1`%MEL3a*XwbjrnI#&=Q(_a!6WadvH9HjOBunk+Qr9VA}a+dfwXmYeMM0LCIyPLW4 z!EL-H^Qu1b=KC0n^qh_RoLf=#9$I*(mXz(=_yHUF{oAN`zDo{B=HWzai}K_o;8VN7 zLo|yKV1_@qNGRbw%GdLs{v0O%pM!qG)XFFnQ6}PN5wQ`nQ!vxk@n6X;9m(7h<9l43 z!h)&n5EJ()<44tr7Zm)Zjm|*GkpcQ>p`qfsf@qxlep*E)@NYb_?4ZUg1lymGi<$9;oZ{w^!d})=9WfySlhN2Nl}+Y*R)g6^u;-YO*y#!!SUt> zH%*>&?DMMz_;VbR_z(P^ATa1}_|^|U%Q(?P?>)GP4^-bp3L6AHy0W9dJz@MdJ-m+~~ILEOF=>RSIALv<@VyVBuXbxNG3|-zW zU|YW41RjQg1tyY>8PYL=^0Hp_B1#Ecnh~tPWQgUu%<9TQo$TY>Q1wF_Kb5D%u^Z0v znkH54(Wiu*tWzr>{xeisL%a_3rG(7HJwz#;AimaoK1gB_-CL0Gn^ zjsG5sb4t-e@$v4iZ=lU3yJsUme*MD%T@hRia&)|=CJp{>zBC}kWBLKdWj3H4>)mhd z)2HLZ*nDkzI*haS+tS+9U{LY^oft89B#qq3D=XC9gv@_TPU_Y~gya{KL_vRR*@~;* z6ldlL{(auuIR3N0A3HNZhaODim1?#O2BoA_W{TzOtBR_oNHCCyCad{~YypR5Tbg0goRct~ zrxzW7lFaW=aVxxS^Uiq(FIp2|pt?JLSAmj3JEPq{ei8uys@=(@O0-r_;zdWU@$Hn~3+z?ES#x36vFPJnqI3`v&JwbNKGTKSoo$M*9^@bPKq?U)WpV-3f zjCGjl5c|UeNKbN!(ue_tmek`nj!AdHG7_HuuY-Gz^d0C`G4nxv6ZmsLpMzex3zs$yLGR!X!H&i@lxB*Cyu zwGt9{G9LeHI_~uFP?@Rs_lD+4`i=8rTx0tvRM!6s+;8fBTY6ESIbX4Nl&Sc%MPq{t zo01jXL7E<0r1~~pdh-!igEv+(QPL8%JdPcWlYI1-f2Ec$$1R~DU+3B>nF3(hPc7+J zLU!q5a*E*%cUksc1z}F|n9X*!Lq#OHNC_m;HA=gAQ_J@OPv0m4>SHwZQ*4(9J49?F zzD$+AT?}Vp2*>Z{P6iWYVnuuXqsNaM>IeE8TJPWK_qcvjw=Pj$a7=E(_?$G^+CXog zcs}KtJ^J_aE9@AAYx@M99?^Bp`S;%w=58^7+WIm6eW_=w@t$2HWico6^DAR&2{ja>Y9WT0gk0}S zTlUfYX=Kq`FX4y}l)NHeFYQ^YSUq>1Vt|sQnRFJtp}JR!rQ;3FijQk})M>(5)EQ4y zzaFA;+AL9`fiTeE9f;pQRp`S^HZ|^DBtaGbd{q5fn!Jq`ZYJ@4C|d%3(3MF|O@tKW z(-}A`7apbwjX}F|E2!W&LIZ`TGFfjQX6(GmVsVzltB0;q6v_nId2`L^9AlB`3_nv{ zcFCVdlv(=B54-X2ft1xjW{I+csUd_v>>7?ids#;15Z=^W6T3_J)977TB5@5!{$3P# zXmc`23bvmlSdRfIt=O-yAkJ`Q3GDJw)eiIIrMavRO~ku{Y-l^s&F$_Lhq;2mK(f7* zwRN+Z=s#TFMzslXnk#H==YTMBQI%IW1=;aU;jg#T%g*kK3-o>pDf$_>;gvhQ$4||h zAi2^xcLr2ZJoL;P&xGH7TzflDo_V3H?T;7#Qli`xg(D@PKap^e&v{R3Z^7l6NVn2N zQAZf>zT;EP!4Ayl*Uls@;m8K9AwMh5FUR2IHa#O(ieiGm|M!DfowBWbqaN>xrx!mv z4n7@{^rTiX)GWWAF8>IddMFPOR}bh_KIaACTzmxhUHberQ4a6Ed!X<+J@sZD12KV& zd#Ax-`8boA3h#8XgUX47(t>8n+Evu=Pu+pkB{-XL`j6c}Yudz^vB=xfoyj{KBjA3a z3*AgLMgrvfA@OeIs7@Cju;mLyv-AlUm42L6yLZp-#m_>}c+iy*D;gZ0`bvBdnc%Tz?2MTsge(Opq zV3>9bl14Q>#fI0&8i`_~ZzY&*rF_S`PgpxwO#SPjao{c7vRrTtOHw1V>4@-E!LqW4 zz&3LsVy@I18!T7`AO8=V>_IiTX35qX&tsi2*z^8^Hv+o|U)cJZbA^TGg)Ne^8Hz8K z+q3k94=sCNh~aVQolop%0jYqh*gC-NKIR(|k8l4svl=@qvVac_I1Abp??V2zW(q=v zp175BaFqirv*EhHh-DH1*d8`dW)%xS6;H>=p6}#79y^z!~$Yug>6}V@7 za+ZN*8@6|>vlPYDjy-qWxiN9p*0t2-Gt4PP1xhvB@te@3R$!odGotZ zQE7M6mW!TkSQdaav@q;!bUQHn7RntJCM12Wm4BS2>PkfN4K`%pSnv~b&5A9^^^_}$ zH^qu=fxI3+J3HaO)36?ywde)jprT?wSw^3L-t^=m@6(Z)fa2aT))&T%jOCGtJJC*g z6>}4$dIMOsM(T`9#1GNoiC~^3nHSfLu+}2>1mjJg-IE2U!97C53V^Vqmp9c-=?b_V z&%Py8WjTn94XckgkT*McJz`>LywJ0#ttl~CLi04CSe{Ric)#Ub_z`3@2V*Za4X~9l ziV4B5BV7 zX3+<@SCrXz8g+KWC%TMPp)w~Qj7!sJU=s(L;uU_`+xy|2{Jnk(9UJlkm}eFQsv6et z9}tC4AcCD;b!R>K$4f5{zURzpA#s6c0)&82i zW1HabRI6IKAoDRJ1YwVEI)knBjvZud_YX++%VccdVpdfN`edo@M+x-6!sf{L^XJ!R zNt)_rMUL(kQF3#qmQQ6#k3Xh})3J;E!~|XN%{rnI*Du9Ynt{J_@wZP&KW0P}zJr%+ zJ>S-k@g0T-wQXhWS}neG3Umsu1Ec?rEL+H5x}tDFVcDU>SuAgV&9~OGj(O5uv%%xD zxNdG$M`EUwGvS7W&x!EM6t8HDN3NrNj2Pq6TS^aW()j5{Gh=6A^X0gb1e!v3MO z9~`3>ImQF#{~>S9?37>E;mvQOEZZJb-1vhZwqAeb82c1g^&NxrvQ!5qxe9J+N5S@sVEcaBUzhUe z36x#ab@V9fOHA0SLa;K80u&XRot=WXUOdl32CdBDF=!6v6e7)+RNq#Sy zVS&ImtVOpuYHz`+ql}u52JcqlR)#31{9z~Xq+oQM#Nj0G@OoR4HyIz`bx2RyRg$H* zlj2_f}mP0Mqb15Nu8H|_8{kKQ>{}PmixC`E4!;o2MsNq%Q z{VdJvwoq}bQ13%Bbe-cd%Jx}AzBre8 z=5Q%0H~j}r9wWI)Y&YYALxM1j#R)sR^0YYI`i$4lkc(DC4d9Tx$x0-2oAw%BH4pcX zdwCS@%@qX>tUgOEydWi=g$g2oS8uudxwiG8xv^Or;v?-QS7o(ojrIB*6I~9kqlbou0MYzomJWes`K$o()ZKG&hiuGD(I% zY|6e7H)Tp-NnGHgEAS)9XY>vmE#>YdmROsZA*52Ae$5>yq6>HbsF6`zZfG2x9Z)0g zXZ1$25Y|i-1?D{2)Evr1aji_^ZLNgG@QVWPiS(6(p}}?PUcKZyT!UB3!kST1>6&3o zRvLDYkZoGE+~7k`-IFIUe&O~|Byv`-pH0OZYQR7E>O;9;gj(8UCJz>7TeK&|3V>^F zS;K9r=bIwV&{$e1+r2z6R`PYXa1p^tOP_w2rMLy!g};D-%DbwIry1c9@M|5~ifh7C z)H?&bJNOH;kLsNaJ>vr9Fent^T){ftx)!F2>Prkc1SZWx(#DyXeWCdt6V3j zH9@C_xW50R=-lI4?EgQ0UAuPPYSlWd<4P$ypHz~z6$zocFG*7CJfwy&#I9{66b(6) z?ixZ4HzDkHN7sasXm{L%=%Q3eM$&oL@B01MA3YvD9_{eCKJWMI^?XtowTm^akH}d+ z6>qm7p02fbe{=m=jm$esiT-)CimBu@!q!Os8Ap|~w@A#)JHybu@z-V<)Z#MEOVkbm z@Yl;sViUF3uyzhP2bz)5$)lRE8sFtnXTWn;~i4x5gVuz-E<~lvcAeO%) zeKT1sU@VH%9<^1Trgydi8@6K@H|99}2txiHXN9PUt8P317K@^^!#TJ^JP0ofS&22^ z2C}!0kDz=~`Q{_eQhH^UKo1Z0QCi82V+MT$oecUyT?j`s$62EO%oDviLJQb4#Eox( z54+YV3_9nJvJy6U!%qvhuHr8=U49o=)m)k#y$AYxh*!PqDb{7uYL1ew#KXPFIXdUV8n=i@?G!YNsuvs$i)S)bgHEH9D@B8p zzyQIkblF!I@fmuDp7Ng^(mRx-+sH@lyxc^&U%Haa1ISD5wxXd+(ChTI>C;iW9l{%* zzn;&riE>9~C!!w=_$OtI2eKPC`8x#*f_FpaYImgjPfVY@W9qXbn_;bM?#;mmz?XKw z=OYy5tYYk8{`T3jK7YTdso}n++ikqiyiszC2c`#_j>rO|uU+%qJ@um-!>W?*-GgxZ zq>X6`;pW#4;{18I&y$`YFs!%@8AeWywE}lOojhc$#!Q1YAT1hP!jNKkdG`taE+Wq1 zx{Ms%t<*%|3rOKPA#wO9F4Y73AP#?3rDe!%VuN8zS&954#boIxnt<6> zR-H2^S=raaH`zGhM$OoJOtjT-=0uB|REUgUvDaOFFoB(^uLk7#%f z(@(I2ctDl_D&)x4M?B0Q)nnrqFS;{Arp)59z?DW&W+A-RL=xNeHLwb2Q2)ZMq~lQC z!69Ca3(dwa8GQB!o+Abqe1$WOWQ!Jxcaa7re8a~dsy;-k&XhTr^-0AIW<50VThlm0 z`8F7Oa+-(GL3Ix|_sYKw4ai)wy9x%Aavw7376CeuAw%MCll$x6?+x{Norp(<(!^GM zl372Um|F*l2dVW|R=M6L4AF9CM+vcnD!XMO-&1iS`DDJsnTS^OujE$cW>@6lboEhs z)HfBopQD)@;7o4z%@d!ry<>I+jL+mdZ{}zm9kegqnsZb?G9t?rhP^&vjO@sj)P!tb zd9MQ|Fd?#oiu`=T;@Q}T3$$`4jm1OwjSrp{Yvt*RsWr8*|CFVD<4iP)==ly$zV}Bv z=;rUi1TK38!8k!_X92EQF|&sbqw^-Y_m<*~GP(K$?c)e0gapqk(RJkbUiqurla%w( z;$VE?ntxq0#ZekL=EI`~zN;v9r@`~zrD)jkea7*9@|ogB>T;Ej;3vgt{Umg}L1@y> zr1U8;JkmAFS|IJk95_*wEWtdTkECH}Pn@^nM?`}Wa(&T~tU1hpjmG*;m z7~ybK8%!d!{?1O@Cn*epArA<+RA#?zr8oS=7QJ>4Ng+!H{Q3!zDA88b#B%I6U8ari zz>v39>e0X9rk%UM!Rz3ABCFy($<7pf6U|EaMC-gd##VnD>7K~Bnz$?P$9Ol6J>>14 z0uz=z^O`}Z2;4dK`^f^d-;;eq1(cL>8cx@#)%nW(ciNxydGVy<^v=4rwl+%0*W*y# z(j)sAlHV-xdqDC(wsn80xq0-#G~$)tW8>d&B)_-#!-3LjC-HwYaV*7OT;&}L?T}D;L z?ZbtiV#p#ns$2m$P}GvKc_hcxLgX)tEr1yd^&}IQ{jy)jP7h4aS;E}-^Eke z`M9~XHRW`@4*FZx%|m?6Bz=(XJ{2uHquMOvPyA~MLvq!#Up#b?9T`1T*J%^NlTJ+; zR;&fKo7Nv>Cwu`LKYo{rZvuARV1~VYp>4<{t8=4a2uBI#9b$-2(HLyWK~8xIA7|Q} zwv5{e{?A<8bLcoL_xW^5mARw;&dCYNfRnEbWJt_-;w7%^o>ngU*-+I3T24z&%RhFj z7+__c}rqMhl&DaWm%NJ5 ztDZ?IUB}@jb_R5zCz%~ujWV!Vzo;n6%==RVSiU74!Dzu8)OMgkjfwd?w&n5w`= z{f^+wr~Txm`P;mi2mIwV##%vJbQ=8^iav4>tZ>pi-A-=K_p2Y2nFgxLp&k#>OSm|; z@no1V$v4aw$nB&gSitgM+@SJPjc=n+mF($7*?|msbyh_4o9O8Ml;cm4F!TCBeAsV3 zFU=()@))?9X1f}!x=5aB$X_tiJM39LKys-OQ&?AJl*7mhQVo6*y(z|%N{`uD6Vm1r z)@kXc%@NuYPo>TC{6&w+GqPlV8aVe)^NFNq096dSp^Hv4OKI+yHs%_%-8<}(GgV3Ow_%y(%6QonIW_k=i$=hG8&5Zrx$`$J^U9uU8{%iXfXOPF6+gtt-3Q(|&6 zlkPo=!4WDro9NkyQGyqN9m@!VR6VpVZfdHrsR^q13rhUj+)PLqX`)vckl#Fc@=QO} zv{JPTxr2TU6t30>>O_ToSG#tPlWiD;TYs*?f!h;5^=ERD^z~z-1qDZrR8>_S8S~;= zyjp-7Hz0PSSVLRoU0}--jvjEalP!?l-7!Mz_`PFHcecL5#=D%iYl@ygr`DUt7c%7T@Sod^g_BdPtZjs4_W?V9+y&>T zkdt5F`^0AIx*3XgcFkhLgRBU#*Cg+<3w=qvDg->ZKxvE*Tg&&ZO|H4dRGPCOI?S9c zcvUO&3w2~b)n%Yv*(B@BBYu1yQ@O`X?FqjG?bll-Zzn7YFV2*I3XC928o+6g*JF;J zLDdl!N1;a(k)kbdAo2tB>is~lLw;2iRk0<_fx1QS0*f0$eWpw;@Chd2<}+EL?D}TH zws`N|uvrikx< z>G7wV8{r&vYW}_>j1vjG-GoY}hF)=!bgZsR3aq4hPlAcj*hnD5QGAl(G%}^Ic}Z?@ zLC$8OCgw4LesFuB=)%Ko=rsXyrbcjJi#8@ryqFs_01eQ9SEIF4nZy#6uiy2}z)54| z91e{OoWmBa07RKU4F(PLK$#)@8;Quyk7WWyk-N&lx%uTxqsPQ`7T)1;QOe&0|Koc6 zV|-p^4mzeGR1-CpeDkL?(uUB)!PPnDG9Oan3Nn2PL(T9Gb%k)3YSS^x0JkRykp07# z|D+UCQYi)$eI_x-2E4tN<1qA4xG+LD`4k`Qkqm2JJfaRxIL3D>H(4Ycnkvih7xYF4BD2yET0^l{LJnEGGjo7UjyjKqzMORKQ(yjGxcG0Bp4 zK`8@&;_Efu2)FanXcO=l_Z=kz7bG0vAG9y?;Y$Ic3SYhrYwd2|NlHl{e}6`V_!IqE zi~VMe>j`B27Jc}|JdwZr92NN%ZN&9gjdEkmWZ#bn^9?@AifGZ17}Rx*In$`jQqy-z z-s{4j%v3lj_#AU97ZHTX>KWYpUp1M6-du#7tgu?b=^A@{hYUw`&uq0n2g zZilF;+&FK*R{Y9XJkC*PnM$zPqGdBgcP%6l6=3;Bqwoei4EvKE-;0Cqe}Zb5TbspCBVSMX_eXS^^rK$|LrH{pEbG8q^; z0|AbXv%ZG$%O5eR_&_bOzmaIE`8vt5pVcW91%Bl9Q@?Y&_`@!u5`PB6+iJI-T>0#g zee)BI=D}$1=eOUJ8VET%*lfbm_X=}+`1fJJdN&dqJbGnoae5m6K&o=$G#_2Tt+VN&4mT5%UP1Y8+J{#K6y&2j~Hya%0vD&7ZwikBz()D z>%WHkw(08Hxc!Ky=(3?YZZMG1ebF2Vu zVpjvYpbhnFccxR2~Z>Ps`lpdz`k4-c}hWBu-AS zE}3zVbo2g>Y0c5J>Se%{1A4&`E6Fj7!#nwFIl;5Ri!U`RW{kqiqNy@p!_oYl=tn7p zz&5_KcNkkI)iHT$bKsM?m<4@dibF?PG`?iReIRiM@k5xaW8_z6`w83yuvW(W(+_d= z5ZxinXN$g5t8Mg*?9s*--oBdiHpn31xG{<=^2E$2R}5j8sLle8{?@)evg41#)|n_7 z`tWs$cw6}y{)>VwrVy^Ok=gZHV>*Wlta0l>IE@+8Xr6Z2p_}htUdZB^j`OEKK+^)&V zpC3k6qP1-!%aA!}gM4WbHdP3!4^$N#=wpi-SlugyZ?UK6w;`LurIW<*3(m;ukV}K=v_zT*vKV|9KN+Le$4;+;Ks(Oz6Gp6@g!VAiF24D7=}y{XB~@+3yNFy zTGq53Y}z8bNyRsAw&LS}lHs1YTTk3@0kDgBj0gS52|9xn(MrV5n9hQ+nV{Gr?ngrFlB zB{RI%S#*H8@Pg0~s$K&8|5syaSi~3yN#!t^+Wt%-{L&RoDKma)MEDt}`V3s3q*ipP zK3iWlO?a;OdIVDr&&4!l27}S!!^ybuR&{xl8+?{}&8Fh5<~Rnny@w9a;E2b9KbdH8 z{pEUp9{26>z??Wv*~58b@bf!AH=WLMFGhy8!dskmS}B?=L(D*?^lx}|ZdJoBRzk~6 z#N}sq8`n7_)-GG$v@B`UW*uSKFce@;B#p{8K4QMPec#@}fm9#!>+3cl5B}aoLDz0X z8U?aIK2Z3XzwHn2jnMI<*MS8i@ITuHZ_mV?;AgVKHV255V30fX%dY0JVa|RzIR=g? zMjFq``X(nS9z?LsPag4AaMusLn;NBhmspHv?+W+9x9#9fuA&*GB-3rJD%u)nbKTw< zI8Ed6CDnCKUZHu-62_Zltc$;p^&bQqSqC;moBD;1zsK~7lkvzKwnn^M7$G?(q$lDpMVb z#QgPU{GEFKT?6d^^dK48afmixh%>X83sX@#ltyItbXby`HJerUNVT&`{*zuJmML5P zi}Xzb;^d^-MP+-&tc zqCt}%uwWA=GsIF-q$!9(XO<2+#0pgN zAG6~OdMEz}uX+}%p<_ zFjft7i&bB_{1tN~Kg*15P9od?Kxu8rDQjWi-XkX`-u5j+_64288am(&9_OU0eMu1! zC`W(kRTZv~egA6e`@8e65+>yhdVF|21SO2n+$r!=H#B9vz!SaKo`iUgU{b02@3C1g z{ByeQj(zhJhj%2Qe||vDvZQ6ZH*S&zd~et_F*vajz2_Z)29VGV3go0ncJct+cTm3d z=k*(#hJMsbM}9OGE<+9z)iKl{8ReI6w585ato`LslR+}516~;rGQaYh?DQm$DZN-y0Z%EnxfNqz zx`c_&mPxgx#0&j`)Sapz%40>E{@WamCf!=HuSF{E z1z0l_3=4D7-^9zQ7&ftMs^w@`L&MLFwMF91m)sU^yrw4^4glwqLgg<3jF9+R} zi$DN+*iO67GTMO^HT|;c=G`dag)Y<*bk-&eNS)COAEGdgZK~E(!_(e1=hI@*Tc(E5 ztDX_8*lBxOLD{*jWrF8q^d^Qzfb9N68+0i__qt2@MrPSwvuDY9!3Z6`Q-QV{n3hDL zdds>~l0^Nt~9=Ljhb7>nDG~`Vs?WhyGws?gYN1n){MVOkhNs|tyvNcoL?LbYN+Mf z)IFCWoU2BcH^r*XhE%J_%SxsS4NLgsK|v^G$rU|*Kpq42Iz4FL(i?pA=#&&Gh5V{ zLzA;2)=M4*Z7`W+t-4Fk&hn8P7Nq27Ef6Nl>yE)!0E4Hp#X@nStaoj+#=v6=3oq}$ zFu$;s%Y5LMue|x!IZ($O?M2A+7J3gigLf>lR+TA}cSO!%`OgCOv!gcxmmi-v<+jTb zSygJB(91X9ilbQzaU0^CB&8((B1G|ZgmS$>t5gheA;GS0(dDVck`!D;Am$n;v`#_? ze&S8@n`!w@OU0Rl-1LwcyJTx1n_%h4_%QC4=jiq1t>psS=sgX1E{hyM#PYC~(TR}~ zucY_V@n5|L{ikbJes2(O(lubCzh8Ap2bjpeZz)F&>+y|_d@J{TOj_XPN zmgqcUZ93WAAD$u`E=BXmI7ryFD-QP^EK|dMN2W4U(o?3Mv;qTkI6}t16lXsY11LB_ zC#eakKUwOxra>7%LfmEqD^rzj~=qBFJnlw)B6*A&|iKcAF88Yb2^&nP!kW_ ziYU`uu&qc`852Dun&UFgJa!7ylCW&w%Eo5;=CUw6_%ibu?uuN|Y1!NAq z4p_j;rS1jWIm!9IdMt&1)0>6z{*?T>7W|FpOL$COo2GH4))I71WVPTvu+>$4+c5YQ zsnlNZ`ZHI=B1!Ij_+Evf&~Nqp`SZ-GtKlN$DCpA=S$B4n7=5f4}1yCyeh``wVU_6p@MEL2u9+KV#c_vw&roQC={TWurXETHL+IuXDyfKKEuj3Wf3&h4cZaehPD;0M)*;5#PK3ZC0?%>ES}UyZx38sxpAp}){xukScZ{bNL$7uS8#S?E z#|C|TrO!thuMDJ|LlrMKx-UAz@^M0quOK<%@;qf#x z=A7T@Ufy2k2rW9wYyNw!kmn0;o({1G=A}o2OxIYx<9tlB01#XTR;HpMv|&p;_?@R= ziRgV@ypF9rop2iDAFKh1o(U>57d1X@+$>0iY4v-7o?T*p#ZH+r0+H5!Ln~R5QjN{PcmBP%(hGjqwYfR5c{vm-0hf7yhwP8*hkuV^v*v zm3b7Oy{Q7kAJFLNlsqVutt^TKeu~B=&2VgRA`nu}Ar*%{oKC z#qtzVh&wAgO>hZ_&hrb~u|<_u)Vx*sUj|c2Ep#PkMvx;U4y=82@u8=evuYSKLy!pm zKnk4s3`4IuiQ5aDg`(4`gc70bf9=wmZe$QnFrd0dsJ>|a5p&{@OUWm2pda#QB66ME zaRRXO9Qw)?ADR92tj9U<;+=5eKKaF1wBrNDLmas1B8O%p^ZtYY=Llg6>CZPmEP{%F z3XYIC>*K08>C|K+A-IkGqP(#9HP}?-RQ-VeA3zb8LDnJ%BZlKV8e=qLht@gS+z6QU zt!$P3{JAw}h*5I%7dSY}NAR-EC;Un=Sm06T_wWJr@om*PnkbqSFKB$?CQ7wTxaJ%7 zXbG*?_NThRXUc)%)wv>nY{NYra5ZJzT7LnW1dB+eu2wNnWRkn z+5xGUjq$3j>7gNv1fkqR?wk@4`F6i?{CnAuLESv)tR!M@6{dLCN<7(?|JMcBG|}i1 z$A0~4!76%vD7|Ni?#o@N?%`t0h;4_Rs+5ywIzIS>YJoY-7ju%~Vg%Rda>OEde7^Gf z1UK!YMSE&;e!v2bCNmX&H6hDPwX`2Ij+9-EPlI1{NEYnXUhoj3HY~lxXGAQH`79Eun&hY4$J347ys2fWQc@Do7hmz05oRXehKfi zIkGnS*UtRSt(&Z}^!>&PnMU^CHEu1*deD`FtG)6j^xY=|8>D%Zr8+{%HvN=EN8(5Q z&u*{U7~s~o#JgiU!;VqR;L8WX6Y55XP-8H0YXJJkw(PvOb$?{GxmPOhbFge*Vk3>;kKBMSt9!peIXVTofcV6kr!d`G2qlE~|h zfSc!tf;j#Wl&EXyqfqs2vMbT@)KfF9{aa~J*CvN5HsVWhY`T-WSNR#z)7Z! z^3v>%(PH;EzTs=ztz$UwnJVM*xt0$5a2$N(s;7W?CVaHJ7%d)V)vCpc?0=@_(+j;yfJaWWBVoI#_pbiXzPm-fq2L7pyzx(=F=*FddH`x%t zr(A=E6)R1E*NEFkSn-GIvsJ8C<>I~Ky6o9T!uDQXbvMxpFEcJkA#dQ2uUHGoOTG{s zE37-3Gf7_OM@Lw3`1x~V-{Sq=c)1xdr-s{slnJ~G4)!NiY`sfjYW z=R0(|EhNe~NNeu|UkT7&e3n*0gdXHYe$#Pd{+KHKwdREC%=BV(Lpo-Evs2oCc!T(i zoy{|?I2~RwKT2oym6sMj$*X=tpee%4J7Zdb-i5Ummi*Q$Y*5g%9`oZv zc%DhL*eoUnp2N2f#1u2aiN4j|gb|vhO2B+m55C{T{A}opxSEM%K=umQfOy;w?I+Kl zJI@k7I}Uw*JCcL4P2sW$R&5dXVXAPYS64lh2;w$oicrCoWK8Tt+YdBc<>g6rqyIIY z&uP$44h=f$Oh&m?_rRPK(tmgJ@+uBbBeYKo%j>k^6XK2$Zgf96j{K9J1T0PiL$yB+ zAy59~SguN|PUElHPDVH4^9XAu;^7`mqsPmBer-E)zD;IXu2A%SU5Ve;nC@9wU2s$V zBseJ0Gwy7(3ux3P+fUIH~VJ5=6nBR z0a#(H4?k=;V-K>{wt9%yiOc`m=V||6Rzp%Y?$>MpYZRIlvmZG1ivh?#)fX{3a(KpjYknWFQe3!@Mlq zbjuqtWMU6|X*xXTMZw<%@F}3;AM(K^;6F2wqYap)pj)B(8_;ohVuTv^Oc6$Z@e11Z z3z;5To%Dcu#aeKU{%ly8MQ@2m=0St^;pYUl`yYJQHPP%M8LfN~2kF+MMi^@eONoOB zMFM&q_#zlOu2uC8XLKB171_AQGPjx4aR3vr6Pf5L{17s7oX4$&Mz|)mwLUu{`iVjQ z-Gu+9K$0=!I4a6?)|u>%oG3T0KZN5TE6)n7KN)@cjA6=ke;Swn!(WRJcmDPkSnWjZ zOhu*OiAhfKD}mz88!7(wH3)*BBRFa|&!k!jo?9YXY;3X|bNKT#rb={iR6F1)y)&|B z&&DZsA1NLsvN|%~c8N~uC8;ekPS^_L*Z6&ur)PZNOFVMq27`nWRxZHxi5ysi z7)&D8J+o!({fHukADH6}G)-A@;HQg(+iZj0QtMN`%;^ufAywU7iyqynq6MRJDY|>N zum}y*8J%oUopRf{H5)yIAlA)-BX3Y+P)L>SIixwu6Ypo#mz2{&f`E$C6D+G|u>T9a zqL&H}(TbIa9`IE2ZIQocN6Yn;;E1DO9><_YinF*~(4|nC=m_m)d@^v`PIcicuPzL0 zqneY?hDOX2OmU}CXEtHs!7TjY$K@I_vjRNKiL>8FD?g1ezSw&hkmHUJPa9QiNS?(~ zpU4D`j|p!JE}BN(7yP{FIX3nZTq&NA;cyNxdF|d9VfeV6Mkxd&f}d|qFoJ_RqLnoUHIVb zRKTrGij-=A_zy!|^|}*93U)^c^n1SS8pU;)=CxhiKlt=4M|S=&ysjk)=YV+a2R9Ib zEi%Ei3F{!dQqt`8;0YmQ%2NF|z9r*wnvomxWwTgrxiMgz0pMp@W0OpPgnu?tt?UWM zCsfmH>MBX?mnnzP_r`T^9|AL{<0aiUkBqG9FYc#>L{bmhiZ+{d+GG%y7+{%vCoAzo ze2KJzHgnbgZ?8n_VE_4oYt%1475*6IragzBI*g7&BcsTYrL{qq=j}Pcvi5{lEX9ON zmPcNAO1x6RzZ)-xj)BJ#^a_h^U;b}bpD_9_OqXx4KG2(Q6^gbngWu9T*YFEMJE9`l zm9&rZoWuw|5<1G!#z65&aFL-H z<1TOV4SRD6S!eX#aFkV*o0jjnnQ-7NzT{ppsj!(&xbPJjnpEz~Z9X(l=aCbIk}_1= z1B5HN?cc%IhOG~aRSAgZ+0*+EJ(?dvoyfz9^0H4;LxD*RdI{M19P92no`l>$IWY9F z6?r>;qD)U1$VKH!dF24;36eK+Co2Y5T6aalsOv z*vSL8nFB?;(JN8t#XNLRB6{a+;P@rMt7 zO+DF$Zn%yt3_)k+s0_d;W=MSqmsd}@5<=XAxA85|mw%9Tbsv)U@bE8C?LJS8k9w&u zJpnvSEOAOi{who4`DcXA3>qHAOF4wUGNC{_WX=`6VrdcizaOeEBV38DlvdfnUv(6W z#fd(vBYWkh&x)CQ>+rDI2C27wJl+Z31XcWW7-N;v@Wu3wQ)46esVAHp5qaMF@Q@|P z68(!Lep5L7L$KYf+{;q9WLe}i^4rJ2rCmr$v2q8g^xCOz7HYX%Tf@@T_-m1m$MGrR zJ;?5zj?`}_G-EOe=&d4r=(vwggP@@iDVjJ(xPFZ5CR$Ar?XuR1kY&i)_esUPdsAam z-6iT8Z?-OKI{GaH59v|{$F^^W%{+95KsI`6Qxp?f6^DwA#3(hW!muKn@T?6uUIZ@7 z{RkXvhQ6-_?K*IXjmr1dFqp#+}^UsiRb?a9US__yS9xW&r+pWM#&30APlFE6Ke z;j7Q14vV566Ox7*zDJD@$8$8_wkZ?EXUf52)q31@|Kxws1}!+~!@|GUaOpGQd3nTI zWLF2Ke8Ft_Lz%t-&BsqYTY0+~MG2t-xuVjbCB#kxrQ7EXZT56^fpR@%17Q36n)a_#2Q&Rorw;&V<=9L**GJ& zpO$9jKIG%;5I_zO-mcvF=>&4rMU+due%iuj2dc5D_CxJ2IC!sApg2kZX(V5OxWBLb686%U2eZ*aonjPjOX7cj}oR3Snu_uCC*cnoI~JQTcssa zsaY&6IsxXXn4&FSK@aeXEk%36z^^)!@nsLbnu@|yt4H9bV64E91hKIs)ULBZ`gg}` zS%GWqNL$OZq2q`CELy3f^mm}Vq=sFT_(p?u6GM;pGN=sk#?7)p8=Olpr*<_$NVtb{ zm8~V%93Aj)QiIGOL>q6iH5UDhL$xba|7Bvb8&qVts!NvcE7noe=+GSTBBil<&1f4- zXrTMf5-%U+O8lVy+G)}20FV8+nCe@(v1CgtS9<}&kbN@CI1y}p4f&@MCV{mNQYZ%h z^P41P^fjpd4Iqo{3O!Mv{5Sq$>R1W?`6zsS{F+}QwS-&kdnwo|JazRjd> zOT2v%zZsF%PCKy$Jv#iBhjBw^P2xB3*Zf97lyXfZ`f(5VaZ&9)r6GYmDz+nQH(o(k z;MF~gG~fKG?>WL2{m$X`4UosDl~>=lMD7O`M+udRz%*F~2j|4=aL8okXd6=V>Ej3V z(_vugYo273zxU+O)XCc)r-dOcra|V!kV{a5k@yis$3zss5z| z?YF9syrg@;RaWX4BSG`9Ws;kl1F^;XDJOQw`gqXJb4Uj?I1U8cNF)xTKOIHE780v| zE;?4KdIF!&Usn{~5zN^KKRwmJZS-T-!5po_X-sgtz7{^}2X7sO zbGD_xriEw%aj7X2S81DQJH3K<;W2c4Oka9Jbu!dcHm7u~qmCW2`Ha(HYMDqFL%KjZ{w=Jd>R3Z1o_m-^fp*?dCRbP@Oi4f9M-l zzBoFNe|OR%9>Ey|p);*IYXO(}(L>bbk9;)ck=i@dQnKtdB~*bQ1J^Mb+o>k~A=ULP z-(Xs_AI;y2wocB!NEG|fEW=ZXW{Ij$vZ&CkIX2o+qpS<{$S40ua@=;>o1D;0EcjnL{>MnvywV_sa#fsdzRP*pkeP9f#+W`XA@B{;!0 zWN5;=v;w(F5|>S44N}ArMH;!X)`qToyGiedehhwk(uR+%DNBhB6O%YlXu-0s`f(kO z;?Bh%aTb6RuM+BQ5MwEAH+tYe7V_6>axIE*bqOD@$7d9yS*oxGBYZ5xz?)v5$y$&?w71IV~kW(CWm|1HX`m z<5F@O@Dkc$Usq+|xsGF*Y+6HV2EO^jZ`R@+gD#qq=ch9aV%z)5Yy(k{k<1KR4ZbxTFd`KBXs|%S6-X zMh&#q*AINWPsmA}3vIZPkw!3YnRa?w&ii|2J&-32C7b&o0zUe$0{N#AUa%LS{E)5Q zEDMi9LP62MXRf2J7hYEN{@M-2?=ir!yuj9f7^fe;+%8eWvq@?vJdMZX)bKCK40H`l zOz7tth5QH;=IL=5RnQ8sRf6d4b*1sKp5`l-n4umU5l5&@L%A(`wOt-Rj`RX<>W8dg zQ%1#hI3(H!iY42z2WrU>arx+`B3qu8$&;9H*UTzzDBrRLFsCQ2XH{8QM_kbRdOds%Hp%`}!RmJv65Np2>vZdmD3Y@_(o5opgN3a<6g@fPO2lP9|WQYQi z^YhJWko?kX=pHlnn~OQz4RVfrZhd&Y;IoeBPm{A}1zqsF zzkhAyL2y6lmH%%+gv;{7#>X~r>`4ZA{qGU5A;*XB%5@g;%E5p-TDxIXU~8=dK{*F` zw@^GrTesCmzS>3gdb+R~$c%;qdx2$-z@;@U;I>V8gtPvDU};1QNBw$bq>+SaQ#;Tr z>?8K_gghV!Gpr9a?a2A~;S*3fqWk1n>xmI7?n%db9j#qhy34!_bZc_J!8YV=Cx$D) z^-WKnkUBqt10{;=O_^j{ACk>G@=`Kz@^`Ch?-V6L!DLF%)==^Lh{(A95th(o(%_5O zTTi(~;ujw9mMwddEF>J9fUZ&q2kpT#Y>`zhF_Tg`r@4*s%dU2#hVX{pe75=`HQ0E6 z!BJYBzKUdXR5|Ou@}^2P7g2f$RmZ43wac3G0Ji#XR)%6Jm9UbcGV^7s3eKn^LfCAl z7Th-95T$G3ByhsY^P_4@?=z2)r@M=NNzPO$Upgv*Hs7j9BN4eoK7I^Zm~vrisHX@@SrF z7S6GP$d8vX(P}+6=78);kaC+nyE{s$_>1E(JiU1>I<*mV=qnej9w-)mMP!VngW6d; z(b#ABm~0PE62~nqz9HtR=>|k=)WCy`qo6(QMI4$^D~*8GQaXqSiAu@-^u%e2V@*){L`w zKHipt#OuRT7|X4^eFT1UX86F4liVPo-VZrdWw=h(#61|~i}CmFU8mhC4#>sh;Y=FpHBEGfZ%H3g~SN5E@q?f z6w79qv>~Jy1w73OgEuFJDUzpLUFgpo)o(o)&Grh=_0UC?B=tGS|9?;K(?bYWbxh)b zRJ-X5hSrU6Uo0e;GgSUG&kp2ov+_S>^X>X$yz$)o8%`^s<#@5`w^fi%;K6qmH36nC zqLjV1jv_xUJ8q`hl1fjFo|ZqK81%)`{?IdO;_z>7MYjDX-=O(^_A#%eqFais{+bnl zlaC+l7Ep^w#U!Ds5%BEREb-fe>8!}6&y}|<9WT!&%yi{&?6>0C-W8TYNVS!Jrw?~&WX?a(+w zHyyFkE9u^po>q~Q{HaIS*VnrP>z$=jQ3GNJmnH4KyJP5&MPXms+Hjli&h1iJY!BgN zE3WW8Ze%<ll8`& zuuA=ydFLC$|0lwm_)^*1qgG zt^OpjZX1*HQ+aKr_(L18-Vkw3a;_O;?mbO1xvQ)&boCQ7cA}mSq><-HZt3zsraruf zqag*)SSQ$k$6GW9m|8oLGEB@<7+)l9>ZRpk4%Ks7sTyVrnz!jzPD>=)U4W zLhyXrwP7Aqw;ln0%&j5IXYJOkpDxHQSh^g>Va1C=YmeB+BS($vM3bz~50L9xsR+>* zatH>&k;O3OtH3Q!AD-7jeO!v20s>0tTNyRMW0VaUzWmnw{Mqw<@R|pRP<4D-ew*44 z-evg36`u1mN~y7yq~QUvWhw&Ae|#L{WgUl_ZTgpcJctp0drZ9|KtQS?gcfEDOvE!pt- z>NaFbSjCe)YDR8aM;oc%S#h3RF3vqxE0y#zxH>LbIB^K0++sT{1d|1mLvB3>#u&&DWQ zrvyhH52&n231|Gm+MR+}H{hX=w0`pP&`HXcqxOICqQ__#_mwFZd_z3ulC`IVs(ix2 zNo6VY!H?gXABPkwIHst?b#gWOH+N?D9W=Y;$1gbRYRaAU(f$ycQnW>=SOd5i&59w~c4YkG+lJ z047U{RX5I+Ib3TZzMnyuIpHwOOEU{lkBzZn$?_+m;`CPHx*Lk)XS{vn41V+;g}42+ zhtwthOywPFr$pv&Y(M8Bzp+SL@THbO>I?w>S}x0-$ER6zT8>i4$S>j@I{g@8pW!3V zIY=t>fU4s%fx?+J>CEhQ`JW^+1rrpsluMQW&0=aFJ&}qXi@+}?-v1&@7L@36rUW=Sxm>4FRVX6JeAK6gPUz##&JO_ zeZx+9Af@6T^7W0}?DlG0SdFY2;QTU;8Do6g!lf>0;7(IsSc6s4-uxn6f`kP5;3qVL zl$h_F{>P!>k(I@Dg{w;e`K^^Ej}ePKIH}&d+{vy%XlUyi^Ck(7t%NTXqRuY43Jo;$ zjeo&L(qb4-;{U~3=;R+c8j_1;u$<9Z1*T1ABDgi3fKAe<{9Z#qp z|?dh@j{<n)6SlWoz69bGNiHR@}EnY>McM&7g!i)>S z!c?cH>??%rLt(mx8b|D&*`#?&(bfL}%gRW?U(|eKCe}~h>fpz(>gQC54Nv~&&-t5w z!9!e;^^-6QbX23q?6Ifw(M@CbTh2_jxL|o<27G3q^JXb}d9NrIKHVG;2wa*dm7O*M zLU!oBe6=L6^6J6e#EnEf;wOvq>2S;e`F>Ulyq(NMUBiz8Jc!{YKE-o8iuNV@PC*vc zV(WS6ZxTU6hqn))=1TNk7bnCmuuRITVlylLDWU|`GUHdHj|^I8kct7|)MOM+8euh0 ztf$1MkB@~U{;>K+qh^Alvpwdj`dG4L#NKX-A|= zsul|f4V3+D+3J!6v`-aFkC;}O7{npSvWlofJE(DjM+WRUWa}2?ylFAEY44__{IpOF z)EI1_5PSq{2>jqJ#cSu;t|jlOjiBp8e=!np}JG{3=5HghrRmCesKR&6BkC-XF? zBhGiyJo5!Rp7X>f7~#v*sKJ0?%D0j;^t7FidD?|mVva+o3!~)udfC~{v_t>ms9n2Y z5%C8!ZoMEP`Q}oRcf|C|y0gf^=}x=C{rG#buwRxL=Em+t@<~BZC1*MqWw2tuA3w>& z20i*sz99~tQGOTZ$d zG;Tk;*~3A9_YmXC(Jrk3}pJ1rl6?pf%(YTCL&F5k1YwSVy~Iiv%5BvvNKf zm5*-UY{Nh0U*-g@Z~=3V;SHM4VZw#r8@~nuP&vC2`)a11)z1r^$zK?Jei6mzwVF6L zKNT=`l<&16kkpFFTw3IW4&Jo``zNMS#Xnik)@xq#*<4G5&=>ldssQinHa4pJD&&QM=q*!q@k9|Lbn+v^)@267 z4fooHclY^UXV}N1=0yJW?Xy7A+jXQ5%N3G)P+w~&S`6HD+&A{wS$ic0 z+fjlpIfveI5H*2=b`sT;>ddhQ^%Htmg-+K8rQaZ&1KPx}B+)G6@X_w>1kp8oWP}lF zNX}iqG)b`vJwOUx0-uOOJ-#PM3(nn9DI9{7Gp06}q{^rkz8jS#yiYz6(o_Dvu^mD z(F>Zw>Yro#CrZ+3E4D}Or3oGV{_wxsmYoFeeQ?_QjK3valN73?opHLTo45&9tTovd^Oq=VeYh|SnaWpW+bO~g9beO? znwhQ7Ew?Z{v@R(Hmzx%D=Km9%l5FSx5T5e}-SCT7QA3*HA}O?vV+oe>1aB_Ds|?fZ z&cCBAq~7M(lh6|=jyzc>SoRFwa{;#zsf|iZp_$&$aPtz)$YC_e-z`vUW4TRPR6Q$W z-(WxGL(Wn3pcx1{VwZhT$6E?65v~65VTeN>CQCDQHN*KmA9L%`7vF)BI(hg*(ohPCmO14U(fPLyJuu$g)_JrW`9vvsHu#lzoJD$4n+c;K z37y7^zt)OwL93l=>y`V>0{v2f2VPIZN`OAU@ma9jW*&`a&($7|Kn<-0Oj08F+jlA%5(F!3)!<;{R$$ z^P{xstXt;qao_{}jgGUh5ngA#c!!^p8OJ?8vd~_{Lq7Nsjsi8nj-&Z0V! zU)0t?;eU)&I~dKh;hv&w`S-R)Dd{g#*p)OCia5hAsLmL9rdAsks%S<#0Ox`{c@Zrx zRXWzI`#5&=TC4rqaeLIe|M%-CzPp0%WlH|&f7Zg zMk=8ox|$ZOwVWX(aw9&Z_2AJLrGVjwo2JxSW!UrsUx47ti0<~?-J8P-q;#7ArD-UqR+`a zL42?C56R38A<4kBGBNr3k0cp$K(=xVQ&~e!)6o;BOi-B`+77NE`Hq2X~R2nbL z2hQq-cIiypt$%w#B+}JIc1nxS{ z|F~RZ6Xu-w$0UA@;7hLQ!Up))I?)MZ7s6aWLW{4Ayf;C1!sWG8M3oMV{_YG(MW#aJ zY!za3t(u2ti~yTeQfSgR;MnmPZ0&mbysz)WF2U;6nDLsWR`VvE#=9d3R!Z?^Q-m#un`5LI zr@5V#VsECLUrfC|KT&qNPf-P3E@z8;EK+vDxrT__fQ{4H$>KD~tx)Q1GUtr)%pYFa zP@Ta}wo5hXRHV7PYSj>Dg%fOMeBOxWvj?Rc7rUSPZ7hD6NIJR&Y~O)=D1k4P-@0>4 z7S=4qLEjC#S;e(F9h4I6OS{Tt<=n1fYW1h{ecL|W9|1x?-MDm})Vh&k-{0PDMV?fV zy!IRL$#8_NiXEONJ`Oub9{;(->V_{*Uo5m~8K)WBW@hM04l~lJ2M#E;Z5$>JUp??J zgM7M0mJ#k-%4`4enLNpp^2a7aKkUP5+@(C-U5ey9(V2tB&bKougO>2sLz%a3)B6}1 zV2S&%&YBZq?d#Tl6USjUjm(zY;Eh^d7#~N?f56VC5{O%c&a>M?EULEYEm}Zs1vLA8OvOgahW*!?$y=VUlVwGyB6Td!gJ*0+*Lkj5 z;RKiFHRk6~hyBXKU1!kA0p_8<`D?%PBpinO8})ZjbBX;yusBFBXvnt6DZfGqe~I>U zJ}skhRLs~VQap;vfp1VICR4TUfzFW2f3O)3l6W$2Z>EQzB)tKCC8LIm#D$QR33lNS zcHHVsB4yRO8iUJWO4k5cotpoL?7LZqb6PJVOb2+(S$W&8tCsga0&GCX|3a!*y@{m4 zD1I4|6i;|yTNSewQBzLK7MCeW3a}{`QRSO1OcVThMx#tIPo$fKE8;3nE)iGJg5DRGC98of}o~SR!^}^xSU6 z8Pj+$%4$@`8l8C!SOZ*-&XEvY6*F_?JfKWx(Cez&BVQVktB9Iv!+lIjv8OjRfJ;z} z#LO5APtl$L1O5=2ItxlV-91YmH5P{UiS*-Z$n&ot&!WZ7))jp}+BjnKcAl8SaxXZ; ze(dsidRBJ9a+?yOs~dg*Zy!oXc(@PR=^8q6fAaUqHb_pee!Zm6UUD1v!Zv>yDn;+W z$li=h*=b?9+;~^}+V=6$?+o!3fG9~zNI^n%s%G03WgPHdTrTEptq1eB+|eZtL%)h- ztVH0fSzl>SUw$f@jiWm)#XqNL!#7YhgAqv@Y?68UxRyt3<45|e&nHnP_o?MdTzxgX zfE|Z!aiGSGlYYa&F#PjZW2*45Nl>`K4KsoD9shs;zE`xXc&hQ~pMry6>#X(gymRBU zn_goySEqjnrX_`t!QbDh}pKlt!HHt}@0vtbz?jTisC;!Tp&lH~f11%>L;KGBZ zW^EWTg$fUZ_wL7H>w!rlY&JKW?fmE|zUIVglWO9y8k%mS))6<2oNLPC9E*48pT5#I z0&Z5SN8Z!WZ#4!H@NW)86SBo!)8nRFNSr(V*_3bd4jy~J{_orOg5^^lW{i@n>PruP zW;xdfVAoiXc5ojCkEM!^BH0d-X}P(@_S&6mqJv+KHKva1dydX0Ok*BEP!*8}3k#N= zaR@!hia!k%n~wJV`H;h@mP~)d!^iN??Y;{#5yH{%Pmq}Q%uRD7x0-AH@0d3GXbxSn zp;c;yJ#FI57~V~kYf1Y9%LAVh7W4F!P;H{&q(9Q?>f8Q)X}4~Pw{6Ryxvj!l|2cP! zJdEuSUQ*C2K9efi5PFrCV^)`2zCYMmwL0sx0k$;UWtPC16&yLCS=uUYU2c=}kIRsyaiP(!hF)doho+NyqM{iu=fgJbwa=A=)0UCumzT~1J) zUVdb5_UUD~dJr}P%kF`*Be^H^a<{+rf{%bLbA&Z10Y~Q7ATDgpAp>D*FGuy%z;}-x ziKgnK&VsYQNY!()2%%nc&RXE;Tx7OUz0lcwbF<^Z4!_7BH*M7Nry`!bJ##s_u2;FV zK0!N@qJT$OQ+Av*4f3SXlc{mF?1f4_v;@9(oqSmKe7&n{$Mr+}@7TuU;PD&iV;@N6Neb!X-sc<_p-Z^LzJ&h4(C}A0 z|H%P#iFw(JAy)WAZ8zszt`g5R$>$&UXfYG@QjJ>G&q!ia?&`;Xu}JfC5O?}W!Vc|K zSafV&rCnbvReO+Cbn~EA<9R~V?*8&PWG@vl22v}@#s}dWPbt|fhGem!s+(bdptL$e zGW4BMKJm%3p82s0zq}plM&-7p(=tJ(2*X(AGTbQqWe!+z;v^Iocl&l=hOT$Ot^8o) zu$9VcynVjNWZ#%^WiKn>9oaiok68E0?yV%*^-GatnSPY9@e;(^PA%ys#qS~8r;4Z! z&?D0O3Cv%`@NppYi_&f;EN@b#HWA`71=&57W3K$4oE$`?%g< z)q%(40EhRjNJ}5*P3kb~SGzxaoEqwh^fZue<l11L@8 zHKMG$TWv&Xe$;v|E)MV93Am-HAJhDpHt2R3Wv{{zp^~sNXet9-H6;eFSjhtd?dYc) zec|ge`m;S`H)V?GK0(-BVlP?5^db;OJY_#e?}3!Xsa}`-*}`<3$}|xXn#X$yGW74r(A@=a`;6*6>KbOef5X7f7hYJ#tNhYx zBK9-?_O02o4RpJEf8*U8Y+JMdvm~Ks#v6H=X+3GUe#pFqL)|I_T>UVkMzq9HTfZE; zu&xcsnYRygct^Is3XRH4D>j_&86yk@Unm)afn@=D)~8|P_iX5oPar>%xguG1IMG!M zQf)0n`;UA=AolSG=Z#d8Z#^3RmONar8C#7y=ZGcXT= zr;ZU%XKOxeImE=Bmd*6{mtO)v$|1|^<$|7~gya zvs@r9cTyINu<|_0PT@`?=4B48nk3-pZgu2Yums{85%GjGbmGbH&I7wvM8~+X!l(9e zjt`+rUh%|EwFV`@NkC(#+&fv;LfLvvutKcZ$#MUL*QR;adzR7EyN7+tUQn<22)y2@ z*KLyGvYTdvkWCD4!9>8le{nn~dSa*z@#QGMv5oA-#5T^VzKr7RQmH;WQ~M*1D!xyt z{0N1bHImI}gRA&O3N`q`;O9QL=rA^&Ih&?@yiKtL(41#HqsEHxBg1$ubWf)7m!$bq3MYYJ(UC1q?`14TuxAxTkjSX)Z zR9OtyUOI|OfHx*m!QTtph;Jl+T{^MfnHD8GNI1xSYI;OAP z^Arn1zCM+oR>#JTU3^Ad2GDP|8==2=Xsb7;eT)lJ|8>%lyudP`|>j z!xQ}unzmBNZWGEO{y&-o?eAUu9QnLx=$|EL&<&)n`I@a`S_ zJqJ7Y8ND_#{4Nli(WM-uS~I@3;iV6Cd&WMGCG7XD8kgN{+qb9hb5S{$=l4-o7%r89 zZ;3_PpVBJHxhE5AYaWq{bB4!qOk3*6j*NkU_GQto*y0-BBoXHXz3Jl`pu}63(__uy zaRM1{J9i}@#9Oaw?*aMm7dbL z+Thoqzij3X{z1-r`fww#>It+lIS@H=0=nBHD01aOt|*a{KEdOAZRTGul41+?VNQFL zRnVDbqO5DnM;TjlBF&ssJD`$DkafCpA-%#7roTaHLwxm&;EDmVqnG^T6Xg%j^l!Gd zp|RMK|CFz`yNXlUW`#t}Gbq$twz51|aix&nL?%&)G5Ae(>t%}3!2p@{<8jI+oVNTK z$#L>1>(6nV5^|H$EU?i?~Ah%co%UG-qu(p@4ejC)|b_ z3`T^W@{|8qASfcpKu>srk3PyFzWF|7{TL(sSl(Gm`hvQICpQx^%l)>Aw~PPH93-j8 z1}fo7;bL-2wO(&2$zzV~(iBV+4{9$N@Y?cOrHJz#eml5qcP0@L`&!qCE-A7v?!xgq zAY>)k{q0g?DQq+s6KaQU@@&+!!pyNAqZBs+f##Qnd^n2yJx%H4kR*I+l)F$klXcRD z=tlnQA*`h>wvxRDQuexvAG>>{xbeJP&dFBn@m5aZVUkw~8fMt}&U%17noQ#Srd(+h zta)j0WOBWShf+h?wUy1CK?y2X>w<*=S$QvY$-|B0m2IpoJNRbaP`vYbQ}WhTf|Wlo zN4KDdHYOLs&WFxiY{8Yv{y^}>g~CG01!~MBc(Rsj@?_0``wsCiaNwPfha)37vXo-$Wq#_@)(Kxm*uNd}vYcLG5c=Xw}7-1JfJUokPO zKy%eGgAFR#p|Ascx%;d%WjbDm8B1(#)8!srzEZGcY8hBZa=4wfc_U7%)a9AB9sxFx zt(Ie>Ip8lHp(yjInEK3MAV?F1En3RY1RK3%SB%8YaV)qPwCfI*cG{A0&6nFpPsSZ($f@IsRuKm)pCf(s4BVFO4-eSQBr!v-{ zwP2LCB^6;XV`%U(1PoR@23C^68-aWxMe~r~-)Do)V1|y3%kTbDuSypO`O9eE`FGUV zFSFxj#Q%0N62LJ}49*5m{?F@GZw4rMC^UqB&J)Dg1VqmK#9bc>1sSXVa}7;nJ9$~F zcbK^|1^<|y_o6x$K;Khh^2i84R%g+iK`VMyg8fH}bnT5JPS3j@w<#G)cGpi=_dTE8_EKW9g#zSy^|5_2% z*oatwqvn$Z_ip;#$`~G|q)!SD473*agfJN5#Rdh%FyR`cOw%?TsKz`Ni-@t)Z<5t? zrERBg%01Ii9GMqHnn)4b8MThC&2n)`@v@081`m5_{f{WGk(moM#sC&aOSx^6e#}q& z(k$-29yChz6IA9YuRhgB?hVCp!6MtT5n2S3dOMPwXe}8CmNc3;r$1BBk8c!A4nM!8 z*5IcT5#*)XC|9wBt%xhky=?Vsv3L{-A&xywj`e%uWUys7)0=7HY=a&&*zB>G+`M!> z5R%P4x18opy1b6R%B)O^J~hZ-wo2pH8C7fq3pIF@8CyuaEj-t0UfFspzNhdX$2KUZ znW2z>+;XYEKOxGrRZW9bi-ge&Bco%x-ln=PHFngzEA&=&noBb&Ti@!Rl8qH1sjKDt z*#&11=UuJgT@(I_L>Uv|nY+Vx_xxc&j1+H{D!8wXg{`@*^l_3Fz!SqXZs@un!@DW? z_^9FHzF)dsqU|T_S{OhH>Dd!%_#eBL`4-j|)WJEQGWOEEUr4L=dU@6il_EnQ>$=hX z4q9LWc4Zx=@GobR!ZR%5Aj0Kp8Oj3Ur!&2K1b1%Y{Jz=Paa#Hv_n=8?Cz?lzdZI(w zzQzX9Lz3gtrTleA=?lejXSr!wtyh^ID7|dF^$>w|vLfcYBI+`#vW0wfR@8ofk)=W0 zPo6kP_H2u$#h}GX=mZ?GjX4JOcS^H= zCDiYe5DR49fd@g|ru3HIHA8-#U=#@Wgo0MRqlIE1;yZ@CFR4G`(s%S~(Q#A{? z!u3Y0_mgi-vElpVOL6q4q1h(QGt`bR1L7F6bLaZNqNYXzX$?6J7sT1K|mqu~T!* zFV>rf+%q7Bh)Gkw#45)MiQuV2ENT$Zmt$J!<21|KqLTP$YEB`Y9>iV+iZ{_SzD+GB zP^k>Z4r_6O^+`VZHA?BUTNC-O6#ElMOoyt>Xf4y^E|#KLfBBD-`dTx8C_To?qK&|x zl*Q_a10mmYQ%duA&S0;T$7fz5VCs_N{)x>!Xmf|PxRUfBXk_;zJ%da(s%iF+>AiRoq0 zm)QKRv>)@Z=NR#H<_D#ge3EL92?QVtChbS5@BA9M7>y#4&Vfc7C| zQ`+?DkBQtoJv~&6hl-yM+$`-v|Eor?KR`W+5P=B4nNZ66H~+LHKG6U8=6rNJA9+x3 zLlCXw&P*8@mPCM^W=Vtjsnx(yC(@Asa0_kF9Jw{z^_w&^_ z=ZO#Hvr#-ZzH4&=Qe<3E0IxLB`@@s|P97f0I}1rJk;Rh=z_=aY{A)Ve>Mur5v&wzW z62qTy#_TytLx3Qw8%>ATf+v?KE#1u%Kl{0C5%lx_GPg*_K$3Wk(T08U8xNAQD$2O4 z7E@zZ5U?<*x|4=(S*#@`316Ev2kKbCv@Q#AkKvViy!Nm!|g*B+j%L|_Z zlVH94!+p^@r{8N1JiuN;m*$JrqpXU7abDs!M#nrys`vr(n@xKZyb;Fo zZ~dHjSJ{47?ng*799s5QR@Vlgl+s|peonG1ixt1NQ4e|f5fAeN%@jyDFu=KDfBqo7 zNd+x&SL&ja@1d%XR_`-MSyuuCpJ|?3)jKAaWhG~Qi&MA>_BnL;IjAy8?~J!*$}Uzq zIRi@WIPH0<=rF$r+;A@H7lFHKa)_tNYdGO*>gsgHAs4Fl%zC~Tj^y8jn#`^E>1BunZNh;DDW77Ts`mk@3c zqZN6L4Tg^CB4Fnq9Nuq_Bv%6SPmeqw?sL0_7_atc#C}r#VlMU|zMnnne|X>gtb}`X z4C4hU){G!0(!(yw;@(@4gS!y9Y0C(lH&;&^!qw*}zh)qHiwlUVDo5fDtT2!l{m50G zHOR&G95-DsENl8DO_^rA(93SO&6Ci;nwfGuyw zdVgDwf4*fUt&VUMvH*$WOX-i)1S!x+qB;IHj6XX5 z`)df6=7jaG7M0NQr%MGdq~hx@$@9qAIQ>wn@bgQQx%y9yw$#*Ap`db{r~6kB@5>)V z!relqnX?!*B`?2eM3)N3%ojh`(~4*tJOcU8b_qAbUYqNi-CHutjj+ozL|L+BC*dNI z(#ke5jZ|^lCO%8>rcfT>97-$!Z?42?v#BCC|HzwGBx@a0Inh5?ljJFx#Zs9A8@KxL zU5N~MK1KE+SM))zgW72dNU)yeeEzH&n6liwBA8ap;`>C_qoX`+UYau9V9I8b_ta5Z zX}}2UdIkTtcS`mn!i9NvJ}KZKID1t2VJd%Gsv=G7WVE!_;8POwUkBET2GU_(N~c3} zH#k(Sm*1hQ##n_f*Mlpb0IT5<>*A~Y+g!ImK^jSwP(vUXso4=w=o9w2Db`d_{Si6r;|8L2e@9FZyb?rU8OrrH~WEoBd};lK2B8J z0?j3M;Q5oZGmNbY#FM6Fi?M^dsoI{eScD~{+FhsRyw8*c-BC}Wyo#X>SaatR)kV_x z5V2ua;#pi{X4@K^bBHL^CxLj>Ge_}?#1Vpk^;G_gMosC>sxWpMpr&NXkAY?@u^ww` z*;np{4qm7w;teemD=SEqW6V|&IX{3~=g&_%jXIEoCA5DAYy@7iGyI9hfzSWG)Tq}} zPUoU$<9JA5Jx`i(+Ts-X$N&_wTRJ+D;~+WDN+C}57lCLi@njqA!;(wBB~ZwaKVoN2 z6ed6aFs<4#cCNhKuiUHt@ymg(?^mmH8+_X`saMFW+ew?N?m*p2@=GX#c7&=+PEOK~ zbTZ2OXyHZ{5~lmqsp9-8?K82Ra;cP`BO|q66hs+H;bn8=(I;%AjNa`a%a-Ii9eXxU z6h<;`Lp(#UkBZIc^5$wZj9F2aXRdm4V5BWoL(rICuq8_srd4A@ywC&a-?rF*jiQQS z8?s*JLW}V?3Ujb77_d=)J*FHq6)$B6*wa_@NgmDI9pPn7w2&(cnrNyfJ?ku!n(wRm zrv|MuTs_tRY?Vj3?hs_tphpv#${BNT_2EXnAV%XJ?rfeIPFuPcIYFSAIjvmq$RGK* zL1jGWQ8MXfjws7zldzinz6JbA2^3r)I4%lL(i<-6!ab>CtqcLviGHO!-D0i6Q7etv zo&`3YB^f<}WsHE66wd=}^_m_|m4w{&CEO-r)hzTMdlFT8Q*o-+ts6-fuY96*Ed#ntkS!J*8Y>5plOk=nSIM29VwiDR)dF zqD0G5>MGg^odwdN|G3C}$Ty8=fJrZ9>gVPgE*3q*AVKh?72S@TQ!ybPyLT z=cM&hLeu%*!}Z}cl*}d(UG!{>tCb@X6 ztf6M~clX$QbRWpegg$KSLSL64nH1J>@Pn7qZU7b-g|D&+tpq$raRKcey1}NZ%f9M` zU1;_g?kjO22d#cgE?!FBYPBwlY%h6X=k5Vp=|O@%?CdT$JlkpaC{MMi)}YeHzPTnz zHgS)zwuHVo1%zxR-VSzdfi6?^wCl7rNm;=f{!@&T=wamiXa@E@O>-q541Z0TJ*49JQb@E!X4_0xsOg9}CIV zm~pPpUXzt>j$R~G5y8b^^Ba-u>`ds6<>GgGsED36v0jI%R}pSwU`L>ULhpE3S1pA* zC2?mmpVtG+K5r+{(%?Ejz!{Jf2a5gJ5+O^qY>XqZC2Nng?MmgrQ~hy-5rP~ZmEwd& z{3ky$o>+|amgd{W(vsiCLbAwYJrwy@~7^B?#9$v0!Pqsr-)g; z{Wf$o^YpP3R;SI@{rCn@y*esth!u*N#M^#2dV{OR&@Wotirh5Hj=dqo-O z9DcS7VUYtT$D$9+;j2Z;521qJRE@#^#M&dA;y?muvz{BcE*9HjgwY8L>Q4UAae_P_ zOZBzSI+7FUFW)q!-qV^oed9V2xiLRLZY7jITPixKPtJO|Rj`!dSNq<|WNxlvVvypA zfvN*JZ|5q$XWtx64SC34WUbHud4uD$?{Nm!i4ND!(bIV;XN?p0knbKLg(s1&d_uOE z387P_Vfjy(8xHFr)@dD-cNWyYK@Z57z-;@xjzHEJaE3+j+L~b(!ZQV&9(+q3} z$d^p0+H-@-`=;2|RnZ#DK+Wgr%B@&UCoVF=EECaBHs&2BccnO-ToV`w)@`wY>$ZT) zeoo8i*FL$>`B!o4T3I++4hHu=1arpmfbsE>_D&*PGD?x`^eZRU=!MpjFAT3hJ8knC zF!SNfw(7L=zfkbxh6ZxN0hUMglks|Plu!Ow+aG=Ox^V4Si;$j!DZyGwOl zhE+}a*0J5S{=aVQv_V-fRY*FJ+1a zL;Q*;*ZzdUf*dn4T+Cah$DEh1vNU3~qvmNt1G+aG-j%I?fNSaE1B#=ijFuq4*bGeGdIrf4 zN+YEv6UMvkF&@RNk>h_2kca#11}!zTJdBQedmeYPM~Tn zA_~BdWsDnrL~LUpb|WaUi5V_2+{gv4dTY1sip^DA2BWoS$%F;z0KHA3-r#x{orMP0+CH_ zCL^4R9E0M6@V{nO;u+APnd1Aj)$xW;1xnXPQsF(rCcpK7^<(9^Kt)g)LYe1nGArgp zDfp^bM9%vbwcWkmQ`l}CA0@W~6u#8Cz4z#6CG`F7a^vzQOT#?IGRBOt|OP9WX!V;pq7H z=Y;=(>Ze)rq8iDcqXQfRSgk1jee7Oi8`3_56k?_up@g*?rCVB;*RjI7Xa)DwM%}og z>k+VX5_CSEY*Zs)ydvHtGs7Ua>wF!Hs_|xb1WA%K=o_Ax*!s^w?9@!9PbpJr@wDFH z>;Pz##XmF++sYAhWc0LdXY-&jxX((<-xI8%IC*1L^hu-w!DUwbAz9HbWfC0#@f>*hFjnD*xY(x< zErO;Y5$rloT)8gGHT1wMKPPGn8XPmsJHL~ZkBhFVo$IkjTI=|tamq4*($Px%f-deS ztBw#w%d`0>z7ITK#^GgrU9-lV()-RvFtf5J4P+UUZnTlYPRY+Yw7j6M_QJb*UN>@h zVBD_1gvq-Fp+Pb0PC8^>MXKJ9>{&=UgcoR_sS|}q` zKir4+4s?FFhn|Z3(iLy-`sOrGFTYEXAGxs(v>b|61p1-7<@A7TM#&lS_xPlgKrpR? zxSm@f(LSo?wONzWkx2tYX5e#T)^ul82zBdJ!C#bk4N`Q=WEEyia+wu%C8_+k@-Q7; zNw7>8NwMAz&Tvu$Rl6rVWvWeaxB5Crjd?o2N=NiOF_ZWVpEObmc41bx+LUixQs_ht zYl*%473A-0xeDkEo1eLsd^8?O-2z!POX0i~(HioE`U;$hJ+UxP$*?I{TYyfpj`5&x z)CNXIlABMEzPoFF{>DzbilqT%?a-&w^q&QC**Bi*n!M!w6NADZ(5dN4btSHuh~q_JQv?DK*&(1N}qs%h_KSts}4>i=xr zFZf4Z1oZMvol?xlf|wdR51YLvQvcChU&NS4+hUKLYF7q~cDGgI%5y!ea0e~tHvI?B zB?FiJJ;wP~9vFS#g5m~Ub&<@H2&_DiQHhe+$QQ$c?UyjCDd+oX6IWml=TMWdcdT7E z+Q$3zcevtr#`HNS*b zZ>t9iZ{+k>=xb*S#5Qn9tH0n!x>NR~vb8izNJ;78H@u`CGIi}Jba$P;4vfDlJCr2; zLc1riQLYDylZZ4!ksDm$eQwKkai?MYG```QZ$EgdE(%?~)?P9r(7EC>Yikl>ea=7f z6l~6MR-N2mhP)?mC;jrnF5-uzOA#@=gWzW8#gC;iwcmJ;JaF|=SZ=f81-icwrXR~= z2XPu+`1F?|hw~Hq-@V=&ekcPy95_N5hF^E0Te0@kQaIUn`|WZt#AF=u4Fr}dGsbJ$ z`1dNSNREM*E?&v#d*g%s+la2reDrt(d8}h#*A)w{|}o{{KVV^P`-_M{Dll0fJ65Z45bO<+xPKzM}XM z;+)d|gH!eiJgq_lHR_2TDd&77m8=STa_A^CEXz242NCFHfkQI3Iyul!kfk14gZOGm zFFFF`b^(ed!AdKFn=lw6aRh?&AQ6w)(a*UXY~T8ZeB&B)DOO`blFYR)Y0c-d zf=~e?-Uek13Qk3oO{0GDX)q|+2F~|@BZ2T8H0HGODT;JM`&Yn#6PFTHcBDcWG3qCOnVd8> z`qi#{9`!nDv@aiQbs47EHh`?-@|E|ojf|5AY|iz$AMi`2{8P(DX!&41OEzhcG-Ql? zy(Q(&spJ10qHJPU{9>m)9gu1crD#3ZQe*BgR|f^ao&Vw3HS%?Y%|`F7^N)I;or}4$ zLMKN~$OZ|B{|9rdhA5wpHUd8H+1&M0oUdUyCk@MQ=YtoSUtk<}I#>!W{7XJ(otEs^ z4|pj6#v@np6xN^EaoR3BbVk@CakMxqfFH1Vn8 zDgk$Slt!VJ5%RTW8mV{u%pNx?8;9W6`c zf)q~K$q`0aphJ+(a7&$h_9UyMj_9Sr^K!F^DQ5wR1H*hcNGx8^XmB?LTWfcDaP?`b|@cPS9^4c-pAL(pN7X!pw+=IXLh+R3NLA}b=!6c||kk!pVg zVy*NCLl(nkwl1$CD`nUr+U-9Iu=US+V0|!V78NZHGzmSfoHjAVUn=Q_y{xgrQHougsAZRI}I(RHlDjHJ^?&V=E0TYlk)%I2 z_9E1J4?4fcUmCd{zwj3iyK+VPN1#VwGGHFSi02@#_Ju?6hXVw9hrvggC(`6OEOyi% zGYX5T{+2>snualbp7>H@z{S?J>HhdF!HKnh}l}O)>`7I8oOvvl6Y!xtd!Hfu;mFMnX00O zMrfHHpk&j@qFV*)7-Pe#`QX$gY$J%RXAsD&|NL{ntsX(IvfESd%k~@s&7DU2*h~}U zxmZ8_?c1$cc!RQ)P?K&y1Hc^?Bh`eY2rr*0?sWGSnRtslJ9&!a1vv7$K<E~vP(=}S*?NE5Xa($WJWF{X*1P|sjRp1OZF4R zsCco*-DQE(+83{ZPA9igvwKw;-vShTO@*&;9C*6B_3~wGoleOe-E%*Q2S{@$yk1YF ztuJmn0j31CaDU8rxFMzdoq~0}xngI2Y$LG}3^nppr`;*d4y5=55H$yZ+sl0>p%09qN zAY=%-Ew=6vj2M({d7+W$eRCBuXK&yWlF;-iT^o%KR6dE$dF37Th*KDb{637G+>Y5q zB0U?`>7A_qDx$~Pw%+`+iwNPajl_~Z8g0d~ab|$qXm0{E>sj>>?FQ32U10BrGc0+>r^O2_`!iedNxo(Om zHCaa$oZX5aeoZmHpqgNoUDqi2bYg^eGd`MZ!1bKW2emj{> zf}LyD!v_yb&wfXGM%~a)@3Hh4QsVNesX^O|1#w%dt5p+prJ}i@H4Lk>HX=>k1q{&6 z2Jzk>G7v5w9IeSFG6$;XQsyrG#tjkdA{X4(VNp{<4H>%(?^UEsUZlET#(;`A4YAw& z0wkxX!olUZ3=VLP*#TPl5ah#? zow$Y|E z0W4hu)oJ2VJLQT@o;-E&1(NxarFUQU;33?+^Gd1Ht^s{N*FN@}exAPC zO?H1TI2PlYc8i@S*tg&zY(GxmRl9O#DE$@*{eX-0!1oCF=_XwNCU(M{+s(AwotICc zisgqUXg=Kj&ztaYAAF9s%jGA?!V~EkCVBVK+mK6|_kTGc$Aje|GeQ1*D={$e5XMux z76qy&**lGa7@Jg>8DvnK`HL63nf6~GSgzBXsuYfqrZG&8iLn!*xW3>vn!P(KJTtKGzW^amM!m}>4|g7$9FO3ORa zUDd?=w6RsL$R-hL6Ghfc`fo!6depIe{tE`d&v@)g7VIhokGyqz%a_tb&bk#kq-leu zL2}&PhetSG05!Bl`^Vn&?hSbsH)PT$=GIC(1K}%RW0>?DIxwYTG=uJhc|Z*4$6 z>QwyqjT76kz+M!nc2*iW;b)A5*O>KZu#{KWc;7&PRGUw->Yd`loo=d9zBhFq$&0462yNk%w_}>Y3=%?nq=fR9XiOCJSi3I*itMnp)`Q?$*e3Hpy8h~H_%?RBPvAHyS$L%+_FT(n0^ z{*T4^D?L-X$PM>3EgYu(B0DP^$0SuFPO;Ibeu?>vu)nITM+N#o$#Tt=Pw-XxK3ZCL z0j$f`s-%RzUvr((6?s;r@v{fDX9do00`3M|n87q7M4Yd)RjceiiGw;|)glpj!$CdB z5L6-uJT8K*7sx~A&#Glp644=sJzNd-jvXO?6@ZQMQhdu^;9;qOr3Fj@cq+fPGw5>*R5Z1 zVB@`h)63YB+i3a$?2h?3DfV8*CjICxu4HlaZWmcchnZZV16(tV?mFlzp|b#}_|nv% z{)1&R&NcC$LcWsep}wh2w)4Sz5tx~cWGVGYLRdxO$Qx~zFNpt=Wn>pXw{s;DqM@;x zX4l8hXivNKI5?ywWMPX$Jy8W!CTbZy#2#pgPud3Nq@RVkyR|CLTs2NSJwvvJ;^loB zHS3R!4)Sm^k59(0Y6yFdWYF^fWS=EqcnNw0Z(^f=?WqBucKBTWWf8^VeEZtCXwG> zIiVHiNb}0eOh0?Fz>qr!2xBS9Q>BxyxiFS&kCl}ndupnkYv~SD@?H1hLJEI)|HOWD zcL4r(l(f-0_8#Zl738IpTkUe`#1_$58l&X28CO#?Yzs_U{-VY|M_`Ao4t3g|oEPt)(_0LzR?G3RTm z*hRBl$SM1qgNhS0{Q0hX1-w-7_7-%mrHP5srALCTn&gSElLwU*Gv29yTQj8J<*Dj% z(hVc}w`)26&-a7^InY!$Io;LSoPfRkQb|yfy%s)MXbJ!S?;xjcHFKv9yt+>=9Wlgy zk&#ejZSGivU|$@Nuz70?*v5WgkHMzrCdZ~cWlv}oY*pXWG3?t2y%miCx=h<5v4X3_qrE|7{SH6H-nDttJR|w1?WgvYXfb7su z*+-520}vgt6lH$(j0J(hdw{_S=}sW&wzkX~Fua2&4sqb5dP=L3rfma#wv?_}jFa*G z=bwg!pHwf$)6J6pUC1n4MLPl%YDUru=DgAfkD~5awB*CA`N*j?QP5bcw2NCY3(*_) z$00MX9@^hd*d5F`a?wmbzgonhjN~XlMwq5x4ik=l@*Rz7I$s-7nD4n$n_yyAkLa5Fgw^5UKNZ_$S=pclhzb@_A z9sPmKZ+GqdyRGa%)}_sOUvqm==4I???d^a9v`d5TZLhriZ%k9W%fm$QhLWdi5+&0M z)=u}kA7U&a+Yip*m@T2Amlw{g_yc%wBaK>iwZA=~cK=)g?OAz${sT0brka{tUGtJs zR8cuSr5sx1ZPIKdJV-Q@w)51k)8liiUshqY(B%-ChmdrezJBoncDRjnv6QEF2)CWl zDcdpqnT@|bVk%LmJNZlvk4kp2R)JH8lGd?m06|NJa_C#Q1*xi?`VM|1c{fNqXp{DQ25XkOdQdsSlLh zB%GW&siU_?x<#kmD3v)lNE&UJ!CSzeRD_HK{A^G$&Q9#r8!ipNZfvcrBp8>=q9I4SZPC-L za@q{;0j>UnQ0XzW93szC%75a7Kd8xWs7((15OA6P|03uYyYT`Y@fwR*udy-D)Kqh! z0~lZ#wY^I`Wgvzbg)?YQ)~SO%V?E9h=(Pr*qA(}xXD6vtcs|Q$sdPRNZ7to6eFOb# zurK5r?HD#3J3*zQ=Y8!>Y-F=3_uA;;K`*Ic!!)1rOl|S9+1?_AC4Oth<`p9r&b>i% zJrqxZv1h;dlv(#F`}l~le%Ywu3iSlD?ZPsPeAIAKmb;$A0JrUxW62hStE1zkuBGL+ang?nN4x|D2j4rh1Ac zYL$vrv2(DgAc54B#GFS~LM;d2$FEXTr!~tjQu9_KjIUGd!2h6PgcARTJc$N-02vgt zi#zW$@?)lOQJrSdhL(lMTtH;-&C}4PhP16C=jVy;o$qXph{r)~BK4talI;`QHP+|Z zTA`Ba0;HyWbUBr>6pcnE9=<5ssOX| zpkZZ>J|BO!Kj#skrImAvw7z!Ph$;FWv=oXRarNS8k2QX`^)H#zE}U@k_d-+(F!GW2H0= z1XG3PKpU>+q*!y@lox6fBFH7x$-eIod$2di`K-a8PfKTRU02+e0z9s(emN)Amo)qJ zB!t-hMaX?6Q)-eAF`MKYl{PmWAz2^^`Db^&1SVXz%Vxh!$RB^t0-YF(ebLdNbbPCuob8qBM;10QqMPLF=+Z%`N3IlH>KV%(fUc*JjA6BgmCdvMu3H2$tUj*3WV2 zfIPi`)#W@MPc_M?(CnC6E;9UGQ|gS7$lyYq-etJ;E^z*QX~fOyrFp69a|Zf&G<_hA z#F`~PLLaeYx0#`HG%t^C1-_7rc8s_!iF-hkxfrNRlMavXN3){W;?Gj>Jv(r{dgs*e za6iQvE)Cab6=l|kwOX9_X>G;U0%bPvkGuExh`^^I&$lkFP=#Uk3sLd9$F@Hyb59sYrbGb~i(bKQEL%MzeQQr4or ze}69!Q~Bh{LUitXp7MjE@ZrW4E0i_P_=r_2R<149)R&ode(P3`cWW#4RtcM^c?t5= zZIR!$g9Z)-zLE|de0prm#?!mGlpM1?3lQFYHLsR#ssVq}C>lp$k}#ISuL0POGy}cG zVb6WS`mt(u=y45)NHj(-E!qNov4BAKg#nw#+9N7dgkei4g>`p&ie*ZjW`79=F1{xd zQPVi!ViRyR0%SFSn`cVb+KHMpm0C$GCp<;ZhWw_a=}~SOM+f28X*l21g))xP^Bz9X zJbp|$v=^>R7A~x#^v6Ia=LadmeON35JkNaIC}QF)QB5mHhwpdRI7gIOSl5qr)|Ccu z+U4TOGbZnHCpF`e(jTv$rrkZR(*Bgdh?A4^{|9b}m!GtaCX4U<2ASV`P78a^?jMF8 zlCd&kZsRPg=`3id(~c95Pd|Ft+JM>SH#N-}^QrJFLH`Jyj(h9JNx`_sFq9 ztH71CA7id*EIlwQZkjsaPHQ^svN+>qKEFm+o#C;?SfKj@e|Zao5dEYb?<0hOS~Y0a zE5_BP_~XE21K}PpbC|#=jHH=twx`He$0d|VtGQ03*99v-cqqP6V@uer8ngxFFQbgY;w${|Ob&TVVQ2Q;2nCTk8YyiJK+THhfSnNfqCxx4M{mtBu6pSK-}H|`?ma+Q z$|a85kue$uDmii7>PPq~c2d1Y%brL|AD!unG$n%C|DkY}PS|Ov$FBwbZjz@&vrZBI zSY1f)+ORTqoU7YIm0owx#Kb_ZEC*1AdEk`p+s-^Oa%fj-{4I$bndojWF32P-bkm9U zL*w)8?6gd8s3~!_qD1iI$UaUbR3m-=Evle$|LdV1;*Mc)W$J;N*Hz58Ds<;Tg~fbt zm;KZ9MZ-iG^s&* z@Gd#_w??$0#nA07$$EOb8lf{l`ayKz39}7hD6J|4VX}<~7nk%A%`yCp)c;;?87<*hj^ z-%96#N@8OA?AeU-W-T8N_qTwo`6}9ZKkTZeVh46-6xC;Vi>~O#ZufQB2kd)34Q}j^ z4xdNpgVPizC%W}MTw$Z^&MM$lLI>lqVLgHU6tbITt#}YEnxd4w$30_vMhoOkq?mp5 zAa}nnct>CO66kM)^_9cJ@bY!!cYA2KIe=8NKIX%>>q1aiNBUJiZ)q8+w8#^qYk^0t z=wH^NaHz$ds8N3J+a&aZ8dGF0z*9u}@@NXFYacwz!33VTlw+v$JI+fVx9P0Mpw)!4rzexmR+jNR$Uav85f$~S!^ls%(#XWg;{zd=??L0!$<6Mo zYSlQUqWJ4nsWX4Hd7uE@ts?|MpTne5)|E^8(bp4g5;48rGHy0DD+^9KW+^(|Qp_WF z8Lbs_OR#HgglRwS;pJ9SLkgfCG2y2cE^+7U8*GzT$GLys3^WpP@T(9Qp@f2r>43y?lUr`d|#kXbdIYwV;cTtUk7AqU}a8f8TsGSu=gDDcm+#qp{@x8Sccr0`kL2?$!z-*e zs^LxxZ3p~47f;_$ED5@eZh`Z&X^s_ecDDkl1ma6w#ozaX!1OgR8V%9BKkelm3vz*QR(5j;xR_vR>?nq&r_*~ zk7s@NEVUTu+f+=Mppr63YhMq1t_b_9Dzja1QiH#@L~6B=Z^(Q2AbrY4+Qu;vC8rIJ z@z#1oi_WtyYJxQyv31I99@cl1NSPYd=bnHrznsxc&#HC-$7~3-wJ2h9yqPikL|gII@Lm4hx19*}kS(}rU9 ze0y2ublr2!(D|>1Ot{0-bn%i!WqRATZSNUZbxxHIw5ti#n<`t4xT`=y9n880)}$b| z`tDieijny_FEs0Ef%NlsWAw{bd}nh6c59w9Gq&Vv?uawf^RsiH$0TTK1_jOL$y4A9 zPYc>4@Xus5BW_%#9#>zvluZww11|SKKH@!N1JZ^Z0=XNl2)&aaHCd1LJcOID?X(Na z{iO{{{jvPuz#T(l1i9>pE-;S8&mT7q_HuD~2-z$ndD)~LYJ?bI2l{RWlM`DIeWuJB zHv4CgBR*``JG544OKUA|s(J;64{L{p(?bE+$5hijs}Z>PoLZa!U8O>%y~1tqnv?4B ziQMC7^;_OT7qYP%lSX_`CA|5h^eu+;t;`D{z~*?c=K^wec6%yR3c5n{jTX8C(neUs z8!K+d%o!qnfm!b&g9`=isQ|3B7wrZ_?}(=Mrh-aM4NVvZgPTC6E&l8+wtW@&*;MmJVHEWl1D;To_~1PMpg(7=Jg6r*S>6c!7Q>v9QURA##=hA71q%5Rv=sa74d9qYdpzN7CAV^ z)>ZaL;J@d{7D78oyy)0gz-fp!$qLql_tqo`=Ml6I<=9oY?jOIE7~b&*G}m2I2_o>Wk$PZ2P>M*R+kx(RZ5o_sE>}#-}rmV|PRErq7rj4v1F)&A@&;F8b56(%|(bfI-Yo2g1eQm~Y;vNa1S)n*hZ3)~O zvt4g%?ka3O$Fji=EKD__>mmh#{{w`!BZW;!EuQiwLU1g zM~z-5jV!`Otz{!a1exnNql4_;!y(F9-iqVfrEUK@E9Q9AerZPzMF{IMb-Av#^;#9^ zBt3Q>lbbe`n}7aF#Q2xec8)v+T${vXLe0ewz~YDCg8V1r2>j4NYC1`GAE*@X2uJ07uB8n60YDCP(`sgdw!c_4qfaah+R=3Y=A^ z9?}&5{5K%g*U#KM8PBvfw6EcaHZuEtfs=*?sX_SQcT7*izY+_dDwRk(6C~+Q*i@5N z=$Y48V-wrq3YpBwq@kzIXVb+BTUpKE&l=~)9NrTu(s>uV7A~aLYZf_&d?bkzXlEpe ziLgJ07}@{m|0e6$aL;wrak=u$9O3X+j)`0B#+g>4HpqSFG>0qHFx3PeYhg={NYHe9>@Y9|A+t4lu zH=Rc}Vak?i`g~+^XVb%~UXrU`2gwJs>>}mrHA=q-V5d<>f>Nhx=38sv4OLoUq}!q> zE`)F!8d5@cD8d5mJWJ#+eQ!hA-z2I!{b-tCfv&gg?JLcqG;|rMJ~l3~Wph_)gzc|; ztuo}keyth0lwHK**Od`yb`k9OfwubtWo;_7Zx(Q8upM*##eUHwm#%(|G7MXeK)cQ0 z2nxG3s}>HcWp1E2oD{fS!5${5)1uW?8T)|%H;VO4#h&e%*z0Vb^g?Td@GYfm`1BF& zb}2tRHRn5|@4$9w#0$Ex-j z;P2`@l@-&@(n#!13wq}T62eDvO}N>RX&?RSIhs#)NAp-g7`9VQN(uK35eY(A<6U(m z*iCL3qR*DZ$KxM;wXjGhVFfX*qH~RbxEsL1L@lk$m_Mj>pL`28KYTFc3fr(N0q1V` zaR<73Ir1e`xhMkb;$dbyf}dq6I;dT;A0^*{$Q7OP^H9Ze%_MjC*als3H=Hy?j}0~t z){~{N2B54EfZmGSr}ZbhxFT$4uq|YWRC6i@ony!5%i1aZ<>`LeYIJp&BF)tTcI_Yy z&aNYk8g~6gmfof4Z0Y$>tV=*(cO!E8_>NcPQkZ0mS&-+l&tIi4y>HH-axTCIXvu_S zA0n7j_ES6l*OCvX(Op`-v2~Ib#OLOFc4Dn0_+PfWR>}2Toy&#NXE50fironQeCncJ z?mw1hGDcXgr{;ug3EVQs`R{i|(p>(MgBAnx(|R^qrf-!tQiFBH?;USWKp|%}%m19= zqoA~o{Wh#1IKg9(VLWiKDN`_JSDTiHZ!!FSY4>6Qj)oESz}ez@L*kW?f|I) zg_g%jWHR^$PDCiuc-D$P0yCwjEM2y%0JEhG(;ZYtjhLeoW+f|FDa*b_D98@?yF5YZ zCIZ%uyKlzb_vQU_)%)9<*zJpGTOw&c&cKG`dMVxYEEr`G8)71oXfg}q!GsgwsZyxM z3AKvAIyQyJuND4>L##9Kmfw?ZqFiI8ow0O1Cn8m2(K(uU^}ifE&`!1q>i+?p)tGm~ z)isqubM94n;is7r{+Am>LaLhH^=||wqMd+yRzDHgoN)!G-eRMA;`j?)Jp%=#E9wvA zZ7Y_lCwouv;*Px?Yfj*#sx&S?2L3#cz=|**UVQtZ-#>mXXE6T2&)6$X=k)e3!FR3> z_Z=j$<#N!Ryuh~k-!yKOw)mHoWF4Vqac-X_dKPscfOi-<;5Gw)BteuiR{z^?rxcwGAk3QJbPU| z6$|VL%;p-{Gm5S>H|i(3;Y}J-V}114aY~8f1)UO~^I0Z&&x}*23Hb6Zu<i`1ml*%Hjc=CgBB^x!y6%!hES&o zI@gw3VX0R(MCDdivqE1Fb4r$729wq)aswjxtEc%-x@&lf$~nTCX}0Kz+qc=Xl(&R| zYRZK(*vd`9NlLLNxV9U)&E%gH=C$yr)}yztqmTauYxL2B{|eTBm7`4ZO7!WabmtGY z$-MvgvE|%UZR>0i&M6 zlg4H7+IgXh08G?joWB-1@SAwetQ_OwUf)s7-})8Ib%_7e59ihbDw zh<`vYR}TzG;-shb@M@4P%LV*VjA@QK`UC%wRS;`Nki!T8%CN(g~ef-Lu`@geD+CBo{azwbtsAu z^ttn=nk{xmD)}{X0(We~XaD4lTP&-M)rhS%aj#+8@%eSjk%#SAAobVV$ZgS-;)m2P znZKuay|M$sG2<3Ih7vK!3h$+qkhtrhtkWcB)*ZNdj!KG9bae1?Vc_ctc141YdlMtX z8jjCO-X^>ZP znqYtUL_7af=6F_kpHO`I$}9Ob&NhAmX4_3?c+?9zO)Qwf78~h>8|aVse_G!TGv>h0+Z%pn`9GZ!U%2&&3{q zE%zuMPRMsgSq`0Bru_ENN|>$79o6y&<&7V?jCfjnh=h*@^m zuqK32-V{T3NJqTUt>IhhkLG|SzF*kw!TMU!YG%^{+t zJ(VaOseM@>eJC|TwC!n%f35tb6aImP$k7)l$VhS%?Vgs9RstFxX#18IxQ%WU%<&~s zhxN}j9^Nn58-p$25+B7ONLFw(p7^#M3v!8Fv%o}@?cK>yvi=mlr#Ltxdv{?}5;mh9 zO;bJxFUX5bTP1l$ob+yXK1%({!000W#xYVJ~hDMsiUY1p#3-XtCuaKBD$Bb^amuyaiXK^ z>C@~8xA}`E2B(?I@{$O-de5FgXfM-#9w*IXN8%o+&d&hb ztpjS+cU{r-JgRAQrhfv;B4|G8Q#}d72(@#sXq1PoI+=dtkD##evEl@YI?XE?%b%?P zbEJpPV@J8Me;RoAKFAp0^CS~PX1!MN!#lFwfO5>J4&*>R{9j}4n)Z|lhKvy|uW?;Zo?Yx`(w|VUn zh`H4_TrH9QSwmhs^QH3k%4v#uGSt6M1(QaIi$4JK!x|S4(pi;2#vY{3fQt^-Vjrdn zf=>rl?-Xj+YJ`n7zSj2f&Y32$*!pGsP4Lr4*rU^!)eQa>3f{<|;kd4w>`e@9%l65F zj^+qqvnJOP3_Go58UvXP!u_P4J7xN%lKj7gOHA_qH9}Tn>iJMjOR9=;_4*K9yo~Ni zAIJ!3S0f+F5WmI2PXxREG!pDxjNP@Ue~#r37q^dBzeJcwt%x0}v09;f9C zi12+jrJDSCySYH^djE^$_l9Eu8e)G~G^w6$E@(DiG^rjgzMlE}#?(H*dLI>HmhFR3obC@yDcSY-PX##bMQY4u?uPr zegxy|OH;jc#~uC%G-FllIAxp!v17ko)E0km7d3c^=71&TMjNKm#y)^1G|YXB?sK0+ zWSm7m96_5F0MU-PmYuy-)O!aR6Fxyz?9P?ettQm)g&k7;0_l4rX{wVfhq~Kla*EAe zS6y3A`=JL8JcZ-kgxkTN5wxR@%tG3?&Pdql@5$dCW+o!L8A)j!5z+XfsRSPU`LWcR zBYK{Hex#EmC@ds9%5z26?5)P7$+&}|vLsCL%FGX6w-0ag#A)_IKOwy_KLx&ew-)kR z1(n#5-ug4Ab;{aI#63Uu3DwleKA!lquBd#fhb`Qz$=yyjl~N6^V~Q!Z?^s{WfE+`o zcv2<(=PcN79$V=eV1VxkkRZ&tsMSvV&qQqL3yrX9s;M>*|BPZCiN7z#?xx^b5XJ~M z7j9&R@mT#Y$uu?W12yiIx}&2DEY~i%Zpb}6MjpoZzjjtkua&OhOFdv%WH-o@wLzyA zBd-n{t05Bu%$O-uz?*w3S|P1^(*)lXVC3&qq67Rg#=g8BXpl)%N=DvS^ywS4P8TbU zMOake*EqQL0aSAmeS}MtU?(P?(5`%=+0q4EiF!@Byt9j9un7CoPwthzFxSHOXewSo zG(3u?9I~Y0mTA~BZ_M0MhH8a*GU8fcx(TZLP`d3Fxf6qT(O9A2z)N_ zkr&-paNHKiyNa}YiTVLCOJ8=2ZmlKUK`UB;Y%`HkPho;3cm-WF<%KX!gnE8AiGvnT>dD$>BY$N)o z%SLp;sO0vncUz|6urokd3!LObPSi|CRZ23k|A)VH>ES^l0sn6yt_O(<=q^veedWNe zJ){q{tIyC{QEMApuNv+dz?M?JB|h8>AKX!ps-7Ibq|-8@8l{(onQUkSJ9SEA!_K|q z9=Nrm@>DcFIak;|&57~?V_z0;OPMbZ2{FfHI-0b;`B+oS5Vuk^faYJMoWRC34(UvtNwusr)B zQ~Ga0FYYyONBRXZLOX4FyB_ks?&C9aRU&qxK%d*K@x(-X-`*3JHz;J zEEdUE_-(>(+KXBZTD0k}K0n=uMZbxN78nR~b5345a^JQ?!#09o~+bYEz~y0)73uhSfAijeCWbdeM8sWBT9QFpicv!Oq}_~+}BJrU@O zN2Zr0f~-hC(cW&h>}(^H+?k-Xy((Q`sfGW@${%c&^Q4eriG@bq$<+BvYD9b+3S4uX zzcT;$glDgwpP%CEuD}uY<;1A2XzEp`F=mt{Sv&gBA;+^+7|S>IAgNL~j0UWrn~}i5 z1%&p_+b-#+j%*cD8ds+q8o&?TdL3Nwqel21J$aSq0Ts-thC+Qb>a%RwF z?v%BEKs9Z0f|9)pe%eFuFDa3Idm#F2UJ3#A8-W2Us7L9djNww}0wVq1@(G(cv&VJC zRy^;|mpl`Y%1-L9RdDh^e*Qx7!u*fOlCS>la?p7gG^=Z@t7;?zJmabw*ae|YqQ;D} zL>BkBX6v(;l=udq`D$X~V5*p4aqZ_>%%-*4QG5#Fvf`jnObN^OPs$uaQJrP6` zlRw387qbKK_JRRvov(5J;iXH_7h{+J26cq=^(*M0a1rn4jB~{o}T!jErSO$j(j%}{X^w;Xci^%b1wi1H8jT?B$4ps;YZ8(bJaY> zjx>CDm*CD~{-rpyt9L*yy<3J~BYAmF1PTZ77v$*dP_Tl8#;>ZiJ~ja1n$XTt;632x zDOwLHHfmw7ap!imTs_1|@@wQN%NQaiE)g#2=f!4#%vj|lPh_=F#59jxPL~O=SFb2L z2Eb-J;Et1`uuCq-j>1;s|1@v{+JL$sVI7^j4E#-(eFDtpRaa{iy%0>v(SHVfyC|o^ z=g&Vxs7XxvoSFDgU|?5ueJ$z2afiM3mq(RE6hYzB7;T;DJK+Q;dYB1Deu25V6Z@+LK~qd{xd z5%^~j@L@AiwQU}jJZD_BT=DoPp?EwgAv2&tljlI?DBb5roz_35gkWtg;_;y>>p)=8 z1l#N#y5%Nlcg7f71k?O#Ygw?Cik{nyQ&;%oORmp21c^rAMCYPaV8F8Ua+Wkpm~+~m zR_hfQIA6oIx9S;P(aTQ4Pk^tBRIEsvO*IFy9J(EIbl{+G+&ew2Sp#2~Z zF8fSnv4n&4uzL38_Ng(G5(C0hbckt4K1h6}Gh+Xp(>KK~kV_^k>}uKd&#({@Q_=?N zqJ@QAW^biIkkAC=A}^{Mv@@R#L{vUYnOe7O?VxvINZsgULk!+O%Vl$7A`@t408NL1 zUyalN1rl(X{uFEpg+`wX)*r{7%)#F*_6s!8Yq<*tNT%s#@%6Qo4R9P;AZdR6AT4E( z%?;MF+h|<%>>(iusoHfpn1AvOe={NQz7#BoxjYTt4VtXeEYAT-O4((<4O@C)Yj6Cp zX>5z3-Wd%1Kw$SdLOT_^T*`P?0XWT|bdy!|Xaj3y6$QJi#J1fD5yYgX85~59GLhBu z2y`EZ>{xzGZ`?)ulZhNLjJ@F)OVP{QDL5I1-VP>cX1}Y&!)<$o{>P6qfno)`b}>O| z%lWMvd}ltBp{Z6450I9J3hc&dWLtG>7$yZCr;dk+`HL3&;Z>0&=ZTw7h~PVX>2lvg zYR2Va@s!TH+?bf2sHh(3-hs)%ToiWyHfmVrsFA0G>>|0k+?&_9iw9wG*pBiZbdnDLr` zaHr(d_{Yt~5ipykqWjpV9XU@g(feWO%oBAwr6WM>F^0Rw1Rj zL6w7g0%_JeoYl=!8sEkajS+m^<=@J9)8kQlV`{`T#l;;@_O0R85c6=4+j zXIkcKBIzR;#2$@f$m3Grb|IlUQi7nL8__4UmXqX4!xyIMUC7)^Bdlw%tB)?c9}|Cb zYW!p0Ktvm&nc#PeCmq~xAnchLYbe^TZ?D_C)HX=*%wGzT%OIRtT(pW+e(UsUM(|C* zkVLPU{-qJxL<@NcS45a6dD+6T$MSTzufeb#+C?MzAp+g!(ja@;6;<{iCr)g>L1VE8 z)ddMig$zGPKk0qg85MQdAQfN0$9F_3JItip2=pb##5?w!W(lD0`VRWQp0$HuZ@$O) zk7fJ`?RP^m?;!N`7B^goYkE=9fa{SdPRVO_n70f6#|&`*6#5rNKZ(%o=JRWIV(;mN zSxp*0SM&Syl|tPl?lGNEI`U?c!0*w54Sa-@AlAi{$iqJX{nJEbU9hE_p_HW0R^SF< zw!j@z{6GpW(-Jw_DYu5>KON<~N`2Y|i~k^OdNzqs1~1w=&t-*rQ^UHLtG3I}f78b{ zXSMJk&w0@85`<)?Jpf4BoLaeLV>ro%vDKU<+)88JAP7claCME%RCh2EWgf{&kO#+SDOVJHX~_=@gVNSY3%S*GSM%4)(yv zXDeJ829vG#UTi6C*1>7I7lm{{QB4Jut6PTH zEiL3(W2R0=kFb*(l#dO6QKzk4A33QWF%;$6X6JB4f2s;1+QTc=wc~v@Z0A^sp@@5I z!r4vqStl2ra=jx(r6%v^9EmSk zb`4`t>$5O8AyuWO_}r#)tD)Aw`Z{{~Bi+2W!3eoYV;#uq#G^pYI*JxmIKQT`s;Fo- zTkD%A`N$}xK!#gX@DWbzUo`^}ym9n1KZLY3zC?Bfkn47a1@mN|$0lBQ;t#Gnw^u@B zXA%2O5$ND^b);vh$U`dmK~P0pk$-W-Guj6p_OjHc72+8oUwp_r;MCjOP3Xxo^ve)w z+LGO$4>wuhU7fUB7Xk|8t`AgXseNa3+Qn$;zdLWq$xvj>o@? zbxWn|j~g2!BVMszXXQB%>5WFfCsvUW(lEKqas*11#Oo0AjicRX21uD4Mcm>l@&r8Fg(p zly1hQXw2FZwS6Y=t8-#hJISG`U5-sKasK}(Iul2z_BV{5Wfo($?7JaF*`<(VW?HmM zn=LUaOeLmLO=UTU7DZ^W6`9*|(@ji~=9V~8QN-M|NW~FFWf@7;x1y%yAXo zuvVB_$_qJ?Ye`8^J>>&$hmPd_BaGl7Nv=hGK+<+3q0iqTj|O;C1H6v`=xl9QA=d9C zH0e!}SMHVPXyMu}_`P6xlcjSoe}rV8j$iQ4=S_{)ZoPy>-ClaKfHz@U_3h$m+Pq@A z=LM}`Ttz+nuc5{5m%Q6g40%zD?;-2=heJi5um-?EZn+~)TDe%}pkjygJV_JzO=$kZ zviLfdl)BMpC6y4Q88tU(+^BUptT69W@7&3VrEC;FY(2e$MvF=Qyez^!dWl*CAo10zYr+x<+wlOGwPM|CH5_Fy)u!!@nrUg4a zTvmkM`Ce93lQ=&9_3OxCtkl!Q#+~jeDjFI4czB2~%|Wt{eT@dV=N3tn#|Mvb#6|~L zzXHga8-~E%>5}Ck*cKyfvz>jzwQILtQZ8+WzY+WVJ(qazueyM)U19hO<|i*xY&1BN zvlz@MPMid)YP;%f@PUMMb#1EGC z50yR`s_~AWH1-%~d8yX${f+x-*+blTv6`HBkxV9q)*NxNm^1$Ur$J0i$1ygCuAvDo z@>JKp+JfUwb8^A5bK`Jm57xklW&#F#ppWC&(mZ6b4ZhZGkq0$-x09Ryz76}V<>7m= z%d?InQ|6&H3GnZQBF}(jGsKD7eF_df;e(L_u)U|Th~=F`vds4`6nh17I32YeWCs8? zG-9HDCu`|oB1*Z!d!I#EWXXell(Rk4OT z#WV9!JpZhgjBGOE2mD3?yr$u>OqTvmp!DD4B`@I|e*%qW#nTeZ9b?BcjD;tfYC4m6 zE7(fVHygIuj;3r_8((0G%>?2w^S-AG1b6A4J2m2+6zc`W5Ln z<-N_6EWLzU*-FTlFb(KR3O(8iS-%VZn=rOniX1pIK)R#gZy;kfR6mQ~DCQkx2;TyI zd8dim+}5qgX~nBYnNA(mQ*_T{gEkv#VHE!ol&A1q z`Hg$gb>=?uLwgkbClY=T?!gcrr^?LE7#Z4QOK9S!WTEnB3zukK9l}nfO#|yl=s!n9 zck4CG*q}iizKtyTYd=M_kZNnqkMK53dC6UAr11~}Haj3M#eRFAJ$>?`c6LKObmojvqK#drg^o)%>-IV2SR3jCAQYFW4&Xs=>^s4VVU}hF=Wk z{=ur}5#zYcp-V6FO-7}oi}8m&+)oA!+?dtBG%G({Zc7hc=)&^^B*?g$;~8_pa1nHQ zggsBcjdMpV)zf5qT zyVo5*b1o^iqe@#_Ka!ADhisw!_Ew0<_&t4r&X0O`e9q4>x0s9~buU$ zIM`d6&@pnnGQ@wsjzDVL51$rmjg+j(=5B5AiK` z(Li5(p-*~rOW0$$BbQnDijtpB*cx48?{#z2boGeBQL&mF znpoS3>Hl(<|Le%82|}(t=c-gw)7@0ze>!kq#VRA04LD9y9A|ik4nBx2>P%7qVynyP zB_z?h_wUOvW<)&plHn1@@}fer^Fz-r5LdT&eSNJjJv&K<_907Mq9sB5DQ@z7K#jhu zQUCf`d%>Of>A;M2cN54g3+Km2Cz6>S8!Rlw`zH?Im$nH0!1~JQrX=+K+qCx_-qs^+ zGUO%)IMt6HxQQ)NquFO@I2wMbvK_dy|(yu|#%W-wxa7hMQk+!7dPK2Do`z z4g4|gcx<4B>w|+|VaL7OcZz@Y&o#J-?ZHcrXLcetDy~<|65Z8RtQ-zg=lwrp;q=#z z?Z6s4$u zU7X=8++-jnw3T_0hC#f`56~2S$=5Q>Wro0=92l3Ibg!N~1!^DZ!e0*(bvPmURQWRgF90UlcEwXKFCeIo_kMMj%*U6E!#@;5R*hbs!y zm!QrOQWCP3Z&?lKt^Da^WnU6%2p(v$yr z+leb@Het%0yjEn#Tg=5#P|phVf?HmXGdsHfeEsQejUT#@#J~>GM#8e3&_`vc1F^bn z-N^sB3|m1%SJa@3Q@LyLJ6Jkix16_}AbMUpq1&0YWQtp1cv6>Wv0m2lq7Ad-wJnOh zB9B*2s8il1uO3oM_ED@U#XU(4rgq}%z1-q|+1_spP_UAu@%o!Ovm&nu1wG01A9wgZ z5rPBv7#0alt0@n0V`j)<^tfR->CM0C>7t)TSUFqL?V4x!7xl$FWR!2q40HfS$xqn~ zd7=h<{pNEG^Fq?zcVJ9WcsO*W6)I693%|AII$=w>hIxDw2lQW(4EEQM>4Tg912WBb zQts$Sf*FHKIL7eE*r0>d)Idw0!nvdoyaWzOXBW(-xOpPIWUyskILXmgJrWQS^5eyY z71%dRsZ6ctvJ$1x6g$`Py%X_`n)qL*vF^vnV@D5#PjZE|i$x)QPCfdw@M)^Z6bRG; zTD)iUbW_Z`$W3wBwQ6LGuITZ#>#3<1i=~(Dd5InxdSpF${_4iHo3m#-jOtYRQ)LWd z>66X$^6YkR5aUv|bOlt>#mnslGG|VX7|5)bZARGQG#k;YIii)yx3>eZYaZ2!E1@gRK$p~#SM)hSLH&~B330>my1y`d{wXD7UE z+_!R-d}4!9_Y*cVx=9Y$GrMh;VN-g<#N~JmxeG{_Ygom2QcbbUJH2OTuT;3)uWp zEv+49*xyH=LkC=>N2s5y{}@ylz%q$>+)l1osWoR2SpP5DL5eX`Q+GcEGp|A0hBfBH z9t)i5fiwqibA7qXx~vY+wQ2$TzeWw?(#Yd~sJyRkVw5Wz0_Xm%uDkRz>kM3A zj;#|Fo3E@h>$jk!DpS9hc^v^R*^o@8IU6fec57RU2uTXqemea4(evQz0~74e5x}kU zyd1RGov`(C-)vOG1GC}{EeXHSwx=4N)1&OZh6D0#(DY6|?cwB=RpC^Jsmg~(LX$8Gb~k}c_kKo)bR41H6wAHMlcVDPlwcmsv& zA)_*KMh(oqJ@HJVX*tGPC{q1cBzpO~J^%-oV}n|KUNFlWpBX5x7~$gi8AU~^$;s)r z^j!;&`1%eWV;60EbRd`PfwVkjd3jRAn>fX0zsESDyKd4i)8NZijK4647%BbOGxz`9 z40s{Z8Zq-AgRwYi3ch$Sgj}Xf1Y~6BM zDj*M+Y|2IHzG&-_kODSmF}3hjBW7)7Mekpd5KnY!8(Sp9`o8#^<|NC?8c0(+EKzmj z&-B0pHbyM?7ASg0GF#L^=!kbyNE0SflD(QQLMP@kw&%wVD7 z6k3?3?9Ob#bp9Z>Bhb2^*u)-zT`yJm$eu2G*u(C7qN8~AZmndE2fA$wMV_52c}SVx z`T}ZqquSdeOII~X8XYVi$KxG6MZ}01gIDtt2DlQHhjgHk6s$)Hu^C~_`R0Cgb=Ew+{U&DI7x|=gBd}H!|X^o%`FK%#z;ySA6=J~$(GIS9V!cx z1W_enh=I_Rr@0a*-zdpCe}??#9k~=CHG6^&ieaVFWd0;QxQ`vYibjm_Blik)vWjx4 z;@O7Ml1YNbOxpX19EL-`tQ5b0a4;*t2pJIJ&U0W<#C%Z~*ZDqhcoX!<1pC(xH*_Qd zb-_wF6lE8DCuh8wmvVGvl6tB24<5bH2-&KkUUD*lmofvpLt#j)uA=LX6RF(8YZ>^O z#5~@j@7SsR<;j1}5uv3JGX0-@YIOfx#gjSKB9L6Y(D>+2&bSXiS((oYT?+3;TwUfZ z(e#T{$Y;P-S4)ASU$lp5P+Mqta>zVJwFhJtn0%q8lR5>zKhfSfQ3}T z&(*<24lU;mtZyhZlILh#Uvj)S#ot4H?%PYF+P7A+hrs9_Y*DZgwkaE1a$aw~;W(!v zkv@kN62@p+h^1^sqQ>dKirumxWUL16UCB*SX+FGjHlHTDpzom^9aP%j0-A|PcCJv* zW2?s6Yc=Q*<@?xh^DcvyQ15t!hIo6XvmaMSUgbbGAuwi<~N?7s)NevifH9S{Tz z{>&WemkjJQ#j3rWW0kO3YKx}*GM?3C`x1PIlM&_s$u}EIYhVIYbHE*4v#?<@2IE zQcBw=!Dumj%t+#zn$KJD19sZ8lRU5jDkYQ5+u;}h$vu_qSQQDy+U`6qOV{!`S`W0B z0$#DqIN7ryZl_wQcCekP5u!s2ac7nY(M<>XqrEU0R4L*tPMBcH7T~Y8K3CW95{!KO zwP?4-imE$~DSnS$&WtXZ4y#sCVpdOrAwwghcEYDRZ}xzt*#af4ok^r6?bC1MJJvbj z3wfRVo5M)+EzozBkbQNB>vyG=Ti&@R_>_+qgHsQWW*7J%ZRiyihV$@DKWMxy%f=ng9G&O;Vny2 zGih;8%=&h&!_Vw@baWIPp@nR-nmwE5bwOK3PtVLd0`_~ z=++h0aQ)LfK0TneyFIU=JExB!f;)SfY}P+2Lw?C?(v;a+ImcD|fEDY@OmTlAwAF5@lbKMqwK1hev@ zfFdiP`sELBR2+>Vx2E$2Za?;RJD5s-_*Qkjs_b?GM zb?Uf^y+djqeKAUps$KLS^32RiH1C_c*uZm(LmX9(*0kECHg72@6a*%EKpvQW$i8-Xd&erpK$n_+3_Nn4Y(8~?TJ4Hm8c7%2Hss&16%#ifSrl^d zh1#-ifuyduc0epNF)1PPVnUUgvr(~h9qj&_|CRz@U#V(=HZ=nIE|d#6SuORphU2}7 z`(_2HQZN3CTV2xl%vpg<;6AG&{TwZ3*7YRt(EPwUuHP{1IY6pt|HZWugEUdDhiIV3 zN#Z4XsVVjwP7-`*AgBXRPMFCaUnDHL1+aIFw_uRMim5s_MN9>~L~|=8vN=NH{cKqu zmx#26p}7D&b{Nb&3U?S;&tg=0rZZ*PMJBTFUk1{hYO4Je)Zs9m;{bw|IBqYRpH)P3 zOtRp-qsS{OMWktESq(Z6;wE@IN)P0@!T~F$!O}viqX2ee^If+{?q04*&-yr?his}% zm*hY?eZlLj<;y`yn}D*7RQG$7pswwL2ow>eawhN8G1QpG!2C^|$;qs36o=+1c6%_n z3Zltcju(FYzs>!fy+}WmSy3^`5$$D+J6cij-4QrOgx(K133AATTwttQg|_O#umLiMk{oZXOQPD-E89K7JBR1W+HCRYJ>r*sTAlor{d)C}$$diwE zPC0ePdL7j)SP1yq13P*M6c6@SlHiDX;O7c0eki_Q4|h9;SB6XKkTtx2QL`{$1 zfLN1pM!}LuV8Ke%>J8@TMSLnH8Pn1hY+#rMlb?OdxU&dda0B3QrB2GhS{*!^DcYeW zotSIuc(A9Bj4vUC#WXBvo&1U&;RSeoSg>Vys%8Bu7fa%|s{CkztByZfAJ$nl_T_y` z5;h}CSMRI7Zo*#Fr3jm11<8ztUT55 z?VCwzbdwnaYdxFS)&Z8C5Rmr{=;~5BE@4NPdT) z%CzC$FvT5D%!GKPH0Gf?ZbRPjy@D@LOz@YlT0}XRE^3=08`l%RBlk`F`A6c_4uYZH z`XMKb{vFX%#o}%Oe#XF84^ggpMItBgBusb0F-zwz@R1ZFtRQP9e|+AGH_wa2p<|A| zc!M@qw8KFW&%nbMmGom>R`Mf*LXdG~Pr0yrlWbB)hG0oN7C4gfheBF-|^y)jKGKt0p;d z^)}LA=YViR+H@Sgf!0^V$63AU2C zWq4U9w|d?*aAkm5Qt1(dnh69K=y3%W&PP<>fnxc)~DMd6^v zN{C<{75AbLw5tM~?AT0+a=~w<5)yc1AFcm1dZ-niT2G*$N&auK#fLGY!wu5r_hFs< z9TZ|A$~-1l6@NZrhXF|AH1Joz@U~h^6hQynti;jEZ*ht{My#)wUUiFT2(93v^L6I zdCvtF8yLZ!w14W2@+=N-cjc`Zsm-QXUDu_w(3}x>GiTmWO4dEj&L8R|0+LL6gHf43 zh>Ne-Fi+3&Hex8yuG90D6u|eu9zO!JezqPk zYMEr4E!_?i~abki@^rP9yxm4782fB47G8McqaI;#r5dftwVCdrl-?5>HAeANs|Az$+LCVn6+h*!83Puv_S-wspo z{MXnIruY?I7}3yKBQcQ&5?}Z7CW$vF9boql0}-g+i}iASfBEn+TmgM{p#@JfS#uQ2 z0I`vQvn-$2U4b8n;g1bCqjn61+(AU@H}JTUKkMmJ^aCyM?!I5Z(CKh;XBjDa2{bjc zDc%b!+}e-%{{DCvJMbn6bz2AJ9&`nAm#g8*cSu?oS{UN(h-W{K#tNujFfaGJyNBSs5@ju7azz&%jZcjo9@v~CAhey+1> zB7Ur+ai}Ig$#weNd0hho9p&^ycOCJ{l)FWxoY=O(=ga5I1{gd`eB=nSJUdH@r@6IH@87tQxF$o6qhLlhko!{!oLvjE-M>+fGR6PQ zmL|}y+zbZ}UG`3xvbMB_Oy@#_b`XaP41Od~*@kF>zkr*YR%qtoJU!JJi{C3gJk$yI zV(2vvwwdjji)52MZUO1|`mpL0-dYhGSod=IbSL1vKL|o%(7Ha&)(Z zxiB3M1-g^^%(qeGJ&r;Wsftb6C`9fp&{<|=wUar+^(Nq?P%K})m-~3hS^>L$(Q8^5T*OTx`fOzA zkBVOyg1-pCmQdPW0~w9rZ4&-nA=xo)sD{hF1HL8TqMO(uP3aIlx*uqy&)sK91Z`ym zX@wfM##!nKIX^Hyoy^Jfj$p*Jr)coqbv61_3crOPY+IvxMyCe9#?3IJLjbplw2(R zr^LeGXPV7=G{p^jCfWYv$?nzXV?(_F)^{M20{K3KVol+UgNf6>$%aUg_v1|g=Z;Mc zGNsD^R$PkjK4puBcQLK)WF!z;uhHyek6As+&elCann2Sa)J64efyjh_0DNfos;a`y zfr5l4jds$Qj>ib8{}^g84hP%<11^HDGep6rvdigko)rDMKy=tvYD4iVW_v7UMb8D^ z8*9>kp(k}vYB2nM1Q^;#vE~a-X~)SKR^xFC{;t989wI4INfvcW@pHTI!63nH;>aZx zyrjhJr8Wyjeuo`2+(5#prm^qH4n@J z-7TS9L)5<%{c{NZI3HUe#|~7YpZ5t4(n}JtDEZPE$z2w%XVQtf zCA;~iG=c9{V~fprH{tiXO7INvT!DvnlA8s@*q?v%7Z~-FSD%U=nuR>~4MKj-wK(hq zmw$})dqwfO$`QxdNf&8&eZBmBkucL!M=FEe-XbZHSl2u^h4~x-%IYUzAPC^Yl>WzT zH=;%()0CW+bjfQqN2m>nC36%WfsX!;g4}aPap(xgkKh@^l1x!j;ZwlW>;D(tb~V{_ z6L^VI(ivF$@g*|#0(A%%3{Q^M2S}ojJ8^g$&^Ji)(_yv+Pr^i^V8_-H$t{+A_##m+ z_1*^0|IH4$Ofr2!#4gRjiVCADC&uPMux_H0+vn)u_d%omJKUUt-*><#^Jt&0Mv)E{ z@Cfl?110k(a&#a|69G5xg>RY$ArlwO#AT+kCbAO;Vy3yE^kY5^fz0b7qGImNfc@!L z&XVt!<*U@En!W0tAF;6VkX#+(itp)f{-TOPKoPwV^pufqnFZnt=&&~M$nzhh4b=G(?U&@)SpqvS}j*+-eEmn&P^3{Xe&I)YI{!7=U_6B zWd$uA9OBBqi_#@$&y<9zRXOLiV4wM-Y#Zm(D(FfP-BeBf{7}Q|GhIv!;4EswxX9_> z?!pNd;Slj0pVJtI%N-QQ58>yAu*xbnQD@Tz3#Mc&N=(8m}xZ|Yb+w#V6!e|eUX3cNi7 z7g!YEQI0Gq{@A1$LMA=5r04z3JG?_l9f*bph!xMkMoL-LB(=i{YQH~XP_;<+Qw`~U z81m6NF@!_lZrUI3>P`cf?Sca@=N`lKy5^|C$4RMIE=%|)VdP%QM z1fR5X-q;qzYdwS2&hVBydrE(frWlCzTx@LQHJlD_s=3oJ=TlpkAe`#xO!8d=T_=9|Y_Uq1cy`fWM( zmXG;N9>%XK#|YHYw1(H<`c?j$mu@@X<)nCdG6{=(RD7|b;-ao%R@Yc8k;*R;697oX zn}z0y`>g$aaqFpovMU;Cn!u@h-Zj*1wa;JGwKP3(oH;9gsLN{cwuAU{il229xHp7s zc8_hJmZS_Py?QuJR0hz`78I9N;ojij$HOtOfdv?w2bQ|(=W0<|nqXrLesv4}Bwla@ zG}j?jF~8Q`bizh=+&ix=d7>#}r)CEkihJ2st_s#n`DqdcX|%mjf)9Vgh7DEIri9%T zW`!Z|UZ_=cdpmTtv2z`KJ1a?IHSyOo(zq9~g{R@SmVK(i7R^(ufWlYxpm71%OmHIK zorHQVk-21#9%g?kf!#=AY4kA_lw7r?!~ z=oVu;>WF$s@<@v8SdP{1!C%@7YWPbwGujq_)~>kjFOI@ngd^I{>rj(7*!fdN;;-*) zBq%1CW_R3e#2BV^q*t$!|9*m5^-8YN+RP^Cp=44pOf4v(_-+SREJvmuqvf9@+gcgv z_?8B2VIT`%?S|_o;7_K@!aCstGy^Ah>EB1x6LjfaDEbyK_Bkc8GASh|2pFpdrtI+h zK9bRE*j8u6)fJNiM@<7SswsCK6KkA&0u=N^!4`G`I*JSv+)#aUeHedJce)xWmtp&Y z6+WJoL~#U+eMzd6@@(wUd*-5P=6q{foUQXtBSOOJh_Kd^_D+zl#5977?!* zi*i*;=GAZ{x;z4Ln1})bD3Y0KP62_oLTVj<4`?X4 zwiLfY4m5oI9y!ANV$|oX)fTG`A0f-Z}>^PMH4H^&tJYhUl=J6o(x*t69VQAqDrv%IM--vmGH7~9iG=pBX5+@!#3&ia z*<)XVrTqSYJklXFF)yiA8Cvt7X|0FL7ezC8Hog&LaaMQ?R z2H5dLoe7JNx^kRfsE(V?p+pD4*#yf)YDm3um&M?Sj;J_Qo+fb`bDTRY?8=OIBlqmF zJ8bOEUp&(86H4_IJub_-FUJ+%vK?D@2Yc2ov8px9N27wlUNo%g81esyOpx+6pl3sH zq2u`O#FI5)N$}Qu?Co8N)&90X8S?&c!X)FLo;EZ9o(97Nzo;H2?2_x~&$2{hy>vpw zl$o++b$~dUB>Fzrr1-z58tqN+!(CKaQwCX_0QdEL$t+ zVIw$TBD+h8%l=tbHj_9y%#Z`g|8I#m9aVi<=xmAV3svEMTQ;eul_QrAmmw>7Ao~JN zG5_(D;WhK(_t_#Cl+tF>yejo7z8ibg)i*t7nRb8~(LjI+;2r?i|3+O~FgwsA4_!V3 z7QXuKq)-y^q6y?dhznx-OD(Y~NAB#BS9{67&&0OvR2!muUL#t}5{)B#qD4+fy_e}q zcfEN2g4Lb@d41Ff6iN|C7JUDR3cBneDcoI?h#vTmgvShWu;3oHaLP$IvCxmMc;|$Z ztjB%7bz++~ian61zg1Jzm{rLq&l{?0rhc=;?&R?+`hp6`MuK$bFeI+=Z66G=#zKBn zeR3p1$O5qAq)#VQ^tj0dRzjBEMV1<%2jSsFA318TYx47j`GNo@e9VDEK+b!~_j8av zKhRz2g%|WZifWr_RgwYj&WlDK$Ec>k)GOauyKAQ-NX-V|$XwAKrgHEPQ3J*Am?xA2Rf5m<%K*!Yuc#!Rq;=~1>TM0kD?@TKA{~czo4)#oi zQ8>RIVS*okPO+0@Vy9@ zy^A4vriDFwgxMZ9)BP-Iv{0vHRDger~|774gtlFQ;iK}K-0+0ond0%-7 zxQuXL%e3L9Vd)aH9-+_5G0N+-@Z%My!yQk&(^A(S1RH|z0Ym&hN6|eZP)+4LA`OlM zjc$H-UI2r|(4z>fNzL>Su|r}VNmab#XUVVUf|UkHTEx?*%aD=l*g@-|j)d)`*miR0 zEE*$5FgBtttNF5IyF@v~;lc!ZZw!DwvM@_RQ}m&?rHIIpp=1q3+(LXMoudEUdN>!G3ky|yo$p2S1)d&h9wBl7RT z={V}$po;OqpM|acWQx_j=WX>wUyv(GQfcCOP9j@c1;wasA^Go>#K2e%N*mh{rX>Yk4+-*u@d{sS#la9|Bao9!?JN~wvVaA)eNQj&x@b{W)Qq~sW6Y|oeA>l%sLdnrm`wq0m8V8?cEBtCLFnPcwnMZXdleFmKh9+|eUdDS zMN)PoSsYl44eKYy95VOC|1!Z^52Kgg@N9H}6$rZE<=A=-LZn;&jF$J7A&t{SyIM)^ z*r^EH5c+FutgJseo>Q&zKCDy?oN}s8m?Blpc=N<>XyEdLAuhB&7c@J~jI(7^%RLls zJ7WCt?IRC{yy5jLr~X{f3Zy?yP=g*{O6;9p!R^|B=MAPzXPhIxsjc9x7T6gET0<** zktsdo=4Y^u(^jIM=sk6^QeSkr^lHJUAxwF^Z1pJG*wNoF_Jxrvr4oUMM znr$7)KJN+NI5skb4Cvt+Z0U7MrHyye1lWG5xk+QKLlfTmAFmK*;Wr|&ZTGQShJ4ya zx`XCuiue$CcpcoC$-rOVq!u@oj&wMko;;+5`LlkUXa< zmP9L_uM~KbE7zmtI%Nd&ctAHqA>z4NQPu z2V~3#IF(HZgj%ViYmon7uTgeY;AyQqZ*+$QyGSYEPrxzZVDANFE-fA~74Fp+rn1EY zwyuq11ab@wL#N!3g-k;Wj5JA)n=lflgC5T0cAt6;uf=0j=jVu-u7rbr{EVRNhwh3g zC%lyCW0B3Uw{7Ym{+J$M_pM0sITX9mhMmn%QW2!<||${ z;4P4D>=#Pn)B)aJda@yH4}Gd!ZbWNx+y0annllc6EE&Q6b zx{KlrhQ((Mp9alm zYWY!M-cC~MFV05urE6k^|1yngqL5w0UbMLBB@v;uUM&8Sr{(pmQR8w_B$CcX>G70h zb!s*DX`ZeususwPIr(l2ZUiYjbc)wCY20=+tUXq5SUVMs-qJTrtEmmBc19k~RPzj! zsN;TMTgxAIRe?dq0vaD}Lo-TIEU>@*lCuLzuK6tMp1C0B}#E&Ul#FkC~ z50@oth1^cAq(NJUCCe8O>GL3qs01~<_re6zb_lWQLZ+KBQk;!w!vd)(HKQu|%xH~d z`Q0R*`#X3OONheK9~0!@(=1J{;x8Kh;~Xu%NyBv<_oOI1tcb8EGvFRtX$YJ;j6V8? zeoW)jam2L=BQZnzSp{xX77kN4B8_%@yBwrDK&^%hs^~P0m8q+f^>b2A5}uJAK7zyM zc_YY_6k$;ZDL`{?!sMTkfm}|Fs(hMM$|7+#j4{OvmG7Of?SMzU#;0GvrxVnsFwBu9 z5@e(i8Pm^H={|CSFZGI+4ZWa-JotWUiYiOsT`VdrN>kQ7YQZGXpJ2r1bGfV1f6K+L zZ#_qUe|a@J)zX)}Z{-6l?OJ~R@(D6}XN>??frVEw?3JBHNT_0L$Q_?p zjoGCV-niOkme=pR?^Ch(Kg5++`xYmRaKK)hpg&xy>#^e*(GQ^FhlF@#hZ=`-@_7dk zY#hos4Yo|d@77?K9T4J?O1XsQetz94f3{k>?-@S;F~oRj`eVEwvk~+lSGGMWO>_xcgjJN zu7FppL^pA2_ycb{KS}P4_G91vGr$k#hN0IE*<+V3v*W%vN@w+u%xUBTOs)FK?(;e7 zBd4j>O3G*PFQ4Eq=Sp?p=x<1jC+Twvd2J!13GdP*P-a>53XME9vK$bq?Tb@>jVxAkx=oDO8MUEnyb@9U=H#(2(6JkmC2(7ldcsV0P9yJ45byQ_HK=1L zore6MVs%V4zxg8J^39u#o-pbe+#~P+>c@x|+=rf*!TC1SraM@HM#ayM>-+i{uJqp` z1AlqEh4d-a%s|t88B}fxRBlU*}2}8|KI}Zn*szZU}O;(qF+D}`S*}h)koO)06?87 zHDx8;?C&u7ej=~d^ zzL>$9{-)?6r2bXBl2JWL?a5?^c|#9U79aa%7#}^$O0>;Ngaxc!t1X@(@4o4T>@pK? zwN!W4ubBSr2gf9WLp350x4$yQS{Cy?z*j?{95^XA@m z@ZeDuT6q?%GYIU3q8=kw8XLc0Tj%l9T2Q;NiSY@f{3}*%A)1&-3vfbcGg5g6hOh&k zNhxj)bZE#=AL6ra}tqI6AnR6xz^82|fgVDr3&)wAp8$ zp8*oDr#O^h3O*P=qj?&N{h2WKM^CgX`?z7vEJdmxUicg{2@E6{MChWKO^gNTX#IyZ z?%ry^k@to2kSfkjtGb?AQ7kXFX)&*v9#1>1BnT?ochSsUK$iUNfaGF>ieLeM94ms| zUne&QMyU2)EqmTl_q*$riNNZQGPlSyWV$mkp8RdE;L53jo0O90Dl|ql$*AhuXxKJm z2EHg^(q19mD7eg)nE`qKBH*)@dUnB5O64o`yMg$*uJk?R+1)*5K%Tn_kF&Jt`x7i* zw-bAQDKFK+Wc<@fwd5QT3#GK4B6cT#QKM_%!*{`48ZcFkl@BBD*8->RAj_TE(to11 zlg$6sOka*ydb1AURdf+z>|smsIyk^yR6Im(3Xq@ME^oGS{`U17cxaCy@21)XxyeA^ z?-0QTeCHJ3GVM-14 zegWpK+ZS3{gv|}YdGG4kVaP6X50a+W&+jIE=K=Y%Q{37;&iwbAoR_vM8Ilx@+6bsm zykDNTP&x`;(gXG;!;bfMQXKz8!7&3Aa~uwSFw$)xx@;+W3@hXh)cEI%7^Lf=g8Na> zU?xoXf?iXvbZP#c?_7e^J>YSZ4K__f=4s4=2VjmC;dhW)H8IS$L(GkYR;)LWx5dKQ zM5U&O+@yP*)|Y969{Mdb)2m?UrkAAXDL6Bs2lAOMD*efk>a>u|o>1!Tg+1gvXC%VW z*(hE)IqD={ZYFvQNi8)>($OH3yrb&moNWO6&X)=D4-7P5r<}Z^LfO&OZ$dfW5$odk zWh>ZjCt=%fNVc8O%(eLX6g#vRjB|4Bdm5(5zJQg�@w;{RvmJin`FWpF0li(HBJ1 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
    >%Yh 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 literal 0 HcmV?d00001 diff --git a/docs/chroma.md b/docs/chroma.md new file mode 100644 index 000000000..d013a43c8 --- /dev/null +++ b/docs/chroma.md @@ -0,0 +1,33 @@ +# How to Use + +You can run Chroma using stable-diffusion.cpp with a GPU that has 6GB or even 4GB of VRAM, without needing to offload to RAM. + +## Download weights + +- Download Chroma + - If you don't want to do the conversion yourself, download the preconverted gguf model from [silveroxides/Chroma-GGUF](https://huggingface.co/silveroxides/Chroma-GGUF) + - Otherwise, download chroma's safetensors from [lodestones/Chroma](https://huggingface.co/lodestones/Chroma) +- Download vae from https://huggingface.co/black-forest-labs/FLUX.1-dev/blob/main/ae.safetensors +- Download t5xxl from https://huggingface.co/comfyanonymous/flux_text_encoders/blob/main/t5xxl_fp16.safetensors + +## Convert Chroma weights + +You can download the preconverted gguf weights from [silveroxides/Chroma-GGUF](https://huggingface.co/silveroxides/Chroma-GGUF), this way you don't have to do the conversion yourself. + +``` +.\bin\Release\sd.exe -M convert -m ..\..\ComfyUI\models\unet\chroma-unlocked-v40.safetensors -o ..\models\chroma-unlocked-v40-q8_0.gguf -v --type q8_0 +``` + +## Run + +### Example +For example: + +``` + .\bin\Release\sd.exe -diffusion-model ..\models\chroma-unlocked-v40-q8_0.gguf --vae ..\models\ae.sft --t5xxl ..\models\t5xxl_fp16.safetensors -p "a lovely cat holding a sign says 'chroma.cpp'" --cfg-scale 4.0 --sampling-method euler -v --chroma-disable-dit-mask +``` + +![](../assets/flux/chroma_v40.png) + + + From b1fc16b504297a03aeb2f0678078dfcad322243e Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Mon, 30 Jun 2025 12:23:21 -0300 Subject: [PATCH 062/143] fix: allow resetting clip_skip to its default value (#697) --- clip.hpp | 12 ++++++------ conditioner.hpp | 43 +++++++++++++++++++++++-------------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/clip.hpp b/clip.hpp index 2307ee3c5..d359f61cd 100644 --- a/clip.hpp +++ b/clip.hpp @@ -678,8 +678,8 @@ class CLIPTextModel : public GGMLBlock { bool with_final_ln = true; CLIPTextModel(CLIPVersion version = OPENAI_CLIP_VIT_L_14, - int clip_skip_value = -1, - bool with_final_ln = true) + bool with_final_ln = true, + int clip_skip_value = -1) : version(version), with_final_ln(with_final_ln) { if (version == OPEN_CLIP_VIT_H_14) { hidden_size = 1024; @@ -701,7 +701,7 @@ class CLIPTextModel : public GGMLBlock { void set_clip_skip(int skip) { if (skip <= 0) { - return; + skip = -1; } clip_skip = skip; } @@ -871,9 +871,9 @@ struct CLIPTextModelRunner : public GGMLRunner { std::map& tensor_types, const std::string prefix, CLIPVersion version = OPENAI_CLIP_VIT_L_14, - int clip_skip_value = 1, - bool with_final_ln = true) - : GGMLRunner(backend), model(version, clip_skip_value, with_final_ln) { + bool with_final_ln = true, + int clip_skip_value = -1) + : GGMLRunner(backend), model(version, with_final_ln, clip_skip_value) { model.init(params_ctx, tensor_types, prefix); } diff --git a/conditioner.hpp b/conditioner.hpp index 4005fadf7..4124a68ac 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -63,23 +63,24 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { PMVersion pv = PM_VERSION_1, int clip_skip = -1) : version(version), pm_version(pv), tokenizer(sd_version_is_sd2(version) ? 0 : 49407), embd_dir(embd_dir) { - if (clip_skip <= 0) { - clip_skip = 1; - if (sd_version_is_sd2(version) || sd_version_is_sdxl(version)) { - clip_skip = 2; - } - } if (sd_version_is_sd1(version)) { - text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip); + text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14); } else if (sd_version_is_sd2(version)) { - text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPEN_CLIP_VIT_H_14, clip_skip); + text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPEN_CLIP_VIT_H_14); } else if (sd_version_is_sdxl(version)) { - text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip, false); - text_model2 = std::make_shared(backend, tensor_types, "cond_stage_model.1.transformer.text_model", OPEN_CLIP_VIT_BIGG_14, clip_skip, false); + text_model = std::make_shared(backend, tensor_types, "cond_stage_model.transformer.text_model", OPENAI_CLIP_VIT_L_14, false); + text_model2 = std::make_shared(backend, tensor_types, "cond_stage_model.1.transformer.text_model", OPEN_CLIP_VIT_BIGG_14, false); } + set_clip_skip(clip_skip); } void set_clip_skip(int clip_skip) { + if (clip_skip <= 0) { + clip_skip = 1; + if (sd_version_is_sd2(version) || sd_version_is_sdxl(version)) { + clip_skip = 2; + } + } text_model->set_clip_skip(clip_skip); if (sd_version_is_sdxl(version)) { text_model2->set_clip_skip(clip_skip); @@ -665,15 +666,16 @@ struct SD3CLIPEmbedder : public Conditioner { std::map& tensor_types, int clip_skip = -1) : clip_g_tokenizer(0) { - if (clip_skip <= 0) { - clip_skip = 2; - } - clip_l = std::make_shared(backend, tensor_types, "text_encoders.clip_l.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip, false); - clip_g = std::make_shared(backend, tensor_types, "text_encoders.clip_g.transformer.text_model", OPEN_CLIP_VIT_BIGG_14, clip_skip, false); + clip_l = std::make_shared(backend, tensor_types, "text_encoders.clip_l.transformer.text_model", OPENAI_CLIP_VIT_L_14, false); + clip_g = std::make_shared(backend, tensor_types, "text_encoders.clip_g.transformer.text_model", OPEN_CLIP_VIT_BIGG_14, false); t5 = std::make_shared(backend, tensor_types, "text_encoders.t5xxl.transformer"); + set_clip_skip(clip_skip); } void set_clip_skip(int clip_skip) { + if (clip_skip <= 0) { + clip_skip = 2; + } clip_l->set_clip_skip(clip_skip); clip_g->set_clip_skip(clip_skip); } @@ -1010,14 +1012,15 @@ struct FluxCLIPEmbedder : public Conditioner { FluxCLIPEmbedder(ggml_backend_t backend, std::map& tensor_types, int clip_skip = -1) { - if (clip_skip <= 0) { - clip_skip = 2; - } - clip_l = std::make_shared(backend, tensor_types, "text_encoders.clip_l.transformer.text_model", OPENAI_CLIP_VIT_L_14, clip_skip, true); + clip_l = std::make_shared(backend, tensor_types, "text_encoders.clip_l.transformer.text_model", OPENAI_CLIP_VIT_L_14, true); t5 = std::make_shared(backend, tensor_types, "text_encoders.t5xxl.transformer"); + set_clip_skip(clip_skip); } void set_clip_skip(int clip_skip) { + if (clip_skip <= 0) { + clip_skip = 2; + } clip_l->set_clip_skip(clip_skip); } @@ -1422,4 +1425,4 @@ struct PixArtCLIPEmbedder : public Conditioner { } }; -#endif \ No newline at end of file +#endif From 539b5b9374b1289ae040f2b8dc83b26dc7372140 Mon Sep 17 00:00:00 2001 From: R0CKSTAR Date: Mon, 30 Jun 2025 23:27:40 +0800 Subject: [PATCH 063/143] fix: fix musa docker build (#662) Signed-off-by: Xiaodong Ye --- Dockerfile.musa | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Dockerfile.musa b/Dockerfile.musa index 0adcb7ee5..c7f5f2e83 100644 --- a/Dockerfile.musa +++ b/Dockerfile.musa @@ -2,14 +2,17 @@ ARG MUSA_VERSION=rc3.1.1 FROM mthreads/musa:${MUSA_VERSION}-devel-ubuntu22.04 as build -RUN apt-get update && apt-get install -y cmake +RUN apt-get update && apt-get install -y ccache cmake git WORKDIR /sd.cpp COPY . . RUN mkdir build && cd build && \ - cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DSD_MUSA=ON -DCMAKE_BUILD_TYPE=Release && \ + cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -fopenmp -I/usr/lib/llvm-14/lib/clang/14.0.0/include -L/usr/lib/llvm-14/lib" \ + -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fopenmp -I/usr/lib/llvm-14/lib/clang/14.0.0/include -L/usr/lib/llvm-14/lib" \ + -DSD_MUSA=ON -DCMAKE_BUILD_TYPE=Release && \ cmake --build . --config Release FROM mthreads/musa:${MUSA_VERSION}-runtime-ubuntu22.04 as runtime From 0d8b39f0ba202e407cc6908dac371ff005bc8ab0 Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Mon, 30 Jun 2025 12:29:32 -0300 Subject: [PATCH 064/143] fix: avoid crash on sdxl loras (#658) Some SDXL LoRAs (eg. PCM) can exceed 12k nodes. --- lora.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lora.hpp b/lora.hpp index ee14bce29..471d0ef8e 100644 --- a/lora.hpp +++ b/lora.hpp @@ -3,7 +3,7 @@ #include "ggml_extend.hpp" -#define LORA_GRAPH_SIZE 10240 +#define LORA_GRAPH_SIZE 15360 struct LoraModel : public GGMLRunner { enum lora_t { From d42fd59464fef92934a082bab24855fd6f6177cb Mon Sep 17 00:00:00 2001 From: rmatif <66360289+rmatif@users.noreply.github.com> Date: Mon, 30 Jun 2025 17:32:23 +0200 Subject: [PATCH 065/143] feat: add OpenCL backend support (#680) --- CMakeLists.txt | 7 +++++ README.md | 69 +++++++++++++++++++++++++++++++++++++++++++- common.hpp | 2 +- esrgan.hpp | 4 +-- ggml | 2 +- ggml_extend.hpp | 7 ++++- model.cpp | 4 +++ stable-diffusion.cpp | 8 +++++ tae.hpp | 2 +- upscaler.cpp | 4 +++ 10 files changed, 102 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 782a893e4..06de0d58b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ option(SD_CUDA "sd: cuda backend" OFF) option(SD_HIPBLAS "sd: rocm backend" OFF) option(SD_METAL "sd: metal backend" OFF) option(SD_VULKAN "sd: vulkan backend" OFF) +option(SD_OPENCL "sd: opencl backend" OFF) option(SD_SYCL "sd: sycl backend" OFF) option(SD_MUSA "sd: musa backend" OFF) option(SD_FAST_SOFTMAX "sd: x1.5 faster softmax, indeterministic (sometimes, same seed don't generate same image), cuda only" OFF) @@ -52,6 +53,12 @@ if (SD_VULKAN) add_definitions(-DSD_USE_VULKAN) endif () +if (SD_OPENCL) + message("-- Use OpenCL as backend stable-diffusion") + set(GGML_OPENCL ON) + add_definitions(-DSD_USE_OPENCL) +endif () + if (SD_HIPBLAS) message("-- Use HIPBLAS as backend stable-diffusion") set(GGML_HIP ON) diff --git a/README.md b/README.md index 232e8816f..8ac08a051 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Inference of Stable Diffusion and Flux in pure C/C++ - Accelerated memory-efficient CPU inference - Only requires ~2.3GB when using txt2img with fp16 precision to generate a 512x512 image, enabling Flash Attention just requires ~1.8GB. - AVX, AVX2 and AVX512 support for x86 architectures -- Full CUDA, Metal, Vulkan and SYCL backend for GPU acceleration. +- Full CUDA, Metal, Vulkan, OpenCL and SYCL backend for GPU acceleration. - Can load ckpt, safetensors and diffusers models/checkpoints. Standalone VAEs models - No need to convert to `.ggml` or `.gguf` anymore! - Flash Attention for memory usage optimization @@ -160,6 +160,73 @@ cmake .. -DSD_VULKAN=ON cmake --build . --config Release ``` +##### Using OpenCL (for Adreno GPU) + +Currently, it supports only Adreno GPUs and is primarily optimized for Q4_0 type + +To build for Windows ARM please refers to [Windows 11 Arm64 +](https://github.com/ggml-org/llama.cpp/blob/master/docs/backend/OPENCL.md#windows-11-arm64) + +Building for Android: + + Android NDK: + Download and install the Android NDK from the [official Android developer site](https://developer.android.com/ndk/downloads). + +Setup OpenCL Dependencies for NDK: + +You need to provide OpenCL headers and the ICD loader library to your NDK sysroot. + +* OpenCL Headers: + ```bash + # In a temporary working directory + git clone https://github.com/KhronosGroup/OpenCL-Headers + cd OpenCL-Headers + # Replace with your actual NDK installation path + # e.g., cp -r CL /path/to/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include + sudo cp -r CL /toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include + cd .. + ``` + +* OpenCL ICD Loader: + ```bash + # In the same temporary working directory + git clone https://github.com/KhronosGroup/OpenCL-ICD-Loader + cd OpenCL-ICD-Loader + mkdir build_ndk && cd build_ndk + + # Replace in the CMAKE_TOOLCHAIN_FILE and OPENCL_ICD_LOADER_HEADERS_DIR + cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE=/build/cmake/android.toolchain.cmake \ + -DOPENCL_ICD_LOADER_HEADERS_DIR=/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include \ + -DANDROID_ABI=arm64-v8a \ + -DANDROID_PLATFORM=24 \ + -DANDROID_STL=c++_shared + + ninja + # Replace + # e.g., cp libOpenCL.so /path/to/android-ndk-r26c/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android + sudo cp libOpenCL.so /toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android + cd ../.. + ``` + +Build `stable-diffusion.cpp` for Android with OpenCL: + +```bash +mkdir build-android && cd build-android + +# Replace with your actual NDK installation path +# e.g., -DCMAKE_TOOLCHAIN_FILE=/path/to/android-ndk-r26c/build/cmake/android.toolchain.cmake +cmake .. -G Ninja \ + -DCMAKE_TOOLCHAIN_FILE=/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=arm64-v8a \ + -DANDROID_PLATFORM=android-28 \ + -DGGML_OPENMP=OFF \ + -DSD_OPENCL=ON + +ninja +``` +*(Note: Don't forget to include `LD_LIBRARY_PATH=/vendor/lib64` in your command line before running the binary)* + ##### Using SYCL Using SYCL makes the computation run on the Intel GPU. Please make sure you have installed the related driver and [Intel® oneAPI Base toolkit](https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit.html) before start. More details and steps can refer to [llama.cpp SYCL backend](https://github.com/ggerganov/llama.cpp/blob/master/docs/backend/SYCL.md#linux). diff --git a/common.hpp b/common.hpp index 337b4a0c4..b20c60ff1 100644 --- a/common.hpp +++ b/common.hpp @@ -56,7 +56,7 @@ class UpSampleBlock : public GGMLBlock { // x: [N, channels, h, w] auto conv = std::dynamic_pointer_cast(blocks["conv"]); - x = ggml_upscale(ctx, x, 2); // [N, channels, h*2, w*2] + x = ggml_upscale(ctx, x, 2, GGML_SCALE_MODE_NEAREST); // [N, channels, h*2, w*2] x = conv->forward(ctx, x); // [N, out_channels, h*2, w*2] return x; } diff --git a/esrgan.hpp b/esrgan.hpp index 989d15fee..5cbb4ad8f 100644 --- a/esrgan.hpp +++ b/esrgan.hpp @@ -130,8 +130,8 @@ class RRDBNet : public GGMLBlock { body_feat = conv_body->forward(ctx, body_feat); feat = ggml_add(ctx, feat, body_feat); // upsample - feat = lrelu(ctx, conv_up1->forward(ctx, ggml_upscale(ctx, feat, 2))); - feat = lrelu(ctx, conv_up2->forward(ctx, ggml_upscale(ctx, feat, 2))); + feat = lrelu(ctx, conv_up1->forward(ctx, ggml_upscale(ctx, feat, 2, GGML_SCALE_MODE_NEAREST))); + feat = lrelu(ctx, conv_up2->forward(ctx, ggml_upscale(ctx, feat, 2, GGML_SCALE_MODE_NEAREST))); auto out = conv_last->forward(ctx, lrelu(ctx, conv_hr->forward(ctx, feat))); return out; } diff --git a/ggml b/ggml index ff9052988..9e4bee1c5 160000 --- a/ggml +++ b/ggml @@ -1 +1 @@ -Subproject commit ff9052988b76e137bcf92bb335733933ca196ac0 +Subproject commit 9e4bee1c5afc2d677a5b32ecb90cbdb483e81fff diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 101a5d1f6..e20fc4f4e 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -39,6 +39,10 @@ #include "ggml-vulkan.h" #endif +#ifdef SD_USE_OPENCL +#include "ggml-opencl.h" +#endif + #ifdef SD_USE_SYCL #include "ggml-sycl.h" #endif @@ -113,7 +117,8 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_kronecker(ggml_context* ctx, struct g a->ne[0] * b->ne[0], a->ne[1] * b->ne[1], a->ne[2] * b->ne[2], - a->ne[3] * b->ne[3]), + a->ne[3] * b->ne[3], + GGML_SCALE_MODE_NEAREST), b); } diff --git a/model.cpp b/model.cpp index 24da39f6d..3e0bde77b 100644 --- a/model.cpp +++ b/model.cpp @@ -26,6 +26,10 @@ #include "ggml-vulkan.h" #endif +#ifdef SD_USE_OPENCL +#include "ggml-opencl.h" +#endif + #define ST_HEADER_SIZE_LEN 8 uint64_t read_u64(uint8_t* buffer) { diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 552228722..e7aa441c2 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -181,6 +181,14 @@ class StableDiffusionGGML { LOG_WARN("Failed to initialize Vulkan backend"); } #endif +#ifdef SD_USE_OPENCL + LOG_DEBUG("Using OpenCL backend"); + // ggml_log_set(ggml_log_callback_default, nullptr); // Optional ggml logs + backend = ggml_backend_opencl_init(); + if (!backend) { + LOG_WARN("Failed to initialize OpenCL backend"); + } +#endif #ifdef SD_USE_SYCL LOG_DEBUG("Using SYCL backend"); backend = ggml_backend_sycl_init(0); diff --git a/tae.hpp b/tae.hpp index c458b87d2..678c44c57 100644 --- a/tae.hpp +++ b/tae.hpp @@ -149,7 +149,7 @@ class TinyDecoder : public UnaryBlock { if (i == 1) { h = ggml_relu_inplace(ctx, h); } else { - h = ggml_upscale(ctx, h, 2); + h = ggml_upscale(ctx, h, 2, GGML_SCALE_MODE_NEAREST); } continue; } diff --git a/upscaler.cpp b/upscaler.cpp index 0c11b666e..137213496 100644 --- a/upscaler.cpp +++ b/upscaler.cpp @@ -28,6 +28,10 @@ struct UpscalerGGML { LOG_DEBUG("Using Vulkan backend"); backend = ggml_backend_vk_init(0); #endif +#ifdef SD_USE_OPENCL + LOG_DEBUG("Using OpenCL backend"); + backend = ggml_backend_opencl_init(); +#endif #ifdef SD_USE_SYCL LOG_DEBUG("Using SYCL backend"); backend = ggml_backend_sycl_init(0); From 23de7fc44a9a93beff02ff8382307baacbffff1e Mon Sep 17 00:00:00 2001 From: leejet Date: Mon, 30 Jun 2025 23:49:52 +0800 Subject: [PATCH 066/143] chore: avoid warnings when building on linux --- stable-diffusion.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index e7aa441c2..78be724f9 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -284,10 +284,10 @@ class StableDiffusionGGML { model_loader.set_wtype_override(GGML_TYPE_F32, "vae."); } - LOG_INFO("Weight type: %s", model_wtype != SD_TYPE_COUNT ? ggml_type_name(model_wtype) : "??"); - LOG_INFO("Conditioner weight type: %s", conditioner_wtype != SD_TYPE_COUNT ? ggml_type_name(conditioner_wtype) : "??"); - LOG_INFO("Diffusion model weight type: %s", diffusion_model_wtype != SD_TYPE_COUNT ? ggml_type_name(diffusion_model_wtype) : "??"); - LOG_INFO("VAE weight type: %s", vae_wtype != SD_TYPE_COUNT ? ggml_type_name(vae_wtype) : "??"); + LOG_INFO("Weight type: %s", model_wtype != GGML_TYPE_COUNT ? ggml_type_name(model_wtype) : "??"); + LOG_INFO("Conditioner weight type: %s", conditioner_wtype != GGML_TYPE_COUNT ? ggml_type_name(conditioner_wtype) : "??"); + LOG_INFO("Diffusion model weight type: %s", diffusion_model_wtype != GGML_TYPE_COUNT ? ggml_type_name(diffusion_model_wtype) : "??"); + LOG_INFO("VAE weight type: %s", vae_wtype != GGML_TYPE_COUNT ? ggml_type_name(vae_wtype) : "??"); LOG_DEBUG("ggml tensor size = %d bytes", (int)sizeof(ggml_tensor)); From ea46fd6948799c517e0346af2eae11ef0ddb8db4 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Tue, 1 Jul 2025 17:01:29 +0200 Subject: [PATCH 067/143] fix: force zero-initialize output of tiling (#703) --- ggml_extend.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ggml_extend.hpp b/ggml_extend.hpp index e20fc4f4e..635b78006 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -602,6 +602,8 @@ typedef std::function on_tile_process; // Tiling __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) { + output = ggml_set_f32(output, 0); + int input_width = (int)input->ne[0]; int input_height = (int)input->ne[1]; int output_width = (int)output->ne[0]; From ecf5db97aeea4630c547f45e2262b2dc1f866a22 Mon Sep 17 00:00:00 2001 From: leejet Date: Tue, 1 Jul 2025 23:05:48 +0800 Subject: [PATCH 068/143] chore: fix windows build and release --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a16e692ec..4112ae9bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -146,7 +146,7 @@ jobs: sd-${{ env.BRANCH_NAME }}-${{ steps.commit.outputs.short }}-bin-${{ steps.system-info.outputs.OS_TYPE }}-${{ steps.system-info.outputs.OS_NAME }}-${{ steps.system-info.outputs.OS_VERSION }}-${{ steps.system-info.outputs.CPU_ARCH }}.zip windows-latest-cmake: - runs-on: windows-2019 + runs-on: windows-2025 env: VULKAN_VERSION: 1.3.261.1 From 9251756086f9467cb6a593efbc1506c00ebf2626 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Tue, 1 Jul 2025 17:13:04 +0200 Subject: [PATCH 069/143] feat: add CosXL support (#683) --- denoiser.hpp | 58 ++++++++++++++++++++++++++++++-------------- stable-diffusion.cpp | 15 ++++++++++-- 2 files changed, 53 insertions(+), 20 deletions(-) diff --git a/denoiser.hpp b/denoiser.hpp index 5dec109de..ee4ae5172 100644 --- a/denoiser.hpp +++ b/denoiser.hpp @@ -168,24 +168,21 @@ struct AYSSchedule : SigmaSchedule { std::vector inputs; std::vector results(n + 1); - switch (version) { - case VERSION_SD2: /* fallthrough */ - LOG_WARN("AYS not designed for SD2.X models"); - case VERSION_SD1: - LOG_INFO("AYS using SD1.5 noise levels"); - inputs = noise_levels[0]; - break; - case VERSION_SDXL: - LOG_INFO("AYS using SDXL noise levels"); - inputs = noise_levels[1]; - break; - case VERSION_SVD: - LOG_INFO("AYS using SVD noise levels"); - inputs = noise_levels[2]; - break; - default: - LOG_ERROR("Version not compatable with AYS scheduler"); - return results; + if (sd_version_is_sd2((SDVersion)version)) { + LOG_WARN("AYS not designed for SD2.X models"); + } /* fallthrough */ + else if (sd_version_is_sd1((SDVersion)version)) { + LOG_INFO("AYS using SD1.5 noise levels"); + inputs = noise_levels[0]; + } else if (sd_version_is_sdxl((SDVersion)version)) { + LOG_INFO("AYS using SDXL noise levels"); + inputs = noise_levels[1]; + } else if (version == VERSION_SVD) { + LOG_INFO("AYS using SVD noise levels"); + inputs = noise_levels[2]; + } else { + LOG_ERROR("Version not compatable with AYS scheduler"); + return results; } /* Stretches those pre-calculated reference levels out to the desired @@ -346,6 +343,31 @@ struct CompVisVDenoiser : public CompVisDenoiser { } }; +struct EDMVDenoiser : public CompVisVDenoiser { + float min_sigma = 0.002; + float max_sigma = 120.0; + + EDMVDenoiser(float min_sigma = 0.002, float max_sigma = 120.0) : min_sigma(min_sigma), max_sigma(max_sigma) { + schedule = std::make_shared(); + } + + float t_to_sigma(float t) { + return std::exp(t * 4/(float)TIMESTEPS); + } + + float sigma_to_t(float s) { + return 0.25 * std::log(s); + } + + float sigma_min() { + return min_sigma; + } + + float sigma_max() { + return max_sigma; + } +}; + float time_snr_shift(float alpha, float t) { if (alpha == 1.0f) { return t; diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 78be724f9..57c2d59bc 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -103,6 +103,9 @@ class StableDiffusionGGML { bool vae_tiling = false; bool stacked_id = false; + bool is_using_v_parameterization = false; + bool is_using_edm_v_parameterization = false; + std::map tensors; std::string lora_model_dir; @@ -543,12 +546,17 @@ class StableDiffusionGGML { LOG_INFO("loading model from '%s' completed, taking %.2fs", model_path.c_str(), (t1 - t0) * 1.0f / 1000); // check is_using_v_parameterization_for_sd2 - bool is_using_v_parameterization = false; + if (sd_version_is_sd2(version)) { if (is_using_v_parameterization_for_sd2(ctx, sd_version_is_inpaint(version))) { is_using_v_parameterization = true; } } else if (sd_version_is_sdxl(version)) { + if (model_loader.tensor_storages_types.find("edm_vpred.sigma_max") != model_loader.tensor_storages_types.end()) { + // CosXL models + // TODO: get sigma_min and sigma_max values from file + is_using_edm_v_parameterization = true; + } if (model_loader.tensor_storages_types.find("v_pred") != model_loader.tensor_storages_types.end()) { is_using_v_parameterization = true; } @@ -573,6 +581,9 @@ class StableDiffusionGGML { } else if (is_using_v_parameterization) { LOG_INFO("running in v-prediction mode"); denoiser = std::make_shared(); + } else if (is_using_edm_v_parameterization) { + LOG_INFO("running in v-prediction EDM mode"); + denoiser = std::make_shared(); } else { LOG_INFO("running in eps-prediction mode"); } @@ -1396,7 +1407,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, SDCondition uncond; if (cfg_scale != 1.0) { bool force_zero_embeddings = false; - if (sd_version_is_sdxl(sd_ctx->sd->version) && negative_prompt.size() == 0) { + if (sd_version_is_sdxl(sd_ctx->sd->version) && negative_prompt.size() == 0 && !sd_ctx->sd->is_using_edm_v_parameterization) { force_zero_embeddings = true; } uncond = sd_ctx->sd->cond_stage_model->get_learned_condition(work_ctx, From 7dac89ad751741038bef623a387e280c8e5c609d Mon Sep 17 00:00:00 2001 From: leejet Date: Tue, 1 Jul 2025 23:33:50 +0800 Subject: [PATCH 070/143] refector: reuse some code --- common.hpp | 2 +- denoiser.hpp | 5 ++-- ggml_extend.hpp | 2 +- stable-diffusion.cpp | 61 ++++++++++++++++++++------------------------ 4 files changed, 32 insertions(+), 38 deletions(-) diff --git a/common.hpp b/common.hpp index b20c60ff1..9b5cc53be 100644 --- a/common.hpp +++ b/common.hpp @@ -57,7 +57,7 @@ class UpSampleBlock : public GGMLBlock { auto conv = std::dynamic_pointer_cast(blocks["conv"]); x = ggml_upscale(ctx, x, 2, GGML_SCALE_MODE_NEAREST); // [N, channels, h*2, w*2] - x = conv->forward(ctx, x); // [N, out_channels, h*2, w*2] + x = conv->forward(ctx, x); // [N, out_channels, h*2, w*2] return x; } }; diff --git a/denoiser.hpp b/denoiser.hpp index ee4ae5172..2bd0b939a 100644 --- a/denoiser.hpp +++ b/denoiser.hpp @@ -347,12 +347,13 @@ struct EDMVDenoiser : public CompVisVDenoiser { float min_sigma = 0.002; float max_sigma = 120.0; - EDMVDenoiser(float min_sigma = 0.002, float max_sigma = 120.0) : min_sigma(min_sigma), max_sigma(max_sigma) { + EDMVDenoiser(float min_sigma = 0.002, float max_sigma = 120.0) + : min_sigma(min_sigma), max_sigma(max_sigma) { schedule = std::make_shared(); } float t_to_sigma(float t) { - return std::exp(t * 4/(float)TIMESTEPS); + return std::exp(t * 4 / (float)TIMESTEPS); } float sigma_to_t(float s) { diff --git a/ggml_extend.hpp b/ggml_extend.hpp index 635b78006..9f6a4fef6 100644 --- a/ggml_extend.hpp +++ b/ggml_extend.hpp @@ -118,7 +118,7 @@ __STATIC_INLINE__ struct ggml_tensor* ggml_kronecker(ggml_context* ctx, struct g a->ne[1] * b->ne[1], a->ne[2] * b->ne[2], a->ne[3] * b->ne[3], - GGML_SCALE_MODE_NEAREST), + GGML_SCALE_MODE_NEAREST), b); } diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 57c2d59bc..b5860cfd3 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -1566,6 +1566,29 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, return result_images; } +ggml_tensor* generate_init_latent(sd_ctx_t* sd_ctx, + ggml_context* work_ctx, + int width, + int height) { + int C = 4; + if (sd_version_is_sd3(sd_ctx->sd->version)) { + C = 16; + } else if (sd_version_is_flux(sd_ctx->sd->version)) { + C = 16; + } + int W = width / 8; + int H = height / 8; + ggml_tensor* init_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, 1); + if (sd_version_is_sd3(sd_ctx->sd->version)) { + ggml_set_f32(init_latent, 0.0609f); + } else if (sd_version_is_flux(sd_ctx->sd->version)) { + ggml_set_f32(init_latent, 0.1159f); + } else { + ggml_set_f32(init_latent, 0.f); + } + return init_latent; +} + sd_image_t* txt2img(sd_ctx_t* sd_ctx, const char* prompt_c_str, const char* negative_prompt_c_str, @@ -1622,27 +1645,12 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); - int C = 4; - if (sd_version_is_sd3(sd_ctx->sd->version)) { - C = 16; - } else if (sd_version_is_flux(sd_ctx->sd->version)) { - C = 16; - } - int W = width / 8; - int H = height / 8; - ggml_tensor* init_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, 1); - if (sd_version_is_sd3(sd_ctx->sd->version)) { - ggml_set_f32(init_latent, 0.0609f); - } else if (sd_version_is_flux(sd_ctx->sd->version)) { - ggml_set_f32(init_latent, 0.1159f); - } else { - ggml_set_f32(init_latent, 0.f); - } - if (sd_version_is_inpaint(sd_ctx->sd->version)) { LOG_WARN("This is an inpainting model, this should only be used in img2img mode with a mask"); } + ggml_tensor* init_latent = generate_init_latent(sd_ctx, work_ctx, width, height); + sd_image_t* result_images = generate_image(sd_ctx, work_ctx, init_latent, @@ -2046,23 +2054,6 @@ sd_image_t* edit(sd_ctx_t* sd_ctx, } sd_ctx->sd->rng->manual_seed(seed); - int C = 4; - if (sd_version_is_sd3(sd_ctx->sd->version)) { - C = 16; - } else if (sd_version_is_flux(sd_ctx->sd->version)) { - C = 16; - } - int W = width / 8; - int H = height / 8; - ggml_tensor* init_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, 1); - if (sd_version_is_sd3(sd_ctx->sd->version)) { - ggml_set_f32(init_latent, 0.0609f); - } else if (sd_version_is_flux(sd_ctx->sd->version)) { - ggml_set_f32(init_latent, 0.1159f); - } else { - ggml_set_f32(init_latent, 0.f); - } - size_t t0 = ggml_time_ms(); std::vector ref_latents; @@ -2085,6 +2076,8 @@ sd_image_t* edit(sd_ctx_t* sd_ctx, std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); + ggml_tensor* init_latent = generate_init_latent(sd_ctx, work_ctx, width, height); + sd_image_t* result_images = generate_image(sd_ctx, work_ctx, init_latent, From 83ef4e44ce3abd9e98f185f4cc88d4837e47925d Mon Sep 17 00:00:00 2001 From: stduhpf Date: Wed, 2 Jul 2025 17:13:00 +0200 Subject: [PATCH 071/143] feat: add T5 with llama.cpp naming convention support (#654) --- model.cpp | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/model.cpp b/model.cpp index 3e0bde77b..333f5d36a 100644 --- a/model.cpp +++ b/model.cpp @@ -181,6 +181,64 @@ std::unordered_map pmid_v2_name_map = { std::string convert_open_clip_to_hf_clip(const std::string& name) { std::string new_name = name; std::string prefix; + if (contains(new_name, ".enc.")) { + // llama.cpp naming convention for T5 + size_t pos = new_name.find(".enc."); + if (pos != std::string::npos) { + new_name.replace(pos, 5, ".encoder."); + } + pos = new_name.find("blk."); + if (pos != std::string::npos) { + new_name.replace(pos, 4, "block."); + } + pos = new_name.find("output_norm."); + if (pos != std::string::npos) { + new_name.replace(pos, 12, "final_layer_norm."); + } + pos = new_name.find("attn_k."); + if (pos != std::string::npos) { + new_name.replace(pos, 7, "layer.0.SelfAttention.k."); + } + pos = new_name.find("attn_v."); + if (pos != std::string::npos) { + new_name.replace(pos, 7, "layer.0.SelfAttention.v."); + } + pos = new_name.find("attn_o."); + if (pos != std::string::npos) { + new_name.replace(pos, 7, "layer.0.SelfAttention.o."); + } + pos = new_name.find("attn_q."); + if (pos != std::string::npos) { + new_name.replace(pos, 7, "layer.0.SelfAttention.q."); + } + pos = new_name.find("attn_norm."); + if (pos != std::string::npos) { + new_name.replace(pos, 10, "layer.0.layer_norm."); + } + pos = new_name.find("ffn_norm."); + if (pos != std::string::npos) { + new_name.replace(pos, 9, "layer.1.layer_norm."); + } + pos = new_name.find("ffn_up."); + if (pos != std::string::npos) { + new_name.replace(pos, 7, "layer.1.DenseReluDense.wi_1."); + } + pos = new_name.find("ffn_down."); + if (pos != std::string::npos) { + new_name.replace(pos, 9, "layer.1.DenseReluDense.wo."); + } + pos = new_name.find("ffn_gate."); + if (pos != std::string::npos) { + new_name.replace(pos, 9, "layer.1.DenseReluDense.wi_0."); + } + pos = new_name.find("attn_rel_b."); + if (pos != std::string::npos) { + new_name.replace(pos, 11, "layer.0.SelfAttention.relative_attention_bias."); + } + } else if (name == "text_encoders.t5xxl.transformer.token_embd.weight") { + new_name = "text_encoders.t5xxl.transformer.shared.weight"; + } + if (starts_with(new_name, "conditioner.embedders.0.open_clip.")) { prefix = "cond_stage_model."; new_name = new_name.substr(strlen("conditioner.embedders.0.open_clip.")); From 0927e8e3223a18065389d163a567e3339d6c06c5 Mon Sep 17 00:00:00 2001 From: rmatif <66360289+rmatif@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:18:16 +0200 Subject: [PATCH 072/143] docs: add Android app to README (#647) --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ac08a051..03d132170 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ Inference of Stable Diffusion and Flux in pure C/C++ - Linux - Mac OS - Windows - - Android (via Termux) + - Android (via Termux, [Local Diffusion](https://github.com/rmatif/Local-Diffusion)) ### TODO @@ -396,6 +396,7 @@ These projects wrap `stable-diffusion.cpp` for easier use in other languages/fra * C#: [DarthAffe/StableDiffusion.NET](https://github.com/DarthAffe/StableDiffusion.NET) * Python: [william-murray1204/stable-diffusion-cpp-python](https://github.com/william-murray1204/stable-diffusion-cpp-python) * Rust: [newfla/diffusion-rs](https://github.com/newfla/diffusion-rs) +* Flutter/Dart: [rmatif/Local-Diffusion](https://github.com/rmatif/Local-Diffusion) ## UIs @@ -404,6 +405,7 @@ These projects use `stable-diffusion.cpp` as a backend for their image generatio - [Jellybox](https://jellybox.com) - [Stable Diffusion GUI](https://github.com/fszontagh/sd.cpp.gui.wx) - [Stable Diffusion CLI-GUI](https://github.com/piallai/stable-diffusion.cpp) +- [Local Diffusion](https://github.com/rmatif/Local-Diffusion) ## Contributors From 7a8ff2e819a629ed8d1846b1c72b989494a353a9 Mon Sep 17 00:00:00 2001 From: Binozo <70137898+Binozo@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:19:49 +0200 Subject: [PATCH 073/143] docs: add golang cgo bindings to README (#635) --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 03d132170..4720dc29c 100644 --- a/README.md +++ b/README.md @@ -392,7 +392,8 @@ Using formats of different precisions will yield results of varying quality. These projects wrap `stable-diffusion.cpp` for easier use in other languages/frameworks. -* Golang: [seasonjs/stable-diffusion](https://github.com/seasonjs/stable-diffusion) +* Golang (non-cgo): [seasonjs/stable-diffusion](https://github.com/seasonjs/stable-diffusion) +* Golang (cgo): [Binozo/GoStableDiffusion](https://github.com/Binozo/GoStableDiffusion) * C#: [DarthAffe/StableDiffusion.NET](https://github.com/DarthAffe/StableDiffusion.NET) * Python: [william-murray1204/stable-diffusion-cpp-python](https://github.com/william-murray1204/stable-diffusion-cpp-python) * Rust: [newfla/diffusion-rs](https://github.com/newfla/diffusion-rs) From 8d0819c548421ca867b7104c53fee25d7b9bff23 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Thu, 3 Jul 2025 16:39:57 +0200 Subject: [PATCH 074/143] fix: actually use embeddings with SDXL (#657) --- conditioner.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conditioner.hpp b/conditioner.hpp index 4124a68ac..3f89d5263 100644 --- a/conditioner.hpp +++ b/conditioner.hpp @@ -459,8 +459,8 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { if (sd_version_is_sdxl(version)) { text_model2->compute(n_threads, input_ids2, - 0, - NULL, + num_custom_embeddings, + token_embed_custom.data(), max_token_idx, false, &chunk_hidden_states2, work_ctx); @@ -470,8 +470,8 @@ struct FrozenCLIPEmbedderWithCustomWords : public Conditioner { if (chunk_idx == 0) { text_model2->compute(n_threads, input_ids2, - 0, - NULL, + num_custom_embeddings, + token_embed_custom.data(), max_token_idx, true, &pooled, From 3bae667f3d7b179437b8adb4e3c1120f576be930 Mon Sep 17 00:00:00 2001 From: vmobilis <75476228+vmobilis@users.noreply.github.com> Date: Thu, 3 Jul 2025 17:50:42 +0300 Subject: [PATCH 075/143] fix: break the line after skipping tensors in VAE (#591) --- model.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/model.cpp b/model.cpp index 333f5d36a..7a572532b 100644 --- a/model.cpp +++ b/model.cpp @@ -1841,6 +1841,7 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend }; int tensor_count = 0; int64_t t1 = ggml_time_ms(); + bool partial = false; for (auto& tensor_storage : processed_tensor_storages) { if (tensor_storage.file_index != file_index) { ++tensor_count; @@ -1922,15 +1923,21 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend ggml_backend_tensor_set(dst_tensor, convert_buffer.data(), 0, ggml_nbytes(dst_tensor)); } } + size_t tensor_max = processed_tensor_storages.size(); int64_t t2 = ggml_time_ms(); - pretty_progress(++tensor_count, processed_tensor_storages.size(), (t2 - t1) / 1000.0f); + pretty_progress(++tensor_count, tensor_max, (t2 - t1) / 1000.0f); t1 = t2; + partial = tensor_count != tensor_max; } if (zip != NULL) { zip_close(zip); } + if (partial) { + printf("\n"); + } + if (!success) { break; } From 76c72628b1326ea8a38a4c8e173047b3f04e7098 Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Fri, 4 Jul 2025 11:15:41 -0300 Subject: [PATCH 076/143] fix: fix a few typos on cli help and error messages (#714) --- denoiser.hpp | 2 +- examples/cli/main.cpp | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/denoiser.hpp b/denoiser.hpp index 2bd0b939a..d4bcec590 100644 --- a/denoiser.hpp +++ b/denoiser.hpp @@ -181,7 +181,7 @@ struct AYSSchedule : SigmaSchedule { LOG_INFO("AYS using SVD noise levels"); inputs = noise_levels[2]; } else { - LOG_ERROR("Version not compatable with AYS scheduler"); + LOG_ERROR("Version not compatible with AYS scheduler"); return results; } diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index d06040445..730a4941e 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -60,6 +60,7 @@ const char* modes_str[] = { "edit", "convert", }; +#define SD_ALL_MODES_STR "txt2img, img2img, edit, convert" enum SDMode { TXT2IMG, @@ -199,14 +200,18 @@ void print_usage(int argc, const char* argv[]) { printf("\n"); printf("arguments:\n"); printf(" -h, --help show this help message and exit\n"); - printf(" -M, --mode [MODEL] run mode (txt2img or img2img or convert, default: txt2img)\n"); + printf(" -M, --mode [MODE] run mode, one of:\n"); + printf(" txt2img: generate an image from a text prompt (default)\n"); + printf(" img2img: generate an image from a text prompt and an initial image (--init-img)\n"); + printf(" edit: modify an image (--ref-image) based on text instructions\n"); + printf(" convert: convert a model file to gguf format, optionally with quantization\n"); printf(" -t, --threads N number of threads to use during computation (default: -1)\n"); printf(" If threads <= 0, then threads will be set to the number of CPU physical cores\n"); printf(" -m, --model [MODEL] path to full model\n"); printf(" --diffusion-model path to the standalone diffusion model\n"); printf(" --clip_l path to the clip-l text encoder\n"); printf(" --clip_g path to the clip-g text encoder\n"); - printf(" --t5xxl path to the the t5xxl text encoder\n"); + printf(" --t5xxl path to the t5xxl text encoder\n"); printf(" --vae [VAE] path to vae\n"); printf(" --taesd [TAESD_PATH] path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)\n"); printf(" --control-net [CONTROL_PATH] path to control net model\n"); @@ -222,7 +227,7 @@ void print_usage(int argc, const char* argv[]) { printf(" -i, --init-img [IMAGE] path to the input image, required by img2img\n"); printf(" --mask [MASK] path to the mask image, required by img2img with mask\n"); printf(" --control-image [IMAGE] path to image condition, control net\n"); - printf(" -r, --ref_image [PATH] reference image for Flux Kontext models (can be used multiple times) \n"); + printf(" -r, --ref-image [PATH] reference image for Flux Kontext models (can be used multiple times) \n"); printf(" -o, --output OUTPUT path to write result image to (default: ./output.png)\n"); printf(" -p, --prompt [PROMPT] the prompt to render\n"); printf(" -n, --negative-prompt PROMPT the negative prompt (default: \"\")\n"); @@ -291,8 +296,8 @@ void parse_args(int argc, const char** argv, SDParams& params) { } if (mode_found == -1) { fprintf(stderr, - "error: invalid mode %s, must be one of [txt2img, img2img, img2vid, convert]\n", - mode_selected); + "error: invalid mode %s, must be one of [%s]\n", + mode_selected, SD_ALL_MODES_STR); exit(1); } params.mode = (SDMode)mode_found; @@ -1218,4 +1223,4 @@ int main(int argc, const char* argv[]) { free(input_image_buffer); return 0; -} \ No newline at end of file +} From 19fbfd8639d1439c9145543c1c993409006caf74 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Fri, 4 Jul 2025 16:19:47 +0200 Subject: [PATCH 077/143] feat: override text encoders for unet models (#682) --- model.cpp | 9 +++++++++ model.h | 1 + stable-diffusion.cpp | 20 +++++++++++--------- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/model.cpp b/model.cpp index 7a572532b..eeee6d321 100644 --- a/model.cpp +++ b/model.cpp @@ -1539,6 +1539,15 @@ bool ModelLoader::init_from_ckpt_file(const std::string& file_path, const std::s return true; } +bool ModelLoader::model_is_unet() { + for (auto& tensor_storage : tensor_storages) { + if (tensor_storage.name.find("model.diffusion_model.input_blocks.") != std::string::npos) { + return true; + } + } + return false; +} + SDVersion ModelLoader::get_sd_version() { TensorStorage token_embedding_weight, input_block_weight; bool input_block_checked = false; diff --git a/model.h b/model.h index 79c25337c..82885dd96 100644 --- a/model.h +++ b/model.h @@ -210,6 +210,7 @@ class ModelLoader { std::map tensor_storages_types; bool init_from_file(const std::string& file_path, const std::string& prefix = ""); + bool model_is_unet(); SDVersion get_sd_version(); ggml_type get_sd_wtype(); ggml_type get_conditioner_wtype(); diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index b5860cfd3..9c8265727 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -213,16 +213,25 @@ class StableDiffusionGGML { } } + if (diffusion_model_path.size() > 0) { + LOG_INFO("loading diffusion model from '%s'", diffusion_model_path.c_str()); + if (!model_loader.init_from_file(diffusion_model_path, "model.diffusion_model.")) { + LOG_WARN("loading diffusion model from '%s' failed", diffusion_model_path.c_str()); + } + } + + bool is_unet = model_loader.model_is_unet(); + if (clip_l_path.size() > 0) { LOG_INFO("loading clip_l from '%s'", clip_l_path.c_str()); - if (!model_loader.init_from_file(clip_l_path, "text_encoders.clip_l.transformer.")) { + if (!model_loader.init_from_file(clip_l_path, is_unet ? "cond_stage_model.transformer." : "text_encoders.clip_l.transformer.")) { LOG_WARN("loading clip_l from '%s' failed", clip_l_path.c_str()); } } if (clip_g_path.size() > 0) { LOG_INFO("loading clip_g from '%s'", clip_g_path.c_str()); - if (!model_loader.init_from_file(clip_g_path, "text_encoders.clip_g.transformer.")) { + if (!model_loader.init_from_file(clip_g_path, is_unet ? "cond_stage_model.1.transformer." : "text_encoders.clip_g.transformer.")) { LOG_WARN("loading clip_g from '%s' failed", clip_g_path.c_str()); } } @@ -234,13 +243,6 @@ class StableDiffusionGGML { } } - if (diffusion_model_path.size() > 0) { - LOG_INFO("loading diffusion model from '%s'", diffusion_model_path.c_str()); - if (!model_loader.init_from_file(diffusion_model_path, "model.diffusion_model.")) { - LOG_WARN("loading diffusion model from '%s' failed", diffusion_model_path.c_str()); - } - } - if (vae_path.size() > 0) { LOG_INFO("loading vae from '%s'", vae_path.c_str()); if (!model_loader.init_from_file(vae_path, "vae.")) { From 1ce1c1adcadb30d112e812cd5ce4cf210f968bc0 Mon Sep 17 00:00:00 2001 From: leejet Date: Sat, 5 Jul 2025 22:44:22 +0800 Subject: [PATCH 078/143] feat: make lora graph size variable --- lora.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lora.hpp b/lora.hpp index 471d0ef8e..35f5aacd1 100644 --- a/lora.hpp +++ b/lora.hpp @@ -3,7 +3,7 @@ #include "ggml_extend.hpp" -#define LORA_GRAPH_SIZE 15360 +#define LORA_GRAPH_BASE_SIZE 10240 struct LoraModel : public GGMLRunner { enum lora_t { @@ -238,7 +238,8 @@ struct LoraModel : public GGMLRunner { } struct ggml_cgraph* build_lora_graph(std::map model_tensors, SDVersion version) { - struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, LORA_GRAPH_SIZE, false); + size_t lora_graph_size = LORA_GRAPH_BASE_SIZE + lora_tensors.size() * 10; + struct ggml_cgraph* gf = ggml_new_graph_custom(compute_ctx, lora_graph_size, false); zero_index = ggml_new_tensor_1d(compute_ctx, GGML_TYPE_I32, 1); set_backend_tensor_data(zero_index, zero_index_vec.data()); From b9e4718facf1794f47e4259dd536b2f5f3c39cd2 Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 6 Jul 2025 11:11:47 +0800 Subject: [PATCH 079/143] fix: correct --chroma-enable-t5-mask argument --- examples/cli/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 730a4941e..c7db3708b 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -660,7 +660,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { params.ref_image_paths.push_back(argv[i]); } else if (arg == "--chroma-disable-dit-mask") { params.chroma_use_dit_mask = false; - } else if (arg == "--chroma-use-t5-mask") { + } else if (arg == "--chroma-enable-t5-mask") { params.chroma_use_t5_mask = true; } else if (arg == "--chroma-t5-mask-pad") { if (++i >= argc) { From 225162f270e07e8adce44c09a4a3af7b4b970cf7 Mon Sep 17 00:00:00 2001 From: idostyle Date: Sun, 6 Jul 2025 17:10:10 +0200 Subject: [PATCH 080/143] fix: mark encoder.embed_tokens.weight as unused tensor (#721) --- model.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/model.cpp b/model.cpp index eeee6d321..89bd07612 100644 --- a/model.cpp +++ b/model.cpp @@ -100,6 +100,7 @@ const char* unused_tensors[] = { "model_ema.diffusion_model", "embedding_manager", "denoiser.sigmas", + "text_encoders.t5xxl.transformer.encoder.embed_tokens.weight", // only used during training }; bool is_unused_tensor(std::string name) { From dafc32d0dd0922272f970c93f961834af460f013 Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sun, 6 Jul 2025 17:24:55 +0200 Subject: [PATCH 081/143] feat: add support for f64/i64 and clip_g diffusers model (#681) --- model.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/model.cpp b/model.cpp index 89bd07612..85c959057 100644 --- a/model.cpp +++ b/model.cpp @@ -338,6 +338,10 @@ std::unordered_map> su {"to_v", "v"}, {"to_out_0", "proj_out"}, {"group_norm", "norm"}, + {"key", "k"}, + {"query", "q"}, + {"value", "v"}, + {"proj_attn", "proj_out"}, }, }, { @@ -362,6 +366,10 @@ std::unordered_map> su {"to_v", "v"}, {"to_out.0", "proj_out"}, {"group_norm", "norm"}, + {"key", "k"}, + {"query", "q"}, + {"value", "v"}, + {"proj_attn", "proj_out"}, }, }, { @@ -433,6 +441,10 @@ std::string convert_diffusers_name_to_compvis(std::string key, char seq) { return format("model%cdiffusion_model%ctime_embed%c", seq, seq, seq) + std::to_string(std::stoi(m[0]) * 2 - 2) + m[1]; } + if (match(m, std::regex(format("unet%cadd_embedding%clinear_(\\d+)(.*)", seq, seq)), key)) { + return format("model%cdiffusion_model%clabel_emb%c0%c", seq, seq, seq, seq) + std::to_string(std::stoi(m[0]) * 2 - 2) + m[1]; + } + if (match(m, std::regex(format("unet%cdown_blocks%c(\\d+)%c(attentions|resnets)%c(\\d+)%c(.+)", seq, seq, seq, seq, seq)), key)) { std::string suffix = get_converted_suffix(m[1], m[3]); // LOG_DEBUG("%s %s %s %s", m[0].c_str(), m[1].c_str(), m[2].c_str(), m[3].c_str()); @@ -470,6 +482,19 @@ std::string convert_diffusers_name_to_compvis(std::string key, char seq) { return format("cond_stage_model%ctransformer%ctext_model", seq, seq) + m[0]; } + // clip-g + if (match(m, std::regex(format("te%c1%ctext_model%cencoder%clayers%c(\\d+)%c(.+)", seq, seq, seq, seq, seq, seq)), key)) { + return format("cond_stage_model%c1%ctransformer%ctext_model%cencoder%clayers%c", seq, seq, seq, seq, seq, seq) + m[0] + seq + m[1]; + } + + if (match(m, std::regex(format("te%c1%ctext_model(.*)", seq, seq)), key)) { + return format("cond_stage_model%c1%ctransformer%ctext_model", seq, seq, seq) + m[0]; + } + + if (match(m, std::regex(format("te%c1%ctext_projection", seq, seq)), key)) { + return format("cond_stage_model%c1%ctransformer%ctext_model%ctext_projection", seq, seq, seq, seq); + } + // vae if (match(m, std::regex(format("vae%c(.*)%cconv_norm_out(.*)", seq, seq)), key)) { return format("first_stage_model%c%s%cnorm_out%s", seq, m[0].c_str(), seq, m[1].c_str()); @@ -606,6 +631,8 @@ std::string convert_tensor_name(std::string name) { std::string new_key = convert_diffusers_name_to_compvis(name_without_network_parts, '.'); if (new_key.empty()) { new_name = name; + } else if (new_key == "cond_stage_model.1.transformer.text_model.text_projection") { + new_name = new_key; } else { new_name = new_key + "." + network_part; } @@ -1029,10 +1056,14 @@ ggml_type str_to_ggml_type(const std::string& dtype) { ttype = GGML_TYPE_F32; } else if (dtype == "F32") { ttype = GGML_TYPE_F32; + } else if (dtype == "F64") { + ttype = GGML_TYPE_F64; } else if (dtype == "F8_E4M3") { ttype = GGML_TYPE_F16; } else if (dtype == "F8_E5M2") { ttype = GGML_TYPE_F16; + } else if (dtype == "I64") { + ttype = GGML_TYPE_I64; } return ttype; } @@ -1045,6 +1076,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const std::ifstream file(file_path, std::ios::binary); if (!file.is_open()) { LOG_ERROR("failed to open '%s'", file_path.c_str()); + file_paths_.pop_back(); return false; } @@ -1056,6 +1088,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const // read header size if (file_size_ <= ST_HEADER_SIZE_LEN) { LOG_ERROR("invalid safetensor file '%s'", file_path.c_str()); + file_paths_.pop_back(); return false; } @@ -1069,6 +1102,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const size_t header_size_ = read_u64(header_size_buf); if (header_size_ >= file_size_) { LOG_ERROR("invalid safetensor file '%s'", file_path.c_str()); + file_paths_.pop_back(); return false; } @@ -1079,6 +1113,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const file.read(header_buf.data(), header_size_); if (!file) { LOG_ERROR("read safetensors header failed: '%s'", file_path.c_str()); + file_paths_.pop_back(); return false; } @@ -1134,6 +1169,7 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const n_dims = 1; } + TensorStorage tensor_storage(prefix + name, type, ne, n_dims, file_index, ST_HEADER_SIZE_LEN + header_size_ + begin); tensor_storage.reverse_ne(); @@ -1166,18 +1202,45 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const /*================================================= DiffusersModelLoader ==================================================*/ bool ModelLoader::init_from_diffusers_file(const std::string& file_path, const std::string& prefix) { - std::string unet_path = path_join(file_path, "unet/diffusion_pytorch_model.safetensors"); - std::string vae_path = path_join(file_path, "vae/diffusion_pytorch_model.safetensors"); - std::string clip_path = path_join(file_path, "text_encoder/model.safetensors"); + std::string unet_path = path_join(file_path, "unet/diffusion_pytorch_model.safetensors"); + std::string vae_path = path_join(file_path, "vae/diffusion_pytorch_model.safetensors"); + std::string clip_path = path_join(file_path, "text_encoder/model.safetensors"); + std::string clip_g_path = path_join(file_path, "text_encoder_2/model.safetensors"); if (!init_from_safetensors_file(unet_path, "unet.")) { return false; } + for (auto ts : tensor_storages) { + if (ts.name.find("add_embedding") != std::string::npos || ts.name.find("label_emb") != std::string::npos) { + // probably SDXL + LOG_DEBUG("Fixing name for SDXL output blocks.2.2"); + for (auto& tensor_storage : tensor_storages) { + int len = 34; + auto pos = tensor_storage.name.find("unet.up_blocks.0.upsamplers.0.conv"); + if (pos == std::string::npos) { + len = 44; + pos = tensor_storage.name.find("model.diffusion_model.output_blocks.2.1.conv"); + } + if (pos != std::string::npos) { + tensor_storage.name = "model.diffusion_model.output_blocks.2.2.conv" + tensor_storage.name.substr(len); + LOG_DEBUG("NEW NAME: %s", tensor_storage.name.c_str()); + add_preprocess_tensor_storage_types(tensor_storages_types, tensor_storage.name, tensor_storage.type); + } + } + break; + } + } + if (!init_from_safetensors_file(vae_path, "vae.")) { - return false; + LOG_WARN("Couldn't find working VAE in %s", file_path.c_str()); + // return false; } if (!init_from_safetensors_file(clip_path, "te.")) { - return false; + LOG_WARN("Couldn't find working text encoder in %s", file_path.c_str()); + // return false; + } + if (!init_from_safetensors_file(clip_g_path, "te.1.")) { + LOG_DEBUG("Couldn't find working second text encoder in %s", file_path.c_str()); } return true; } @@ -1571,7 +1634,7 @@ SDVersion ModelLoader::get_sd_version() { if (tensor_storage.name.find("model.diffusion_model.joint_blocks.") != std::string::npos) { return VERSION_SD3; } - if (tensor_storage.name.find("model.diffusion_model.input_blocks.") != std::string::npos) { + if (tensor_storage.name.find("model.diffusion_model.input_blocks.") != std::string::npos || tensor_storage.name.find("unet.down_blocks.") != std::string::npos) { is_unet = true; if (has_multiple_encoders) { is_xl = true; @@ -1580,7 +1643,7 @@ SDVersion ModelLoader::get_sd_version() { } } } - if (tensor_storage.name.find("conditioner.embedders.1") != std::string::npos || tensor_storage.name.find("cond_stage_model.1") != std::string::npos) { + if (tensor_storage.name.find("conditioner.embedders.1") != std::string::npos || tensor_storage.name.find("cond_stage_model.1") != std::string::npos || tensor_storage.name.find("te.1") != std::string::npos) { has_multiple_encoders = true; if (is_unet) { is_xl = true; @@ -1602,7 +1665,7 @@ SDVersion ModelLoader::get_sd_version() { token_embedding_weight = tensor_storage; // break; } - if (tensor_storage.name == "model.diffusion_model.input_blocks.0.0.weight" || tensor_storage.name == "model.diffusion_model.img_in.weight") { + if (tensor_storage.name == "model.diffusion_model.input_blocks.0.0.weight" || tensor_storage.name == "model.diffusion_model.img_in.weight" || tensor_storage.name == "unet.conv_in.weight") { input_block_weight = tensor_storage; input_block_checked = true; if (found_family) { @@ -1687,7 +1750,7 @@ ggml_type ModelLoader::get_diffusion_model_wtype() { continue; } - if (tensor_storage.name.find("model.diffusion_model.") == std::string::npos) { + if (tensor_storage.name.find("model.diffusion_model.") == std::string::npos && tensor_storage.name.find("unet.") == std::string::npos) { continue; } From 6d84a30c66cc619fe41a78bc87f83ba41f059cc0 Mon Sep 17 00:00:00 2001 From: Wagner Bruna Date: Mon, 7 Jul 2025 13:11:38 -0300 Subject: [PATCH 082/143] feat: overriding quant types for specific tensors on model conversion (#724) --- examples/cli/main.cpp | 14 ++++++++- model.cpp | 67 ++++++++++++++++++++++++++++++++++++------- model.h | 2 +- stable-diffusion.h | 2 +- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index c7db3708b..bb695c3bb 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -87,6 +87,7 @@ struct SDParams { std::string stacked_id_embeddings_path; std::string input_id_images_path; sd_type_t wtype = SD_TYPE_COUNT; + std::string tensor_type_rules; std::string lora_model_dir; std::string output_path = "output.png"; std::string input_path; @@ -223,6 +224,7 @@ void print_usage(int argc, const char* argv[]) { printf(" --upscale-repeats Run the ESRGAN upscaler this many times (default 1)\n"); printf(" --type [TYPE] weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K)\n"); printf(" If not specified, the default is the type of the weight file\n"); + printf(" --tensor-type-rules [EXPRESSION] weight type per tensor pattern (example: \"^vae\\.=f16,model\\.=q8_0\")\n"); printf(" --lora-model-dir [DIR] lora model directory\n"); printf(" -i, --init-img [IMAGE] path to the input image, required by img2img\n"); printf(" --mask [MASK] path to the mask image, required by img2img with mask\n"); @@ -404,6 +406,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { valid_types.c_str()); exit(1); } + } else if (arg == "--tensor-type-rules") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.tensor_type_rules = argv[i]; } else if (arg == "--lora-model-dir") { if (++i >= argc) { invalid_arg = true; @@ -733,6 +741,10 @@ void parse_args(int argc, const char** argv, SDParams& params) { exit(1); } + if (params.mode != CONVERT && params.tensor_type_rules.size() > 0) { + fprintf(stderr, "warning: --tensor-type-rules is currently supported only for conversion\n"); + } + if (params.seed < 0) { srand((int)time(NULL)); params.seed = rand(); @@ -845,7 +857,7 @@ int main(int argc, const char* argv[]) { } if (params.mode == CONVERT) { - bool success = convert(params.model_path.c_str(), params.vae_path.c_str(), params.output_path.c_str(), params.wtype); + bool success = convert(params.model_path.c_str(), params.vae_path.c_str(), params.output_path.c_str(), params.wtype, params.tensor_type_rules.c_str()); if (!success) { fprintf(stderr, "convert '%s'/'%s' to '%s' failed\n", diff --git a/model.cpp b/model.cpp index 85c959057..559c876c6 100644 --- a/model.cpp +++ b/model.cpp @@ -100,7 +100,7 @@ const char* unused_tensors[] = { "model_ema.diffusion_model", "embedding_manager", "denoiser.sigmas", - "text_encoders.t5xxl.transformer.encoder.embed_tokens.weight", // only used during training + "text_encoders.t5xxl.transformer.encoder.embed_tokens.weight", // only used during training }; bool is_unused_tensor(std::string name) { @@ -1169,7 +1169,6 @@ bool ModelLoader::init_from_safetensors_file(const std::string& file_path, const n_dims = 1; } - TensorStorage tensor_storage(prefix + name, type, ne, n_dims, file_index, ST_HEADER_SIZE_LEN + header_size_ + begin); tensor_storage.reverse_ne(); @@ -1914,7 +1913,7 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend }; int tensor_count = 0; int64_t t1 = ggml_time_ms(); - bool partial = false; + bool partial = false; for (auto& tensor_storage : processed_tensor_storages) { if (tensor_storage.file_index != file_index) { ++tensor_count; @@ -1997,9 +1996,9 @@ bool ModelLoader::load_tensors(on_new_tensor_cb_t on_new_tensor_cb, ggml_backend } } size_t tensor_max = processed_tensor_storages.size(); - int64_t t2 = ggml_time_ms(); + int64_t t2 = ggml_time_ms(); pretty_progress(++tensor_count, tensor_max, (t2 - t1) / 1000.0f); - t1 = t2; + t1 = t2; partial = tensor_count != tensor_max; } @@ -2088,6 +2087,41 @@ bool ModelLoader::load_tensors(std::map& tenso return true; } +std::vector> parse_tensor_type_rules(const std::string& tensor_type_rules) { + std::vector> result; + for (const auto& item : splitString(tensor_type_rules, ',')) { + if (item.size() == 0) + continue; + std::string::size_type pos = item.find('='); + if (pos == std::string::npos) { + LOG_WARN("ignoring invalid quant override \"%s\"", item.c_str()); + continue; + } + std::string tensor_pattern = item.substr(0, pos); + std::string type_name = item.substr(pos + 1); + + ggml_type tensor_type = GGML_TYPE_COUNT; + + if (type_name == "f32") { + tensor_type = GGML_TYPE_F32; + } else { + for (size_t i = 0; i < SD_TYPE_COUNT; i++) { + auto trait = ggml_get_type_traits((ggml_type)i); + if (trait->to_float && trait->type_size && type_name == trait->type_name) { + tensor_type = (ggml_type)i; + } + } + } + + if (tensor_type != GGML_TYPE_COUNT) { + result.emplace_back(tensor_pattern, tensor_type); + } else { + LOG_WARN("ignoring invalid quant override \"%s\"", item.c_str()); + } + } + return result; +} + bool ModelLoader::tensor_should_be_converted(const TensorStorage& tensor_storage, ggml_type type) { const std::string& name = tensor_storage.name; if (type != GGML_TYPE_COUNT) { @@ -2119,7 +2153,7 @@ bool ModelLoader::tensor_should_be_converted(const TensorStorage& tensor_storage return false; } -bool ModelLoader::save_to_gguf_file(const std::string& file_path, ggml_type type) { +bool ModelLoader::save_to_gguf_file(const std::string& file_path, ggml_type type, const std::string& tensor_type_rules_str) { auto backend = ggml_backend_cpu_init(); size_t mem_size = 1 * 1024 * 1024; // for padding mem_size += tensor_storages.size() * ggml_tensor_overhead(); @@ -2129,12 +2163,23 @@ bool ModelLoader::save_to_gguf_file(const std::string& file_path, ggml_type type gguf_context* gguf_ctx = gguf_init_empty(); + auto tensor_type_rules = parse_tensor_type_rules(tensor_type_rules_str); + auto on_new_tensor_cb = [&](const TensorStorage& tensor_storage, ggml_tensor** dst_tensor) -> bool { const std::string& name = tensor_storage.name; + ggml_type tensor_type = tensor_storage.type; + ggml_type dst_type = type; - ggml_type tensor_type = tensor_storage.type; - if (tensor_should_be_converted(tensor_storage, type)) { - tensor_type = type; + for (const auto& tensor_type_rule : tensor_type_rules) { + std::regex pattern(tensor_type_rule.first); + if (std::regex_search(name, pattern)) { + dst_type = tensor_type_rule.second; + break; + } + } + + if (tensor_should_be_converted(tensor_storage, dst_type)) { + tensor_type = dst_type; } ggml_tensor* tensor = ggml_new_tensor(ggml_ctx, tensor_type, tensor_storage.n_dims, tensor_storage.ne); @@ -2193,7 +2238,7 @@ int64_t ModelLoader::get_params_mem_size(ggml_backend_t backend, ggml_type type) return mem_size; } -bool convert(const char* input_path, const char* vae_path, const char* output_path, sd_type_t output_type) { +bool convert(const char* input_path, const char* vae_path, const char* output_path, sd_type_t output_type, const char* tensor_type_rules) { ModelLoader model_loader; if (!model_loader.init_from_file(input_path)) { @@ -2207,6 +2252,6 @@ bool convert(const char* input_path, const char* vae_path, const char* output_pa return false; } } - bool success = model_loader.save_to_gguf_file(output_path, (ggml_type)output_type); + bool success = model_loader.save_to_gguf_file(output_path, (ggml_type)output_type, tensor_type_rules); return success; } diff --git a/model.h b/model.h index 82885dd96..95c66319d 100644 --- a/model.h +++ b/model.h @@ -222,7 +222,7 @@ class ModelLoader { ggml_backend_t backend, std::set ignore_tensors = {}); - bool save_to_gguf_file(const std::string& file_path, ggml_type type); + bool save_to_gguf_file(const std::string& file_path, ggml_type type, const std::string& tensor_type_rules); bool tensor_should_be_converted(const TensorStorage& tensor_storage, ggml_type type); int64_t get_params_mem_size(ggml_backend_t backend, ggml_type type = GGML_TYPE_COUNT); ~ModelLoader() = default; diff --git a/stable-diffusion.h b/stable-diffusion.h index b4d6fc327..212e1c918 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -257,7 +257,7 @@ SD_API void free_upscaler_ctx(upscaler_ctx_t* upscaler_ctx); SD_API sd_image_t upscale(upscaler_ctx_t* upscaler_ctx, sd_image_t input_image, uint32_t upscale_factor); -SD_API bool convert(const char* input_path, const char* vae_path, const char* output_path, enum sd_type_t output_type); +SD_API bool convert(const char* input_path, const char* vae_path, const char* output_path, enum sd_type_t output_type, const char* tensor_type_rules); SD_API uint8_t* preprocess_canny(uint8_t* img, int width, From a772dca27aa4bba498efa8aa8320e64ca6c1b92c Mon Sep 17 00:00:00 2001 From: stduhpf Date: Sat, 12 Jul 2025 09:36:45 +0200 Subject: [PATCH 083/143] feat: add Instruct-Pix2pix/CosXL-Edit support (#679) * Instruct-p2p support * support 2 conditionings cfg * Do not re-encode the exact same image twice * fixes for 2-cfg * Fix pix2pix latent inputs + improve inpainting a bit + fix naming * prepare for other pix2pix-like models * Support sdxl ip2p * fix reference image embeddings * Support 2-cond cfg properly in cli * fix typo in help * Support masks for ip2p models * unify code style * delete unused code * use edit mode * add img_cond * format code --------- Co-authored-by: leejet --- examples/cli/main.cpp | 78 ++++++------ model.cpp | 7 + model.h | 14 +- stable-diffusion.cpp | 289 +++++++++++++++++++++++------------------- stable-diffusion.h | 49 ++++--- unet.hpp | 2 + 6 files changed, 242 insertions(+), 197 deletions(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index bb695c3bb..234dad3a6 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -97,15 +97,16 @@ struct SDParams { std::string prompt; std::string negative_prompt; - float min_cfg = 1.0f; - float cfg_scale = 7.0f; - float guidance = 3.5f; - float eta = 0.f; - float style_ratio = 20.f; - int clip_skip = -1; // <= 0 represents unspecified - int width = 512; - int height = 512; - int batch_count = 1; + float min_cfg = 1.0f; + float cfg_scale = 7.0f; + float img_cfg_scale = INFINITY; + float guidance = 3.5f; + float eta = 0.f; + float style_ratio = 20.f; + int clip_skip = -1; // <= 0 represents unspecified + int width = 512; + int height = 512; + int batch_count = 1; int video_frames = 6; int motion_bucket_id = 127; @@ -176,6 +177,7 @@ void print_params(SDParams params) { printf(" negative_prompt: %s\n", params.negative_prompt.c_str()); printf(" min_cfg: %.2f\n", params.min_cfg); printf(" cfg_scale: %.2f\n", params.cfg_scale); + printf(" img_cfg_scale: %.2f\n", params.img_cfg_scale); printf(" slg_scale: %.2f\n", params.slg_scale); printf(" guidance: %.2f\n", params.guidance); printf(" eta: %.2f\n", params.eta); @@ -234,7 +236,8 @@ void print_usage(int argc, const char* argv[]) { printf(" -p, --prompt [PROMPT] the prompt to render\n"); printf(" -n, --negative-prompt PROMPT the negative prompt (default: \"\")\n"); printf(" --cfg-scale SCALE unconditional guidance scale: (default: 7.0)\n"); - printf(" --guidance SCALE guidance scale for img2img (default: 3.5)\n"); + printf(" --img-cfg-scale SCALE image guidance scale for inpaint or instruct-pix2pix models: (default: same as --cfg-scale)\n"); + printf(" --guidance SCALE distilled guidance scale for models with guidance input (default: 3.5)\n"); printf(" --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0)\n"); printf(" 0 means disabled, a value of 2.5 is nice for sd3.5 medium\n"); printf(" --eta SCALE eta in DDIM, only for DDIM and TCD: (default: 0)\n"); @@ -470,6 +473,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.cfg_scale = std::stof(argv[i]); + } else if (arg == "--img-cfg-scale") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.img_cfg_scale = std::stof(argv[i]); } else if (arg == "--guidance") { if (++i >= argc) { invalid_arg = true; @@ -755,6 +764,10 @@ void parse_args(int argc, const char** argv, SDParams& params) { params.output_path = "output.gguf"; } } + + if (!isfinite(params.img_cfg_scale)) { + params.img_cfg_scale = params.cfg_scale; + } } static std::string sd_basename(const std::string& path) { @@ -849,6 +862,18 @@ int main(int argc, const char* argv[]) { parse_args(argc, argv, params); + sd_guidance_params_t guidance_params = {params.cfg_scale, + params.img_cfg_scale, + params.min_cfg, + params.guidance, + { + params.skip_layers.data(), + params.skip_layers.size(), + params.skip_layer_start, + params.skip_layer_end, + params.slg_scale, + }}; + sd_set_log_callback(sd_log_cb, (void*)¶ms); if (params.verbose) { @@ -1041,8 +1066,7 @@ int main(int argc, const char* argv[]) { params.prompt.c_str(), params.negative_prompt.c_str(), params.clip_skip, - params.cfg_scale, - params.guidance, + guidance_params, params.eta, params.width, params.height, @@ -1054,12 +1078,7 @@ int main(int argc, const char* argv[]) { params.control_strength, params.style_ratio, params.normalize_input, - params.input_id_images_path.c_str(), - params.skip_layers.data(), - params.skip_layers.size(), - params.slg_scale, - params.skip_layer_start, - params.skip_layer_end); + params.input_id_images_path.c_str()); } else if (params.mode == IMG2IMG || params.mode == IMG2VID) { sd_image_t input_image = {(uint32_t)params.width, (uint32_t)params.height, @@ -1075,8 +1094,7 @@ int main(int argc, const char* argv[]) { params.motion_bucket_id, params.fps, params.augmentation_level, - params.min_cfg, - params.cfg_scale, + guidance_params, params.sample_method, params.sample_steps, params.strength, @@ -1109,8 +1127,7 @@ int main(int argc, const char* argv[]) { params.prompt.c_str(), params.negative_prompt.c_str(), params.clip_skip, - params.cfg_scale, - params.guidance, + guidance_params, params.eta, params.width, params.height, @@ -1123,12 +1140,7 @@ int main(int argc, const char* argv[]) { params.control_strength, params.style_ratio, params.normalize_input, - params.input_id_images_path.c_str(), - params.skip_layers.data(), - params.skip_layers.size(), - params.slg_scale, - params.skip_layer_start, - params.skip_layer_end); + params.input_id_images_path.c_str()); } } else { // EDIT results = edit(sd_ctx, @@ -1137,25 +1149,19 @@ int main(int argc, const char* argv[]) { params.prompt.c_str(), params.negative_prompt.c_str(), params.clip_skip, - params.cfg_scale, - params.guidance, + guidance_params, params.eta, params.width, params.height, params.sample_method, params.sample_steps, - params.strength, params.seed, params.batch_count, control_image, params.control_strength, params.style_ratio, params.normalize_input, - params.skip_layers.data(), - params.skip_layers.size(), - params.slg_scale, - params.skip_layer_start, - params.skip_layer_end); + params.input_id_images_path.c_str()); } if (results == NULL) { diff --git a/model.cpp b/model.cpp index 559c876c6..2e40e004a 100644 --- a/model.cpp +++ b/model.cpp @@ -1673,10 +1673,14 @@ SDVersion ModelLoader::get_sd_version() { } } bool is_inpaint = input_block_weight.ne[2] == 9; + bool is_ip2p = input_block_weight.ne[2] == 8; if (is_xl) { if (is_inpaint) { return VERSION_SDXL_INPAINT; } + if (is_ip2p) { + return VERSION_SDXL_PIX2PIX; + } return VERSION_SDXL; } @@ -1692,6 +1696,9 @@ SDVersion ModelLoader::get_sd_version() { if (is_inpaint) { return VERSION_SD1_INPAINT; } + if (is_ip2p) { + return VERSION_SD1_PIX2PIX; + } return VERSION_SD1; } else if (token_embedding_weight.ne[0] == 1024) { if (is_inpaint) { diff --git a/model.h b/model.h index 95c66319d..a6266039a 100644 --- a/model.h +++ b/model.h @@ -21,10 +21,12 @@ enum SDVersion { VERSION_SD1, VERSION_SD1_INPAINT, + VERSION_SD1_PIX2PIX, VERSION_SD2, VERSION_SD2_INPAINT, VERSION_SDXL, VERSION_SDXL_INPAINT, + VERSION_SDXL_PIX2PIX, VERSION_SVD, VERSION_SD3, VERSION_FLUX, @@ -47,7 +49,7 @@ static inline bool sd_version_is_sd3(SDVersion version) { } static inline bool sd_version_is_sd1(SDVersion version) { - if (version == VERSION_SD1 || version == VERSION_SD1_INPAINT) { + if (version == VERSION_SD1 || version == VERSION_SD1_INPAINT || version == VERSION_SD1_PIX2PIX) { return true; } return false; @@ -61,7 +63,7 @@ static inline bool sd_version_is_sd2(SDVersion version) { } static inline bool sd_version_is_sdxl(SDVersion version) { - if (version == VERSION_SDXL || version == VERSION_SDXL_INPAINT) { + if (version == VERSION_SDXL || version == VERSION_SDXL_INPAINT || version == VERSION_SDXL_PIX2PIX) { return true; } return false; @@ -81,6 +83,14 @@ static inline bool sd_version_is_dit(SDVersion version) { return false; } +static inline bool sd_version_is_unet_edit(SDVersion version) { + return version == VERSION_SD1_PIX2PIX || version == VERSION_SDXL_PIX2PIX; +} + +static bool sd_version_is_inpaint_or_unet_edit(SDVersion version) { + return sd_version_is_unet_edit(version) || sd_version_is_inpaint(version); +} + enum PMVersion { PM_VERSION_1, PM_VERSION_2, diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 9c8265727..c6b873fad 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -27,10 +27,12 @@ const char* model_version_to_str[] = { "SD 1.x", "SD 1.x Inpaint", + "Instruct-Pix2Pix", "SD 2.x", "SD 2.x Inpaint", "SDXL", "SDXL Inpaint", + "SDXL Instruct-Pix2Pix", "SVD", "SD3.x", "Flux", @@ -824,22 +826,30 @@ class StableDiffusionGGML { ggml_tensor* noise, SDCondition cond, SDCondition uncond, + SDCondition img_cond, ggml_tensor* control_hint, float control_strength, - float min_cfg, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, sample_method_t method, const std::vector& sigmas, int start_merge_step, SDCondition id_cond, std::vector ref_latents = {}, - std::vector skip_layers = {}, - float slg_scale = 0, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2, - ggml_tensor* noise_mask = nullptr) { + ggml_tensor* denoise_mask = nullptr) { + std::vector skip_layers(guidance.slg.layers, guidance.slg.layers + guidance.slg.layer_count); + + float cfg_scale = guidance.txt_cfg; + float img_cfg_scale = guidance.img_cfg; + float slg_scale = guidance.slg.scale; + + float min_cfg = guidance.min_cfg; + + if (img_cfg_scale != cfg_scale && !sd_version_is_inpaint_or_unet_edit(version)) { + LOG_WARN("2-conditioning CFG is not supported with this model, disabling it for better performance..."); + img_cfg_scale = cfg_scale; + } + LOG_DEBUG("Sample"); struct ggml_init_params params; size_t data_size = ggml_row_size(init_latent->type, init_latent->ne[0]); @@ -861,13 +871,15 @@ class StableDiffusionGGML { struct ggml_tensor* noised_input = ggml_dup_tensor(work_ctx, noise); - bool has_unconditioned = cfg_scale != 1.0 && uncond.c_crossattn != NULL; + bool has_unconditioned = img_cfg_scale != 1.0 && uncond.c_crossattn != NULL; + bool has_img_cond = cfg_scale != img_cfg_scale && img_cond.c_crossattn != NULL; bool has_skiplayer = slg_scale != 0.0 && skip_layers.size() > 0; // denoise wrapper - struct ggml_tensor* out_cond = ggml_dup_tensor(work_ctx, x); - struct ggml_tensor* out_uncond = NULL; - struct ggml_tensor* out_skip = NULL; + struct ggml_tensor* out_cond = ggml_dup_tensor(work_ctx, x); + struct ggml_tensor* out_uncond = NULL; + struct ggml_tensor* out_skip = NULL; + struct ggml_tensor* out_img_cond = NULL; if (has_unconditioned) { out_uncond = ggml_dup_tensor(work_ctx, x); @@ -880,6 +892,9 @@ class StableDiffusionGGML { LOG_WARN("SLG is incompatible with %s models", model_version_to_str[version]); } } + if (has_img_cond) { + out_img_cond = ggml_dup_tensor(work_ctx, x); + } struct ggml_tensor* denoised = ggml_dup_tensor(work_ctx, x); auto denoise = [&](ggml_tensor* input, float sigma, int step) -> ggml_tensor* { @@ -897,7 +912,7 @@ class StableDiffusionGGML { float t = denoiser->sigma_to_t(sigma); std::vector timesteps_vec(x->ne[3], t); // [N, ] auto timesteps = vector_to_ggml_tensor(work_ctx, timesteps_vec); - std::vector guidance_vec(x->ne[3], guidance); + std::vector guidance_vec(x->ne[3], guidance.distilled_guidance); auto guidance_tensor = vector_to_ggml_tensor(work_ctx, guidance_vec); copy_ggml_tensor(noised_input, input); @@ -964,8 +979,25 @@ class StableDiffusionGGML { negative_data = (float*)out_uncond->data; } + float* img_cond_data = NULL; + if (has_img_cond) { + diffusion_model->compute(n_threads, + noised_input, + timesteps, + img_cond.c_crossattn, + img_cond.c_concat, + img_cond.c_vector, + guidance_tensor, + ref_latents, + -1, + controls, + control_strength, + &out_img_cond); + img_cond_data = (float*)out_img_cond->data; + } + int step_count = sigmas.size(); - bool is_skiplayer_step = has_skiplayer && step > (int)(skip_layer_start * step_count) && step < (int)(skip_layer_end * step_count); + bool is_skiplayer_step = has_skiplayer && step > (int)(guidance.slg.layer_start * step_count) && step < (int)(guidance.slg.layer_end * step_count); float* skip_layer_data = NULL; if (is_skiplayer_step) { LOG_DEBUG("Skipping layers at step %d\n", step); @@ -999,8 +1031,17 @@ class StableDiffusionGGML { int64_t i3 = i / out_cond->ne[0] * out_cond->ne[1] * out_cond->ne[2]; float scale = min_cfg + (cfg_scale - min_cfg) * (i3 * 1.0f / ne3); } else { - latent_result = negative_data[i] + cfg_scale * (positive_data[i] - negative_data[i]); + if (has_img_cond) { + // out_uncond + text_cfg_scale * (out_cond - out_img_cond) + image_cfg_scale * (out_img_cond - out_uncond) + latent_result = negative_data[i] + img_cfg_scale * (img_cond_data[i] - negative_data[i]) + cfg_scale * (positive_data[i] - img_cond_data[i]); + } else { + // img_cfg_scale == cfg_scale + latent_result = negative_data[i] + cfg_scale * (positive_data[i] - negative_data[i]); + } } + } else if (has_img_cond) { + // img_cfg_scale == 1 + latent_result = img_cond_data[i] + cfg_scale * (positive_data[i] - img_cond_data[i]); } if (is_skiplayer_step) { latent_result = latent_result + (positive_data[i] - skip_layer_data[i]) * slg_scale; @@ -1014,10 +1055,10 @@ class StableDiffusionGGML { pretty_progress(step, (int)steps, (t1 - t0) / 1000000.f); // LOG_INFO("step %d sampling completed taking %.2fs", step, (t1 - t0) * 1.0f / 1000000); } - if (noise_mask != nullptr) { + if (denoise_mask != nullptr) { for (int64_t x = 0; x < denoised->ne[0]; x++) { for (int64_t y = 0; y < denoised->ne[1]; y++) { - float mask = ggml_tensor_get_f32(noise_mask, x, y); + float mask = ggml_tensor_get_f32(denoise_mask, x, y); for (int64_t k = 0; k < denoised->ne[2]; k++) { float init = ggml_tensor_get_f32(init_latent, x, y, k); float den = ggml_tensor_get_f32(denoised, x, y, k); @@ -1240,8 +1281,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, std::string prompt, std::string negative_prompt, int clip_skip, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, int width, int height, @@ -1255,11 +1295,8 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, bool normalize_input, std::string input_id_images_path, std::vector ref_latents, - std::vector skip_layers = {}, - float slg_scale = 0, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2, - ggml_tensor* masked_image = NULL) { + ggml_tensor* concat_latent = NULL, + ggml_tensor* denoise_mask = NULL) { if (seed < 0) { // Generally, when using the provided command line, the seed is always >0. // However, to prevent potential issues if 'stable-diffusion.cpp' is invoked as a library @@ -1407,7 +1444,8 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, sd_ctx->sd->diffusion_model->get_adm_in_channels()); SDCondition uncond; - if (cfg_scale != 1.0) { + if (guidance.txt_cfg != 1.0 || + (sd_version_is_inpaint_or_unet_edit(sd_ctx->sd->version) && guidance.txt_cfg != guidance.img_cfg)) { bool force_zero_embeddings = false; if (sd_version_is_sdxl(sd_ctx->sd->version) && negative_prompt.size() == 0 && !sd_ctx->sd->is_using_edm_v_parameterization) { force_zero_embeddings = true; @@ -1446,38 +1484,50 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, int W = width / 8; int H = height / 8; LOG_INFO("sampling using %s method", sampling_methods_str[sample_method]); - ggml_tensor* noise_mask = nullptr; if (sd_version_is_inpaint(sd_ctx->sd->version)) { - if (masked_image == NULL) { - int64_t mask_channels = 1; - if (sd_ctx->sd->version == VERSION_FLUX_FILL) { - mask_channels = 8 * 8; // flatten the whole mask - } - // no mask, set the whole image as masked - masked_image = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, init_latent->ne[0], init_latent->ne[1], mask_channels + init_latent->ne[2], 1); - for (int64_t x = 0; x < masked_image->ne[0]; x++) { - for (int64_t y = 0; y < masked_image->ne[1]; y++) { - if (sd_ctx->sd->version == VERSION_FLUX_FILL) { - // TODO: this might be wrong - for (int64_t c = 0; c < init_latent->ne[2]; c++) { - ggml_tensor_set_f32(masked_image, 0, x, y, c); - } - for (int64_t c = init_latent->ne[2]; c < masked_image->ne[2]; c++) { - ggml_tensor_set_f32(masked_image, 1, x, y, c); - } - } else { - ggml_tensor_set_f32(masked_image, 1, x, y, 0); - for (int64_t c = 1; c < masked_image->ne[2]; c++) { - ggml_tensor_set_f32(masked_image, 0, x, y, c); - } + int64_t mask_channels = 1; + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + mask_channels = 8 * 8; // flatten the whole mask + } + auto empty_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, init_latent->ne[0], init_latent->ne[1], mask_channels + init_latent->ne[2], 1); + // no mask, set the whole image as masked + for (int64_t x = 0; x < empty_latent->ne[0]; x++) { + for (int64_t y = 0; y < empty_latent->ne[1]; y++) { + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + // TODO: this might be wrong + for (int64_t c = 0; c < init_latent->ne[2]; c++) { + ggml_tensor_set_f32(empty_latent, 0, x, y, c); + } + for (int64_t c = init_latent->ne[2]; c < empty_latent->ne[2]; c++) { + ggml_tensor_set_f32(empty_latent, 1, x, y, c); + } + } else { + ggml_tensor_set_f32(empty_latent, 1, x, y, 0); + for (int64_t c = 1; c < empty_latent->ne[2]; c++) { + ggml_tensor_set_f32(empty_latent, 0, x, y, c); } } } } - cond.c_concat = masked_image; - uncond.c_concat = masked_image; - } else { - noise_mask = masked_image; + if (concat_latent == NULL) { + concat_latent = empty_latent; + } + cond.c_concat = concat_latent; + uncond.c_concat = empty_latent; + denoise_mask = NULL; + } else if (sd_version_is_unet_edit(sd_ctx->sd->version)) { + auto empty_latent = ggml_dup_tensor(work_ctx, init_latent); + ggml_set_f32(empty_latent, 0); + uncond.c_concat = empty_latent; + if (concat_latent == NULL) { + concat_latent = empty_latent; + } + cond.c_concat = ref_latents[0]; + } + SDCondition img_cond; + if (uncond.c_crossattn != NULL && + (sd_version_is_inpaint_or_unet_edit(sd_ctx->sd->version) && guidance.txt_cfg != guidance.img_cfg)) { + img_cond = SDCondition(uncond.c_crossattn, uncond.c_vector, cond.c_concat); } for (int b = 0; b < batch_count; b++) { int64_t sampling_start = ggml_time_ms(); @@ -1497,15 +1547,17 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, LOG_INFO("PHOTOMAKER: start_merge_step: %d", start_merge_step); } + // Disable min_cfg + guidance.min_cfg = guidance.txt_cfg; + struct ggml_tensor* x_0 = sd_ctx->sd->sample(work_ctx, x_t, noise, cond, uncond, + img_cond, image_hint, control_strength, - cfg_scale, - cfg_scale, guidance, eta, sample_method, @@ -1513,11 +1565,7 @@ sd_image_t* generate_image(sd_ctx_t* sd_ctx, start_merge_step, id_cond, ref_latents, - skip_layers, - slg_scale, - skip_layer_start, - skip_layer_end, - noise_mask); + denoise_mask); // struct ggml_tensor* x_0 = load_tensor_from_file(ctx, "samples_ddim.bin"); // print_ggml_tensor(x_0); @@ -1595,8 +1643,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, const char* prompt_c_str, const char* negative_prompt_c_str, int clip_skip, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, int width, int height, @@ -1608,13 +1655,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, float control_strength, float style_ratio, bool normalize_input, - const char* input_id_images_path_c_str, - int* skip_layers = NULL, - size_t skip_layers_count = 0, - float slg_scale = 0, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2) { - std::vector skip_layers_vec(skip_layers, skip_layers + skip_layers_count); + const char* input_id_images_path_c_str) { LOG_DEBUG("txt2img %dx%d", width, height); if (sd_ctx == NULL) { return NULL; @@ -1659,7 +1700,6 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, prompt_c_str, negative_prompt_c_str, clip_skip, - cfg_scale, guidance, eta, width, @@ -1673,11 +1713,7 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, style_ratio, normalize_input, input_id_images_path_c_str, - {}, - skip_layers_vec, - slg_scale, - skip_layer_start, - skip_layer_end); + {}); size_t t1 = ggml_time_ms(); @@ -1692,8 +1728,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, const char* prompt_c_str, const char* negative_prompt_c_str, int clip_skip, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, int width, int height, @@ -1706,13 +1741,7 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, float control_strength, float style_ratio, bool normalize_input, - const char* input_id_images_path_c_str, - int* skip_layers = NULL, - size_t skip_layers_count = 0, - float slg_scale = 0, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2) { - std::vector skip_layers_vec(skip_layers, skip_layers + skip_layers_count); + const char* input_id_images_path_c_str) { LOG_DEBUG("img2img %dx%d", width, height); if (sd_ctx == NULL) { return NULL; @@ -1756,7 +1785,8 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, sd_image_to_tensor(init_image.data, init_img); - ggml_tensor* masked_image; + ggml_tensor* concat_latent; + ggml_tensor* denoise_mask = NULL; if (sd_version_is_inpaint(sd_ctx->sd->version)) { int64_t mask_channels = 1; @@ -1765,22 +1795,22 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, } ggml_tensor* masked_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 3, 1); sd_apply_mask(init_img, mask_img, masked_img); - ggml_tensor* masked_image_0 = NULL; + ggml_tensor* masked_latent = NULL; if (!sd_ctx->sd->use_tiny_autoencoder) { ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); - masked_image_0 = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); + masked_latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); } else { - masked_image_0 = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); + masked_latent = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); } - masked_image = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, masked_image_0->ne[0], masked_image_0->ne[1], mask_channels + masked_image_0->ne[2], 1); - for (int ix = 0; ix < masked_image_0->ne[0]; ix++) { - for (int iy = 0; iy < masked_image_0->ne[1]; iy++) { + concat_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, masked_latent->ne[0], masked_latent->ne[1], mask_channels + masked_latent->ne[2], 1); + for (int ix = 0; ix < masked_latent->ne[0]; ix++) { + for (int iy = 0; iy < masked_latent->ne[1]; iy++) { int mx = ix * 8; int my = iy * 8; if (sd_ctx->sd->version == VERSION_FLUX_FILL) { - for (int k = 0; k < masked_image_0->ne[2]; k++) { - float v = ggml_tensor_get_f32(masked_image_0, ix, iy, k); - ggml_tensor_set_f32(masked_image, v, ix, iy, k); + for (int k = 0; k < masked_latent->ne[2]; k++) { + float v = ggml_tensor_get_f32(masked_latent, ix, iy, k); + ggml_tensor_set_f32(concat_latent, v, ix, iy, k); } // "Encode" 8x8 mask chunks into a flattened 1x64 vector, and concatenate to masked image for (int x = 0; x < 8; x++) { @@ -1788,28 +1818,30 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, float m = ggml_tensor_get_f32(mask_img, mx + x, my + y); // TODO: check if the way the mask is flattened is correct (is it supposed to be x*8+y or x+8*y?) // python code was using "b (h 8) (w 8) -> b (8 8) h w" - ggml_tensor_set_f32(masked_image, m, ix, iy, masked_image_0->ne[2] + x * 8 + y); + ggml_tensor_set_f32(concat_latent, m, ix, iy, masked_latent->ne[2] + x * 8 + y); } } } else { float m = ggml_tensor_get_f32(mask_img, mx, my); - ggml_tensor_set_f32(masked_image, m, ix, iy, 0); - for (int k = 0; k < masked_image_0->ne[2]; k++) { - float v = ggml_tensor_get_f32(masked_image_0, ix, iy, k); - ggml_tensor_set_f32(masked_image, v, ix, iy, k + mask_channels); + ggml_tensor_set_f32(concat_latent, m, ix, iy, 0); + for (int k = 0; k < masked_latent->ne[2]; k++) { + float v = ggml_tensor_get_f32(masked_latent, ix, iy, k); + ggml_tensor_set_f32(concat_latent, v, ix, iy, k + mask_channels); } } } } - } else { + } + + { // LOG_WARN("Inpainting with a base model is not great"); - masked_image = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width / 8, height / 8, 1, 1); - for (int ix = 0; ix < masked_image->ne[0]; ix++) { - for (int iy = 0; iy < masked_image->ne[1]; iy++) { + denoise_mask = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width / 8, height / 8, 1, 1); + for (int ix = 0; ix < denoise_mask->ne[0]; ix++) { + for (int iy = 0; iy < denoise_mask->ne[1]; iy++) { int mx = ix * 8; int my = iy * 8; float m = ggml_tensor_get_f32(mask_img, mx, my); - ggml_tensor_set_f32(masked_image, m, ix, iy); + ggml_tensor_set_f32(denoise_mask, m, ix, iy); } } } @@ -1822,7 +1854,6 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, init_latent = sd_ctx->sd->encode_first_stage(work_ctx, init_img); } - print_ggml_tensor(init_latent, true); size_t t1 = ggml_time_ms(); LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); @@ -1840,7 +1871,6 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, prompt_c_str, negative_prompt_c_str, clip_skip, - cfg_scale, guidance, eta, width, @@ -1855,11 +1885,8 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, normalize_input, input_id_images_path_c_str, {}, - skip_layers_vec, - slg_scale, - skip_layer_start, - skip_layer_end, - masked_image); + concat_latent, + denoise_mask); size_t t2 = ggml_time_ms(); @@ -1876,8 +1903,7 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, int motion_bucket_id, int fps, float augmentation_level, - float min_cfg, - float cfg_scale, + sd_guidance_params_t guidance, enum sample_method_t sample_method, int sample_steps, float strength, @@ -1953,10 +1979,9 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, cond, uncond, {}, + {}, 0.f, - min_cfg, - cfg_scale, - 0.f, + guidance, 0.f, sample_method, sigmas, @@ -2007,26 +2032,19 @@ sd_image_t* edit(sd_ctx_t* sd_ctx, const char* prompt_c_str, const char* negative_prompt_c_str, int clip_skip, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, int width, int height, - sample_method_t sample_method, + enum sample_method_t sample_method, int sample_steps, - float strength, int64_t seed, int batch_count, const sd_image_t* control_cond, float control_strength, float style_ratio, bool normalize_input, - int* skip_layers = NULL, - size_t skip_layers_count = 0, - float slg_scale = 0, - float skip_layer_start = 0.01, - float skip_layer_end = 0.2) { - std::vector skip_layers_vec(skip_layers, skip_layers + skip_layers_count); + const char* input_id_images_path_c_str) { LOG_DEBUG("edit %dx%d", width, height); if (sd_ctx == NULL) { return NULL; @@ -2064,11 +2082,21 @@ sd_image_t* edit(sd_ctx_t* sd_ctx, sd_image_to_tensor(ref_images[i].data, img); ggml_tensor* latent = NULL; - if (!sd_ctx->sd->use_tiny_autoencoder) { + if (sd_ctx->sd->use_tiny_autoencoder) { + latent = sd_ctx->sd->encode_first_stage(work_ctx, img); + } else if (sd_ctx->sd->version == VERSION_SD1_PIX2PIX) { + latent = sd_ctx->sd->encode_first_stage(work_ctx, img); + latent = ggml_view_3d(work_ctx, + latent, + latent->ne[0], + latent->ne[1], + latent->ne[2] / 2, + latent->nb[1], + latent->nb[2], + 0); + } else { ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, img); latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); - } else { - latent = sd_ctx->sd->encode_first_stage(work_ctx, img); } ref_latents.push_back(latent); } @@ -2086,7 +2114,6 @@ sd_image_t* edit(sd_ctx_t* sd_ctx, prompt_c_str, negative_prompt_c_str, clip_skip, - cfg_scale, guidance, eta, width, @@ -2101,10 +2128,6 @@ sd_image_t* edit(sd_ctx_t* sd_ctx, normalize_input, "", ref_latents, - skip_layers_vec, - slg_scale, - skip_layer_start, - skip_layer_end, NULL); size_t t2 = ggml_time_ms(); diff --git a/stable-diffusion.h b/stable-diffusion.h index 212e1c918..ac50df1af 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -129,6 +129,22 @@ typedef struct { typedef struct sd_ctx_t sd_ctx_t; +typedef struct { + int* layers; + size_t layer_count; + float layer_start; + float layer_end; + float scale; +} sd_slg_params_t; + +typedef struct { + float txt_cfg; + float img_cfg; + float min_cfg; + float distilled_guidance; + sd_slg_params_t slg; +} sd_guidance_params_t; + SD_API sd_ctx_t* new_sd_ctx(const char* model_path, const char* clip_l_path, const char* clip_g_path, @@ -161,8 +177,7 @@ SD_API sd_image_t* txt2img(sd_ctx_t* sd_ctx, const char* prompt, const char* negative_prompt, int clip_skip, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, int width, int height, @@ -174,12 +189,7 @@ SD_API sd_image_t* txt2img(sd_ctx_t* sd_ctx, float control_strength, float style_strength, bool normalize_input, - const char* input_id_images_path, - int* skip_layers, - size_t skip_layers_count, - float slg_scale, - float skip_layer_start, - float skip_layer_end); + const char* input_id_images_path); SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, sd_image_t init_image, @@ -187,8 +197,7 @@ SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, const char* prompt, const char* negative_prompt, int clip_skip, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, int width, int height, @@ -201,12 +210,7 @@ SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, float control_strength, float style_strength, bool normalize_input, - const char* input_id_images_path, - int* skip_layers, - size_t skip_layers_count, - float slg_scale, - float skip_layer_start, - float skip_layer_end); + const char* input_id_images_path); SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, sd_image_t init_image, @@ -216,8 +220,7 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, int motion_bucket_id, int fps, float augmentation_level, - float min_cfg, - float cfg_scale, + sd_guidance_params_t guidance, enum sample_method_t sample_method, int sample_steps, float strength, @@ -229,25 +232,19 @@ SD_API sd_image_t* edit(sd_ctx_t* sd_ctx, const char* prompt, const char* negative_prompt, int clip_skip, - float cfg_scale, - float guidance, + sd_guidance_params_t guidance, float eta, int width, int height, enum sample_method_t sample_method, int sample_steps, - float strength, int64_t seed, int batch_count, const sd_image_t* control_cond, float control_strength, float style_strength, bool normalize_input, - int* skip_layers, - size_t skip_layers_count, - float slg_scale, - float skip_layer_start, - float skip_layer_end); + const char* input_id_images_path); typedef struct upscaler_ctx_t upscaler_ctx_t; diff --git a/unet.hpp b/unet.hpp index 31b7fe986..9193dcd67 100644 --- a/unet.hpp +++ b/unet.hpp @@ -207,6 +207,8 @@ class UnetModelBlock : public GGMLBlock { } if (sd_version_is_inpaint(version)) { in_channels = 9; + } else if (sd_version_is_unet_edit(version)) { + in_channels = 8; } // dims is always 2 From ca0bd9396ed7dc7bc1958f91e78cbd2dc1f402ca Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 13 Jul 2025 18:48:42 +0800 Subject: [PATCH 084/143] refactor: update c api (#728) --- README.md | 14 +- docs/kontext.md | 2 +- examples/cli/main.cpp | 1019 ++++++++++++++++---------------------- stable-diffusion.cpp | 1093 ++++++++++++++++++++++------------------- stable-diffusion.h | 211 ++++---- util.cpp | 4 - util.h | 3 + 7 files changed, 1114 insertions(+), 1232 deletions(-) diff --git a/README.md b/README.md index 4720dc29c..8ce98137f 100644 --- a/README.md +++ b/README.md @@ -282,14 +282,14 @@ usage: ./bin/sd [arguments] arguments: -h, --help show this help message and exit - -M, --mode [MODEL] run mode (txt2img or img2img or convert, default: txt2img) + -M, --mode [MODE] run mode, one of: [img_gen, convert], default: img_gen -t, --threads N number of threads to use during computation (default: -1) If threads <= 0, then threads will be set to the number of CPU physical cores -m, --model [MODEL] path to full model --diffusion-model path to the standalone diffusion model --clip_l path to the clip-l text encoder --clip_g path to the clip-g text encoder - --t5xxl path to the the t5xxl text encoder + --t5xxl path to the t5xxl text encoder --vae [VAE] path to vae --taesd [TAESD_PATH] path to taesd. Using Tiny AutoEncoder for fast decoding (low quality) --control-net [CONTROL_PATH] path to control net model @@ -301,16 +301,18 @@ arguments: --upscale-repeats Run the ESRGAN upscaler this many times (default 1) --type [TYPE] weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K) If not specified, the default is the type of the weight file + --tensor-type-rules [EXPRESSION] weight type per tensor pattern (example: "^vae\.=f16,model\.=q8_0") --lora-model-dir [DIR] lora model directory -i, --init-img [IMAGE] path to the input image, required by img2img --mask [MASK] path to the mask image, required by img2img with mask --control-image [IMAGE] path to image condition, control net - -r, --ref_image [PATH] reference image for Flux Kontext models (can be used multiple times) + -r, --ref-image [PATH] reference image for Flux Kontext models (can be used multiple times) -o, --output OUTPUT path to write result image to (default: ./output.png) -p, --prompt [PROMPT] the prompt to render -n, --negative-prompt PROMPT the negative prompt (default: "") --cfg-scale SCALE unconditional guidance scale: (default: 7.0) - --guidance SCALE guidance scale for img2img (default: 3.5) + --img-cfg-scale SCALE image guidance scale for inpaint or instruct-pix2pix models: (default: same as --cfg-scale) + --guidance SCALE distilled guidance scale for models with guidance input (default: 3.5) --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0) 0 means disabled, a value of 2.5 is nice for sd3.5 medium --eta SCALE eta in DDIM, only for DDIM and TCD: (default: 0) @@ -319,7 +321,7 @@ arguments: --skip-layer-end END SLG disabling point: (default: 0.2) SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END]) --strength STRENGTH strength for noising/unnoising (default: 0.75) - --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%) + --style-ratio STYLE-RATIO strength for keeping input identity (default: 20) --control-strength STRENGTH strength to apply Control Net (default: 0.9) 1.0 corresponds to full destruction of information in init image -H, --height H image height, in pixel space (default: 512) @@ -371,7 +373,7 @@ Using formats of different precisions will yield results of varying quality. ``` -./bin/sd --mode img2img -m ../models/sd-v1-4.ckpt -p "cat with blue eyes" -i ./output.png -o ./img2img_output.png --strength 0.4 +./bin/sd -m ../models/sd-v1-4.ckpt -p "cat with blue eyes" -i ./output.png -o ./img2img_output.png --strength 0.4 ```

    diff --git a/docs/kontext.md b/docs/kontext.md index 519752553..698735039 100644 --- a/docs/kontext.md +++ b/docs/kontext.md @@ -27,7 +27,7 @@ You can download the preconverted gguf weights from [FLUX.1-Kontext-dev-GGUF](ht For example: ``` - .\bin\Release\sd.exe -M edit -r .\flux1-dev-q8_0.png --diffusion-model ..\models\flux1-kontext-dev-q8_0.gguf --vae ..\models\ae.sft --clip_l ..\models\clip_l.safetensors --t5xxl ..\models\t5xxl_fp16.safetensors -p "change 'flux.cpp' to 'kontext.cpp'" --cfg-scale 1.0 --sampling-method euler -v + .\bin\Release\sd.exe -r .\flux1-dev-q8_0.png --diffusion-model ..\models\flux1-kontext-dev-q8_0.gguf --vae ..\models\ae.sft --clip_l ..\models\clip_l.safetensors --t5xxl ..\models\t5xxl_fp16.safetensors -p "change 'flux.cpp' to 'kontext.cpp'" --cfg-scale 1.0 --sampling-method euler -v ``` diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 234dad3a6..5879967fb 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -1,13 +1,15 @@ #include #include #include +#include #include +#include #include +#include #include #include // #include "preprocessing.hpp" -#include "flux.hpp" #include "stable-diffusion.h" #define STB_IMAGE_IMPLEMENTATION @@ -22,58 +24,26 @@ #define STB_IMAGE_RESIZE_STATIC #include "stb_image_resize.h" -const char* rng_type_to_str[] = { - "std_default", - "cuda", -}; - -// Names of the sampler method, same order as enum sample_method in stable-diffusion.h -const char* sample_method_str[] = { - "euler_a", - "euler", - "heun", - "dpm2", - "dpm++2s_a", - "dpm++2m", - "dpm++2mv2", - "ipndm", - "ipndm_v", - "lcm", - "ddim_trailing", - "tcd", -}; - -// Names of the sigma schedule overrides, same order as sample_schedule in stable-diffusion.h -const char* schedule_str[] = { - "default", - "discrete", - "karras", - "exponential", - "ays", - "gits", -}; +#define SAFE_STR(s) ((s) ? (s) : "") +#define BOOL_STR(b) ((b) ? "true" : "false") const char* modes_str[] = { - "txt2img", - "img2img", - "img2vid", - "edit", + "img_gen", + "vid_gen", "convert", }; -#define SD_ALL_MODES_STR "txt2img, img2img, edit, convert" +#define SD_ALL_MODES_STR "img_gen, vid_gen, convert" enum SDMode { - TXT2IMG, - IMG2IMG, - IMG2VID, - EDIT, + IMG_GEN, + VID_GEN, CONVERT, MODE_COUNT }; struct SDParams { int n_threads = -1; - SDMode mode = TXT2IMG; + SDMode mode = IMG_GEN; std::string model_path; std::string clip_l_path; std::string clip_g_path; @@ -82,9 +52,9 @@ struct SDParams { std::string vae_path; std::string taesd_path; std::string esrgan_path; - std::string controlnet_path; - std::string embeddings_path; - std::string stacked_id_embeddings_path; + std::string control_net_path; + std::string embedding_dir; + std::string stacked_id_embed_dir; std::string input_id_images_path; sd_type_t wtype = SD_TYPE_COUNT; std::string tensor_type_rules; @@ -154,9 +124,9 @@ void print_params(SDParams params) { printf(" vae_path: %s\n", params.vae_path.c_str()); printf(" taesd_path: %s\n", params.taesd_path.c_str()); printf(" esrgan_path: %s\n", params.esrgan_path.c_str()); - printf(" controlnet_path: %s\n", params.controlnet_path.c_str()); - printf(" embeddings_path: %s\n", params.embeddings_path.c_str()); - printf(" stacked_id_embeddings_path: %s\n", params.stacked_id_embeddings_path.c_str()); + printf(" control_net_path: %s\n", params.control_net_path.c_str()); + printf(" embedding_dir: %s\n", params.embedding_dir.c_str()); + printf(" stacked_id_embed_dir: %s\n", params.stacked_id_embed_dir.c_str()); printf(" input_id_images_path: %s\n", params.input_id_images_path.c_str()); printf(" style ratio: %.2f\n", params.style_ratio); printf(" normalize input image : %s\n", params.normalize_input ? "true" : "false"); @@ -184,11 +154,11 @@ void print_params(SDParams params) { printf(" clip_skip: %d\n", params.clip_skip); printf(" width: %d\n", params.width); printf(" height: %d\n", params.height); - printf(" sample_method: %s\n", sample_method_str[params.sample_method]); - printf(" schedule: %s\n", schedule_str[params.schedule]); + printf(" sample_method: %s\n", sd_sample_method_name(params.sample_method)); + printf(" schedule: %s\n", sd_schedule_name(params.schedule)); printf(" sample_steps: %d\n", params.sample_steps); printf(" strength(img2img): %.2f\n", params.strength); - printf(" rng: %s\n", rng_type_to_str[params.rng_type]); + printf(" rng: %s\n", sd_rng_type_name(params.rng_type)); printf(" seed: %ld\n", params.seed); printf(" batch_count: %d\n", params.batch_count); printf(" vae_tiling: %s\n", params.vae_tiling ? "true" : "false"); @@ -203,11 +173,7 @@ void print_usage(int argc, const char* argv[]) { printf("\n"); printf("arguments:\n"); printf(" -h, --help show this help message and exit\n"); - printf(" -M, --mode [MODE] run mode, one of:\n"); - printf(" txt2img: generate an image from a text prompt (default)\n"); - printf(" img2img: generate an image from a text prompt and an initial image (--init-img)\n"); - printf(" edit: modify an image (--ref-image) based on text instructions\n"); - printf(" convert: convert a model file to gguf format, optionally with quantization\n"); + printf(" -M, --mode [MODE] run mode, one of: [img_gen, convert], default: img_gen\n"); printf(" -t, --threads N number of threads to use during computation (default: -1)\n"); printf(" If threads <= 0, then threads will be set to the number of CPU physical cores\n"); printf(" -m, --model [MODEL] path to full model\n"); @@ -246,7 +212,7 @@ void print_usage(int argc, const char* argv[]) { printf(" --skip-layer-end END SLG disabling point: (default: 0.2)\n"); printf(" SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END])\n"); printf(" --strength STRENGTH strength for noising/unnoising (default: 0.75)\n"); - printf(" --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%%)\n"); + printf(" --style-ratio STYLE-RATIO strength for keeping input identity (default: 20)\n"); printf(" --control-strength STRENGTH strength to apply Control Net (default: 0.9)\n"); printf(" 1.0 corresponds to full destruction of information in init image\n"); printf(" -H, --height H image height, in pixel space (default: 512)\n"); @@ -275,432 +241,344 @@ void print_usage(int argc, const char* argv[]) { printf(" -v, --verbose print extra info\n"); } -void parse_args(int argc, const char** argv, SDParams& params) { +struct StringOption { + std::string short_name; + std::string long_name; + std::string desc; + std::string* target; +}; + +struct IntOption { + std::string short_name; + std::string long_name; + std::string desc; + int* target; +}; + +struct FloatOption { + std::string short_name; + std::string long_name; + std::string desc; + float* target; +}; + +struct BoolOption { + std::string short_name; + std::string long_name; + std::string desc; + bool keep_true; + bool* target; +}; + +struct ManualOption { + std::string short_name; + std::string long_name; + std::string desc; + std::function cb; +}; + +struct ArgOptions { + std::vector string_options; + std::vector int_options; + std::vector float_options; + std::vector bool_options; + std::vector manual_options; +}; + +bool parse_options(int argc, const char** argv, ArgOptions& options) { bool invalid_arg = false; std::string arg; for (int i = 1; i < argc; i++) { arg = argv[i]; - if (arg == "-t" || arg == "--threads") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.n_threads = std::stoi(argv[i]); - } else if (arg == "-M" || arg == "--mode") { - if (++i >= argc) { - invalid_arg = true; - break; - } - const char* mode_selected = argv[i]; - int mode_found = -1; - for (int d = 0; d < MODE_COUNT; d++) { - if (!strcmp(mode_selected, modes_str[d])) { - mode_found = d; - } - } - if (mode_found == -1) { - fprintf(stderr, - "error: invalid mode %s, must be one of [%s]\n", - mode_selected, SD_ALL_MODES_STR); - exit(1); - } - params.mode = (SDMode)mode_found; - } else if (arg == "-m" || arg == "--model") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.model_path = argv[i]; - } else if (arg == "--clip_l") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.clip_l_path = argv[i]; - } else if (arg == "--clip_g") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.clip_g_path = argv[i]; - } else if (arg == "--t5xxl") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.t5xxl_path = argv[i]; - } else if (arg == "--diffusion-model") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.diffusion_model_path = argv[i]; - } else if (arg == "--vae") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.vae_path = argv[i]; - } else if (arg == "--taesd") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.taesd_path = argv[i]; - } else if (arg == "--control-net") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.controlnet_path = argv[i]; - } else if (arg == "--upscale-model") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.esrgan_path = argv[i]; - } else if (arg == "--embd-dir") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.embeddings_path = argv[i]; - } else if (arg == "--stacked-id-embd-dir") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.stacked_id_embeddings_path = argv[i]; - } else if (arg == "--input-id-images-dir") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.input_id_images_path = argv[i]; - } else if (arg == "--type") { - if (++i >= argc) { - invalid_arg = true; - break; - } - std::string type = argv[i]; - bool found = false; - std::string valid_types = ""; - for (size_t i = 0; i < SD_TYPE_COUNT; i++) { - auto trait = ggml_get_type_traits((ggml_type)i); - std::string name(trait->type_name); - if (name == "f32" || trait->to_float && trait->type_size) { - if (i) - valid_types += ", "; - valid_types += name; - if (type == name) { - if (ggml_quantize_requires_imatrix((ggml_type)i)) { - printf("\033[35;1m[WARNING]\033[0m: type %s requires imatrix to work properly. A dummy imatrix will be used, expect poor quality.\n", trait->type_name); - } - params.wtype = (enum sd_type_t)i; - found = true; - break; - } - } - } - if (!found) { - fprintf(stderr, "error: invalid weight format %s, must be one of [%s]\n", - type.c_str(), - valid_types.c_str()); - exit(1); - } - } else if (arg == "--tensor-type-rules") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.tensor_type_rules = argv[i]; - } else if (arg == "--lora-model-dir") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.lora_model_dir = argv[i]; - } else if (arg == "-i" || arg == "--init-img") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.input_path = argv[i]; - } else if (arg == "--mask") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.mask_path = argv[i]; - } else if (arg == "--control-image") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.control_image_path = argv[i]; - } else if (arg == "-o" || arg == "--output") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.output_path = argv[i]; - } else if (arg == "-p" || arg == "--prompt") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.prompt = argv[i]; - } else if (arg == "--upscale-repeats") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.upscale_repeats = std::stoi(argv[i]); - if (params.upscale_repeats < 1) { - fprintf(stderr, "error: upscale multiplier must be at least 1\n"); - exit(1); - } - } else if (arg == "-n" || arg == "--negative-prompt") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.negative_prompt = argv[i]; - } else if (arg == "--cfg-scale") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.cfg_scale = std::stof(argv[i]); - } else if (arg == "--img-cfg-scale") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.img_cfg_scale = std::stof(argv[i]); - } else if (arg == "--guidance") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.guidance = std::stof(argv[i]); - } else if (arg == "--eta") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.eta = std::stof(argv[i]); - } else if (arg == "--strength") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.strength = std::stof(argv[i]); - } else if (arg == "--style-ratio") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.style_ratio = std::stof(argv[i]); - } else if (arg == "--control-strength") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.control_strength = std::stof(argv[i]); - } else if (arg == "-H" || arg == "--height") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.height = std::stoi(argv[i]); - } else if (arg == "-W" || arg == "--width") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.width = std::stoi(argv[i]); - } else if (arg == "--steps") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.sample_steps = std::stoi(argv[i]); - } else if (arg == "--clip-skip") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.clip_skip = std::stoi(argv[i]); - } else if (arg == "--vae-tiling") { - params.vae_tiling = true; - } else if (arg == "--control-net-cpu") { - params.control_net_cpu = true; - } else if (arg == "--normalize-input") { - params.normalize_input = true; - } else if (arg == "--clip-on-cpu") { - params.clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs - } else if (arg == "--vae-on-cpu") { - params.vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs - } else if (arg == "--diffusion-fa") { - params.diffusion_flash_attn = true; // can reduce MEM significantly - } else if (arg == "--canny") { - params.canny_preprocess = true; - } else if (arg == "-b" || arg == "--batch-count") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.batch_count = std::stoi(argv[i]); - } else if (arg == "--rng") { - if (++i >= argc) { - invalid_arg = true; - break; - } - std::string rng_type_str = argv[i]; - if (rng_type_str == "std_default") { - params.rng_type = STD_DEFAULT_RNG; - } else if (rng_type_str == "cuda") { - params.rng_type = CUDA_RNG; - } else { - invalid_arg = true; - break; - } - } else if (arg == "--schedule") { - if (++i >= argc) { - invalid_arg = true; - break; - } - const char* schedule_selected = argv[i]; - int schedule_found = -1; - for (int d = 0; d < N_SCHEDULES; d++) { - if (!strcmp(schedule_selected, schedule_str[d])) { - schedule_found = d; - } - } - if (schedule_found == -1) { - invalid_arg = true; - break; - } - params.schedule = (schedule_t)schedule_found; - } else if (arg == "-s" || arg == "--seed") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.seed = std::stoll(argv[i]); - } else if (arg == "--sampling-method") { - if (++i >= argc) { - invalid_arg = true; - break; - } - const char* sample_method_selected = argv[i]; - int sample_method_found = -1; - for (int m = 0; m < N_SAMPLE_METHODS; m++) { - if (!strcmp(sample_method_selected, sample_method_str[m])) { - sample_method_found = m; + for (auto& option : options.string_options) { + if ((option.short_name.size() > 0 && arg == option.short_name) || (option.long_name.size() > 0 && arg == option.long_name)) { + if (++i >= argc) { + invalid_arg = true; + break; } + *option.target = std::string(argv[i]); } - if (sample_method_found == -1) { - invalid_arg = true; - break; - } - params.sample_method = (sample_method_t)sample_method_found; - } else if (arg == "-h" || arg == "--help") { - print_usage(argc, argv); - exit(0); - } else if (arg == "-v" || arg == "--verbose") { - params.verbose = true; - } else if (arg == "--color") { - params.color = true; - } else if (arg == "--slg-scale") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.slg_scale = std::stof(argv[i]); - } else if (arg == "--skip-layers") { - if (++i >= argc) { - invalid_arg = true; - break; - } - if (argv[i][0] != '[') { - invalid_arg = true; - break; - } - std::string layers_str = argv[i]; - while (layers_str.back() != ']') { + } + if (invalid_arg) { + break; + } + + for (auto& option : options.int_options) { + if ((option.short_name.size() > 0 && arg == option.short_name) || (option.long_name.size() > 0 && arg == option.long_name)) { if (++i >= argc) { invalid_arg = true; break; } - layers_str += " " + std::string(argv[i]); + *option.target = std::stoi(argv[i]); } - layers_str = layers_str.substr(1, layers_str.size() - 2); - - std::regex regex("[, ]+"); - std::sregex_token_iterator iter(layers_str.begin(), layers_str.end(), regex, -1); - std::sregex_token_iterator end; - std::vector tokens(iter, end); - std::vector layers; - for (const auto& token : tokens) { - try { - layers.push_back(std::stoi(token)); - } catch (const std::invalid_argument& e) { + } + if (invalid_arg) { + break; + } + + for (auto& option : options.float_options) { + if ((option.short_name.size() > 0 && arg == option.short_name) || (option.long_name.size() > 0 && arg == option.long_name)) { + if (++i >= argc) { invalid_arg = true; break; } + *option.target = std::stof(argv[i]); } - params.skip_layers = layers; + } + if (invalid_arg) { + break; + } - if (invalid_arg) { - break; - } - } else if (arg == "--skip-layer-start") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.skip_layer_start = std::stof(argv[i]); - } else if (arg == "--skip-layer-end") { - if (++i >= argc) { - invalid_arg = true; - break; - } - params.skip_layer_end = std::stof(argv[i]); - } else if (arg == "-r" || arg == "--ref-image") { - if (++i >= argc) { - invalid_arg = true; - break; + for (auto& option : options.bool_options) { + if ((option.short_name.size() > 0 && arg == option.short_name) || (option.long_name.size() > 0 && arg == option.long_name)) { + if (option.keep_true) { + *option.target = true; + } else { + *option.target = false; + } } - params.ref_image_paths.push_back(argv[i]); - } else if (arg == "--chroma-disable-dit-mask") { - params.chroma_use_dit_mask = false; - } else if (arg == "--chroma-enable-t5-mask") { - params.chroma_use_t5_mask = true; - } else if (arg == "--chroma-t5-mask-pad") { - if (++i >= argc) { - invalid_arg = true; - break; + } + if (invalid_arg) { + break; + } + + for (auto& option : options.manual_options) { + if ((option.short_name.size() > 0 && arg == option.short_name) || (option.long_name.size() > 0 && arg == option.long_name)) { + int ret = option.cb(argc, argv, i); + if (ret < 0) { + invalid_arg = true; + break; + } + i += ret; } - params.chroma_t5_mask_pad = std::stoi(argv[i]); - } else { - fprintf(stderr, "error: unknown argument: %s\n", arg.c_str()); - print_usage(argc, argv); - exit(1); + } + if (invalid_arg) { + break; } } if (invalid_arg) { fprintf(stderr, "error: invalid parameter for argument: %s\n", arg.c_str()); + return false; + } + return true; +} + +void parse_args(int argc, const char** argv, SDParams& params) { + ArgOptions options; + options.string_options = { + {"-m", "--model", "", ¶ms.model_path}, + {"", "--clip_l", "", ¶ms.clip_l_path}, + {"", "--clip_g", "", ¶ms.clip_g_path}, + {"", "--t5xxl", "", ¶ms.t5xxl_path}, + {"", "--diffusion-model", "", ¶ms.diffusion_model_path}, + {"", "--vae", "", ¶ms.vae_path}, + {"", "--taesd", "", ¶ms.taesd_path}, + {"", "--control-net", "", ¶ms.control_net_path}, + {"", "--embd-dir", "", ¶ms.embedding_dir}, + {"", "--stacked-id-embd-dir", "", ¶ms.stacked_id_embed_dir}, + {"", "--lora-model-dir", "", ¶ms.lora_model_dir}, + {"-i", "--init-img", "", ¶ms.input_path}, + {"", "--tensor-type-rules", "", ¶ms.tensor_type_rules}, + {"", "--input-id-images-dir", "", ¶ms.input_id_images_path}, + {"", "--mask", "", ¶ms.mask_path}, + {"", "--control-image", "", ¶ms.control_image_path}, + {"-o", "--output", "", ¶ms.output_path}, + {"-p", "--prompt", "", ¶ms.prompt}, + {"-n", "--negative-prompt", "", ¶ms.negative_prompt}, + + {"", "--upscale-model", "", ¶ms.esrgan_path}, + }; + + options.int_options = { + {"-t", "--threads", "", ¶ms.n_threads}, + {"", "--upscale-repeats", "", ¶ms.upscale_repeats}, + {"-H", "--height", "", ¶ms.height}, + {"-W", "--width", "", ¶ms.width}, + {"", "--steps", "", ¶ms.sample_steps}, + {"", "--clip-skip", "", ¶ms.clip_skip}, + {"-b", "--batch-count", "", ¶ms.batch_count}, + {"", "--chroma-t5-mask-pad", "", ¶ms.chroma_t5_mask_pad}, + }; + + options.float_options = { + {"", "--cfg-scale", "", ¶ms.cfg_scale}, + {"", "--img-cfg-scale", "", ¶ms.img_cfg_scale}, + {"", "--guidance", "", ¶ms.guidance}, + {"", "--eta", "", ¶ms.eta}, + {"", "--strength", "", ¶ms.strength}, + {"", "--style-ratio", "", ¶ms.style_ratio}, + {"", "--control-strength", "", ¶ms.control_strength}, + {"", "--slg-scale", "", ¶ms.slg_scale}, + {"", "--skip-layer-start", "", ¶ms.skip_layer_start}, + {"", "--skip-layer-end", "", ¶ms.skip_layer_end}, + + }; + + options.bool_options = { + {"", "--vae-tiling", "", true, ¶ms.vae_tiling}, + {"", "--control-net-cpu", "", true, ¶ms.control_net_cpu}, + {"", "--normalize-input", "", true, ¶ms.normalize_input}, + {"", "--clip-on-cpu", "", true, ¶ms.clip_on_cpu}, + {"", "--vae-on-cpu", "", true, ¶ms.vae_on_cpu}, + {"", "--diffusion-fa", "", true, ¶ms.diffusion_flash_attn}, + {"", "--canny", "", true, ¶ms.canny_preprocess}, + {"-v", "--verbos", "", true, ¶ms.verbose}, + {"", "--color", "", true, ¶ms.color}, + {"", "--chroma-disable-dit-mask", "", false, ¶ms.chroma_use_dit_mask}, + {"", "--chroma-enable-t5-mask", "", true, ¶ms.chroma_use_t5_mask}, + }; + + auto on_mode_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + const char* mode = argv[index]; + if (mode != NULL) { + int mode_found = -1; + for (int i = 0; i < MODE_COUNT; i++) { + if (!strcmp(mode, modes_str[i])) { + mode_found = i; + } + } + if (mode_found == -1) { + fprintf(stderr, + "error: invalid mode %s, must be one of [%s]\n", + mode, SD_ALL_MODES_STR); + exit(1); + } + params.mode = (SDMode)mode_found; + } + return 1; + }; + + auto on_type_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + const char* arg = argv[index]; + params.wtype = str_to_sd_type(arg); + if (params.wtype == SD_TYPE_COUNT) { + fprintf(stderr, "error: invalid weight format %s\n", + arg); + return -1; + } + return 1; + }; + + auto on_rng_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + const char* arg = argv[index]; + params.rng_type = str_to_rng_type(arg); + if (params.rng_type == RNG_TYPE_COUNT) { + fprintf(stderr, "error: invalid rng type %s\n", + arg); + return -1; + } + return 1; + }; + + auto on_schedule_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + const char* arg = argv[index]; + params.schedule = str_to_schedule(arg); + if (params.schedule == SCHEDULE_COUNT) { + fprintf(stderr, "error: invalid schedule %s\n", + arg); + return -1; + } + return 1; + }; + + auto on_sample_method_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + const char* arg = argv[index]; + params.sample_method = str_to_sample_method(arg); + if (params.sample_method == SAMPLE_METHOD_COUNT) { + fprintf(stderr, "error: invalid sample method %s\n", + arg); + return -1; + } + return 1; + }; + + auto on_seed_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + params.seed = std::stoll(argv[index]); + return 1; + }; + + auto on_help_arg = [&](int argc, const char** argv, int index) { + print_usage(argc, argv); + exit(0); + return 0; + }; + + auto on_skip_layers_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + std::string layers_str = argv[index]; + if (layers_str[0] != '[' || layers_str[layers_str.size() - 1] != ']') { + return -1; + } + + layers_str = layers_str.substr(1, layers_str.size() - 2); + + std::regex regex("[, ]+"); + std::sregex_token_iterator iter(layers_str.begin(), layers_str.end(), regex, -1); + std::sregex_token_iterator end; + std::vector tokens(iter, end); + std::vector layers; + for (const auto& token : tokens) { + try { + layers.push_back(std::stoi(token)); + } catch (const std::invalid_argument& e) { + return -1; + } + } + params.skip_layers = layers; + return 1; + }; + + auto on_ref_image_arg = [&](int argc, const char** argv, int index) { + if (++index >= argc) { + return -1; + } + params.ref_image_paths.push_back(argv[index]); + return 1; + }; + + options.manual_options = { + {"-M", "--mode", "", on_mode_arg}, + {"", "--type", "", on_type_arg}, + {"", "--rng", "", on_rng_arg}, + {"-s", "--seed", "", on_seed_arg}, + {"", "--sampling-method", "", on_sample_method_arg}, + {"", "--schedule", "", on_schedule_arg}, + {"", "--skip-layers", "", on_skip_layers_arg}, + {"-r", "--ref-image", "", on_ref_image_arg}, + {"-h", "--help", "", on_help_arg}, + }; + + if (!parse_options(argc, argv, options)) { print_usage(argc, argv); exit(1); } + if (params.n_threads <= 0) { params.n_threads = get_num_physical_cores(); } - if (params.mode != CONVERT && params.mode != IMG2VID && params.prompt.length() == 0) { + if (params.mode != CONVERT && params.mode != VID_GEN && params.prompt.length() == 0) { fprintf(stderr, "error: the following arguments are required: prompt\n"); print_usage(argc, argv); exit(1); @@ -712,18 +590,6 @@ void parse_args(int argc, const char** argv, SDParams& params) { exit(1); } - if ((params.mode == IMG2IMG || params.mode == IMG2VID) && params.input_path.length() == 0) { - fprintf(stderr, "error: when using the img2img/img2vid mode, the following arguments are required: init-img\n"); - print_usage(argc, argv); - exit(1); - } - - if (params.mode == EDIT && params.ref_image_paths.size() == 0) { - fprintf(stderr, "error: when using the edit mode, the following arguments are required: ref-image\n"); - print_usage(argc, argv); - exit(1); - } - if (params.output_path.length() == 0) { fprintf(stderr, "error: the following arguments are required: output_path\n"); print_usage(argc, argv); @@ -754,6 +620,11 @@ void parse_args(int argc, const char** argv, SDParams& params) { fprintf(stderr, "warning: --tensor-type-rules is currently supported only for conversion\n"); } + if (params.upscale_repeats < 1) { + fprintf(stderr, "error: upscale multiplier must be at least 1\n"); + exit(1); + } + if (params.seed < 0) { srand((int)time(NULL)); params.seed = rand(); @@ -804,8 +675,8 @@ std::string get_image_params(SDParams params, int64_t seed) { parameter_string += "Seed: " + std::to_string(seed) + ", "; parameter_string += "Size: " + std::to_string(params.width) + "x" + std::to_string(params.height) + ", "; parameter_string += "Model: " + sd_basename(params.model_path) + ", "; - parameter_string += "RNG: " + std::string(rng_type_to_str[params.rng_type]) + ", "; - parameter_string += "Sampler: " + std::string(sample_method_str[params.sample_method]); + parameter_string += "RNG: " + std::string(sd_rng_type_name(params.rng_type)) + ", "; + parameter_string += "Sampler: " + std::string(sd_sample_method_name(params.sample_method)); if (params.schedule == KARRAS) { parameter_string += " karras"; } @@ -899,7 +770,7 @@ int main(int argc, const char* argv[]) { } } - if (params.mode == IMG2VID) { + if (params.mode == VID_GEN) { fprintf(stderr, "SVD support is broken, do not use it!!!\n"); return 1; } @@ -910,7 +781,7 @@ int main(int argc, const char* argv[]) { uint8_t* mask_image_buffer = NULL; std::vector ref_images; - if (params.mode == IMG2IMG || params.mode == IMG2VID) { + if (params.input_path.size() > 0) { vae_decode_only = false; int c = 0; @@ -960,7 +831,7 @@ int main(int argc, const char* argv[]) { free(input_image_buffer); input_image_buffer = resized_image_buffer; } - } else if (params.mode == EDIT) { + } else if (params.ref_image_paths.size() > 0) { vae_decode_only = false; for (auto& path : params.ref_image_paths) { int c = 0; @@ -993,39 +864,48 @@ int main(int argc, const char* argv[]) { } } - sd_ctx_t* sd_ctx = new_sd_ctx(params.model_path.c_str(), - params.clip_l_path.c_str(), - params.clip_g_path.c_str(), - params.t5xxl_path.c_str(), - params.diffusion_model_path.c_str(), - params.vae_path.c_str(), - params.taesd_path.c_str(), - params.controlnet_path.c_str(), - params.lora_model_dir.c_str(), - params.embeddings_path.c_str(), - params.stacked_id_embeddings_path.c_str(), - vae_decode_only, - params.vae_tiling, - true, - params.n_threads, - params.wtype, - params.rng_type, - params.schedule, - params.clip_on_cpu, - params.control_net_cpu, - params.vae_on_cpu, - params.diffusion_flash_attn, - params.chroma_use_dit_mask, - params.chroma_use_t5_mask, - params.chroma_t5_mask_pad); + sd_ctx_params_t sd_ctx_params = { + params.model_path.c_str(), + params.clip_l_path.c_str(), + params.clip_g_path.c_str(), + params.t5xxl_path.c_str(), + params.diffusion_model_path.c_str(), + params.vae_path.c_str(), + params.taesd_path.c_str(), + params.control_net_path.c_str(), + params.lora_model_dir.c_str(), + params.embedding_dir.c_str(), + params.stacked_id_embed_dir.c_str(), + vae_decode_only, + params.vae_tiling, + true, + params.n_threads, + params.wtype, + params.rng_type, + params.schedule, + params.clip_on_cpu, + params.control_net_cpu, + params.vae_on_cpu, + params.diffusion_flash_attn, + params.chroma_use_dit_mask, + params.chroma_use_t5_mask, + params.chroma_t5_mask_pad, + }; + + sd_ctx_t* sd_ctx = new_sd_ctx(&sd_ctx_params); if (sd_ctx == NULL) { printf("new_sd_ctx_t failed\n"); return 1; } + sd_image_t input_image = {(uint32_t)params.width, + (uint32_t)params.height, + 3, + input_image_buffer}; + sd_image_t* control_image = NULL; - if (params.controlnet_path.size() > 0 && params.control_image_path.size() > 0) { + if (params.control_net_path.size() > 0 && params.control_image_path.size() > 0) { int c = 0; control_image_buffer = stbi_load(params.control_image_path.c_str(), ¶ms.width, ¶ms.height, &c, 3); if (control_image_buffer == NULL) { @@ -1061,107 +941,52 @@ int main(int argc, const char* argv[]) { mask_image_buffer}; sd_image_t* results; - if (params.mode == TXT2IMG) { - results = txt2img(sd_ctx, - params.prompt.c_str(), - params.negative_prompt.c_str(), - params.clip_skip, - guidance_params, - params.eta, - params.width, - params.height, - params.sample_method, - params.sample_steps, - params.seed, - params.batch_count, - control_image, - params.control_strength, - params.style_ratio, - params.normalize_input, - params.input_id_images_path.c_str()); - } else if (params.mode == IMG2IMG || params.mode == IMG2VID) { - sd_image_t input_image = {(uint32_t)params.width, - (uint32_t)params.height, - 3, - input_image_buffer}; - - if (params.mode == IMG2VID) { - results = img2vid(sd_ctx, - input_image, - params.width, - params.height, - params.video_frames, - params.motion_bucket_id, - params.fps, - params.augmentation_level, - guidance_params, - params.sample_method, - params.sample_steps, - params.strength, - params.seed); - if (results == NULL) { - printf("generate failed\n"); - free_sd_ctx(sd_ctx); - return 1; - } - size_t last = params.output_path.find_last_of("."); - std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path; - for (int i = 0; i < params.video_frames; i++) { - if (results[i].data == NULL) { - continue; - } - std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1) + ".png" : dummy_name + ".png"; - stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, - results[i].data, 0, get_image_params(params, params.seed + i).c_str()); - printf("save result image to '%s'\n", final_image_path.c_str()); - free(results[i].data); - results[i].data = NULL; - } - free(results); - free_sd_ctx(sd_ctx); - return 0; - } else { - results = img2img(sd_ctx, - input_image, - mask_image, - params.prompt.c_str(), - params.negative_prompt.c_str(), - params.clip_skip, - guidance_params, - params.eta, - params.width, - params.height, - params.sample_method, - params.sample_steps, - params.strength, - params.seed, - params.batch_count, - control_image, - params.control_strength, - params.style_ratio, - params.normalize_input, - params.input_id_images_path.c_str()); - } - } else { // EDIT - results = edit(sd_ctx, - ref_images.data(), - ref_images.size(), - params.prompt.c_str(), - params.negative_prompt.c_str(), - params.clip_skip, - guidance_params, - params.eta, - params.width, - params.height, - params.sample_method, - params.sample_steps, - params.seed, - params.batch_count, - control_image, - params.control_strength, - params.style_ratio, - params.normalize_input, - params.input_id_images_path.c_str()); + int expected_num_results = 1; + if (params.mode == IMG_GEN) { + sd_img_gen_params_t img_gen_params = { + params.prompt.c_str(), + params.negative_prompt.c_str(), + params.clip_skip, + guidance_params, + input_image, + ref_images.data(), + ref_images.size(), + mask_image, + params.width, + params.height, + params.sample_method, + params.sample_steps, + params.eta, + params.strength, + params.seed, + params.batch_count, + control_image, + params.control_strength, + params.style_ratio, + params.normalize_input, + params.input_id_images_path.c_str(), + }; + + results = generate_image(sd_ctx, &img_gen_params); + expected_num_results = params.batch_count; + } else if (params.mode == VID_GEN) { + sd_vid_gen_params_t vid_gen_params = { + input_image, + params.width, + params.height, + guidance_params, + params.sample_method, + params.sample_steps, + params.strength, + params.seed, + params.video_frames, + params.motion_bucket_id, + params.fps, + params.augmentation_level, + }; + + results = generate_video(sd_ctx, &vid_gen_params); + expected_num_results = params.video_frames; } if (results == NULL) { @@ -1218,7 +1043,7 @@ int main(int argc, const char* argv[]) { dummy_name += ext; ext = ".png"; } - for (int i = 0; i < params.batch_count; i++) { + for (int i = 0; i < expected_num_results; i++) { if (results[i].data == NULL) { continue; } diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index c6b873fad..43b027ee8 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -118,22 +118,6 @@ class StableDiffusionGGML { StableDiffusionGGML() = default; - StableDiffusionGGML(int n_threads, - bool vae_decode_only, - bool free_params_immediately, - std::string lora_model_dir, - rng_type_t rng_type) - : n_threads(n_threads), - vae_decode_only(vae_decode_only), - free_params_immediately(free_params_immediately), - lora_model_dir(lora_model_dir) { - if (rng_type == STD_DEFAULT_RNG) { - rng = std::make_shared(); - } else if (rng_type == CUDA_RNG) { - rng = std::make_shared(); - } - } - ~StableDiffusionGGML() { if (clip_backend != backend) { ggml_backend_free(clip_backend); @@ -147,27 +131,7 @@ class StableDiffusionGGML { ggml_backend_free(backend); } - bool load_from_file(const std::string& model_path, - const std::string& clip_l_path, - const std::string& clip_g_path, - const std::string& t5xxl_path, - const std::string& diffusion_model_path, - const std::string& vae_path, - const std::string control_net_path, - const std::string embeddings_path, - const std::string id_embeddings_path, - const std::string& taesd_path, - bool vae_tiling_, - ggml_type wtype, - schedule_t schedule, - bool clip_on_cpu, - bool control_net_cpu, - bool vae_on_cpu, - bool diffusion_flash_attn, - bool chroma_use_dit_mask, - bool chroma_use_t5_mask, - int chroma_t5_mask_pad) { - use_tiny_autoencoder = taesd_path.size() > 0; + void init_backend() { #ifdef SD_USE_CUDA LOG_DEBUG("Using CUDA backend"); backend = ggml_backend_cuda_init(0); @@ -203,62 +167,80 @@ class StableDiffusionGGML { LOG_DEBUG("Using CPU backend"); backend = ggml_backend_cpu_init(); } + } - ModelLoader model_loader; + bool init(const sd_ctx_params_t* sd_ctx_params) { + n_threads = sd_ctx_params->n_threads; + vae_decode_only = sd_ctx_params->vae_decode_only; + free_params_immediately = sd_ctx_params->free_params_immediately; + lora_model_dir = SAFE_STR(sd_ctx_params->lora_model_dir); + use_tiny_autoencoder = taesd_path.size() > 0; + vae_tiling = sd_ctx_params->vae_tiling; - vae_tiling = vae_tiling_; + if (sd_ctx_params->rng_type == STD_DEFAULT_RNG) { + rng = std::make_shared(); + } else if (sd_ctx_params->rng_type == CUDA_RNG) { + rng = std::make_shared(); + } + + init_backend(); + + ModelLoader model_loader; - if (model_path.size() > 0) { - LOG_INFO("loading model from '%s'", model_path.c_str()); - if (!model_loader.init_from_file(model_path)) { - LOG_ERROR("init model loader from file failed: '%s'", model_path.c_str()); + if (strlen(SAFE_STR(sd_ctx_params->model_path)) > 0) { + LOG_INFO("loading model from '%s'", sd_ctx_params->model_path); + if (!model_loader.init_from_file(sd_ctx_params->model_path)) { + LOG_ERROR("init model loader from file failed: '%s'", sd_ctx_params->model_path); } } - if (diffusion_model_path.size() > 0) { - LOG_INFO("loading diffusion model from '%s'", diffusion_model_path.c_str()); - if (!model_loader.init_from_file(diffusion_model_path, "model.diffusion_model.")) { - LOG_WARN("loading diffusion model from '%s' failed", diffusion_model_path.c_str()); + if (strlen(SAFE_STR(sd_ctx_params->diffusion_model_path)) > 0) { + LOG_INFO("loading diffusion model from '%s'", sd_ctx_params->diffusion_model_path); + if (!model_loader.init_from_file(sd_ctx_params->diffusion_model_path, "model.diffusion_model.")) { + LOG_WARN("loading diffusion model from '%s' failed", sd_ctx_params->diffusion_model_path); } } bool is_unet = model_loader.model_is_unet(); - if (clip_l_path.size() > 0) { - LOG_INFO("loading clip_l from '%s'", clip_l_path.c_str()); - if (!model_loader.init_from_file(clip_l_path, is_unet ? "cond_stage_model.transformer." : "text_encoders.clip_l.transformer.")) { - LOG_WARN("loading clip_l from '%s' failed", clip_l_path.c_str()); + if (strlen(SAFE_STR(sd_ctx_params->clip_l_path)) > 0) { + LOG_INFO("loading clip_l from '%s'", sd_ctx_params->clip_l_path); + std::string prefix = is_unet ? "cond_stage_model.transformer." : "text_encoders.clip_l.transformer."; + if (!model_loader.init_from_file(sd_ctx_params->clip_l_path, prefix)) { + LOG_WARN("loading clip_l from '%s' failed", sd_ctx_params->clip_l_path); } } - if (clip_g_path.size() > 0) { - LOG_INFO("loading clip_g from '%s'", clip_g_path.c_str()); - if (!model_loader.init_from_file(clip_g_path, is_unet ? "cond_stage_model.1.transformer." : "text_encoders.clip_g.transformer.")) { - LOG_WARN("loading clip_g from '%s' failed", clip_g_path.c_str()); + if (strlen(SAFE_STR(sd_ctx_params->clip_g_path)) > 0) { + LOG_INFO("loading clip_g from '%s'", sd_ctx_params->clip_g_path); + std::string prefix = is_unet ? "cond_stage_model.1.transformer." : "text_encoders.clip_g.transformer."; + if (!model_loader.init_from_file(sd_ctx_params->clip_g_path, prefix)) { + LOG_WARN("loading clip_g from '%s' failed", sd_ctx_params->clip_g_path); } } - if (t5xxl_path.size() > 0) { - LOG_INFO("loading t5xxl from '%s'", t5xxl_path.c_str()); - if (!model_loader.init_from_file(t5xxl_path, "text_encoders.t5xxl.transformer.")) { - LOG_WARN("loading t5xxl from '%s' failed", t5xxl_path.c_str()); + if (strlen(SAFE_STR(sd_ctx_params->t5xxl_path)) > 0) { + LOG_INFO("loading t5xxl from '%s'", sd_ctx_params->t5xxl_path); + if (!model_loader.init_from_file(sd_ctx_params->t5xxl_path, "text_encoders.t5xxl.transformer.")) { + LOG_WARN("loading t5xxl from '%s' failed", sd_ctx_params->t5xxl_path); } } - if (vae_path.size() > 0) { - LOG_INFO("loading vae from '%s'", vae_path.c_str()); - if (!model_loader.init_from_file(vae_path, "vae.")) { - LOG_WARN("loading vae from '%s' failed", vae_path.c_str()); + if (strlen(SAFE_STR(sd_ctx_params->vae_path)) > 0) { + LOG_INFO("loading vae from '%s'", sd_ctx_params->vae_path); + if (!model_loader.init_from_file(sd_ctx_params->vae_path, "vae.")) { + LOG_WARN("loading vae from '%s' failed", sd_ctx_params->vae_path); } } version = model_loader.get_sd_version(); if (version == VERSION_COUNT) { - LOG_ERROR("get sd version from file failed: '%s'", model_path.c_str()); + LOG_ERROR("get sd version from file failed: '%s'", SAFE_STR(sd_ctx_params->model_path)); return false; } LOG_INFO("Version: %s ", model_version_to_str[version]); + ggml_type wtype = (ggml_type)sd_ctx_params->wtype; if (wtype == GGML_TYPE_COUNT) { model_wtype = model_loader.get_sd_wtype(); if (model_wtype == GGML_TYPE_COUNT) { @@ -300,7 +282,7 @@ class StableDiffusionGGML { if (sd_version_is_sdxl(version)) { scale_factor = 0.13025f; - if (vae_path.size() == 0 && taesd_path.size() == 0) { + if (strlen(SAFE_STR(sd_ctx_params->vae_path)) == 0 && strlen(SAFE_STR(sd_ctx_params->taesd_path)) == 0) { LOG_WARN( "!!!It looks like you are using SDXL model. " "If you find that the generated images are completely black, " @@ -314,6 +296,8 @@ class StableDiffusionGGML { // TODO: shift_factor } + bool clip_on_cpu = sd_ctx_params->keep_clip_on_cpu; + if (version == VERSION_SVD) { clip_vision = std::make_shared(backend, model_loader.tensor_storages_types); clip_vision->alloc_params_buffer(); @@ -341,11 +325,11 @@ class StableDiffusionGGML { LOG_INFO("CLIP: Using CPU backend"); clip_backend = ggml_backend_cpu_init(); } - if (diffusion_flash_attn) { + if (sd_ctx_params->diffusion_flash_attn) { LOG_INFO("Using flash attention in the diffusion model"); } if (sd_version_is_sd3(version)) { - if (diffusion_flash_attn) { + if (sd_ctx_params->diffusion_flash_attn) { LOG_WARN("flash attention in this diffusion model is currently unsupported!"); } cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); @@ -359,18 +343,36 @@ class StableDiffusionGGML { } } if (is_chroma) { - cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, -1, chroma_use_t5_mask, chroma_t5_mask_pad); + cond_stage_model = std::make_shared(clip_backend, + model_loader.tensor_storages_types, + -1, + sd_ctx_params->chroma_use_t5_mask, + sd_ctx_params->chroma_t5_mask_pad); } else { cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types); } - diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, version, diffusion_flash_attn, chroma_use_dit_mask); + diffusion_model = std::make_shared(backend, + model_loader.tensor_storages_types, + version, + sd_ctx_params->diffusion_flash_attn, + sd_ctx_params->chroma_use_dit_mask); } else { - if (id_embeddings_path.find("v2") != std::string::npos) { - cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, embeddings_path, version, PM_VERSION_2); + if (strstr(SAFE_STR(sd_ctx_params->stacked_id_embed_dir), "v2")) { + cond_stage_model = std::make_shared(clip_backend, + model_loader.tensor_storages_types, + SAFE_STR(sd_ctx_params->embedding_dir), + version, + PM_VERSION_2); } else { - cond_stage_model = std::make_shared(clip_backend, model_loader.tensor_storages_types, embeddings_path, version); + cond_stage_model = std::make_shared(clip_backend, + model_loader.tensor_storages_types, + SAFE_STR(sd_ctx_params->embedding_dir), + version); } - diffusion_model = std::make_shared(backend, model_loader.tensor_storages_types, version, diffusion_flash_attn); + diffusion_model = std::make_shared(backend, + model_loader.tensor_storages_types, + version, + sd_ctx_params->diffusion_flash_attn); } cond_stage_model->alloc_params_buffer(); @@ -380,23 +382,32 @@ class StableDiffusionGGML { diffusion_model->get_param_tensors(tensors); if (!use_tiny_autoencoder) { - if (vae_on_cpu && !ggml_backend_is_cpu(backend)) { + if (sd_ctx_params->keep_vae_on_cpu && !ggml_backend_is_cpu(backend)) { LOG_INFO("VAE Autoencoder: Using CPU backend"); vae_backend = ggml_backend_cpu_init(); } else { vae_backend = backend; } - first_stage_model = std::make_shared(vae_backend, model_loader.tensor_storages_types, "first_stage_model", vae_decode_only, false, version); + first_stage_model = std::make_shared(vae_backend, + model_loader.tensor_storages_types, + "first_stage_model", + vae_decode_only, + false, + version); first_stage_model->alloc_params_buffer(); first_stage_model->get_param_tensors(tensors, "first_stage_model"); } else { - tae_first_stage = std::make_shared(backend, model_loader.tensor_storages_types, "decoder.layers", vae_decode_only, version); + tae_first_stage = std::make_shared(backend, + model_loader.tensor_storages_types, + "decoder.layers", + vae_decode_only, + version); } // first_stage_model->get_param_tensors(tensors, "first_stage_model."); - if (control_net_path.size() > 0) { + if (strlen(SAFE_STR(sd_ctx_params->control_net_path)) > 0) { ggml_backend_t controlnet_backend = NULL; - if (control_net_cpu && !ggml_backend_is_cpu(backend)) { + if (sd_ctx_params->keep_control_net_on_cpu && !ggml_backend_is_cpu(backend)) { LOG_DEBUG("ControlNet: Using CPU backend"); controlnet_backend = ggml_backend_cpu_init(); } else { @@ -405,21 +416,21 @@ class StableDiffusionGGML { control_net = std::make_shared(controlnet_backend, model_loader.tensor_storages_types, version); } - if (id_embeddings_path.find("v2") != std::string::npos) { + if (strstr(SAFE_STR(sd_ctx_params->stacked_id_embed_dir), "v2")) { pmid_model = std::make_shared(backend, model_loader.tensor_storages_types, "pmid", version, PM_VERSION_2); LOG_INFO("using PhotoMaker Version 2"); } else { pmid_model = std::make_shared(backend, model_loader.tensor_storages_types, "pmid", version); } - if (id_embeddings_path.size() > 0) { - pmid_lora = std::make_shared(backend, id_embeddings_path, ""); + if (strlen(SAFE_STR(sd_ctx_params->stacked_id_embed_dir)) > 0) { + pmid_lora = std::make_shared(backend, sd_ctx_params->stacked_id_embed_dir, ""); if (!pmid_lora->load_from_file(true)) { - LOG_WARN("load photomaker lora tensors from %s failed", id_embeddings_path.c_str()); + LOG_WARN("load photomaker lora tensors from %s failed", sd_ctx_params->stacked_id_embed_dir); return false; } - LOG_INFO("loading stacked ID embedding (PHOTOMAKER) model file from '%s'", id_embeddings_path.c_str()); - if (!model_loader.init_from_file(id_embeddings_path, "pmid.")) { - LOG_WARN("loading stacked ID embedding from '%s' failed", id_embeddings_path.c_str()); + LOG_INFO("loading stacked ID embedding (PHOTOMAKER) model file from '%s'", sd_ctx_params->stacked_id_embed_dir); + if (!model_loader.init_from_file(sd_ctx_params->stacked_id_embed_dir, "pmid.")) { + LOG_WARN("loading stacked ID embedding from '%s' failed", sd_ctx_params->stacked_id_embed_dir); } else { stacked_id = true; } @@ -491,7 +502,7 @@ class StableDiffusionGGML { } size_t control_net_params_mem_size = 0; if (control_net) { - if (!control_net->load_from_file(control_net_path)) { + if (!control_net->load_from_file(SAFE_STR(sd_ctx_params->control_net_path))) { return false; } control_net_params_mem_size = control_net->get_params_buffer_size(); @@ -547,7 +558,7 @@ class StableDiffusionGGML { } int64_t t1 = ggml_time_ms(); - LOG_INFO("loading model from '%s' completed, taking %.2fs", model_path.c_str(), (t1 - t0) * 1.0f / 1000); + LOG_INFO("loading model from '%s' completed, taking %.2fs", SAFE_STR(sd_ctx_params->model_path), (t1 - t0) * 1.0f / 1000); // check is_using_v_parameterization_for_sd2 @@ -592,8 +603,8 @@ class StableDiffusionGGML { LOG_INFO("running in eps-prediction mode"); } - if (schedule != DEFAULT) { - switch (schedule) { + if (sd_ctx_params->schedule != DEFAULT) { + switch (sd_ctx_params->schedule) { case DISCRETE: LOG_INFO("running with discrete schedule"); denoiser->schedule = std::make_shared(); @@ -620,7 +631,7 @@ class StableDiffusionGGML { // Don't touch anything. break; default: - LOG_ERROR("Unknown schedule %i", schedule); + LOG_ERROR("Unknown schedule %i", sd_ctx_params->schedule); abort(); } } @@ -1185,80 +1196,301 @@ class StableDiffusionGGML { /*================================================= SD API ==================================================*/ +#define NONE_STR "NONE" + +const char* sd_type_name(enum sd_type_t type) { + return ggml_type_name((ggml_type)type); +} + +enum sd_type_t str_to_sd_type(const char* str) { + for (int i = 0; i < SD_TYPE_COUNT; i++) { + auto trait = ggml_get_type_traits((ggml_type)i); + if (!strcmp(str, trait->type_name)) { + return (enum sd_type_t)i; + } + } + return SD_TYPE_COUNT; +} + +const char* rng_type_to_str[] = { + "std_default", + "cuda", +}; + +const char* sd_rng_type_name(enum rng_type_t rng_type) { + if (rng_type < RNG_TYPE_COUNT) { + return rng_type_to_str[rng_type]; + } + return NONE_STR; +} + +enum rng_type_t str_to_rng_type(const char* str) { + for (int i = 0; i < RNG_TYPE_COUNT; i++) { + if (!strcmp(str, rng_type_to_str[i])) { + return (enum rng_type_t)i; + } + } + return RNG_TYPE_COUNT; +} + +const char* sample_method_to_str[] = { + "euler_a", + "euler", + "heun", + "dpm2", + "dpm++2s_a", + "dpm++2m", + "dpm++2mv2", + "ipndm", + "ipndm_v", + "lcm", + "ddim_trailing", + "tcd", +}; + +const char* sd_sample_method_name(enum sample_method_t sample_method) { + if (sample_method < SAMPLE_METHOD_COUNT) { + return sample_method_to_str[sample_method]; + } + return NONE_STR; +} + +enum sample_method_t str_to_sample_method(const char* str) { + for (int i = 0; i < SAMPLE_METHOD_COUNT; i++) { + if (!strcmp(str, sample_method_to_str[i])) { + return (enum sample_method_t)i; + } + } + return SAMPLE_METHOD_COUNT; +} + +const char* schedule_to_str[] = { + "default", + "discrete", + "karras", + "exponential", + "ays", + "gits", +}; + +const char* sd_schedule_name(enum schedule_t schedule) { + if (schedule < SCHEDULE_COUNT) { + return schedule_to_str[schedule]; + } + return NONE_STR; +} + +enum schedule_t str_to_schedule(const char* str) { + for (int i = 0; i < SCHEDULE_COUNT; i++) { + if (!strcmp(str, schedule_to_str[i])) { + return (enum schedule_t)i; + } + } + return SCHEDULE_COUNT; +} + +void sd_ctx_params_init(sd_ctx_params_t* sd_ctx_params) { + memset((void*)sd_ctx_params, 0, sizeof(sd_ctx_params_t)); + sd_ctx_params->vae_decode_only = true; + sd_ctx_params->vae_tiling = false; + sd_ctx_params->free_params_immediately = true; + sd_ctx_params->n_threads = get_num_physical_cores(); + sd_ctx_params->wtype = SD_TYPE_COUNT; + sd_ctx_params->rng_type = CUDA_RNG; + sd_ctx_params->schedule = DEFAULT; + sd_ctx_params->keep_clip_on_cpu = false; + sd_ctx_params->keep_control_net_on_cpu = false; + sd_ctx_params->keep_vae_on_cpu = false; + sd_ctx_params->diffusion_flash_attn = false; + sd_ctx_params->chroma_use_dit_mask = true; + sd_ctx_params->chroma_use_t5_mask = false; + sd_ctx_params->chroma_t5_mask_pad = 1; +} + +char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params) { + char* buf = (char*)malloc(4096); + if (!buf) + return NULL; + buf[0] = '\0'; + + snprintf(buf + strlen(buf), 4096 - strlen(buf), + "model_path: %s\n" + "clip_l_path: %s\n" + "clip_g_path: %s\n" + "t5xxl_path: %s\n" + "diffusion_model_path: %s\n" + "vae_path: %s\n" + "taesd_path: %s\n" + "control_net_path: %s\n" + "lora_model_dir: %s\n" + "embedding_dir: %s\n" + "stacked_id_embed_dir: %s\n" + "vae_decode_only: %s\n" + "vae_tiling: %s\n" + "free_params_immediately: %s\n" + "n_threads: %d\n" + "wtype: %s\n" + "rng_type: %s\n" + "schedule: %s\n" + "keep_clip_on_cpu: %s\n" + "keep_control_net_on_cpu: %s\n" + "keep_vae_on_cpu: %s\n" + "diffusion_flash_attn: %s\n" + "chroma_use_dit_mask: %s\n" + "chroma_use_t5_mask: %s\n" + "chroma_t5_mask_pad: %d\n", + SAFE_STR(sd_ctx_params->model_path), + SAFE_STR(sd_ctx_params->clip_l_path), + SAFE_STR(sd_ctx_params->clip_g_path), + SAFE_STR(sd_ctx_params->t5xxl_path), + SAFE_STR(sd_ctx_params->diffusion_model_path), + SAFE_STR(sd_ctx_params->vae_path), + SAFE_STR(sd_ctx_params->taesd_path), + SAFE_STR(sd_ctx_params->control_net_path), + SAFE_STR(sd_ctx_params->lora_model_dir), + SAFE_STR(sd_ctx_params->embedding_dir), + SAFE_STR(sd_ctx_params->stacked_id_embed_dir), + BOOL_STR(sd_ctx_params->vae_decode_only), + BOOL_STR(sd_ctx_params->vae_tiling), + BOOL_STR(sd_ctx_params->free_params_immediately), + sd_ctx_params->n_threads, + sd_type_name(sd_ctx_params->wtype), + sd_rng_type_name(sd_ctx_params->rng_type), + sd_schedule_name(sd_ctx_params->schedule), + BOOL_STR(sd_ctx_params->keep_clip_on_cpu), + BOOL_STR(sd_ctx_params->keep_control_net_on_cpu), + BOOL_STR(sd_ctx_params->keep_vae_on_cpu), + BOOL_STR(sd_ctx_params->diffusion_flash_attn), + BOOL_STR(sd_ctx_params->chroma_use_dit_mask), + BOOL_STR(sd_ctx_params->chroma_use_t5_mask), + sd_ctx_params->chroma_t5_mask_pad); + + return buf; +} + +void sd_img_gen_params_init(sd_img_gen_params_t* sd_img_gen_params) { + memset((void*)sd_img_gen_params, 0, sizeof(sd_img_gen_params_t)); + sd_img_gen_params->clip_skip = -1; + sd_img_gen_params->guidance.txt_cfg = 7.0f; + sd_img_gen_params->guidance.min_cfg = 1.0f; + sd_img_gen_params->guidance.img_cfg = INFINITY; + sd_img_gen_params->guidance.distilled_guidance = 3.5f; + sd_img_gen_params->guidance.slg.layer_count = 0; + sd_img_gen_params->guidance.slg.layer_start = 0.01f; + sd_img_gen_params->guidance.slg.layer_end = 0.2f; + sd_img_gen_params->guidance.slg.scale = 0.f; + sd_img_gen_params->ref_images_count = 0; + sd_img_gen_params->width = 512; + sd_img_gen_params->height = 512; + sd_img_gen_params->sample_method = EULER_A; + sd_img_gen_params->sample_steps = 20; + sd_img_gen_params->eta = 0.f; + sd_img_gen_params->strength = 0.75f; + sd_img_gen_params->seed = -1; + sd_img_gen_params->batch_count = 1; + sd_img_gen_params->control_strength = 0.9f; + sd_img_gen_params->style_strength = 20.f; + sd_img_gen_params->normalize_input = false; +} + +char* sd_img_gen_params_to_str(const sd_img_gen_params_t* sd_img_gen_params) { + char* buf = (char*)malloc(4096); + if (!buf) + return NULL; + buf[0] = '\0'; + + snprintf(buf + strlen(buf), 4096 - strlen(buf), + "prompt: %s\n" + "negative_prompt: %s\n" + "clip_skip: %d\n" + "txt_cfg: %.2f\n" + "img_cfg: %.2f\n" + "min_cfg: %.2f\n" + "distilled_guidance: %.2f\n" + "slg.layer_count: %zu\n" + "slg.layer_start: %.2f\n" + "slg.layer_end: %.2f\n" + "slg.scale: %.2f\n" + "width: %d\n" + "height: %d\n" + "sample_method: %s\n" + "sample_steps: %d\n" + "eta: %.2f\n" + "strength: %.2f\n" + "seed: %" PRId64 + "\n" + "batch_count: %d\n" + "ref_images_count: %d\n" + "control_strength: %.2f\n" + "style_strength: %.2f\n" + "normalize_input: %s\n" + "input_id_images_path: %s\n", + SAFE_STR(sd_img_gen_params->prompt), + SAFE_STR(sd_img_gen_params->negative_prompt), + sd_img_gen_params->clip_skip, + sd_img_gen_params->guidance.txt_cfg, + sd_img_gen_params->guidance.img_cfg, + sd_img_gen_params->guidance.min_cfg, + sd_img_gen_params->guidance.distilled_guidance, + sd_img_gen_params->guidance.slg.layer_count, + sd_img_gen_params->guidance.slg.layer_start, + sd_img_gen_params->guidance.slg.layer_end, + sd_img_gen_params->guidance.slg.scale, + sd_img_gen_params->width, + sd_img_gen_params->height, + sd_sample_method_name(sd_img_gen_params->sample_method), + sd_img_gen_params->sample_steps, + sd_img_gen_params->eta, + sd_img_gen_params->strength, + sd_img_gen_params->seed, + sd_img_gen_params->batch_count, + sd_img_gen_params->ref_images_count, + sd_img_gen_params->control_strength, + sd_img_gen_params->style_strength, + BOOL_STR(sd_img_gen_params->normalize_input), + SAFE_STR(sd_img_gen_params->input_id_images_path)); + + return buf; +} + +void sd_vid_gen_params_init(sd_vid_gen_params_t* sd_vid_gen_params) { + memset((void*)sd_vid_gen_params, 0, sizeof(sd_vid_gen_params_t)); + sd_vid_gen_params->guidance.txt_cfg = 7.0f; + sd_vid_gen_params->guidance.min_cfg = 1.0f; + sd_vid_gen_params->guidance.img_cfg = INFINITY; + sd_vid_gen_params->guidance.distilled_guidance = 3.5f; + sd_vid_gen_params->guidance.slg.layer_count = 0; + sd_vid_gen_params->guidance.slg.layer_start = 0.01f; + sd_vid_gen_params->guidance.slg.layer_end = 0.2f; + sd_vid_gen_params->guidance.slg.scale = 0.f; + sd_vid_gen_params->width = 512; + sd_vid_gen_params->height = 512; + sd_vid_gen_params->sample_method = EULER_A; + sd_vid_gen_params->sample_steps = 20; + sd_vid_gen_params->strength = 0.75f; + sd_vid_gen_params->seed = -1; + sd_vid_gen_params->video_frames = 6; + sd_vid_gen_params->motion_bucket_id = 127; + sd_vid_gen_params->fps = 6; + sd_vid_gen_params->augmentation_level = 0.f; +} + struct sd_ctx_t { StableDiffusionGGML* sd = NULL; }; -sd_ctx_t* new_sd_ctx(const char* model_path_c_str, - const char* clip_l_path_c_str, - const char* clip_g_path_c_str, - const char* t5xxl_path_c_str, - const char* diffusion_model_path_c_str, - const char* vae_path_c_str, - const char* taesd_path_c_str, - const char* control_net_path_c_str, - const char* lora_model_dir_c_str, - const char* embed_dir_c_str, - const char* id_embed_dir_c_str, - bool vae_decode_only, - bool vae_tiling, - bool free_params_immediately, - int n_threads, - enum sd_type_t wtype, - enum rng_type_t rng_type, - enum schedule_t s, - bool keep_clip_on_cpu, - bool keep_control_net_cpu, - bool keep_vae_on_cpu, - bool diffusion_flash_attn, - bool chroma_use_dit_mask, - bool chroma_use_t5_mask, - int chroma_t5_mask_pad) { +sd_ctx_t* new_sd_ctx(const sd_ctx_params_t* sd_ctx_params) { sd_ctx_t* sd_ctx = (sd_ctx_t*)malloc(sizeof(sd_ctx_t)); if (sd_ctx == NULL) { return NULL; } - std::string model_path(model_path_c_str); - std::string clip_l_path(clip_l_path_c_str); - std::string clip_g_path(clip_g_path_c_str); - std::string t5xxl_path(t5xxl_path_c_str); - std::string diffusion_model_path(diffusion_model_path_c_str); - std::string vae_path(vae_path_c_str); - std::string taesd_path(taesd_path_c_str); - std::string control_net_path(control_net_path_c_str); - std::string embd_path(embed_dir_c_str); - std::string id_embd_path(id_embed_dir_c_str); - std::string lora_model_dir(lora_model_dir_c_str); - - sd_ctx->sd = new StableDiffusionGGML(n_threads, - vae_decode_only, - free_params_immediately, - lora_model_dir, - rng_type); + + sd_ctx->sd = new StableDiffusionGGML(); if (sd_ctx->sd == NULL) { return NULL; } - if (!sd_ctx->sd->load_from_file(model_path, - clip_l_path, - clip_g_path, - t5xxl_path_c_str, - diffusion_model_path, - vae_path, - control_net_path, - embd_path, - id_embd_path, - taesd_path, - vae_tiling, - (ggml_type)wtype, - s, - keep_clip_on_cpu, - keep_control_net_cpu, - keep_vae_on_cpu, - diffusion_flash_attn, - chroma_use_dit_mask, - chroma_use_t5_mask, - chroma_t5_mask_pad)) { + if (!sd_ctx->sd->init(sd_ctx_params)) { delete sd_ctx->sd; sd_ctx->sd = NULL; free(sd_ctx); @@ -1275,28 +1507,28 @@ void free_sd_ctx(sd_ctx_t* sd_ctx) { free(sd_ctx); } -sd_image_t* generate_image(sd_ctx_t* sd_ctx, - struct ggml_context* work_ctx, - ggml_tensor* init_latent, - std::string prompt, - std::string negative_prompt, - int clip_skip, - sd_guidance_params_t guidance, - float eta, - int width, - int height, - enum sample_method_t sample_method, - const std::vector& sigmas, - int64_t seed, - int batch_count, - const sd_image_t* control_cond, - float control_strength, - float style_ratio, - bool normalize_input, - std::string input_id_images_path, - std::vector ref_latents, - ggml_tensor* concat_latent = NULL, - ggml_tensor* denoise_mask = NULL) { +sd_image_t* generate_image_internal(sd_ctx_t* sd_ctx, + struct ggml_context* work_ctx, + ggml_tensor* init_latent, + std::string prompt, + std::string negative_prompt, + int clip_skip, + sd_guidance_params_t guidance, + float eta, + int width, + int height, + enum sample_method_t sample_method, + const std::vector& sigmas, + int64_t seed, + int batch_count, + const sd_image_t* control_cond, + float control_strength, + float style_ratio, + bool normalize_input, + std::string input_id_images_path, + std::vector ref_latents, + ggml_tensor* concat_latent = NULL, + ggml_tensor* denoise_mask = NULL) { if (seed < 0) { // Generally, when using the provided command line, the seed is always >0. // However, to prevent potential issues if 'stable-diffusion.cpp' is invoked as a library @@ -1639,25 +1871,11 @@ ggml_tensor* generate_init_latent(sd_ctx_t* sd_ctx, return init_latent; } -sd_image_t* txt2img(sd_ctx_t* sd_ctx, - const char* prompt_c_str, - const char* negative_prompt_c_str, - int clip_skip, - sd_guidance_params_t guidance, - float eta, - int width, - int height, - enum sample_method_t sample_method, - int sample_steps, - int64_t seed, - int batch_count, - const sd_image_t* control_cond, - float control_strength, - float style_ratio, - bool normalize_input, - const char* input_id_images_path_c_str) { - LOG_DEBUG("txt2img %dx%d", width, height); - if (sd_ctx == NULL) { +sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_gen_params) { + int width = sd_img_gen_params->width; + int height = sd_img_gen_params->height; + LOG_DEBUG("generate_image %dx%d", width, height); + if (sd_ctx == NULL || sd_img_gen_params == NULL) { return NULL; } @@ -1672,94 +1890,9 @@ sd_image_t* txt2img(sd_ctx_t* sd_ctx, if (sd_ctx->sd->stacked_id) { params.mem_size += static_cast(10 * 1024 * 1024); // 10 MB } - params.mem_size += width * height * 3 * sizeof(float); - params.mem_size *= batch_count; - params.mem_buffer = NULL; - params.no_alloc = false; - // LOG_DEBUG("mem_size %u ", params.mem_size); - - struct ggml_context* work_ctx = ggml_init(params); - if (!work_ctx) { - LOG_ERROR("ggml_init() failed"); - return NULL; - } - - size_t t0 = ggml_time_ms(); - - std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); - - if (sd_version_is_inpaint(sd_ctx->sd->version)) { - LOG_WARN("This is an inpainting model, this should only be used in img2img mode with a mask"); - } - - ggml_tensor* init_latent = generate_init_latent(sd_ctx, work_ctx, width, height); - - sd_image_t* result_images = generate_image(sd_ctx, - work_ctx, - init_latent, - prompt_c_str, - negative_prompt_c_str, - clip_skip, - guidance, - eta, - width, - height, - sample_method, - sigmas, - seed, - batch_count, - control_cond, - control_strength, - style_ratio, - normalize_input, - input_id_images_path_c_str, - {}); - - size_t t1 = ggml_time_ms(); - - LOG_INFO("txt2img completed in %.2fs", (t1 - t0) * 1.0f / 1000); - - return result_images; -} - -sd_image_t* img2img(sd_ctx_t* sd_ctx, - sd_image_t init_image, - sd_image_t mask, - const char* prompt_c_str, - const char* negative_prompt_c_str, - int clip_skip, - sd_guidance_params_t guidance, - float eta, - int width, - int height, - sample_method_t sample_method, - int sample_steps, - float strength, - int64_t seed, - int batch_count, - const sd_image_t* control_cond, - float control_strength, - float style_ratio, - bool normalize_input, - const char* input_id_images_path_c_str) { - LOG_DEBUG("img2img %dx%d", width, height); - if (sd_ctx == NULL) { - return NULL; - } - - struct ggml_init_params params; - params.mem_size = static_cast(10 * 1024 * 1024); // 10 MB - if (sd_version_is_sd3(sd_ctx->sd->version)) { - params.mem_size *= 2; - } - if (sd_version_is_flux(sd_ctx->sd->version)) { - params.mem_size *= 3; - } - if (sd_ctx->sd->stacked_id) { - params.mem_size += static_cast(10 * 1024 * 1024); // 10 MB - } params.mem_size += width * height * 3 * sizeof(float) * 3; - params.mem_size *= batch_count; + params.mem_size += width * height * 3 * sizeof(float) * 3 * sd_img_gen_params->ref_images_count; + params.mem_size *= sd_img_gen_params->batch_count; params.mem_buffer = NULL; params.no_alloc = false; // LOG_DEBUG("mem_size %u ", params.mem_size); @@ -1770,155 +1903,197 @@ sd_image_t* img2img(sd_ctx_t* sd_ctx, return NULL; } - size_t t0 = ggml_time_ms(); - + int64_t seed = sd_img_gen_params->seed; if (seed < 0) { srand((int)time(NULL)); seed = rand(); } sd_ctx->sd->rng->manual_seed(seed); - ggml_tensor* init_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 3, 1); - ggml_tensor* mask_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 1, 1); + size_t t0 = ggml_time_ms(); - sd_mask_to_tensor(mask.data, mask_img); + ggml_tensor* init_latent = NULL; + ggml_tensor* concat_latent = NULL; + ggml_tensor* denoise_mask = NULL; + std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sd_img_gen_params->sample_steps); - sd_image_to_tensor(init_image.data, init_img); + if (sd_img_gen_params->init_image.data) { + LOG_INFO("IMG2IMG"); - ggml_tensor* concat_latent; - ggml_tensor* denoise_mask = NULL; + size_t t_enc = static_cast(sd_img_gen_params->sample_steps * sd_img_gen_params->strength); + if (t_enc == sd_img_gen_params->sample_steps) + t_enc--; + LOG_INFO("target t_enc is %zu steps", t_enc); + std::vector sigma_sched; + sigma_sched.assign(sigmas.begin() + sd_img_gen_params->sample_steps - t_enc - 1, sigmas.end()); + sigmas = sigma_sched; - if (sd_version_is_inpaint(sd_ctx->sd->version)) { - int64_t mask_channels = 1; - if (sd_ctx->sd->version == VERSION_FLUX_FILL) { - mask_channels = 8 * 8; // flatten the whole mask - } - ggml_tensor* masked_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 3, 1); - sd_apply_mask(init_img, mask_img, masked_img); - ggml_tensor* masked_latent = NULL; - if (!sd_ctx->sd->use_tiny_autoencoder) { - ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); - masked_latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); - } else { - masked_latent = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); - } - concat_latent = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, masked_latent->ne[0], masked_latent->ne[1], mask_channels + masked_latent->ne[2], 1); - for (int ix = 0; ix < masked_latent->ne[0]; ix++) { - for (int iy = 0; iy < masked_latent->ne[1]; iy++) { - int mx = ix * 8; - int my = iy * 8; - if (sd_ctx->sd->version == VERSION_FLUX_FILL) { - for (int k = 0; k < masked_latent->ne[2]; k++) { - float v = ggml_tensor_get_f32(masked_latent, ix, iy, k); - ggml_tensor_set_f32(concat_latent, v, ix, iy, k); - } - // "Encode" 8x8 mask chunks into a flattened 1x64 vector, and concatenate to masked image - for (int x = 0; x < 8; x++) { - for (int y = 0; y < 8; y++) { - float m = ggml_tensor_get_f32(mask_img, mx + x, my + y); - // TODO: check if the way the mask is flattened is correct (is it supposed to be x*8+y or x+8*y?) - // python code was using "b (h 8) (w 8) -> b (8 8) h w" - ggml_tensor_set_f32(concat_latent, m, ix, iy, masked_latent->ne[2] + x * 8 + y); + ggml_tensor* init_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 3, 1); + ggml_tensor* mask_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 1, 1); + + sd_mask_to_tensor(sd_img_gen_params->mask_image.data, mask_img); + sd_image_to_tensor(sd_img_gen_params->init_image.data, init_img); + + if (sd_version_is_inpaint(sd_ctx->sd->version)) { + int64_t mask_channels = 1; + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + mask_channels = 8 * 8; // flatten the whole mask + } + ggml_tensor* masked_img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width, height, 3, 1); + sd_apply_mask(init_img, mask_img, masked_img); + ggml_tensor* masked_latent = NULL; + if (!sd_ctx->sd->use_tiny_autoencoder) { + ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); + masked_latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); + } else { + masked_latent = sd_ctx->sd->encode_first_stage(work_ctx, masked_img); + } + concat_latent = ggml_new_tensor_4d(work_ctx, + GGML_TYPE_F32, + masked_latent->ne[0], + masked_latent->ne[1], + mask_channels + masked_latent->ne[2], + 1); + for (int ix = 0; ix < masked_latent->ne[0]; ix++) { + for (int iy = 0; iy < masked_latent->ne[1]; iy++) { + int mx = ix * 8; + int my = iy * 8; + if (sd_ctx->sd->version == VERSION_FLUX_FILL) { + for (int k = 0; k < masked_latent->ne[2]; k++) { + float v = ggml_tensor_get_f32(masked_latent, ix, iy, k); + ggml_tensor_set_f32(concat_latent, v, ix, iy, k); + } + // "Encode" 8x8 mask chunks into a flattened 1x64 vector, and concatenate to masked image + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + float m = ggml_tensor_get_f32(mask_img, mx + x, my + y); + // TODO: check if the way the mask is flattened is correct (is it supposed to be x*8+y or x+8*y?) + // python code was using "b (h 8) (w 8) -> b (8 8) h w" + ggml_tensor_set_f32(concat_latent, m, ix, iy, masked_latent->ne[2] + x * 8 + y); + } + } + } else { + float m = ggml_tensor_get_f32(mask_img, mx, my); + ggml_tensor_set_f32(concat_latent, m, ix, iy, 0); + for (int k = 0; k < masked_latent->ne[2]; k++) { + float v = ggml_tensor_get_f32(masked_latent, ix, iy, k); + ggml_tensor_set_f32(concat_latent, v, ix, iy, k + mask_channels); } - } - } else { - float m = ggml_tensor_get_f32(mask_img, mx, my); - ggml_tensor_set_f32(concat_latent, m, ix, iy, 0); - for (int k = 0; k < masked_latent->ne[2]; k++) { - float v = ggml_tensor_get_f32(masked_latent, ix, iy, k); - ggml_tensor_set_f32(concat_latent, v, ix, iy, k + mask_channels); } } } } - } - { - // LOG_WARN("Inpainting with a base model is not great"); - denoise_mask = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width / 8, height / 8, 1, 1); - for (int ix = 0; ix < denoise_mask->ne[0]; ix++) { - for (int iy = 0; iy < denoise_mask->ne[1]; iy++) { - int mx = ix * 8; - int my = iy * 8; - float m = ggml_tensor_get_f32(mask_img, mx, my); - ggml_tensor_set_f32(denoise_mask, m, ix, iy); + { + // LOG_WARN("Inpainting with a base model is not great"); + denoise_mask = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, width / 8, height / 8, 1, 1); + for (int ix = 0; ix < denoise_mask->ne[0]; ix++) { + for (int iy = 0; iy < denoise_mask->ne[1]; iy++) { + int mx = ix * 8; + int my = iy * 8; + float m = ggml_tensor_get_f32(mask_img, mx, my); + ggml_tensor_set_f32(denoise_mask, m, ix, iy); + } } } - } - ggml_tensor* init_latent = NULL; - if (!sd_ctx->sd->use_tiny_autoencoder) { - ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, init_img); - init_latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); + if (!sd_ctx->sd->use_tiny_autoencoder) { + ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, init_img); + init_latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); + } else { + init_latent = sd_ctx->sd->encode_first_stage(work_ctx, init_img); + } } else { - init_latent = sd_ctx->sd->encode_first_stage(work_ctx, init_img); + LOG_INFO("TXT2IMG"); + if (sd_version_is_inpaint(sd_ctx->sd->version)) { + LOG_WARN("This is an inpainting model, this should only be used in img2img mode with a mask"); + } + init_latent = generate_init_latent(sd_ctx, work_ctx, width, height); + } + + if (sd_img_gen_params->ref_images_count > 0) { + LOG_INFO("EDIT mode"); + } + + std::vector ref_latents; + for (int i = 0; i < sd_img_gen_params->ref_images_count; i++) { + ggml_tensor* img = ggml_new_tensor_4d(work_ctx, + GGML_TYPE_F32, + sd_img_gen_params->ref_images[i].width, + sd_img_gen_params->ref_images[i].height, + 3, + 1); + sd_image_to_tensor(sd_img_gen_params->ref_images[i].data, img); + + ggml_tensor* latent = NULL; + if (sd_ctx->sd->use_tiny_autoencoder) { + latent = sd_ctx->sd->encode_first_stage(work_ctx, img); + } else if (sd_ctx->sd->version == VERSION_SD1_PIX2PIX) { + latent = sd_ctx->sd->encode_first_stage(work_ctx, img); + latent = ggml_view_3d(work_ctx, + latent, + latent->ne[0], + latent->ne[1], + latent->ne[2] / 2, + latent->nb[1], + latent->nb[2], + 0); + } else { + ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, img); + latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); + } + ref_latents.push_back(latent); } - size_t t1 = ggml_time_ms(); - LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); - - std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); - size_t t_enc = static_cast(sample_steps * strength); - if (t_enc == sample_steps) - t_enc--; - LOG_INFO("target t_enc is %zu steps", t_enc); - std::vector sigma_sched; - sigma_sched.assign(sigmas.begin() + sample_steps - t_enc - 1, sigmas.end()); - - sd_image_t* result_images = generate_image(sd_ctx, - work_ctx, - init_latent, - prompt_c_str, - negative_prompt_c_str, - clip_skip, - guidance, - eta, - width, - height, - sample_method, - sigma_sched, - seed, - batch_count, - control_cond, - control_strength, - style_ratio, - normalize_input, - input_id_images_path_c_str, - {}, - concat_latent, - denoise_mask); + if (sd_img_gen_params->init_image.data != NULL || sd_img_gen_params->ref_images_count > 0) { + size_t t1 = ggml_time_ms(); + LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); + } + + sd_image_t* result_images = generate_image_internal(sd_ctx, + work_ctx, + init_latent, + SAFE_STR(sd_img_gen_params->prompt), + SAFE_STR(sd_img_gen_params->negative_prompt), + sd_img_gen_params->clip_skip, + sd_img_gen_params->guidance, + sd_img_gen_params->eta, + width, + height, + sd_img_gen_params->sample_method, + sigmas, + seed, + sd_img_gen_params->batch_count, + sd_img_gen_params->control_cond, + sd_img_gen_params->control_strength, + sd_img_gen_params->style_strength, + sd_img_gen_params->normalize_input, + sd_img_gen_params->input_id_images_path, + ref_latents, + concat_latent, + denoise_mask); size_t t2 = ggml_time_ms(); - LOG_INFO("img2img completed in %.2fs", (t2 - t0) * 1.0f / 1000); + LOG_INFO("generate_image completed in %.2fs", (t2 - t0) * 1.0f / 1000); return result_images; } -SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, - sd_image_t init_image, - int width, - int height, - int video_frames, - int motion_bucket_id, - int fps, - float augmentation_level, - sd_guidance_params_t guidance, - enum sample_method_t sample_method, - int sample_steps, - float strength, - int64_t seed) { - if (sd_ctx == NULL) { +SD_API sd_image_t* generate_video(sd_ctx_t* sd_ctx, const sd_vid_gen_params_t* sd_vid_gen_params) { + if (sd_ctx == NULL || sd_vid_gen_params == NULL) { return NULL; } + int width = sd_vid_gen_params->width; + int height = sd_vid_gen_params->height; LOG_INFO("img2vid %dx%d", width, height); - std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); + std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sd_vid_gen_params->sample_steps); struct ggml_init_params params; params.mem_size = static_cast(10 * 1024) * 1024; // 10 MB - params.mem_size += width * height * 3 * sizeof(float) * video_frames; + params.mem_size += width * height * 3 * sizeof(float) * sd_vid_gen_params->video_frames; params.mem_buffer = NULL; params.no_alloc = false; // LOG_DEBUG("mem_size %u ", params.mem_size); @@ -1930,6 +2105,7 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, return NULL; } + int64_t seed = sd_vid_gen_params->seed; if (seed < 0) { seed = (int)time(NULL); } @@ -1939,12 +2115,12 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, int64_t t0 = ggml_time_ms(); SDCondition cond = sd_ctx->sd->get_svd_condition(work_ctx, - init_image, + sd_vid_gen_params->init_image, width, height, - fps, - motion_bucket_id, - augmentation_level); + sd_vid_gen_params->fps, + sd_vid_gen_params->motion_bucket_id, + sd_vid_gen_params->augmentation_level); auto uc_crossattn = ggml_dup_tensor(work_ctx, cond.c_crossattn); ggml_set_f32(uc_crossattn, 0.f); @@ -1966,13 +2142,13 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, int C = 4; int W = width / 8; int H = height / 8; - struct ggml_tensor* x_t = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, video_frames); + struct ggml_tensor* x_t = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, sd_vid_gen_params->video_frames); ggml_set_f32(x_t, 0.f); - struct ggml_tensor* noise = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, video_frames); + struct ggml_tensor* noise = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, W, H, C, sd_vid_gen_params->video_frames); ggml_tensor_set_f32_randn(noise, sd_ctx->sd->rng); - LOG_INFO("sampling using %s method", sampling_methods_str[sample_method]); + LOG_INFO("sampling using %s method", sampling_methods_str[sd_vid_gen_params->sample_method]); struct ggml_tensor* x_0 = sd_ctx->sd->sample(work_ctx, x_t, noise, @@ -1981,9 +2157,9 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, {}, {}, 0.f, - guidance, + sd_vid_gen_params->guidance, 0.f, - sample_method, + sd_vid_gen_params->sample_method, sigmas, -1, SDCondition(NULL, NULL, NULL)); @@ -2003,13 +2179,13 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, return NULL; } - sd_image_t* result_images = (sd_image_t*)calloc(video_frames, sizeof(sd_image_t)); + sd_image_t* result_images = (sd_image_t*)calloc(sd_vid_gen_params->video_frames, sizeof(sd_image_t)); if (result_images == NULL) { ggml_free(work_ctx); return NULL; } - for (size_t i = 0; i < video_frames; i++) { + for (size_t i = 0; i < sd_vid_gen_params->video_frames; i++) { auto img_i = ggml_view_3d(work_ctx, img, img->ne[0], img->ne[1], img->ne[2], img->nb[1], img->nb[2], img->nb[3] * i); result_images[i].width = width; @@ -2025,114 +2201,3 @@ SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, return result_images; } - -sd_image_t* edit(sd_ctx_t* sd_ctx, - sd_image_t* ref_images, - int ref_images_count, - const char* prompt_c_str, - const char* negative_prompt_c_str, - int clip_skip, - sd_guidance_params_t guidance, - float eta, - int width, - int height, - enum sample_method_t sample_method, - int sample_steps, - int64_t seed, - int batch_count, - const sd_image_t* control_cond, - float control_strength, - float style_ratio, - bool normalize_input, - const char* input_id_images_path_c_str) { - LOG_DEBUG("edit %dx%d", width, height); - if (sd_ctx == NULL) { - return NULL; - } - if (ref_images_count <= 0) { - LOG_ERROR("ref images count should > 0"); - return NULL; - } - - struct ggml_init_params params; - params.mem_size = static_cast(30 * 1024 * 1024); // 10 MB - params.mem_size += width * height * 3 * sizeof(float) * 3 * ref_images_count; - params.mem_size *= batch_count; - params.mem_buffer = NULL; - params.no_alloc = false; - // LOG_DEBUG("mem_size %u ", params.mem_size); - - struct ggml_context* work_ctx = ggml_init(params); - if (!work_ctx) { - LOG_ERROR("ggml_init() failed"); - return NULL; - } - - if (seed < 0) { - srand((int)time(NULL)); - seed = rand(); - } - sd_ctx->sd->rng->manual_seed(seed); - - size_t t0 = ggml_time_ms(); - - std::vector ref_latents; - for (int i = 0; i < ref_images_count; i++) { - ggml_tensor* img = ggml_new_tensor_4d(work_ctx, GGML_TYPE_F32, ref_images[i].width, ref_images[i].height, 3, 1); - sd_image_to_tensor(ref_images[i].data, img); - - ggml_tensor* latent = NULL; - if (sd_ctx->sd->use_tiny_autoencoder) { - latent = sd_ctx->sd->encode_first_stage(work_ctx, img); - } else if (sd_ctx->sd->version == VERSION_SD1_PIX2PIX) { - latent = sd_ctx->sd->encode_first_stage(work_ctx, img); - latent = ggml_view_3d(work_ctx, - latent, - latent->ne[0], - latent->ne[1], - latent->ne[2] / 2, - latent->nb[1], - latent->nb[2], - 0); - } else { - ggml_tensor* moments = sd_ctx->sd->encode_first_stage(work_ctx, img); - latent = sd_ctx->sd->get_first_stage_encoding(work_ctx, moments); - } - ref_latents.push_back(latent); - } - - size_t t1 = ggml_time_ms(); - LOG_INFO("encode_first_stage completed, taking %.2fs", (t1 - t0) * 1.0f / 1000); - - std::vector sigmas = sd_ctx->sd->denoiser->get_sigmas(sample_steps); - - ggml_tensor* init_latent = generate_init_latent(sd_ctx, work_ctx, width, height); - - sd_image_t* result_images = generate_image(sd_ctx, - work_ctx, - init_latent, - prompt_c_str, - negative_prompt_c_str, - clip_skip, - guidance, - eta, - width, - height, - sample_method, - sigmas, - seed, - batch_count, - control_cond, - control_strength, - style_ratio, - normalize_input, - "", - ref_latents, - NULL); - - size_t t2 = ggml_time_ms(); - - LOG_INFO("edit completed in %.2fs", (t2 - t0) * 1.0f / 1000); - - return result_images; -} \ No newline at end of file diff --git a/stable-diffusion.h b/stable-diffusion.h index ac50df1af..a60325923 100644 --- a/stable-diffusion.h +++ b/stable-diffusion.h @@ -30,7 +30,8 @@ extern "C" { enum rng_type_t { STD_DEFAULT_RNG, - CUDA_RNG + CUDA_RNG, + RNG_TYPE_COUNT }; enum sample_method_t { @@ -46,7 +47,7 @@ enum sample_method_t { LCM, DDIM_TRAILING, TCD, - N_SAMPLE_METHODS + SAMPLE_METHOD_COUNT }; enum schedule_t { @@ -56,7 +57,7 @@ enum schedule_t { EXPONENTIAL, AYS, GITS, - N_SCHEDULES + SCHEDULE_COUNT }; // same as enum ggml_type @@ -103,8 +104,6 @@ enum sd_type_t { SD_TYPE_COUNT = 39, }; -SD_API const char* sd_type_name(enum sd_type_t type); - enum sd_log_level_t { SD_LOG_DEBUG, SD_LOG_INFO, @@ -112,13 +111,33 @@ enum sd_log_level_t { SD_LOG_ERROR }; -typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data); -typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data); - -SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data); -SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data); -SD_API int32_t get_num_physical_cores(); -SD_API const char* sd_get_system_info(); +typedef struct { + const char* model_path; + const char* clip_l_path; + const char* clip_g_path; + const char* t5xxl_path; + const char* diffusion_model_path; + const char* vae_path; + const char* taesd_path; + const char* control_net_path; + const char* lora_model_dir; + const char* embedding_dir; + const char* stacked_id_embed_dir; + bool vae_decode_only; + bool vae_tiling; + bool free_params_immediately; + int n_threads; + enum sd_type_t wtype; + enum rng_type_t rng_type; + enum schedule_t schedule; + bool keep_clip_on_cpu; + bool keep_control_net_on_cpu; + bool keep_vae_on_cpu; + bool diffusion_flash_attn; + bool chroma_use_dit_mask; + bool chroma_use_t5_mask; + int chroma_t5_mask_pad; +} sd_ctx_params_t; typedef struct { uint32_t width; @@ -127,8 +146,6 @@ typedef struct { uint8_t* data; } sd_image_t; -typedef struct sd_ctx_t sd_ctx_t; - typedef struct { int* layers; size_t layer_count; @@ -145,106 +162,76 @@ typedef struct { sd_slg_params_t slg; } sd_guidance_params_t; -SD_API sd_ctx_t* new_sd_ctx(const char* model_path, - const char* clip_l_path, - const char* clip_g_path, - const char* t5xxl_path, - const char* diffusion_model_path, - const char* vae_path, - const char* taesd_path, - const char* control_net_path_c_str, - const char* lora_model_dir, - const char* embed_dir_c_str, - const char* stacked_id_embed_dir_c_str, - bool vae_decode_only, - bool vae_tiling, - bool free_params_immediately, - int n_threads, - enum sd_type_t wtype, - enum rng_type_t rng_type, - enum schedule_t s, - bool keep_clip_on_cpu, - bool keep_control_net_cpu, - bool keep_vae_on_cpu, - bool diffusion_flash_attn, - bool chroma_use_dit_mask, - bool chroma_use_t5_mask, - int chroma_t5_mask_pad); +typedef struct { + const char* prompt; + const char* negative_prompt; + int clip_skip; + sd_guidance_params_t guidance; + sd_image_t init_image; + sd_image_t* ref_images; + int ref_images_count; + sd_image_t mask_image; + int width; + int height; + enum sample_method_t sample_method; + int sample_steps; + float eta; + float strength; + int64_t seed; + int batch_count; + const sd_image_t* control_cond; + float control_strength; + float style_strength; + bool normalize_input; + const char* input_id_images_path; +} sd_img_gen_params_t; -SD_API void free_sd_ctx(sd_ctx_t* sd_ctx); +typedef struct { + sd_image_t init_image; + int width; + int height; + sd_guidance_params_t guidance; + enum sample_method_t sample_method; + int sample_steps; + float strength; + int64_t seed; + int video_frames; + int motion_bucket_id; + int fps; + float augmentation_level; +} sd_vid_gen_params_t; + +typedef struct sd_ctx_t sd_ctx_t; + +typedef void (*sd_log_cb_t)(enum sd_log_level_t level, const char* text, void* data); +typedef void (*sd_progress_cb_t)(int step, int steps, float time, void* data); + +SD_API void sd_set_log_callback(sd_log_cb_t sd_log_cb, void* data); +SD_API void sd_set_progress_callback(sd_progress_cb_t cb, void* data); +SD_API int32_t get_num_physical_cores(); +SD_API const char* sd_get_system_info(); -SD_API sd_image_t* txt2img(sd_ctx_t* sd_ctx, - const char* prompt, - const char* negative_prompt, - int clip_skip, - sd_guidance_params_t guidance, - float eta, - int width, - int height, - enum sample_method_t sample_method, - int sample_steps, - int64_t seed, - int batch_count, - const sd_image_t* control_cond, - float control_strength, - float style_strength, - bool normalize_input, - const char* input_id_images_path); +SD_API const char* sd_type_name(enum sd_type_t type); +SD_API enum sd_type_t str_to_sd_type(const char* str); +SD_API const char* sd_rng_type_name(enum rng_type_t rng_type); +SD_API enum rng_type_t str_to_rng_type(const char* str); +SD_API const char* sd_sample_method_name(enum sample_method_t sample_method); +SD_API enum sample_method_t str_to_sample_method(const char* str); +SD_API const char* sd_schedule_name(enum schedule_t schedule); +SD_API enum schedule_t str_to_schedule(const char* str); + +SD_API void sd_ctx_params_init(sd_ctx_params_t* sd_ctx_params); +SD_API char* sd_ctx_params_to_str(const sd_ctx_params_t* sd_ctx_params); -SD_API sd_image_t* img2img(sd_ctx_t* sd_ctx, - sd_image_t init_image, - sd_image_t mask_image, - const char* prompt, - const char* negative_prompt, - int clip_skip, - sd_guidance_params_t guidance, - float eta, - int width, - int height, - enum sample_method_t sample_method, - int sample_steps, - float strength, - int64_t seed, - int batch_count, - const sd_image_t* control_cond, - float control_strength, - float style_strength, - bool normalize_input, - const char* input_id_images_path); +SD_API sd_ctx_t* new_sd_ctx(const sd_ctx_params_t* sd_ctx_params); +SD_API void free_sd_ctx(sd_ctx_t* sd_ctx); -SD_API sd_image_t* img2vid(sd_ctx_t* sd_ctx, - sd_image_t init_image, - int width, - int height, - int video_frames, - int motion_bucket_id, - int fps, - float augmentation_level, - sd_guidance_params_t guidance, - enum sample_method_t sample_method, - int sample_steps, - float strength, - int64_t seed); +SD_API void sd_img_gen_params_init(sd_img_gen_params_t* sd_img_gen_params); +SD_API char* sd_img_gen_params_to_str(const sd_img_gen_params_t* sd_img_gen_params); +SD_API sd_image_t* generate_image(sd_ctx_t* sd_ctx, const sd_img_gen_params_t* sd_img_gen_params); -SD_API sd_image_t* edit(sd_ctx_t* sd_ctx, - sd_image_t* ref_images, - int ref_images_count, - const char* prompt, - const char* negative_prompt, - int clip_skip, - sd_guidance_params_t guidance, - float eta, - int width, - int height, - enum sample_method_t sample_method, - int sample_steps, - int64_t seed, - int batch_count, - const sd_image_t* control_cond, - float control_strength, - float style_strength, - bool normalize_input, - const char* input_id_images_path); +SD_API void sd_vid_gen_params_init(sd_vid_gen_params_t* sd_vid_gen_params); +SD_API sd_image_t* generate_video(sd_ctx_t* sd_ctx, const sd_vid_gen_params_t* sd_vid_gen_params); // broken typedef struct upscaler_ctx_t upscaler_ctx_t; @@ -254,7 +241,11 @@ SD_API void free_upscaler_ctx(upscaler_ctx_t* upscaler_ctx); SD_API sd_image_t upscale(upscaler_ctx_t* upscaler_ctx, sd_image_t input_image, uint32_t upscale_factor); -SD_API bool convert(const char* input_path, const char* vae_path, const char* output_path, enum sd_type_t output_type, const char* tensor_type_rules); +SD_API bool convert(const char* input_path, + const char* vae_path, + const char* output_path, + enum sd_type_t output_type, + const char* tensor_type_rules); SD_API uint8_t* preprocess_canny(uint8_t* img, int width, diff --git a/util.cpp b/util.cpp index 631c12066..92bc9ef50 100644 --- a/util.cpp +++ b/util.cpp @@ -441,10 +441,6 @@ const char* sd_get_system_info() { return buffer; } -const char* sd_type_name(enum sd_type_t type) { - return ggml_type_name((ggml_type)type); -} - sd_image_f32_t sd_image_t_to_sd_image_f32_t(sd_image_t image) { sd_image_f32_t converted_image; converted_image.width = image.width; diff --git a/util.h b/util.h index 14fa812e5..d98c9a280 100644 --- a/util.h +++ b/util.h @@ -7,6 +7,9 @@ #include "stable-diffusion.h" +#define SAFE_STR(s) ((s) ? (s) : "") +#define BOOL_STR(b) ((b) ? "true" : "false") + bool ends_with(const std::string& str, const std::string& ending); bool starts_with(const std::string& str, const std::string& start); bool contains(const std::string& str, const std::string& substr); From 0739361bfe5fbb1337c7f9d05e32606752d145e2 Mon Sep 17 00:00:00 2001 From: leejet Date: Sun, 13 Jul 2025 20:18:10 +0800 Subject: [PATCH 085/143] fix: avoid macOS build failed --- examples/cli/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cli/main.cpp b/examples/cli/main.cpp index 5879967fb..b3ae569e6 100644 --- a/examples/cli/main.cpp +++ b/examples/cli/main.cpp @@ -950,7 +950,7 @@ int main(int argc, const char* argv[]) { guidance_params, input_image, ref_images.data(), - ref_images.size(), + (int)ref_images.size(), mask_image, params.width, params.height, From 1896b28ef2fd5b3643120e66979bea487385439f Mon Sep 17 00:00:00 2001 From: Oleg Skutte <45887963+SkutteOleg@users.noreply.github.com> Date: Mon, 14 Jul 2025 20:45:22 +0400 Subject: [PATCH 086/143] fix: make --taesd work (#731) --- stable-diffusion.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/stable-diffusion.cpp b/stable-diffusion.cpp index 43b027ee8..402585f1c 100644 --- a/stable-diffusion.cpp +++ b/stable-diffusion.cpp @@ -174,6 +174,7 @@ class StableDiffusionGGML { vae_decode_only = sd_ctx_params->vae_decode_only; free_params_immediately = sd_ctx_params->free_params_immediately; lora_model_dir = SAFE_STR(sd_ctx_params->lora_model_dir); + taesd_path = SAFE_STR(sd_ctx_params->taesd_path); use_tiny_autoencoder = taesd_path.size() > 0; vae_tiling = sd_ctx_params->vae_tiling; From a6f6e5d7da681cbedcd142bb89138b0d5e7cdc69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sun, 25 Aug 2024 23:46:41 +0200 Subject: [PATCH 087/143] Add server example --- examples/CMakeLists.txt | 3 +- examples/server/CMakeLists.txt | 6 + examples/server/httplib.h | 9465 ++++++++++++++++++++++++++++++++ examples/server/main.cpp | 707 +++ 4 files changed, 10180 insertions(+), 1 deletion(-) create mode 100644 examples/server/CMakeLists.txt create mode 100644 examples/server/httplib.h create mode 100644 examples/server/main.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 81053f9e2..2dcd1d53a 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,4 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -add_subdirectory(cli) \ No newline at end of file +add_subdirectory(cli) +add_subdirectory(server) \ No newline at end of file diff --git a/examples/server/CMakeLists.txt b/examples/server/CMakeLists.txt new file mode 100644 index 000000000..5e5768227 --- /dev/null +++ b/examples/server/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET sd-server) + +add_executable(${TARGET} main.cpp) +install(TARGETS ${TARGET} RUNTIME) +target_link_libraries(${TARGET} PRIVATE stable-diffusion ${CMAKE_THREAD_LIBS_INIT}) +target_compile_features(${TARGET} PUBLIC cxx_std_11) \ No newline at end of file diff --git a/examples/server/httplib.h b/examples/server/httplib.h new file mode 100644 index 000000000..f360bd93e --- /dev/null +++ b/examples/server/httplib.h @@ -0,0 +1,9465 @@ +// +// httplib.h +// +// Copyright (c) 2024 Yuji Hirose. All rights reserved. +// MIT License +// + +#ifndef CPPHTTPLIB_HTTPLIB_H +#define CPPHTTPLIB_HTTPLIB_H + +#define CPPHTTPLIB_VERSION "0.15.3" + +/* + * Configuration + */ + +#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND +#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT +#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 5 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300 +#endif + +#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND +#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_SECOND +#define CPPHTTPLIB_READ_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_READ_TIMEOUT_USECOND +#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_SECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_SECOND 5 +#endif + +#ifndef CPPHTTPLIB_WRITE_TIMEOUT_USECOND +#define CPPHTTPLIB_WRITE_TIMEOUT_USECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND +#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0 +#endif + +#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND +#ifdef _WIN32 +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 10000 +#else +#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0 +#endif +#endif + +#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH +#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH +#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT +#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20 +#endif + +#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT +#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH ((std::numeric_limits::max)()) +#endif + +#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH +#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192 +#endif + +#ifndef CPPHTTPLIB_RANGE_MAX_COUNT +#define CPPHTTPLIB_RANGE_MAX_COUNT 1024 +#endif + +#ifndef CPPHTTPLIB_TCP_NODELAY +#define CPPHTTPLIB_TCP_NODELAY false +#endif + +#ifndef CPPHTTPLIB_RECV_BUFSIZ +#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u) +#endif + +#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ +#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u) +#endif + +#ifndef CPPHTTPLIB_THREAD_POOL_COUNT +#define CPPHTTPLIB_THREAD_POOL_COUNT \ + ((std::max)(8u, std::thread::hardware_concurrency() > 0 \ + ? std::thread::hardware_concurrency() - 1 \ + : 0)) +#endif + +#ifndef CPPHTTPLIB_RECV_FLAGS +#define CPPHTTPLIB_RECV_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_SEND_FLAGS +#define CPPHTTPLIB_SEND_FLAGS 0 +#endif + +#ifndef CPPHTTPLIB_LISTEN_BACKLOG +#define CPPHTTPLIB_LISTEN_BACKLOG 5 +#endif + +/* + * Headers + */ + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif //_CRT_SECURE_NO_WARNINGS + +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif //_CRT_NONSTDC_NO_DEPRECATE + +#if defined(_MSC_VER) +#if _MSC_VER < 1900 +#error Sorry, Visual Studio versions prior to 2015 are not supported +#endif + +#pragma comment(lib, "ws2_32.lib") + +#ifdef _WIN64 +using ssize_t = __int64; +#else +using ssize_t = long; +#endif +#endif // _MSC_VER + +#ifndef S_ISREG +#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG) +#endif // S_ISREG + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR) +#endif // S_ISDIR + +#ifndef NOMINMAX +#define NOMINMAX +#endif // NOMINMAX + +#include +#include +#include + +#ifndef WSA_FLAG_NO_HANDLE_INHERIT +#define WSA_FLAG_NO_HANDLE_INHERIT 0x80 +#endif + +using socket_t = SOCKET; +#ifdef CPPHTTPLIB_USE_POLL +#define poll(fds, nfds, timeout) WSAPoll(fds, nfds, timeout) +#endif + +#else // not _WIN32 + +#include +#if !defined(_AIX) && !defined(__MVS__) +#include +#endif +#ifdef __MVS__ +#include +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif +#endif +#include +#include +#include +#ifdef __linux__ +#include +#endif +#include +#ifdef CPPHTTPLIB_USE_POLL +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +using socket_t = int; +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif +#endif //_WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +#include + +// these are defined in wincrypt.h and it breaks compilation if BoringSSL is +// used +#undef X509_NAME +#undef X509_CERT_PAIR +#undef X509_EXTENSIONS +#undef PKCS7_SIGNER_INFO + +#ifdef _MSC_VER +#pragma comment(lib, "crypt32.lib") +#endif +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#include +#if TARGET_OS_OSX +#include +#include +#endif // TARGET_OS_OSX +#endif // _WIN32 + +#include +#include +#include +#include + +#if defined(_WIN32) && defined(OPENSSL_USE_APPLINK) +#include +#endif + +#include +#include + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#error Sorry, OpenSSL versions prior to 3.0.0 are not supported +#endif + +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +#include +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +#include +#include +#endif + +/* + * Declaration + */ +namespace httplib { + +namespace detail { + +/* + * Backport std::make_unique from C++14. + * + * NOTE: This code came up with the following stackoverflow post: + * https://stackoverflow.com/questions/10149840/c-arrays-and-make-unique + * + */ + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} + +template +typename std::enable_if::value, std::unique_ptr>::type +make_unique(std::size_t n) { + typedef typename std::remove_extent::type RT; + return std::unique_ptr(new RT[n]); +} + +struct ci { + bool operator()(const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare(s1.begin(), s1.end(), s2.begin(), + s2.end(), + [](unsigned char c1, unsigned char c2) { + return ::tolower(c1) < ::tolower(c2); + }); + } +}; + +// This is based on +// "http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4189". + +struct scope_exit { + explicit scope_exit(std::function &&f) + : exit_function(std::move(f)), execute_on_destruction{true} {} + + scope_exit(scope_exit &&rhs) noexcept + : exit_function(std::move(rhs.exit_function)), + execute_on_destruction{rhs.execute_on_destruction} { + rhs.release(); + } + + ~scope_exit() { + if (execute_on_destruction) { this->exit_function(); } + } + + void release() { this->execute_on_destruction = false; } + +private: + scope_exit(const scope_exit &) = delete; + void operator=(const scope_exit &) = delete; + scope_exit &operator=(scope_exit &&) = delete; + + std::function exit_function; + bool execute_on_destruction; +}; + +} // namespace detail + +enum StatusCode { + // Information responses + Continue_100 = 100, + SwitchingProtocol_101 = 101, + Processing_102 = 102, + EarlyHints_103 = 103, + + // Successful responses + OK_200 = 200, + Created_201 = 201, + Accepted_202 = 202, + NonAuthoritativeInformation_203 = 203, + NoContent_204 = 204, + ResetContent_205 = 205, + PartialContent_206 = 206, + MultiStatus_207 = 207, + AlreadyReported_208 = 208, + IMUsed_226 = 226, + + // Redirection messages + MultipleChoices_300 = 300, + MovedPermanently_301 = 301, + Found_302 = 302, + SeeOther_303 = 303, + NotModified_304 = 304, + UseProxy_305 = 305, + unused_306 = 306, + TemporaryRedirect_307 = 307, + PermanentRedirect_308 = 308, + + // Client error responses + BadRequest_400 = 400, + Unauthorized_401 = 401, + PaymentRequired_402 = 402, + Forbidden_403 = 403, + NotFound_404 = 404, + MethodNotAllowed_405 = 405, + NotAcceptable_406 = 406, + ProxyAuthenticationRequired_407 = 407, + RequestTimeout_408 = 408, + Conflict_409 = 409, + Gone_410 = 410, + LengthRequired_411 = 411, + PreconditionFailed_412 = 412, + PayloadTooLarge_413 = 413, + UriTooLong_414 = 414, + UnsupportedMediaType_415 = 415, + RangeNotSatisfiable_416 = 416, + ExpectationFailed_417 = 417, + ImATeapot_418 = 418, + MisdirectedRequest_421 = 421, + UnprocessableContent_422 = 422, + Locked_423 = 423, + FailedDependency_424 = 424, + TooEarly_425 = 425, + UpgradeRequired_426 = 426, + PreconditionRequired_428 = 428, + TooManyRequests_429 = 429, + RequestHeaderFieldsTooLarge_431 = 431, + UnavailableForLegalReasons_451 = 451, + + // Server error responses + InternalServerError_500 = 500, + NotImplemented_501 = 501, + BadGateway_502 = 502, + ServiceUnavailable_503 = 503, + GatewayTimeout_504 = 504, + HttpVersionNotSupported_505 = 505, + VariantAlsoNegotiates_506 = 506, + InsufficientStorage_507 = 507, + LoopDetected_508 = 508, + NotExtended_510 = 510, + NetworkAuthenticationRequired_511 = 511, +}; + +using Headers = std::multimap; + +using Params = std::multimap; +using Match = std::smatch; + +using Progress = std::function; + +struct Response; +using ResponseHandler = std::function; + +struct MultipartFormData { + std::string name; + std::string content; + std::string filename; + std::string content_type; +}; +using MultipartFormDataItems = std::vector; +using MultipartFormDataMap = std::multimap; + +class DataSink { +public: + DataSink() : os(&sb_), sb_(*this) {} + + DataSink(const DataSink &) = delete; + DataSink &operator=(const DataSink &) = delete; + DataSink(DataSink &&) = delete; + DataSink &operator=(DataSink &&) = delete; + + std::function write; + std::function is_writable; + std::function done; + std::function done_with_trailer; + std::ostream os; + +private: + class data_sink_streambuf final : public std::streambuf { + public: + explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {} + + protected: + std::streamsize xsputn(const char *s, std::streamsize n) override { + sink_.write(s, static_cast(n)); + return n; + } + + private: + DataSink &sink_; + }; + + data_sink_streambuf sb_; +}; + +using ContentProvider = + std::function; + +using ContentProviderWithoutLength = + std::function; + +using ContentProviderResourceReleaser = std::function; + +struct MultipartFormDataProvider { + std::string name; + ContentProviderWithoutLength provider; + std::string filename; + std::string content_type; +}; +using MultipartFormDataProviderItems = std::vector; + +using ContentReceiverWithProgress = + std::function; + +using ContentReceiver = + std::function; + +using MultipartContentHeader = + std::function; + +class ContentReader { +public: + using Reader = std::function; + using MultipartReader = std::function; + + ContentReader(Reader reader, MultipartReader multipart_reader) + : reader_(std::move(reader)), + multipart_reader_(std::move(multipart_reader)) {} + + bool operator()(MultipartContentHeader header, + ContentReceiver receiver) const { + return multipart_reader_(std::move(header), std::move(receiver)); + } + + bool operator()(ContentReceiver receiver) const { + return reader_(std::move(receiver)); + } + + Reader reader_; + MultipartReader multipart_reader_; +}; + +using Range = std::pair; +using Ranges = std::vector; + +struct Request { + std::string method; + std::string path; + Headers headers; + std::string body; + + std::string remote_addr; + int remote_port = -1; + std::string local_addr; + int local_port = -1; + + // for server + std::string version; + std::string target; + Params params; + MultipartFormDataMap files; + Ranges ranges; + Match matches; + std::unordered_map path_params; + + // for client + ResponseHandler response_handler; + ContentReceiverWithProgress content_receiver; + Progress progress; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + const SSL *ssl = nullptr; +#endif + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + bool has_param(const std::string &key) const; + std::string get_param_value(const std::string &key, size_t id = 0) const; + size_t get_param_value_count(const std::string &key) const; + + bool is_multipart_form_data() const; + + bool has_file(const std::string &key) const; + MultipartFormData get_file_value(const std::string &key) const; + std::vector get_file_values(const std::string &key) const; + + // private members... + size_t redirect_count_ = CPPHTTPLIB_REDIRECT_MAX_COUNT; + size_t content_length_ = 0; + ContentProvider content_provider_; + bool is_chunked_content_provider_ = false; + size_t authorization_count_ = 0; +}; + +struct Response { + std::string version; + int status = -1; + std::string reason; + Headers headers; + std::string body; + std::string location; // Redirect location + + bool has_header(const std::string &key) const; + std::string get_header_value(const std::string &key, size_t id = 0) const; + uint64_t get_header_value_u64(const std::string &key, size_t id = 0) const; + size_t get_header_value_count(const std::string &key) const; + void set_header(const std::string &key, const std::string &val); + + void set_redirect(const std::string &url, int status = StatusCode::Found_302); + void set_content(const char *s, size_t n, const std::string &content_type); + void set_content(const std::string &s, const std::string &content_type); + void set_content(std::string &&s, const std::string &content_type); + + void set_content_provider( + size_t length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + void set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser = nullptr); + + Response() = default; + Response(const Response &) = default; + Response &operator=(const Response &) = default; + Response(Response &&) = default; + Response &operator=(Response &&) = default; + ~Response() { + if (content_provider_resource_releaser_) { + content_provider_resource_releaser_(content_provider_success_); + } + } + + // private members... + size_t content_length_ = 0; + ContentProvider content_provider_; + ContentProviderResourceReleaser content_provider_resource_releaser_; + bool is_chunked_content_provider_ = false; + bool content_provider_success_ = false; +}; + +class Stream { +public: + virtual ~Stream() = default; + + virtual bool is_readable() const = 0; + virtual bool is_writable() const = 0; + + virtual ssize_t read(char *ptr, size_t size) = 0; + virtual ssize_t write(const char *ptr, size_t size) = 0; + virtual void get_remote_ip_and_port(std::string &ip, int &port) const = 0; + virtual void get_local_ip_and_port(std::string &ip, int &port) const = 0; + virtual socket_t socket() const = 0; + + template + ssize_t write_format(const char *fmt, const Args &...args); + ssize_t write(const char *ptr); + ssize_t write(const std::string &s); +}; + +class TaskQueue { +public: + TaskQueue() = default; + virtual ~TaskQueue() = default; + + virtual bool enqueue(std::function fn) = 0; + virtual void shutdown() = 0; + + virtual void on_idle() {} +}; + +class ThreadPool final : public TaskQueue { +public: + explicit ThreadPool(size_t n, size_t mqr = 0) + : shutdown_(false), max_queued_requests_(mqr) { + while (n) { + threads_.emplace_back(worker(*this)); + n--; + } + } + + ThreadPool(const ThreadPool &) = delete; + ~ThreadPool() override = default; + + bool enqueue(std::function fn) override { + { + std::unique_lock lock(mutex_); + if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) { + return false; + } + jobs_.push_back(std::move(fn)); + } + + cond_.notify_one(); + return true; + } + + void shutdown() override { + // Stop all worker threads... + { + std::unique_lock lock(mutex_); + shutdown_ = true; + } + + cond_.notify_all(); + + // Join... + for (auto &t : threads_) { + t.join(); + } + } + +private: + struct worker { + explicit worker(ThreadPool &pool) : pool_(pool) {} + + void operator()() { + for (;;) { + std::function fn; + { + std::unique_lock lock(pool_.mutex_); + + pool_.cond_.wait( + lock, [&] { return !pool_.jobs_.empty() || pool_.shutdown_; }); + + if (pool_.shutdown_ && pool_.jobs_.empty()) { break; } + + fn = std::move(pool_.jobs_.front()); + pool_.jobs_.pop_front(); + } + + assert(true == static_cast(fn)); + fn(); + } + } + + ThreadPool &pool_; + }; + friend struct worker; + + std::vector threads_; + std::list> jobs_; + + bool shutdown_; + size_t max_queued_requests_ = 0; + + std::condition_variable cond_; + std::mutex mutex_; +}; + +using Logger = std::function; + +using SocketOptions = std::function; + +void default_socket_options(socket_t sock); + +const char *status_message(int status); + +std::string get_bearer_token_auth(const Request &req); + +namespace detail { + +class MatcherBase { +public: + virtual ~MatcherBase() = default; + + // Match request path and populate its matches and + virtual bool match(Request &request) const = 0; +}; + +/** + * Captures parameters in request path and stores them in Request::path_params + * + * Capture name is a substring of a pattern from : to /. + * The rest of the pattern is matched agains the request path directly + * Parameters are captured starting from the next character after + * the end of the last matched static pattern fragment until the next /. + * + * Example pattern: + * "/path/fragments/:capture/more/fragments/:second_capture" + * Static fragments: + * "/path/fragments/", "more/fragments/" + * + * Given the following request path: + * "/path/fragments/:1/more/fragments/:2" + * the resulting capture will be + * {{"capture", "1"}, {"second_capture", "2"}} + */ +class PathParamsMatcher final : public MatcherBase { +public: + PathParamsMatcher(const std::string &pattern); + + bool match(Request &request) const override; + +private: + static constexpr char marker = ':'; + // Treat segment separators as the end of path parameter capture + // Does not need to handle query parameters as they are parsed before path + // matching + static constexpr char separator = '/'; + + // Contains static path fragments to match against, excluding the '/' after + // path params + // Fragments are separated by path params + std::vector static_fragments_; + // Stores the names of the path parameters to be used as keys in the + // Request::path_params map + std::vector param_names_; +}; + +/** + * Performs std::regex_match on request path + * and stores the result in Request::matches + * + * Note that regex match is performed directly on the whole request. + * This means that wildcard patterns may match multiple path segments with /: + * "/begin/(.*)/end" will match both "/begin/middle/end" and "/begin/1/2/end". + */ +class RegexMatcher final : public MatcherBase { +public: + RegexMatcher(const std::string &pattern) : regex_(pattern) {} + + bool match(Request &request) const override; + +private: + std::regex regex_; +}; + +ssize_t write_headers(Stream &strm, const Headers &headers); + +} // namespace detail + +class Server { +public: + using Handler = std::function; + + using ExceptionHandler = + std::function; + + enum class HandlerResponse { + Handled, + Unhandled, + }; + using HandlerWithResponse = + std::function; + + using HandlerWithContentReader = std::function; + + using Expect100ContinueHandler = + std::function; + + Server(); + + virtual ~Server(); + + virtual bool is_valid() const; + + Server &Get(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, Handler handler); + Server &Post(const std::string &pattern, HandlerWithContentReader handler); + Server &Put(const std::string &pattern, Handler handler); + Server &Put(const std::string &pattern, HandlerWithContentReader handler); + Server &Patch(const std::string &pattern, Handler handler); + Server &Patch(const std::string &pattern, HandlerWithContentReader handler); + Server &Delete(const std::string &pattern, Handler handler); + Server &Delete(const std::string &pattern, HandlerWithContentReader handler); + Server &Options(const std::string &pattern, Handler handler); + + bool set_base_dir(const std::string &dir, + const std::string &mount_point = std::string()); + bool set_mount_point(const std::string &mount_point, const std::string &dir, + Headers headers = Headers()); + bool remove_mount_point(const std::string &mount_point); + Server &set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime); + Server &set_default_file_mimetype(const std::string &mime); + Server &set_file_request_handler(Handler handler); + + Server &set_error_handler(HandlerWithResponse handler); + Server &set_error_handler(Handler handler); + Server &set_exception_handler(ExceptionHandler handler); + Server &set_pre_routing_handler(HandlerWithResponse handler); + Server &set_post_routing_handler(Handler handler); + + Server &set_expect_100_continue_handler(Expect100ContinueHandler handler); + Server &set_logger(Logger logger); + + Server &set_address_family(int family); + Server &set_tcp_nodelay(bool on); + Server &set_socket_options(SocketOptions socket_options); + + Server &set_default_headers(Headers headers); + Server & + set_header_writer(std::function const &writer); + + Server &set_keep_alive_max_count(size_t count); + Server &set_keep_alive_timeout(time_t sec); + + Server &set_read_timeout(time_t sec, time_t usec = 0); + template + Server &set_read_timeout(const std::chrono::duration &duration); + + Server &set_write_timeout(time_t sec, time_t usec = 0); + template + Server &set_write_timeout(const std::chrono::duration &duration); + + Server &set_idle_interval(time_t sec, time_t usec = 0); + template + Server &set_idle_interval(const std::chrono::duration &duration); + + Server &set_payload_max_length(size_t length); + + bool bind_to_port(const std::string &host, int port, int socket_flags = 0); + int bind_to_any_port(const std::string &host, int socket_flags = 0); + bool listen_after_bind(); + + bool listen(const std::string &host, int port, int socket_flags = 0); + + bool is_running() const; + void wait_until_ready() const; + void stop(); + + std::function new_task_queue; + +protected: + bool process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request); + + std::atomic svr_sock_{INVALID_SOCKET}; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; + time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + time_t idle_interval_sec_ = CPPHTTPLIB_IDLE_INTERVAL_SECOND; + time_t idle_interval_usec_ = CPPHTTPLIB_IDLE_INTERVAL_USECOND; + size_t payload_max_length_ = CPPHTTPLIB_PAYLOAD_MAX_LENGTH; + +private: + using Handlers = + std::vector, Handler>>; + using HandlersForContentReader = + std::vector, + HandlerWithContentReader>>; + + static std::unique_ptr + make_matcher(const std::string &pattern); + + socket_t create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const; + int bind_internal(const std::string &host, int port, int socket_flags); + bool listen_internal(); + + bool routing(Request &req, Response &res, Stream &strm); + bool handle_file_request(const Request &req, Response &res, + bool head = false); + bool dispatch_request(Request &req, Response &res, + const Handlers &handlers) const; + bool dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const; + + bool parse_request_line(const char *s, Request &req) const; + void apply_ranges(const Request &req, Response &res, + std::string &content_type, std::string &boundary) const; + bool write_response(Stream &strm, bool close_connection, Request &req, + Response &res); + bool write_response_with_content(Stream &strm, bool close_connection, + const Request &req, Response &res); + bool write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges); + bool write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type); + bool read_content(Stream &strm, Request &req, Response &res); + bool + read_content_with_content_receiver(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver); + bool read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const; + + virtual bool process_and_close_socket(socket_t sock); + + std::atomic is_running_{false}; + std::atomic done_{false}; + + struct MountPointEntry { + std::string mount_point; + std::string base_dir; + Headers headers; + }; + std::vector base_dirs_; + std::map file_extension_and_mimetype_map_; + std::string default_file_mimetype_ = "application/octet-stream"; + Handler file_request_handler_; + + Handlers get_handlers_; + Handlers post_handlers_; + HandlersForContentReader post_handlers_for_content_reader_; + Handlers put_handlers_; + HandlersForContentReader put_handlers_for_content_reader_; + Handlers patch_handlers_; + HandlersForContentReader patch_handlers_for_content_reader_; + Handlers delete_handlers_; + HandlersForContentReader delete_handlers_for_content_reader_; + Handlers options_handlers_; + + HandlerWithResponse error_handler_; + ExceptionHandler exception_handler_; + HandlerWithResponse pre_routing_handler_; + Handler post_routing_handler_; + Expect100ContinueHandler expect_100_continue_handler_; + + Logger logger_; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = default_socket_options; + + Headers default_headers_; + std::function header_writer_ = + detail::write_headers; +}; + +enum class Error { + Success = 0, + Unknown, + Connection, + BindIPAddress, + Read, + Write, + ExceedRedirectCount, + Canceled, + SSLConnection, + SSLLoadingCerts, + SSLServerVerification, + UnsupportedMultipartBoundaryChars, + Compression, + ConnectionTimeout, + ProxyConnection, + + // For internal use only + SSLPeerCouldBeClosed_, +}; + +std::string to_string(Error error); + +std::ostream &operator<<(std::ostream &os, const Error &obj); + +class Result { +public: + Result() = default; + Result(std::unique_ptr &&res, Error err, + Headers &&request_headers = Headers{}) + : res_(std::move(res)), err_(err), + request_headers_(std::move(request_headers)) {} + // Response + operator bool() const { return res_ != nullptr; } + bool operator==(std::nullptr_t) const { return res_ == nullptr; } + bool operator!=(std::nullptr_t) const { return res_ != nullptr; } + const Response &value() const { return *res_; } + Response &value() { return *res_; } + const Response &operator*() const { return *res_; } + Response &operator*() { return *res_; } + const Response *operator->() const { return res_.get(); } + Response *operator->() { return res_.get(); } + + // Error + Error error() const { return err_; } + + // Request Headers + bool has_request_header(const std::string &key) const; + std::string get_request_header_value(const std::string &key, + size_t id = 0) const; + uint64_t get_request_header_value_u64(const std::string &key, + size_t id = 0) const; + size_t get_request_header_value_count(const std::string &key) const; + +private: + std::unique_ptr res_; + Error err_ = Error::Unknown; + Headers request_headers_; +}; + +class ClientImpl { +public: + explicit ClientImpl(const std::string &host); + + explicit ClientImpl(const std::string &host, int port); + + explicit ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + virtual ~ClientImpl(); + + virtual bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + void set_ca_cert_store(X509_STORE *ca_cert_store); + X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + +protected: + struct Socket { + socket_t sock = INVALID_SOCKET; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSL *ssl = nullptr; +#endif + + bool is_open() const { return sock != INVALID_SOCKET; } + }; + + virtual bool create_and_connect_socket(Socket &socket, Error &error); + + // All of: + // shutdown_ssl + // shutdown_socket + // close_socket + // should ONLY be called when socket_mutex_ is locked. + // Also, shutdown_ssl and close_socket should also NOT be called concurrently + // with a DIFFERENT thread sending requests using that socket. + virtual void shutdown_ssl(Socket &socket, bool shutdown_gracefully); + void shutdown_socket(Socket &socket) const; + void close_socket(Socket &socket); + + bool process_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + + bool write_content_with_provider(Stream &strm, const Request &req, + Error &error) const; + + void copy_settings(const ClientImpl &rhs); + + // Socket endpoint information + const std::string host_; + const int port_; + const std::string host_and_port_; + + // Current open socket + Socket socket_; + mutable std::mutex socket_mutex_; + std::recursive_mutex request_mutex_; + + // These are all protected under socket_mutex + size_t socket_requests_in_flight_ = 0; + std::thread::id socket_requests_are_from_thread_ = std::thread::id(); + bool socket_should_be_closed_when_request_is_done_ = false; + + // Hostname-IP map + std::map addr_map_; + + // Default headers + Headers default_headers_; + + // Header writer + std::function header_writer_ = + detail::write_headers; + + // Settings + std::string client_cert_path_; + std::string client_key_path_; + + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + time_t read_timeout_sec_ = CPPHTTPLIB_READ_TIMEOUT_SECOND; + time_t read_timeout_usec_ = CPPHTTPLIB_READ_TIMEOUT_USECOND; + time_t write_timeout_sec_ = CPPHTTPLIB_WRITE_TIMEOUT_SECOND; + time_t write_timeout_usec_ = CPPHTTPLIB_WRITE_TIMEOUT_USECOND; + + std::string basic_auth_username_; + std::string basic_auth_password_; + std::string bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string digest_auth_username_; + std::string digest_auth_password_; +#endif + + bool keep_alive_ = false; + bool follow_location_ = false; + + bool url_encode_ = true; + + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + SocketOptions socket_options_ = nullptr; + + bool compress_ = false; + bool decompress_ = true; + + std::string interface_; + + std::string proxy_host_; + int proxy_port_ = -1; + + std::string proxy_basic_auth_username_; + std::string proxy_basic_auth_password_; + std::string proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string proxy_digest_auth_username_; + std::string proxy_digest_auth_password_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; + + X509_STORE *ca_cert_store_ = nullptr; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool server_certificate_verification_ = true; +#endif + + Logger logger_; + +private: + bool send_(Request &req, Response &res, Error &error); + Result send_(Request &&req); + + socket_t create_client_socket(Error &error) const; + bool read_response_line(Stream &strm, const Request &req, + Response &res) const; + bool write_request(Stream &strm, Request &req, bool close_connection, + Error &error); + bool redirect(Request &req, Response &res, Error &error); + bool handle_request(Stream &strm, Request &req, Response &res, + bool close_connection, Error &error); + std::unique_ptr send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error); + Result send_with_content_provider( + const std::string &method, const std::string &path, + const Headers &headers, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type); + ContentProviderWithoutLength get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) const; + + std::string adjust_host_string(const std::string &host) const; + + virtual bool process_socket(const Socket &socket, + std::function callback); + virtual bool is_ssl() const; +}; + +class Client { +public: + // Universal interface + explicit Client(const std::string &scheme_host_port); + + explicit Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path); + + // HTTP only interface + explicit Client(const std::string &host, int port); + + explicit Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path); + + Client(Client &&) = default; + + ~Client(); + + bool is_valid() const; + + Result Get(const std::string &path); + Result Get(const std::string &path, const Headers &headers); + Result Get(const std::string &path, Progress progress); + Result Get(const std::string &path, const Headers &headers, + Progress progress); + Result Get(const std::string &path, ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver); + Result Get(const std::string &path, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver); + Result Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, ContentReceiver content_receiver, + Progress progress); + Result Get(const std::string &path, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress); + + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ContentReceiver content_receiver, + Progress progress = nullptr); + Result Get(const std::string &path, const Params ¶ms, + const Headers &headers, ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress = nullptr); + + Result Head(const std::string &path); + Result Head(const std::string &path, const Headers &headers); + + Result Post(const std::string &path); + Result Post(const std::string &path, const Headers &headers); + Result Post(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Post(const std::string &path, const std::string &body, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Post(const std::string &path, const Params ¶ms); + Result Post(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Post(const std::string &path, const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Put(const std::string &path); + Result Put(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, const char *body, + size_t content_length, const std::string &content_type); + Result Put(const std::string &path, const std::string &body, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Put(const std::string &path, size_t content_length, + ContentProvider content_provider, const std::string &content_type); + Result Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Put(const std::string &path, const Params ¶ms); + Result Put(const std::string &path, const Headers &headers, + const Params ¶ms); + Result Put(const std::string &path, const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, const std::string &boundary); + Result Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items); + + Result Patch(const std::string &path); + Result Patch(const std::string &path, const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Patch(const std::string &path, const std::string &body, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + Result Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + size_t content_length, ContentProvider content_provider, + const std::string &content_type); + Result Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type); + + Result Delete(const std::string &path); + Result Delete(const std::string &path, const Headers &headers); + Result Delete(const std::string &path, const char *body, + size_t content_length, const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type); + Result Delete(const std::string &path, const std::string &body, + const std::string &content_type); + Result Delete(const std::string &path, const Headers &headers, + const std::string &body, const std::string &content_type); + + Result Options(const std::string &path); + Result Options(const std::string &path, const Headers &headers); + + bool send(Request &req, Response &res, Error &error); + Result send(const Request &req); + + void stop(); + + std::string host() const; + int port() const; + + size_t is_socket_open() const; + socket_t socket() const; + + void set_hostname_addr_map(std::map addr_map); + + void set_default_headers(Headers headers); + + void + set_header_writer(std::function const &writer); + + void set_address_family(int family); + void set_tcp_nodelay(bool on); + void set_socket_options(SocketOptions socket_options); + + void set_connection_timeout(time_t sec, time_t usec = 0); + template + void + set_connection_timeout(const std::chrono::duration &duration); + + void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + + void set_basic_auth(const std::string &username, const std::string &password); + void set_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_digest_auth(const std::string &username, + const std::string &password); +#endif + + void set_keep_alive(bool on); + void set_follow_location(bool on); + + void set_url_encode(bool on); + + void set_compress(bool on); + + void set_decompress(bool on); + + void set_interface(const std::string &intf); + + void set_proxy(const std::string &host, int port); + void set_proxy_basic_auth(const std::string &username, + const std::string &password); + void set_proxy_bearer_token_auth(const std::string &token); +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_proxy_digest_auth(const std::string &username, + const std::string &password); +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void enable_server_certificate_verification(bool enabled); +#endif + + void set_logger(Logger logger); + + // SSL +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; +#endif + +private: + std::unique_ptr cli_; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + bool is_ssl_ = false; +#endif +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLServer : public Server { +public: + SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path = nullptr, + const char *client_ca_cert_dir_path = nullptr, + const char *private_key_password = nullptr); + + SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store = nullptr); + + SSLServer( + const std::function &setup_ssl_ctx_callback); + + ~SSLServer() override; + + bool is_valid() const override; + + SSL_CTX *ssl_context() const; + +private: + bool process_and_close_socket(socket_t sock) override; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; +}; + +class SSLClient final : public ClientImpl { +public: + explicit SSLClient(const std::string &host); + + explicit SSLClient(const std::string &host, int port); + + explicit SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password = std::string()); + + explicit SSLClient(const std::string &host, int port, X509 *client_cert, + EVP_PKEY *client_key, + const std::string &private_key_password = std::string()); + + ~SSLClient() override; + + bool is_valid() const override; + + void set_ca_cert_store(X509_STORE *ca_cert_store); + void load_ca_cert_store(const char *ca_cert, std::size_t size); + + long get_openssl_verify_result() const; + + SSL_CTX *ssl_context() const; + +private: + bool create_and_connect_socket(Socket &socket, Error &error) override; + void shutdown_ssl(Socket &socket, bool shutdown_gracefully) override; + void shutdown_ssl_impl(Socket &socket, bool shutdown_gracefully); + + bool process_socket(const Socket &socket, + std::function callback) override; + bool is_ssl() const override; + + bool connect_with_proxy(Socket &sock, Response &res, bool &success, + Error &error); + bool initialize_ssl(Socket &socket, Error &error); + + bool load_certs(); + + bool verify_host(X509 *server_cert) const; + bool verify_host_with_subject_alt_name(X509 *server_cert) const; + bool verify_host_with_common_name(X509 *server_cert) const; + bool check_host_name(const char *pattern, size_t pattern_len) const; + + SSL_CTX *ctx_; + std::mutex ctx_mutex_; + std::once_flag initialize_cert_; + + std::vector host_components_; + + long verify_result_ = 0; + + friend class ClientImpl; +}; +#endif + +/* + * Implementation of template methods. + */ + +namespace detail { + +template +inline void duration_to_sec_and_usec(const T &duration, U callback) { + auto sec = std::chrono::duration_cast(duration).count(); + auto usec = std::chrono::duration_cast( + duration - std::chrono::seconds(sec)) + .count(); + callback(static_cast(sec), static_cast(usec)); +} + +inline uint64_t get_header_value_u64(const Headers &headers, + const std::string &key, size_t id, + uint64_t def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { + return std::strtoull(it->second.data(), nullptr, 10); + } + return def; +} + +} // namespace detail + +inline uint64_t Request::get_header_value_u64(const std::string &key, + size_t id) const { + return detail::get_header_value_u64(headers, key, id, 0); +} + +inline uint64_t Response::get_header_value_u64(const std::string &key, + size_t id) const { + return detail::get_header_value_u64(headers, key, id, 0); +} + +template +inline ssize_t Stream::write_format(const char *fmt, const Args &...args) { + const auto bufsiz = 2048; + std::array buf{}; + + auto sn = snprintf(buf.data(), buf.size() - 1, fmt, args...); + if (sn <= 0) { return sn; } + + auto n = static_cast(sn); + + if (n >= buf.size() - 1) { + std::vector glowable_buf(buf.size()); + + while (n >= glowable_buf.size() - 1) { + glowable_buf.resize(glowable_buf.size() * 2); + n = static_cast( + snprintf(&glowable_buf[0], glowable_buf.size() - 1, fmt, args...)); + } + return write(&glowable_buf[0], n); + } else { + return write(buf.data(), n); + } +} + +inline void default_socket_options(socket_t sock) { + int yes = 1; +#ifdef _WIN32 + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&yes), sizeof(yes)); + setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + reinterpret_cast(&yes), sizeof(yes)); +#else +#ifdef SO_REUSEPORT + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, + reinterpret_cast(&yes), sizeof(yes)); +#else + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + reinterpret_cast(&yes), sizeof(yes)); +#endif +#endif +} + +inline const char *status_message(int status) { + switch (status) { + case StatusCode::Continue_100: return "Continue"; + case StatusCode::SwitchingProtocol_101: return "Switching Protocol"; + case StatusCode::Processing_102: return "Processing"; + case StatusCode::EarlyHints_103: return "Early Hints"; + case StatusCode::OK_200: return "OK"; + case StatusCode::Created_201: return "Created"; + case StatusCode::Accepted_202: return "Accepted"; + case StatusCode::NonAuthoritativeInformation_203: + return "Non-Authoritative Information"; + case StatusCode::NoContent_204: return "No Content"; + case StatusCode::ResetContent_205: return "Reset Content"; + case StatusCode::PartialContent_206: return "Partial Content"; + case StatusCode::MultiStatus_207: return "Multi-Status"; + case StatusCode::AlreadyReported_208: return "Already Reported"; + case StatusCode::IMUsed_226: return "IM Used"; + case StatusCode::MultipleChoices_300: return "Multiple Choices"; + case StatusCode::MovedPermanently_301: return "Moved Permanently"; + case StatusCode::Found_302: return "Found"; + case StatusCode::SeeOther_303: return "See Other"; + case StatusCode::NotModified_304: return "Not Modified"; + case StatusCode::UseProxy_305: return "Use Proxy"; + case StatusCode::unused_306: return "unused"; + case StatusCode::TemporaryRedirect_307: return "Temporary Redirect"; + case StatusCode::PermanentRedirect_308: return "Permanent Redirect"; + case StatusCode::BadRequest_400: return "Bad Request"; + case StatusCode::Unauthorized_401: return "Unauthorized"; + case StatusCode::PaymentRequired_402: return "Payment Required"; + case StatusCode::Forbidden_403: return "Forbidden"; + case StatusCode::NotFound_404: return "Not Found"; + case StatusCode::MethodNotAllowed_405: return "Method Not Allowed"; + case StatusCode::NotAcceptable_406: return "Not Acceptable"; + case StatusCode::ProxyAuthenticationRequired_407: + return "Proxy Authentication Required"; + case StatusCode::RequestTimeout_408: return "Request Timeout"; + case StatusCode::Conflict_409: return "Conflict"; + case StatusCode::Gone_410: return "Gone"; + case StatusCode::LengthRequired_411: return "Length Required"; + case StatusCode::PreconditionFailed_412: return "Precondition Failed"; + case StatusCode::PayloadTooLarge_413: return "Payload Too Large"; + case StatusCode::UriTooLong_414: return "URI Too Long"; + case StatusCode::UnsupportedMediaType_415: return "Unsupported Media Type"; + case StatusCode::RangeNotSatisfiable_416: return "Range Not Satisfiable"; + case StatusCode::ExpectationFailed_417: return "Expectation Failed"; + case StatusCode::ImATeapot_418: return "I'm a teapot"; + case StatusCode::MisdirectedRequest_421: return "Misdirected Request"; + case StatusCode::UnprocessableContent_422: return "Unprocessable Content"; + case StatusCode::Locked_423: return "Locked"; + case StatusCode::FailedDependency_424: return "Failed Dependency"; + case StatusCode::TooEarly_425: return "Too Early"; + case StatusCode::UpgradeRequired_426: return "Upgrade Required"; + case StatusCode::PreconditionRequired_428: return "Precondition Required"; + case StatusCode::TooManyRequests_429: return "Too Many Requests"; + case StatusCode::RequestHeaderFieldsTooLarge_431: + return "Request Header Fields Too Large"; + case StatusCode::UnavailableForLegalReasons_451: + return "Unavailable For Legal Reasons"; + case StatusCode::NotImplemented_501: return "Not Implemented"; + case StatusCode::BadGateway_502: return "Bad Gateway"; + case StatusCode::ServiceUnavailable_503: return "Service Unavailable"; + case StatusCode::GatewayTimeout_504: return "Gateway Timeout"; + case StatusCode::HttpVersionNotSupported_505: + return "HTTP Version Not Supported"; + case StatusCode::VariantAlsoNegotiates_506: return "Variant Also Negotiates"; + case StatusCode::InsufficientStorage_507: return "Insufficient Storage"; + case StatusCode::LoopDetected_508: return "Loop Detected"; + case StatusCode::NotExtended_510: return "Not Extended"; + case StatusCode::NetworkAuthenticationRequired_511: + return "Network Authentication Required"; + + default: + case StatusCode::InternalServerError_500: return "Internal Server Error"; + } +} + +inline std::string get_bearer_token_auth(const Request &req) { + if (req.has_header("Authorization")) { + static std::string BearerHeaderPrefix = "Bearer "; + return req.get_header_value("Authorization") + .substr(BearerHeaderPrefix.length()); + } + return ""; +} + +template +inline Server & +Server::set_read_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_write_timeout(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); + return *this; +} + +template +inline Server & +Server::set_idle_interval(const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_idle_interval(sec, usec); }); + return *this; +} + +inline std::string to_string(const Error error) { + switch (error) { + case Error::Success: return "Success (no error)"; + case Error::Connection: return "Could not establish connection"; + case Error::BindIPAddress: return "Failed to bind IP address"; + case Error::Read: return "Failed to read connection"; + case Error::Write: return "Failed to write connection"; + case Error::ExceedRedirectCount: return "Maximum redirect count exceeded"; + case Error::Canceled: return "Connection handling canceled"; + case Error::SSLConnection: return "SSL connection failed"; + case Error::SSLLoadingCerts: return "SSL certificate loading failed"; + case Error::SSLServerVerification: return "SSL server verification failed"; + case Error::UnsupportedMultipartBoundaryChars: + return "Unsupported HTTP multipart boundary characters"; + case Error::Compression: return "Compression failed"; + case Error::ConnectionTimeout: return "Connection timed out"; + case Error::ProxyConnection: return "Proxy connection failed"; + case Error::Unknown: return "Unknown"; + default: break; + } + + return "Invalid"; +} + +inline std::ostream &operator<<(std::ostream &os, const Error &obj) { + os << to_string(obj); + os << " (" << static_cast::type>(obj) << ')'; + return os; +} + +inline uint64_t Result::get_request_header_value_u64(const std::string &key, + size_t id) const { + return detail::get_header_value_u64(request_headers_, key, id, 0); +} + +template +inline void ClientImpl::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + +template +inline void ClientImpl::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void ClientImpl::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void Client::set_connection_timeout( + const std::chrono::duration &duration) { + cli_->set_connection_timeout(duration); +} + +template +inline void +Client::set_read_timeout(const std::chrono::duration &duration) { + cli_->set_read_timeout(duration); +} + +template +inline void +Client::set_write_timeout(const std::chrono::duration &duration) { + cli_->set_write_timeout(duration); +} + +/* + * Forward declarations and types that will be part of the .h file if split into + * .h + .cc. + */ + +std::string hosted_at(const std::string &hostname); + +void hosted_at(const std::string &hostname, std::vector &addrs); + +std::string append_query_params(const std::string &path, const Params ¶ms); + +std::pair make_range_header(const Ranges &ranges); + +std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, + bool is_proxy = false); + +namespace detail { + +std::string encode_query_param(const std::string &value); + +std::string decode_url(const std::string &s, bool convert_plus_to_space); + +void read_file(const std::string &path, std::string &out); + +std::string trim_copy(const std::string &s); + +void split(const char *b, const char *e, char d, + std::function fn); + +void split(const char *b, const char *e, char d, size_t m, + std::function fn); + +bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback); + +socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error); + +const char *get_header_value(const Headers &headers, const std::string &key, + size_t id = 0, const char *def = nullptr); + +std::string params_to_query_str(const Params ¶ms); + +void parse_query_text(const std::string &s, Params ¶ms); + +bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary); + +bool parse_range_header(const std::string &s, Ranges &ranges); + +int close_socket(socket_t sock); + +ssize_t send_socket(socket_t sock, const void *ptr, size_t size, int flags); + +ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags); + +enum class EncodingType { None = 0, Gzip, Brotli }; + +EncodingType encoding_type(const Request &req, const Response &res); + +class BufferStream final : public Stream { +public: + BufferStream() = default; + ~BufferStream() override = default; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + + const std::string &get_buffer() const; + +private: + std::string buffer; + size_t position = 0; +}; + +class compressor { +public: + virtual ~compressor() = default; + + typedef std::function Callback; + virtual bool compress(const char *data, size_t data_length, bool last, + Callback callback) = 0; +}; + +class decompressor { +public: + virtual ~decompressor() = default; + + virtual bool is_valid() const = 0; + + typedef std::function Callback; + virtual bool decompress(const char *data, size_t data_length, + Callback callback) = 0; +}; + +class nocompressor final : public compressor { +public: + ~nocompressor() override = default; + + bool compress(const char *data, size_t data_length, bool /*last*/, + Callback callback) override; +}; + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +class gzip_compressor final : public compressor { +public: + gzip_compressor(); + ~gzip_compressor() override; + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; + +class gzip_decompressor final : public decompressor { +public: + gzip_decompressor(); + ~gzip_decompressor() override; + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + bool is_valid_ = false; + z_stream strm_; +}; +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +class brotli_compressor final : public compressor { +public: + brotli_compressor(); + ~brotli_compressor(); + + bool compress(const char *data, size_t data_length, bool last, + Callback callback) override; + +private: + BrotliEncoderState *state_ = nullptr; +}; + +class brotli_decompressor final : public decompressor { +public: + brotli_decompressor(); + ~brotli_decompressor(); + + bool is_valid() const override; + + bool decompress(const char *data, size_t data_length, + Callback callback) override; + +private: + BrotliDecoderResult decoder_r; + BrotliDecoderState *decoder_s = nullptr; +}; +#endif + +// NOTE: until the read size reaches `fixed_buffer_size`, use `fixed_buffer` +// to store data. The call can set memory on stack for performance. +class stream_line_reader { +public: + stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size); + const char *ptr() const; + size_t size() const; + bool end_with_crlf() const; + bool getline(); + +private: + void append(char c); + + Stream &strm_; + char *fixed_buffer_; + const size_t fixed_buffer_size_; + size_t fixed_buffer_used_size_ = 0; + std::string glowable_buffer_; +}; + +class mmap { +public: + mmap(const char *path); + ~mmap(); + + bool open(const char *path); + void close(); + + bool is_open() const; + size_t size() const; + const char *data() const; + +private: +#if defined(_WIN32) + HANDLE hFile_; + HANDLE hMapping_; +#else + int fd_; +#endif + size_t size_; + void *addr_; +}; + +} // namespace detail + +// ---------------------------------------------------------------------------- + +/* + * Implementation that will be part of the .cc file if split into .h + .cc. + */ + +namespace detail { + +inline bool is_hex(char c, int &v) { + if (0x20 <= c && isdigit(c)) { + v = c - '0'; + return true; + } else if ('A' <= c && c <= 'F') { + v = c - 'A' + 10; + return true; + } else if ('a' <= c && c <= 'f') { + v = c - 'a' + 10; + return true; + } + return false; +} + +inline bool from_hex_to_i(const std::string &s, size_t i, size_t cnt, + int &val) { + if (i >= s.size()) { return false; } + + val = 0; + for (; cnt; i++, cnt--) { + if (!s[i]) { return false; } + auto v = 0; + if (is_hex(s[i], v)) { + val = val * 16 + v; + } else { + return false; + } + } + return true; +} + +inline std::string from_i_to_hex(size_t n) { + static const auto charset = "0123456789abcdef"; + std::string ret; + do { + ret = charset[n & 15] + ret; + n >>= 4; + } while (n > 0); + return ret; +} + +inline size_t to_utf8(int code, char *buff) { + if (code < 0x0080) { + buff[0] = static_cast(code & 0x7F); + return 1; + } else if (code < 0x0800) { + buff[0] = static_cast(0xC0 | ((code >> 6) & 0x1F)); + buff[1] = static_cast(0x80 | (code & 0x3F)); + return 2; + } else if (code < 0xD800) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0xE000) { // D800 - DFFF is invalid... + return 0; + } else if (code < 0x10000) { + buff[0] = static_cast(0xE0 | ((code >> 12) & 0xF)); + buff[1] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[2] = static_cast(0x80 | (code & 0x3F)); + return 3; + } else if (code < 0x110000) { + buff[0] = static_cast(0xF0 | ((code >> 18) & 0x7)); + buff[1] = static_cast(0x80 | ((code >> 12) & 0x3F)); + buff[2] = static_cast(0x80 | ((code >> 6) & 0x3F)); + buff[3] = static_cast(0x80 | (code & 0x3F)); + return 4; + } + + // NOTREACHED + return 0; +} + +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/180947/base64-decode-snippet-in-c +inline std::string base64_encode(const std::string &in) { + static const auto lookup = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + std::string out; + out.reserve(in.size()); + + auto val = 0; + auto valb = -6; + + for (auto c : in) { + val = (val << 8) + static_cast(c); + valb += 8; + while (valb >= 0) { + out.push_back(lookup[(val >> valb) & 0x3F]); + valb -= 6; + } + } + + if (valb > -6) { out.push_back(lookup[((val << 8) >> (valb + 8)) & 0x3F]); } + + while (out.size() % 4) { + out.push_back('='); + } + + return out; +} + +inline bool is_file(const std::string &path) { +#ifdef _WIN32 + return _access_s(path.c_str(), 0) == 0; +#else + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISREG(st.st_mode); +#endif +} + +inline bool is_dir(const std::string &path) { + struct stat st; + return stat(path.c_str(), &st) >= 0 && S_ISDIR(st.st_mode); +} + +inline bool is_valid_path(const std::string &path) { + size_t level = 0; + size_t i = 0; + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + + while (i < path.size()) { + // Read component + auto beg = i; + while (i < path.size() && path[i] != '/') { + if (path[i] == '\0') { + return false; + } else if (path[i] == '\\') { + return false; + } + i++; + } + + auto len = i - beg; + assert(len > 0); + + if (!path.compare(beg, len, ".")) { + ; + } else if (!path.compare(beg, len, "..")) { + if (level == 0) { return false; } + level--; + } else { + level++; + } + + // Skip slash + while (i < path.size() && path[i] == '/') { + i++; + } + } + + return true; +} + +inline std::string encode_query_param(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (auto c : value) { + if (std::isalnum(static_cast(c)) || c == '-' || c == '_' || + c == '.' || c == '!' || c == '~' || c == '*' || c == '\'' || c == '(' || + c == ')') { + escaped << c; + } else { + escaped << std::uppercase; + escaped << '%' << std::setw(2) + << static_cast(static_cast(c)); + escaped << std::nouppercase; + } + } + + return escaped.str(); +} + +inline std::string encode_url(const std::string &s) { + std::string result; + result.reserve(s.size()); + + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case ' ': result += "%20"; break; + case '+': result += "%2B"; break; + case '\r': result += "%0D"; break; + case '\n': result += "%0A"; break; + case '\'': result += "%27"; break; + case ',': result += "%2C"; break; + // case ':': result += "%3A"; break; // ok? probably... + case ';': result += "%3B"; break; + default: + auto c = static_cast(s[i]); + if (c >= 0x80) { + result += '%'; + char hex[4]; + auto len = snprintf(hex, sizeof(hex) - 1, "%02X", c); + assert(len == 2); + result.append(hex, static_cast(len)); + } else { + result += s[i]; + } + break; + } + } + + return result; +} + +inline std::string decode_url(const std::string &s, + bool convert_plus_to_space) { + std::string result; + + for (size_t i = 0; i < s.size(); i++) { + if (s[i] == '%' && i + 1 < s.size()) { + if (s[i + 1] == 'u') { + auto val = 0; + if (from_hex_to_i(s, i + 2, 4, val)) { + // 4 digits Unicode codes + char buff[4]; + size_t len = to_utf8(val, buff); + if (len > 0) { result.append(buff, len); } + i += 5; // 'u0000' + } else { + result += s[i]; + } + } else { + auto val = 0; + if (from_hex_to_i(s, i + 1, 2, val)) { + // 2 digits hex codes + result += static_cast(val); + i += 2; // '00' + } else { + result += s[i]; + } + } + } else if (convert_plus_to_space && s[i] == '+') { + result += ' '; + } else { + result += s[i]; + } + } + + return result; +} + +inline void read_file(const std::string &path, std::string &out) { + std::ifstream fs(path, std::ios_base::binary); + fs.seekg(0, std::ios_base::end); + auto size = fs.tellg(); + fs.seekg(0); + out.resize(static_cast(size)); + fs.read(&out[0], static_cast(size)); +} + +inline std::string file_extension(const std::string &path) { + std::smatch m; + static auto re = std::regex("\\.([a-zA-Z0-9]+)$"); + if (std::regex_search(path, m, re)) { return m[1].str(); } + return std::string(); +} + +inline bool is_space_or_tab(char c) { return c == ' ' || c == '\t'; } + +inline std::pair trim(const char *b, const char *e, size_t left, + size_t right) { + while (b + left < e && is_space_or_tab(b[left])) { + left++; + } + while (right > 0 && is_space_or_tab(b[right - 1])) { + right--; + } + return std::make_pair(left, right); +} + +inline std::string trim_copy(const std::string &s) { + auto r = trim(s.data(), s.data() + s.size(), 0, s.size()); + return s.substr(r.first, r.second - r.first); +} + +inline std::string trim_double_quotes_copy(const std::string &s) { + if (s.length() >= 2 && s.front() == '"' && s.back() == '"') { + return s.substr(1, s.size() - 2); + } + return s; +} + +inline void split(const char *b, const char *e, char d, + std::function fn) { + return split(b, e, d, (std::numeric_limits::max)(), std::move(fn)); +} + +inline void split(const char *b, const char *e, char d, size_t m, + std::function fn) { + size_t i = 0; + size_t beg = 0; + size_t count = 1; + + while (e ? (b + i < e) : (b[i] != '\0')) { + if (b[i] == d && count < m) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + beg = i + 1; + count++; + } + i++; + } + + if (i) { + auto r = trim(b, e, beg, i); + if (r.first < r.second) { fn(&b[r.first], &b[r.second]); } + } +} + +inline stream_line_reader::stream_line_reader(Stream &strm, char *fixed_buffer, + size_t fixed_buffer_size) + : strm_(strm), fixed_buffer_(fixed_buffer), + fixed_buffer_size_(fixed_buffer_size) {} + +inline const char *stream_line_reader::ptr() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_; + } else { + return glowable_buffer_.data(); + } +} + +inline size_t stream_line_reader::size() const { + if (glowable_buffer_.empty()) { + return fixed_buffer_used_size_; + } else { + return glowable_buffer_.size(); + } +} + +inline bool stream_line_reader::end_with_crlf() const { + auto end = ptr() + size(); + return size() >= 2 && end[-2] == '\r' && end[-1] == '\n'; +} + +inline bool stream_line_reader::getline() { + fixed_buffer_used_size_ = 0; + glowable_buffer_.clear(); + + for (size_t i = 0;; i++) { + char byte; + auto n = strm_.read(&byte, 1); + + if (n < 0) { + return false; + } else if (n == 0) { + if (i == 0) { + return false; + } else { + break; + } + } + + append(byte); + + if (byte == '\n') { break; } + } + + return true; +} + +inline void stream_line_reader::append(char c) { + if (fixed_buffer_used_size_ < fixed_buffer_size_ - 1) { + fixed_buffer_[fixed_buffer_used_size_++] = c; + fixed_buffer_[fixed_buffer_used_size_] = '\0'; + } else { + if (glowable_buffer_.empty()) { + assert(fixed_buffer_[fixed_buffer_used_size_] == '\0'); + glowable_buffer_.assign(fixed_buffer_, fixed_buffer_used_size_); + } + glowable_buffer_ += c; + } +} + +inline mmap::mmap(const char *path) +#if defined(_WIN32) + : hFile_(NULL), hMapping_(NULL) +#else + : fd_(-1) +#endif + , + size_(0), addr_(nullptr) { + open(path); +} + +inline mmap::~mmap() { close(); } + +inline bool mmap::open(const char *path) { + close(); + +#if defined(_WIN32) + std::wstring wpath; + for (size_t i = 0; i < strlen(path); i++) { + wpath += path[i]; + } + + hFile_ = ::CreateFile2(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ, + OPEN_EXISTING, NULL); + + if (hFile_ == INVALID_HANDLE_VALUE) { return false; } + + LARGE_INTEGER size{}; + if (!::GetFileSizeEx(hFile_, &size)) { return false; } + size_ = static_cast(size.QuadPart); + + hMapping_ = + ::CreateFileMappingFromApp(hFile_, NULL, PAGE_READONLY, size_, NULL); + + if (hMapping_ == NULL) { + close(); + return false; + } + + addr_ = ::MapViewOfFileFromApp(hMapping_, FILE_MAP_READ, 0, 0); +#else + fd_ = ::open(path, O_RDONLY); + if (fd_ == -1) { return false; } + + struct stat sb; + if (fstat(fd_, &sb) == -1) { + close(); + return false; + } + size_ = static_cast(sb.st_size); + + addr_ = ::mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0); +#endif + + if (addr_ == nullptr) { + close(); + return false; + } + + return true; +} + +inline bool mmap::is_open() const { return addr_ != nullptr; } + +inline size_t mmap::size() const { return size_; } + +inline const char *mmap::data() const { + return static_cast(addr_); +} + +inline void mmap::close() { +#if defined(_WIN32) + if (addr_) { + ::UnmapViewOfFile(addr_); + addr_ = nullptr; + } + + if (hMapping_) { + ::CloseHandle(hMapping_); + hMapping_ = NULL; + } + + if (hFile_ != INVALID_HANDLE_VALUE) { + ::CloseHandle(hFile_); + hFile_ = INVALID_HANDLE_VALUE; + } +#else + if (addr_ != nullptr) { + munmap(addr_, size_); + addr_ = nullptr; + } + + if (fd_ != -1) { + ::close(fd_); + fd_ = -1; + } +#endif + size_ = 0; +} +inline int close_socket(socket_t sock) { +#ifdef _WIN32 + return closesocket(sock); +#else + return close(sock); +#endif +} + +template inline ssize_t handle_EINTR(T fn) { + ssize_t res = 0; + while (true) { + res = fn(); + if (res < 0 && errno == EINTR) { continue; } + break; + } + return res; +} + +inline ssize_t read_socket(socket_t sock, void *ptr, size_t size, int flags) { + return handle_EINTR([&]() { + return recv(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t send_socket(socket_t sock, const void *ptr, size_t size, + int flags) { + return handle_EINTR([&]() { + return send(sock, +#ifdef _WIN32 + static_cast(ptr), static_cast(size), +#else + ptr, size, +#endif + flags); + }); +} + +inline ssize_t select_read(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return -1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), &fds, nullptr, nullptr, &tv); + }); +#endif +} + +inline ssize_t select_write(socket_t sock, time_t sec, time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + return handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return -1; } +#endif + + fd_set fds; + FD_ZERO(&fds); + FD_SET(sock, &fds); + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + return handle_EINTR([&]() { + return select(static_cast(sock + 1), nullptr, &fds, nullptr, &tv); + }); +#endif +} + +inline Error wait_until_socket_is_ready(socket_t sock, time_t sec, + time_t usec) { +#ifdef CPPHTTPLIB_USE_POLL + struct pollfd pfd_read; + pfd_read.fd = sock; + pfd_read.events = POLLIN | POLLOUT; + + auto timeout = static_cast(sec * 1000 + usec / 1000); + + auto poll_res = handle_EINTR([&]() { return poll(&pfd_read, 1, timeout); }); + + if (poll_res == 0) { return Error::ConnectionTimeout; } + + if (poll_res > 0 && pfd_read.revents & (POLLIN | POLLOUT)) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + + return Error::Connection; +#else +#ifndef _WIN32 + if (sock >= FD_SETSIZE) { return Error::Connection; } +#endif + + fd_set fdsr; + FD_ZERO(&fdsr); + FD_SET(sock, &fdsr); + + auto fdsw = fdsr; + auto fdse = fdsr; + + timeval tv; + tv.tv_sec = static_cast(sec); + tv.tv_usec = static_cast(usec); + + auto ret = handle_EINTR([&]() { + return select(static_cast(sock + 1), &fdsr, &fdsw, &fdse, &tv); + }); + + if (ret == 0) { return Error::ConnectionTimeout; } + + if (ret > 0 && (FD_ISSET(sock, &fdsr) || FD_ISSET(sock, &fdsw))) { + auto error = 0; + socklen_t len = sizeof(error); + auto res = getsockopt(sock, SOL_SOCKET, SO_ERROR, + reinterpret_cast(&error), &len); + auto successful = res >= 0 && !error; + return successful ? Error::Success : Error::Connection; + } + return Error::Connection; +#endif +} + +inline bool is_socket_alive(socket_t sock) { + const auto val = detail::select_read(sock, 0, 0); + if (val == 0) { + return true; + } else if (val < 0 && errno == EBADF) { + return false; + } + char buf[1]; + return detail::read_socket(sock, &buf[0], sizeof(buf), MSG_PEEK) > 0; +} + +class SocketStream final : public Stream { +public: + SocketStream(socket_t sock, time_t read_timeout_sec, time_t read_timeout_usec, + time_t write_timeout_sec, time_t write_timeout_usec); + ~SocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; + + std::vector read_buff_; + size_t read_buff_off_ = 0; + size_t read_buff_content_size_ = 0; + + static const size_t read_buff_size_ = 1024l * 4; +}; + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +class SSLSocketStream final : public Stream { +public: + SSLSocketStream(socket_t sock, SSL *ssl, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec); + ~SSLSocketStream() override; + + bool is_readable() const override; + bool is_writable() const override; + ssize_t read(char *ptr, size_t size) override; + ssize_t write(const char *ptr, size_t size) override; + void get_remote_ip_and_port(std::string &ip, int &port) const override; + void get_local_ip_and_port(std::string &ip, int &port) const override; + socket_t socket() const override; + +private: + socket_t sock_; + SSL *ssl_; + time_t read_timeout_sec_; + time_t read_timeout_usec_; + time_t write_timeout_sec_; + time_t write_timeout_usec_; +}; +#endif + +inline bool keep_alive(socket_t sock, time_t keep_alive_timeout_sec) { + using namespace std::chrono; + auto start = steady_clock::now(); + while (true) { + auto val = select_read(sock, 0, 10000); + if (val < 0) { + return false; + } else if (val == 0) { + auto current = steady_clock::now(); + auto duration = duration_cast(current - start); + auto timeout = keep_alive_timeout_sec * 1000; + if (duration.count() > timeout) { return false; } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } else { + return true; + } + } +} + +template +inline bool +process_server_socket_core(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, T callback) { + assert(keep_alive_max_count > 0); + auto ret = false; + auto count = keep_alive_max_count; + while (svr_sock != INVALID_SOCKET && count > 0 && + keep_alive(sock, keep_alive_timeout_sec)) { + auto close_connection = count == 1; + auto connection_closed = false; + ret = callback(close_connection, connection_closed); + if (!ret || connection_closed) { break; } + count--; + } + return ret; +} + +template +inline bool +process_server_socket(const std::atomic &svr_sock, socket_t sock, + size_t keep_alive_max_count, + time_t keep_alive_timeout_sec, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +inline bool process_client_socket(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec, + std::function callback) { + SocketStream strm(sock, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +inline int shutdown_socket(socket_t sock) { +#ifdef _WIN32 + return shutdown(sock, SD_BOTH); +#else + return shutdown(sock, SHUT_RDWR); +#endif +} + +template +socket_t create_socket(const std::string &host, const std::string &ip, int port, + int address_family, int socket_flags, bool tcp_nodelay, + SocketOptions socket_options, + BindOrConnect bind_or_connect) { + // Get address info + const char *node = nullptr; + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (!ip.empty()) { + node = ip.c_str(); + // Ask getaddrinfo to convert IP in c-string to address + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_NUMERICHOST; + } else { + if (!host.empty()) { node = host.c_str(); } + hints.ai_family = address_family; + hints.ai_flags = socket_flags; + } + +#ifndef _WIN32 + if (hints.ai_family == AF_UNIX) { + const auto addrlen = host.length(); + if (addrlen > sizeof(sockaddr_un::sun_path)) { return INVALID_SOCKET; } + + auto sock = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol); + if (sock != INVALID_SOCKET) { + sockaddr_un addr{}; + addr.sun_family = AF_UNIX; + std::copy(host.begin(), host.end(), addr.sun_path); + + hints.ai_addr = reinterpret_cast(&addr); + hints.ai_addrlen = static_cast( + sizeof(addr) - sizeof(addr.sun_path) + addrlen); + + fcntl(sock, F_SETFD, FD_CLOEXEC); + if (socket_options) { socket_options(sock); } + + if (!bind_or_connect(sock, hints)) { + close_socket(sock); + sock = INVALID_SOCKET; + } + } + return sock; + } +#endif + + auto service = std::to_string(port); + + if (getaddrinfo(node, service.c_str(), &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return INVALID_SOCKET; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + // Create a socket +#ifdef _WIN32 + auto sock = + WSASocketW(rp->ai_family, rp->ai_socktype, rp->ai_protocol, nullptr, 0, + WSA_FLAG_NO_HANDLE_INHERIT | WSA_FLAG_OVERLAPPED); + /** + * Since the WSA_FLAG_NO_HANDLE_INHERIT is only supported on Windows 7 SP1 + * and above the socket creation fails on older Windows Systems. + * + * Let's try to create a socket the old way in this case. + * + * Reference: + * https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsasocketa + * + * WSA_FLAG_NO_HANDLE_INHERIT: + * This flag is supported on Windows 7 with SP1, Windows Server 2008 R2 with + * SP1, and later + * + */ + if (sock == INVALID_SOCKET) { + sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + } +#else + auto sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); +#endif + if (sock == INVALID_SOCKET) { continue; } + +#ifndef _WIN32 + if (fcntl(sock, F_SETFD, FD_CLOEXEC) == -1) { + close_socket(sock); + continue; + } +#endif + + if (tcp_nodelay) { + auto yes = 1; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&yes), sizeof(yes)); +#else + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast(&yes), sizeof(yes)); +#endif + } + + if (socket_options) { socket_options(sock); } + + if (rp->ai_family == AF_INET6) { + auto no = 0; +#ifdef _WIN32 + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&no), sizeof(no)); +#else + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&no), sizeof(no)); +#endif + } + + // bind or connect + if (bind_or_connect(sock, *rp)) { + freeaddrinfo(result); + return sock; + } + + close_socket(sock); + } + + freeaddrinfo(result); + return INVALID_SOCKET; +} + +inline void set_nonblocking(socket_t sock, bool nonblocking) { +#ifdef _WIN32 + auto flags = nonblocking ? 1UL : 0UL; + ioctlsocket(sock, FIONBIO, &flags); +#else + auto flags = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, + nonblocking ? (flags | O_NONBLOCK) : (flags & (~O_NONBLOCK))); +#endif +} + +inline bool is_connection_error() { +#ifdef _WIN32 + return WSAGetLastError() != WSAEWOULDBLOCK; +#else + return errno != EINPROGRESS; +#endif +} + +inline bool bind_ip_address(socket_t sock, const std::string &host) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(host.c_str(), "0", &hints, &result)) { return false; } + + auto ret = false; + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &ai = *rp; + if (!::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + ret = true; + break; + } + } + + freeaddrinfo(result); + return ret; +} + +#if !defined _WIN32 && !defined ANDROID && !defined _AIX && !defined __MVS__ +#define USE_IF2IP +#endif + +#ifdef USE_IF2IP +inline std::string if2ip(int address_family, const std::string &ifn) { + struct ifaddrs *ifap; + getifaddrs(&ifap); + std::string addr_candidate; + for (auto ifa = ifap; ifa; ifa = ifa->ifa_next) { + if (ifa->ifa_addr && ifn == ifa->ifa_name && + (AF_UNSPEC == address_family || + ifa->ifa_addr->sa_family == address_family)) { + if (ifa->ifa_addr->sa_family == AF_INET) { + auto sa = reinterpret_cast(ifa->ifa_addr); + char buf[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &sa->sin_addr, buf, INET_ADDRSTRLEN)) { + freeifaddrs(ifap); + return std::string(buf, INET_ADDRSTRLEN); + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + auto sa = reinterpret_cast(ifa->ifa_addr); + if (!IN6_IS_ADDR_LINKLOCAL(&sa->sin6_addr)) { + char buf[INET6_ADDRSTRLEN] = {}; + if (inet_ntop(AF_INET6, &sa->sin6_addr, buf, INET6_ADDRSTRLEN)) { + // equivalent to mac's IN6_IS_ADDR_UNIQUE_LOCAL + auto s6_addr_head = sa->sin6_addr.s6_addr[0]; + if (s6_addr_head == 0xfc || s6_addr_head == 0xfd) { + addr_candidate = std::string(buf, INET6_ADDRSTRLEN); + } else { + freeifaddrs(ifap); + return std::string(buf, INET6_ADDRSTRLEN); + } + } + } + } + } + } + freeifaddrs(ifap); + return addr_candidate; +} +#endif + +inline socket_t create_client_socket( + const std::string &host, const std::string &ip, int port, + int address_family, bool tcp_nodelay, SocketOptions socket_options, + time_t connection_timeout_sec, time_t connection_timeout_usec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, const std::string &intf, Error &error) { + auto sock = create_socket( + host, ip, port, address_family, 0, tcp_nodelay, std::move(socket_options), + [&](socket_t sock2, struct addrinfo &ai) -> bool { + if (!intf.empty()) { +#ifdef USE_IF2IP + auto ip_from_if = if2ip(address_family, intf); + if (ip_from_if.empty()) { ip_from_if = intf; } + if (!bind_ip_address(sock2, ip_from_if)) { + error = Error::BindIPAddress; + return false; + } +#endif + } + + set_nonblocking(sock2, true); + + auto ret = + ::connect(sock2, ai.ai_addr, static_cast(ai.ai_addrlen)); + + if (ret < 0) { + if (is_connection_error()) { + error = Error::Connection; + return false; + } + error = wait_until_socket_is_ready(sock2, connection_timeout_sec, + connection_timeout_usec); + if (error != Error::Success) { return false; } + } + + set_nonblocking(sock2, false); + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec * 1000 + + read_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec); + tv.tv_usec = static_cast(read_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec * 1000 + + write_timeout_usec / 1000); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec); + tv.tv_usec = static_cast(write_timeout_usec); + setsockopt(sock2, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + error = Error::Success; + return true; + }); + + if (sock != INVALID_SOCKET) { + error = Error::Success; + } else { + if (error == Error::Success) { error = Error::Connection; } + } + + return sock; +} + +inline bool get_ip_and_port(const struct sockaddr_storage &addr, + socklen_t addr_len, std::string &ip, int &port) { + if (addr.ss_family == AF_INET) { + port = ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + port = + ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return false; + } + + std::array ipstr{}; + if (getnameinfo(reinterpret_cast(&addr), addr_len, + ipstr.data(), static_cast(ipstr.size()), nullptr, + 0, NI_NUMERICHOST)) { + return false; + } + + ip = ipstr.data(); + return true; +} + +inline void get_local_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (!getsockname(sock, reinterpret_cast(&addr), + &addr_len)) { + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline void get_remote_ip_and_port(socket_t sock, std::string &ip, int &port) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + + if (!getpeername(sock, reinterpret_cast(&addr), + &addr_len)) { +#ifndef _WIN32 + if (addr.ss_family == AF_UNIX) { +#if defined(__linux__) + struct ucred ucred; + socklen_t len = sizeof(ucred); + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0) { + port = ucred.pid; + } +#elif defined(SOL_LOCAL) && defined(SO_PEERPID) // __APPLE__ + pid_t pid; + socklen_t len = sizeof(pid); + if (getsockopt(sock, SOL_LOCAL, SO_PEERPID, &pid, &len) == 0) { + port = pid; + } +#endif + return; + } +#endif + get_ip_and_port(addr, addr_len, ip, port); + } +} + +inline constexpr unsigned int str2tag_core(const char *s, size_t l, + unsigned int h) { + return (l == 0) + ? h + : str2tag_core( + s + 1, l - 1, + // Unsets the 6 high bits of h, therefore no overflow happens + (((std::numeric_limits::max)() >> 6) & + h * 33) ^ + static_cast(*s)); +} + +inline unsigned int str2tag(const std::string &s) { + return str2tag_core(s.data(), s.size(), 0); +} + +namespace udl { + +inline constexpr unsigned int operator"" _t(const char *s, size_t l) { + return str2tag_core(s, l, 0); +} + +} // namespace udl + +inline std::string +find_content_type(const std::string &path, + const std::map &user_data, + const std::string &default_content_type) { + auto ext = file_extension(path); + + auto it = user_data.find(ext); + if (it != user_data.end()) { return it->second; } + + using udl::operator""_t; + + switch (str2tag(ext)) { + default: return default_content_type; + + case "css"_t: return "text/css"; + case "csv"_t: return "text/csv"; + case "htm"_t: + case "html"_t: return "text/html"; + case "js"_t: + case "mjs"_t: return "text/javascript"; + case "txt"_t: return "text/plain"; + case "vtt"_t: return "text/vtt"; + + case "apng"_t: return "image/apng"; + case "avif"_t: return "image/avif"; + case "bmp"_t: return "image/bmp"; + case "gif"_t: return "image/gif"; + case "png"_t: return "image/png"; + case "svg"_t: return "image/svg+xml"; + case "webp"_t: return "image/webp"; + case "ico"_t: return "image/x-icon"; + case "tif"_t: return "image/tiff"; + case "tiff"_t: return "image/tiff"; + case "jpg"_t: + case "jpeg"_t: return "image/jpeg"; + + case "mp4"_t: return "video/mp4"; + case "mpeg"_t: return "video/mpeg"; + case "webm"_t: return "video/webm"; + + case "mp3"_t: return "audio/mp3"; + case "mpga"_t: return "audio/mpeg"; + case "weba"_t: return "audio/webm"; + case "wav"_t: return "audio/wave"; + + case "otf"_t: return "font/otf"; + case "ttf"_t: return "font/ttf"; + case "woff"_t: return "font/woff"; + case "woff2"_t: return "font/woff2"; + + case "7z"_t: return "application/x-7z-compressed"; + case "atom"_t: return "application/atom+xml"; + case "pdf"_t: return "application/pdf"; + case "json"_t: return "application/json"; + case "rss"_t: return "application/rss+xml"; + case "tar"_t: return "application/x-tar"; + case "xht"_t: + case "xhtml"_t: return "application/xhtml+xml"; + case "xslt"_t: return "application/xslt+xml"; + case "xml"_t: return "application/xml"; + case "gz"_t: return "application/gzip"; + case "zip"_t: return "application/zip"; + case "wasm"_t: return "application/wasm"; + } +} + +inline bool can_compress_content_type(const std::string &content_type) { + using udl::operator""_t; + + auto tag = str2tag(content_type); + + switch (tag) { + case "image/svg+xml"_t: + case "application/javascript"_t: + case "application/json"_t: + case "application/xml"_t: + case "application/protobuf"_t: + case "application/xhtml+xml"_t: return true; + + default: + return !content_type.rfind("text/", 0) && tag != "text/event-stream"_t; + } +} + +inline EncodingType encoding_type(const Request &req, const Response &res) { + auto ret = + detail::can_compress_content_type(res.get_header_value("Content-Type")); + if (!ret) { return EncodingType::None; } + + const auto &s = req.get_header_value("Accept-Encoding"); + (void)(s); + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + // TODO: 'Accept-Encoding' has br, not br;q=0 + ret = s.find("br") != std::string::npos; + if (ret) { return EncodingType::Brotli; } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + // TODO: 'Accept-Encoding' has gzip, not gzip;q=0 + ret = s.find("gzip") != std::string::npos; + if (ret) { return EncodingType::Gzip; } +#endif + + return EncodingType::None; +} + +inline bool nocompressor::compress(const char *data, size_t data_length, + bool /*last*/, Callback callback) { + if (!data_length) { return true; } + return callback(data, data_length); +} + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT +inline gzip_compressor::gzip_compressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + is_valid_ = deflateInit2(&strm_, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8, + Z_DEFAULT_STRATEGY) == Z_OK; +} + +inline gzip_compressor::~gzip_compressor() { deflateEnd(&strm_); } + +inline bool gzip_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + assert(is_valid_); + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + auto flush = (last && data_length == 0) ? Z_FINISH : Z_NO_FLUSH; + auto ret = Z_OK; + + std::array buff{}; + do { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = deflate(&strm_, flush); + if (ret == Z_STREAM_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } while (strm_.avail_out == 0); + + assert((flush == Z_FINISH && ret == Z_STREAM_END) || + (flush == Z_NO_FLUSH && ret == Z_OK)); + assert(strm_.avail_in == 0); + } while (data_length > 0); + + return true; +} + +inline gzip_decompressor::gzip_decompressor() { + std::memset(&strm_, 0, sizeof(strm_)); + strm_.zalloc = Z_NULL; + strm_.zfree = Z_NULL; + strm_.opaque = Z_NULL; + + // 15 is the value of wbits, which should be at the maximum possible value + // to ensure that any gzip stream can be decoded. The offset of 32 specifies + // that the stream type should be automatically detected either gzip or + // deflate. + is_valid_ = inflateInit2(&strm_, 32 + 15) == Z_OK; +} + +inline gzip_decompressor::~gzip_decompressor() { inflateEnd(&strm_); } + +inline bool gzip_decompressor::is_valid() const { return is_valid_; } + +inline bool gzip_decompressor::decompress(const char *data, size_t data_length, + Callback callback) { + assert(is_valid_); + + auto ret = Z_OK; + + do { + constexpr size_t max_avail_in = + (std::numeric_limits::max)(); + + strm_.avail_in = static_cast( + (std::min)(data_length, max_avail_in)); + strm_.next_in = const_cast(reinterpret_cast(data)); + + data_length -= strm_.avail_in; + data += strm_.avail_in; + + std::array buff{}; + while (strm_.avail_in > 0 && ret == Z_OK) { + strm_.avail_out = static_cast(buff.size()); + strm_.next_out = reinterpret_cast(buff.data()); + + ret = inflate(&strm_, Z_NO_FLUSH); + + assert(ret != Z_STREAM_ERROR); + switch (ret) { + case Z_NEED_DICT: + case Z_DATA_ERROR: + case Z_MEM_ERROR: inflateEnd(&strm_); return false; + } + + if (!callback(buff.data(), buff.size() - strm_.avail_out)) { + return false; + } + } + + if (ret != Z_OK && ret != Z_STREAM_END) { return false; } + + } while (data_length > 0); + + return true; +} +#endif + +#ifdef CPPHTTPLIB_BROTLI_SUPPORT +inline brotli_compressor::brotli_compressor() { + state_ = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); +} + +inline brotli_compressor::~brotli_compressor() { + BrotliEncoderDestroyInstance(state_); +} + +inline bool brotli_compressor::compress(const char *data, size_t data_length, + bool last, Callback callback) { + std::array buff{}; + + auto operation = last ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS; + auto available_in = data_length; + auto next_in = reinterpret_cast(data); + + for (;;) { + if (last) { + if (BrotliEncoderIsFinished(state_)) { break; } + } else { + if (!available_in) { break; } + } + + auto available_out = buff.size(); + auto next_out = buff.data(); + + if (!BrotliEncoderCompressStream(state_, operation, &available_in, &next_in, + &available_out, &next_out, nullptr)) { + return false; + } + + auto output_bytes = buff.size() - available_out; + if (output_bytes) { + callback(reinterpret_cast(buff.data()), output_bytes); + } + } + + return true; +} + +inline brotli_decompressor::brotli_decompressor() { + decoder_s = BrotliDecoderCreateInstance(0, 0, 0); + decoder_r = decoder_s ? BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT + : BROTLI_DECODER_RESULT_ERROR; +} + +inline brotli_decompressor::~brotli_decompressor() { + if (decoder_s) { BrotliDecoderDestroyInstance(decoder_s); } +} + +inline bool brotli_decompressor::is_valid() const { return decoder_s; } + +inline bool brotli_decompressor::decompress(const char *data, + size_t data_length, + Callback callback) { + if (decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_ERROR) { + return 0; + } + + auto next_in = reinterpret_cast(data); + size_t avail_in = data_length; + size_t total_out; + + decoder_r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT; + + std::array buff{}; + while (decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { + char *next_out = buff.data(); + size_t avail_out = buff.size(); + + decoder_r = BrotliDecoderDecompressStream( + decoder_s, &avail_in, &next_in, &avail_out, + reinterpret_cast(&next_out), &total_out); + + if (decoder_r == BROTLI_DECODER_RESULT_ERROR) { return false; } + + if (!callback(buff.data(), buff.size() - avail_out)) { return false; } + } + + return decoder_r == BROTLI_DECODER_RESULT_SUCCESS || + decoder_r == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; +} +#endif + +inline bool has_header(const Headers &headers, const std::string &key) { + return headers.find(key) != headers.end(); +} + +inline const char *get_header_value(const Headers &headers, + const std::string &key, size_t id, + const char *def) { + auto rng = headers.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second.c_str(); } + return def; +} + +inline bool compare_case_ignore(const std::string &a, const std::string &b) { + if (a.size() != b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; +} + +template +inline bool parse_header(const char *beg, const char *end, T fn) { + // Skip trailing spaces and tabs. + while (beg < end && is_space_or_tab(end[-1])) { + end--; + } + + auto p = beg; + while (p < end && *p != ':') { + p++; + } + + if (p == end) { return false; } + + auto key_end = p; + + if (*p++ != ':') { return false; } + + while (p < end && is_space_or_tab(*p)) { + p++; + } + + if (p < end) { + auto key_len = key_end - beg; + if (!key_len) { return false; } + + auto key = std::string(beg, key_end); + auto val = compare_case_ignore(key, "Location") + ? std::string(p, end) + : decode_url(std::string(p, end), false); + fn(std::move(key), std::move(val)); + return true; + } + + return false; +} + +inline bool read_headers(Stream &strm, Headers &headers) { + const auto bufsiz = 2048; + char buf[bufsiz]; + stream_line_reader line_reader(strm, buf, bufsiz); + + for (;;) { + if (!line_reader.getline()) { return false; } + + // Check if the line ends with CRLF. + auto line_terminator_len = 2; + if (line_reader.end_with_crlf()) { + // Blank line indicates end of headers. + if (line_reader.size() == 2) { break; } +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + } else { + // Blank line indicates end of headers. + if (line_reader.size() == 1) { break; } + line_terminator_len = 1; + } +#else + } else { + continue; // Skip invalid line. + } +#endif + + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + headers.emplace(std::move(key), std::move(val)); + }); + } + + return true; +} + +inline bool read_content_with_length(Stream &strm, uint64_t len, + Progress progress, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return false; } + + if (!out(buf, static_cast(n), r, len)) { return false; } + r += static_cast(n); + + if (progress) { + if (!progress(r, len)) { return false; } + } + } + + return true; +} + +inline void skip_content_with_length(Stream &strm, uint64_t len) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + while (r < len) { + auto read_len = static_cast(len - r); + auto n = strm.read(buf, (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ)); + if (n <= 0) { return; } + r += static_cast(n); + } +} + +inline bool read_content_without_length(Stream &strm, + ContentReceiverWithProgress out) { + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + uint64_t r = 0; + for (;;) { + auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ); + if (n <= 0) { return true; } + + if (!out(buf, static_cast(n), r, 0)) { return false; } + r += static_cast(n); + } + + return true; +} + +template +inline bool read_content_chunked(Stream &strm, T &x, + ContentReceiverWithProgress out) { + const auto bufsiz = 16; + char buf[bufsiz]; + + stream_line_reader line_reader(strm, buf, bufsiz); + + if (!line_reader.getline()) { return false; } + + unsigned long chunk_len; + while (true) { + char *end_ptr; + + chunk_len = std::strtoul(line_reader.ptr(), &end_ptr, 16); + + if (end_ptr == line_reader.ptr()) { return false; } + if (chunk_len == ULONG_MAX) { return false; } + + if (chunk_len == 0) { break; } + + if (!read_content_with_length(strm, chunk_len, nullptr, out)) { + return false; + } + + if (!line_reader.getline()) { return false; } + + if (strcmp(line_reader.ptr(), "\r\n") != 0) { return false; } + + if (!line_reader.getline()) { return false; } + } + + assert(chunk_len == 0); + + // Trailer + if (!line_reader.getline()) { return false; } + + while (strcmp(line_reader.ptr(), "\r\n") != 0) { + if (line_reader.size() > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + + // Exclude line terminator + constexpr auto line_terminator_len = 2; + auto end = line_reader.ptr() + line_reader.size() - line_terminator_len; + + parse_header(line_reader.ptr(), end, + [&](std::string &&key, std::string &&val) { + x.headers.emplace(std::move(key), std::move(val)); + }); + + if (!line_reader.getline()) { return false; } + } + + return true; +} + +inline bool is_chunked_transfer_encoding(const Headers &headers) { + return compare_case_ignore( + get_header_value(headers, "Transfer-Encoding", 0, ""), "chunked"); +} + +template +bool prepare_content_receiver(T &x, int &status, + ContentReceiverWithProgress receiver, + bool decompress, U callback) { + if (decompress) { + std::string encoding = x.get_header_value("Content-Encoding"); + std::unique_ptr decompressor; + + if (encoding == "gzip" || encoding == "deflate") { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } else if (encoding.find("br") != std::string::npos) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + decompressor = detail::make_unique(); +#else + status = StatusCode::UnsupportedMediaType_415; + return false; +#endif + } + + if (decompressor) { + if (decompressor->is_valid()) { + ContentReceiverWithProgress out = [&](const char *buf, size_t n, + uint64_t off, uint64_t len) { + return decompressor->decompress(buf, n, + [&](const char *buf2, size_t n2) { + return receiver(buf2, n2, off, len); + }); + }; + return callback(std::move(out)); + } else { + status = StatusCode::InternalServerError_500; + return false; + } + } + } + + ContentReceiverWithProgress out = [&](const char *buf, size_t n, uint64_t off, + uint64_t len) { + return receiver(buf, n, off, len); + }; + return callback(std::move(out)); +} + +template +bool read_content(Stream &strm, T &x, size_t payload_max_length, int &status, + Progress progress, ContentReceiverWithProgress receiver, + bool decompress) { + return prepare_content_receiver( + x, status, std::move(receiver), decompress, + [&](const ContentReceiverWithProgress &out) { + auto ret = true; + auto exceed_payload_max_length = false; + + if (is_chunked_transfer_encoding(x.headers)) { + ret = read_content_chunked(strm, x, out); + } else if (!has_header(x.headers, "Content-Length")) { + ret = read_content_without_length(strm, out); + } else { + auto len = get_header_value_u64(x.headers, "Content-Length", 0, 0); + if (len > payload_max_length) { + exceed_payload_max_length = true; + skip_content_with_length(strm, len); + ret = false; + } else if (len > 0) { + ret = read_content_with_length(strm, len, std::move(progress), out); + } + } + + if (!ret) { + status = exceed_payload_max_length ? StatusCode::PayloadTooLarge_413 + : StatusCode::BadRequest_400; + } + return ret; + }); +} // namespace detail + +inline ssize_t write_headers(Stream &strm, const Headers &headers) { + ssize_t write_len = 0; + for (const auto &x : headers) { + auto len = + strm.write_format("%s: %s\r\n", x.first.c_str(), x.second.c_str()); + if (len < 0) { return len; } + write_len += len; + } + auto len = strm.write("\r\n"); + if (len < 0) { return len; } + write_len += len; + return write_len; +} + +inline bool write_data(Stream &strm, const char *d, size_t l) { + size_t offset = 0; + while (offset < l) { + auto length = strm.write(d + offset, l - offset); + if (length < 0) { return false; } + offset += static_cast(length); + } + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, T is_shutting_down, + Error &error) { + size_t end_offset = offset + length; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + if (strm.is_writable() && write_data(strm, d, l)) { + offset += l; + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + while (offset < end_offset && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, end_offset - offset, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content(Stream &strm, const ContentProvider &content_provider, + size_t offset, size_t length, + const T &is_shutting_down) { + auto error = Error::Success; + return write_content(strm, content_provider, offset, length, is_shutting_down, + error); +} + +template +inline bool +write_content_without_length(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + offset += l; + if (!strm.is_writable() || !write_data(strm, d, l)) { ok = false; } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + data_sink.done = [&](void) { data_available = false; }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + return false; + } else if (!content_provider(offset, 0, data_sink)) { + return false; + } else if (!ok) { + return false; + } + } + return true; +} + +template +inline bool +write_content_chunked(Stream &strm, const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor, Error &error) { + size_t offset = 0; + auto data_available = true; + auto ok = true; + DataSink data_sink; + + data_sink.write = [&](const char *d, size_t l) -> bool { + if (ok) { + data_available = l > 0; + offset += l; + + std::string payload; + if (compressor.compress(d, l, false, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = + from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + } + } + } else { + ok = false; + } + } + return ok; + }; + + data_sink.is_writable = [&]() -> bool { return strm.is_writable(); }; + + auto done_with_trailer = [&](const Headers *trailer) { + if (!ok) { return; } + + data_available = false; + + std::string payload; + if (!compressor.compress(nullptr, 0, true, + [&](const char *data, size_t data_len) { + payload.append(data, data_len); + return true; + })) { + ok = false; + return; + } + + if (!payload.empty()) { + // Emit chunked response header and footer for each chunk + auto chunk = from_i_to_hex(payload.size()) + "\r\n" + payload + "\r\n"; + if (!strm.is_writable() || + !write_data(strm, chunk.data(), chunk.size())) { + ok = false; + return; + } + } + + static const std::string done_marker("0\r\n"); + if (!write_data(strm, done_marker.data(), done_marker.size())) { + ok = false; + } + + // Trailer + if (trailer) { + for (const auto &kv : *trailer) { + std::string field_line = kv.first + ": " + kv.second + "\r\n"; + if (!write_data(strm, field_line.data(), field_line.size())) { + ok = false; + } + } + } + + static const std::string crlf("\r\n"); + if (!write_data(strm, crlf.data(), crlf.size())) { ok = false; } + }; + + data_sink.done = [&](void) { done_with_trailer(nullptr); }; + + data_sink.done_with_trailer = [&](const Headers &trailer) { + done_with_trailer(&trailer); + }; + + while (data_available && !is_shutting_down()) { + if (!strm.is_writable()) { + error = Error::Write; + return false; + } else if (!content_provider(offset, 0, data_sink)) { + error = Error::Canceled; + return false; + } else if (!ok) { + error = Error::Write; + return false; + } + } + + error = Error::Success; + return true; +} + +template +inline bool write_content_chunked(Stream &strm, + const ContentProvider &content_provider, + const T &is_shutting_down, U &compressor) { + auto error = Error::Success; + return write_content_chunked(strm, content_provider, is_shutting_down, + compressor, error); +} + +template +inline bool redirect(T &cli, Request &req, Response &res, + const std::string &path, const std::string &location, + Error &error) { + Request new_req = req; + new_req.path = path; + new_req.redirect_count_ -= 1; + + if (res.status == StatusCode::SeeOther_303 && + (req.method != "GET" && req.method != "HEAD")) { + new_req.method = "GET"; + new_req.body.clear(); + new_req.headers.clear(); + } + + Response new_res; + + auto ret = cli.send(new_req, new_res, error); + if (ret) { + req = new_req; + res = new_res; + + if (res.location.empty()) { res.location = location; } + } + return ret; +} + +inline std::string params_to_query_str(const Params ¶ms) { + std::string query; + + for (auto it = params.begin(); it != params.end(); ++it) { + if (it != params.begin()) { query += "&"; } + query += it->first; + query += "="; + query += encode_query_param(it->second); + } + return query; +} + +inline void parse_query_text(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(decode_url(key, true), decode_url(val, true)); + } + }); +} + +inline bool parse_multipart_boundary(const std::string &content_type, + std::string &boundary) { + auto boundary_keyword = "boundary="; + auto pos = content_type.find(boundary_keyword); + if (pos == std::string::npos) { return false; } + auto end = content_type.find(';', pos); + auto beg = pos + strlen(boundary_keyword); + boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg)); + return !boundary.empty(); +} + +inline void parse_disposition_params(const std::string &s, Params ¶ms) { + std::set cache; + split(s.data(), s.data() + s.size(), ';', [&](const char *b, const char *e) { + std::string kv(b, e); + if (cache.find(kv) != cache.end()) { return; } + cache.insert(kv); + + std::string key; + std::string val; + split(b, e, '=', [&](const char *b2, const char *e2) { + if (key.empty()) { + key.assign(b2, e2); + } else { + val.assign(b2, e2); + } + }); + + if (!key.empty()) { + params.emplace(trim_double_quotes_copy((key)), + trim_double_quotes_copy((val))); + } + }); +} + +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +inline bool parse_range_header(const std::string &s, Ranges &ranges) { +#else +inline bool parse_range_header(const std::string &s, Ranges &ranges) try { +#endif + static auto re_first_range = std::regex(R"(bytes=(\d*-\d*(?:,\s*\d*-\d*)*))"); + std::smatch m; + if (std::regex_match(s, m, re_first_range)) { + auto pos = static_cast(m.position(1)); + auto len = static_cast(m.length(1)); + auto all_valid_ranges = true; + split(&s[pos], &s[pos + len], ',', [&](const char *b, const char *e) { + if (!all_valid_ranges) { return; } + static auto re_another_range = std::regex(R"(\s*(\d*)-(\d*))"); + std::cmatch cm; + if (std::regex_match(b, e, cm, re_another_range)) { + ssize_t first = -1; + if (!cm.str(1).empty()) { + first = static_cast(std::stoll(cm.str(1))); + } + + ssize_t last = -1; + if (!cm.str(2).empty()) { + last = static_cast(std::stoll(cm.str(2))); + } + + if (first != -1 && last != -1 && first > last) { + all_valid_ranges = false; + return; + } + ranges.emplace_back(std::make_pair(first, last)); + } + }); + return all_valid_ranges; + } + return false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS +} +#else +} catch (...) { return false; } +#endif + +class MultipartFormDataParser { +public: + MultipartFormDataParser() = default; + + void set_boundary(std::string &&boundary) { + boundary_ = boundary; + dash_boundary_crlf_ = dash_ + boundary_ + crlf_; + crlf_dash_boundary_ = crlf_ + dash_ + boundary_; + } + + bool is_valid() const { return is_valid_; } + + bool parse(const char *buf, size_t n, const ContentReceiver &content_callback, + const MultipartContentHeader &header_callback) { + + buf_append(buf, n); + + while (buf_size() > 0) { + switch (state_) { + case 0: { // Initial boundary + buf_erase(buf_find(dash_boundary_crlf_)); + if (dash_boundary_crlf_.size() > buf_size()) { return true; } + if (!buf_start_with(dash_boundary_crlf_)) { return false; } + buf_erase(dash_boundary_crlf_.size()); + state_ = 1; + break; + } + case 1: { // New entry + clear_file_info(); + state_ = 2; + break; + } + case 2: { // Headers + auto pos = buf_find(crlf_); + if (pos > CPPHTTPLIB_HEADER_MAX_LENGTH) { return false; } + while (pos < buf_size()) { + // Empty line + if (pos == 0) { + if (!header_callback(file_)) { + is_valid_ = false; + return false; + } + buf_erase(crlf_.size()); + state_ = 3; + break; + } + + const auto header = buf_head(pos); + + if (!parse_header(header.data(), header.data() + header.size(), + [&](std::string &&, std::string &&) {})) { + is_valid_ = false; + return false; + } + + static const std::string header_content_type = "Content-Type:"; + + if (start_with_case_ignore(header, header_content_type)) { + file_.content_type = + trim_copy(header.substr(header_content_type.size())); + } else { + static const std::regex re_content_disposition( + R"~(^Content-Disposition:\s*form-data;\s*(.*)$)~", + std::regex_constants::icase); + + std::smatch m; + if (std::regex_match(header, m, re_content_disposition)) { + Params params; + parse_disposition_params(m[1], params); + + auto it = params.find("name"); + if (it != params.end()) { + file_.name = it->second; + } else { + is_valid_ = false; + return false; + } + + it = params.find("filename"); + if (it != params.end()) { file_.filename = it->second; } + + it = params.find("filename*"); + if (it != params.end()) { + // Only allow UTF-8 enconnding... + static const std::regex re_rfc5987_encoding( + R"~(^UTF-8''(.+?)$)~", std::regex_constants::icase); + + std::smatch m2; + if (std::regex_match(it->second, m2, re_rfc5987_encoding)) { + file_.filename = decode_url(m2[1], false); // override... + } else { + is_valid_ = false; + return false; + } + } + } + } + buf_erase(pos + crlf_.size()); + pos = buf_find(crlf_); + } + if (state_ != 3) { return true; } + break; + } + case 3: { // Body + if (crlf_dash_boundary_.size() > buf_size()) { return true; } + auto pos = buf_find(crlf_dash_boundary_); + if (pos < buf_size()) { + if (!content_callback(buf_data(), pos)) { + is_valid_ = false; + return false; + } + buf_erase(pos + crlf_dash_boundary_.size()); + state_ = 4; + } else { + auto len = buf_size() - crlf_dash_boundary_.size(); + if (len > 0) { + if (!content_callback(buf_data(), len)) { + is_valid_ = false; + return false; + } + buf_erase(len); + } + return true; + } + break; + } + case 4: { // Boundary + if (crlf_.size() > buf_size()) { return true; } + if (buf_start_with(crlf_)) { + buf_erase(crlf_.size()); + state_ = 1; + } else { + if (dash_.size() > buf_size()) { return true; } + if (buf_start_with(dash_)) { + buf_erase(dash_.size()); + is_valid_ = true; + buf_erase(buf_size()); // Remove epilogue + } else { + return true; + } + } + break; + } + } + } + + return true; + } + +private: + void clear_file_info() { + file_.name.clear(); + file_.filename.clear(); + file_.content_type.clear(); + } + + bool start_with_case_ignore(const std::string &a, + const std::string &b) const { + if (a.size() < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (::tolower(a[i]) != ::tolower(b[i])) { return false; } + } + return true; + } + + const std::string dash_ = "--"; + const std::string crlf_ = "\r\n"; + std::string boundary_; + std::string dash_boundary_crlf_; + std::string crlf_dash_boundary_; + + size_t state_ = 0; + bool is_valid_ = false; + MultipartFormData file_; + + // Buffer + bool start_with(const std::string &a, size_t spos, size_t epos, + const std::string &b) const { + if (epos - spos < b.size()) { return false; } + for (size_t i = 0; i < b.size(); i++) { + if (a[i + spos] != b[i]) { return false; } + } + return true; + } + + size_t buf_size() const { return buf_epos_ - buf_spos_; } + + const char *buf_data() const { return &buf_[buf_spos_]; } + + std::string buf_head(size_t l) const { return buf_.substr(buf_spos_, l); } + + bool buf_start_with(const std::string &s) const { + return start_with(buf_, buf_spos_, buf_epos_, s); + } + + size_t buf_find(const std::string &s) const { + auto c = s.front(); + + size_t off = buf_spos_; + while (off < buf_epos_) { + auto pos = off; + while (true) { + if (pos == buf_epos_) { return buf_size(); } + if (buf_[pos] == c) { break; } + pos++; + } + + auto remaining_size = buf_epos_ - pos; + if (s.size() > remaining_size) { return buf_size(); } + + if (start_with(buf_, pos, buf_epos_, s)) { return pos - buf_spos_; } + + off = pos + 1; + } + + return buf_size(); + } + + void buf_append(const char *data, size_t n) { + auto remaining_size = buf_size(); + if (remaining_size > 0 && buf_spos_ > 0) { + for (size_t i = 0; i < remaining_size; i++) { + buf_[i] = buf_[buf_spos_ + i]; + } + } + buf_spos_ = 0; + buf_epos_ = remaining_size; + + if (remaining_size + n > buf_.size()) { buf_.resize(remaining_size + n); } + + for (size_t i = 0; i < n; i++) { + buf_[buf_epos_ + i] = data[i]; + } + buf_epos_ += n; + } + + void buf_erase(size_t size) { buf_spos_ += size; } + + std::string buf_; + size_t buf_spos_ = 0; + size_t buf_epos_ = 0; +}; + +inline std::string to_lower(const char *beg, const char *end) { + std::string out; + auto it = beg; + while (it != end) { + out += static_cast(::tolower(*it)); + it++; + } + return out; +} + +inline std::string random_string(size_t length) { + static const char data[] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + // std::random_device might actually be deterministic on some + // platforms, but due to lack of support in the c++ standard library, + // doing better requires either some ugly hacks or breaking portability. + static std::random_device seed_gen; + + // Request 128 bits of entropy for initialization + static std::seed_seq seed_sequence{seed_gen(), seed_gen(), seed_gen(), + seed_gen()}; + + static std::mt19937 engine(seed_sequence); + + std::string result; + for (size_t i = 0; i < length; i++) { + result += data[engine() % (sizeof(data) - 1)]; + } + return result; +} + +inline std::string make_multipart_data_boundary() { + return "--cpp-httplib-multipart-data-" + detail::random_string(16); +} + +inline bool is_multipart_boundary_chars_valid(const std::string &boundary) { + auto valid = true; + for (size_t i = 0; i < boundary.size(); i++) { + auto c = boundary[i]; + if (!std::isalnum(c) && c != '-' && c != '_') { + valid = false; + break; + } + } + return valid; +} + +template +inline std::string +serialize_multipart_formdata_item_begin(const T &item, + const std::string &boundary) { + std::string body = "--" + boundary + "\r\n"; + body += "Content-Disposition: form-data; name=\"" + item.name + "\""; + if (!item.filename.empty()) { + body += "; filename=\"" + item.filename + "\""; + } + body += "\r\n"; + if (!item.content_type.empty()) { + body += "Content-Type: " + item.content_type + "\r\n"; + } + body += "\r\n"; + + return body; +} + +inline std::string serialize_multipart_formdata_item_end() { return "\r\n"; } + +inline std::string +serialize_multipart_formdata_finish(const std::string &boundary) { + return "--" + boundary + "--\r\n"; +} + +inline std::string +serialize_multipart_formdata_get_content_type(const std::string &boundary) { + return "multipart/form-data; boundary=" + boundary; +} + +inline std::string +serialize_multipart_formdata(const MultipartFormDataItems &items, + const std::string &boundary, bool finish = true) { + std::string body; + + for (const auto &item : items) { + body += serialize_multipart_formdata_item_begin(item, boundary); + body += item.content + serialize_multipart_formdata_item_end(); + } + + if (finish) { body += serialize_multipart_formdata_finish(boundary); } + + return body; +} + +inline bool range_error(Request &req, Response &res) { + if (!req.ranges.empty() && 200 <= res.status && res.status < 300) { + ssize_t contant_len = static_cast( + res.content_length_ ? res.content_length_ : res.body.size()); + + ssize_t prev_first_pos = -1; + ssize_t prev_last_pos = -1; + size_t overwrapping_count = 0; + + // NOTE: The following Range check is based on '14.2. Range' in RFC 9110 + // 'HTTP Semantics' to avoid potential denial-of-service attacks. + // https://www.rfc-editor.org/rfc/rfc9110#section-14.2 + + // Too many ranges + if (req.ranges.size() > CPPHTTPLIB_RANGE_MAX_COUNT) { return true; } + + for (auto &r : req.ranges) { + auto &first_pos = r.first; + auto &last_pos = r.second; + + if (first_pos == -1 && last_pos == -1) { + first_pos = 0; + last_pos = contant_len; + } + + if (first_pos == -1) { + first_pos = contant_len - last_pos; + last_pos = contant_len - 1; + } + + if (last_pos == -1) { last_pos = contant_len - 1; } + + // Range must be within content length + if (!(0 <= first_pos && first_pos <= last_pos && + last_pos <= contant_len - 1)) { + return true; + } + + // Ranges must be in ascending order + if (first_pos <= prev_first_pos) { return true; } + + // Request must not have more than two overlapping ranges + if (first_pos <= prev_last_pos) { + overwrapping_count++; + if (overwrapping_count > 2) { return true; } + } + + prev_first_pos = (std::max)(prev_first_pos, first_pos); + prev_last_pos = (std::max)(prev_last_pos, last_pos); + } + } + + return false; +} + +inline std::pair +get_range_offset_and_length(Range r, size_t content_length) { + (void)(content_length); // patch to get rid of "unused parameter" on release build + assert(r.first != -1 && r.second != -1); + assert(0 <= r.first && r.first < static_cast(content_length)); + assert(r.first <= r.second && + r.second < static_cast(content_length)); + + return std::make_pair(r.first, static_cast(r.second - r.first) + 1); +} + +inline std::string make_content_range_header_field( + const std::pair &offset_and_length, size_t content_length) { + auto st = offset_and_length.first; + auto ed = st + offset_and_length.second - 1; + + std::string field = "bytes "; + field += std::to_string(st); + field += "-"; + field += std::to_string(ed); + field += "/"; + field += std::to_string(content_length); + return field; +} + +template +bool process_multipart_ranges_data(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length, SToken stoken, + CToken ctoken, Content content) { + for (size_t i = 0; i < req.ranges.size(); i++) { + ctoken("--"); + stoken(boundary); + ctoken("\r\n"); + if (!content_type.empty()) { + ctoken("Content-Type: "); + stoken(content_type); + ctoken("\r\n"); + } + + auto offset_and_length = + get_range_offset_and_length(req.ranges[i], content_length); + + ctoken("Content-Range: "); + stoken(make_content_range_header_field(offset_and_length, content_length)); + ctoken("\r\n"); + ctoken("\r\n"); + + if (!content(offset_and_length.first, offset_and_length.second)) { + return false; + } + ctoken("\r\n"); + } + + ctoken("--"); + stoken(boundary); + ctoken("--"); + + return true; +} + +inline void make_multipart_ranges_data(const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, + std::string &data) { + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data += token; }, + [&](const std::string &token) { data += token; }, + [&](size_t offset, size_t length) { + assert(offset + length <= content_length); + data += res.body.substr(offset, length); + return true; + }); +} + +inline size_t get_multipart_ranges_data_length(const Request &req, + const std::string &boundary, + const std::string &content_type, + size_t content_length) { + size_t data_length = 0; + + process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { data_length += token.size(); }, + [&](const std::string &token) { data_length += token.size(); }, + [&](size_t /*offset*/, size_t length) { + data_length += length; + return true; + }); + + return data_length; +} + +template +inline bool +write_multipart_ranges_data(Stream &strm, const Request &req, Response &res, + const std::string &boundary, + const std::string &content_type, + size_t content_length, const T &is_shutting_down) { + return process_multipart_ranges_data( + req, boundary, content_type, content_length, + [&](const std::string &token) { strm.write(token); }, + [&](const std::string &token) { strm.write(token); }, + [&](size_t offset, size_t length) { + return write_content(strm, res.content_provider_, offset, length, + is_shutting_down); + }); +} + +inline bool expect_content(const Request &req) { + if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH" || + req.method == "PRI" || req.method == "DELETE") { + return true; + } + // TODO: check if Content-Length is set + return false; +} + +inline bool has_crlf(const std::string &s) { + auto p = s.c_str(); + while (*p) { + if (*p == '\r' || *p == '\n') { return true; } + p++; + } + return false; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::string message_digest(const std::string &s, const EVP_MD *algo) { + auto context = std::unique_ptr( + EVP_MD_CTX_new(), EVP_MD_CTX_free); + + unsigned int hash_length = 0; + unsigned char hash[EVP_MAX_MD_SIZE]; + + EVP_DigestInit_ex(context.get(), algo, nullptr); + EVP_DigestUpdate(context.get(), s.c_str(), s.size()); + EVP_DigestFinal_ex(context.get(), hash, &hash_length); + + std::stringstream ss; + for (auto i = 0u; i < hash_length; ++i) { + ss << std::hex << std::setw(2) << std::setfill('0') + << static_cast(hash[i]); + } + + return ss.str(); +} + +inline std::string MD5(const std::string &s) { + return message_digest(s, EVP_md5()); +} + +inline std::string SHA_256(const std::string &s) { + return message_digest(s, EVP_sha256()); +} + +inline std::string SHA_512(const std::string &s) { + return message_digest(s, EVP_sha512()); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +#ifdef _WIN32 +// NOTE: This code came up with the following stackoverflow post: +// https://stackoverflow.com/questions/9507184/can-openssl-on-windows-use-the-system-certificate-store +inline bool load_system_certs_on_windows(X509_STORE *store) { + auto hStore = CertOpenSystemStoreW((HCRYPTPROV_LEGACY)NULL, L"ROOT"); + if (!hStore) { return false; } + + auto result = false; + PCCERT_CONTEXT pContext = NULL; + while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != + nullptr) { + auto encoded_cert = + static_cast(pContext->pbCertEncoded); + + auto x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded); + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + CertFreeCertificateContext(pContext); + CertCloseStore(hStore, 0); + + return result; +} +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX +template +using CFObjectPtr = + std::unique_ptr::type, void (*)(CFTypeRef)>; + +inline void cf_object_ptr_deleter(CFTypeRef obj) { + if (obj) { CFRelease(obj); } +} + +inline bool retrieve_certs_from_keychain(CFObjectPtr &certs) { + CFStringRef keys[] = {kSecClass, kSecMatchLimit, kSecReturnRef}; + CFTypeRef values[] = {kSecClassCertificate, kSecMatchLimitAll, + kCFBooleanTrue}; + + CFObjectPtr query( + CFDictionaryCreate(nullptr, reinterpret_cast(keys), values, + sizeof(keys) / sizeof(keys[0]), + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), + cf_object_ptr_deleter); + + if (!query) { return false; } + + CFTypeRef security_items = nullptr; + if (SecItemCopyMatching(query.get(), &security_items) != errSecSuccess || + CFArrayGetTypeID() != CFGetTypeID(security_items)) { + return false; + } + + certs.reset(reinterpret_cast(security_items)); + return true; +} + +inline bool retrieve_root_certs_from_keychain(CFObjectPtr &certs) { + CFArrayRef root_security_items = nullptr; + if (SecTrustCopyAnchorCertificates(&root_security_items) != errSecSuccess) { + return false; + } + + certs.reset(root_security_items); + return true; +} + +inline bool add_certs_to_x509_store(CFArrayRef certs, X509_STORE *store) { + auto result = false; + for (auto i = 0; i < CFArrayGetCount(certs); ++i) { + const auto cert = reinterpret_cast( + CFArrayGetValueAtIndex(certs, i)); + + if (SecCertificateGetTypeID() != CFGetTypeID(cert)) { continue; } + + CFDataRef cert_data = nullptr; + if (SecItemExport(cert, kSecFormatX509Cert, 0, nullptr, &cert_data) != + errSecSuccess) { + continue; + } + + CFObjectPtr cert_data_ptr(cert_data, cf_object_ptr_deleter); + + auto encoded_cert = static_cast( + CFDataGetBytePtr(cert_data_ptr.get())); + + auto x509 = + d2i_X509(NULL, &encoded_cert, CFDataGetLength(cert_data_ptr.get())); + + if (x509) { + X509_STORE_add_cert(store, x509); + X509_free(x509); + result = true; + } + } + + return result; +} + +inline bool load_system_certs_on_macos(X509_STORE *store) { + auto result = false; + CFObjectPtr certs(nullptr, cf_object_ptr_deleter); + if (retrieve_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store); + } + + if (retrieve_root_certs_from_keychain(certs) && certs) { + result = add_certs_to_x509_store(certs.get(), store) || result; + } + + return result; +} +#endif // TARGET_OS_OSX +#endif // _WIN32 +#endif // CPPHTTPLIB_OPENSSL_SUPPORT + +#ifdef _WIN32 +class WSInit { +public: + WSInit() { + WSADATA wsaData; + if (WSAStartup(0x0002, &wsaData) == 0) is_valid_ = true; + } + + ~WSInit() { + if (is_valid_) WSACleanup(); + } + + bool is_valid_ = false; +}; + +static WSInit wsinit_; +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline std::pair make_digest_authentication_header( + const Request &req, const std::map &auth, + size_t cnonce_count, const std::string &cnonce, const std::string &username, + const std::string &password, bool is_proxy = false) { + std::string nc; + { + std::stringstream ss; + ss << std::setfill('0') << std::setw(8) << std::hex << cnonce_count; + nc = ss.str(); + } + + std::string qop; + if (auth.find("qop") != auth.end()) { + qop = auth.at("qop"); + if (qop.find("auth-int") != std::string::npos) { + qop = "auth-int"; + } else if (qop.find("auth") != std::string::npos) { + qop = "auth"; + } else { + qop.clear(); + } + } + + std::string algo = "MD5"; + if (auth.find("algorithm") != auth.end()) { algo = auth.at("algorithm"); } + + std::string response; + { + auto H = algo == "SHA-256" ? detail::SHA_256 + : algo == "SHA-512" ? detail::SHA_512 + : detail::MD5; + + auto A1 = username + ":" + auth.at("realm") + ":" + password; + + auto A2 = req.method + ":" + req.path; + if (qop == "auth-int") { A2 += ":" + H(req.body); } + + if (qop.empty()) { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + H(A2)); + } else { + response = H(H(A1) + ":" + auth.at("nonce") + ":" + nc + ":" + cnonce + + ":" + qop + ":" + H(A2)); + } + } + + auto opaque = (auth.find("opaque") != auth.end()) ? auth.at("opaque") : ""; + + auto field = "Digest username=\"" + username + "\", realm=\"" + + auth.at("realm") + "\", nonce=\"" + auth.at("nonce") + + "\", uri=\"" + req.path + "\", algorithm=" + algo + + (qop.empty() ? ", response=\"" + : ", qop=" + qop + ", nc=" + nc + ", cnonce=\"" + + cnonce + "\", response=\"") + + response + "\"" + + (opaque.empty() ? "" : ", opaque=\"" + opaque + "\""); + + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, field); +} +#endif + +inline bool parse_www_authenticate(const Response &res, + std::map &auth, + bool is_proxy) { + auto auth_key = is_proxy ? "Proxy-Authenticate" : "WWW-Authenticate"; + if (res.has_header(auth_key)) { + static auto re = std::regex(R"~((?:(?:,\s*)?(.+?)=(?:"(.*?)"|([^,]*))))~"); + auto s = res.get_header_value(auth_key); + auto pos = s.find(' '); + if (pos != std::string::npos) { + auto type = s.substr(0, pos); + if (type == "Basic") { + return false; + } else if (type == "Digest") { + s = s.substr(pos + 1); + auto beg = std::sregex_iterator(s.begin(), s.end(), re); + for (auto i = beg; i != std::sregex_iterator(); ++i) { + const auto &m = *i; + auto key = s.substr(static_cast(m.position(1)), + static_cast(m.length(1))); + auto val = m.length(2) > 0 + ? s.substr(static_cast(m.position(2)), + static_cast(m.length(2))) + : s.substr(static_cast(m.position(3)), + static_cast(m.length(3))); + auth[key] = val; + } + return true; + } + } + } + return false; +} + +class ContentProviderAdapter { +public: + explicit ContentProviderAdapter( + ContentProviderWithoutLength &&content_provider) + : content_provider_(content_provider) {} + + bool operator()(size_t offset, size_t, DataSink &sink) { + return content_provider_(offset, sink); + } + +private: + ContentProviderWithoutLength content_provider_; +}; + +} // namespace detail + +inline std::string hosted_at(const std::string &hostname) { + std::vector addrs; + hosted_at(hostname, addrs); + if (addrs.empty()) { return std::string(); } + return addrs[0]; +} + +inline void hosted_at(const std::string &hostname, + std::vector &addrs) { + struct addrinfo hints; + struct addrinfo *result; + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + + if (getaddrinfo(hostname.c_str(), nullptr, &hints, &result)) { +#if defined __linux__ && !defined __ANDROID__ + res_init(); +#endif + return; + } + + for (auto rp = result; rp; rp = rp->ai_next) { + const auto &addr = + *reinterpret_cast(rp->ai_addr); + std::string ip; + auto dummy = -1; + if (detail::get_ip_and_port(addr, sizeof(struct sockaddr_storage), ip, + dummy)) { + addrs.push_back(ip); + } + } + + freeaddrinfo(result); +} + +inline std::string append_query_params(const std::string &path, + const Params ¶ms) { + std::string path_with_query = path; + const static std::regex re("[^?]+\\?.*"); + auto delm = std::regex_match(path, re) ? '&' : '?'; + path_with_query += delm + detail::params_to_query_str(params); + return path_with_query; +} + +// Header utilities +inline std::pair +make_range_header(const Ranges &ranges) { + std::string field = "bytes="; + auto i = 0; + for (const auto &r : ranges) { + if (i != 0) { field += ", "; } + if (r.first != -1) { field += std::to_string(r.first); } + field += '-'; + if (r.second != -1) { field += std::to_string(r.second); } + i++; + } + return std::make_pair("Range", std::move(field)); +} + +inline std::pair +make_basic_authentication_header(const std::string &username, + const std::string &password, bool is_proxy) { + auto field = "Basic " + detail::base64_encode(username + ":" + password); + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +inline std::pair +make_bearer_token_authentication_header(const std::string &token, + bool is_proxy = false) { + auto field = "Bearer " + token; + auto key = is_proxy ? "Proxy-Authorization" : "Authorization"; + return std::make_pair(key, std::move(field)); +} + +// Request implementation +inline bool Request::has_header(const std::string &key) const { + return detail::has_header(headers, key); +} + +inline std::string Request::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Request::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Request::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline bool Request::has_param(const std::string &key) const { + return params.find(key) != params.end(); +} + +inline std::string Request::get_param_value(const std::string &key, + size_t id) const { + auto rng = params.equal_range(key); + auto it = rng.first; + std::advance(it, static_cast(id)); + if (it != rng.second) { return it->second; } + return std::string(); +} + +inline size_t Request::get_param_value_count(const std::string &key) const { + auto r = params.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline bool Request::is_multipart_form_data() const { + const auto &content_type = get_header_value("Content-Type"); + return !content_type.rfind("multipart/form-data", 0); +} + +inline bool Request::has_file(const std::string &key) const { + return files.find(key) != files.end(); +} + +inline MultipartFormData Request::get_file_value(const std::string &key) const { + auto it = files.find(key); + if (it != files.end()) { return it->second; } + return MultipartFormData(); +} + +inline std::vector +Request::get_file_values(const std::string &key) const { + std::vector values; + auto rng = files.equal_range(key); + for (auto it = rng.first; it != rng.second; it++) { + values.push_back(it->second); + } + return values; +} + +// Response implementation +inline bool Response::has_header(const std::string &key) const { + return headers.find(key) != headers.end(); +} + +inline std::string Response::get_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(headers, key, id, ""); +} + +inline size_t Response::get_header_value_count(const std::string &key) const { + auto r = headers.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +inline void Response::set_header(const std::string &key, + const std::string &val) { + if (!detail::has_crlf(key) && !detail::has_crlf(val)) { + headers.emplace(key, val); + } +} + +inline void Response::set_redirect(const std::string &url, int stat) { + if (!detail::has_crlf(url)) { + set_header("Location", url); + if (300 <= stat && stat < 400) { + this->status = stat; + } else { + this->status = StatusCode::Found_302; + } + } +} + +inline void Response::set_content(const char *s, size_t n, + const std::string &content_type) { + body.assign(s, n); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content(const std::string &s, + const std::string &content_type) { + set_content(s.data(), s.size(), content_type); +} + +inline void Response::set_content(std::string &&s, + const std::string &content_type) { + body = std::move(s); + + auto rng = headers.equal_range("Content-Type"); + headers.erase(rng.first, rng.second); + set_header("Content-Type", content_type); +} + +inline void Response::set_content_provider( + size_t in_length, const std::string &content_type, ContentProvider provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = in_length; + if (in_length > 0) { content_provider_ = std::move(provider); } + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = false; +} + +inline void Response::set_chunked_content_provider( + const std::string &content_type, ContentProviderWithoutLength provider, + ContentProviderResourceReleaser resource_releaser) { + set_header("Content-Type", content_type); + content_length_ = 0; + content_provider_ = detail::ContentProviderAdapter(std::move(provider)); + content_provider_resource_releaser_ = std::move(resource_releaser); + is_chunked_content_provider_ = true; +} + +// Result implementation +inline bool Result::has_request_header(const std::string &key) const { + return request_headers_.find(key) != request_headers_.end(); +} + +inline std::string Result::get_request_header_value(const std::string &key, + size_t id) const { + return detail::get_header_value(request_headers_, key, id, ""); +} + +inline size_t +Result::get_request_header_value_count(const std::string &key) const { + auto r = request_headers_.equal_range(key); + return static_cast(std::distance(r.first, r.second)); +} + +// Stream implementation +inline ssize_t Stream::write(const char *ptr) { + return write(ptr, strlen(ptr)); +} + +inline ssize_t Stream::write(const std::string &s) { + return write(s.data(), s.size()); +} + +namespace detail { + +// Socket stream implementation +inline SocketStream::SocketStream(socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec), read_buff_(read_buff_size_, 0) {} + +inline SocketStream::~SocketStream() = default; + +inline bool SocketStream::is_readable() const { + return select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SocketStream::read(char *ptr, size_t size) { +#ifdef _WIN32 + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#else + size = (std::min)(size, + static_cast((std::numeric_limits::max)())); +#endif + + if (read_buff_off_ < read_buff_content_size_) { + auto remaining_size = read_buff_content_size_ - read_buff_off_; + if (size <= remaining_size) { + memcpy(ptr, read_buff_.data() + read_buff_off_, size); + read_buff_off_ += size; + return static_cast(size); + } else { + memcpy(ptr, read_buff_.data() + read_buff_off_, remaining_size); + read_buff_off_ += remaining_size; + return static_cast(remaining_size); + } + } + + if (!is_readable()) { return -1; } + + read_buff_off_ = 0; + read_buff_content_size_ = 0; + + if (size < read_buff_size_) { + auto n = read_socket(sock_, read_buff_.data(), read_buff_size_, + CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { + return n; + } else if (n <= static_cast(size)) { + memcpy(ptr, read_buff_.data(), static_cast(n)); + return n; + } else { + memcpy(ptr, read_buff_.data(), size); + read_buff_off_ = size; + read_buff_content_size_ = static_cast(n); + return static_cast(size); + } + } else { + return read_socket(sock_, ptr, size, CPPHTTPLIB_RECV_FLAGS); + } +} + +inline ssize_t SocketStream::write(const char *ptr, size_t size) { + if (!is_writable()) { return -1; } + +#if defined(_WIN32) && !defined(_WIN64) + size = + (std::min)(size, static_cast((std::numeric_limits::max)())); +#endif + + return send_socket(sock_, ptr, size, CPPHTTPLIB_SEND_FLAGS); +} + +inline void SocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + return detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + return detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SocketStream::socket() const { return sock_; } + +// Buffer stream implementation +inline bool BufferStream::is_readable() const { return true; } + +inline bool BufferStream::is_writable() const { return true; } + +inline ssize_t BufferStream::read(char *ptr, size_t size) { +#if defined(_MSC_VER) && _MSC_VER < 1910 + auto len_read = buffer._Copy_s(ptr, size, size, position); +#else + auto len_read = buffer.copy(ptr, size, position); +#endif + position += static_cast(len_read); + return static_cast(len_read); +} + +inline ssize_t BufferStream::write(const char *ptr, size_t size) { + buffer.append(ptr, size); + return static_cast(size); +} + +inline void BufferStream::get_remote_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline void BufferStream::get_local_ip_and_port(std::string & /*ip*/, + int & /*port*/) const {} + +inline socket_t BufferStream::socket() const { return 0; } + +inline const std::string &BufferStream::get_buffer() const { return buffer; } + +inline PathParamsMatcher::PathParamsMatcher(const std::string &pattern) { + // One past the last ending position of a path param substring + std::size_t last_param_end = 0; + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + // Needed to ensure that parameter names are unique during matcher + // construction + // If exceptions are disabled, only last duplicate path + // parameter will be set + std::unordered_set param_name_set; +#endif + + while (true) { + const auto marker_pos = pattern.find(marker, last_param_end); + if (marker_pos == std::string::npos) { break; } + + static_fragments_.push_back( + pattern.substr(last_param_end, marker_pos - last_param_end)); + + const auto param_name_start = marker_pos + 1; + + auto sep_pos = pattern.find(separator, param_name_start); + if (sep_pos == std::string::npos) { sep_pos = pattern.length(); } + + auto param_name = + pattern.substr(param_name_start, sep_pos - param_name_start); + +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + if (param_name_set.find(param_name) != param_name_set.cend()) { + std::string msg = "Encountered path parameter '" + param_name + + "' multiple times in route pattern '" + pattern + "'."; + throw std::invalid_argument(msg); + } +#endif + + param_names_.push_back(std::move(param_name)); + + last_param_end = sep_pos + 1; + } + + if (last_param_end < pattern.length()) { + static_fragments_.push_back(pattern.substr(last_param_end)); + } +} + +inline bool PathParamsMatcher::match(Request &request) const { + request.matches = std::smatch(); + request.path_params.clear(); + request.path_params.reserve(param_names_.size()); + + // One past the position at which the path matched the pattern last time + std::size_t starting_pos = 0; + for (size_t i = 0; i < static_fragments_.size(); ++i) { + const auto &fragment = static_fragments_[i]; + + if (starting_pos + fragment.length() > request.path.length()) { + return false; + } + + // Avoid unnecessary allocation by using strncmp instead of substr + + // comparison + if (std::strncmp(request.path.c_str() + starting_pos, fragment.c_str(), + fragment.length()) != 0) { + return false; + } + + starting_pos += fragment.length(); + + // Should only happen when we have a static fragment after a param + // Example: '/users/:id/subscriptions' + // The 'subscriptions' fragment here does not have a corresponding param + if (i >= param_names_.size()) { continue; } + + auto sep_pos = request.path.find(separator, starting_pos); + if (sep_pos == std::string::npos) { sep_pos = request.path.length(); } + + const auto ¶m_name = param_names_[i]; + + request.path_params.emplace( + param_name, request.path.substr(starting_pos, sep_pos - starting_pos)); + + // Mark everythin up to '/' as matched + starting_pos = sep_pos + 1; + } + // Returns false if the path is longer than the pattern + return starting_pos >= request.path.length(); +} + +inline bool RegexMatcher::match(Request &request) const { + request.path_params.clear(); + return std::regex_match(request.path, request.matches, regex_); +} + +} // namespace detail + +// HTTP server implementation +inline Server::Server() + : new_task_queue( + [] { return new ThreadPool(CPPHTTPLIB_THREAD_POOL_COUNT); }) { +#ifndef _WIN32 + signal(SIGPIPE, SIG_IGN); +#endif +} + +inline Server::~Server() = default; + +inline std::unique_ptr +Server::make_matcher(const std::string &pattern) { + if (pattern.find("/:") != std::string::npos) { + return detail::make_unique(pattern); + } else { + return detail::make_unique(pattern); + } +} + +inline Server &Server::Get(const std::string &pattern, Handler handler) { + get_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, Handler handler) { + post_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Post(const std::string &pattern, + HandlerWithContentReader handler) { + post_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, Handler handler) { + put_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Put(const std::string &pattern, + HandlerWithContentReader handler) { + put_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, Handler handler) { + patch_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Patch(const std::string &pattern, + HandlerWithContentReader handler) { + patch_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, Handler handler) { + delete_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline Server &Server::Delete(const std::string &pattern, + HandlerWithContentReader handler) { + delete_handlers_for_content_reader_.emplace_back(make_matcher(pattern), + std::move(handler)); + return *this; +} + +inline Server &Server::Options(const std::string &pattern, Handler handler) { + options_handlers_.emplace_back(make_matcher(pattern), std::move(handler)); + return *this; +} + +inline bool Server::set_base_dir(const std::string &dir, + const std::string &mount_point) { + return set_mount_point(mount_point, dir); +} + +inline bool Server::set_mount_point(const std::string &mount_point, + const std::string &dir, Headers headers) { + if (detail::is_dir(dir)) { + std::string mnt = !mount_point.empty() ? mount_point : "/"; + if (!mnt.empty() && mnt[0] == '/') { + base_dirs_.push_back({mnt, dir, std::move(headers)}); + return true; + } + } + return false; +} + +inline bool Server::remove_mount_point(const std::string &mount_point) { + for (auto it = base_dirs_.begin(); it != base_dirs_.end(); ++it) { + if (it->mount_point == mount_point) { + base_dirs_.erase(it); + return true; + } + } + return false; +} + +inline Server & +Server::set_file_extension_and_mimetype_mapping(const std::string &ext, + const std::string &mime) { + file_extension_and_mimetype_map_[ext] = mime; + return *this; +} + +inline Server &Server::set_default_file_mimetype(const std::string &mime) { + default_file_mimetype_ = mime; + return *this; +} + +inline Server &Server::set_file_request_handler(Handler handler) { + file_request_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(HandlerWithResponse handler) { + error_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_error_handler(Handler handler) { + error_handler_ = [handler](const Request &req, Response &res) { + handler(req, res); + return HandlerResponse::Handled; + }; + return *this; +} + +inline Server &Server::set_exception_handler(ExceptionHandler handler) { + exception_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_pre_routing_handler(HandlerWithResponse handler) { + pre_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_post_routing_handler(Handler handler) { + post_routing_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_logger(Logger logger) { + logger_ = std::move(logger); + return *this; +} + +inline Server & +Server::set_expect_100_continue_handler(Expect100ContinueHandler handler) { + expect_100_continue_handler_ = std::move(handler); + return *this; +} + +inline Server &Server::set_address_family(int family) { + address_family_ = family; + return *this; +} + +inline Server &Server::set_tcp_nodelay(bool on) { + tcp_nodelay_ = on; + return *this; +} + +inline Server &Server::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); + return *this; +} + +inline Server &Server::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); + return *this; +} + +inline Server &Server::set_header_writer( + std::function const &writer) { + header_writer_ = writer; + return *this; +} + +inline Server &Server::set_keep_alive_max_count(size_t count) { + keep_alive_max_count_ = count; + return *this; +} + +inline Server &Server::set_keep_alive_timeout(time_t sec) { + keep_alive_timeout_sec_ = sec; + return *this; +} + +inline Server &Server::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; + return *this; +} + +inline Server &Server::set_idle_interval(time_t sec, time_t usec) { + idle_interval_sec_ = sec; + idle_interval_usec_ = usec; + return *this; +} + +inline Server &Server::set_payload_max_length(size_t length) { + payload_max_length_ = length; + return *this; +} + +inline bool Server::bind_to_port(const std::string &host, int port, + int socket_flags) { + return bind_internal(host, port, socket_flags) >= 0; +} +inline int Server::bind_to_any_port(const std::string &host, int socket_flags) { + return bind_internal(host, 0, socket_flags); +} + +inline bool Server::listen_after_bind() { + auto se = detail::scope_exit([&]() { done_ = true; }); + return listen_internal(); +} + +inline bool Server::listen(const std::string &host, int port, + int socket_flags) { + auto se = detail::scope_exit([&]() { done_ = true; }); + return bind_to_port(host, port, socket_flags) && listen_internal(); +} + +inline bool Server::is_running() const { return is_running_; } + +inline void Server::wait_until_ready() const { + while (!is_running() && !done_) { + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } +} + +inline void Server::stop() { + if (is_running_) { + assert(svr_sock_ != INVALID_SOCKET); + std::atomic sock(svr_sock_.exchange(INVALID_SOCKET)); + detail::shutdown_socket(sock); + detail::close_socket(sock); + } +} + +inline bool Server::parse_request_line(const char *s, Request &req) const { + auto len = strlen(s); + if (len < 2 || s[len - 2] != '\r' || s[len - 1] != '\n') { return false; } + len -= 2; + + { + size_t count = 0; + + detail::split(s, s + len, ' ', [&](const char *b, const char *e) { + switch (count) { + case 0: req.method = std::string(b, e); break; + case 1: req.target = std::string(b, e); break; + case 2: req.version = std::string(b, e); break; + default: break; + } + count++; + }); + + if (count != 3) { return false; } + } + + static const std::set methods{ + "GET", "HEAD", "POST", "PUT", "DELETE", + "CONNECT", "OPTIONS", "TRACE", "PATCH", "PRI"}; + + if (methods.find(req.method) == methods.end()) { return false; } + + if (req.version != "HTTP/1.1" && req.version != "HTTP/1.0") { return false; } + + { + // Skip URL fragment + for (size_t i = 0; i < req.target.size(); i++) { + if (req.target[i] == '#') { + req.target.erase(i); + break; + } + } + + size_t count = 0; + + detail::split(req.target.data(), req.target.data() + req.target.size(), '?', + 2, [&](const char *b, const char *e) { + switch (count) { + case 0: + req.path = detail::decode_url(std::string(b, e), false); + break; + case 1: { + if (e - b > 0) { + detail::parse_query_text(std::string(b, e), req.params); + } + break; + } + default: break; + } + count++; + }); + + if (count > 2) { return false; } + } + + return true; +} + +inline bool Server::write_response(Stream &strm, bool close_connection, + Request &req, Response &res) { + // NOTE: `req.ranges` should be empty, otherwise it will be applied + // incorrectly to the error content. + req.ranges.clear(); + return write_response_core(strm, close_connection, req, res, false); +} + +inline bool Server::write_response_with_content(Stream &strm, + bool close_connection, + const Request &req, + Response &res) { + return write_response_core(strm, close_connection, req, res, true); +} + +inline bool Server::write_response_core(Stream &strm, bool close_connection, + const Request &req, Response &res, + bool need_apply_ranges) { + assert(res.status != -1); + + if (400 <= res.status && error_handler_ && + error_handler_(req, res) == HandlerResponse::Handled) { + need_apply_ranges = true; + } + + std::string content_type; + std::string boundary; + if (need_apply_ranges) { apply_ranges(req, res, content_type, boundary); } + + // Prepare additional headers + if (close_connection || req.get_header_value("Connection") == "close") { + res.set_header("Connection", "close"); + } else { + std::stringstream ss; + ss << "timeout=" << keep_alive_timeout_sec_ + << ", max=" << keep_alive_max_count_; + res.set_header("Keep-Alive", ss.str()); + } + + if (!res.has_header("Content-Type") && + (!res.body.empty() || res.content_length_ > 0 || res.content_provider_)) { + res.set_header("Content-Type", "text/plain"); + } + + if (!res.has_header("Content-Length") && res.body.empty() && + !res.content_length_ && !res.content_provider_) { + res.set_header("Content-Length", "0"); + } + + if (!res.has_header("Accept-Ranges") && req.method == "HEAD") { + res.set_header("Accept-Ranges", "bytes"); + } + + if (post_routing_handler_) { post_routing_handler_(req, res); } + + // Response line and headers + { + detail::BufferStream bstrm; + + if (!bstrm.write_format("HTTP/1.1 %d %s\r\n", res.status, + status_message(res.status))) { + return false; + } + + if (!header_writer_(bstrm, res.headers)) { return false; } + + // Flush buffer + auto &data = bstrm.get_buffer(); + detail::write_data(strm, data.data(), data.size()); + } + + // Body + auto ret = true; + if (req.method != "HEAD") { + if (!res.body.empty()) { + if (!detail::write_data(strm, res.body.data(), res.body.size())) { + ret = false; + } + } else if (res.content_provider_) { + if (write_content_with_provider(strm, req, res, boundary, content_type)) { + res.content_provider_success_ = true; + } else { + ret = false; + } + } + } + + // Log + if (logger_) { logger_(req, res); } + + return ret; +} + +inline bool +Server::write_content_with_provider(Stream &strm, const Request &req, + Response &res, const std::string &boundary, + const std::string &content_type) { + auto is_shutting_down = [this]() { + return this->svr_sock_ == INVALID_SOCKET; + }; + + if (res.content_length_ > 0) { + if (req.ranges.empty()) { + return detail::write_content(strm, res.content_provider_, 0, + res.content_length_, is_shutting_down); + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + return detail::write_content(strm, res.content_provider_, + offset_and_length.first, + offset_and_length.second, is_shutting_down); + } else { + return detail::write_multipart_ranges_data( + strm, req, res, boundary, content_type, res.content_length_, + is_shutting_down); + } + } else { + if (res.is_chunked_content_provider_) { + auto type = detail::encoding_type(req, res); + + std::unique_ptr compressor; + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); +#endif + } else { + compressor = detail::make_unique(); + } + assert(compressor != nullptr); + + return detail::write_content_chunked(strm, res.content_provider_, + is_shutting_down, *compressor); + } else { + return detail::write_content_without_length(strm, res.content_provider_, + is_shutting_down); + } + } +} + +inline bool Server::read_content(Stream &strm, Request &req, Response &res) { + MultipartFormDataMap::iterator cur; + auto file_count = 0; + if (read_content_core( + strm, req, res, + // Regular + [&](const char *buf, size_t n) { + if (req.body.size() + n > req.body.max_size()) { return false; } + req.body.append(buf, n); + return true; + }, + // Multipart + [&](const MultipartFormData &file) { + if (file_count++ == CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT) { + return false; + } + cur = req.files.emplace(file.name, file); + return true; + }, + [&](const char *buf, size_t n) { + auto &content = cur->second.content; + if (content.size() + n > content.max_size()) { return false; } + content.append(buf, n); + return true; + })) { + const auto &content_type = req.get_header_value("Content-Type"); + if (!content_type.find("application/x-www-form-urlencoded")) { + if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) { + res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414? + return false; + } + detail::parse_query_text(req.body, req.params); + } + return true; + } + return false; +} + +inline bool Server::read_content_with_content_receiver( + Stream &strm, Request &req, Response &res, ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) { + return read_content_core(strm, req, res, std::move(receiver), + std::move(multipart_header), + std::move(multipart_receiver)); +} + +inline bool +Server::read_content_core(Stream &strm, Request &req, Response &res, + ContentReceiver receiver, + MultipartContentHeader multipart_header, + ContentReceiver multipart_receiver) const { + detail::MultipartFormDataParser multipart_form_data_parser; + ContentReceiverWithProgress out; + + if (req.is_multipart_form_data()) { + const auto &content_type = req.get_header_value("Content-Type"); + std::string boundary; + if (!detail::parse_multipart_boundary(content_type, boundary)) { + res.status = StatusCode::BadRequest_400; + return false; + } + + multipart_form_data_parser.set_boundary(std::move(boundary)); + out = [&](const char *buf, size_t n, uint64_t /*off*/, uint64_t /*len*/) { + /* For debug + size_t pos = 0; + while (pos < n) { + auto read_size = (std::min)(1, n - pos); + auto ret = multipart_form_data_parser.parse( + buf + pos, read_size, multipart_receiver, multipart_header); + if (!ret) { return false; } + pos += read_size; + } + return true; + */ + return multipart_form_data_parser.parse(buf, n, multipart_receiver, + multipart_header); + }; + } else { + out = [receiver](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { return receiver(buf, n); }; + } + + if (req.method == "DELETE" && !req.has_header("Content-Length")) { + return true; + } + + if (!detail::read_content(strm, req, payload_max_length_, res.status, nullptr, + out, true)) { + return false; + } + + if (req.is_multipart_form_data()) { + if (!multipart_form_data_parser.is_valid()) { + res.status = StatusCode::BadRequest_400; + return false; + } + } + + return true; +} + +inline bool Server::handle_file_request(const Request &req, Response &res, + bool head) { + for (const auto &entry : base_dirs_) { + // Prefix match + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); + if (detail::is_valid_path(sub_path)) { + auto path = entry.base_dir + sub_path; + if (path.back() == '/') { path += "index.html"; } + + if (detail::is_file(path)) { + for (const auto &kv : entry.headers) { + res.set_header(kv.first, kv.second); + } + + auto mm = std::make_shared(path.c_str()); + if (!mm->is_open()) { return false; } + + res.set_content_provider( + mm->size(), + detail::find_content_type(path, file_extension_and_mimetype_map_, + default_file_mimetype_), + [mm](size_t offset, size_t length, DataSink &sink) -> bool { + sink.write(mm->data() + offset, length); + return true; + }); + + if (!head && file_request_handler_) { + file_request_handler_(req, res); + } + + return true; + } + } + } + } + return false; +} + +inline socket_t +Server::create_server_socket(const std::string &host, int port, + int socket_flags, + SocketOptions socket_options) const { + return detail::create_socket( + host, std::string(), port, address_family_, socket_flags, tcp_nodelay_, + std::move(socket_options), + [](socket_t sock, struct addrinfo &ai) -> bool { + if (::bind(sock, ai.ai_addr, static_cast(ai.ai_addrlen))) { + return false; + } + if (::listen(sock, CPPHTTPLIB_LISTEN_BACKLOG)) { return false; } + return true; + }); +} + +inline int Server::bind_internal(const std::string &host, int port, + int socket_flags) { + if (!is_valid()) { return -1; } + + svr_sock_ = create_server_socket(host, port, socket_flags, socket_options_); + if (svr_sock_ == INVALID_SOCKET) { return -1; } + + if (port == 0) { + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + if (getsockname(svr_sock_, reinterpret_cast(&addr), + &addr_len) == -1) { + return -1; + } + if (addr.ss_family == AF_INET) { + return ntohs(reinterpret_cast(&addr)->sin_port); + } else if (addr.ss_family == AF_INET6) { + return ntohs(reinterpret_cast(&addr)->sin6_port); + } else { + return -1; + } + } else { + return port; + } +} + +inline bool Server::listen_internal() { + auto ret = true; + is_running_ = true; + auto se = detail::scope_exit([&]() { is_running_ = false; }); + + { + std::unique_ptr task_queue(new_task_queue()); + + while (svr_sock_ != INVALID_SOCKET) { +#ifndef _WIN32 + if (idle_interval_sec_ > 0 || idle_interval_usec_ > 0) { +#endif + auto val = detail::select_read(svr_sock_, idle_interval_sec_, + idle_interval_usec_); + if (val == 0) { // Timeout + task_queue->on_idle(); + continue; + } +#ifndef _WIN32 + } +#endif + socket_t sock = accept(svr_sock_, nullptr, nullptr); + + if (sock == INVALID_SOCKET) { + if (errno == EMFILE) { + // The per-process limit of open file descriptors has been reached. + // Try to accept new connections after a short sleep. + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } else if (errno == EINTR || errno == EAGAIN) { + continue; + } + if (svr_sock_ != INVALID_SOCKET) { + detail::close_socket(svr_sock_); + ret = false; + } else { + ; // The server socket was closed by user. + } + break; + } + + { +#ifdef _WIN32 + auto timeout = static_cast(read_timeout_sec_ * 1000 + + read_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(read_timeout_sec_); + tv.tv_usec = static_cast(read_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + { + +#ifdef _WIN32 + auto timeout = static_cast(write_timeout_sec_ * 1000 + + write_timeout_usec_ / 1000); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&timeout), sizeof(timeout)); +#else + timeval tv; + tv.tv_sec = static_cast(write_timeout_sec_); + tv.tv_usec = static_cast(write_timeout_usec_); + setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, + reinterpret_cast(&tv), sizeof(tv)); +#endif + } + + if (!task_queue->enqueue( + [this, sock]() { process_and_close_socket(sock); })) { + detail::shutdown_socket(sock); + detail::close_socket(sock); + } + } + + task_queue->shutdown(); + } + + return ret; +} + +inline bool Server::routing(Request &req, Response &res, Stream &strm) { + if (pre_routing_handler_ && + pre_routing_handler_(req, res) == HandlerResponse::Handled) { + return true; + } + + // File handler + auto is_head_request = req.method == "HEAD"; + if ((req.method == "GET" || is_head_request) && + handle_file_request(req, res, is_head_request)) { + return true; + } + + if (detail::expect_content(req)) { + // Content reader handler + { + ContentReader reader( + [&](ContentReceiver receiver) { + return read_content_with_content_receiver( + strm, req, res, std::move(receiver), nullptr, nullptr); + }, + [&](MultipartContentHeader header, ContentReceiver receiver) { + return read_content_with_content_receiver(strm, req, res, nullptr, + std::move(header), + std::move(receiver)); + }); + + if (req.method == "POST") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + post_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PUT") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + put_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "PATCH") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + patch_handlers_for_content_reader_)) { + return true; + } + } else if (req.method == "DELETE") { + if (dispatch_request_for_content_reader( + req, res, std::move(reader), + delete_handlers_for_content_reader_)) { + return true; + } + } + } + + // Read content into `req.body` + if (!read_content(strm, req, res)) { return false; } + } + + // Regular handler + if (req.method == "GET" || req.method == "HEAD") { + return dispatch_request(req, res, get_handlers_); + } else if (req.method == "POST") { + return dispatch_request(req, res, post_handlers_); + } else if (req.method == "PUT") { + return dispatch_request(req, res, put_handlers_); + } else if (req.method == "DELETE") { + return dispatch_request(req, res, delete_handlers_); + } else if (req.method == "OPTIONS") { + return dispatch_request(req, res, options_handlers_); + } else if (req.method == "PATCH") { + return dispatch_request(req, res, patch_handlers_); + } + + res.status = StatusCode::BadRequest_400; + return false; +} + +inline bool Server::dispatch_request(Request &req, Response &res, + const Handlers &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res); + return true; + } + } + return false; +} + +inline void Server::apply_ranges(const Request &req, Response &res, + std::string &content_type, + std::string &boundary) const { + if (req.ranges.size() > 1) { + auto it = res.headers.find("Content-Type"); + if (it != res.headers.end()) { + content_type = it->second; + res.headers.erase(it); + } + + boundary = detail::make_multipart_data_boundary(); + + res.set_header("Content-Type", + "multipart/byteranges; boundary=" + boundary); + } + + auto type = detail::encoding_type(req, res); + + if (res.body.empty()) { + if (res.content_length_ > 0) { + size_t length = 0; + if (req.ranges.empty()) { + length = res.content_length_; + } else if (req.ranges.size() == 1) { + auto offset_and_length = detail::get_range_offset_and_length( + req.ranges[0], res.content_length_); + + length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.content_length_); + res.set_header("Content-Range", content_range); + } else { + length = detail::get_multipart_ranges_data_length( + req, boundary, content_type, res.content_length_); + } + res.set_header("Content-Length", std::to_string(length)); + } else { + if (res.content_provider_) { + if (res.is_chunked_content_provider_) { + res.set_header("Transfer-Encoding", "chunked"); + if (type == detail::EncodingType::Gzip) { + res.set_header("Content-Encoding", "gzip"); + } else if (type == detail::EncodingType::Brotli) { + res.set_header("Content-Encoding", "br"); + } + } + } + } + } else { + if (req.ranges.empty()) { + ; + } else if (req.ranges.size() == 1) { + auto offset_and_length = + detail::get_range_offset_and_length(req.ranges[0], res.body.size()); + auto offset = offset_and_length.first; + auto length = offset_and_length.second; + + auto content_range = detail::make_content_range_header_field( + offset_and_length, res.body.size()); + res.set_header("Content-Range", content_range); + + assert(offset + length <= res.body.size()); + res.body = res.body.substr(offset, length); + } else { + std::string data; + detail::make_multipart_ranges_data(req, res, boundary, content_type, + res.body.size(), data); + res.body.swap(data); + } + + if (type != detail::EncodingType::None) { + std::unique_ptr compressor; + std::string content_encoding; + + if (type == detail::EncodingType::Gzip) { +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + compressor = detail::make_unique(); + content_encoding = "gzip"; +#endif + } else if (type == detail::EncodingType::Brotli) { +#ifdef CPPHTTPLIB_BROTLI_SUPPORT + compressor = detail::make_unique(); + content_encoding = "br"; +#endif + } + + if (compressor) { + std::string compressed; + if (compressor->compress(res.body.data(), res.body.size(), true, + [&](const char *data, size_t data_len) { + compressed.append(data, data_len); + return true; + })) { + res.body.swap(compressed); + res.set_header("Content-Encoding", content_encoding); + } + } + } + + auto length = std::to_string(res.body.size()); + res.set_header("Content-Length", length); + } +} + +inline bool Server::dispatch_request_for_content_reader( + Request &req, Response &res, ContentReader content_reader, + const HandlersForContentReader &handlers) const { + for (const auto &x : handlers) { + const auto &matcher = x.first; + const auto &handler = x.second; + + if (matcher->match(req)) { + handler(req, res, content_reader); + return true; + } + } + return false; +} + +inline bool +Server::process_request(Stream &strm, bool close_connection, + bool &connection_closed, + const std::function &setup_request) { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + // Connection has been closed on client + if (!line_reader.getline()) { return false; } + + Request req; + + Response res; + res.version = "HTTP/1.1"; + res.headers = default_headers_; + +#ifdef _WIN32 + // TODO: Increase FD_SETSIZE statically (libzmq), dynamically (MySQL). +#else +#ifndef CPPHTTPLIB_USE_POLL + // Socket file descriptor exceeded FD_SETSIZE... + if (strm.socket() >= FD_SETSIZE) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::InternalServerError_500; + return write_response(strm, close_connection, req, res); + } +#endif +#endif + + // Check if the request URI doesn't exceed the limit + if (line_reader.size() > CPPHTTPLIB_REQUEST_URI_MAX_LENGTH) { + Headers dummy; + detail::read_headers(strm, dummy); + res.status = StatusCode::UriTooLong_414; + return write_response(strm, close_connection, req, res); + } + + // Request line and headers + if (!parse_request_line(line_reader.ptr(), req) || + !detail::read_headers(strm, req.headers)) { + res.status = StatusCode::BadRequest_400; + return write_response(strm, close_connection, req, res); + } + + if (req.get_header_value("Connection") == "close") { + connection_closed = true; + } + + if (req.version == "HTTP/1.0" && + req.get_header_value("Connection") != "Keep-Alive") { + connection_closed = true; + } + + strm.get_remote_ip_and_port(req.remote_addr, req.remote_port); + req.set_header("REMOTE_ADDR", req.remote_addr); + req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); + + strm.get_local_ip_and_port(req.local_addr, req.local_port); + req.set_header("LOCAL_ADDR", req.local_addr); + req.set_header("LOCAL_PORT", std::to_string(req.local_port)); + + if (req.has_header("Range")) { + const auto &range_header_value = req.get_header_value("Range"); + if (!detail::parse_range_header(range_header_value, req.ranges)) { + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + } + + if (setup_request) { setup_request(req); } + + if (req.get_header_value("Expect") == "100-continue") { + int status = StatusCode::Continue_100; + if (expect_100_continue_handler_) { + status = expect_100_continue_handler_(req, res); + } + switch (status) { + case StatusCode::Continue_100: + case StatusCode::ExpectationFailed_417: + strm.write_format("HTTP/1.1 %d %s\r\n\r\n", status, + status_message(status)); + break; + default: return write_response(strm, close_connection, req, res); + } + } + + // Routing + auto routed = false; +#ifdef CPPHTTPLIB_NO_EXCEPTIONS + routed = routing(req, res, strm); +#else + try { + routed = routing(req, res, strm); + } catch (std::exception &e) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + std::string val; + auto s = e.what(); + for (size_t i = 0; s[i]; i++) { + switch (s[i]) { + case '\r': val += "\\r"; break; + case '\n': val += "\\n"; break; + default: val += s[i]; break; + } + } + res.set_header("EXCEPTION_WHAT", val); + } + } catch (...) { + if (exception_handler_) { + auto ep = std::current_exception(); + exception_handler_(req, res, ep); + routed = true; + } else { + res.status = StatusCode::InternalServerError_500; + res.set_header("EXCEPTION_WHAT", "UNKNOWN"); + } + } +#endif + if (routed) { + if (res.status == -1) { + res.status = req.ranges.empty() ? StatusCode::OK_200 + : StatusCode::PartialContent_206; + } + + if (detail::range_error(req, res)) { + res.body.clear(); + res.content_length_ = 0; + res.content_provider_ = nullptr; + res.status = StatusCode::RangeNotSatisfiable_416; + return write_response(strm, close_connection, req, res); + } + + return write_response_with_content(strm, close_connection, req, res); + } else { + if (res.status == -1) { res.status = StatusCode::NotFound_404; } + + return write_response(strm, close_connection, req, res); + } +} + +inline bool Server::is_valid() const { return true; } + +inline bool Server::process_and_close_socket(socket_t sock) { + auto ret = detail::process_server_socket( + svr_sock_, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this](Stream &strm, bool close_connection, bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + nullptr); + }); + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// HTTP client implementation +inline ClientImpl::ClientImpl(const std::string &host) + : ClientImpl(host, 80, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port) + : ClientImpl(host, port, std::string(), std::string()) {} + +inline ClientImpl::ClientImpl(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : host_(host), port_(port), + host_and_port_(adjust_host_string(host) + ":" + std::to_string(port)), + client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} + +inline ClientImpl::~ClientImpl() { + std::lock_guard guard(socket_mutex_); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline bool ClientImpl::is_valid() const { return true; } + +inline void ClientImpl::copy_settings(const ClientImpl &rhs) { + client_cert_path_ = rhs.client_cert_path_; + client_key_path_ = rhs.client_key_path_; + connection_timeout_sec_ = rhs.connection_timeout_sec_; + read_timeout_sec_ = rhs.read_timeout_sec_; + read_timeout_usec_ = rhs.read_timeout_usec_; + write_timeout_sec_ = rhs.write_timeout_sec_; + write_timeout_usec_ = rhs.write_timeout_usec_; + basic_auth_username_ = rhs.basic_auth_username_; + basic_auth_password_ = rhs.basic_auth_password_; + bearer_token_auth_token_ = rhs.bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + digest_auth_username_ = rhs.digest_auth_username_; + digest_auth_password_ = rhs.digest_auth_password_; +#endif + keep_alive_ = rhs.keep_alive_; + follow_location_ = rhs.follow_location_; + url_encode_ = rhs.url_encode_; + address_family_ = rhs.address_family_; + tcp_nodelay_ = rhs.tcp_nodelay_; + socket_options_ = rhs.socket_options_; + compress_ = rhs.compress_; + decompress_ = rhs.decompress_; + interface_ = rhs.interface_; + proxy_host_ = rhs.proxy_host_; + proxy_port_ = rhs.proxy_port_; + proxy_basic_auth_username_ = rhs.proxy_basic_auth_username_; + proxy_basic_auth_password_ = rhs.proxy_basic_auth_password_; + proxy_bearer_token_auth_token_ = rhs.proxy_bearer_token_auth_token_; +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + proxy_digest_auth_username_ = rhs.proxy_digest_auth_username_; + proxy_digest_auth_password_ = rhs.proxy_digest_auth_password_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + ca_cert_file_path_ = rhs.ca_cert_file_path_; + ca_cert_dir_path_ = rhs.ca_cert_dir_path_; + ca_cert_store_ = rhs.ca_cert_store_; +#endif +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + server_certificate_verification_ = rhs.server_certificate_verification_; +#endif + logger_ = rhs.logger_; +} + +inline socket_t ClientImpl::create_client_socket(Error &error) const { + if (!proxy_host_.empty() && proxy_port_ != -1) { + return detail::create_client_socket( + proxy_host_, std::string(), proxy_port_, address_family_, tcp_nodelay_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, interface_, error); + } + + // Check is custom IP specified for host_ + std::string ip; + auto it = addr_map_.find(host_); + if (it != addr_map_.end()) { ip = it->second; } + + return detail::create_client_socket( + host_, ip, port_, address_family_, tcp_nodelay_, socket_options_, + connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, + read_timeout_usec_, write_timeout_sec_, write_timeout_usec_, interface_, + error); +} + +inline bool ClientImpl::create_and_connect_socket(Socket &socket, + Error &error) { + auto sock = create_client_socket(error); + if (sock == INVALID_SOCKET) { return false; } + socket.sock = sock; + return true; +} + +inline void ClientImpl::shutdown_ssl(Socket & /*socket*/, + bool /*shutdown_gracefully*/) { + // If there are any requests in flight from threads other than us, then it's + // a thread-unsafe race because individual ssl* objects are not thread-safe. + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); +} + +inline void ClientImpl::shutdown_socket(Socket &socket) const { + if (socket.sock == INVALID_SOCKET) { return; } + detail::shutdown_socket(socket.sock); +} + +inline void ClientImpl::close_socket(Socket &socket) { + // If there are requests in flight in another thread, usually closing + // the socket will be fine and they will simply receive an error when + // using the closed socket, but it is still a bug since rarely the OS + // may reassign the socket id to be used for a new socket, and then + // suddenly they will be operating on a live socket that is different + // than the one they intended! + assert(socket_requests_in_flight_ == 0 || + socket_requests_are_from_thread_ == std::this_thread::get_id()); + + // It is also a bug if this happens while SSL is still active +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + assert(socket.ssl == nullptr); +#endif + if (socket.sock == INVALID_SOCKET) { return; } + detail::close_socket(socket.sock); + socket.sock = INVALID_SOCKET; +} + +inline bool ClientImpl::read_response_line(Stream &strm, const Request &req, + Response &res) const { + std::array buf{}; + + detail::stream_line_reader line_reader(strm, buf.data(), buf.size()); + + if (!line_reader.getline()) { return false; } + +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + const static std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line_reader.ptr(), m, re)) { + return req.method == "CONNECT"; + } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + + // Ignore '100 Continue' + while (res.status == StatusCode::Continue_100) { + if (!line_reader.getline()) { return false; } // CRLF + if (!line_reader.getline()) { return false; } // next response line + + if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } + res.version = std::string(m[1]); + res.status = std::stoi(std::string(m[2])); + res.reason = std::string(m[3]); + } + + return true; +} + +inline bool ClientImpl::send(Request &req, Response &res, Error &error) { + std::lock_guard request_mutex_guard(request_mutex_); + auto ret = send_(req, res, error); + if (error == Error::SSLPeerCouldBeClosed_) { + assert(!ret); + ret = send_(req, res, error); + } + return ret; +} + +inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { + { + std::lock_guard guard(socket_mutex_); + + // Set this to false immediately - if it ever gets set to true by the end of + // the request, we know another thread instructed us to close the socket. + socket_should_be_closed_when_request_is_done_ = false; + + auto is_alive = false; + if (socket_.is_open()) { + is_alive = detail::is_socket_alive(socket_.sock); + if (!is_alive) { + // Attempt to avoid sigpipe by shutting down nongracefully if it seems + // like the other side has already closed the connection Also, there + // cannot be any requests in flight from other threads since we locked + // request_mutex_, so safe to close everything immediately + const bool shutdown_gracefully = false; + shutdown_ssl(socket_, shutdown_gracefully); + shutdown_socket(socket_); + close_socket(socket_); + } + } + + if (!is_alive) { + if (!create_and_connect_socket(socket_, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + // TODO: refactoring + if (is_ssl()) { + auto &scli = static_cast(*this); + if (!proxy_host_.empty() && proxy_port_ != -1) { + auto success = false; + if (!scli.connect_with_proxy(socket_, res, success, error)) { + return success; + } + } + + if (!scli.initialize_ssl(socket_, error)) { return false; } + } +#endif + } + + // Mark the current socket as being in use so that it cannot be closed by + // anyone else while this request is ongoing, even though we will be + // releasing the mutex. + if (socket_requests_in_flight_ > 1) { + assert(socket_requests_are_from_thread_ == std::this_thread::get_id()); + } + socket_requests_in_flight_ += 1; + socket_requests_are_from_thread_ = std::this_thread::get_id(); + } + + for (const auto &header : default_headers_) { + if (req.headers.find(header.first) == req.headers.end()) { + req.headers.insert(header); + } + } + + auto ret = false; + auto close_connection = !keep_alive_; + + auto se = detail::scope_exit([&]() { + // Briefly lock mutex in order to mark that a request is no longer ongoing + std::lock_guard guard(socket_mutex_); + socket_requests_in_flight_ -= 1; + if (socket_requests_in_flight_ <= 0) { + assert(socket_requests_in_flight_ == 0); + socket_requests_are_from_thread_ = std::thread::id(); + } + + if (socket_should_be_closed_when_request_is_done_ || close_connection || + !ret) { + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + }); + + ret = process_socket(socket_, [&](Stream &strm) { + return handle_request(strm, req, res, close_connection, error); + }); + + if (!ret) { + if (error == Error::Success) { error = Error::Unknown; } + } + + return ret; +} + +inline Result ClientImpl::send(const Request &req) { + auto req2 = req; + return send_(std::move(req2)); +} + +inline Result ClientImpl::send_(Request &&req) { + auto res = detail::make_unique(); + auto error = Error::Success; + auto ret = send(req, *res, error); + return Result{ret ? std::move(res) : nullptr, error, std::move(req.headers)}; +} + +inline bool ClientImpl::handle_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + if (req.path.empty()) { + error = Error::Connection; + return false; + } + + auto req_save = req; + + bool ret; + + if (!is_ssl() && !proxy_host_.empty() && proxy_port_ != -1) { + auto req2 = req; + req2.path = "http://" + host_and_port_ + req.path; + ret = process_request(strm, req2, res, close_connection, error); + req = req2; + req.path = req_save.path; + } else { + ret = process_request(strm, req, res, close_connection, error); + } + + if (!ret) { return false; } + + if (res.get_header_value("Connection") == "close" || + (res.version == "HTTP/1.0" && res.reason != "Connection established")) { + // TODO this requires a not-entirely-obvious chain of calls to be correct + // for this to be safe. + + // This is safe to call because handle_request is only called by send_ + // which locks the request mutex during the process. It would be a bug + // to call it from a different thread since it's a thread-safety issue + // to do these things to the socket if another thread is using the socket. + std::lock_guard guard(socket_mutex_); + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); + } + + if (300 < res.status && res.status < 400 && follow_location_) { + req = req_save; + ret = redirect(req, res, error); + } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if ((res.status == StatusCode::Unauthorized_401 || + res.status == StatusCode::ProxyAuthenticationRequired_407) && + req.authorization_count_ < 5) { + auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407; + const auto &username = + is_proxy ? proxy_digest_auth_username_ : digest_auth_username_; + const auto &password = + is_proxy ? proxy_digest_auth_password_ : digest_auth_password_; + + if (!username.empty() && !password.empty()) { + std::map auth; + if (detail::parse_www_authenticate(res, auth, is_proxy)) { + Request new_req = req; + new_req.authorization_count_ += 1; + new_req.headers.erase(is_proxy ? "Proxy-Authorization" + : "Authorization"); + new_req.headers.insert(detail::make_digest_authentication_header( + req, auth, new_req.authorization_count_, detail::random_string(10), + username, password, is_proxy)); + + Response new_res; + + ret = send(new_req, new_res, error); + if (ret) { res = new_res; } + } + } + } +#endif + + return ret; +} + +inline bool ClientImpl::redirect(Request &req, Response &res, Error &error) { + if (req.redirect_count_ == 0) { + error = Error::ExceedRedirectCount; + return false; + } + + auto location = res.get_header_value("location"); + if (location.empty()) { return false; } + + const static std::regex re( + R"((?:(https?):)?(?://(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)?([^?#]*)(\?[^#]*)?(?:#.*)?)"); + + std::smatch m; + if (!std::regex_match(location, m, re)) { return false; } + + auto scheme = is_ssl() ? "https" : "http"; + + auto next_scheme = m[1].str(); + auto next_host = m[2].str(); + if (next_host.empty()) { next_host = m[3].str(); } + auto port_str = m[4].str(); + auto next_path = m[5].str(); + auto next_query = m[6].str(); + + auto next_port = port_; + if (!port_str.empty()) { + next_port = std::stoi(port_str); + } else if (!next_scheme.empty()) { + next_port = next_scheme == "https" ? 443 : 80; + } + + if (next_scheme.empty()) { next_scheme = scheme; } + if (next_host.empty()) { next_host = host_; } + if (next_path.empty()) { next_path = "/"; } + + auto path = detail::decode_url(next_path, true) + next_query; + + if (next_scheme == scheme && next_host == host_ && next_port == port_) { + return detail::redirect(*this, req, res, path, location, error); + } else { + if (next_scheme == "https") { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + SSLClient cli(next_host, next_port); + cli.copy_settings(*this); + if (ca_cert_store_) { cli.set_ca_cert_store(ca_cert_store_); } + return detail::redirect(cli, req, res, path, location, error); +#else + return false; +#endif + } else { + ClientImpl cli(next_host, next_port); + cli.copy_settings(*this); + return detail::redirect(cli, req, res, path, location, error); + } + } +} + +inline bool ClientImpl::write_content_with_provider(Stream &strm, + const Request &req, + Error &error) const { + auto is_shutting_down = []() { return false; }; + + if (req.is_chunked_content_provider_) { + // TODO: Brotli support + std::unique_ptr compressor; +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { + compressor = detail::make_unique(); + } else +#endif + { + compressor = detail::make_unique(); + } + + return detail::write_content_chunked(strm, req.content_provider_, + is_shutting_down, *compressor, error); + } else { + return detail::write_content(strm, req.content_provider_, 0, + req.content_length_, is_shutting_down, error); + } +} + +inline bool ClientImpl::write_request(Stream &strm, Request &req, + bool close_connection, Error &error) { + // Prepare additional headers + if (close_connection) { + if (!req.has_header("Connection")) { + req.set_header("Connection", "close"); + } + } + + if (!req.has_header("Host")) { + if (is_ssl()) { + if (port_ == 443) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } else { + if (port_ == 80) { + req.set_header("Host", host_); + } else { + req.set_header("Host", host_and_port_); + } + } + } + + if (!req.has_header("Accept")) { req.set_header("Accept", "*/*"); } + +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; + req.set_header("User-Agent", agent); + } +#endif + + if (req.body.empty()) { + if (req.content_provider_) { + if (!req.is_chunked_content_provider_) { + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.content_length_); + req.set_header("Content-Length", length); + } + } + } else { + if (req.method == "POST" || req.method == "PUT" || + req.method == "PATCH") { + req.set_header("Content-Length", "0"); + } + } + } else { + if (!req.has_header("Content-Type")) { + req.set_header("Content-Type", "text/plain"); + } + + if (!req.has_header("Content-Length")) { + auto length = std::to_string(req.body.size()); + req.set_header("Content-Length", length); + } + } + + if (!basic_auth_password_.empty() || !basic_auth_username_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_basic_authentication_header( + basic_auth_username_, basic_auth_password_, false)); + } + } + + if (!proxy_basic_auth_username_.empty() && + !proxy_basic_auth_password_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_basic_authentication_header( + proxy_basic_auth_username_, proxy_basic_auth_password_, true)); + } + } + + if (!bearer_token_auth_token_.empty()) { + if (!req.has_header("Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + bearer_token_auth_token_, false)); + } + } + + if (!proxy_bearer_token_auth_token_.empty()) { + if (!req.has_header("Proxy-Authorization")) { + req.headers.insert(make_bearer_token_authentication_header( + proxy_bearer_token_auth_token_, true)); + } + } + + // Request line and headers + { + detail::BufferStream bstrm; + + const auto &path = url_encode_ ? detail::encode_url(req.path) : req.path; + bstrm.write_format("%s %s HTTP/1.1\r\n", req.method.c_str(), path.c_str()); + + header_writer_(bstrm, req.headers); + + // Flush buffer + auto &data = bstrm.get_buffer(); + if (!detail::write_data(strm, data.data(), data.size())) { + error = Error::Write; + return false; + } + } + + // Body + if (req.body.empty()) { + return write_content_with_provider(strm, req, error); + } + + if (!detail::write_data(strm, req.body.data(), req.body.size())) { + error = Error::Write; + return false; + } + + return true; +} + +inline std::unique_ptr ClientImpl::send_with_content_provider( + Request &req, const char *body, size_t content_length, + ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type, Error &error) { + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_) { req.set_header("Content-Encoding", "gzip"); } +#endif + +#ifdef CPPHTTPLIB_ZLIB_SUPPORT + if (compress_ && !content_provider_without_length) { + // TODO: Brotli support + detail::gzip_compressor compressor; + + if (content_provider) { + auto ok = true; + size_t offset = 0; + DataSink data_sink; + + data_sink.write = [&](const char *data, size_t data_len) -> bool { + if (ok) { + auto last = offset + data_len == content_length; + + auto ret = compressor.compress( + data, data_len, last, + [&](const char *compressed_data, size_t compressed_data_len) { + req.body.append(compressed_data, compressed_data_len); + return true; + }); + + if (ret) { + offset += data_len; + } else { + ok = false; + } + } + return ok; + }; + + while (ok && offset < content_length) { + if (!content_provider(offset, content_length - offset, data_sink)) { + error = Error::Canceled; + return nullptr; + } + } + } else { + if (!compressor.compress(body, content_length, true, + [&](const char *data, size_t data_len) { + req.body.append(data, data_len); + return true; + })) { + error = Error::Compression; + return nullptr; + } + } + } else +#endif + { + if (content_provider) { + req.content_length_ = content_length; + req.content_provider_ = std::move(content_provider); + req.is_chunked_content_provider_ = false; + } else if (content_provider_without_length) { + req.content_length_ = 0; + req.content_provider_ = detail::ContentProviderAdapter( + std::move(content_provider_without_length)); + req.is_chunked_content_provider_ = true; + req.set_header("Transfer-Encoding", "chunked"); + } else { + req.body.assign(body, content_length); + } + } + + auto res = detail::make_unique(); + return send(req, *res, error) ? std::move(res) : nullptr; +} + +inline Result ClientImpl::send_with_content_provider( + const std::string &method, const std::string &path, const Headers &headers, + const char *body, size_t content_length, ContentProvider content_provider, + ContentProviderWithoutLength content_provider_without_length, + const std::string &content_type) { + Request req; + req.method = method; + req.headers = headers; + req.path = path; + + auto error = Error::Success; + + auto res = send_with_content_provider( + req, body, content_length, std::move(content_provider), + std::move(content_provider_without_length), content_type, error); + + return Result{std::move(res), error, std::move(req.headers)}; +} + +inline std::string +ClientImpl::adjust_host_string(const std::string &host) const { + if (host.find(':') != std::string::npos) { return "[" + host + "]"; } + return host; +} + +inline bool ClientImpl::process_request(Stream &strm, Request &req, + Response &res, bool close_connection, + Error &error) { + // Send request + if (!write_request(strm, req, close_connection, error)) { return false; } + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (is_ssl()) { + auto is_proxy_enabled = !proxy_host_.empty() && proxy_port_ != -1; + if (!is_proxy_enabled) { + char buf[1]; + if (SSL_peek(socket_.ssl, buf, 1) == 0 && + SSL_get_error(socket_.ssl, 0) == SSL_ERROR_ZERO_RETURN) { + error = Error::SSLPeerCouldBeClosed_; + return false; + } + } + } +#endif + + // Receive response and headers + if (!read_response_line(strm, req, res) || + !detail::read_headers(strm, res.headers)) { + error = Error::Read; + return false; + } + + // Body + if ((res.status != StatusCode::NoContent_204) && req.method != "HEAD" && + req.method != "CONNECT") { + auto redirect = 300 < res.status && res.status < 400 && follow_location_; + + if (req.response_handler && !redirect) { + if (!req.response_handler(res)) { + error = Error::Canceled; + return false; + } + } + + auto out = + req.content_receiver + ? static_cast( + [&](const char *buf, size_t n, uint64_t off, uint64_t len) { + if (redirect) { return true; } + auto ret = req.content_receiver(buf, n, off, len); + if (!ret) { error = Error::Canceled; } + return ret; + }) + : static_cast( + [&](const char *buf, size_t n, uint64_t /*off*/, + uint64_t /*len*/) { + if (res.body.size() + n > res.body.max_size()) { + return false; + } + res.body.append(buf, n); + return true; + }); + + auto progress = [&](uint64_t current, uint64_t total) { + if (!req.progress || redirect) { return true; } + auto ret = req.progress(current, total); + if (!ret) { error = Error::Canceled; } + return ret; + }; + + int dummy_status; + if (!detail::read_content(strm, res, (std::numeric_limits::max)(), + dummy_status, std::move(progress), std::move(out), + decompress_)) { + if (error != Error::Canceled) { error = Error::Read; } + return false; + } + } + + // Log + if (logger_) { logger_(req, res); } + + return true; +} + +inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( + const std::string &boundary, const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) const { + size_t cur_item = 0; + size_t cur_start = 0; + // cur_item and cur_start are copied to within the std::function and maintain + // state between successive calls + return [&, cur_item, cur_start](size_t offset, + DataSink &sink) mutable -> bool { + if (!offset && !items.empty()) { + sink.os << detail::serialize_multipart_formdata(items, boundary, false); + return true; + } else if (cur_item < provider_items.size()) { + if (!cur_start) { + const auto &begin = detail::serialize_multipart_formdata_item_begin( + provider_items[cur_item], boundary); + offset += begin.size(); + cur_start = offset; + sink.os << begin; + } + + DataSink cur_sink; + auto has_data = true; + cur_sink.write = sink.write; + cur_sink.done = [&]() { has_data = false; }; + + if (!provider_items[cur_item].provider(offset - cur_start, cur_sink)) { + return false; + } + + if (!has_data) { + sink.os << detail::serialize_multipart_formdata_item_end(); + cur_item++; + cur_start = 0; + } + return true; + } else { + sink.os << detail::serialize_multipart_formdata_finish(boundary); + sink.done(); + return true; + } + }; +} + +inline bool +ClientImpl::process_socket(const Socket &socket, + std::function callback) { + return detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, std::move(callback)); +} + +inline bool ClientImpl::is_ssl() const { return false; } + +inline Result ClientImpl::Get(const std::string &path) { + return Get(path, Headers(), Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, Progress progress) { + return Get(path, Headers(), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers) { + return Get(path, headers, Progress()); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver) { + return Get(path, Headers(), nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return Get(path, headers, nullptr, std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), nullptr); +} + +inline Result ClientImpl::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, Headers(), std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + Request req; + req.method = "GET"; + req.path = path; + req.headers = headers; + req.response_handler = std::move(response_handler); + req.content_receiver = + [content_receiver](const char *data, size_t data_length, + uint64_t /*offset*/, uint64_t /*total_length*/) { + return content_receiver(data, data_length); + }; + req.progress = std::move(progress); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + if (params.empty()) { return Get(path, headers); } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, + Progress progress) { + return Get(path, params, headers, nullptr, std::move(content_receiver), + std::move(progress)); +} + +inline Result ClientImpl::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, + Progress progress) { + if (params.empty()) { + return Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); + } + + std::string path_with_query = append_query_params(path, params); + return Get(path_with_query, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result ClientImpl::Head(const std::string &path) { + return Head(path, Headers()); +} + +inline Result ClientImpl::Head(const std::string &path, + const Headers &headers) { + Request req; + req.method = "HEAD"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline Result ClientImpl::Post(const std::string &path) { + return Post(path, std::string(), std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, + const Headers &headers) { + return Post(path, headers, nullptr, 0, std::string()); +} + +inline Result ClientImpl::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Post(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return Post(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Params ¶ms) { + return Post(path, Headers(), params); +} + +inline Result ClientImpl::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Post(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Post(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("POST", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Post(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Post(const std::string &path, + const MultipartFormDataItems &items) { + return Post(path, Headers(), items); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Post(path, headers, body, content_type); +} + +inline Result +ClientImpl::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "POST", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path) { + return Put(path, std::string(), std::string()); +} + +inline Result ClientImpl::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Put(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body, content_length, + nullptr, nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return Put(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Put(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Put(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PUT", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Params ¶ms) { + return Put(path, Headers(), params); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + auto query = detail::params_to_query_str(params); + return Put(path, headers, query, "application/x-www-form-urlencoded"); +} + +inline Result ClientImpl::Put(const std::string &path, + const MultipartFormDataItems &items) { + return Put(path, Headers(), items); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + if (!detail::is_multipart_boundary_chars_valid(boundary)) { + return Result{nullptr, Error::UnsupportedMultipartBoundaryChars}; + } + + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + const auto &body = detail::serialize_multipart_formdata(items, boundary); + return Put(path, headers, body, content_type); +} + +inline Result +ClientImpl::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + const auto &boundary = detail::make_multipart_data_boundary(); + const auto &content_type = + detail::serialize_multipart_formdata_get_content_type(boundary); + return send_with_content_provider( + "PUT", path, headers, nullptr, 0, nullptr, + get_multipart_content_provider(boundary, items, provider_items), + content_type); +} +inline Result ClientImpl::Patch(const std::string &path) { + return Patch(path, std::string(), std::string()); +} + +inline Result ClientImpl::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Patch(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body, + content_length, nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Patch(path, Headers(), body, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, body.data(), + body.size(), nullptr, nullptr, + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return Patch(path, Headers(), content_length, std::move(content_provider), + content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return Patch(path, Headers(), std::move(content_provider), content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, + content_length, std::move(content_provider), + nullptr, content_type); +} + +inline Result ClientImpl::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return send_with_content_provider("PATCH", path, headers, nullptr, 0, nullptr, + std::move(content_provider), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path) { + return Delete(path, Headers(), std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers) { + return Delete(path, headers, std::string(), std::string()); +} + +inline Result ClientImpl::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return Delete(path, Headers(), body, content_length, content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, const char *body, + size_t content_length, + const std::string &content_type) { + Request req; + req.method = "DELETE"; + req.headers = headers; + req.path = path; + + if (!content_type.empty()) { req.set_header("Content-Type", content_type); } + req.body.assign(body, content_length); + + return send_(std::move(req)); +} + +inline Result ClientImpl::Delete(const std::string &path, + const std::string &body, + const std::string &content_type) { + return Delete(path, Headers(), body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Delete(const std::string &path, + const Headers &headers, + const std::string &body, + const std::string &content_type) { + return Delete(path, headers, body.data(), body.size(), content_type); +} + +inline Result ClientImpl::Options(const std::string &path) { + return Options(path, Headers()); +} + +inline Result ClientImpl::Options(const std::string &path, + const Headers &headers) { + Request req; + req.method = "OPTIONS"; + req.headers = headers; + req.path = path; + + return send_(std::move(req)); +} + +inline void ClientImpl::stop() { + std::lock_guard guard(socket_mutex_); + + // If there is anything ongoing right now, the ONLY thread-safe thing we can + // do is to shutdown_socket, so that threads using this socket suddenly + // discover they can't read/write any more and error out. Everything else + // (closing the socket, shutting ssl down) is unsafe because these actions are + // not thread-safe. + if (socket_requests_in_flight_ > 0) { + shutdown_socket(socket_); + + // Aside from that, we set a flag for the socket to be closed when we're + // done. + socket_should_be_closed_when_request_is_done_ = true; + return; + } + + // Otherwise, still holding the mutex, we can shut everything down ourselves + shutdown_ssl(socket_, true); + shutdown_socket(socket_); + close_socket(socket_); +} + +inline std::string ClientImpl::host() const { return host_; } + +inline int ClientImpl::port() const { return port_; } + +inline size_t ClientImpl::is_socket_open() const { + std::lock_guard guard(socket_mutex_); + return socket_.is_open(); +} + +inline socket_t ClientImpl::socket() const { return socket_.sock; } + +inline void ClientImpl::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +inline void ClientImpl::set_read_timeout(time_t sec, time_t usec) { + read_timeout_sec_ = sec; + read_timeout_usec_ = usec; +} + +inline void ClientImpl::set_write_timeout(time_t sec, time_t usec) { + write_timeout_sec_ = sec; + write_timeout_usec_ = usec; +} + +inline void ClientImpl::set_basic_auth(const std::string &username, + const std::string &password) { + basic_auth_username_ = username; + basic_auth_password_ = password; +} + +inline void ClientImpl::set_bearer_token_auth(const std::string &token) { + bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_digest_auth(const std::string &username, + const std::string &password) { + digest_auth_username_ = username; + digest_auth_password_ = password; +} +#endif + +inline void ClientImpl::set_keep_alive(bool on) { keep_alive_ = on; } + +inline void ClientImpl::set_follow_location(bool on) { follow_location_ = on; } + +inline void ClientImpl::set_url_encode(bool on) { url_encode_ = on; } + +inline void +ClientImpl::set_hostname_addr_map(std::map addr_map) { + addr_map_ = std::move(addr_map); +} + +inline void ClientImpl::set_default_headers(Headers headers) { + default_headers_ = std::move(headers); +} + +inline void ClientImpl::set_header_writer( + std::function const &writer) { + header_writer_ = writer; +} + +inline void ClientImpl::set_address_family(int family) { + address_family_ = family; +} + +inline void ClientImpl::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +inline void ClientImpl::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +inline void ClientImpl::set_compress(bool on) { compress_ = on; } + +inline void ClientImpl::set_decompress(bool on) { decompress_ = on; } + +inline void ClientImpl::set_interface(const std::string &intf) { + interface_ = intf; +} + +inline void ClientImpl::set_proxy(const std::string &host, int port) { + proxy_host_ = host; + proxy_port_ = port; +} + +inline void ClientImpl::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + proxy_basic_auth_username_ = username; + proxy_basic_auth_password_ = password; +} + +inline void ClientImpl::set_proxy_bearer_token_auth(const std::string &token) { + proxy_bearer_token_auth_token_ = token; +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void ClientImpl::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + proxy_digest_auth_username_ = username; + proxy_digest_auth_password_ = password; +} + +inline void ClientImpl::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; +} + +inline void ClientImpl::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store && ca_cert_store != ca_cert_store_) { + ca_cert_store_ = ca_cert_store; + } +} + +inline X509_STORE *ClientImpl::create_ca_cert_store(const char *ca_cert, + std::size_t size) const { + auto mem = BIO_new_mem_buf(ca_cert, static_cast(size)); + if (!mem) { return nullptr; } + + auto inf = PEM_X509_INFO_read_bio(mem, nullptr, nullptr, nullptr); + if (!inf) { + BIO_free_all(mem); + return nullptr; + } + + auto cts = X509_STORE_new(); + if (cts) { + for (auto i = 0; i < static_cast(sk_X509_INFO_num(inf)); i++) { + auto itmp = sk_X509_INFO_value(inf, i); + if (!itmp) { continue; } + + if (itmp->x509) { X509_STORE_add_cert(cts, itmp->x509); } + if (itmp->crl) { X509_STORE_add_crl(cts, itmp->crl); } + } + } + + sk_X509_INFO_pop_free(inf, X509_INFO_free); + BIO_free_all(mem); + return cts; +} + +inline void ClientImpl::enable_server_certificate_verification(bool enabled) { + server_certificate_verification_ = enabled; +} +#endif + +inline void ClientImpl::set_logger(Logger logger) { + logger_ = std::move(logger); +} + +/* + * SSL Implementation + */ +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +namespace detail { + +template +inline SSL *ssl_new(socket_t sock, SSL_CTX *ctx, std::mutex &ctx_mutex, + U SSL_connect_or_accept, V setup) { + SSL *ssl = nullptr; + { + std::lock_guard guard(ctx_mutex); + ssl = SSL_new(ctx); + } + + if (ssl) { + set_nonblocking(sock, true); + auto bio = BIO_new_socket(static_cast(sock), BIO_NOCLOSE); + BIO_set_nbio(bio, 1); + SSL_set_bio(ssl, bio, bio); + + if (!setup(ssl) || SSL_connect_or_accept(ssl) != 1) { + SSL_shutdown(ssl); + { + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); + } + set_nonblocking(sock, false); + return nullptr; + } + BIO_set_nbio(bio, 0); + set_nonblocking(sock, false); + } + + return ssl; +} + +inline void ssl_delete(std::mutex &ctx_mutex, SSL *ssl, + bool shutdown_gracefully) { + // sometimes we may want to skip this to try to avoid SIGPIPE if we know + // the remote has closed the network connection + // Note that it is not always possible to avoid SIGPIPE, this is merely a + // best-efforts. + if (shutdown_gracefully) { SSL_shutdown(ssl); } + + std::lock_guard guard(ctx_mutex); + SSL_free(ssl); +} + +template +bool ssl_connect_or_accept_nonblocking(socket_t sock, SSL *ssl, + U ssl_connect_or_accept, + time_t timeout_sec, + time_t timeout_usec) { + auto res = 0; + while ((res = ssl_connect_or_accept(ssl)) != 1) { + auto err = SSL_get_error(ssl, res); + switch (err) { + case SSL_ERROR_WANT_READ: + if (select_read(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + case SSL_ERROR_WANT_WRITE: + if (select_write(sock, timeout_sec, timeout_usec) > 0) { continue; } + break; + default: break; + } + return false; + } + return true; +} + +template +inline bool process_server_socket_ssl( + const std::atomic &svr_sock, SSL *ssl, socket_t sock, + size_t keep_alive_max_count, time_t keep_alive_timeout_sec, + time_t read_timeout_sec, time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + return process_server_socket_core( + svr_sock, sock, keep_alive_max_count, keep_alive_timeout_sec, + [&](bool close_connection, bool &connection_closed) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm, close_connection, connection_closed); + }); +} + +template +inline bool +process_client_socket_ssl(SSL *ssl, socket_t sock, time_t read_timeout_sec, + time_t read_timeout_usec, time_t write_timeout_sec, + time_t write_timeout_usec, T callback) { + SSLSocketStream strm(sock, ssl, read_timeout_sec, read_timeout_usec, + write_timeout_sec, write_timeout_usec); + return callback(strm); +} + +class SSLInit { +public: + SSLInit() { + OPENSSL_init_ssl( + OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL); + } +}; + +// SSL socket stream implementation +inline SSLSocketStream::SSLSocketStream(socket_t sock, SSL *ssl, + time_t read_timeout_sec, + time_t read_timeout_usec, + time_t write_timeout_sec, + time_t write_timeout_usec) + : sock_(sock), ssl_(ssl), read_timeout_sec_(read_timeout_sec), + read_timeout_usec_(read_timeout_usec), + write_timeout_sec_(write_timeout_sec), + write_timeout_usec_(write_timeout_usec) { + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); +} + +inline SSLSocketStream::~SSLSocketStream() = default; + +inline bool SSLSocketStream::is_readable() const { + return detail::select_read(sock_, read_timeout_sec_, read_timeout_usec_) > 0; +} + +inline bool SSLSocketStream::is_writable() const { + return select_write(sock_, write_timeout_sec_, write_timeout_usec_) > 0 && + is_socket_alive(sock_); +} + +inline ssize_t SSLSocketStream::read(char *ptr, size_t size) { + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + auto ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_READ || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_READ) { +#endif + if (SSL_pending(ssl_) > 0) { + return SSL_read(ssl_, ptr, static_cast(size)); + } else if (is_readable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_read(ssl_, ptr, static_cast(size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline ssize_t SSLSocketStream::write(const char *ptr, size_t size) { + if (is_writable()) { + auto handle_size = static_cast( + std::min(size, (std::numeric_limits::max)())); + + auto ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret < 0) { + auto err = SSL_get_error(ssl_, ret); + auto n = 1000; +#ifdef _WIN32 + while (--n >= 0 && (err == SSL_ERROR_WANT_WRITE || + (err == SSL_ERROR_SYSCALL && + WSAGetLastError() == WSAETIMEDOUT))) { +#else + while (--n >= 0 && err == SSL_ERROR_WANT_WRITE) { +#endif + if (is_writable()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + ret = SSL_write(ssl_, ptr, static_cast(handle_size)); + if (ret >= 0) { return ret; } + err = SSL_get_error(ssl_, ret); + } else { + return -1; + } + } + } + return ret; + } + return -1; +} + +inline void SSLSocketStream::get_remote_ip_and_port(std::string &ip, + int &port) const { + detail::get_remote_ip_and_port(sock_, ip, port); +} + +inline void SSLSocketStream::get_local_ip_and_port(std::string &ip, + int &port) const { + detail::get_local_ip_and_port(sock_, ip, port); +} + +inline socket_t SSLSocketStream::socket() const { return sock_; } + +static SSLInit sslinit_; + +} // namespace detail + +// SSL HTTP server implementation +inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, + const char *client_ca_cert_file_path, + const char *client_ca_cert_dir_path, + const char *private_key_password) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (private_key_password != nullptr && (private_key_password[0] != '\0')) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, + reinterpret_cast(const_cast(private_key_password))); + } + + if (SSL_CTX_use_certificate_chain_file(ctx_, cert_path) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, private_key_path, SSL_FILETYPE_PEM) != + 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_file_path || client_ca_cert_dir_path) { + SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, + client_ca_cert_dir_path); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, + X509_STORE *client_ca_cert_store) { + ctx_ = SSL_CTX_new(TLS_server_method()); + + if (ctx_) { + SSL_CTX_set_options(ctx_, + SSL_OP_NO_COMPRESSION | + SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + + SSL_CTX_set_min_proto_version(ctx_, TLS1_1_VERSION); + + if (SSL_CTX_use_certificate(ctx_, cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, private_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } else if (client_ca_cert_store) { + SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + + SSL_CTX_set_verify( + ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + } +} + +inline SSLServer::SSLServer( + const std::function &setup_ssl_ctx_callback) { + ctx_ = SSL_CTX_new(TLS_method()); + if (ctx_) { + if (!setup_ssl_ctx_callback(*ctx_)) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLServer::~SSLServer() { + if (ctx_) { SSL_CTX_free(ctx_); } +} + +inline bool SSLServer::is_valid() const { return ctx_; } + +inline SSL_CTX *SSLServer::ssl_context() const { return ctx_; } + +inline bool SSLServer::process_and_close_socket(socket_t sock) { + auto ssl = detail::ssl_new( + sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + return detail::ssl_connect_or_accept_nonblocking( + sock, ssl2, SSL_accept, read_timeout_sec_, read_timeout_usec_); + }, + [](SSL * /*ssl2*/) { return true; }); + + auto ret = false; + if (ssl) { + ret = detail::process_server_socket_ssl( + svr_sock_, ssl, sock, keep_alive_max_count_, keep_alive_timeout_sec_, + read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, + write_timeout_usec_, + [this, ssl](Stream &strm, bool close_connection, + bool &connection_closed) { + return process_request(strm, close_connection, connection_closed, + [&](Request &req) { req.ssl = ssl; }); + }); + + // Shutdown gracefully if the result seemed successful, non-gracefully if + // the connection appeared to be closed. + const bool shutdown_gracefully = ret; + detail::ssl_delete(ctx_mutex_, ssl, shutdown_gracefully); + } + + detail::shutdown_socket(sock); + detail::close_socket(sock); + return ret; +} + +// SSL HTTP client implementation +inline SSLClient::SSLClient(const std::string &host) + : SSLClient(host, 443, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port) + : SSLClient(host, port, std::string(), std::string()) {} + +inline SSLClient::SSLClient(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path, + const std::string &private_key_password) + : ClientImpl(host, port, client_cert_path, client_key_path) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (!client_cert_path.empty() && !client_key_path.empty()) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate_file(ctx_, client_cert_path.c_str(), + SSL_FILETYPE_PEM) != 1 || + SSL_CTX_use_PrivateKey_file(ctx_, client_key_path.c_str(), + SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::SSLClient(const std::string &host, int port, + X509 *client_cert, EVP_PKEY *client_key, + const std::string &private_key_password) + : ClientImpl(host, port) { + ctx_ = SSL_CTX_new(TLS_client_method()); + + detail::split(&host_[0], &host_[host_.size()], '.', + [&](const char *b, const char *e) { + host_components_.emplace_back(b, e); + }); + + if (client_cert != nullptr && client_key != nullptr) { + if (!private_key_password.empty()) { + SSL_CTX_set_default_passwd_cb_userdata( + ctx_, reinterpret_cast( + const_cast(private_key_password.c_str()))); + } + + if (SSL_CTX_use_certificate(ctx_, client_cert) != 1 || + SSL_CTX_use_PrivateKey(ctx_, client_key) != 1) { + SSL_CTX_free(ctx_); + ctx_ = nullptr; + } + } +} + +inline SSLClient::~SSLClient() { + if (ctx_) { SSL_CTX_free(ctx_); } + // Make sure to shut down SSL since shutdown_ssl will resolve to the + // base function rather than the derived function once we get to the + // base class destructor, and won't free the SSL (causing a leak). + shutdown_ssl_impl(socket_, true); +} + +inline bool SSLClient::is_valid() const { return ctx_; } + +inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (ca_cert_store) { + if (ctx_) { + if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { + // Free memory allocated for old cert and use new store `ca_cert_store` + SSL_CTX_set_cert_store(ctx_, ca_cert_store); + } + } else { + X509_STORE_free(ca_cert_store); + } + } +} + +inline void SSLClient::load_ca_cert_store(const char *ca_cert, + std::size_t size) { + set_ca_cert_store(ClientImpl::create_ca_cert_store(ca_cert, size)); +} + +inline long SSLClient::get_openssl_verify_result() const { + return verify_result_; +} + +inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } + +inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { + return is_valid() && ClientImpl::create_and_connect_socket(socket, error); +} + +// Assumes that socket_mutex_ is locked and that there are no requests in flight +inline bool SSLClient::connect_with_proxy(Socket &socket, Response &res, + bool &success, Error &error) { + success = true; + Response proxy_res; + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req2; + req2.method = "CONNECT"; + req2.path = host_and_port_; + return process_request(strm, req2, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are no + // requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + + if (proxy_res.status == StatusCode::ProxyAuthenticationRequired_407) { + if (!proxy_digest_auth_username_.empty() && + !proxy_digest_auth_password_.empty()) { + std::map auth; + if (detail::parse_www_authenticate(proxy_res, auth, true)) { + proxy_res = Response(); + if (!detail::process_client_socket( + socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, [&](Stream &strm) { + Request req3; + req3.method = "CONNECT"; + req3.path = host_and_port_; + req3.headers.insert(detail::make_digest_authentication_header( + req3, auth, 1, detail::random_string(10), + proxy_digest_auth_username_, proxy_digest_auth_password_, + true)); + return process_request(strm, req3, proxy_res, false, error); + })) { + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + success = false; + return false; + } + } + } + } + + // If status code is not 200, proxy request is failed. + // Set error to ProxyConnection and return proxy response + // as the response of the request + if (proxy_res.status != StatusCode::OK_200) { + error = Error::ProxyConnection; + res = std::move(proxy_res); + // Thread-safe to close everything because we are assuming there are + // no requests in flight + shutdown_ssl(socket, true); + shutdown_socket(socket); + close_socket(socket); + return false; + } + + return true; +} + +inline bool SSLClient::load_certs() { + auto ret = true; + + std::call_once(initialize_cert_, [&]() { + std::lock_guard guard(ctx_mutex_); + if (!ca_cert_file_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, ca_cert_file_path_.c_str(), + nullptr)) { + ret = false; + } + } else if (!ca_cert_dir_path_.empty()) { + if (!SSL_CTX_load_verify_locations(ctx_, nullptr, + ca_cert_dir_path_.c_str())) { + ret = false; + } + } else { + auto loaded = false; +#ifdef _WIN32 + loaded = + detail::load_system_certs_on_windows(SSL_CTX_get_cert_store(ctx_)); +#elif defined(CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN) && defined(__APPLE__) +#if TARGET_OS_OSX + loaded = detail::load_system_certs_on_macos(SSL_CTX_get_cert_store(ctx_)); +#endif // TARGET_OS_OSX +#endif // _WIN32 + if (!loaded) { SSL_CTX_set_default_verify_paths(ctx_); } + } + }); + + return ret; +} + +inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { + auto ssl = detail::ssl_new( + socket.sock, ctx_, ctx_mutex_, + [&](SSL *ssl2) { + if (server_certificate_verification_) { + if (!load_certs()) { + error = Error::SSLLoadingCerts; + return false; + } + SSL_set_verify(ssl2, SSL_VERIFY_NONE, nullptr); + } + + if (!detail::ssl_connect_or_accept_nonblocking( + socket.sock, ssl2, SSL_connect, connection_timeout_sec_, + connection_timeout_usec_)) { + error = Error::SSLConnection; + return false; + } + + if (server_certificate_verification_) { + verify_result_ = SSL_get_verify_result(ssl2); + + if (verify_result_ != X509_V_OK) { + error = Error::SSLServerVerification; + return false; + } + + auto server_cert = SSL_get1_peer_certificate(ssl2); + + if (server_cert == nullptr) { + error = Error::SSLServerVerification; + return false; + } + + if (!verify_host(server_cert)) { + X509_free(server_cert); + error = Error::SSLServerVerification; + return false; + } + X509_free(server_cert); + } + + return true; + }, + [&](SSL *ssl2) { + // NOTE: Direct call instead of using the OpenSSL macro to suppress + // -Wold-style-cast warning + // SSL_set_tlsext_host_name(ssl2, host_.c_str()); + SSL_ctrl(ssl2, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, + static_cast(const_cast(host_.c_str()))); + return true; + }); + + if (ssl) { + socket.ssl = ssl; + return true; + } + + shutdown_socket(socket); + close_socket(socket); + return false; +} + +inline void SSLClient::shutdown_ssl(Socket &socket, bool shutdown_gracefully) { + shutdown_ssl_impl(socket, shutdown_gracefully); +} + +inline void SSLClient::shutdown_ssl_impl(Socket &socket, + bool shutdown_gracefully) { + if (socket.sock == INVALID_SOCKET) { + assert(socket.ssl == nullptr); + return; + } + if (socket.ssl) { + detail::ssl_delete(ctx_mutex_, socket.ssl, shutdown_gracefully); + socket.ssl = nullptr; + } + assert(socket.ssl == nullptr); +} + +inline bool +SSLClient::process_socket(const Socket &socket, + std::function callback) { + assert(socket.ssl); + return detail::process_client_socket_ssl( + socket.ssl, socket.sock, read_timeout_sec_, read_timeout_usec_, + write_timeout_sec_, write_timeout_usec_, std::move(callback)); +} + +inline bool SSLClient::is_ssl() const { return true; } + +inline bool SSLClient::verify_host(X509 *server_cert) const { + /* Quote from RFC2818 section 3.1 "Server Identity" + + If a subjectAltName extension of type dNSName is present, that MUST + be used as the identity. Otherwise, the (most specific) Common Name + field in the Subject field of the certificate MUST be used. Although + the use of the Common Name is existing practice, it is deprecated and + Certification Authorities are encouraged to use the dNSName instead. + + Matching is performed using the matching rules specified by + [RFC2459]. If more than one identity of a given type is present in + the certificate (e.g., more than one dNSName name, a match in any one + of the set is considered acceptable.) Names may contain the wildcard + character * which is considered to match any single domain name + component or component fragment. E.g., *.a.com matches foo.a.com but + not bar.foo.a.com. f*.com matches foo.com but not bar.com. + + In some cases, the URI is specified as an IP address rather than a + hostname. In this case, the iPAddress subjectAltName must be present + in the certificate and must exactly match the IP in the URI. + + */ + return verify_host_with_subject_alt_name(server_cert) || + verify_host_with_common_name(server_cert); +} + +inline bool +SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { + auto ret = false; + + auto type = GEN_DNS; + + struct in6_addr addr6 {}; + struct in_addr addr {}; + size_t addr_len = 0; + +#ifndef __MINGW32__ + if (inet_pton(AF_INET6, host_.c_str(), &addr6)) { + type = GEN_IPADD; + addr_len = sizeof(struct in6_addr); + } else if (inet_pton(AF_INET, host_.c_str(), &addr)) { + type = GEN_IPADD; + addr_len = sizeof(struct in_addr); + } +#endif + + auto alt_names = static_cast( + X509_get_ext_d2i(server_cert, NID_subject_alt_name, nullptr, nullptr)); + + if (alt_names) { + auto dsn_matched = false; + auto ip_matched = false; + + auto count = sk_GENERAL_NAME_num(alt_names); + + for (decltype(count) i = 0; i < count && !dsn_matched; i++) { + auto val = sk_GENERAL_NAME_value(alt_names, i); + if (val->type == type) { + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || + !memcmp(&addr, name, addr_len)) { + ip_matched = true; + } + break; + } + } + } + + if (dsn_matched || ip_matched) { ret = true; } + } + + GENERAL_NAMES_free(const_cast( + reinterpret_cast(alt_names))); + return ret; +} + +inline bool SSLClient::verify_host_with_common_name(X509 *server_cert) const { + const auto subject_name = X509_get_subject_name(server_cert); + + if (subject_name != nullptr) { + char name[BUFSIZ]; + auto name_len = X509_NAME_get_text_by_NID(subject_name, NID_commonName, + name, sizeof(name)); + + if (name_len != -1) { + return check_host_name(name, static_cast(name_len)); + } + } + + return false; +} + +inline bool SSLClient::check_host_name(const char *pattern, + size_t pattern_len) const { + if (host_.size() == pattern_len && host_ == pattern) { return true; } + + // Wildcard match + // https://bugs.launchpad.net/ubuntu/+source/firefox-3.0/+bug/376484 + std::vector pattern_components; + detail::split(&pattern[0], &pattern[pattern_len], '.', + [&](const char *b, const char *e) { + pattern_components.emplace_back(b, e); + }); + + if (host_components_.size() != pattern_components.size()) { return false; } + + auto itr = pattern_components.begin(); + for (const auto &h : host_components_) { + auto &p = *itr; + if (p != h && p != "*") { + auto partial_match = (p.size() > 0 && p[p.size() - 1] == '*' && + !p.compare(0, p.size() - 1, h)); + if (!partial_match) { return false; } + } + ++itr; + } + + return true; +} +#endif + +// Universal client implementation +inline Client::Client(const std::string &scheme_host_port) + : Client(scheme_host_port, std::string(), std::string()) {} + +inline Client::Client(const std::string &scheme_host_port, + const std::string &client_cert_path, + const std::string &client_key_path) { + const static std::regex re( + R"((?:([a-z]+):\/\/)?(?:\[([\d:]+)\]|([^:/?#]+))(?::(\d+))?)"); + + std::smatch m; + if (std::regex_match(scheme_host_port, m, re)) { + auto scheme = m[1].str(); + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + if (!scheme.empty() && (scheme != "http" && scheme != "https")) { +#else + if (!scheme.empty() && scheme != "http") { +#endif +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "'" + scheme + "' scheme is not supported."; + throw std::invalid_argument(msg); +#endif + return; + } + + auto is_ssl = scheme == "https"; + + auto host = m[2].str(); + if (host.empty()) { host = m[3].str(); } + + auto port_str = m[4].str(); + auto port = !port_str.empty() ? std::stoi(port_str) : (is_ssl ? 443 : 80); + + if (is_ssl) { +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + is_ssl_ = is_ssl; +#endif + } else { + cli_ = detail::make_unique(host, port, client_cert_path, + client_key_path); + } + } else { + cli_ = detail::make_unique(scheme_host_port, 80, + client_cert_path, client_key_path); + } +} + +inline Client::Client(const std::string &host, int port) + : cli_(detail::make_unique(host, port)) {} + +inline Client::Client(const std::string &host, int port, + const std::string &client_cert_path, + const std::string &client_key_path) + : cli_(detail::make_unique(host, port, client_cert_path, + client_key_path)) {} + +inline Client::~Client() = default; + +inline bool Client::is_valid() const { + return cli_ != nullptr && cli_->is_valid(); +} + +inline Result Client::Get(const std::string &path) { return cli_->Get(path); } +inline Result Client::Get(const std::string &path, const Headers &headers) { + return cli_->Get(path, headers); +} +inline Result Client::Get(const std::string &path, Progress progress) { + return cli_->Get(path, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + Progress progress) { + return cli_->Get(path, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver)); +} +inline Result Client::Get(const std::string &path, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, Progress progress) { + return cli_->Get(path, params, headers, std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, std::move(content_receiver), + std::move(progress)); +} +inline Result Client::Get(const std::string &path, const Params ¶ms, + const Headers &headers, + ResponseHandler response_handler, + ContentReceiver content_receiver, Progress progress) { + return cli_->Get(path, params, headers, std::move(response_handler), + std::move(content_receiver), std::move(progress)); +} + +inline Result Client::Head(const std::string &path) { return cli_->Head(path); } +inline Result Client::Head(const std::string &path, const Headers &headers) { + return cli_->Head(path, headers); +} + +inline Result Client::Post(const std::string &path) { return cli_->Post(path); } +inline Result Client::Post(const std::string &path, const Headers &headers) { + return cli_->Post(path, headers); +} +inline Result Client::Post(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Post(path, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_length, content_type); +} +inline Result Client::Post(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Post(path, body, content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Post(path, headers, body, content_type); +} +inline Result Client::Post(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Post(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Post(const std::string &path, const Params ¶ms) { + return cli_->Post(path, params); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Post(path, headers, params); +} +inline Result Client::Post(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Post(path, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Post(path, headers, items); +} +inline Result Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Post(path, headers, items, boundary); +} +inline Result +Client::Post(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Post(path, headers, items, provider_items); +} +inline Result Client::Put(const std::string &path) { return cli_->Put(path); } +inline Result Client::Put(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Put(path, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_length, content_type); +} +inline Result Client::Put(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Put(path, body, content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Put(path, headers, body, content_type); +} +inline Result Client::Put(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Put(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Put(const std::string &path, const Params ¶ms) { + return cli_->Put(path, params); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const Params ¶ms) { + return cli_->Put(path, headers, params); +} +inline Result Client::Put(const std::string &path, + const MultipartFormDataItems &items) { + return cli_->Put(path, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items) { + return cli_->Put(path, headers, items); +} +inline Result Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const std::string &boundary) { + return cli_->Put(path, headers, items, boundary); +} +inline Result +Client::Put(const std::string &path, const Headers &headers, + const MultipartFormDataItems &items, + const MultipartFormDataProviderItems &provider_items) { + return cli_->Put(path, headers, items, provider_items); +} +inline Result Client::Patch(const std::string &path) { + return cli_->Patch(path); +} +inline Result Client::Patch(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_length, content_type); +} +inline Result Client::Patch(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, body, content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Patch(path, headers, body, content_type); +} +inline Result Client::Patch(const std::string &path, size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, std::move(content_provider), content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + size_t content_length, + ContentProvider content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, content_length, std::move(content_provider), + content_type); +} +inline Result Client::Patch(const std::string &path, const Headers &headers, + ContentProviderWithoutLength content_provider, + const std::string &content_type) { + return cli_->Patch(path, headers, std::move(content_provider), content_type); +} +inline Result Client::Delete(const std::string &path) { + return cli_->Delete(path); +} +inline Result Client::Delete(const std::string &path, const Headers &headers) { + return cli_->Delete(path, headers); +} +inline Result Client::Delete(const std::string &path, const char *body, + size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const char *body, size_t content_length, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_length, content_type); +} +inline Result Client::Delete(const std::string &path, const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, body, content_type); +} +inline Result Client::Delete(const std::string &path, const Headers &headers, + const std::string &body, + const std::string &content_type) { + return cli_->Delete(path, headers, body, content_type); +} +inline Result Client::Options(const std::string &path) { + return cli_->Options(path); +} +inline Result Client::Options(const std::string &path, const Headers &headers) { + return cli_->Options(path, headers); +} + +inline bool Client::send(Request &req, Response &res, Error &error) { + return cli_->send(req, res, error); +} + +inline Result Client::send(const Request &req) { return cli_->send(req); } + +inline void Client::stop() { cli_->stop(); } + +inline std::string Client::host() const { return cli_->host(); } + +inline int Client::port() const { return cli_->port(); } + +inline size_t Client::is_socket_open() const { return cli_->is_socket_open(); } + +inline socket_t Client::socket() const { return cli_->socket(); } + +inline void +Client::set_hostname_addr_map(std::map addr_map) { + cli_->set_hostname_addr_map(std::move(addr_map)); +} + +inline void Client::set_default_headers(Headers headers) { + cli_->set_default_headers(std::move(headers)); +} + +inline void Client::set_header_writer( + std::function const &writer) { + cli_->set_header_writer(writer); +} + +inline void Client::set_address_family(int family) { + cli_->set_address_family(family); +} + +inline void Client::set_tcp_nodelay(bool on) { cli_->set_tcp_nodelay(on); } + +inline void Client::set_socket_options(SocketOptions socket_options) { + cli_->set_socket_options(std::move(socket_options)); +} + +inline void Client::set_connection_timeout(time_t sec, time_t usec) { + cli_->set_connection_timeout(sec, usec); +} + +inline void Client::set_read_timeout(time_t sec, time_t usec) { + cli_->set_read_timeout(sec, usec); +} + +inline void Client::set_write_timeout(time_t sec, time_t usec) { + cli_->set_write_timeout(sec, usec); +} + +inline void Client::set_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_basic_auth(username, password); +} +inline void Client::set_bearer_token_auth(const std::string &token) { + cli_->set_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_digest_auth(username, password); +} +#endif + +inline void Client::set_keep_alive(bool on) { cli_->set_keep_alive(on); } +inline void Client::set_follow_location(bool on) { + cli_->set_follow_location(on); +} + +inline void Client::set_url_encode(bool on) { cli_->set_url_encode(on); } + +inline void Client::set_compress(bool on) { cli_->set_compress(on); } + +inline void Client::set_decompress(bool on) { cli_->set_decompress(on); } + +inline void Client::set_interface(const std::string &intf) { + cli_->set_interface(intf); +} + +inline void Client::set_proxy(const std::string &host, int port) { + cli_->set_proxy(host, port); +} +inline void Client::set_proxy_basic_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_basic_auth(username, password); +} +inline void Client::set_proxy_bearer_token_auth(const std::string &token) { + cli_->set_proxy_bearer_token_auth(token); +} +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_proxy_digest_auth(const std::string &username, + const std::string &password) { + cli_->set_proxy_digest_auth(username, password); +} +#endif + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::enable_server_certificate_verification(bool enabled) { + cli_->enable_server_certificate_verification(enabled); +} +#endif + +inline void Client::set_logger(Logger logger) { + cli_->set_logger(std::move(logger)); +} + +#ifdef CPPHTTPLIB_OPENSSL_SUPPORT +inline void Client::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + cli_->set_ca_cert_path(ca_cert_file_path, ca_cert_dir_path); +} + +inline void Client::set_ca_cert_store(X509_STORE *ca_cert_store) { + if (is_ssl_) { + static_cast(*cli_).set_ca_cert_store(ca_cert_store); + } else { + cli_->set_ca_cert_store(ca_cert_store); + } +} + +inline void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) { + set_ca_cert_store(cli_->create_ca_cert_store(ca_cert, size)); +} + +inline long Client::get_openssl_verify_result() const { + if (is_ssl_) { + return static_cast(*cli_).get_openssl_verify_result(); + } + return -1; // NOTE: -1 doesn't match any of X509_V_ERR_??? +} + +inline SSL_CTX *Client::ssl_context() const { + if (is_ssl_) { return static_cast(*cli_).ssl_context(); } + return nullptr; +} +#endif + +// ---------------------------------------------------------------------------- + +} // namespace httplib + +#if defined(_WIN32) && defined(CPPHTTPLIB_USE_POLL) +#undef poll +#endif + +#endif // CPPHTTPLIB_HTTPLIB_H diff --git a/examples/server/main.cpp b/examples/server/main.cpp new file mode 100644 index 000000000..21d719501 --- /dev/null +++ b/examples/server/main.cpp @@ -0,0 +1,707 @@ +#include +#include +#include +#include +#include +#include +#include + +// #include "preprocessing.hpp" +#include "flux.hpp" +#include "stable-diffusion.h" + +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_STATIC +#include "stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#define STB_IMAGE_WRITE_STATIC +#include "stb_image_write.h" + +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#define STB_IMAGE_RESIZE_STATIC +#include "stb_image_resize.h" + +#include "httplib.h" + +const char* rng_type_to_str[] = { + "std_default", + "cuda", +}; + +// Names of the sampler method, same order as enum sample_method in stable-diffusion.h +const char* sample_method_str[] = { + "euler_a", + "euler", + "heun", + "dpm2", + "dpm++2s_a", + "dpm++2m", + "dpm++2mv2", + "lcm", +}; + +// Names of the sigma schedule overrides, same order as sample_schedule in stable-diffusion.h +const char* schedule_str[] = { + "default", + "discrete", + "karras", + "ays", +}; + + +enum SDMode { + TXT2IMG, + IMG2IMG, + IMG2VID, + CONVERT, + MODE_COUNT +}; + +struct SDParams { + int n_threads = -1; + SDMode mode = TXT2IMG; + + std::string model_path; + std::string clip_l_path; + std::string t5xxl_path; + std::string diffusion_model_path; + std::string vae_path; + // std::string taesd_path; + std::string embeddings_path; + std::string stacked_id_embeddings_path; + sd_type_t wtype = SD_TYPE_COUNT; + std::string lora_model_dir; + std::string output_path = "output.png"; + std::string input_path; + + std::string prompt; + std::string negative_prompt; + float min_cfg = 1.0f; + float cfg_scale = 7.0f; + float guidance = 3.5f; + float style_ratio = 20.f; + int clip_skip = -1; // <= 0 represents unspecified + int width = 512; + int height = 512; + int batch_count = 1; + + + sample_method_t sample_method = EULER_A; + schedule_t schedule = DEFAULT; + int sample_steps = 20; + float strength = 0.75f; + rng_type_t rng_type = CUDA_RNG; + int64_t seed = 42; + bool verbose = false; + bool vae_tiling = false; + bool normalize_input = false; + bool clip_on_cpu = false; + bool vae_on_cpu = false; + bool color = false; + + //server things + int port = 8080; + std::string host = "localhost"; +}; + +void print_params(SDParams params) { + printf("Option: \n"); + printf(" n_threads: %d\n", params.n_threads); + printf(" model_path: %s\n", params.model_path.c_str()); + printf(" wtype: %s\n", params.wtype < SD_TYPE_COUNT ? sd_type_name(params.wtype) : "unspecified"); + printf(" clip_l_path: %s\n", params.clip_l_path.c_str()); + printf(" t5xxl_path: %s\n", params.t5xxl_path.c_str()); + printf(" diffusion_model_path: %s\n", params.diffusion_model_path.c_str()); + printf(" vae_path: %s\n", params.vae_path.c_str()); + // printf(" taesd_path: %s\n", params.taesd_path.c_str()); + printf(" embeddings_path: %s\n", params.embeddings_path.c_str()); + printf(" stacked_id_embeddings_path: %s\n", params.stacked_id_embeddings_path.c_str()); + printf(" style ratio: %.2f\n", params.style_ratio); + printf(" normzalize input image : %s\n", params.normalize_input ? "true" : "false"); + printf(" output_path: %s\n", params.output_path.c_str()); + printf(" clip on cpu: %s\n", params.clip_on_cpu ? "true" : "false"); + printf(" vae decoder on cpu:%s\n", params.vae_on_cpu ? "true" : "false"); + printf(" prompt: %s\n", params.prompt.c_str()); + printf(" negative_prompt: %s\n", params.negative_prompt.c_str()); + printf(" min_cfg: %.2f\n", params.min_cfg); + printf(" cfg_scale: %.2f\n", params.cfg_scale); + printf(" guidance: %.2f\n", params.guidance); + printf(" clip_skip: %d\n", params.clip_skip); + printf(" width: %d\n", params.width); + printf(" height: %d\n", params.height); + printf(" sample_method: %s\n", sample_method_str[params.sample_method]); + printf(" schedule: %s\n", schedule_str[params.schedule]); + printf(" sample_steps: %d\n", params.sample_steps); + printf(" strength(img2img): %.2f\n", params.strength); + printf(" rng: %s\n", rng_type_to_str[params.rng_type]); + printf(" seed: %ld\n", params.seed); + printf(" batch_count: %d\n", params.batch_count); + printf(" vae_tiling: %s\n", params.vae_tiling ? "true" : "false"); +} + +void print_usage(int argc, const char* argv[]) { + printf("usage: %s [arguments]\n", argv[0]); + printf("\n"); + printf("arguments:\n"); + printf(" -h, --help show this help message and exit\n"); + printf(" -M, --mode [MODEL] run mode (txt2img or img2img or convert, default: txt2img)\n"); + printf(" -t, --threads N number of threads to use during computation (default: -1).\n"); + printf(" If threads <= 0, then threads will be set to the number of CPU physical cores\n"); + printf(" -m, --model [MODEL] path to full model\n"); + printf(" --diffusion-model path to the standalone diffusion model\n"); + printf(" --clip_l path to the clip-l text encoder\n"); + printf(" --t5xxl path to the the t5xxl text encoder.\n"); + printf(" --vae [VAE] path to vae\n"); + printf(" --embd-dir [EMBEDDING_PATH] path to embeddings.\n"); + printf(" --type [TYPE] weight type (f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_k, q3_k, q4_k)\n"); + printf(" If not specified, the default is the type of the weight file.\n"); + printf(" --lora-model-dir [DIR] lora model directory\n"); + printf(" -o, --output OUTPUT path to write result image to (default: ./output.png)\n"); + printf(" -p, --prompt [PROMPT] the prompt to render\n"); + printf(" -n, --negative-prompt PROMPT the negative prompt (default: \"\")\n"); + printf(" --cfg-scale SCALE unconditional guidance scale: (default: 7.0)\n"); + printf(" --strength STRENGTH strength for noising/unnoising (default: 0.75)\n"); + printf(" --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%%)\n"); + printf(" --control-strength STRENGTH strength to apply Control Net (default: 0.9)\n"); + printf(" 1.0 corresponds to full destruction of information in init image\n"); + printf(" -H, --height H image height, in pixel space (default: 512)\n"); + printf(" -W, --width W image width, in pixel space (default: 512)\n"); + printf(" --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, lcm}\n"); + printf(" sampling method (default: \"euler_a\")\n"); + printf(" --steps STEPS number of sample steps (default: 20)\n"); + printf(" --rng {std_default, cuda} RNG (default: cuda)\n"); + printf(" -s SEED, --seed SEED RNG seed (default: 42, use random seed for < 0)\n"); + printf(" -b, --batch-count COUNT number of images to generate.\n"); + printf(" --schedule {discrete, karras, ays} Denoiser sigma schedule (default: discrete)\n"); + printf(" --clip-skip N ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer (default: -1)\n"); + printf(" <= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x\n"); + printf(" --vae-tiling process vae in tiles to reduce memory usage\n"); + printf(" --vae-on-cpu keep vae in cpu (for low vram)\n"); + printf(" --clip-on-cpu keep clip in cpu (for low vram).\n"); + printf(" --color Colors the logging tags according to level\n"); + printf(" -v, --verbose print extra info\n"); + printf(" --port port used for server (default: 8080)\n"); + printf(" --host IP address used for server. Use 0.0.0.0 to expose server to LAN (default: localhost)\n"); +} + +void parse_args(int argc, const char** argv, SDParams& params) { + bool invalid_arg = false; + std::string arg; + for (int i = 1; i < argc; i++) { + arg = argv[i]; + + if (arg == "-t" || arg == "--threads") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.n_threads = std::stoi(argv[i]); + } else if (arg == "-m" || arg == "--model") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.model_path = argv[i]; + } else if (arg == "--clip_l") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.clip_l_path = argv[i]; + } else if (arg == "--t5xxl") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.t5xxl_path = argv[i]; + } else if (arg == "--diffusion-model") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.diffusion_model_path = argv[i]; + } else if (arg == "--vae") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.vae_path = argv[i]; + // TODO Tiny AE + } else if (arg == "--type") { + if (++i >= argc) { + invalid_arg = true; + break; + } + std::string type = argv[i]; + if (type == "f32") { + params.wtype = SD_TYPE_F32; + } else if (type == "f16") { + params.wtype = SD_TYPE_F16; + } else if (type == "q4_0") { + params.wtype = SD_TYPE_Q4_0; + } else if (type == "q4_1") { + params.wtype = SD_TYPE_Q4_1; + } else if (type == "q5_0") { + params.wtype = SD_TYPE_Q5_0; + } else if (type == "q5_1") { + params.wtype = SD_TYPE_Q5_1; + } else if (type == "q8_0") { + params.wtype = SD_TYPE_Q8_0; + } else if (type == "q2_k") { + params.wtype = SD_TYPE_Q2_K; + } else if (type == "q3_k") { + params.wtype = SD_TYPE_Q3_K; + } else if (type == "q4_k") { + params.wtype = SD_TYPE_Q4_K; + } else { + fprintf(stderr, "error: invalid weight format %s, must be one of [f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_k, q3_k, q4_k]\n", + type.c_str()); + exit(1); + } + } else if (arg == "--lora-model-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.lora_model_dir = argv[i]; + } else if (arg == "-o" || arg == "--output") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.output_path = argv[i]; + } else if (arg == "-p" || arg == "--prompt") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.prompt = argv[i]; + } else if (arg == "-n" || arg == "--negative-prompt") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.negative_prompt = argv[i]; + } else if (arg == "--cfg-scale") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.cfg_scale = std::stof(argv[i]); + } else if (arg == "--guidance") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.guidance = std::stof(argv[i]); + } else if (arg == "--strength") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.strength = std::stof(argv[i]); + } else if (arg == "--style-ratio") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.style_ratio = std::stof(argv[i]); + } else if (arg == "-H" || arg == "--height") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.height = std::stoi(argv[i]); + } else if (arg == "-W" || arg == "--width") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.width = std::stoi(argv[i]); + } else if (arg == "--steps") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.sample_steps = std::stoi(argv[i]); + } else if (arg == "--clip-skip") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.clip_skip = std::stoi(argv[i]); + } else if (arg == "--vae-tiling") { + params.vae_tiling = true; + } else if (arg == "--normalize-input") { + params.normalize_input = true; + } else if (arg == "--clip-on-cpu") { + params.clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs + } else if (arg == "--vae-on-cpu") { + params.vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs + } else if (arg == "-b" || arg == "--batch-count") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.batch_count = std::stoi(argv[i]); + } else if (arg == "--rng") { + if (++i >= argc) { + invalid_arg = true; + break; + } + std::string rng_type_str = argv[i]; + if (rng_type_str == "std_default") { + params.rng_type = STD_DEFAULT_RNG; + } else if (rng_type_str == "cuda") { + params.rng_type = CUDA_RNG; + } else { + invalid_arg = true; + break; + } + } else if (arg == "--schedule") { + if (++i >= argc) { + invalid_arg = true; + break; + } + const char* schedule_selected = argv[i]; + int schedule_found = -1; + for (int d = 0; d < N_SCHEDULES; d++) { + if (!strcmp(schedule_selected, schedule_str[d])) { + schedule_found = d; + } + } + if (schedule_found == -1) { + invalid_arg = true; + break; + } + params.schedule = (schedule_t)schedule_found; + } else if (arg == "-s" || arg == "--seed") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.seed = std::stoll(argv[i]); + } else if (arg == "--sampling-method") { + if (++i >= argc) { + invalid_arg = true; + break; + } + const char* sample_method_selected = argv[i]; + int sample_method_found = -1; + for (int m = 0; m < N_SAMPLE_METHODS; m++) { + if (!strcmp(sample_method_selected, sample_method_str[m])) { + sample_method_found = m; + } + } + if (sample_method_found == -1) { + invalid_arg = true; + break; + } + params.sample_method = (sample_method_t)sample_method_found; + } else if (arg == "-h" || arg == "--help") { + print_usage(argc, argv); + exit(0); + } else if (arg == "-v" || arg == "--verbose") { + params.verbose = true; + } else if (arg == "--color") { + params.color = true; + } else if (arg == "--port") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.port = std::stoi(argv[i]); + } else if (arg == "--host") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.host = argv[i]; + } else { + fprintf(stderr, "error: unknown argument: %s\n", arg.c_str()); + print_usage(argc, argv); + exit(1); + } + } + if (invalid_arg) { + fprintf(stderr, "error: invalid parameter for argument: %s\n", arg.c_str()); + print_usage(argc, argv); + exit(1); + } + if (params.n_threads <= 0) { + params.n_threads = get_num_physical_cores(); + } + + if (params.mode != CONVERT && params.mode != IMG2VID && params.prompt.length() == 0) { + fprintf(stderr, "error: the following arguments are required: prompt\n"); + print_usage(argc, argv); + exit(1); + } + + if (params.model_path.length() == 0 && params.diffusion_model_path.length() == 0) { + fprintf(stderr, "error: the following arguments are required: model_path/diffusion_model\n"); + print_usage(argc, argv); + exit(1); + } + + if ((params.mode == IMG2IMG || params.mode == IMG2VID) && params.input_path.length() == 0) { + fprintf(stderr, "error: when using the img2img mode, the following arguments are required: init-img\n"); + print_usage(argc, argv); + exit(1); + } + + if (params.output_path.length() == 0) { + fprintf(stderr, "error: the following arguments are required: output_path\n"); + print_usage(argc, argv); + exit(1); + } + + if (params.width <= 0 || params.width % 64 != 0) { + fprintf(stderr, "error: the width must be a multiple of 64\n"); + exit(1); + } + + if (params.height <= 0 || params.height % 64 != 0) { + fprintf(stderr, "error: the height must be a multiple of 64\n"); + exit(1); + } + + if (params.sample_steps <= 0) { + fprintf(stderr, "error: the sample_steps must be greater than 0\n"); + exit(1); + } + + if (params.strength < 0.f || params.strength > 1.f) { + fprintf(stderr, "error: can only work with strength in [0.0, 1.0]\n"); + exit(1); + } + + if (params.seed < 0) { + srand((int)time(NULL)); + params.seed = rand(); + } + + if (params.mode == CONVERT) { + if (params.output_path == "output.png") { + params.output_path = "output.gguf"; + } + } +} + +static std::string sd_basename(const std::string& path) { + size_t pos = path.find_last_of('/'); + if (pos != std::string::npos) { + return path.substr(pos + 1); + } + pos = path.find_last_of('\\'); + if (pos != std::string::npos) { + return path.substr(pos + 1); + } + return path; +} + +std::string get_image_params(SDParams params, int64_t seed) { + std::string parameter_string = params.prompt + "\n"; + if (params.negative_prompt.size() != 0) { + parameter_string += "Negative prompt: " + params.negative_prompt + "\n"; + } + parameter_string += "Steps: " + std::to_string(params.sample_steps) + ", "; + parameter_string += "CFG scale: " + std::to_string(params.cfg_scale) + ", "; + parameter_string += "Guidance: " + std::to_string(params.guidance) + ", "; + parameter_string += "Seed: " + std::to_string(seed) + ", "; + parameter_string += "Size: " + std::to_string(params.width) + "x" + std::to_string(params.height) + ", "; + parameter_string += "Model: " + sd_basename(params.model_path) + ", "; + parameter_string += "RNG: " + std::string(rng_type_to_str[params.rng_type]) + ", "; + parameter_string += "Sampler: " + std::string(sample_method_str[params.sample_method]); + if (params.schedule == KARRAS) { + parameter_string += " karras"; + } + parameter_string += ", "; + parameter_string += "Version: stable-diffusion.cpp"; + return parameter_string; +} + +/* Enables Printing the log level tag in color using ANSI escape codes */ +void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) { + SDParams* params = (SDParams*)data; + int tag_color; + const char* level_str; + FILE* out_stream = (level == SD_LOG_ERROR) ? stderr : stdout; + + if (!log || (!params->verbose && level <= SD_LOG_DEBUG)) { + return; + } + + switch (level) { + case SD_LOG_DEBUG: + tag_color = 37; + level_str = "DEBUG"; + break; + case SD_LOG_INFO: + tag_color = 34; + level_str = "INFO"; + break; + case SD_LOG_WARN: + tag_color = 35; + level_str = "WARN"; + break; + case SD_LOG_ERROR: + tag_color = 31; + level_str = "ERROR"; + break; + default: /* Potential future-proofing */ + tag_color = 33; + level_str = "?????"; + break; + } + + if (params->color == true) { + fprintf(out_stream, "\033[%d;1m[%-5s]\033[0m ", tag_color, level_str); + } else { + fprintf(out_stream, "[%-5s] ", level_str); + } + fputs(log, out_stream); + fflush(out_stream); +} + +static void log_server_request(const httplib::Request & req, const httplib::Response & res) { + printf("request: %s %s (%s)\n", req.method.c_str(), req.path.c_str(), req.body.c_str()); +} + +const auto pingpong = [&](const httplib::Request &, httplib::Response & res) { + std::string resp = "{\"ping\": \"pong\"}"; + res.set_content(resp, "application/json"); + }; + +int main(int argc, const char* argv[]) { + SDParams params; + + parse_args(argc, argv, params); + + + sd_set_log_callback(sd_log_cb, (void*)¶ms); + + if (params.verbose) { + print_params(params); + printf("%s", sd_get_system_info()); + } + + + bool vae_decode_only = true; + + sd_ctx_t* sd_ctx = new_sd_ctx(params.model_path.c_str(), + params.clip_l_path.c_str(), + params.t5xxl_path.c_str(), + params.diffusion_model_path.c_str(), + params.vae_path.c_str(), + "", + "", + params.lora_model_dir.c_str(), + params.embeddings_path.c_str(), + params.stacked_id_embeddings_path.c_str(), + vae_decode_only, + params.vae_tiling, + false, + params.n_threads, + params.wtype, + params.rng_type, + params.schedule, + params.clip_on_cpu, + true, + params.vae_on_cpu); + + if (sd_ctx == NULL) { + printf("new_sd_ctx_t failed\n"); + return 1; + } + + int n_prompts = 0; + + const auto txt2imgRequest = [&sd_ctx, ¶ms, &n_prompts](const httplib::Request & req, httplib::Response & res) { + //TODO: proper payloads + std::string prompt = req.body; + if(!prompt.empty()){ + params.prompt = prompt; + }else{ + params.seed+=1; + } + { + sd_image_t* results; + results = txt2img(sd_ctx, + params.prompt.c_str(), + params.negative_prompt.c_str(), + params.clip_skip, + params.cfg_scale, + params.guidance, + params.width, + params.height, + params.sample_method, + params.sample_steps, + params.seed, + params.batch_count, + NULL, + 1, + params.style_ratio, + params.normalize_input, + ""); + + if (results == NULL) { + printf("generate failed\n"); + free_sd_ctx(sd_ctx); + return 1; + } + + size_t last = params.output_path.find_last_of("."); + std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path; + for (int i = 0; i < params.batch_count; i++) { + if (results[i].data == NULL) { + continue; + } + std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1 + n_prompts*params.batch_count) + ".png" : dummy_name + ".png"; + stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, + results[i].data, 0, get_image_params(params, params.seed + i).c_str()); + printf("save result image to '%s'\n", final_image_path.c_str()); + // Todo: return base64 encoded image via websocket? + free(results[i].data); + results[i].data = NULL; + } + free(results); + n_prompts++; + } + }; + + + std::unique_ptr svr; + svr.reset(new httplib::Server()); + svr->set_default_headers({{"Server", "sd.cpp"}}); + // CORS preflight + svr->Options(R"(.*)", [](const httplib::Request &, httplib::Response & res) { + // Access-Control-Allow-Origin is already set by middleware + res.set_header("Access-Control-Allow-Credentials", "true"); + res.set_header("Access-Control-Allow-Methods", "POST"); + res.set_header("Access-Control-Allow-Headers", "*"); + return res.set_content("", "text/html"); // blank response, no data + }); + svr->set_logger(log_server_request); + + svr->Get("/Ping", pingpong); + svr->Post("/txt2img", txt2imgRequest); + + + // bind HTTP listen port, run the HTTP server in a thread + if (!svr->bind_to_port(params.host, params.port)) { + //TODO: Error message + return 1; + } + std::thread t([&]() { svr->listen_after_bind(); }); + svr->wait_until_ready(); + + printf("server listening"); + + while(1); + + free_sd_ctx(sd_ctx); + + return 0; +} \ No newline at end of file From ce76decdbd34ef93c498e893d306c218f11490bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Tue, 27 Aug 2024 00:37:55 +0200 Subject: [PATCH 088/143] server: remove pingpong endpoint --- examples/server/main.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 21d719501..ee3b67f13 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -569,10 +569,6 @@ static void log_server_request(const httplib::Request & req, const httplib::Resp printf("request: %s %s (%s)\n", req.method.c_str(), req.path.c_str(), req.body.c_str()); } -const auto pingpong = [&](const httplib::Request &, httplib::Response & res) { - std::string resp = "{\"ping\": \"pong\"}"; - res.set_content(resp, "application/json"); - }; int main(int argc, const char* argv[]) { SDParams params; @@ -685,7 +681,6 @@ int main(int argc, const char* argv[]) { }); svr->set_logger(log_server_request); - svr->Get("/Ping", pingpong); svr->Post("/txt2img", txt2imgRequest); From 377a2d14d11975ddcc1b58e96de2b35886e013e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Tue, 27 Aug 2024 11:15:27 +0200 Subject: [PATCH 089/143] Server: Fix missing return on non-void function --- examples/server/main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index ee3b67f13..15300a1b0 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -665,6 +665,7 @@ int main(int argc, const char* argv[]) { free(results); n_prompts++; } + return 0; }; From d018c23f270522f90f18b11950e9bbdc8194c648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Tue, 27 Aug 2024 11:16:41 +0200 Subject: [PATCH 090/143] Server: change default host --- examples/server/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 15300a1b0..13285ea6f 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -102,7 +102,7 @@ struct SDParams { //server things int port = 8080; - std::string host = "localhost"; + std::string host = "127.0.0.1"; }; void print_params(SDParams params) { @@ -693,7 +693,7 @@ int main(int argc, const char* argv[]) { std::thread t([&]() { svr->listen_after_bind(); }); svr->wait_until_ready(); - printf("server listening"); + printf("Server listening at %s:%d\n",params.host,params.port); while(1); From 1b91a093e7e3520466cfb64ce89228bec31f206e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Tue, 27 Aug 2024 11:24:01 +0200 Subject: [PATCH 091/143] Server: Fix printf --- examples/server/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 13285ea6f..73ea0b21f 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -693,7 +693,7 @@ int main(int argc, const char* argv[]) { std::thread t([&]() { svr->listen_after_bind(); }); svr->wait_until_ready(); - printf("Server listening at %s:%d\n",params.host,params.port); + printf("Server listening at %s:%d\n",params.host.c_str(),params.port); while(1); From d22ef2443db93b10f4a6678eeac28d6f49a008b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 4 Oct 2024 14:05:04 +0200 Subject: [PATCH 092/143] server: move httplib to thirdparty folder --- {examples/server => thirdparty}/httplib.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {examples/server => thirdparty}/httplib.h (100%) diff --git a/examples/server/httplib.h b/thirdparty/httplib.h similarity index 100% rename from examples/server/httplib.h rename to thirdparty/httplib.h From eb239ba08c7dc8667229bd73569eabc7a0967b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 4 Oct 2024 14:05:49 +0200 Subject: [PATCH 093/143] Server: accept json inputs + return bas64 image --- examples/server/b64.cpp | 42 ++++++++ examples/server/main.cpp | 208 ++++++++++++++++++++++++++++++--------- 2 files changed, 204 insertions(+), 46 deletions(-) create mode 100644 examples/server/b64.cpp diff --git a/examples/server/b64.cpp b/examples/server/b64.cpp new file mode 100644 index 000000000..e0aacf045 --- /dev/null +++ b/examples/server/b64.cpp @@ -0,0 +1,42 @@ + +//FROM +//https://stackoverflow.com/a/34571089/5155484 + +static const std::string b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//= +static std::string base64_encode(const std::string &in) { + std::string out; + + int val=0, valb=-6; + for (uint8_t c : in) { + val = (val<<8) + c; + valb += 8; + while (valb>=0) { + out.push_back(b[(val>>valb)&0x3F]); + valb-=6; + } + } + if (valb>-6) out.push_back(b[((val<<8)>>(valb+8))&0x3F]); + while (out.size()%4) out.push_back('='); + return out; +} + + +static std::string base64_decode(const std::string &in) { + + std::string out; + + std::vector T(256,-1); + for (int i=0; i<64; i++) T[b[i]] = i; + + int val=0, valb=-8; + for (uint8_t c : in) { + if (T[c] == -1) break; + val = (val<<6) + T[c]; + valb += 6; + if (valb>=0) { + out.push_back(char((val>>valb)&0xFF)); + valb-=8; + } + } + return out; +} \ No newline at end of file diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 73ea0b21f..419c15ae2 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -7,7 +7,9 @@ #include // #include "preprocessing.hpp" +#include "b64.cpp" #include "flux.hpp" +#include "json.hpp" #include "stable-diffusion.h" #define STB_IMAGE_IMPLEMENTATION @@ -49,7 +51,6 @@ const char* schedule_str[] = { "ays", }; - enum SDMode { TXT2IMG, IMG2IMG, @@ -86,7 +87,6 @@ struct SDParams { int height = 512; int batch_count = 1; - sample_method_t sample_method = EULER_A; schedule_t schedule = DEFAULT; int sample_steps = 20; @@ -100,9 +100,9 @@ struct SDParams { bool vae_on_cpu = false; bool color = false; - //server things - int port = 8080; - std::string host = "127.0.0.1"; + // server things + int port = 8080; + std::string host = "127.0.0.1"; }; void print_params(SDParams params) { @@ -227,7 +227,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.vae_path = argv[i]; - // TODO Tiny AE + // TODO Tiny AE } else if (arg == "--type") { if (++i >= argc) { invalid_arg = true; @@ -565,17 +565,104 @@ void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) { fflush(out_stream); } -static void log_server_request(const httplib::Request & req, const httplib::Response & res) { +static void log_server_request(const httplib::Request& req, const httplib::Response& res) { printf("request: %s %s (%s)\n", req.method.c_str(), req.path.c_str(), req.body.c_str()); } +void parseJsonPrompt(std::string json_str, SDParams* params) { + using namespace nlohmann; + json payload = json::parse(json_str); + // if no exception, the request is a json object + // now we try to get the new param values from the payload object + // const char *prompt, const char *negative_prompt, int clip_skip, float cfg_scale, float guidance, int width, int height, sample_method_t sample_method, int sample_steps, int64_t seed, int batch_count, const sd_image_t *control_cond, float control_strength, float style_strength, bool normalize_input, const char *input_id_images_path + try { + std::string prompt = payload["prompt"]; + params->prompt = prompt; + } catch (...) { + } + try { + std::string negative_prompt = payload["negative_prompt"]; + params->negative_prompt = negative_prompt; + } catch (...) { + } + try { + int clip_skip = payload["clip_skip"]; + params->clip_skip = clip_skip; + } catch (...) { + } + try { + float cfg_scale = payload["cfg_scale"]; + params->cfg_scale = cfg_scale; + } catch (...) { + } + try { + float guidance = payload["guidance"]; + params->guidance = guidance; + } catch (...) { + } + try { + int width = payload["width"]; + params->width = width; + } catch (...) { + } + try { + int height = payload["height"]; + params->height = height; + } catch (...) { + } + try { + std::string sample_method = payload["sample_method"]; + // TODO map to enum value + LOG_WARN("sample_method is not supported yet\n"); + } catch (...) { + } + try { + int sample_steps = payload["sample_steps"]; + params->sample_steps = sample_steps; + } catch (...) { + } + try { + int64_t seed = payload["seed"]; + params->seed = seed; + } catch (...) { + } + try { + int batch_count = payload["batch_count"]; + params->batch_count = batch_count; + } catch (...) { + } + + try { + std::string control_cond = payload["control_cond"]; + // TODO map to enum value + LOG_WARN("control_cond is not supported yet\n"); + } catch (...) { + } + try { + float control_strength = payload["control_strength"]; + } catch (...) { + } + try { + float style_strength = payload["style_strength"]; + } catch (...) { + } + try { + bool normalize_input = payload["normalize_input"]; + params->normalize_input = normalize_input; + } catch (...) { + } + try { + std::string input_id_images_path = payload["input_id_images_path"]; + // TODO replace with b64 image maybe? + } catch (...) { + } +} int main(int argc, const char* argv[]) { SDParams params; parse_args(argc, argv, params); - sd_set_log_callback(sd_log_cb, (void*)¶ms); if (params.verbose) { @@ -583,9 +670,8 @@ int main(int argc, const char* argv[]) { printf("%s", sd_get_system_info()); } + bool vae_decode_only = true; - bool vae_decode_only = true; - sd_ctx_t* sd_ctx = new_sd_ctx(params.model_path.c_str(), params.clip_l_path.c_str(), params.t5xxl_path.c_str(), @@ -614,33 +700,48 @@ int main(int argc, const char* argv[]) { int n_prompts = 0; - const auto txt2imgRequest = [&sd_ctx, ¶ms, &n_prompts](const httplib::Request & req, httplib::Response & res) { - //TODO: proper payloads - std::string prompt = req.body; - if(!prompt.empty()){ - params.prompt = prompt; - }else{ - params.seed+=1; + const auto txt2imgRequest = [&sd_ctx, ¶ms, &n_prompts](const httplib::Request& req, httplib::Response& res) { + LOG_INFO("raw body is: %s\n", req.body.c_str()); + // parse req.body as json using jsoncpp + using json = nlohmann::json; + + try { + std::string json_str = req.body; + parseJsonPrompt(json_str, ¶ms); + } catch (json::parse_error& e) { + // assume the request is just a prompt + LOG_WARN("Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); + std::string prompt = req.body; + if (!prompt.empty()) { + params.prompt = prompt; + } else { + params.seed += 1; + } + } catch (...) { + // Handle any other type of exception + LOG_ERROR("An unexpected error occurred\n"); } + LOG_INFO("prompt is: %s\n", params.prompt.c_str()); + { sd_image_t* results; results = txt2img(sd_ctx, - params.prompt.c_str(), - params.negative_prompt.c_str(), - params.clip_skip, - params.cfg_scale, - params.guidance, - params.width, - params.height, - params.sample_method, - params.sample_steps, - params.seed, - params.batch_count, - NULL, - 1, - params.style_ratio, - params.normalize_input, - ""); + params.prompt.c_str(), + params.negative_prompt.c_str(), + params.clip_skip, + params.cfg_scale, + params.guidance, + params.width, + params.height, + params.sample_method, + params.sample_steps, + params.seed, + params.batch_count, + NULL, + 1, + params.style_ratio, + params.normalize_input, + ""); if (results == NULL) { printf("generate failed\n"); @@ -650,52 +751,67 @@ int main(int argc, const char* argv[]) { size_t last = params.output_path.find_last_of("."); std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path; + json images_json = json::array(); for (int i = 0; i < params.batch_count; i++) { if (results[i].data == NULL) { continue; } - std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1 + n_prompts*params.batch_count) + ".png" : dummy_name + ".png"; + // TODO allow disable save to disk + std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1 + n_prompts * params.batch_count) + ".png" : dummy_name + ".png"; stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, - results[i].data, 0, get_image_params(params, params.seed + i).c_str()); + results[i].data, 0, get_image_params(params, params.seed + i).c_str()); printf("save result image to '%s'\n", final_image_path.c_str()); - // Todo: return base64 encoded image via websocket? + // Todo: return base64 encoded image via httplib::Response& res + + int len; + unsigned char* png = stbi_write_png_to_mem((const unsigned char*)results[i].data, 0, results[i].width, results[i].height, results[i].channel, &len, NULL); + + std::string data_str(png, png + len); + std::string encoded_img = base64_encode(data_str); + + images_json.push_back({{"width", results[i].width}, + {"height", results[i].height}, + {"channel", results[i].channel}, + {"data", encoded_img}, + {"encoding", "png"}}); + free(results[i].data); results[i].data = NULL; } free(results); n_prompts++; + res.set_content(images_json.dump(), "application/json"); } return 0; }; - std::unique_ptr svr; svr.reset(new httplib::Server()); svr->set_default_headers({{"Server", "sd.cpp"}}); // CORS preflight - svr->Options(R"(.*)", [](const httplib::Request &, httplib::Response & res) { + svr->Options(R"(.*)", [](const httplib::Request&, httplib::Response& res) { // Access-Control-Allow-Origin is already set by middleware res.set_header("Access-Control-Allow-Credentials", "true"); - res.set_header("Access-Control-Allow-Methods", "POST"); - res.set_header("Access-Control-Allow-Headers", "*"); - return res.set_content("", "text/html"); // blank response, no data + res.set_header("Access-Control-Allow-Methods", "POST"); + res.set_header("Access-Control-Allow-Headers", "*"); + return res.set_content("", "text/html"); // blank response, no data }); svr->set_logger(log_server_request); svr->Post("/txt2img", txt2imgRequest); - // bind HTTP listen port, run the HTTP server in a thread if (!svr->bind_to_port(params.host, params.port)) { - //TODO: Error message + // TODO: Error message return 1; - } + } std::thread t([&]() { svr->listen_after_bind(); }); svr->wait_until_ready(); - printf("Server listening at %s:%d\n",params.host.c_str(),params.port); + printf("Server listening at %s:%d\n", params.host.c_str(), params.port); - while(1); + while (1) + ; free_sd_ctx(sd_ctx); From b61fce8a6488394984de52cfcdfb42cf8803df98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 4 Oct 2024 14:54:46 +0200 Subject: [PATCH 094/143] server: use t.join() instead of infinite loop --- examples/server/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 419c15ae2..9f516e831 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -810,8 +810,7 @@ int main(int argc, const char* argv[]) { printf("Server listening at %s:%d\n", params.host.c_str(), params.port); - while (1) - ; + t.join(); free_sd_ctx(sd_ctx); From 559f20530d027f7957d78ac43ef04f72f4b13941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 4 Oct 2024 15:06:47 +0200 Subject: [PATCH 095/143] server: fix CI Build --- examples/server/main.cpp | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 9f516e831..15b08c494 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -7,9 +7,7 @@ #include // #include "preprocessing.hpp" -#include "b64.cpp" #include "flux.hpp" -#include "json.hpp" #include "stable-diffusion.h" #define STB_IMAGE_IMPLEMENTATION @@ -24,7 +22,9 @@ #define STB_IMAGE_RESIZE_STATIC #include "stb_image_resize.h" +#include "b64.cpp" #include "httplib.h" +#include "json.hpp" const char* rng_type_to_str[] = { "std_default", @@ -565,6 +565,22 @@ void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) { fflush(out_stream); } +void* server_log_params = NULL; + +// enable logging in the server +#define LOG_BUFFER_SIZE 1024 +void sd_log(enum sd_log_level_t level, const char* format, ...) { + va_list args; + va_start(args, format); + + char log[LOG_BUFFER_SIZE]; + vsnprintf(log, 1024, format, args); + strncat(log, "\n", LOG_BUFFER_SIZE - strlen(log)); + + sd_log_cb(level, log, server_log_params); + va_end(args); +} + static void log_server_request(const httplib::Request& req, const httplib::Response& res) { printf("request: %s %s (%s)\n", req.method.c_str(), req.path.c_str(), req.body.c_str()); } @@ -613,7 +629,8 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { try { std::string sample_method = payload["sample_method"]; // TODO map to enum value - LOG_WARN("sample_method is not supported yet\n"); + // LOG_WARN("sample_method is not supported yet\n"); + sd_log(sd_log_level_t::SD_LOG_WARN, "sample_method is not supported yet\n"); } catch (...) { } try { @@ -635,7 +652,8 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { try { std::string control_cond = payload["control_cond"]; // TODO map to enum value - LOG_WARN("control_cond is not supported yet\n"); + // LOG_WARN("control_cond is not supported yet\n"); + sd_log(sd_log_level_t::SD_LOG_WARN, "control_cond is not supported yet\n"); } catch (...) { } try { @@ -665,6 +683,8 @@ int main(int argc, const char* argv[]) { sd_set_log_callback(sd_log_cb, (void*)¶ms); + server_log_params = (void*)¶ms; + if (params.verbose) { print_params(params); printf("%s", sd_get_system_info()); @@ -701,7 +721,8 @@ int main(int argc, const char* argv[]) { int n_prompts = 0; const auto txt2imgRequest = [&sd_ctx, ¶ms, &n_prompts](const httplib::Request& req, httplib::Response& res) { - LOG_INFO("raw body is: %s\n", req.body.c_str()); + // LOG_DEBUG("raw body is: %s\n", req.body.c_str()); + sd_log(sd_log_level_t::SD_LOG_DEBUG, "raw body is: %s\n", req.body.c_str()); // parse req.body as json using jsoncpp using json = nlohmann::json; @@ -710,7 +731,8 @@ int main(int argc, const char* argv[]) { parseJsonPrompt(json_str, ¶ms); } catch (json::parse_error& e) { // assume the request is just a prompt - LOG_WARN("Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); + // LOG_WARN("Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); + sd_log(sd_log_level_t::SD_LOG_WARN, "Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); std::string prompt = req.body; if (!prompt.empty()) { params.prompt = prompt; @@ -719,9 +741,11 @@ int main(int argc, const char* argv[]) { } } catch (...) { // Handle any other type of exception - LOG_ERROR("An unexpected error occurred\n"); + // LOG_ERROR("An unexpected error occurred\n"); + sd_log(sd_log_level_t::SD_LOG_ERROR, "An unexpected error occurred\n"); } - LOG_INFO("prompt is: %s\n", params.prompt.c_str()); + // LOG_DEBUG("prompt is: %s\n", params.prompt.c_str()); + sd_log(sd_log_level_t::SD_LOG_DEBUG, "prompt is: %s\n", params.prompt.c_str()); { sd_image_t* results; From 8b836166e99fefe2b271c7b3d108baac485552fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sat, 5 Oct 2024 13:11:41 +0200 Subject: [PATCH 096/143] server: attach image metadata in response --- examples/server/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 15b08c494..37b3291fa 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -788,7 +788,7 @@ int main(int argc, const char* argv[]) { // Todo: return base64 encoded image via httplib::Response& res int len; - unsigned char* png = stbi_write_png_to_mem((const unsigned char*)results[i].data, 0, results[i].width, results[i].height, results[i].channel, &len, NULL); + unsigned char* png = stbi_write_png_to_mem((const unsigned char*)results[i].data, 0, results[i].width, results[i].height, results[i].channel, &len, get_image_params(params, params.seed + i).c_str()); std::string data_str(png, png + len); std::string encoded_img = base64_encode(data_str); From bf69f5a1a66da7a934ee3ad9ee662fe1e6e719fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sat, 5 Oct 2024 16:40:43 +0200 Subject: [PATCH 097/143] server: add simple client script --- examples/server/test_client.py | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/server/test_client.py diff --git a/examples/server/test_client.py b/examples/server/test_client.py new file mode 100644 index 000000000..aeb2d8e12 --- /dev/null +++ b/examples/server/test_client.py @@ -0,0 +1,60 @@ +import requests, json, base64 +from io import BytesIO +from PIL import Image, PngImagePlugin, ImageShow +from threading import Thread + +def save_img(img: Image,path: str) -> None: + info = PngImagePlugin.PngInfo() + for key, value in img.info.items(): + info.add_text(key, value) + img.save(path,pnginfo=info) + +def show_img(img: Image, title = None) -> None: + info = PngImagePlugin.PngInfo() + for key, value in img.info.items(): + info.add_text(key, value) + tmp = img._dump(title, format=img.format, pnginfo=info) + print(f"Image path: {tmp}\n") + for viewer in ImageShow._viewers: + if viewer.show_file(tmp,title=title): + return + +_protocol = "http" +_server = "localhost" +_port = 8080 +_endpoint = "txt2img" + +def update_url(protocol= None, server=None,port=None,endpoint=None): + global _protocol, _server, _port, _endpoint + if protocol: + _protocol = protocol + if server: + _server = server + if port: + _port = port + if endpoint: + _endpoint = endpoint + return f"{_protocol}://{_server}:{_port}/{_endpoint}" + +url = update_url(port=8084) + +def sendRequest(payload): + global url + return requests.post(url,payload ).text + +def getImages(response): + return [Image.open(BytesIO(base64.b64decode(img["data"]) )) for img in json.loads(response)] + +def showImages(imgs): + for img in imgs: + t =Thread(target=show_img,args=(img,)) + t.setDaemon(True) + t.start() + + +def print_usage(): + print("""Example usage (images will be displayed and saved to a temporary file): +showImages(getImages(sendRequest(json.dumps({'seed': -1, 'batch_count':4, 'sample_steps':24, 'width': 512, 'height':768, 'negative_prompt': "Bad quality", 'prompt': "A beautiful image"}))))""") + +if __name__ == "__main__": + print_usage() From b128c413bec5dc3f28c70bc870fce11e34755a66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sat, 5 Oct 2024 17:14:55 +0200 Subject: [PATCH 098/143] Server: add client docstrings --- examples/server/test_client.py | 135 +++++++++++++++++++++++++++++---- 1 file changed, 119 insertions(+), 16 deletions(-) diff --git a/examples/server/test_client.py b/examples/server/test_client.py index aeb2d8e12..491752090 100644 --- a/examples/server/test_client.py +++ b/examples/server/test_client.py @@ -3,13 +3,36 @@ from PIL import Image, PngImagePlugin, ImageShow from threading import Thread -def save_img(img: Image,path: str) -> None: +from typing import List + + +def save_img(img: Image, path: str) -> None: + """ + Save the image to the specified path with metadata. + + Args: + img (Image): The image to be saved. + path (str): The path where the image will be saved. + + Returns: + None + """ info = PngImagePlugin.PngInfo() for key, value in img.info.items(): info.add_text(key, value) - img.save(path,pnginfo=info) + img.save(path, pnginfo=info) def show_img(img: Image, title = None) -> None: + """ + Display the image (with metadata) in a new window and print the path of the temporary file. + + Args: + img (Image): The image to be displayed. + title (str, optional): The title of the image window. Defaults to None. + + Returns: + None + """ info = PngImagePlugin.PngInfo() for key, value in img.info.items(): info.add_text(key, value) @@ -23,9 +46,26 @@ def show_img(img: Image, title = None) -> None: _server = "localhost" _port = 8080 _endpoint = "txt2img" +url="" -def update_url(protocol= None, server=None,port=None,endpoint=None): - global _protocol, _server, _port, _endpoint +def update_url(protocol=None, server=None, port=None, endpoint=None) -> str: + """ + Update the global URL variable with the provided protocol, server, port, and endpoint. + + This function takes optional arguments for protocol, server, port, and endpoint. + If any of these arguments are provided, the corresponding global variable is updated with the new value. + The function then constructs the URL using the updated global variables and returns it. + + Args: + protocol (str, optional): The protocol to be used in the URL. Defaults to None. + server (str, optional): The server address to be used in the URL. Defaults to None. + port (int, optional): The port number to be used in the URL. Defaults to None. + endpoint (str, optional): The endpoint to be used in the URL. Defaults to None. + + Returns: + str: The updated URL. + """ + global _protocol, _server, _port, _endpoint, url if protocol: _protocol = protocol if server: @@ -34,27 +74,90 @@ def update_url(protocol= None, server=None,port=None,endpoint=None): _port = port if endpoint: _endpoint = endpoint - return f"{_protocol}://{_server}:{_port}/{_endpoint}" + url = f"{_protocol}://{_server}:{_port}/{_endpoint}" + return url + +# set default url value +update_url() + +def sendRequest(payload: str) -> str: + """ + Send a POST request to the API endpoint with the provided payload. -url = update_url(port=8084) + This function takes a payload as input and sends a POST request to the API endpoint specified by the global URL variable. + The function then returns the text content of the response. -def sendRequest(payload): + Args: + payload (str): The payload to be sent in the POST request. + + Returns: + str: The text content of the response from the POST request. + """ global url - return requests.post(url,payload ).text + return requests.post(url, payload).text + +def getImages(response: str) -> List[Image.Image]: + """ + Convert base64 encoded image data from the API response into a list of Image objects. + + This function takes the text response from the API as input and parses it as JSON. + It then iterates over each image data in the JSON response, decodes the base64 encoded image data, + and uses the BytesIO class to convert it into a PIL Image object. + The function returns a list of these Image objects. + + Args: + response (str): The text response from the API containing base64 encoded image data. + + Returns: + List[Image.Image]: A list of PIL Image objects decoded from the base64 encoded image data in the API response. + """ + return [Image.open(BytesIO(base64.b64decode(img["data"]))) for img in json.loads(response)] + +def showImages(imgs: List[Image.Image]) -> None: + """ + Display a list of images in separate threads. -def getImages(response): - return [Image.open(BytesIO(base64.b64decode(img["data"]) )) for img in json.loads(response)] + This function takes a list of PIL Image objects as input and creates a new thread for each image. + Each thread calls the show_img function to display the image in a new window and print the path of the temporary file. + The function does not return any value. -def showImages(imgs): - for img in imgs: - t =Thread(target=show_img,args=(img,)) + Args: + imgs (List[Image.Image]): A list of PIL Image objects to be displayed. + + Returns: + None + """ + for (i,img) in enumerate(imgs): + t = Thread(target=show_img, args=(img, f"IMG {i}")) t.setDaemon(True) - t.start() + t.start + +def saveImages(imgs: List[Image.Image], path: str) -> None: + """ + Save a list of images to the specified path with metadata. + + This function takes a list of PIL Image objects and a path as input. + For each image, it calls the save_img function to save the image to a file + with the name "{path}{i}.png", where i is the index of the image in the list. + The function does not return any value. + + Args: + imgs (List[Image.Image]): A list of PIL Image objects to be saved. + path (str): The path where the images will be saved. + + Returns: + None + """ + if path.endswith(".png"): + path = path[:-4] + for (i, img) in enumerate(imgs): + save_img(img, f"{path}{i}.png") -def print_usage(): +def _print_usage(): print("""Example usage (images will be displayed and saved to a temporary file): +update_url(server=127.0.0.1, port=8080) showImages(getImages(sendRequest(json.dumps({'seed': -1, 'batch_count':4, 'sample_steps':24, 'width': 512, 'height':768, 'negative_prompt': "Bad quality", 'prompt': "A beautiful image"}))))""") if __name__ == "__main__": - print_usage() + _print_usage() From 897df4c3088515a279503cccd263f16d47284b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sat, 5 Oct 2024 19:33:11 +0200 Subject: [PATCH 099/143] server: client: fix image preview on non-windows os --- examples/server/test_client.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/server/test_client.py b/examples/server/test_client.py index 491752090..d1e28e7fd 100644 --- a/examples/server/test_client.py +++ b/examples/server/test_client.py @@ -1,7 +1,10 @@ import requests, json, base64 from io import BytesIO from PIL import Image, PngImagePlugin, ImageShow -from threading import Thread +import os +if os.name == 'nt': + from threading import Thread + from typing import List @@ -128,9 +131,12 @@ def showImages(imgs: List[Image.Image]) -> None: None """ for (i,img) in enumerate(imgs): - t = Thread(target=show_img, args=(img, f"IMG {i}")) - t.setDaemon(True) - t.start + if os.name == 'nt': + t = Thread(target=show_img, args=(img, f"IMG {i}")) + t.setDaemon(True) + t.start + else: + show_img(img, f"IMG {i}") def saveImages(imgs: List[Image.Image], path: str) -> None: """ From 5c7d32fd94c1a41ab4d9a00c7f989ddca9ff5f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sun, 6 Oct 2024 02:00:11 +0200 Subject: [PATCH 100/143] server: support sampling method arg --- examples/server/main.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 37b3291fa..1f8a7282a 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -628,9 +628,18 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { } try { std::string sample_method = payload["sample_method"]; - // TODO map to enum value - // LOG_WARN("sample_method is not supported yet\n"); - sd_log(sd_log_level_t::SD_LOG_WARN, "sample_method is not supported yet\n"); + + int sample_method_found = -1; + for (int m = 0; m < N_SAMPLE_METHODS; m++) { + if (!strcmp(sample_method.c_str(), sample_method_str[m])) { + sample_method_found = m; + } + } + if (sample_method_found >= 0) { + params->sample_method = (sample_method_t)sample_method_found; + } else { + sd_log(sd_log_level_t::SD_LOG_WARN, "Unknown sampling method: %s\n", sample_method.c_str()); + } } catch (...) { } try { From 45ca9bb96ef56c2958a8dfe82bc9fad338777b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sun, 6 Oct 2024 01:58:28 +0200 Subject: [PATCH 101/143] server: small test client fixes --- examples/server/test_client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/server/test_client.py b/examples/server/test_client.py index d1e28e7fd..69a00dc7a 100644 --- a/examples/server/test_client.py +++ b/examples/server/test_client.py @@ -39,7 +39,7 @@ def show_img(img: Image, title = None) -> None: info = PngImagePlugin.PngInfo() for key, value in img.info.items(): info.add_text(key, value) - tmp = img._dump(title, format=img.format, pnginfo=info) + tmp = img._dump(None, format=img.format, pnginfo=info) print(f"Image path: {tmp}\n") for viewer in ImageShow._viewers: if viewer.show_file(tmp,title=title): @@ -133,8 +133,8 @@ def showImages(imgs: List[Image.Image]) -> None: for (i,img) in enumerate(imgs): if os.name == 'nt': t = Thread(target=show_img, args=(img, f"IMG {i}")) - t.setDaemon(True) - t.start + t.daemon = True + t.start() else: show_img(img, f"IMG {i}") @@ -162,7 +162,7 @@ def saveImages(imgs: List[Image.Image], path: str) -> None: def _print_usage(): print("""Example usage (images will be displayed and saved to a temporary file): -update_url(server=127.0.0.1, port=8080) +update_url(server="127.0.0.1", port=8080) showImages(getImages(sendRequest(json.dumps({'seed': -1, 'batch_count':4, 'sample_steps':24, 'width': 512, 'height':768, 'negative_prompt': "Bad quality", 'prompt': "A beautiful image"}))))""") if __name__ == "__main__": From b5772ae11d2e969548eb8c83f6a2394525e587ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Tue, 22 Oct 2024 17:08:48 +0200 Subject: [PATCH 102/143] Try adding photomaker support --- examples/server/main.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 1f8a7282a..4dfdf1bf1 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -63,6 +63,7 @@ struct SDParams { int n_threads = -1; SDMode mode = TXT2IMG; + // models std::string model_path; std::string clip_l_path; std::string t5xxl_path; @@ -71,8 +72,9 @@ struct SDParams { // std::string taesd_path; std::string embeddings_path; std::string stacked_id_embeddings_path; - sd_type_t wtype = SD_TYPE_COUNT; std::string lora_model_dir; + + sd_type_t wtype = SD_TYPE_COUNT; std::string output_path = "output.png"; std::string input_path; @@ -100,6 +102,9 @@ struct SDParams { bool vae_on_cpu = false; bool color = false; + // Photomaker params + std::string input_id_images_path; + // server things int port = 8080; std::string host = "127.0.0.1"; @@ -660,6 +665,7 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { try { std::string control_cond = payload["control_cond"]; + // TODO map to enum value // LOG_WARN("control_cond is not supported yet\n"); sd_log(sd_log_level_t::SD_LOG_WARN, "control_cond is not supported yet\n"); @@ -667,10 +673,16 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { } try { float control_strength = payload["control_strength"]; + // params->control_strength = control_strength; + // LOG_WARN("control_strength is not supported yet\n"); + sd_log(sd_log_level_t::SD_LOG_WARN, "control_strength is not supported yet\n", params); } catch (...) { } try { float style_strength = payload["style_strength"]; + // params->style_strength = style_strength; + // LOG_WARN("style_strength is not supported yet\n"); + sd_log(sd_log_level_t::SD_LOG_WARN, "style_strength is not supported yet\n", params); } catch (...) { } try { @@ -681,6 +693,7 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { try { std::string input_id_images_path = payload["input_id_images_path"]; // TODO replace with b64 image maybe? + params->input_id_images_path = input_id_images_path; } catch (...) { } } @@ -774,7 +787,7 @@ int main(int argc, const char* argv[]) { 1, params.style_ratio, params.normalize_input, - ""); + params.input_id_images_path.c_str()); if (results == NULL) { printf("generate failed\n"); From 4e651e2062767c6933e1aeb3d2e869e681a1aefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Mon, 25 Nov 2024 01:32:21 +0100 Subject: [PATCH 103/143] server: update --- examples/server/main.cpp | 207 ++++++++++++++++++++++++++++++++++----- 1 file changed, 185 insertions(+), 22 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 4dfdf1bf1..9e6292971 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -66,17 +66,21 @@ struct SDParams { // models std::string model_path; std::string clip_l_path; + std::string clip_g_path; std::string t5xxl_path; std::string diffusion_model_path; std::string vae_path; - // std::string taesd_path; + std::string taesd_path; + std::string esrgan_path; + std::string controlnet_path; std::string embeddings_path; std::string stacked_id_embeddings_path; - std::string lora_model_dir; - + std::string input_id_images_path; sd_type_t wtype = SD_TYPE_COUNT; + std::string lora_model_dir; std::string output_path = "output.png"; std::string input_path; + std::string control_image_path; std::string prompt; std::string negative_prompt; @@ -93,17 +97,22 @@ struct SDParams { schedule_t schedule = DEFAULT; int sample_steps = 20; float strength = 0.75f; + float control_strength = 0.9f; rng_type_t rng_type = CUDA_RNG; int64_t seed = 42; bool verbose = false; bool vae_tiling = false; + bool control_net_cpu = false; bool normalize_input = false; bool clip_on_cpu = false; bool vae_on_cpu = false; + bool diffusion_flash_attn = false; bool color = false; - // Photomaker params - std::string input_id_images_path; + std::vector skip_layers = {7, 8, 9}; + float slg_scale = 0.; + float skip_layer_start = 0.01; + float skip_layer_end = 0.2; // server things int port = 8080; @@ -113,24 +122,34 @@ struct SDParams { void print_params(SDParams params) { printf("Option: \n"); printf(" n_threads: %d\n", params.n_threads); + printf(" mode: server\n"); printf(" model_path: %s\n", params.model_path.c_str()); printf(" wtype: %s\n", params.wtype < SD_TYPE_COUNT ? sd_type_name(params.wtype) : "unspecified"); printf(" clip_l_path: %s\n", params.clip_l_path.c_str()); + printf(" clip_g_path: %s\n", params.clip_g_path.c_str()); printf(" t5xxl_path: %s\n", params.t5xxl_path.c_str()); printf(" diffusion_model_path: %s\n", params.diffusion_model_path.c_str()); printf(" vae_path: %s\n", params.vae_path.c_str()); - // printf(" taesd_path: %s\n", params.taesd_path.c_str()); + printf(" taesd_path: %s\n", params.taesd_path.c_str()); + printf(" controlnet_path: %s\n", params.controlnet_path.c_str()); printf(" embeddings_path: %s\n", params.embeddings_path.c_str()); printf(" stacked_id_embeddings_path: %s\n", params.stacked_id_embeddings_path.c_str()); + printf(" input_id_images_path: %s\n", params.input_id_images_path.c_str()); printf(" style ratio: %.2f\n", params.style_ratio); - printf(" normzalize input image : %s\n", params.normalize_input ? "true" : "false"); + printf(" normalize input image : %s\n", params.normalize_input ? "true" : "false"); printf(" output_path: %s\n", params.output_path.c_str()); + printf(" init_img: %s\n", params.input_path.c_str()); + printf(" control_image: %s\n", params.control_image_path.c_str()); printf(" clip on cpu: %s\n", params.clip_on_cpu ? "true" : "false"); + printf(" controlnet cpu: %s\n", params.control_net_cpu ? "true" : "false"); printf(" vae decoder on cpu:%s\n", params.vae_on_cpu ? "true" : "false"); + printf(" diffusion flash attention:%s\n", params.diffusion_flash_attn ? "true" : "false"); + printf(" strength(control): %.2f\n", params.control_strength); printf(" prompt: %s\n", params.prompt.c_str()); printf(" negative_prompt: %s\n", params.negative_prompt.c_str()); printf(" min_cfg: %.2f\n", params.min_cfg); printf(" cfg_scale: %.2f\n", params.cfg_scale); + printf(" slg_scale: %.2f\n", params.slg_scale); printf(" guidance: %.2f\n", params.guidance); printf(" clip_skip: %d\n", params.clip_skip); printf(" width: %d\n", params.width); @@ -150,40 +169,59 @@ void print_usage(int argc, const char* argv[]) { printf("\n"); printf("arguments:\n"); printf(" -h, --help show this help message and exit\n"); - printf(" -M, --mode [MODEL] run mode (txt2img or img2img or convert, default: txt2img)\n"); - printf(" -t, --threads N number of threads to use during computation (default: -1).\n"); + printf(" -t, --threads N number of threads to use during computation (default: -1)\n"); printf(" If threads <= 0, then threads will be set to the number of CPU physical cores\n"); printf(" -m, --model [MODEL] path to full model\n"); printf(" --diffusion-model path to the standalone diffusion model\n"); printf(" --clip_l path to the clip-l text encoder\n"); - printf(" --t5xxl path to the the t5xxl text encoder.\n"); + printf(" --clip_g path to the clip-g text encoder\n"); + printf(" --t5xxl path to the the t5xxl text encoder\n"); printf(" --vae [VAE] path to vae\n"); - printf(" --embd-dir [EMBEDDING_PATH] path to embeddings.\n"); + printf(" --taesd [TAESD_PATH] path to taesd. Using Tiny AutoEncoder for fast decoding (low quality)\n"); + printf(" --control-net [CONTROL_PATH] path to control net model\n"); + printf(" --embd-dir [EMBEDDING_PATH] path to embeddings\n"); + printf(" --stacked-id-embd-dir [DIR] path to PHOTOMAKER stacked id embeddings\n"); + printf(" --input-id-images-dir [DIR] path to PHOTOMAKER input id images dir\n"); + printf(" --normalize-input normalize PHOTOMAKER input id images\n"); + // printf(" --upscale-model [ESRGAN_PATH] path to esrgan model. Upscale images after generate, just RealESRGAN_x4plus_anime_6B supported by now\n"); + // printf(" --upscale-repeats Run the ESRGAN upscaler this many times (default 1)\n"); printf(" --type [TYPE] weight type (f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_k, q3_k, q4_k)\n"); - printf(" If not specified, the default is the type of the weight file.\n"); + printf(" If not specified, the default is the type of the weight file\n"); printf(" --lora-model-dir [DIR] lora model directory\n"); + printf(" --control-image [IMAGE] path to image condition, control net\n"); printf(" -o, --output OUTPUT path to write result image to (default: ./output.png)\n"); printf(" -p, --prompt [PROMPT] the prompt to render\n"); printf(" -n, --negative-prompt PROMPT the negative prompt (default: \"\")\n"); printf(" --cfg-scale SCALE unconditional guidance scale: (default: 7.0)\n"); + printf(" --slg-scale SCALE skip layer guidance (SLG) scale, only for DiT models: (default: 0)\n"); + printf(" 0 means disabled, a value of 2.5 is nice for sd3.5 medium\n"); + printf(" --skip_layers LAYERS Layers to skip for SLG steps: (default: [7,8,9])\n"); + printf(" --skip_layer_start START SLG enabling point: (default: 0.01)\n"); + printf(" --skip_layer_end END SLG disabling point: (default: 0.2)\n"); + printf(" SLG will be enabled at step int([STEPS]*[START]) and disabled at int([STEPS]*[END])\n"); printf(" --strength STRENGTH strength for noising/unnoising (default: 0.75)\n"); printf(" --style-ratio STYLE-RATIO strength for keeping input identity (default: 20%%)\n"); printf(" --control-strength STRENGTH strength to apply Control Net (default: 0.9)\n"); printf(" 1.0 corresponds to full destruction of information in init image\n"); printf(" -H, --height H image height, in pixel space (default: 512)\n"); printf(" -W, --width W image width, in pixel space (default: 512)\n"); - printf(" --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, lcm}\n"); + printf(" --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, ipndm, ipndm_v, lcm}\n"); printf(" sampling method (default: \"euler_a\")\n"); printf(" --steps STEPS number of sample steps (default: 20)\n"); printf(" --rng {std_default, cuda} RNG (default: cuda)\n"); printf(" -s SEED, --seed SEED RNG seed (default: 42, use random seed for < 0)\n"); - printf(" -b, --batch-count COUNT number of images to generate.\n"); - printf(" --schedule {discrete, karras, ays} Denoiser sigma schedule (default: discrete)\n"); + printf(" -b, --batch-count COUNT number of images to generate\n"); + printf(" --schedule {discrete, karras, exponential, ays, gits} Denoiser sigma schedule (default: discrete)\n"); printf(" --clip-skip N ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer (default: -1)\n"); printf(" <= 0 represents unspecified, will be 1 for SD1.x, 2 for SD2.x\n"); printf(" --vae-tiling process vae in tiles to reduce memory usage\n"); printf(" --vae-on-cpu keep vae in cpu (for low vram)\n"); - printf(" --clip-on-cpu keep clip in cpu (for low vram).\n"); + printf(" --clip-on-cpu keep clip in cpu (for low vram)\n"); + printf(" --diffusion-fa use flash attention in the diffusion model (for low vram)\n"); + printf(" Might lower quality, since it implies converting k and v to f16.\n"); + printf(" This might crash if it is not supported by the backend.\n"); + printf(" --control-net-cpu keep controlnet in cpu (for low vram)\n"); + printf(" --canny apply canny preprocessor (edge detection)\n"); printf(" --color Colors the logging tags according to level\n"); printf(" -v, --verbose print extra info\n"); printf(" --port port used for server (default: 8080)\n"); @@ -214,6 +252,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.clip_l_path = argv[i]; + } else if (arg == "--clip_g") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.clip_g_path = argv[i]; } else if (arg == "--t5xxl") { if (++i >= argc) { invalid_arg = true; @@ -232,7 +276,42 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.vae_path = argv[i]; - // TODO Tiny AE + } else if (arg == "--taesd") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.taesd_path = argv[i]; + } else if (arg == "--control-net") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.controlnet_path = argv[i]; + } else if (arg == "--upscale-model") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.esrgan_path = argv[i]; + } else if (arg == "--embd-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.embeddings_path = argv[i]; + } else if (arg == "--stacked-id-embd-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.stacked_id_embeddings_path = argv[i]; + } else if (arg == "--input-id-images-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.input_id_images_path = argv[i]; } else if (arg == "--type") { if (++i >= argc) { invalid_arg = true; @@ -270,6 +349,18 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.lora_model_dir = argv[i]; + } else if (arg == "-i" || arg == "--init-img") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.input_path = argv[i]; + } else if (arg == "--control-image") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.control_image_path = argv[i]; } else if (arg == "-o" || arg == "--output") { if (++i >= argc) { invalid_arg = true; @@ -312,6 +403,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.style_ratio = std::stof(argv[i]); + } else if (arg == "--control-strength") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.control_strength = std::stof(argv[i]); } else if (arg == "-H" || arg == "--height") { if (++i >= argc) { invalid_arg = true; @@ -338,12 +435,16 @@ void parse_args(int argc, const char** argv, SDParams& params) { params.clip_skip = std::stoi(argv[i]); } else if (arg == "--vae-tiling") { params.vae_tiling = true; + } else if (arg == "--control-net-cpu") { + params.control_net_cpu = true; } else if (arg == "--normalize-input") { params.normalize_input = true; } else if (arg == "--clip-on-cpu") { params.clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs } else if (arg == "--vae-on-cpu") { params.vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs + } else if (arg == "--diffusion-fa") { + params.diffusion_flash_attn = true; // can reduce MEM significantly } else if (arg == "-b" || arg == "--batch-count") { if (++i >= argc) { invalid_arg = true; @@ -411,6 +512,61 @@ void parse_args(int argc, const char** argv, SDParams& params) { params.verbose = true; } else if (arg == "--color") { params.color = true; + } else if (arg == "--slg-scale") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.slg_scale = std::stof(argv[i]); + } else if (arg == "--skip-layers") { + if (++i >= argc) { + invalid_arg = true; + break; + } + if (argv[i][0] != '[') { + invalid_arg = true; + break; + } + std::string layers_str = argv[i]; + while (layers_str.back() != ']') { + if (++i >= argc) { + invalid_arg = true; + break; + } + layers_str += " " + std::string(argv[i]); + } + layers_str = layers_str.substr(1, layers_str.size() - 2); + + std::regex regex("[, ]+"); + std::sregex_token_iterator iter(layers_str.begin(), layers_str.end(), regex, -1); + std::sregex_token_iterator end; + std::vector tokens(iter, end); + std::vector layers; + for (const auto& token : tokens) { + try { + layers.push_back(std::stoi(token)); + } catch (const std::invalid_argument& e) { + invalid_arg = true; + break; + } + } + params.skip_layers = layers; + + if (invalid_arg) { + break; + } + } else if (arg == "--skip-layer-start") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.skip_layer_start = std::stof(argv[i]); + } else if (arg == "--skip-layer-end") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.skip_layer_end = std::stof(argv[i]); } else if (arg == "--port") { if (++i >= argc) { invalid_arg = true; @@ -716,11 +872,12 @@ int main(int argc, const char* argv[]) { sd_ctx_t* sd_ctx = new_sd_ctx(params.model_path.c_str(), params.clip_l_path.c_str(), + params.clip_g_path.c_str(), params.t5xxl_path.c_str(), params.diffusion_model_path.c_str(), params.vae_path.c_str(), - "", - "", + params.taesd_path.c_str(), + params.controlnet_path.c_str(), params.lora_model_dir.c_str(), params.embeddings_path.c_str(), params.stacked_id_embeddings_path.c_str(), @@ -732,8 +889,9 @@ int main(int argc, const char* argv[]) { params.rng_type, params.schedule, params.clip_on_cpu, - true, - params.vae_on_cpu); + params.control_net_cpu, + params.vae_on_cpu, + params.diffusion_flash_attn); if (sd_ctx == NULL) { printf("new_sd_ctx_t failed\n"); @@ -787,7 +945,12 @@ int main(int argc, const char* argv[]) { 1, params.style_ratio, params.normalize_input, - params.input_id_images_path.c_str()); + params.input_id_images_path.c_str(), + params.skip_layers.data(), + params.skip_layers.size(), + params.slg_scale, + params.skip_layer_start, + params.skip_layer_end); if (results == NULL) { printf("generate failed\n"); From 9bce34c2b13d87cef33f0ec06f5e6a02cfd48bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Thu, 2 Jan 2025 19:41:25 +0100 Subject: [PATCH 104/143] server: update and refacor (add queue) --- examples/server/main.cpp | 752 ++++++++++++++++++--------------- examples/server/test_client.py | 18 +- 2 files changed, 430 insertions(+), 340 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 9e6292971..1dc98ed6d 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -26,6 +26,13 @@ #include "httplib.h" #include "json.hpp" +#include +#include +#include +#include +#include +#include + const char* rng_type_to_str[] = { "std_default", "cuda", @@ -54,16 +61,10 @@ const char* schedule_str[] = { enum SDMode { TXT2IMG, IMG2IMG, - IMG2VID, - CONVERT, MODE_COUNT }; -struct SDParams { - int n_threads = -1; - SDMode mode = TXT2IMG; - - // models +struct SDCtxParams { std::string model_path; std::string clip_l_path; std::string clip_g_path; @@ -71,19 +72,37 @@ struct SDParams { std::string diffusion_model_path; std::string vae_path; std::string taesd_path; - std::string esrgan_path; + std::string controlnet_path; + std::string lora_model_dir; std::string embeddings_path; std::string stacked_id_embeddings_path; - std::string input_id_images_path; + + bool vae_decode_only = false; + bool vae_tiling = false; + + int n_threads = -1; sd_type_t wtype = SD_TYPE_COUNT; - std::string lora_model_dir; - std::string output_path = "output.png"; - std::string input_path; - std::string control_image_path; + + rng_type_t rng_type = CUDA_RNG; + schedule_t schedule = DEFAULT; + + bool control_net_cpu = false; + bool clip_on_cpu = false; + bool vae_on_cpu = false; + + bool diffusion_flash_attn = false; +}; + +struct SDRequestParams { + // TODO set to true if esrgan_path is specified in args + bool upscale = false; + + SDMode mode = TXT2IMG; std::string prompt; std::string negative_prompt; + float min_cfg = 1.0f; float cfg_scale = 7.0f; float guidance = 3.5f; @@ -94,25 +113,34 @@ struct SDParams { int batch_count = 1; sample_method_t sample_method = EULER_A; - schedule_t schedule = DEFAULT; int sample_steps = 20; float strength = 0.75f; float control_strength = 0.9f; - rng_type_t rng_type = CUDA_RNG; int64_t seed = 42; - bool verbose = false; - bool vae_tiling = false; - bool control_net_cpu = false; - bool normalize_input = false; - bool clip_on_cpu = false; - bool vae_on_cpu = false; - bool diffusion_flash_attn = false; - bool color = false; std::vector skip_layers = {7, 8, 9}; float slg_scale = 0.; float skip_layer_start = 0.01; float skip_layer_end = 0.2; + bool normalize_input = false; +}; + +struct SDParams { + SDCtxParams ctxParams; + SDRequestParams lastRequest; + + std::string esrgan_path; + + std::string output_path = "./server/output.png"; + std::string input_path = "./server/input.png"; + std::string control_image_path = "./server/control.png"; + + // external dir + std::string input_id_images_path; + + bool verbose = false; + + bool color = false; // server things int port = 8080; @@ -120,48 +148,48 @@ struct SDParams { }; void print_params(SDParams params) { - printf("Option: \n"); - printf(" n_threads: %d\n", params.n_threads); + printf("Starting Options: \n"); + printf(" n_threads: %d\n", params.ctxParams.n_threads); printf(" mode: server\n"); - printf(" model_path: %s\n", params.model_path.c_str()); - printf(" wtype: %s\n", params.wtype < SD_TYPE_COUNT ? sd_type_name(params.wtype) : "unspecified"); - printf(" clip_l_path: %s\n", params.clip_l_path.c_str()); - printf(" clip_g_path: %s\n", params.clip_g_path.c_str()); - printf(" t5xxl_path: %s\n", params.t5xxl_path.c_str()); - printf(" diffusion_model_path: %s\n", params.diffusion_model_path.c_str()); - printf(" vae_path: %s\n", params.vae_path.c_str()); - printf(" taesd_path: %s\n", params.taesd_path.c_str()); - printf(" controlnet_path: %s\n", params.controlnet_path.c_str()); - printf(" embeddings_path: %s\n", params.embeddings_path.c_str()); - printf(" stacked_id_embeddings_path: %s\n", params.stacked_id_embeddings_path.c_str()); + printf(" model_path: %s\n", params.ctxParams.model_path.c_str()); + printf(" wtype: %s\n", params.ctxParams.wtype < SD_TYPE_COUNT ? sd_type_name(params.ctxParams.wtype) : "unspecified"); + printf(" clip_l_path: %s\n", params.ctxParams.clip_l_path.c_str()); + printf(" clip_g_path: %s\n", params.ctxParams.clip_g_path.c_str()); + printf(" t5xxl_path: %s\n", params.ctxParams.t5xxl_path.c_str()); + printf(" diffusion_model_path: %s\n", params.ctxParams.diffusion_model_path.c_str()); + printf(" vae_path: %s\n", params.ctxParams.vae_path.c_str()); + printf(" taesd_path: %s\n", params.ctxParams.taesd_path.c_str()); + printf(" controlnet_path: %s\n", params.ctxParams.controlnet_path.c_str()); + printf(" embeddings_path: %s\n", params.ctxParams.embeddings_path.c_str()); + printf(" stacked_id_embeddings_path: %s\n", params.ctxParams.stacked_id_embeddings_path.c_str()); printf(" input_id_images_path: %s\n", params.input_id_images_path.c_str()); - printf(" style ratio: %.2f\n", params.style_ratio); - printf(" normalize input image : %s\n", params.normalize_input ? "true" : "false"); + printf(" style ratio: %.2f\n", params.lastRequest.style_ratio); + printf(" normalize input image : %s\n", params.lastRequest.normalize_input ? "true" : "false"); printf(" output_path: %s\n", params.output_path.c_str()); printf(" init_img: %s\n", params.input_path.c_str()); printf(" control_image: %s\n", params.control_image_path.c_str()); - printf(" clip on cpu: %s\n", params.clip_on_cpu ? "true" : "false"); - printf(" controlnet cpu: %s\n", params.control_net_cpu ? "true" : "false"); - printf(" vae decoder on cpu:%s\n", params.vae_on_cpu ? "true" : "false"); - printf(" diffusion flash attention:%s\n", params.diffusion_flash_attn ? "true" : "false"); - printf(" strength(control): %.2f\n", params.control_strength); - printf(" prompt: %s\n", params.prompt.c_str()); - printf(" negative_prompt: %s\n", params.negative_prompt.c_str()); - printf(" min_cfg: %.2f\n", params.min_cfg); - printf(" cfg_scale: %.2f\n", params.cfg_scale); - printf(" slg_scale: %.2f\n", params.slg_scale); - printf(" guidance: %.2f\n", params.guidance); - printf(" clip_skip: %d\n", params.clip_skip); - printf(" width: %d\n", params.width); - printf(" height: %d\n", params.height); - printf(" sample_method: %s\n", sample_method_str[params.sample_method]); - printf(" schedule: %s\n", schedule_str[params.schedule]); - printf(" sample_steps: %d\n", params.sample_steps); - printf(" strength(img2img): %.2f\n", params.strength); - printf(" rng: %s\n", rng_type_to_str[params.rng_type]); - printf(" seed: %ld\n", params.seed); - printf(" batch_count: %d\n", params.batch_count); - printf(" vae_tiling: %s\n", params.vae_tiling ? "true" : "false"); + printf(" clip on cpu: %s\n", params.ctxParams.clip_on_cpu ? "true" : "false"); + printf(" controlnet cpu: %s\n", params.ctxParams.control_net_cpu ? "true" : "false"); + printf(" vae decoder on cpu:%s\n", params.ctxParams.vae_on_cpu ? "true" : "false"); + printf(" diffusion flash attention:%s\n", params.ctxParams.diffusion_flash_attn ? "true" : "false"); + printf(" strength(control): %.2f\n", params.lastRequest.control_strength); + printf(" prompt: %s\n", params.lastRequest.prompt.c_str()); + printf(" negative_prompt: %s\n", params.lastRequest.negative_prompt.c_str()); + printf(" min_cfg: %.2f\n", params.lastRequest.min_cfg); + printf(" cfg_scale: %.2f\n", params.lastRequest.cfg_scale); + printf(" slg_scale: %.2f\n", params.lastRequest.slg_scale); + printf(" guidance: %.2f\n", params.lastRequest.guidance); + printf(" clip_skip: %d\n", params.lastRequest.clip_skip); + printf(" width: %d\n", params.lastRequest.width); + printf(" height: %d\n", params.lastRequest.height); + printf(" sample_method: %s\n", sample_method_str[params.lastRequest.sample_method]); + printf(" schedule: %s\n", schedule_str[params.ctxParams.schedule]); + printf(" sample_steps: %d\n", params.lastRequest.sample_steps); + printf(" strength(img2img): %.2f\n", params.lastRequest.strength); + printf(" rng: %s\n", rng_type_to_str[params.ctxParams.rng_type]); + printf(" seed: %ld\n", params.lastRequest.seed); + printf(" batch_count: %d\n", params.lastRequest.batch_count); + printf(" vae_tiling: %s\n", params.ctxParams.vae_tiling ? "true" : "false"); } void print_usage(int argc, const char* argv[]) { @@ -239,55 +267,55 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.n_threads = std::stoi(argv[i]); + params.ctxParams.n_threads = std::stoi(argv[i]); } else if (arg == "-m" || arg == "--model") { if (++i >= argc) { invalid_arg = true; break; } - params.model_path = argv[i]; + params.ctxParams.model_path = argv[i]; } else if (arg == "--clip_l") { if (++i >= argc) { invalid_arg = true; break; } - params.clip_l_path = argv[i]; + params.ctxParams.clip_l_path = argv[i]; } else if (arg == "--clip_g") { if (++i >= argc) { invalid_arg = true; break; } - params.clip_g_path = argv[i]; + params.ctxParams.clip_g_path = argv[i]; } else if (arg == "--t5xxl") { if (++i >= argc) { invalid_arg = true; break; } - params.t5xxl_path = argv[i]; + params.ctxParams.t5xxl_path = argv[i]; } else if (arg == "--diffusion-model") { if (++i >= argc) { invalid_arg = true; break; } - params.diffusion_model_path = argv[i]; + params.ctxParams.diffusion_model_path = argv[i]; } else if (arg == "--vae") { if (++i >= argc) { invalid_arg = true; break; } - params.vae_path = argv[i]; + params.ctxParams.vae_path = argv[i]; } else if (arg == "--taesd") { if (++i >= argc) { invalid_arg = true; break; } - params.taesd_path = argv[i]; + params.ctxParams.taesd_path = argv[i]; } else if (arg == "--control-net") { if (++i >= argc) { invalid_arg = true; break; } - params.controlnet_path = argv[i]; + params.ctxParams.controlnet_path = argv[i]; } else if (arg == "--upscale-model") { if (++i >= argc) { invalid_arg = true; @@ -299,13 +327,13 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.embeddings_path = argv[i]; + params.ctxParams.embeddings_path = argv[i]; } else if (arg == "--stacked-id-embd-dir") { if (++i >= argc) { invalid_arg = true; break; } - params.stacked_id_embeddings_path = argv[i]; + params.ctxParams.stacked_id_embeddings_path = argv[i]; } else if (arg == "--input-id-images-dir") { if (++i >= argc) { invalid_arg = true; @@ -317,30 +345,30 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - std::string type = argv[i]; - if (type == "f32") { - params.wtype = SD_TYPE_F32; - } else if (type == "f16") { - params.wtype = SD_TYPE_F16; - } else if (type == "q4_0") { - params.wtype = SD_TYPE_Q4_0; - } else if (type == "q4_1") { - params.wtype = SD_TYPE_Q4_1; - } else if (type == "q5_0") { - params.wtype = SD_TYPE_Q5_0; - } else if (type == "q5_1") { - params.wtype = SD_TYPE_Q5_1; - } else if (type == "q8_0") { - params.wtype = SD_TYPE_Q8_0; - } else if (type == "q2_k") { - params.wtype = SD_TYPE_Q2_K; - } else if (type == "q3_k") { - params.wtype = SD_TYPE_Q3_K; - } else if (type == "q4_k") { - params.wtype = SD_TYPE_Q4_K; - } else { - fprintf(stderr, "error: invalid weight format %s, must be one of [f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_k, q3_k, q4_k]\n", - type.c_str()); + std::string type = argv[i]; + bool found = false; + std::string valid_types = ""; + for (size_t i = 0; i < SD_TYPE_COUNT; i++) { + auto trait = ggml_get_type_traits((ggml_type)i); + std::string name(trait->type_name); + if (name == "f32" || trait->to_float && trait->type_size) { + if (i) + valid_types += ", "; + valid_types += name; + if (type == name) { + if (ggml_quantize_requires_imatrix((ggml_type)i)) { + printf("\033[35;1m[WARNING]\033[0m: type %s requires imatrix to work properly. A dummy imatrix will be used, expect poor quality.\n", trait->type_name); + } + params.ctxParams.wtype = (enum sd_type_t)i; + found = true; + break; + } + } + } + if (!found) { + fprintf(stderr, "error: invalid weight format %s, must be one of [%s]\n", + type.c_str(), + valid_types.c_str()); exit(1); } } else if (arg == "--lora-model-dir") { @@ -348,7 +376,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.lora_model_dir = argv[i]; + params.ctxParams.lora_model_dir = argv[i]; } else if (arg == "-i" || arg == "--init-img") { if (++i >= argc) { invalid_arg = true; @@ -372,85 +400,85 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.prompt = argv[i]; + params.lastRequest.prompt = argv[i]; } else if (arg == "-n" || arg == "--negative-prompt") { if (++i >= argc) { invalid_arg = true; break; } - params.negative_prompt = argv[i]; + params.lastRequest.negative_prompt = argv[i]; } else if (arg == "--cfg-scale") { if (++i >= argc) { invalid_arg = true; break; } - params.cfg_scale = std::stof(argv[i]); + params.lastRequest.cfg_scale = std::stof(argv[i]); } else if (arg == "--guidance") { if (++i >= argc) { invalid_arg = true; break; } - params.guidance = std::stof(argv[i]); + params.lastRequest.guidance = std::stof(argv[i]); } else if (arg == "--strength") { if (++i >= argc) { invalid_arg = true; break; } - params.strength = std::stof(argv[i]); + params.lastRequest.strength = std::stof(argv[i]); } else if (arg == "--style-ratio") { if (++i >= argc) { invalid_arg = true; break; } - params.style_ratio = std::stof(argv[i]); + params.lastRequest.style_ratio = std::stof(argv[i]); } else if (arg == "--control-strength") { if (++i >= argc) { invalid_arg = true; break; } - params.control_strength = std::stof(argv[i]); + params.lastRequest.control_strength = std::stof(argv[i]); } else if (arg == "-H" || arg == "--height") { if (++i >= argc) { invalid_arg = true; break; } - params.height = std::stoi(argv[i]); + params.lastRequest.height = std::stoi(argv[i]); } else if (arg == "-W" || arg == "--width") { if (++i >= argc) { invalid_arg = true; break; } - params.width = std::stoi(argv[i]); + params.lastRequest.width = std::stoi(argv[i]); } else if (arg == "--steps") { if (++i >= argc) { invalid_arg = true; break; } - params.sample_steps = std::stoi(argv[i]); + params.lastRequest.sample_steps = std::stoi(argv[i]); } else if (arg == "--clip-skip") { if (++i >= argc) { invalid_arg = true; break; } - params.clip_skip = std::stoi(argv[i]); + params.lastRequest.clip_skip = std::stoi(argv[i]); } else if (arg == "--vae-tiling") { - params.vae_tiling = true; + params.ctxParams.vae_tiling = true; } else if (arg == "--control-net-cpu") { - params.control_net_cpu = true; + params.ctxParams.control_net_cpu = true; } else if (arg == "--normalize-input") { - params.normalize_input = true; + params.lastRequest.normalize_input = true; } else if (arg == "--clip-on-cpu") { - params.clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs + params.ctxParams.clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs } else if (arg == "--vae-on-cpu") { - params.vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs + params.ctxParams.vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs } else if (arg == "--diffusion-fa") { - params.diffusion_flash_attn = true; // can reduce MEM significantly + params.ctxParams.diffusion_flash_attn = true; // can reduce MEM significantly } else if (arg == "-b" || arg == "--batch-count") { if (++i >= argc) { invalid_arg = true; break; } - params.batch_count = std::stoi(argv[i]); + params.lastRequest.batch_count = std::stoi(argv[i]); } else if (arg == "--rng") { if (++i >= argc) { invalid_arg = true; @@ -458,9 +486,9 @@ void parse_args(int argc, const char** argv, SDParams& params) { } std::string rng_type_str = argv[i]; if (rng_type_str == "std_default") { - params.rng_type = STD_DEFAULT_RNG; + params.ctxParams.rng_type = STD_DEFAULT_RNG; } else if (rng_type_str == "cuda") { - params.rng_type = CUDA_RNG; + params.ctxParams.rng_type = CUDA_RNG; } else { invalid_arg = true; break; @@ -481,13 +509,13 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.schedule = (schedule_t)schedule_found; + params.ctxParams.schedule = (schedule_t)schedule_found; } else if (arg == "-s" || arg == "--seed") { if (++i >= argc) { invalid_arg = true; break; } - params.seed = std::stoll(argv[i]); + params.lastRequest.seed = std::stoll(argv[i]); } else if (arg == "--sampling-method") { if (++i >= argc) { invalid_arg = true; @@ -504,7 +532,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.sample_method = (sample_method_t)sample_method_found; + params.lastRequest.sample_method = (sample_method_t)sample_method_found; } else if (arg == "-h" || arg == "--help") { print_usage(argc, argv); exit(0); @@ -517,7 +545,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.slg_scale = std::stof(argv[i]); + params.lastRequest.slg_scale = std::stof(argv[i]); } else if (arg == "--skip-layers") { if (++i >= argc) { invalid_arg = true; @@ -550,7 +578,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } } - params.skip_layers = layers; + params.lastRequest.skip_layers = layers; if (invalid_arg) { break; @@ -560,13 +588,13 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.skip_layer_start = std::stof(argv[i]); + params.lastRequest.skip_layer_start = std::stof(argv[i]); } else if (arg == "--skip-layer-end") { if (++i >= argc) { invalid_arg = true; break; } - params.skip_layer_end = std::stof(argv[i]); + params.lastRequest.skip_layer_end = std::stof(argv[i]); } else if (arg == "--port") { if (++i >= argc) { invalid_arg = true; @@ -590,63 +618,8 @@ void parse_args(int argc, const char** argv, SDParams& params) { print_usage(argc, argv); exit(1); } - if (params.n_threads <= 0) { - params.n_threads = get_num_physical_cores(); - } - - if (params.mode != CONVERT && params.mode != IMG2VID && params.prompt.length() == 0) { - fprintf(stderr, "error: the following arguments are required: prompt\n"); - print_usage(argc, argv); - exit(1); - } - - if (params.model_path.length() == 0 && params.diffusion_model_path.length() == 0) { - fprintf(stderr, "error: the following arguments are required: model_path/diffusion_model\n"); - print_usage(argc, argv); - exit(1); - } - - if ((params.mode == IMG2IMG || params.mode == IMG2VID) && params.input_path.length() == 0) { - fprintf(stderr, "error: when using the img2img mode, the following arguments are required: init-img\n"); - print_usage(argc, argv); - exit(1); - } - - if (params.output_path.length() == 0) { - fprintf(stderr, "error: the following arguments are required: output_path\n"); - print_usage(argc, argv); - exit(1); - } - - if (params.width <= 0 || params.width % 64 != 0) { - fprintf(stderr, "error: the width must be a multiple of 64\n"); - exit(1); - } - - if (params.height <= 0 || params.height % 64 != 0) { - fprintf(stderr, "error: the height must be a multiple of 64\n"); - exit(1); - } - - if (params.sample_steps <= 0) { - fprintf(stderr, "error: the sample_steps must be greater than 0\n"); - exit(1); - } - - if (params.strength < 0.f || params.strength > 1.f) { - fprintf(stderr, "error: can only work with strength in [0.0, 1.0]\n"); - exit(1); - } - - if (params.seed < 0) { - srand((int)time(NULL)); - params.seed = rand(); - } - - if (params.mode == CONVERT) { - if (params.output_path == "output.png") { - params.output_path = "output.gguf"; - } + if (params.ctxParams.n_threads <= 0) { + params.ctxParams.n_threads = get_num_physical_cores(); } } @@ -663,19 +636,19 @@ static std::string sd_basename(const std::string& path) { } std::string get_image_params(SDParams params, int64_t seed) { - std::string parameter_string = params.prompt + "\n"; - if (params.negative_prompt.size() != 0) { - parameter_string += "Negative prompt: " + params.negative_prompt + "\n"; + std::string parameter_string = params.lastRequest.prompt + "\n"; + if (params.lastRequest.negative_prompt.size() != 0) { + parameter_string += "Negative prompt: " + params.lastRequest.negative_prompt + "\n"; } - parameter_string += "Steps: " + std::to_string(params.sample_steps) + ", "; - parameter_string += "CFG scale: " + std::to_string(params.cfg_scale) + ", "; - parameter_string += "Guidance: " + std::to_string(params.guidance) + ", "; + parameter_string += "Steps: " + std::to_string(params.lastRequest.sample_steps) + ", "; + parameter_string += "CFG scale: " + std::to_string(params.lastRequest.cfg_scale) + ", "; + parameter_string += "Guidance: " + std::to_string(params.lastRequest.guidance) + ", "; parameter_string += "Seed: " + std::to_string(seed) + ", "; - parameter_string += "Size: " + std::to_string(params.width) + "x" + std::to_string(params.height) + ", "; - parameter_string += "Model: " + sd_basename(params.model_path) + ", "; - parameter_string += "RNG: " + std::string(rng_type_to_str[params.rng_type]) + ", "; - parameter_string += "Sampler: " + std::string(sample_method_str[params.sample_method]); - if (params.schedule == KARRAS) { + parameter_string += "Size: " + std::to_string(params.lastRequest.width) + "x" + std::to_string(params.lastRequest.height) + ", "; + parameter_string += "Model: " + sd_basename(params.ctxParams.model_path) + ", "; + parameter_string += "RNG: " + std::string(rng_type_to_str[params.ctxParams.rng_type]) + ", "; + parameter_string += "Sampler: " + std::string(sample_method_str[params.lastRequest.sample_method]); + if (params.ctxParams.schedule == KARRAS) { parameter_string += " karras"; } parameter_string += ", "; @@ -753,38 +726,38 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { // now we try to get the new param values from the payload object // const char *prompt, const char *negative_prompt, int clip_skip, float cfg_scale, float guidance, int width, int height, sample_method_t sample_method, int sample_steps, int64_t seed, int batch_count, const sd_image_t *control_cond, float control_strength, float style_strength, bool normalize_input, const char *input_id_images_path try { - std::string prompt = payload["prompt"]; - params->prompt = prompt; + std::string prompt = payload["prompt"]; + params->lastRequest.prompt = prompt; } catch (...) { } try { - std::string negative_prompt = payload["negative_prompt"]; - params->negative_prompt = negative_prompt; + std::string negative_prompt = payload["negative_prompt"]; + params->lastRequest.negative_prompt = negative_prompt; } catch (...) { } try { - int clip_skip = payload["clip_skip"]; - params->clip_skip = clip_skip; + int clip_skip = payload["clip_skip"]; + params->lastRequest.clip_skip = clip_skip; } catch (...) { } try { - float cfg_scale = payload["cfg_scale"]; - params->cfg_scale = cfg_scale; + float cfg_scale = payload["cfg_scale"]; + params->lastRequest.cfg_scale = cfg_scale; } catch (...) { } try { - float guidance = payload["guidance"]; - params->guidance = guidance; + float guidance = payload["guidance"]; + params->lastRequest.guidance = guidance; } catch (...) { } try { - int width = payload["width"]; - params->width = width; + int width = payload["width"]; + params->lastRequest.width = width; } catch (...) { } try { - int height = payload["height"]; - params->height = height; + int height = payload["height"]; + params->lastRequest.height = height; } catch (...) { } try { @@ -797,25 +770,25 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { } } if (sample_method_found >= 0) { - params->sample_method = (sample_method_t)sample_method_found; + params->lastRequest.sample_method = (sample_method_t)sample_method_found; } else { sd_log(sd_log_level_t::SD_LOG_WARN, "Unknown sampling method: %s\n", sample_method.c_str()); } } catch (...) { } try { - int sample_steps = payload["sample_steps"]; - params->sample_steps = sample_steps; + int sample_steps = payload["sample_steps"]; + params->lastRequest.sample_steps = sample_steps; } catch (...) { } try { - int64_t seed = payload["seed"]; - params->seed = seed; + int64_t seed = payload["seed"]; + params->lastRequest.seed = seed; } catch (...) { } try { - int batch_count = payload["batch_count"]; - params->batch_count = batch_count; + int batch_count = payload["batch_count"]; + params->lastRequest.batch_count = batch_count; } catch (...) { } @@ -842,8 +815,8 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { } catch (...) { } try { - bool normalize_input = payload["normalize_input"]; - params->normalize_input = normalize_input; + bool normalize_input = payload["normalize_input"]; + params->lastRequest.normalize_input = normalize_input; } catch (...) { } try { @@ -854,11 +827,42 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { } } -int main(int argc, const char* argv[]) { - SDParams params; +//--------------------------------------// +// Thread-safe queue +std::queue> task_queue; +std::mutex queue_mutex; +std::condition_variable queue_cond; +bool stop_worker = false; +std::atomic is_task_running(false); + +std::unordered_map task_results; +std::mutex results_mutex; + +void worker_thread() { + while (!stop_worker) { + std::unique_lock lock(queue_mutex); + queue_cond.wait(lock, [] { return !task_queue.empty() || stop_worker; }); + + if (!task_queue.empty()) { + is_task_running = true; + auto task = task_queue.front(); + task_queue.pop(); + lock.unlock(); + task(); + is_task_running = false; + } + } +} - parse_args(argc, argv, params); +void add_task(std::string task_id, std::function task) { + std::lock_guard lock(queue_mutex); + task_queue.push([task_id, task]() { + task(); + }); + queue_cond.notify_one(); +} +void start_server(SDParams params) { sd_set_log_callback(sd_log_cb, (void*)¶ms); server_log_params = (void*)¶ms; @@ -868,132 +872,36 @@ int main(int argc, const char* argv[]) { printf("%s", sd_get_system_info()); } - bool vae_decode_only = true; - - sd_ctx_t* sd_ctx = new_sd_ctx(params.model_path.c_str(), - params.clip_l_path.c_str(), - params.clip_g_path.c_str(), - params.t5xxl_path.c_str(), - params.diffusion_model_path.c_str(), - params.vae_path.c_str(), - params.taesd_path.c_str(), - params.controlnet_path.c_str(), - params.lora_model_dir.c_str(), - params.embeddings_path.c_str(), - params.stacked_id_embeddings_path.c_str(), - vae_decode_only, - params.vae_tiling, + sd_ctx_t* sd_ctx = new_sd_ctx(params.ctxParams.model_path.c_str(), + params.ctxParams.clip_l_path.c_str(), + params.ctxParams.clip_g_path.c_str(), + params.ctxParams.t5xxl_path.c_str(), + params.ctxParams.diffusion_model_path.c_str(), + params.ctxParams.vae_path.c_str(), + params.ctxParams.taesd_path.c_str(), + params.ctxParams.controlnet_path.c_str(), + params.ctxParams.lora_model_dir.c_str(), + params.ctxParams.embeddings_path.c_str(), + params.ctxParams.stacked_id_embeddings_path.c_str(), + params.ctxParams.vae_decode_only, + params.ctxParams.vae_tiling, false, - params.n_threads, - params.wtype, - params.rng_type, - params.schedule, - params.clip_on_cpu, - params.control_net_cpu, - params.vae_on_cpu, - params.diffusion_flash_attn); + params.ctxParams.n_threads, + params.ctxParams.wtype, + params.ctxParams.rng_type, + params.ctxParams.schedule, + params.ctxParams.clip_on_cpu, + params.ctxParams.control_net_cpu, + params.ctxParams.vae_on_cpu, + params.ctxParams.diffusion_flash_attn); if (sd_ctx == NULL) { printf("new_sd_ctx_t failed\n"); - return 1; + return; } int n_prompts = 0; - const auto txt2imgRequest = [&sd_ctx, ¶ms, &n_prompts](const httplib::Request& req, httplib::Response& res) { - // LOG_DEBUG("raw body is: %s\n", req.body.c_str()); - sd_log(sd_log_level_t::SD_LOG_DEBUG, "raw body is: %s\n", req.body.c_str()); - // parse req.body as json using jsoncpp - using json = nlohmann::json; - - try { - std::string json_str = req.body; - parseJsonPrompt(json_str, ¶ms); - } catch (json::parse_error& e) { - // assume the request is just a prompt - // LOG_WARN("Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); - sd_log(sd_log_level_t::SD_LOG_WARN, "Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); - std::string prompt = req.body; - if (!prompt.empty()) { - params.prompt = prompt; - } else { - params.seed += 1; - } - } catch (...) { - // Handle any other type of exception - // LOG_ERROR("An unexpected error occurred\n"); - sd_log(sd_log_level_t::SD_LOG_ERROR, "An unexpected error occurred\n"); - } - // LOG_DEBUG("prompt is: %s\n", params.prompt.c_str()); - sd_log(sd_log_level_t::SD_LOG_DEBUG, "prompt is: %s\n", params.prompt.c_str()); - - { - sd_image_t* results; - results = txt2img(sd_ctx, - params.prompt.c_str(), - params.negative_prompt.c_str(), - params.clip_skip, - params.cfg_scale, - params.guidance, - params.width, - params.height, - params.sample_method, - params.sample_steps, - params.seed, - params.batch_count, - NULL, - 1, - params.style_ratio, - params.normalize_input, - params.input_id_images_path.c_str(), - params.skip_layers.data(), - params.skip_layers.size(), - params.slg_scale, - params.skip_layer_start, - params.skip_layer_end); - - if (results == NULL) { - printf("generate failed\n"); - free_sd_ctx(sd_ctx); - return 1; - } - - size_t last = params.output_path.find_last_of("."); - std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path; - json images_json = json::array(); - for (int i = 0; i < params.batch_count; i++) { - if (results[i].data == NULL) { - continue; - } - // TODO allow disable save to disk - std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1 + n_prompts * params.batch_count) + ".png" : dummy_name + ".png"; - stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, - results[i].data, 0, get_image_params(params, params.seed + i).c_str()); - printf("save result image to '%s'\n", final_image_path.c_str()); - // Todo: return base64 encoded image via httplib::Response& res - - int len; - unsigned char* png = stbi_write_png_to_mem((const unsigned char*)results[i].data, 0, results[i].width, results[i].height, results[i].channel, &len, get_image_params(params, params.seed + i).c_str()); - - std::string data_str(png, png + len); - std::string encoded_img = base64_encode(data_str); - - images_json.push_back({{"width", results[i].width}, - {"height", results[i].height}, - {"channel", results[i].channel}, - {"data", encoded_img}, - {"encoding", "png"}}); - - free(results[i].data); - results[i].data = NULL; - } - free(results); - n_prompts++; - res.set_content(images_json.dump(), "application/json"); - } - return 0; - }; - std::unique_ptr svr; svr.reset(new httplib::Server()); svr->set_default_headers({{"Server", "sd.cpp"}}); @@ -1005,14 +913,167 @@ int main(int argc, const char* argv[]) { res.set_header("Access-Control-Allow-Headers", "*"); return res.set_content("", "text/html"); // blank response, no data }); - svr->set_logger(log_server_request); + if (params.verbose) { + svr->set_logger(log_server_request); + } + + svr->Post("/txt2img", [&sd_ctx, ¶ms, &n_prompts](const httplib::Request& req, httplib::Response& res) { + using json = nlohmann::json; + std::string task_id = std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); + + json pending_task_json = json::object(); + pending_task_json["status"] = "Pending"; + pending_task_json["data"] = json::array(); + pending_task_json["step"] = -1; + pending_task_json["eta"] = "?"; + + std::lock_guard results_lock(results_mutex); + task_results[task_id] = pending_task_json; + + auto task = [&req, &sd_ctx, ¶ms, &n_prompts, task_id]() { + // LOG_DEBUG("raw body is: %s\n", req.body.c_str()); + sd_log(sd_log_level_t::SD_LOG_DEBUG, "raw body is: %s\n", req.body.c_str()); + // parse req.body as json using jsoncpp + try { + std::string json_str = req.body; + parseJsonPrompt(json_str, ¶ms); + } catch (json::parse_error& e) { + // assume the request is just a prompt + // LOG_WARN("Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); + sd_log(sd_log_level_t::SD_LOG_WARN, "Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); + std::string prompt = req.body; + if (!prompt.empty()) { + params.lastRequest.prompt = prompt; + } else { + params.lastRequest.seed += 1; + } + } catch (...) { + // Handle any other type of exception + // LOG_ERROR("An unexpected error occurred\n"); + sd_log(sd_log_level_t::SD_LOG_ERROR, "An unexpected error occurred\n"); + } + // LOG_DEBUG("prompt is: %s\n", params.prompt.c_str()); + sd_log(sd_log_level_t::SD_LOG_DEBUG, "prompt is: %s\n", params.lastRequest.prompt.c_str()); + + { + json started_task_json = json::object(); + started_task_json["status"] = "Working"; + started_task_json["data"] = json::array(); + started_task_json["step"] = 0; + started_task_json["eta"] = "?"; + + std::lock_guard results_lock(results_mutex); + task_results[task_id] = started_task_json; + } + + { + sd_image_t* results; + results = txt2img(sd_ctx, + params.lastRequest.prompt.c_str(), + params.lastRequest.negative_prompt.c_str(), + params.lastRequest.clip_skip, + params.lastRequest.cfg_scale, + params.lastRequest.guidance, + params.lastRequest.width, + params.lastRequest.height, + params.lastRequest.sample_method, + params.lastRequest.sample_steps, + params.lastRequest.seed, + params.lastRequest.batch_count, + NULL, + 1, + params.lastRequest.style_ratio, + params.lastRequest.normalize_input, + params.input_id_images_path.c_str(), + params.lastRequest.skip_layers.data(), + params.lastRequest.skip_layers.size(), + params.lastRequest.slg_scale, + params.lastRequest.skip_layer_start, + params.lastRequest.skip_layer_end); + + if (results == NULL) { + printf("generate failed\n"); + free_sd_ctx(sd_ctx); + std::lock_guard results_lock(results_mutex); + task_results[task_id]["status"] = "Failed"; + return; + } - svr->Post("/txt2img", txt2imgRequest); + size_t last = params.output_path.find_last_of("."); + std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path; + json images_json = json::array(); + for (int i = 0; i < params.lastRequest.batch_count; i++) { + if (results[i].data == NULL) { + continue; + } + // TODO allow disable save to disk + std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1 + n_prompts * params.lastRequest.batch_count) + ".png" : dummy_name + ".png"; + stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel, + results[i].data, 0, get_image_params(params, params.lastRequest.seed + i).c_str()); + printf("save result image to '%s'\n", final_image_path.c_str()); + // Todo: return base64 encoded image via httplib::Response& res + + int len; + unsigned char* png = stbi_write_png_to_mem((const unsigned char*)results[i].data, 0, results[i].width, results[i].height, results[i].channel, &len, get_image_params(params, params.lastRequest.seed + i).c_str()); + + std::string data_str(png, png + len); + std::string encoded_img = base64_encode(data_str); + + images_json.push_back({{"width", results[i].width}, + {"height", results[i].height}, + {"channel", results[i].channel}, + {"data", encoded_img}, + {"encoding", "png"}}); + + free(results[i].data); + results[i].data = NULL; + } + free(results); + n_prompts++; + // res.set_content(images_json.dump(), "application/json"); + json end_task_json = json::object(); + end_task_json["status"] = "Done"; + end_task_json["data"] = images_json; + end_task_json["step"] = 0; + end_task_json["eta"] = "?"; + std::lock_guard results_lock(results_mutex); + task_results[task_id] = end_task_json; + return; + } + + std::lock_guard results_lock(results_mutex); + task_results[task_id]["status"] = "Failed"; + return; + }; + // Add the task to the queue + add_task(task_id, task); + + json response = json::object(); + response["task_id"] = task_id; + res.set_content(response.dump(), "application/json"); + }); + + svr->Post("/result", [](const httplib::Request& req, httplib::Response& res) { + using json = nlohmann::json; + // Parse task ID from query parameters + try { + std::string task_id = json::parse(req.body)["task_id"]; + std::lock_guard lock(results_mutex); + if (task_results.find(task_id) != task_results.end()) { + json result = task_results[task_id]; + res.set_content(result.dump(), "application/json"); + } else { + res.set_content("Cannot find task " + task_id + " in queue", "text/plain"); + } + } catch (...) { + sd_log(sd_log_level_t::SD_LOG_WARN, "Invalid request body: %s\n", req.body.c_str()); + } + }); // bind HTTP listen port, run the HTTP server in a thread if (!svr->bind_to_port(params.host, params.port)) { // TODO: Error message - return 1; + return; } std::thread t([&]() { svr->listen_after_bind(); }); svr->wait_until_ready(); @@ -1022,6 +1083,21 @@ int main(int argc, const char* argv[]) { t.join(); free_sd_ctx(sd_ctx); +} + +int main(int argc, const char* argv[]) { + SDParams params; + // Setup default args + parse_args(argc, argv, params); + + std::thread worker(worker_thread); + // Start the HTTP server + start_server(params); + + // Cleanup + stop_worker = true; + queue_cond.notify_one(); + worker.join(); return 0; } \ No newline at end of file diff --git a/examples/server/test_client.py b/examples/server/test_client.py index 69a00dc7a..deda02ca1 100644 --- a/examples/server/test_client.py +++ b/examples/server/test_client.py @@ -5,6 +5,8 @@ if os.name == 'nt': from threading import Thread +import time + from typing import List @@ -83,6 +85,18 @@ def update_url(protocol=None, server=None, port=None, endpoint=None) -> str: # set default url value update_url() +def poll_result(id: str): + global _protocol + global _server + global _port + + res = {'status':""} + while res['status'] != "Done": + time.sleep(0.1) + res = requests.post(f"{_protocol}://{_server}:{_port}/result", json.dumps({'task_id':id})).json() + + return json.dumps(res['data']) + def sendRequest(payload: str) -> str: """ Send a POST request to the API endpoint with the provided payload. @@ -97,7 +111,7 @@ def sendRequest(payload: str) -> str: str: The text content of the response from the POST request. """ global url - return requests.post(url, payload).text + return requests.post(url, payload).json()['task_id'] def getImages(response: str) -> List[Image.Image]: """ @@ -163,7 +177,7 @@ def saveImages(imgs: List[Image.Image], path: str) -> None: def _print_usage(): print("""Example usage (images will be displayed and saved to a temporary file): update_url(server="127.0.0.1", port=8080) -showImages(getImages(sendRequest(json.dumps({'seed': -1, 'batch_count':4, 'sample_steps':24, 'width': 512, 'height':768, 'negative_prompt': "Bad quality", 'prompt': "A beautiful image"}))))""") +showImages(getImages(poll_result(sendRequest(json.dumps({'seed': -1, 'batch_count':4, 'sample_steps':24, 'width': 512, 'height':768, 'negative_prompt': "Bad quality", 'prompt': "A beautiful image"})))))""") if __name__ == "__main__": _print_usage() From f9b2a6dc812b667750f076a483d574ffe9f7c96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Thu, 2 Jan 2025 21:04:23 +0100 Subject: [PATCH 105/143] server: update samplers and schedulers --- examples/server/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 1dc98ed6d..31419106c 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -47,6 +47,8 @@ const char* sample_method_str[] = { "dpm++2s_a", "dpm++2m", "dpm++2mv2", + "ipndm", + "ipndm_v", "lcm", }; @@ -55,7 +57,9 @@ const char* schedule_str[] = { "default", "discrete", "karras", + "exponential", "ays", + "gits", }; enum SDMode { From 578ae14a17fc8e68a1a95eff12d7cee98179ae98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Thu, 2 Jan 2025 21:54:53 +0100 Subject: [PATCH 106/143] global running task_id --- examples/server/main.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 31419106c..91ef31d83 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -837,7 +837,8 @@ std::queue> task_queue; std::mutex queue_mutex; std::condition_variable queue_cond; bool stop_worker = false; -std::atomic is_task_running(false); +std::atomic is_busy(false); +std::string running_task_id(""); std::unordered_map task_results; std::mutex results_mutex; @@ -848,12 +849,12 @@ void worker_thread() { queue_cond.wait(lock, [] { return !task_queue.empty() || stop_worker; }); if (!task_queue.empty()) { - is_task_running = true; + is_busy = true; auto task = task_queue.front(); task_queue.pop(); lock.unlock(); task(); - is_task_running = false; + is_busy = false; } } } @@ -935,6 +936,7 @@ void start_server(SDParams params) { task_results[task_id] = pending_task_json; auto task = [&req, &sd_ctx, ¶ms, &n_prompts, task_id]() { + running_task_id = task_id; // LOG_DEBUG("raw body is: %s\n", req.body.c_str()); sd_log(sd_log_level_t::SD_LOG_DEBUG, "raw body is: %s\n", req.body.c_str()); // parse req.body as json using jsoncpp From c9e46e27cafa5fc60da848ffe2e3103bd51867cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 3 Jan 2025 00:29:14 +0100 Subject: [PATCH 107/143] basic webui --- examples/server/main.cpp | 331 +++++++++++++++++++++++++++++++-- examples/server/test_client.py | 7 +- 2 files changed, 319 insertions(+), 19 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 91ef31d83..815337d7f 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -854,7 +854,8 @@ void worker_thread() { task_queue.pop(); lock.unlock(); task(); - is_busy = false; + is_busy = false; + running_task_id = ""; } } } @@ -926,14 +927,16 @@ void start_server(SDParams params) { using json = nlohmann::json; std::string task_id = std::to_string(std::chrono::system_clock::now().time_since_epoch().count()); - json pending_task_json = json::object(); - pending_task_json["status"] = "Pending"; - pending_task_json["data"] = json::array(); - pending_task_json["step"] = -1; - pending_task_json["eta"] = "?"; + { + json pending_task_json = json::object(); + pending_task_json["status"] = "Pending"; + pending_task_json["data"] = json::array(); + pending_task_json["step"] = -1; + pending_task_json["eta"] = "?"; - std::lock_guard results_lock(results_mutex); - task_results[task_id] = pending_task_json; + std::lock_guard results_lock(results_mutex); + task_results[task_id] = pending_task_json; + } auto task = [&req, &sd_ctx, ¶ms, &n_prompts, task_id]() { running_task_id = task_id; @@ -1044,12 +1047,7 @@ void start_server(SDParams params) { end_task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); task_results[task_id] = end_task_json; - return; } - - std::lock_guard results_lock(results_mutex); - task_results[task_id]["status"] = "Failed"; - return; }; // Add the task to the queue add_task(task_id, task); @@ -1059,20 +1057,321 @@ void start_server(SDParams params) { res.set_content(response.dump(), "application/json"); }); - svr->Post("/result", [](const httplib::Request& req, httplib::Response& res) { + svr->Get("/params", [¶ms](const httplib::Request& req, httplib::Response& res) { + using json = nlohmann::json; + json response; + json params_json = json::object(); + params_json["prompt"] = params.lastRequest.prompt; + params_json["negative_prompt"] = params.lastRequest.negative_prompt; + params_json["clip_skip"] = params.lastRequest.clip_skip; + params_json["cfg_scale"] = params.lastRequest.cfg_scale; + params_json["guidance"] = params.lastRequest.guidance; + params_json["width"] = params.lastRequest.width; + params_json["height"] = params.lastRequest.height; + params_json["sample_method"] = sample_method_str[params.lastRequest.sample_method]; + params_json["sample_steps"] = params.lastRequest.sample_steps; + params_json["seed"] = params.lastRequest.seed; + params_json["batch_count"] = params.lastRequest.batch_count; + params_json["normalize_input"] = params.lastRequest.normalize_input; + // params_json["input_id_images_path"] = params.input_id_images_path; + response["generation_params"] = params_json; + + json context_params = json::object(); + // Do not expose paths + // context_params["model_path"] = params.ctxParams.model_path; + // context_params["clip_l_path"] = params.ctxParams.clip_l_path; + // context_params["clip_g_path"] = params.ctxParams.clip_g_path; + // context_params["t5xxl_path"] = params.ctxParams.t5xxl_path; + // context_params["diffusion_model_path"] = params.ctxParams.diffusion_model_path; + // context_params["vae_path"] = params.ctxParams.vae_path; + // context_params["controlnet_path"] = params.ctxParams.controlnet_path; + context_params["lora_model_dir"] = params.ctxParams.lora_model_dir; + // context_params["embeddings_path"] = params.ctxParams.embeddings_path; + // context_params["stacked_id_embeddings_path"] = params.ctxParams.stacked_id_embeddings_path; + context_params["vae_decode_only"] = params.ctxParams.vae_decode_only; + context_params["vae_tiling"] = params.ctxParams.vae_tiling; + context_params["n_threads"] = params.ctxParams.n_threads; + context_params["wtype"] = params.ctxParams.wtype; + context_params["rng_type"] = params.ctxParams.rng_type; + context_params["schedule"] = params.ctxParams.schedule; + context_params["clip_on_cpu"] = params.ctxParams.clip_on_cpu; + context_params["control_net_cpu"] = params.ctxParams.control_net_cpu; + context_params["vae_on_cpu"] = params.ctxParams.vae_on_cpu; + context_params["diffusion_flash_attn"] = params.ctxParams.diffusion_flash_attn; + response["context_params"] = context_params; + + res.set_content(response.dump(), "application/json"); + }); + + + svr->Get("/result", [](const httplib::Request& req, httplib::Response& res) { using json = nlohmann::json; // Parse task ID from query parameters try { - std::string task_id = json::parse(req.body)["task_id"]; + std::string task_id = req.get_param_value("task_id"); std::lock_guard lock(results_mutex); if (task_results.find(task_id) != task_results.end()) { json result = task_results[task_id]; res.set_content(result.dump(), "application/json"); + // Erase data after sending + result["data"] = json::array(); + task_results[task_id] = result; } else { res.set_content("Cannot find task " + task_id + " in queue", "text/plain"); } } catch (...) { - sd_log(sd_log_level_t::SD_LOG_WARN, "Invalid request body: %s\n", req.body.c_str()); + sd_log(sd_log_level_t::SD_LOG_WARN, "Error when fetching result"); + } + }); + + svr->Get("/sample_methods", [](const httplib::Request& req, httplib::Response& res) { + using json = nlohmann::json; + json response; + for (int m = 0; m < N_SAMPLE_METHODS; m++) { + response.push_back(sample_method_str[m]); + } + res.set_content(response.dump(), "application/json"); + }); + + svr->Get("/schedules", [](const httplib::Request& req, httplib::Response& res) { + using json = nlohmann::json; + json response; + for (int s = 0; s < N_SCHEDULES; s++) { + response.push_back(schedule_str[s]); + } + res.set_content(response.dump(), "application/json"); + }); + + + svr->Get("/index.html", [](const httplib::Request& req, httplib::Response& res) { + try { + std::string html_content = R"xxx( + + + + + + SDCPP Server + + + +

    + + + + )xxx"; + res.set_content(html_content, "text/html"); + } catch (const std::exception& e) { + res.set_content("Error loading page", "text/plain"); } }); diff --git a/examples/server/test_client.py b/examples/server/test_client.py index deda02ca1..1a81e7f54 100644 --- a/examples/server/test_client.py +++ b/examples/server/test_client.py @@ -85,15 +85,16 @@ def update_url(protocol=None, server=None, port=None, endpoint=None) -> str: # set default url value update_url() -def poll_result(id: str): +def poll_result(id: str, show_previews = False): global _protocol global _server global _port res = {'status':""} while res['status'] != "Done": - time.sleep(0.1) - res = requests.post(f"{_protocol}://{_server}:{_port}/result", json.dumps({'task_id':id})).json() + res = requests.get(f"{_protocol}://{_server}:{_port}/result", params={'task_id':id}, timeout=.25).json() + if(show_previews and res['status'] == "Working" and len(res['data'])>0): + showImages(getImages(json.dumps(res['data']))) return json.dumps(res['data']) From 8d725e7ec0eb33f5b2fc1c66de034c1e63e69f1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 3 Jan 2025 20:16:13 +0100 Subject: [PATCH 108/143] Make server ui better --- examples/server/CMakeLists.txt | 2 +- examples/server/frontend.cpp | 631 +++++++++++++++++++++++++++++++++ examples/server/main.cpp | 563 +++++++++++++++-------------- 3 files changed, 927 insertions(+), 269 deletions(-) create mode 100644 examples/server/frontend.cpp diff --git a/examples/server/CMakeLists.txt b/examples/server/CMakeLists.txt index 5e5768227..8cbd979d5 100644 --- a/examples/server/CMakeLists.txt +++ b/examples/server/CMakeLists.txt @@ -3,4 +3,4 @@ set(TARGET sd-server) add_executable(${TARGET} main.cpp) install(TARGETS ${TARGET} RUNTIME) target_link_libraries(${TARGET} PRIVATE stable-diffusion ${CMAKE_THREAD_LIBS_INIT}) -target_compile_features(${TARGET} PUBLIC cxx_std_11) \ No newline at end of file +target_compile_features(${TARGET} PUBLIC cxx_std_17) \ No newline at end of file diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp new file mode 100644 index 000000000..c7e1151f8 --- /dev/null +++ b/examples/server/frontend.cpp @@ -0,0 +1,631 @@ +const std::string html_content = R"xxx( + + + + + + + SDCPP Server + + +)xxx" R"xxx( + + +
    +
    +

    SDCPP Server

    +

    Model:

    +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + +
    +

    Note: Changing these parameters may cause a longer wait time due to the models + reloading. Please use these parameters carefully.

    +
    + + +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +

    Load new model

    +
    + + +
    +
    + + +
    +
    + +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    +
    + )xxx" R"xxx( + + + + +)xxx"; \ No newline at end of file diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 815337d7f..f1563b474 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,8 @@ #include #include +#include "frontend.cpp" + const char* rng_type_to_str[] = { "std_default", "cuda", @@ -139,6 +142,12 @@ struct SDParams { std::string input_path = "./server/input.png"; std::string control_image_path = "./server/control.png"; + std::string models_dir; + std::string diffusion_models_dir; + std::string clip_dir; + std::string vae_dir; + std::string tae_dir; + // external dir std::string input_id_images_path; @@ -611,6 +620,36 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } params.host = argv[i]; + } else if (arg == "--models-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.models_dir = argv[i]; + } else if (arg == "--diffusion-models-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.diffusion_models_dir = argv[i]; + } else if (arg == "--encoders-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.clip_dir = argv[i]; + } else if (arg == "--vae-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.vae_dir = argv[i]; + } else if (arg == "--tae-dir") { + if (++i >= argc) { + invalid_arg = true; + break; + } + params.tae_dir = argv[i]; } else { fprintf(stderr, "error: unknown argument: %s\n", arg.c_str()); print_usage(argc, argv); @@ -723,7 +762,8 @@ static void log_server_request(const httplib::Request& req, const httplib::Respo printf("request: %s %s (%s)\n", req.method.c_str(), req.path.c_str(), req.body.c_str()); } -void parseJsonPrompt(std::string json_str, SDParams* params) { +bool parseJsonPrompt(std::string json_str, SDParams* params) { + bool updatectx = false; using namespace nlohmann; json payload = json::parse(json_str); // if no exception, the request is a json object @@ -829,6 +869,126 @@ void parseJsonPrompt(std::string json_str, SDParams* params) { params->input_id_images_path = input_id_images_path; } catch (...) { } + + try { + bool vae_cpu = payload["vae_on_cpu"]; + if (params->ctxParams.vae_on_cpu != vae_cpu) { + params->ctxParams.vae_on_cpu = vae_cpu; + updatectx = true; + } + } catch (...) { + } + try { + bool clip_cpu = payload["clip_on_cpu"]; + if (params->ctxParams.clip_on_cpu != clip_cpu) { + params->ctxParams.clip_on_cpu = clip_cpu; + updatectx = true; + } + } catch (...) { + } + try { + bool vae_tiling = payload["vae_tiling"]; + printf("VAE_tiling : %s", vae_tiling ? "true" : "false"); + if (params->ctxParams.vae_tiling != vae_tiling) { + params->ctxParams.vae_tiling = vae_tiling; + updatectx = true; + } + } catch (...) { + } + try { + std::string model = payload["model"]; + if (params->ctxParams.model_path != params->models_dir + model) { + params->ctxParams.model_path = params->models_dir + model; + params->ctxParams.diffusion_model_path = ""; + updatectx = true; + } + } catch (...) { + } + try { + std::string diffusion_model = payload["diffusion_model"]; + if (params->ctxParams.diffusion_model_path != params->diffusion_models_dir + diffusion_model) { + params->ctxParams.diffusion_model_path = params->diffusion_models_dir + diffusion_model; + params->ctxParams.model_path = ""; + updatectx = true; + } + } catch (...) { + } + try { + std::string clip_l = payload["clip_l"]; + if (params->ctxParams.clip_l_path != params->clip_dir + clip_l) { + params->ctxParams.clip_l_path = params->clip_dir + clip_l; + updatectx = true; + } + } catch (...) { + } + try { + std::string clip_g = payload["clip_g"]; + if (params->ctxParams.clip_g_path != params->clip_dir + clip_g) { + params->ctxParams.clip_g_path = params->clip_dir + clip_g; + updatectx = true; + } + } catch (...) { + } + try { + std::string t5xxl = payload["t5xxl"]; + if (params->ctxParams.t5xxl_path != params->clip_dir + t5xxl) { + params->ctxParams.t5xxl_path = params->clip_dir + t5xxl; + updatectx = true; + } + } catch (...) { + } + try { + std::string vae = payload["vae"]; + if (params->ctxParams.vae_path != params->vae_dir + vae) { + params->ctxParams.vae_path = params->vae_dir + vae; + updatectx = true; + } + } catch (...) { + } + try { + std::string tae = payload["tae"]; + if (params->ctxParams.taesd_path != params->tae_dir + tae) { + params->ctxParams.taesd_path = params->tae_dir + tae; + updatectx = true; + } + } catch (...) { + } + + try { + std::string schedule = payload["schedule"]; + int schedule_found = -1; + for (int m = 0; m < N_SAMPLE_METHODS; m++) { + if (!strcmp(schedule.c_str(), schedule_str[m])) { + schedule_found = m; + } + } + if (schedule_found >= 0) { + if (params->ctxParams.schedule != (schedule_t)schedule_found) { + params->ctxParams.schedule = (schedule_t)schedule_found; + updatectx = true; + } + } else { + sd_log(sd_log_level_t::SD_LOG_WARN, "Unknown schedule: %s\n", schedule.c_str()); + } + } catch (...) { + } + + return updatectx; +} + +std::vector list_files(const std::string& dir_path) { + namespace fs = std::filesystem; + std::vector files; + if (dir_path != "") + for (const auto& entry : fs::recursive_directory_iterator(dir_path)) { + if (entry.is_regular_file()) { + auto relative_path = fs::relative(entry.path(), dir_path); + std::string path_str = relative_path.string(); + std::replace(path_str.begin(), path_str.end(), '\\', '/'); + files.push_back(path_str); + } + } + return files; } //--------------------------------------// @@ -878,33 +1038,7 @@ void start_server(SDParams params) { printf("%s", sd_get_system_info()); } - sd_ctx_t* sd_ctx = new_sd_ctx(params.ctxParams.model_path.c_str(), - params.ctxParams.clip_l_path.c_str(), - params.ctxParams.clip_g_path.c_str(), - params.ctxParams.t5xxl_path.c_str(), - params.ctxParams.diffusion_model_path.c_str(), - params.ctxParams.vae_path.c_str(), - params.ctxParams.taesd_path.c_str(), - params.ctxParams.controlnet_path.c_str(), - params.ctxParams.lora_model_dir.c_str(), - params.ctxParams.embeddings_path.c_str(), - params.ctxParams.stacked_id_embeddings_path.c_str(), - params.ctxParams.vae_decode_only, - params.ctxParams.vae_tiling, - false, - params.ctxParams.n_threads, - params.ctxParams.wtype, - params.ctxParams.rng_type, - params.ctxParams.schedule, - params.ctxParams.clip_on_cpu, - params.ctxParams.control_net_cpu, - params.ctxParams.vae_on_cpu, - params.ctxParams.diffusion_flash_attn); - - if (sd_ctx == NULL) { - printf("new_sd_ctx_t failed\n"); - return; - } + sd_ctx_t* sd_ctx = NULL; int n_prompts = 0; @@ -943,9 +1077,10 @@ void start_server(SDParams params) { // LOG_DEBUG("raw body is: %s\n", req.body.c_str()); sd_log(sd_log_level_t::SD_LOG_DEBUG, "raw body is: %s\n", req.body.c_str()); // parse req.body as json using jsoncpp + bool updateCTX = false; try { std::string json_str = req.body; - parseJsonPrompt(json_str, ¶ms); + updateCTX = parseJsonPrompt(json_str, ¶ms); } catch (json::parse_error& e) { // assume the request is just a prompt // LOG_WARN("Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); @@ -964,6 +1099,51 @@ void start_server(SDParams params) { // LOG_DEBUG("prompt is: %s\n", params.prompt.c_str()); sd_log(sd_log_level_t::SD_LOG_DEBUG, "prompt is: %s\n", params.lastRequest.prompt.c_str()); + if (updateCTX && sd_ctx != NULL) { + free_sd_ctx(sd_ctx); + sd_ctx = NULL; + } + + if (sd_ctx == NULL) { + json task_json = json::object(); + task_json["status"] = "Loading"; + task_json["data"] = json::array(); + task_json["step"] = -1; + task_json["eta"] = "?"; + + std::lock_guard results_lock(results_mutex); + task_results[task_id] = task_json; + + sd_ctx = new_sd_ctx(params.ctxParams.model_path.c_str(), + params.ctxParams.clip_l_path.c_str(), + params.ctxParams.clip_g_path.c_str(), + params.ctxParams.t5xxl_path.c_str(), + params.ctxParams.diffusion_model_path.c_str(), + params.ctxParams.vae_path.c_str(), + params.ctxParams.taesd_path.c_str(), + params.ctxParams.controlnet_path.c_str(), + params.ctxParams.lora_model_dir.c_str(), + params.ctxParams.embeddings_path.c_str(), + params.ctxParams.stacked_id_embeddings_path.c_str(), + params.ctxParams.vae_decode_only, + params.ctxParams.vae_tiling, + false, + params.ctxParams.n_threads, + params.ctxParams.wtype, + params.ctxParams.rng_type, + params.ctxParams.schedule, + params.ctxParams.clip_on_cpu, + params.ctxParams.control_net_cpu, + params.ctxParams.vae_on_cpu, + params.ctxParams.diffusion_flash_attn); + if (sd_ctx == NULL) { + printf("new_sd_ctx_t failed\n"); + std::lock_guard results_lock(results_mutex); + task_results[task_id]["status"] = "Failed"; + return; + } + } + { json started_task_json = json::object(); started_task_json["status"] = "Working"; @@ -1060,21 +1240,20 @@ void start_server(SDParams params) { svr->Get("/params", [¶ms](const httplib::Request& req, httplib::Response& res) { using json = nlohmann::json; json response; - json params_json = json::object(); - params_json["prompt"] = params.lastRequest.prompt; + json params_json = json::object(); + params_json["prompt"] = params.lastRequest.prompt; params_json["negative_prompt"] = params.lastRequest.negative_prompt; - params_json["clip_skip"] = params.lastRequest.clip_skip; - params_json["cfg_scale"] = params.lastRequest.cfg_scale; - params_json["guidance"] = params.lastRequest.guidance; - params_json["width"] = params.lastRequest.width; - params_json["height"] = params.lastRequest.height; - params_json["sample_method"] = sample_method_str[params.lastRequest.sample_method]; - params_json["sample_steps"] = params.lastRequest.sample_steps; - params_json["seed"] = params.lastRequest.seed; - params_json["batch_count"] = params.lastRequest.batch_count; + params_json["clip_skip"] = params.lastRequest.clip_skip; + params_json["cfg_scale"] = params.lastRequest.cfg_scale; + params_json["guidance"] = params.lastRequest.guidance; + params_json["width"] = params.lastRequest.width; + params_json["height"] = params.lastRequest.height; + params_json["sample_method"] = sample_method_str[params.lastRequest.sample_method]; + params_json["sample_steps"] = params.lastRequest.sample_steps; + params_json["seed"] = params.lastRequest.seed; + params_json["batch_count"] = params.lastRequest.batch_count; params_json["normalize_input"] = params.lastRequest.normalize_input; // params_json["input_id_images_path"] = params.input_id_images_path; - response["generation_params"] = params_json; json context_params = json::object(); // Do not expose paths @@ -1088,22 +1267,26 @@ void start_server(SDParams params) { context_params["lora_model_dir"] = params.ctxParams.lora_model_dir; // context_params["embeddings_path"] = params.ctxParams.embeddings_path; // context_params["stacked_id_embeddings_path"] = params.ctxParams.stacked_id_embeddings_path; - context_params["vae_decode_only"] = params.ctxParams.vae_decode_only; - context_params["vae_tiling"] = params.ctxParams.vae_tiling; - context_params["n_threads"] = params.ctxParams.n_threads; - context_params["wtype"] = params.ctxParams.wtype; - context_params["rng_type"] = params.ctxParams.rng_type; - context_params["schedule"] = params.ctxParams.schedule; - context_params["clip_on_cpu"] = params.ctxParams.clip_on_cpu; - context_params["control_net_cpu"] = params.ctxParams.control_net_cpu; - context_params["vae_on_cpu"] = params.ctxParams.vae_on_cpu; + context_params["vae_decode_only"] = params.ctxParams.vae_decode_only; + context_params["vae_tiling"] = params.ctxParams.vae_tiling; + context_params["n_threads"] = params.ctxParams.n_threads; + context_params["wtype"] = params.ctxParams.wtype; + context_params["rng_type"] = params.ctxParams.rng_type; + context_params["schedule"] = schedule_str[params.ctxParams.schedule]; + context_params["clip_on_cpu"] = params.ctxParams.clip_on_cpu; + context_params["control_net_cpu"] = params.ctxParams.control_net_cpu; + context_params["vae_on_cpu"] = params.ctxParams.vae_on_cpu; context_params["diffusion_flash_attn"] = params.ctxParams.diffusion_flash_attn; - response["context_params"] = context_params; + // response["taesd_preview"] = params.taesd_preview; + // params_json["preview_method"] = previews_str[params.lastRequest.preview_method]; + // params_json["preview_interval"] = params.lastRequest.preview_interval; + + response["generation_params"] = params_json; + response["context_params"] = context_params; res.set_content(response.dump(), "application/json"); }); - svr->Get("/result", [](const httplib::Request& req, httplib::Response& res) { using json = nlohmann::json; // Parse task ID from query parameters @@ -1142,233 +1325,77 @@ void start_server(SDParams params) { res.set_content(response.dump(), "application/json"); }); + svr->Get("/previews", [](const httplib::Request& req, httplib::Response& res) { + using json = nlohmann::json; + json response; + // unsupported + // for (int s = 0; s < N_PREVIEWS; s++) { + // response.push_back(previews_str[s]); + // } + res.set_content(response.dump(), "application/json"); + }); - svr->Get("/index.html", [](const httplib::Request& req, httplib::Response& res) { - try { - std::string html_content = R"xxx( - - - - - - SDCPP Server - - - -
    -
    -

    SDCPP Server

    -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    -
    - - - - )xxx"; + if (!params.ctxParams.taesd_path.empty()) { + response["tae"] = sd_basename(params.ctxParams.taesd_path); + } + res.set_content(response.dump(), "application/json"); + }); + + svr->Get("/index.html", [](const httplib::Request& req, httplib::Response& res) { + try { res.set_content(html_content, "text/html"); } catch (const std::exception& e) { res.set_content("Error loading page", "text/plain"); From 476a66f8f96bbcc7ce49370f582548f67daf1993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 3 Jan 2025 20:31:56 +0100 Subject: [PATCH 109/143] Fixes --- examples/server/frontend.cpp | 21 ++++++++------------- examples/server/main.cpp | 11 ++++++----- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp index c7e1151f8..cc1d2bfd1 100644 --- a/examples/server/frontend.cpp +++ b/examples/server/frontend.cpp @@ -328,19 +328,14 @@ const std::string html_content = R"xxx( const data = await response.json(); const select = document.getElementById('preview_mode'); - select.innerHTML = ''; - data.forEach(preview => { - const option = document.createElement('option'); - option.value = preview; - option.textContent = preview; - select.appendChild(option); - }); - if (!select.firstChild) { - // preview isn't supported - const option = document.createElement('option'); - option.value = ""; - option.textContent = "unsupported"; - select.appendChild(option); + if (data) { + select.innerHTML = ''; + data.forEach(preview => { + const option = document.createElement('option'); + option.value = preview; + option.textContent = preview; + select.appendChild(option); + }); } } diff --git a/examples/server/main.cpp b/examples/server/main.cpp index f1563b474..21dc96126 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -888,13 +888,13 @@ bool parseJsonPrompt(std::string json_str, SDParams* params) { } try { bool vae_tiling = payload["vae_tiling"]; - printf("VAE_tiling : %s", vae_tiling ? "true" : "false"); if (params->ctxParams.vae_tiling != vae_tiling) { params->ctxParams.vae_tiling = vae_tiling; updatectx = true; } } catch (...) { } + try { std::string model = payload["model"]; if (params->ctxParams.model_path != params->models_dir + model) { @@ -957,7 +957,7 @@ bool parseJsonPrompt(std::string json_str, SDParams* params) { try { std::string schedule = payload["schedule"]; int schedule_found = -1; - for (int m = 0; m < N_SAMPLE_METHODS; m++) { + for (int m = 0; m < N_SCHEDULES; m++) { if (!strcmp(schedule.c_str(), schedule_str[m])) { schedule_found = m; } @@ -1009,8 +1009,8 @@ void worker_thread() { queue_cond.wait(lock, [] { return !task_queue.empty() || stop_worker; }); if (!task_queue.empty()) { - is_busy = true; - auto task = task_queue.front(); + is_busy = true; + auto task = task_queue.front(); task_queue.pop(); lock.unlock(); task(); @@ -1080,7 +1080,7 @@ void start_server(SDParams params) { bool updateCTX = false; try { std::string json_str = req.body; - updateCTX = parseJsonPrompt(json_str, ¶ms); + updateCTX = parseJsonPrompt(json_str, ¶ms); } catch (json::parse_error& e) { // assume the request is just a prompt // LOG_WARN("Failed to parse json: %s\n Assuming it's just a prompt...\n", e.what()); @@ -1105,6 +1105,7 @@ void start_server(SDParams params) { } if (sd_ctx == NULL) { + printf("Loading sd_ctx\n"); json task_json = json::object(); task_json["status"] = "Loading"; task_json["data"] = json::array(); From 15bd8940346d5b6227b1639c944da547e0c117e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 3 Jan 2025 21:10:19 +0100 Subject: [PATCH 110/143] Frontend: dirty queue display --- examples/server/frontend.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp index cc1d2bfd1..a683790cb 100644 --- a/examples/server/frontend.cpp +++ b/examples/server/frontend.cpp @@ -10,6 +10,7 @@ const std::string html_content = R"xxx( body { font-family: Arial, sans-serif; display: flex; + flex-direction: column; align-items: center; justify-content: center; height: 100vh; @@ -254,8 +255,17 @@ const std::string html_content = R"xxx( +
    +

    Current task status: -- | Queue: 0

    +
    )xxx" R"xxx( - )xxx"; \ No newline at end of file From a1e82ec68c527d457a6f536f80df34657a903c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sun, 5 Jan 2025 00:23:58 +0100 Subject: [PATCH 115/143] Use progress_callback --- examples/server/frontend.cpp | 8 ++++---- examples/server/main.cpp | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp index 809002397..44912f60e 100644 --- a/examples/server/frontend.cpp +++ b/examples/server/frontend.cpp @@ -643,7 +643,6 @@ R"xxx( const taskId = data.task_id; let status = 'Pending'; const progressBar = document.getElementById("progress"); - progressBar.max = steps; while (status !== 'Done' && status !== 'Failed') { const statusResponse = await fetch(`/result?task_id=${taskId}`); const statusData = await statusResponse.json(); @@ -671,9 +670,10 @@ R"xxx( } status = statusData.status; document.getElementById('status').innerHTML = status; - if (status === 'Working') { - progressBar.value = statusData.step; - progressBar.innerHTML = Math.floor(100 * statusData.step / steps) + "%"; + if (statusData.step >= 0) { + progressBar.value = statusData.step; + progressBar.max = statusData.steps ?? steps; + progressBar.innerHTML = Math.floor(100 * statusData.step / statusData.steps) + "%"; progressBar.style.display = 'inline-block'; } if (status === 'Done' || (status === 'Working' && statusData.data.length > 0)) { diff --git a/examples/server/main.cpp b/examples/server/main.cpp index a745d124b..40ae1858b 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -1084,8 +1084,23 @@ void add_task(std::string task_id, std::function task) { queue_cond.notify_one(); } +void update_progress_cb(int step, int steps, float time, void* _data) { + using json = nlohmann::json; + if (running_task_id != "") { + std::lock_guard results_lock(results_mutex); + json running_task_json = task_results[running_task_id]; + running_task_json["step"] = step; + running_task_json["steps"] = steps; + if (running_task_json["status"] == "Working" && step == steps) { + running_task_json["status"] = "Decoding"; + } + task_results[running_task_id] = running_task_json; + } +} + void start_server(SDParams params) { sd_set_log_callback(sd_log_cb, (void*)¶ms); + sd_set_progress_callback(update_progress_cb, NULL); server_log_params = (void*)¶ms; @@ -1122,6 +1137,7 @@ void start_server(SDParams params) { pending_task_json["status"] = "Pending"; pending_task_json["data"] = json::array(); pending_task_json["step"] = -1; + pending_task_json["steps"] = -1; pending_task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); @@ -1167,6 +1183,7 @@ void start_server(SDParams params) { task_json["status"] = "Loading"; task_json["data"] = json::array(); task_json["step"] = -1; + task_json["step"] = -1; task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); @@ -1208,6 +1225,7 @@ void start_server(SDParams params) { started_task_json["status"] = "Working"; started_task_json["data"] = json::array(); started_task_json["step"] = 0; + started_task_json["step"] = params.lastRequest.sample_steps; started_task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); From 4a1fb94cdfa9bd7f34206124e6eb13edfdf338f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Sun, 5 Jan 2025 01:16:19 +0100 Subject: [PATCH 116/143] update progress bars (+fixes) --- examples/server/frontend.cpp | 51 +++++++++++++++++++++++++++++------- examples/server/main.cpp | 17 ++++++------ 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp index 44912f60e..ee724a2f4 100644 --- a/examples/server/frontend.cpp +++ b/examples/server/frontend.cpp @@ -157,6 +157,7 @@ const std::string html_content = R"xxx( height: 100%; } .imageFrame { + margin-top: auto; border: 1px solid #ccc; width: 512px; height: 512px; @@ -176,12 +177,30 @@ const std::string html_content = R"xxx( .left-section, .right-section { width: 50%; + height: 100%; } .imageFrame { max-width: 45vw; max-height: 45vw; } } + progress { + border: none; + width: 100%; + height: 8px; + margin-inline: auto; + } + progress::-webkit-progress-value { + background-color: blue; + } + .bars { + margin-bottom: 10%; + height: max-content; + display: flex; + width: 80%; + flex-direction: column; + margin-top: auto; + } )xxx" @@ -350,7 +369,11 @@ R"xxx( - +
    + 0% + 0% + 0% +
    @@ -642,16 +665,12 @@ R"xxx( const data = await response.json(); const taskId = data.task_id; let status = 'Pending'; - const progressBar = document.getElementById("progress"); while (status !== 'Done' && status !== 'Failed') { const statusResponse = await fetch(`/result?task_id=${taskId}`); const statusData = await statusResponse.json(); if (status == 'Pending' && statusData.status != status) { - //Task has started, update setTimeout(() => { fetchModelId(); - // Updating params can be annoying, let's just hope they are taken into account - // fetchParams(); const modelsSelect = document.getElementById('model'); const diffModelsSelect = document.getElementById('diff-model'); const clipLSelect = document.getElementById('clip_l'); @@ -666,12 +685,23 @@ R"xxx( t5xxlSelect.selectedIndex = 0; vaeSelect.selectedIndex = 0; taeSelect.selectedIndex = 0; + document.getElementById("load_progress").value = 0; + document.getElementById("work_progress").value = 0; + document.getElementById("vae_progress").value = 0; }, 0); } status = statusData.status; + if (status !== "Pending" && status !== "Loading") { + const progressBar = document.getElementById("load_progress"); + progressBar.value = 1; + progressBar.max = 1; + progressBar.innerHTML = "100%"; + progressBar.style.display = 'inline-block'; + } + const progressBar = status === "Loading" ? document.getElementById("load_progress") : status === "Working" ? document.getElementById("work_progress") : document.getElementById("vae_progress"); document.getElementById('status').innerHTML = status; - if (statusData.step >= 0) { - progressBar.value = statusData.step; + if (status !== 'Done' && statusData.step >= 0) { + progressBar.value = statusData.step; progressBar.max = statusData.steps ?? steps; progressBar.innerHTML = Math.floor(100 * statusData.step / statusData.steps) + "%"; progressBar.style.display = 'inline-block'; @@ -706,10 +736,11 @@ R"xxx( } else if (status === 'Failed') { alert('Image generation failed'); } - await new Promise(resolve => setTimeout(resolve, 250)); + await new Promise(resolve => setTimeout(resolve, 100)); } - progressBar.value = steps; - progressBar.innerHTML = "100%"; + document.getElementById("load_progress").value = document.getElementById("load_progress").max; + document.getElementById("work_progress").value = document.getElementById("work_progress").max; + document.getElementById("vae_progress").value = document.getElementById("vae_progress").max; queued_tasks--; update_queue(); } diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 40ae1858b..8690fa697 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -1088,12 +1088,12 @@ void update_progress_cb(int step, int steps, float time, void* _data) { using json = nlohmann::json; if (running_task_id != "") { std::lock_guard results_lock(results_mutex); - json running_task_json = task_results[running_task_id]; - running_task_json["step"] = step; - running_task_json["steps"] = steps; - if (running_task_json["status"] == "Working" && step == steps) { + json running_task_json = task_results[running_task_id]; + if (running_task_json["status"] == "Working" && running_task_json["step"] == running_task_json["steps"]) { running_task_json["status"] = "Decoding"; } + running_task_json["step"] = step; + running_task_json["steps"] = steps; task_results[running_task_id] = running_task_json; } } @@ -1137,7 +1137,7 @@ void start_server(SDParams params) { pending_task_json["status"] = "Pending"; pending_task_json["data"] = json::array(); pending_task_json["step"] = -1; - pending_task_json["steps"] = -1; + pending_task_json["steps"] = 0; pending_task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); @@ -1183,7 +1183,7 @@ void start_server(SDParams params) { task_json["status"] = "Loading"; task_json["data"] = json::array(); task_json["step"] = -1; - task_json["step"] = -1; + task_json["steps"] = 0; task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); @@ -1225,7 +1225,7 @@ void start_server(SDParams params) { started_task_json["status"] = "Working"; started_task_json["data"] = json::array(); started_task_json["step"] = 0; - started_task_json["step"] = params.lastRequest.sample_steps; + started_task_json["steps"] = params.lastRequest.sample_steps; started_task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); @@ -1300,7 +1300,8 @@ void start_server(SDParams params) { json end_task_json = json::object(); end_task_json["status"] = "Done"; end_task_json["data"] = images_json; - end_task_json["step"] = 0; + end_task_json["step"] = -1; + end_task_json["steps"] = 0; end_task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); task_results[task_id] = end_task_json; From 226caf0eda93697be52a59bce6efeb9e978c832f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?mord=C3=B8d?= <48280847+mord0d@users.noreply.github.com> Date: Thu, 10 Jul 2025 13:05:57 +0000 Subject: [PATCH 117/143] frontend.cpp: use relative paths in fetch() (#4) (cherry-picked) With absolute paths, there's no way to change base url. --- examples/server/frontend.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp index ee724a2f4..31ccc67b5 100644 --- a/examples/server/frontend.cpp +++ b/examples/server/frontend.cpp @@ -389,7 +389,7 @@ R"xxx( } const modelIdElement = document.getElementById('model-id'); async function fetchModelId() { - const response = await fetch('/model'); + const response = await fetch('model'); const data = await response.json(); let modelIdText = ''; if (data.model) { @@ -423,7 +423,7 @@ R"xxx( modelIdElement.textContent = modelIdText; } async function fetchSampleMethods() { - const response = await fetch('/sample_methods'); + const response = await fetch('sample_methods'); const data = await response.json(); const select = document.getElementById('sample_method'); data.forEach(method => { @@ -434,7 +434,7 @@ R"xxx( }); } async function fetchSchedules() { - const response = await fetch('/schedules'); + const response = await fetch('schedules'); const data = await response.json(); const select = document.getElementById('schedule_method'); data.forEach(schedule => { @@ -445,7 +445,7 @@ R"xxx( }); } async function fetchPreviewMethods() { - const response = await fetch('/previews'); + const response = await fetch('previews'); const data = await response.json(); const select = document.getElementById('preview_mode'); if (data) { @@ -459,7 +459,7 @@ R"xxx( } } async function fetchModelsEncodersAE() { - const response = await fetch('/models'); + const response = await fetch('models'); const data = await response.json(); const modelsSelect = document.getElementById('model'); if (data.models.length > 0) { @@ -568,7 +568,7 @@ R"xxx( } } async function fetchParams() { - const response = await fetch('/params'); + const response = await fetch('params'); const data = await response.json(); document.getElementById('prompt').value = data.generation_params.prompt; document.getElementById('neg_prompt').value = data.generation_params.negative_prompt; @@ -655,7 +655,7 @@ R"xxx( ...(preview_mode && { preview_mode: preview_mode }), ...(preview_interval && { preview_interval: preview_interval }), }; - const response = await fetch('/txt2img', { + const response = await fetch('txt2img', { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -666,7 +666,7 @@ R"xxx( const taskId = data.task_id; let status = 'Pending'; while (status !== 'Done' && status !== 'Failed') { - const statusResponse = await fetch(`/result?task_id=${taskId}`); + const statusResponse = await fetch(`result?task_id=${taskId}`); const statusData = await statusResponse.json(); if (status == 'Pending' && statusData.status != status) { setTimeout(() => { @@ -770,4 +770,4 @@ R"xxx( -)xxx"; \ No newline at end of file +)xxx"; From bcba77c399c0d955238d98f12581a46ac1391f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Thu, 10 Jul 2025 15:35:39 +0200 Subject: [PATCH 118/143] rebaseon master and apply api changes --- examples/server/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index 8690fa697..d2deacd2d 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -1211,7 +1211,8 @@ void start_server(SDParams params) { params.ctxParams.clip_on_cpu, params.ctxParams.control_net_cpu, params.ctxParams.vae_on_cpu, - params.ctxParams.diffusion_flash_attn); + params.ctxParams.diffusion_flash_attn, + true, false, 1); if (sd_ctx == NULL) { printf("new_sd_ctx_t failed\n"); std::lock_guard results_lock(results_mutex); @@ -1240,6 +1241,7 @@ void start_server(SDParams params) { params.lastRequest.clip_skip, params.lastRequest.cfg_scale, params.lastRequest.guidance, + 0.f, // eta params.lastRequest.width, params.lastRequest.height, params.lastRequest.sample_method, @@ -1301,7 +1303,7 @@ void start_server(SDParams params) { end_task_json["status"] = "Done"; end_task_json["data"] = images_json; end_task_json["step"] = -1; - end_task_json["steps"] = 0; + end_task_json["steps"] = 0; end_task_json["eta"] = "?"; std::lock_guard results_lock(results_mutex); task_results[task_id] = end_task_json; From febe7b5a117cdc4fbbb9e31762778baeb16e0304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Thu, 17 Jul 2025 18:16:50 +0200 Subject: [PATCH 119/143] server: update api --- examples/server/main.cpp | 218 ++++++++++++++++----------------------- 1 file changed, 90 insertions(+), 128 deletions(-) diff --git a/examples/server/main.cpp b/examples/server/main.cpp index d2deacd2d..ca714d433 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -36,41 +36,6 @@ #include "frontend.cpp" -const char* rng_type_to_str[] = { - "std_default", - "cuda", -}; - -// Names of the sampler method, same order as enum sample_method in stable-diffusion.h -const char* sample_method_str[] = { - "euler_a", - "euler", - "heun", - "dpm2", - "dpm++2s_a", - "dpm++2m", - "dpm++2mv2", - "ipndm", - "ipndm_v", - "lcm", -}; - -// Names of the sigma schedule overrides, same order as sample_schedule in stable-diffusion.h -const char* schedule_str[] = { - "default", - "discrete", - "karras", - "exponential", - "ays", - "gits", -}; - -enum SDMode { - TXT2IMG, - IMG2IMG, - MODE_COUNT -}; - struct SDCtxParams { std::string model_path; std::string clip_l_path; @@ -105,8 +70,6 @@ struct SDRequestParams { // TODO set to true if esrgan_path is specified in args bool upscale = false; - SDMode mode = TXT2IMG; - std::string prompt; std::string negative_prompt; @@ -195,11 +158,11 @@ void print_params(SDParams params) { printf(" clip_skip: %d\n", params.lastRequest.clip_skip); printf(" width: %d\n", params.lastRequest.width); printf(" height: %d\n", params.lastRequest.height); - printf(" sample_method: %s\n", sample_method_str[params.lastRequest.sample_method]); - printf(" schedule: %s\n", schedule_str[params.ctxParams.schedule]); + printf(" sample_method: %s\n", sd_sample_method_name(params.lastRequest.sample_method)); + printf(" schedule: %s\n", sd_schedule_name(params.ctxParams.schedule)); printf(" sample_steps: %d\n", params.lastRequest.sample_steps); printf(" strength(img2img): %.2f\n", params.lastRequest.strength); - printf(" rng: %s\n", rng_type_to_str[params.ctxParams.rng_type]); + printf(" rng: %s\n", sd_rng_type_name(params.ctxParams.rng_type)); printf(" seed: %ld\n", params.lastRequest.seed); printf(" batch_count: %d\n", params.lastRequest.batch_count); printf(" vae_tiling: %s\n", params.ctxParams.vae_tiling ? "true" : "false"); @@ -512,17 +475,12 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } const char* schedule_selected = argv[i]; - int schedule_found = -1; - for (int d = 0; d < N_SCHEDULES; d++) { - if (!strcmp(schedule_selected, schedule_str[d])) { - schedule_found = d; - } - } - if (schedule_found == -1) { + schedule_t schedule_found = str_to_schedule(schedule_selected); + if (schedule_found == SCHEDULE_COUNT) { invalid_arg = true; break; } - params.ctxParams.schedule = (schedule_t)schedule_found; + params.ctxParams.schedule = schedule_found; } else if (arg == "-s" || arg == "--seed") { if (++i >= argc) { invalid_arg = true; @@ -535,13 +493,8 @@ void parse_args(int argc, const char** argv, SDParams& params) { break; } const char* sample_method_selected = argv[i]; - int sample_method_found = -1; - for (int m = 0; m < N_SAMPLE_METHODS; m++) { - if (!strcmp(sample_method_selected, sample_method_str[m])) { - sample_method_found = m; - } - } - if (sample_method_found == -1) { + int sample_method_found = str_to_sample_method(sample_method_selected); + if (sample_method_found == SAMPLE_METHOD_COUNT) { invalid_arg = true; break; } @@ -689,8 +642,8 @@ std::string get_image_params(SDParams params, int64_t seed) { parameter_string += "Seed: " + std::to_string(seed) + ", "; parameter_string += "Size: " + std::to_string(params.lastRequest.width) + "x" + std::to_string(params.lastRequest.height) + ", "; parameter_string += "Model: " + sd_basename(params.ctxParams.model_path) + ", "; - parameter_string += "RNG: " + std::string(rng_type_to_str[params.ctxParams.rng_type]) + ", "; - parameter_string += "Sampler: " + std::string(sample_method_str[params.lastRequest.sample_method]); + parameter_string += "RNG: " + std::string(sd_rng_type_name(params.ctxParams.rng_type)) + ", "; + parameter_string += "Sampler: " + std::string(sd_sample_method_name(params.lastRequest.sample_method)); if (params.ctxParams.schedule == KARRAS) { parameter_string += " karras"; } @@ -807,14 +760,9 @@ bool parseJsonPrompt(std::string json_str, SDParams* params) { try { std::string sample_method = payload["sample_method"]; - int sample_method_found = -1; - for (int m = 0; m < N_SAMPLE_METHODS; m++) { - if (!strcmp(sample_method.c_str(), sample_method_str[m])) { - sample_method_found = m; - } - } - if (sample_method_found >= 0) { - params->lastRequest.sample_method = (sample_method_t)sample_method_found; + sample_method_t sample_method_found = str_to_sample_method(sample_method.c_str()); + if (sample_method_found != SAMPLE_METHOD_COUNT) { + params->lastRequest.sample_method = sample_method_found; } else { sd_log(sd_log_level_t::SD_LOG_WARN, "Unknown sampling method: %s\n", sample_method.c_str()); } @@ -1011,16 +959,11 @@ bool parseJsonPrompt(std::string json_str, SDParams* params) { } try { - std::string schedule = payload["schedule"]; - int schedule_found = -1; - for (int m = 0; m < N_SCHEDULES; m++) { - if (!strcmp(schedule.c_str(), schedule_str[m])) { - schedule_found = m; - } - } - if (schedule_found >= 0) { - if (params->ctxParams.schedule != (schedule_t)schedule_found) { - params->ctxParams.schedule = (schedule_t)schedule_found; + std::string schedule = payload["schedule"]; + schedule_t schedule_found = str_to_schedule(schedule.c_str()); + if (schedule_found != SCHEDULE_COUNT) { + if (params->ctxParams.schedule != schedule_found) { + params->ctxParams.schedule = schedule_found; updatectx = true; } } else { @@ -1189,30 +1132,31 @@ void start_server(SDParams params) { std::lock_guard results_lock(results_mutex); task_results[task_id] = task_json; } - - sd_ctx = new_sd_ctx(params.ctxParams.model_path.c_str(), - params.ctxParams.clip_l_path.c_str(), - params.ctxParams.clip_g_path.c_str(), - params.ctxParams.t5xxl_path.c_str(), - params.ctxParams.diffusion_model_path.c_str(), - params.ctxParams.vae_path.c_str(), - params.ctxParams.taesd_path.c_str(), - params.ctxParams.controlnet_path.c_str(), - params.ctxParams.lora_model_dir.c_str(), - params.ctxParams.embeddings_path.c_str(), - params.ctxParams.stacked_id_embeddings_path.c_str(), - params.ctxParams.vae_decode_only, - params.ctxParams.vae_tiling, - false, - params.ctxParams.n_threads, - params.ctxParams.wtype, - params.ctxParams.rng_type, - params.ctxParams.schedule, - params.ctxParams.clip_on_cpu, - params.ctxParams.control_net_cpu, - params.ctxParams.vae_on_cpu, - params.ctxParams.diffusion_flash_attn, - true, false, 1); + sd_ctx_params_t sd_ctx_params = { + params.ctxParams.model_path.c_str(), + params.ctxParams.clip_l_path.c_str(), + params.ctxParams.clip_g_path.c_str(), + params.ctxParams.t5xxl_path.c_str(), + params.ctxParams.diffusion_model_path.c_str(), + params.ctxParams.vae_path.c_str(), + params.ctxParams.taesd_path.c_str(), + params.ctxParams.controlnet_path.c_str(), + params.ctxParams.lora_model_dir.c_str(), + params.ctxParams.embeddings_path.c_str(), + params.ctxParams.stacked_id_embeddings_path.c_str(), + params.ctxParams.vae_decode_only, + params.ctxParams.vae_tiling, + false, + params.ctxParams.n_threads, + params.ctxParams.wtype, + params.ctxParams.rng_type, + params.ctxParams.schedule, + params.ctxParams.clip_on_cpu, + params.ctxParams.control_net_cpu, + params.ctxParams.vae_on_cpu, + params.ctxParams.diffusion_flash_attn, + true, false, 1}; + sd_ctx = new_sd_ctx(&sd_ctx_params); if (sd_ctx == NULL) { printf("new_sd_ctx_t failed\n"); std::lock_guard results_lock(results_mutex); @@ -1235,29 +1179,47 @@ void start_server(SDParams params) { { sd_image_t* results; - results = txt2img(sd_ctx, - params.lastRequest.prompt.c_str(), - params.lastRequest.negative_prompt.c_str(), - params.lastRequest.clip_skip, - params.lastRequest.cfg_scale, - params.lastRequest.guidance, - 0.f, // eta - params.lastRequest.width, - params.lastRequest.height, - params.lastRequest.sample_method, - params.lastRequest.sample_steps, - params.lastRequest.seed, - params.lastRequest.batch_count, - NULL, - 1, - params.lastRequest.style_ratio, - params.lastRequest.normalize_input, - params.input_id_images_path.c_str(), - params.lastRequest.skip_layers.data(), - params.lastRequest.skip_layers.size(), - params.lastRequest.slg_scale, - params.lastRequest.skip_layer_start, - params.lastRequest.skip_layer_end); + sd_slg_params_t slg = { + params.lastRequest.skip_layers.data(), + params.lastRequest.skip_layers.size(), + params.lastRequest.skip_layer_start, + params.lastRequest.skip_layer_end, + params.lastRequest.slg_scale}; + sd_guidance_params_t guidance = { + params.lastRequest.cfg_scale, + params.lastRequest.cfg_scale, + params.lastRequest.cfg_scale, + params.lastRequest.guidance, + slg}; + sd_image_t input_image = { + (uint32_t)params.lastRequest.width, + (uint32_t)params.lastRequest.height, + 3, + NULL}; + sd_image_t mask_img = input_image; + sd_img_gen_params_t gen_params = { + params.lastRequest.prompt.c_str(), + params.lastRequest.negative_prompt.c_str(), + params.lastRequest.clip_skip, + guidance, + input_image, + NULL, // ref images + 0, // ref images count + mask_img, + params.lastRequest.width, + params.lastRequest.height, + params.lastRequest.sample_method, + params.lastRequest.sample_steps, + 0.f, // eta + 1.f, // denoise strength + params.lastRequest.seed, + params.lastRequest.batch_count, + NULL, // control image ptr + 1.f, // control strength + params.lastRequest.style_ratio, + params.lastRequest.normalize_input, + params.input_id_images_path.c_str()}; + results = generate_image(sd_ctx, &gen_params); if (results == NULL) { printf("generate failed\n"); @@ -1328,7 +1290,7 @@ void start_server(SDParams params) { params_json["guidance"] = params.lastRequest.guidance; params_json["width"] = params.lastRequest.width; params_json["height"] = params.lastRequest.height; - params_json["sample_method"] = sample_method_str[params.lastRequest.sample_method]; + params_json["sample_method"] = sd_sample_method_name(params.lastRequest.sample_method); params_json["sample_steps"] = params.lastRequest.sample_steps; params_json["seed"] = params.lastRequest.seed; params_json["batch_count"] = params.lastRequest.batch_count; @@ -1352,7 +1314,7 @@ void start_server(SDParams params) { context_params["n_threads"] = params.ctxParams.n_threads; context_params["wtype"] = params.ctxParams.wtype; context_params["rng_type"] = params.ctxParams.rng_type; - context_params["schedule"] = schedule_str[params.ctxParams.schedule]; + context_params["schedule"] = sd_schedule_name(params.ctxParams.schedule); context_params["clip_on_cpu"] = params.ctxParams.clip_on_cpu; context_params["control_net_cpu"] = params.ctxParams.control_net_cpu; context_params["vae_on_cpu"] = params.ctxParams.vae_on_cpu; @@ -1390,8 +1352,8 @@ void start_server(SDParams params) { svr->Get("/sample_methods", [](const httplib::Request& req, httplib::Response& res) { using json = nlohmann::json; json response; - for (int m = 0; m < N_SAMPLE_METHODS; m++) { - response.push_back(sample_method_str[m]); + for (int m = 0; m < SAMPLE_METHOD_COUNT; m++) { + response.push_back(sd_sample_method_name((sample_method_t)m)); } res.set_content(response.dump(), "application/json"); }); @@ -1399,8 +1361,8 @@ void start_server(SDParams params) { svr->Get("/schedules", [](const httplib::Request& req, httplib::Response& res) { using json = nlohmann::json; json response; - for (int s = 0; s < N_SCHEDULES; s++) { - response.push_back(schedule_str[s]); + for (int s = 0; s < SCHEDULE_COUNT; s++) { + response.push_back(sd_schedule_name((schedule_t)s)); } res.set_content(response.dump(), "application/json"); }); From 47e1744040fe9a8227f42c2e42bef74dd92a5925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Fri, 18 Jul 2025 11:37:14 +0200 Subject: [PATCH 120/143] server: use new naming convention --- examples/server/frontend.cpp | 20 ++++++------- examples/server/main.cpp | 54 ++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp index 31ccc67b5..2434b385d 100644 --- a/examples/server/frontend.cpp +++ b/examples/server/frontend.cpp @@ -288,12 +288,12 @@ R"xxx(
    - - + +
    - - + +
    @@ -581,8 +581,8 @@ R"xxx( document.getElementById('seed').value = data.generation_params.seed; document.getElementById('batch_count').value = data.generation_params.batch_count; document.getElementById('schedule_method').value = data.context_params.schedule; - document.getElementById('vae_on_cpu').checked = data.context_params.vae_on_cpu; - document.getElementById('clip_on_cpu').checked = data.context_params.clip_on_cpu; + document.getElementById('keep_vae_on_cpu').checked = data.context_params.keep_vae_on_cpu; + document.getElementById('keep_clip_on_cpu').checked = data.context_params.keep_clip_on_cpu; document.getElementById('vae_tiling').checked = data.context_params.vae_tiling; document.getElementById('tae_decode').checked = !(data.taesd_preview); if (data.generation_params.preview_method) { @@ -620,8 +620,8 @@ R"xxx( const t5xxl = document.getElementById('t5xxl').value; const vae = document.getElementById('vae').value; const tae = document.getElementById('tae').value; - const vae_on_cpu = document.getElementById('vae_on_cpu').checked; - const clip_on_cpu = document.getElementById('clip_on_cpu').checked; + const keep_vae_on_cpu = document.getElementById('keep_vae_on_cpu').checked; + const keep_clip_on_cpu = document.getElementById('keep_clip_on_cpu').checked; const vae_tiling = document.getElementById('vae_tiling').checked; const tae_decode = document.getElementById('tae_decode').checked; const preview_mode = document.getElementById('preview_mode').value; @@ -648,8 +648,8 @@ R"xxx( ...(t5xxl && { t5xxl: t5xxl }), ...(vae && { vae: vae }), ...(tae && { tae: tae }), - ... { vae_on_cpu: vae_on_cpu }, - ... { clip_on_cpu: clip_on_cpu }, + ... { keep_vae_on_cpu: keep_vae_on_cpu }, + ... { keep_clip_on_cpu: keep_clip_on_cpu }, ... { vae_tiling: vae_tiling }, ... { tae_decode: tae_decode }, ...(preview_mode && { preview_mode: preview_mode }), diff --git a/examples/server/main.cpp b/examples/server/main.cpp index ca714d433..f3043ae27 100644 --- a/examples/server/main.cpp +++ b/examples/server/main.cpp @@ -45,7 +45,7 @@ struct SDCtxParams { std::string vae_path; std::string taesd_path; - std::string controlnet_path; + std::string control_net_path; std::string lora_model_dir; std::string embeddings_path; std::string stacked_id_embeddings_path; @@ -59,9 +59,9 @@ struct SDCtxParams { rng_type_t rng_type = CUDA_RNG; schedule_t schedule = DEFAULT; - bool control_net_cpu = false; - bool clip_on_cpu = false; - bool vae_on_cpu = false; + bool keep_control_net_on_cpu = false; + bool keep_clip_on_cpu = false; + bool keep_vae_on_cpu = false; bool diffusion_flash_attn = false; }; @@ -135,7 +135,7 @@ void print_params(SDParams params) { printf(" diffusion_model_path: %s\n", params.ctxParams.diffusion_model_path.c_str()); printf(" vae_path: %s\n", params.ctxParams.vae_path.c_str()); printf(" taesd_path: %s\n", params.ctxParams.taesd_path.c_str()); - printf(" controlnet_path: %s\n", params.ctxParams.controlnet_path.c_str()); + printf(" control_net_path: %s\n", params.ctxParams.control_net_path.c_str()); printf(" embeddings_path: %s\n", params.ctxParams.embeddings_path.c_str()); printf(" stacked_id_embeddings_path: %s\n", params.ctxParams.stacked_id_embeddings_path.c_str()); printf(" input_id_images_path: %s\n", params.input_id_images_path.c_str()); @@ -144,9 +144,9 @@ void print_params(SDParams params) { printf(" output_path: %s\n", params.output_path.c_str()); printf(" init_img: %s\n", params.input_path.c_str()); printf(" control_image: %s\n", params.control_image_path.c_str()); - printf(" clip on cpu: %s\n", params.ctxParams.clip_on_cpu ? "true" : "false"); - printf(" controlnet cpu: %s\n", params.ctxParams.control_net_cpu ? "true" : "false"); - printf(" vae decoder on cpu:%s\n", params.ctxParams.vae_on_cpu ? "true" : "false"); + printf(" clip on cpu: %s\n", params.ctxParams.keep_clip_on_cpu ? "true" : "false"); + printf(" control_net cpu: %s\n", params.ctxParams.keep_control_net_on_cpu ? "true" : "false"); + printf(" vae decoder on cpu:%s\n", params.ctxParams.keep_vae_on_cpu ? "true" : "false"); printf(" diffusion flash attention:%s\n", params.ctxParams.diffusion_flash_attn ? "true" : "false"); printf(" strength(control): %.2f\n", params.lastRequest.control_strength); printf(" prompt: %s\n", params.lastRequest.prompt.c_str()); @@ -224,7 +224,7 @@ void print_usage(int argc, const char* argv[]) { printf(" --diffusion-fa use flash attention in the diffusion model (for low vram)\n"); printf(" Might lower quality, since it implies converting k and v to f16.\n"); printf(" This might crash if it is not supported by the backend.\n"); - printf(" --control-net-cpu keep controlnet in cpu (for low vram)\n"); + printf(" --control-net-cpu keep control_net in cpu (for low vram)\n"); printf(" --canny apply canny preprocessor (edge detection)\n"); printf(" --color Colors the logging tags according to level\n"); printf(" -v, --verbose print extra info\n"); @@ -291,7 +291,7 @@ void parse_args(int argc, const char** argv, SDParams& params) { invalid_arg = true; break; } - params.ctxParams.controlnet_path = argv[i]; + params.ctxParams.control_net_path = argv[i]; } else if (arg == "--upscale-model") { if (++i >= argc) { invalid_arg = true; @@ -440,13 +440,13 @@ void parse_args(int argc, const char** argv, SDParams& params) { } else if (arg == "--vae-tiling") { params.ctxParams.vae_tiling = true; } else if (arg == "--control-net-cpu") { - params.ctxParams.control_net_cpu = true; + params.ctxParams.keep_control_net_on_cpu = true; } else if (arg == "--normalize-input") { params.lastRequest.normalize_input = true; } else if (arg == "--clip-on-cpu") { - params.ctxParams.clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs + params.ctxParams.keep_clip_on_cpu = true; // will slow down get_learned_condiotion but necessary for low MEM GPUs } else if (arg == "--vae-on-cpu") { - params.ctxParams.vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs + params.ctxParams.keep_vae_on_cpu = true; // will slow down latent decoding but necessary for low MEM GPUs } else if (arg == "--diffusion-fa") { params.ctxParams.diffusion_flash_attn = true; // can reduce MEM significantly } else if (arg == "-b" || arg == "--batch-count") { @@ -819,17 +819,17 @@ bool parseJsonPrompt(std::string json_str, SDParams* params) { } try { - bool vae_cpu = payload["vae_on_cpu"]; - if (params->ctxParams.vae_on_cpu != vae_cpu) { - params->ctxParams.vae_on_cpu = vae_cpu; + bool vae_cpu = payload["keep_vae_on_cpu"]; + if (params->ctxParams.keep_vae_on_cpu != vae_cpu) { + params->ctxParams.keep_vae_on_cpu = vae_cpu; updatectx = true; } } catch (...) { } try { - bool clip_cpu = payload["clip_on_cpu"]; - if (params->ctxParams.clip_on_cpu != clip_cpu) { - params->ctxParams.clip_on_cpu = clip_cpu; + bool clip_cpu = payload["keep_clip_on_cpu"]; + if (params->ctxParams.keep_clip_on_cpu != clip_cpu) { + params->ctxParams.keep_clip_on_cpu = clip_cpu; updatectx = true; } } catch (...) { @@ -1140,7 +1140,7 @@ void start_server(SDParams params) { params.ctxParams.diffusion_model_path.c_str(), params.ctxParams.vae_path.c_str(), params.ctxParams.taesd_path.c_str(), - params.ctxParams.controlnet_path.c_str(), + params.ctxParams.control_net_path.c_str(), params.ctxParams.lora_model_dir.c_str(), params.ctxParams.embeddings_path.c_str(), params.ctxParams.stacked_id_embeddings_path.c_str(), @@ -1151,9 +1151,9 @@ void start_server(SDParams params) { params.ctxParams.wtype, params.ctxParams.rng_type, params.ctxParams.schedule, - params.ctxParams.clip_on_cpu, - params.ctxParams.control_net_cpu, - params.ctxParams.vae_on_cpu, + params.ctxParams.keep_clip_on_cpu, + params.ctxParams.keep_control_net_on_cpu, + params.ctxParams.keep_vae_on_cpu, params.ctxParams.diffusion_flash_attn, true, false, 1}; sd_ctx = new_sd_ctx(&sd_ctx_params); @@ -1305,7 +1305,7 @@ void start_server(SDParams params) { // context_params["t5xxl_path"] = params.ctxParams.t5xxl_path; // context_params["diffusion_model_path"] = params.ctxParams.diffusion_model_path; // context_params["vae_path"] = params.ctxParams.vae_path; - // context_params["controlnet_path"] = params.ctxParams.controlnet_path; + // context_params["control_net_path"] = params.ctxParams.control_net_path; context_params["lora_model_dir"] = params.ctxParams.lora_model_dir; // context_params["embeddings_path"] = params.ctxParams.embeddings_path; // context_params["stacked_id_embeddings_path"] = params.ctxParams.stacked_id_embeddings_path; @@ -1315,9 +1315,9 @@ void start_server(SDParams params) { context_params["wtype"] = params.ctxParams.wtype; context_params["rng_type"] = params.ctxParams.rng_type; context_params["schedule"] = sd_schedule_name(params.ctxParams.schedule); - context_params["clip_on_cpu"] = params.ctxParams.clip_on_cpu; - context_params["control_net_cpu"] = params.ctxParams.control_net_cpu; - context_params["vae_on_cpu"] = params.ctxParams.vae_on_cpu; + context_params["keep_clip_on_cpu"] = params.ctxParams.keep_clip_on_cpu; + context_params["keep_control_net_on_cpu"] = params.ctxParams.keep_control_net_on_cpu; + context_params["keep_vae_on_cpu"] = params.ctxParams.keep_vae_on_cpu; context_params["diffusion_flash_attn"] = params.ctxParams.diffusion_flash_attn; // response["taesd_preview"] = params.taesd_preview; From 2ac5e3127d13eee92223b43cda92b303a37fabea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20du=20Hamel?= Date: Wed, 23 Jul 2025 16:04:05 +0200 Subject: [PATCH 121/143] server: change API --- examples/server/frontend.cpp | 198 ++++++++++++++------ examples/server/main.cpp | 352 ++++++++++++++++++++++++++--------- 2 files changed, 411 insertions(+), 139 deletions(-) diff --git a/examples/server/frontend.cpp b/examples/server/frontend.cpp index 2434b385d..b35279f52 100644 --- a/examples/server/frontend.cpp +++ b/examples/server/frontend.cpp @@ -1,9 +1,12 @@ const std::string html_content = R"xxx( + + SDCPP Server