Compare commits

...
9 changed files with 134 additions and 47 deletions
+17 -6
View File
@@ -1008,7 +1008,7 @@ ArgOptions SDGenerationParams::get_options() {
&hires_upscaler},
{"",
"--extra-sample-args",
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_divisions",
"extra sampler/scheduler/guidance args, key=value list. CFG supports guidance_schedule; APG supports apg_eta, apg_momentum, apg_norm_threshold, apg_norm_threshold_smoothing; SLG supports slg_uncond; lcm supports noise_clip_std, noise_scale_start, noise_scale_end; flux supports base_shift, max_shift; ltx2 supports max_shift, base_shift, stretch, terminal; euler_ge supports gamma; beta scheduler supports alpha, beta; logit_normal supports mu, std, logsnr_min, logsnr_max, resolution_aware; lms supports lms_max_order, lms_shift, lms_divisions",
(int)',',
&extra_sample_args},
{"",
@@ -1555,6 +1555,16 @@ ArgOptions SDGenerationParams::get_options() {
return 1;
};
std::string sample_methods = sample_method_to_str[0];
for (int i = 1; i < SAMPLE_METHOD_COUNT; i++) {
sample_methods += ", " + std::string(sample_method_to_str[i]);
}
std::string schedulers = scheduler_to_str[0];
for (int i = 1; i < SCHEDULER_COUNT; i++) {
schedulers += ", " + std::string(scheduler_to_str[i]);
}
options.manual_options = {
{"-s",
"--seed",
@@ -1562,17 +1572,18 @@ ArgOptions SDGenerationParams::get_options() {
on_seed_arg},
{"",
"--sampling-method",
"sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp, lms]"
"(default: euler for Flux/SD3/Wan, euler_a otherwise)",
"sampling method, one of [" + sample_methods + "], "
"default: euler for Flux/SD3/Wan, euler_a otherwise",
on_sample_method_arg},
{"",
"--high-noise-sampling-method",
"(high noise) sampling method, one of [euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2, dpm++2m_sde, dpm++2m_sde_bt, ipndm, ipndm_v, lcm, ddim_trailing, tcd, res_multistep, res_2s, er_sde, euler_cfg_pp, euler_a_cfg_pp, lms]"
" default: euler for Flux/SD3/Wan, euler_a otherwise",
"(high noise) sampling method, one of [" + sample_methods + "], "
"default: euler for Flux/SD3/Wan, euler_a otherwise",
on_high_noise_sample_method_arg},
{"",
"--scheduler",
"denoiser sigma scheduler, one of [discrete, karras, exponential, ays, gits, smoothstep, sgm_uniform, simple, kl_optimal, lcm, bong_tangent, ltx2, logit_normal, flux2, flux, beta], alias: normal=discrete, default: model-specific",
"denoiser sigma scheduler, one of [" + schedulers + "], "
"alias: normal=discrete, default: model-specific",
on_scheduler_arg},
{"",
"--sigmas",
+4
View File
@@ -60,6 +60,8 @@ enum sample_method_t {
SAMPLE_METHOD_COUNT
};
extern SD_API const char* sample_method_to_str[];
enum scheduler_t {
DISCRETE_SCHEDULER,
KARRAS_SCHEDULER,
@@ -80,6 +82,8 @@ enum scheduler_t {
SCHEDULER_COUNT
};
extern SD_API const char* scheduler_to_str[];
enum prediction_t {
EPS_PRED,
V_PRED,
+3 -3
View File
@@ -23,16 +23,16 @@ from typing import BinaryIO
# Configuration
# -----------------------------------------------------------------------------
OUTPUT_PATH = Path(r"..\models\diffusion_models\minimax_h3_ref2va_pruned_bf16.safetensors")
OUTPUT_PATH = Path(r".minimax_h3_fl2va_pruned_bf16.safetensors")
SOURCE_RULES = [
{
"path": Path(r"..\models\diffusion_models\minimax_h3_ref2va_bf16.safetensors"),
"path": Path(r".minimax_h3_fl2va_bf16.safetensors"),
"include": [r".*"],
"exclude": [r".*adaln_proj\.linear.*", r"time_embedder.*"],
},
{
"path": Path(r"..\models\diffusion_models\minimax_h3_ref2va_pruned_int8_convrot.safetensors"),
"path": Path(r".minimax_h3_fl2va_pruned_int8_convrot.safetensors"),
"include": [r"^.*adaln_proj\.linear.*", "adaln_t_table"],
"exclude": [],
},
+5 -2
View File
@@ -2456,13 +2456,14 @@ protected:
GGML_ASSERT(gf != nullptr);
size_t effective_budget = max_graph_vram_bytes;
size_t free_clamp = SIZE_MAX;
if (stream_layers_enabled && max_graph_vram_bytes > 0 && runtime_backend != nullptr) {
ggml_backend_dev_t dev = ggml_backend_get_device(runtime_backend);
if (dev != nullptr && ggml_backend_dev_type(dev) != GGML_BACKEND_DEVICE_TYPE_CPU) {
size_t free_vram = 0, total_vram = 0;
ggml_backend_dev_memory(dev, &free_vram, &total_vram);
constexpr size_t safety_margin = 512ull * 1024 * 1024;
size_t free_clamp = (free_vram > safety_margin) ? (free_vram - safety_margin) : 0;
free_clamp = (free_vram > safety_margin) ? (free_vram - safety_margin) : 0;
if (free_clamp < effective_budget) {
LOG_DEBUG("%s clamping streaming budget: actual free VRAM %.2f MB < user cap %.2f MB",
get_desc().c_str(),
@@ -2479,7 +2480,9 @@ protected:
observed_max_effective_budget_ = effective_budget;
budget_increased = true;
} else {
effective_budget = observed_max_effective_budget_;
// Keep the plan cache stable, but never plan above what is free now:
// another model or process can take VRAM after the first measurement.
effective_budget = std::min(observed_max_effective_budget_, free_clamp);
}
}
+45 -10
View File
@@ -453,11 +453,12 @@ namespace sd::ggml_graph_cut {
if (tensor == nullptr || tensor->name[0] == '\0') {
return false;
}
return std::strncmp(tensor->name, GGML_RUNNER_CUT_PREFIX, std::strlen(GGML_RUNNER_CUT_PREFIX)) == 0;
return starts_with(tensor->name, GGML_RUNNER_CUT_PREFIX) &&
ends_with(tensor->name, GGML_RUNNER_CUT_SUFFIX);
}
std::string make_graph_cut_name(const std::string& group, const std::string& output) {
return std::string(GGML_RUNNER_CUT_PREFIX) + group + "|" + output;
return std::string(GGML_RUNNER_CUT_PREFIX) + group + "|" + output + GGML_RUNNER_CUT_SUFFIX;
}
void mark_graph_cut(ggml_tensor* tensor, const std::string& group, const std::string& output) {
@@ -603,7 +604,43 @@ namespace sd::ggml_graph_cut {
GGML_ASSERT(gf != nullptr);
GGML_ASSERT(graph_ctx_out != nullptr);
const size_t graph_size = segment.internal_node_indices.size() + segment.input_refs.size() + 8;
// Collect leaf inputs and internal nodes, then any tensor they
// reference that is not already represented, notably the view_src of a
// view-typed input leaf. ggml_gallocr sizes its hash set from
// n_nodes + n_leafs (plus a 25% margin that rounds down to zero for a
// one-node segment), so every distinct tensor it will hash must be
// counted here or a tiny segment overflows the hash set and aborts.
std::vector<ggml_tensor*> leaves;
std::unordered_set<ggml_tensor*> represented;
for (const auto& input : segment.input_refs) {
ggml_tensor* current_input = input_tensor(gf, input);
if (current_input == nullptr) {
continue;
}
if (represented.insert(current_input).second) {
leaves.push_back(current_input);
}
}
for (int node_idx : segment.internal_node_indices) {
represented.insert(ggml_graph_node(gf, node_idx));
}
auto add_reference = [&](ggml_tensor* tensor) {
if (tensor != nullptr && represented.insert(tensor).second) {
leaves.push_back(tensor);
}
};
for (int node_idx : segment.internal_node_indices) {
ggml_tensor* node = ggml_graph_node(gf, node_idx);
for (int src_idx = 0; src_idx < GGML_MAX_SRC; ++src_idx) {
add_reference(node->src[src_idx]);
}
add_reference(node->view_src);
}
for (size_t i = 0; i < leaves.size(); ++i) {
add_reference(leaves[i]->view_src);
}
const size_t graph_size = segment.internal_node_indices.size() + leaves.size() + 8;
ggml_init_params params = {
/*.mem_size =*/ggml_graph_overhead_custom(graph_size, false) + 1024,
/*.mem_buffer =*/nullptr,
@@ -614,13 +651,9 @@ namespace sd::ggml_graph_cut {
ggml_cgraph* segment_graph = ggml_new_graph_custom(graph_ctx, graph_size, false);
GGML_ASSERT(segment_graph != nullptr);
for (const auto& input : segment.input_refs) {
ggml_tensor* current_input = input_tensor(gf, input);
if (current_input == nullptr) {
continue;
}
for (ggml_tensor* leaf : leaves) {
GGML_ASSERT(segment_graph->n_leafs < segment_graph->size);
segment_graph->leafs[segment_graph->n_leafs++] = current_input;
segment_graph->leafs[segment_graph->n_leafs++] = leaf;
}
for (int output_node_index : segment.output_node_indices) {
@@ -751,7 +784,9 @@ namespace sd::ggml_graph_cut {
plan.has_cuts = true;
std::string full_name(node->name);
std::string payload = full_name.substr(std::strlen(GGML_RUNNER_CUT_PREFIX));
size_t prefix_len = std::strlen(GGML_RUNNER_CUT_PREFIX);
size_t suffix_len = std::strlen(GGML_RUNNER_CUT_SUFFIX);
std::string payload = full_name.substr(prefix_len, full_name.size() - prefix_len - suffix_len);
size_t sep = payload.find('|');
std::string group = sep == std::string::npos ? payload : payload.substr(0, sep);
+1
View File
@@ -68,6 +68,7 @@ namespace sd::ggml_graph_cut {
};
static constexpr const char* GGML_RUNNER_CUT_PREFIX = "ggml_runner_cut:";
static constexpr const char* GGML_RUNNER_CUT_SUFFIX = "|";
struct MaxVramAssignment {
float default_gib = 0.f;
+3 -3
View File
@@ -427,9 +427,9 @@ class TinyVideoDecoder : public UnaryBlock {
int channels[num_layers + 1] = {256, 128, 64, 64};
int patch_size = 1;
bool is_wide = false;
public:
int t_upscale = 1;
int t_upscale = 1;
TinyVideoDecoder(int z_channels = 4, int patch_size = 1, std::vector<bool> time_upscale = {false, true, true}, bool is_wide = false)
: z_channels(z_channels), patch_size(patch_size), is_wide(is_wide) {
t_upscale = 1;
@@ -663,7 +663,7 @@ public:
if (sd_version_is_minimax_h3(version)) {
return encode_h3(ctx, x);
}
auto encoder = std::dynamic_pointer_cast<TinyVideoEncoder>(blocks["encoder"]);
int64_t num_frames = x->ne[3];
+46 -22
View File
@@ -2582,27 +2582,49 @@ static sd::Tensor<float> sample_lms(denoise_cb_t model,
sd::Tensor<float> x,
const std::vector<float>& sigmas,
const SamplerExtraArgs& extra_sample_args) {
// Linear Multi-Step from https://github.com/crowsonkb/k-diffusion
// Linear Multi-Step from https://github.com/crowsonkb/k-diffusion,
// modified with "history shift" value, which seemingly needs less steps
int divisions = 1000;
int max_order = 4;
int shift = 1; // 4, 0 - original; 4, 1 - PR #1843; 3, 1 - smoother image
for (const auto& [key, value] : extra_sample_args) {
int parsed = 0;
if (key == "lms_max_order") {
if (!parse_strict_int(value, parsed)) {
LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str());
continue;
}
max_order = std::max(1, parsed);
// smaller values make the result softer, closer to Euler
// higher values need more steps
// values above 12 can produce NaNs, depending on steps and scheduler
}
if (key == "lms_shift") {
if (!parse_strict_int(value, parsed)) {
LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str());
continue;
}
shift = std::max(0, parsed);
// for a low number of steps, the value 1 works best
}
if (key == "lms_divisions") {
if (!parse_strict_int(value, parsed)) {
LOG_WARN("ignoring invalid lms extra sample arg '%s=%s'", key.c_str(), value.c_str());
continue;
}
divisions = parsed; // std::max(1, parsed);
// values above 35M produce noise, can be fixed by double precision
// values < 1 always produce noise
// values above 30M require double precision in the integrator
// (they are needless and just slow the integration down, but
// with single precision they softly produce noise
// near the 35M, it can be used for distorted generations)
}
}
LOG_DEBUG("linear multi-step sampler: integrating using %i division%s", divisions, (divisions == 1) ? "" : "s");
auto linear_multistep_coeff = [=](const int order, const int m, const int j) -> float {
if (!divisions)
return sigmas[m + 1] - sigmas[m]; // delta / 0 * 0
#define LMS_PRECISION float // double
#define LMS_PRECISION float // when divisions > 30 millions, the double precision fixes noise
const LMS_PRECISION a = sigmas[m], dx = (sigmas[m + 1] - a) / divisions, s = sigmas[m - j];
const LMS_PRECISION b0 = a + 0.5f * dx; // using Riemann middle integral
LMS_PRECISION sum = 0.0f;
@@ -2622,11 +2644,12 @@ static sd::Tensor<float> sample_lms(denoise_cb_t model,
return sum * dx;
};
const int max_order = 4;
float lms_coeff[max_order];
int steps = static_cast<int>(sigmas.size()) - 1;
max_order = std::min(max_order, steps); // history can not be larger than steps
LOG_DEBUG("linear multi-step sampler: lms_max_order = %i, lms_shift = %i, lms_divisions = %i", max_order, shift, divisions);
std::vector<float> lms_coeff(max_order);
std::vector<sd::Tensor<float>> hist = {};
int steps = static_cast<int>(sigmas.size()) - 1;
for (int i = 0; i < steps; i++) {
const float sigma = sigmas[i];
@@ -2637,25 +2660,26 @@ static sd::Tensor<float> sample_lms(denoise_cb_t model,
sd::Tensor<float> denoised = std::move(denoised_opt.pred);
const int order = std::min(max_order, i + 1);
for (int c = 0; c < order; c++) // computing coefficients
lms_coeff[c] = linear_multistep_coeff(order, i, c);
sd::Tensor<float> d_cur = (x - denoised) / sigma;
switch (order) {
case 4: // derivative + 3 history points
x += hist[hist.size() - 2] * lms_coeff[3];
case 3:
x += hist[hist.size() - 1] * lms_coeff[2];
case 2:
x += hist.back() * lms_coeff[1];
case 1:
x += d_cur * lms_coeff[0];
x += d_cur * lms_coeff[0];
if (max_order > 1) { // if max_order == 1, the history is not used (order always < 2)
int hist_size_p1 = hist.size() + 1;
if (i) { // history does not exist at 1st step
int hist_max = hist.size() - 1;
for (int c = 2; c <= order; c++)
x += hist[std::min(hist_max, hist_size_p1 - c + shift)] * lms_coeff[c - 1];
// max_order == 4 => hist[] index = 2, 1, 0
// shift == 1 => hist[] index = 2, 2, 1
}
if (hist_size_p1 == max_order) {
hist.erase(hist.begin());
}
hist.push_back(std::move(d_cur));
}
if (hist.size() == static_cast<size_t>(max_order - 1)) {
hist.erase(hist.begin());
}
hist.push_back(std::move(d_cur));
}
return x;
}
+10 -1
View File
@@ -150,6 +150,9 @@ const char* sampling_methods_str[] = {
"LMS",
};
static_assert(SAMPLE_METHOD_COUNT == sizeof(sampling_methods_str) / sizeof(sampling_methods_str[0]),
"\nnumber of elements in sampling_methods_str[] != SAMPLE_METHOD_COUNT");
/*================================================== Helper Functions ================================================*/
static bool sd_version_supports_ref_latent_img_cfg(SDVersion version) {
@@ -2312,7 +2315,7 @@ public:
return;
}
} else if (channels == 24) {
if(sd_version_is_minimax_h3(version)){
if (sd_version_is_minimax_h3(version)) {
latent_rgb_proj = minimax_latent_rgb_proj;
latent_rgb_bias = minimax_latent_rgb_bias;
} else {
@@ -3306,6 +3309,9 @@ const char* sample_method_to_str[] = {
"lms",
};
static_assert(SAMPLE_METHOD_COUNT == sizeof(sample_method_to_str) / sizeof(sample_method_to_str[0]),
"\nnumber of elements in sample_method_to_str[] != SAMPLE_METHOD_COUNT");
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];
@@ -3341,6 +3347,9 @@ const char* scheduler_to_str[] = {
"beta",
};
static_assert(SCHEDULER_COUNT == sizeof(scheduler_to_str) / sizeof(scheduler_to_str[0]),
"\nnumber of elements in scheduler_to_str[] != SCHEDULER_COUNT");
const char* sd_scheduler_name(enum scheduler_t scheduler) {
if (scheduler < SCHEDULER_COUNT) {
return scheduler_to_str[scheduler];