mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-11-11 03:24:30 +00:00
feat: run conditional and unconditional CFG in one batched UNet forward (#2085)
This commit is contained in:
1 parent
b7a74e68d7
commit
1b0ba10893
7 files changed
+148
-23
No files matched your search
@@ -21,6 +21,16 @@ CPU fallback. It excludes weights and cache buffers. Within a runner lifecycle,
|
|||||||
the summary is printed only on the first graph or when backend capacities or the
|
the summary is printed only on the first graph or when backend capacities or the
|
||||||
segment count change.
|
segment count change.
|
||||||
|
|
||||||
|
## Run conditional and unconditional CFG in one batched UNet forward.
|
||||||
|
|
||||||
|
For UNet models, the conditional and unconditional guidance branches are
|
||||||
|
concatenated into a single batch of two and run through one UNet forward per
|
||||||
|
step instead of two separate forwards. This is enabled by default whenever the
|
||||||
|
run qualifies for it.
|
||||||
|
|
||||||
|
Use `--batched-cfg off` to force separate conditional and unconditional
|
||||||
|
forwards.
|
||||||
|
|
||||||
## Use VAE tiling to reduce encode and decode memory usage.
|
## Use VAE tiling to reduce encode and decode memory usage.
|
||||||
|
|
||||||
`--vae-tiling` enables spatial tiling for both VAE encoding and decoding. The
|
`--vae-tiling` enables spatial tiling for both VAE encoding and decoding. The
|
||||||
|
|||||||
+28
-18
@@ -378,6 +378,23 @@ static int parse_scale_override(int argc, const char** argv, int index, float& s
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parse_on_off_arg(int argc, const char** argv, int index, const char* option, bool& value) {
|
||||||
|
if (++index >= argc) {
|
||||||
|
LOG_ERROR("%s requires 'on' or 'off'", option);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
const std::string arg = argv[index];
|
||||||
|
if (arg == "on") {
|
||||||
|
value = true;
|
||||||
|
} else if (arg == "off") {
|
||||||
|
value = false;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("invalid %s value '%s'; expected 'on' or 'off'", option, argv[index]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
ArgOptions SDContextParams::get_options() {
|
ArgOptions SDContextParams::get_options() {
|
||||||
ArgOptions options;
|
ArgOptions options;
|
||||||
options.string_options = {
|
options.string_options = {
|
||||||
@@ -637,23 +654,6 @@ ArgOptions SDContextParams::get_options() {
|
|||||||
true, &vae_conv_direct},
|
true, &vae_conv_direct},
|
||||||
};
|
};
|
||||||
|
|
||||||
auto on_auto_fit_arg = [&](int argc, const char** argv, int index) {
|
|
||||||
if (++index >= argc) {
|
|
||||||
LOG_ERROR("--auto-fit requires 'on' or 'off'");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
const std::string arg = argv[index];
|
|
||||||
if (arg == "on") {
|
|
||||||
auto_fit = true;
|
|
||||||
} else if (arg == "off") {
|
|
||||||
auto_fit = false;
|
|
||||||
} else {
|
|
||||||
LOG_ERROR("invalid --auto-fit value '%s'; expected 'on' or 'off'", argv[index]);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto on_type_arg = [&](int argc, const char** argv, int index) {
|
auto on_type_arg = [&](int argc, const char** argv, int index) {
|
||||||
if (++index >= argc) {
|
if (++index >= argc) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -742,7 +742,15 @@ ArgOptions SDContextParams::get_options() {
|
|||||||
"on|off (default: on). Preserve --backend (otherwise select one GPU) and place weights on the compute GPU, "
|
"on|off (default: on). Preserve --backend (otherwise select one GPU) and place weights on the compute GPU, "
|
||||||
"RAM, another GPU, or disk in that order, according to available memory (--max-vram limits GPU budgets). "
|
"RAM, another GPU, or disk in that order, according to available memory (--max-vram limits GPU budgets). "
|
||||||
"Disabled by explicit --params-backend; uses automatic graph segmentation when needed",
|
"Disabled by explicit --params-backend; uses automatic graph segmentation when needed",
|
||||||
on_auto_fit_arg},
|
[this](int argc, const char** argv, int index) {
|
||||||
|
return parse_on_off_arg(argc, argv, index, "--auto-fit", auto_fit);
|
||||||
|
}},
|
||||||
|
{"",
|
||||||
|
"--batched-cfg",
|
||||||
|
"on|off (default: on). Run the conditional and unconditional CFG branches in one batched UNet forward when supported",
|
||||||
|
[this](int argc, const char** argv, int index) {
|
||||||
|
return parse_on_off_arg(argc, argv, index, "--batched-cfg", batched_cfg);
|
||||||
|
}},
|
||||||
{"",
|
{"",
|
||||||
"--type",
|
"--type",
|
||||||
"weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K). "
|
"weight type (examples: f32, f16, q4_0, q4_1, q5_0, q5_1, q8_0, q2_K, q3_K, q4_K). "
|
||||||
@@ -940,6 +948,7 @@ std::string SDContextParams::to_string() const {
|
|||||||
<< " max_vram: \"" << max_vram << "\",\n"
|
<< " max_vram: \"" << max_vram << "\",\n"
|
||||||
<< " disable_prefetch: " << (disable_prefetch ? "true" : "false") << ",\n"
|
<< " disable_prefetch: " << (disable_prefetch ? "true" : "false") << ",\n"
|
||||||
<< " disable_segmented_compute: " << (disable_segmented_compute ? "true" : "false") << ",\n"
|
<< " disable_segmented_compute: " << (disable_segmented_compute ? "true" : "false") << ",\n"
|
||||||
|
<< " batched_cfg: " << (batched_cfg ? "true" : "false") << ",\n"
|
||||||
<< " eager_load: " << (eager_load ? "true" : "false") << ",\n"
|
<< " eager_load: " << (eager_load ? "true" : "false") << ",\n"
|
||||||
<< " backend: \"" << backend << "\",\n"
|
<< " backend: \"" << backend << "\",\n"
|
||||||
<< " params_backend: \"" << params_backend << "\",\n"
|
<< " params_backend: \"" << params_backend << "\",\n"
|
||||||
@@ -1022,6 +1031,7 @@ sd_ctx_params_t SDContextParams::to_sd_ctx_params_t(bool taesd_preview) {
|
|||||||
sd_ctx_params.max_vram = max_vram.c_str();
|
sd_ctx_params.max_vram = max_vram.c_str();
|
||||||
sd_ctx_params.disable_prefetch = disable_prefetch;
|
sd_ctx_params.disable_prefetch = disable_prefetch;
|
||||||
sd_ctx_params.disable_segmented_compute = disable_segmented_compute;
|
sd_ctx_params.disable_segmented_compute = disable_segmented_compute;
|
||||||
|
sd_ctx_params.batched_cfg = batched_cfg;
|
||||||
sd_ctx_params.eager_load = eager_load;
|
sd_ctx_params.eager_load = eager_load;
|
||||||
sd_ctx_params.backend = effective_backend.c_str();
|
sd_ctx_params.backend = effective_backend.c_str();
|
||||||
sd_ctx_params.params_backend = effective_params_backend.c_str();
|
sd_ctx_params.params_backend = effective_params_backend.c_str();
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ struct SDContextParams {
|
|||||||
bool disable_prefetch = false;
|
bool disable_prefetch = false;
|
||||||
bool disable_segmented_compute = false;
|
bool disable_segmented_compute = false;
|
||||||
bool eager_load = false;
|
bool eager_load = false;
|
||||||
|
bool batched_cfg = true;
|
||||||
std::string backend;
|
std::string backend;
|
||||||
std::string params_backend;
|
std::string params_backend;
|
||||||
std::string split_mode;
|
std::string split_mode;
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ typedef struct {
|
|||||||
const char* rpc_servers;
|
const char* rpc_servers;
|
||||||
const char* model_args;
|
const char* model_args;
|
||||||
bool disable_segmented_compute; // Force monolithic graph execution even when automatic graph cutting would fit memory better
|
bool disable_segmented_compute; // Force monolithic graph execution even when automatic graph cutting would fit memory better
|
||||||
|
bool batched_cfg; // Run the conditional and unconditional CFG branches in one batched UNet forward when supported
|
||||||
float linear_scale; // Override linear input scaling; 0 keeps the model default
|
float linear_scale; // Override linear input scaling; 0 keeps the model default
|
||||||
float attn_scale; // Override flash-attention K/V scaling; 0 keeps the model default
|
float attn_scale; // Override flash-attention K/V scaling; 0 keeps the model default
|
||||||
const char* tokenizer; // tokenizer.json path or main=FILE,clip-l=FILE,clip-g=FILE assignments; required for PiD and Lens
|
const char* tokenizer; // tokenizer.json path or main=FILE,clip-l=FILE,clip-g=FILE assignments; required for PiD and Lens
|
||||||
|
|||||||
@@ -587,7 +587,9 @@ public:
|
|||||||
label_emb = ggml_silu_inplace(ctx->ggml_ctx, label_emb);
|
label_emb = ggml_silu_inplace(ctx->ggml_ctx, label_emb);
|
||||||
label_emb = label_embed_2->forward(ctx, label_emb); // [N, time_embed_dim]
|
label_emb = label_embed_2->forward(ctx, label_emb); // [N, time_embed_dim]
|
||||||
|
|
||||||
emb = ggml_add(ctx->ggml_ctx, emb, label_emb); // [N, time_embed_dim]
|
emb = label_emb->ne[1] > emb->ne[1]
|
||||||
|
? ggml_add(ctx->ggml_ctx, label_emb, emb)
|
||||||
|
: ggml_add(ctx->ggml_ctx, emb, label_emb); // [N, time_embed_dim]
|
||||||
}
|
}
|
||||||
// sd::ggml_graph_cut::mark_graph_cut(emb, "unet.prelude", "emb");
|
// sd::ggml_graph_cut::mark_graph_cut(emb, "unet.prelude", "emb");
|
||||||
|
|
||||||
|
|||||||
@@ -2227,6 +2227,27 @@ void StableDiffusionGGML::report_sample_progress(int step,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static sd::Tensor<float> batch_two_condition_tensors(const sd::Tensor<float>& a, const sd::Tensor<float>& b) {
|
||||||
|
if (a.empty() || b.empty() || a.dim() != b.dim()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (a.dim() == 1) {
|
||||||
|
if (a.shape() != b.shape()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto batched = sd::ops::concat(a, b, 0);
|
||||||
|
batched.reshape_({a.shape()[0], 2});
|
||||||
|
return batched;
|
||||||
|
}
|
||||||
|
const int64_t batch_dim = a.dim() - 1;
|
||||||
|
for (int64_t d = 0; d < batch_dim; d++) {
|
||||||
|
if (a.shape()[d] != b.shape()[d]) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sd::ops::concat(a, b, static_cast<size_t>(batch_dim));
|
||||||
|
}
|
||||||
|
|
||||||
void StableDiffusionGGML::compute_sample_controls(const sd::Tensor<float>& control_image,
|
void StableDiffusionGGML::compute_sample_controls(const sd::Tensor<float>& control_image,
|
||||||
const sd::Tensor<float>& noised_input,
|
const sd::Tensor<float>& noised_input,
|
||||||
const sd::Tensor<float>& timesteps_tensor,
|
const sd::Tensor<float>& timesteps_tensor,
|
||||||
@@ -2604,6 +2625,57 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
|||||||
return output_opt;
|
return output_opt;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto run_batched_condition = [&](const SDCondition& condition,
|
||||||
|
const sd::Tensor<float>* c_concat_override) -> sd::Tensor<float> {
|
||||||
|
const sd::Tensor<float>& condition_concat =
|
||||||
|
c_concat_override != nullptr ? *c_concat_override : condition.c_concat;
|
||||||
|
|
||||||
|
sd::Tensor<float> batched_context = batch_two_condition_tensors(condition.c_crossattn, uncond.c_crossattn);
|
||||||
|
sd::Tensor<float> batched_y = batch_two_condition_tensors(condition.c_vector, uncond.c_vector);
|
||||||
|
sd::Tensor<float> batched_concat = batch_two_condition_tensors(condition_concat, uncond.c_concat);
|
||||||
|
if (!condition.c_crossattn.empty() && batched_context.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if ((!condition.c_vector.empty() || !uncond.c_vector.empty()) && batched_y.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if ((!condition_concat.empty() || !uncond.c_concat.empty()) && batched_concat.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sd::Tensor<float>> uncond_controls;
|
||||||
|
compute_sample_controls(control_image, noised_input, timesteps_tensor, uncond, &uncond_controls);
|
||||||
|
if (controls.size() != uncond_controls.size()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<sd::Tensor<float>> batched_controls;
|
||||||
|
batched_controls.reserve(controls.size());
|
||||||
|
for (size_t i = 0; i < controls.size(); i++) {
|
||||||
|
sd::Tensor<float> batched_control = batch_two_condition_tensors(controls[i], uncond_controls[i]);
|
||||||
|
if (batched_control.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
batched_controls.push_back(std::move(batched_control));
|
||||||
|
}
|
||||||
|
|
||||||
|
sd::Tensor<float> batched_x =
|
||||||
|
sd::ops::concat(noised_input, noised_input, static_cast<size_t>(noised_input.dim() - 1));
|
||||||
|
|
||||||
|
DiffusionParams batched_params = diffusion_params;
|
||||||
|
batched_params.x = &batched_x;
|
||||||
|
batched_params.context = batched_context.empty() ? nullptr : &batched_context;
|
||||||
|
batched_params.c_concat = batched_concat.empty() ? nullptr : &batched_concat;
|
||||||
|
batched_params.y = batched_y.empty() ? nullptr : &batched_y;
|
||||||
|
batched_params.ref_latents = nullptr;
|
||||||
|
batched_params.extra = UNetDiffusionExtra{1, &batched_controls, control_strength};
|
||||||
|
|
||||||
|
sd::Tensor<float> output = work_diffusion_model->compute(n_threads, batched_params);
|
||||||
|
if (output.empty()) {
|
||||||
|
LOG_ERROR("batched diffusion model compute failed");
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
const SDCondition* positive_condition = &cond;
|
const SDCondition* positive_condition = &cond;
|
||||||
const sd::Tensor<float>* c_concat_override = nullptr;
|
const sd::Tensor<float>* c_concat_override = nullptr;
|
||||||
for (const auto& extension : generation_extensions) {
|
for (const auto& extension : generation_extensions) {
|
||||||
@@ -2643,12 +2715,40 @@ sd::Tensor<float> StableDiffusionGGML::sample(const std::shared_ptr<DiffusionMod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cond_out = run_condition(*positive_condition, c_concat_override);
|
const bool batch_cfg_ok = config_->params.batched_cfg &&
|
||||||
if (cond_out.empty()) {
|
sd_version_is_unet(version) &&
|
||||||
return {};
|
!uncond.empty() &&
|
||||||
|
img_uncond.empty() &&
|
||||||
|
!skip_uncond &&
|
||||||
|
!cache_runtime.ucache_enabled() &&
|
||||||
|
!(is_skiplayer_step && slg_uncond) &&
|
||||||
|
ip_adapter_tokens.empty() &&
|
||||||
|
ip_adapter_uncond_tokens.empty() &&
|
||||||
|
!config_->animatediff_loaded &&
|
||||||
|
(noised_input.dim() < 4 || noised_input.shape()[3] <= 1) &&
|
||||||
|
std::none_of(generation_extensions.begin(),
|
||||||
|
generation_extensions.end(),
|
||||||
|
[](const std::shared_ptr<GenerationExtension>& extension) {
|
||||||
|
return extension->is_enabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (batch_cfg_ok) {
|
||||||
|
sd::Tensor<float> batched_out = run_batched_condition(*positive_condition, c_concat_override);
|
||||||
|
if (!batched_out.empty() && batched_out.dim() >= 4 && batched_out.shape()[3] == 2) {
|
||||||
|
auto parts = sd::ops::chunk(batched_out, 2, 3);
|
||||||
|
cond_out = std::move(parts[0]);
|
||||||
|
uncond_out = std::move(parts[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!uncond.empty()) {
|
if (cond_out.empty()) {
|
||||||
|
cond_out = run_condition(*positive_condition, c_concat_override);
|
||||||
|
if (cond_out.empty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uncond_out.empty() && !uncond.empty()) {
|
||||||
if (!skip_uncond) {
|
if (!skip_uncond) {
|
||||||
const std::vector<int>* uncond_skip_layers = nullptr;
|
const std::vector<int>* uncond_skip_layers = nullptr;
|
||||||
if (is_skiplayer_step && slg_uncond) {
|
if (is_skiplayer_step && slg_uncond) {
|
||||||
|
|||||||
@@ -335,6 +335,7 @@ void sd_ctx_params_init(sd_ctx_params_t* sd_ctx_params) {
|
|||||||
sd_ctx_params->max_vram = nullptr;
|
sd_ctx_params->max_vram = nullptr;
|
||||||
sd_ctx_params->disable_prefetch = false;
|
sd_ctx_params->disable_prefetch = false;
|
||||||
sd_ctx_params->disable_segmented_compute = false;
|
sd_ctx_params->disable_segmented_compute = false;
|
||||||
|
sd_ctx_params->batched_cfg = true;
|
||||||
sd_ctx_params->eager_load = false;
|
sd_ctx_params->eager_load = false;
|
||||||
sd_ctx_params->enable_mmap = false;
|
sd_ctx_params->enable_mmap = false;
|
||||||
sd_ctx_params->diffusion_flash_attn = false;
|
sd_ctx_params->diffusion_flash_attn = false;
|
||||||
|
|||||||
Reference in new issue
Block a user