mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-10-02 10:24:37 +08:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
afec5051cf | ||
|
|
bd62138751 | ||
|
|
3a25179d52 |
@@ -22,9 +22,12 @@ Inference of [Stable Diffusion](https://github.com/CompVis/stable-diffusion) in
|
||||
- `Euler A`
|
||||
- `Euler`
|
||||
- `Heun`
|
||||
- `DPM2`
|
||||
- `DPM++ 2M`
|
||||
- [`DPM++ 2M v2`](https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/8457)
|
||||
- `DPM++ 2S a`
|
||||
- Cross-platform reproducibility (`--rng cuda`, consistent with the `stable-diffusion-webui GPU RNG`)
|
||||
- Embedds generation parameters into png output as webui-compatible text string
|
||||
- Supported platforms
|
||||
- Linux
|
||||
- Mac OS
|
||||
|
||||
+33
-2
@@ -77,6 +77,8 @@ const char* sample_method_str[] = {
|
||||
"euler_a",
|
||||
"euler",
|
||||
"heun",
|
||||
"dpm2",
|
||||
"dpm++2s_a",
|
||||
"dpm++2m",
|
||||
"dpm++2mv2"};
|
||||
|
||||
@@ -144,7 +146,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, dpm++2m, dpm++2mv2}\n");
|
||||
printf(" --sampling-method {euler, euler_a, heun, dpm2, dpm++2s_a, dpm++2m, dpm++2mv2}\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");
|
||||
@@ -363,6 +365,18 @@ void parse_args(int argc, const char* argv[], Option* opt) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string 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;
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
Option opt;
|
||||
parse_args(argc, argv, &opt);
|
||||
@@ -435,7 +449,24 @@ int main(int argc, const char* argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
stbi_write_png(opt.output_path.c_str(), opt.w, opt.h, 3, img.data(), 0);
|
||||
std::string parameter_string = opt.prompt + "\n";
|
||||
if (opt.negative_prompt.size() != 0) {
|
||||
parameter_string += "Negative prompt: " + opt.negative_prompt + "\n";
|
||||
}
|
||||
parameter_string += "Steps: " + std::to_string(opt.sample_steps) + ", ";
|
||||
parameter_string += "CFG scale: " + std::to_string(opt.cfg_scale) + ", ";
|
||||
parameter_string += "Seed: " + std::to_string(opt.seed) + ", ";
|
||||
parameter_string += "Size: " + std::to_string(opt.w) + "x" + std::to_string(opt.h) + ", ";
|
||||
parameter_string += "Model: " + basename(opt.model_path) + ", ";
|
||||
parameter_string += "RNG: " + std::string(rng_type_to_str[opt.rng_type]) + ", ";
|
||||
parameter_string += "Sampler: " + std::string(sample_method_str[opt.sample_method]);
|
||||
if (opt.schedule == KARRAS) {
|
||||
parameter_string += " karras";
|
||||
}
|
||||
parameter_string += ", ";
|
||||
parameter_string += "Version: stable-diffusion.cpp";
|
||||
|
||||
stbi_write_png(opt.output_path.c_str(), opt.w, opt.h, 3, img.data(), 0, parameter_string.c_str());
|
||||
printf("save result image to '%s'\n", opt.output_path.c_str());
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -173,7 +173,7 @@ STBIWDEF int stbi_write_force_png_filter;
|
||||
#endif
|
||||
|
||||
#ifndef STBI_WRITE_NO_STDIO
|
||||
STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes);
|
||||
STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes, const char* parameters = NULL);
|
||||
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);
|
||||
@@ -1125,9 +1125,10 @@ static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int
|
||||
}
|
||||
}
|
||||
|
||||
STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
|
||||
STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len, const char* parameters)
|
||||
{
|
||||
int force_filter = stbi_write_force_png_filter;
|
||||
int param_length = 0;
|
||||
int ctype[5] = { -1, 0, 4, 2, 6 };
|
||||
unsigned char sig[8] = { 137,80,78,71,13,10,26,10 };
|
||||
unsigned char *out,*o, *filt, *zlib;
|
||||
@@ -1177,10 +1178,15 @@ STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int s
|
||||
STBIW_FREE(filt);
|
||||
if (!zlib) return 0;
|
||||
|
||||
if(parameters != NULL) {
|
||||
param_length = strlen(parameters);
|
||||
param_length += strlen("parameters") + 1; // For the name and the null-byte
|
||||
}
|
||||
|
||||
// each tag requires 12 bytes of overhead
|
||||
out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12);
|
||||
out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12 + ((parameters)?(param_length+12):0));
|
||||
if (!out) return 0;
|
||||
*out_len = 8 + 12+13 + 12+zlen + 12;
|
||||
*out_len = 8 + 12+13 + 12+zlen + 12 + ((parameters)?(param_length+12):0);
|
||||
|
||||
o=out;
|
||||
STBIW_MEMMOVE(o,sig,8); o+= 8;
|
||||
@@ -1195,6 +1201,17 @@ STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int s
|
||||
*o++ = 0;
|
||||
stbiw__wpcrc(&o,13);
|
||||
|
||||
if(parameters != NULL) {
|
||||
stbiw__wp32(o, param_length);
|
||||
stbiw__wptag(o, "tEXt");
|
||||
STBIW_MEMMOVE(o, "parameters", strlen("parameters"));
|
||||
o+=strlen("parameters");
|
||||
*o++ = 0; // Null pyte separator
|
||||
STBIW_MEMMOVE(o, parameters, strlen(parameters));
|
||||
o+=strlen(parameters);
|
||||
stbiw__wpcrc(&o, param_length);
|
||||
}
|
||||
|
||||
stbiw__wp32(o, zlen);
|
||||
stbiw__wptag(o, "IDAT");
|
||||
STBIW_MEMMOVE(o, zlib, zlen);
|
||||
@@ -1212,11 +1229,11 @@ STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int s
|
||||
}
|
||||
|
||||
#ifndef STBI_WRITE_NO_STDIO
|
||||
STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes)
|
||||
STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes, const char* parameters)
|
||||
{
|
||||
FILE *f;
|
||||
int len;
|
||||
unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
|
||||
unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len, parameters);
|
||||
if (png == NULL) return 0;
|
||||
|
||||
f = stbiw__fopen(filename, "wb");
|
||||
@@ -1231,7 +1248,7 @@ STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const
|
||||
STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes)
|
||||
{
|
||||
int len;
|
||||
unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len);
|
||||
unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len, NULL);
|
||||
if (png == NULL) return 0;
|
||||
func(context, png, len);
|
||||
STBIW_FREE(png);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
*.bin
|
||||
*.ckpt
|
||||
*.safetensor
|
||||
*.safetensors
|
||||
*.log
|
||||
+27
-11
@@ -179,9 +179,9 @@ def preprocess(state_dict):
|
||||
state_dict["alphas_cumprod"] = alphas_cumprod
|
||||
|
||||
new_state_dict = {}
|
||||
for name in state_dict.keys():
|
||||
for name, w in state_dict.items():
|
||||
# ignore unused tensors
|
||||
if not isinstance(state_dict[name], torch.Tensor):
|
||||
if not isinstance(w, torch.Tensor):
|
||||
continue
|
||||
skip = False
|
||||
for unused_tensor in unused_tensors:
|
||||
@@ -190,13 +190,25 @@ def preprocess(state_dict):
|
||||
break
|
||||
if skip:
|
||||
continue
|
||||
|
||||
|
||||
# # convert BF16 to FP16
|
||||
if w.dtype == torch.bfloat16:
|
||||
w = w.to(torch.float16)
|
||||
|
||||
# convert open_clip to hf CLIPTextModel (for SD2.x)
|
||||
open_clip_to_hf_clip_model = {
|
||||
"cond_stage_model.model.ln_final.bias": "cond_stage_model.transformer.text_model.final_layer_norm.bias",
|
||||
"cond_stage_model.model.ln_final.weight": "cond_stage_model.transformer.text_model.final_layer_norm.weight",
|
||||
"cond_stage_model.model.positional_embedding": "cond_stage_model.transformer.text_model.embeddings.position_embedding.weight",
|
||||
"cond_stage_model.model.token_embedding.weight": "cond_stage_model.transformer.text_model.embeddings.token_embedding.weight",
|
||||
"first_stage_model.decoder.mid.attn_1.to_k.bias": "first_stage_model.decoder.mid.attn_1.k.bias",
|
||||
"first_stage_model.decoder.mid.attn_1.to_k.weight": "first_stage_model.decoder.mid.attn_1.k.weight",
|
||||
"first_stage_model.decoder.mid.attn_1.to_out.0.bias": "first_stage_model.decoder.mid.attn_1.proj_out.bias",
|
||||
"first_stage_model.decoder.mid.attn_1.to_out.0.weight": "first_stage_model.decoder.mid.attn_1.proj_out.weight",
|
||||
"first_stage_model.decoder.mid.attn_1.to_q.bias": "first_stage_model.decoder.mid.attn_1.q.bias",
|
||||
"first_stage_model.decoder.mid.attn_1.to_q.weight": "first_stage_model.decoder.mid.attn_1.q.weight",
|
||||
"first_stage_model.decoder.mid.attn_1.to_v.bias": "first_stage_model.decoder.mid.attn_1.v.bias",
|
||||
"first_stage_model.decoder.mid.attn_1.to_v.weight": "first_stage_model.decoder.mid.attn_1.v.weight",
|
||||
}
|
||||
open_clip_to_hk_clip_resblock = {
|
||||
"attn.out_proj.bias": "self_attn.out_proj.bias",
|
||||
@@ -214,22 +226,19 @@ def preprocess(state_dict):
|
||||
hf_clip_resblock_prefix = "cond_stage_model.transformer.text_model.encoder.layers."
|
||||
if name in open_clip_to_hf_clip_model:
|
||||
new_name = open_clip_to_hf_clip_model[name]
|
||||
new_state_dict[new_name] = state_dict[name]
|
||||
print(f"preprocess {name} => {new_name}")
|
||||
continue
|
||||
name = new_name
|
||||
if name.startswith(open_clip_resblock_prefix):
|
||||
remain = name[len(open_clip_resblock_prefix):]
|
||||
idx = remain.split(".")[0]
|
||||
suffix = remain[len(idx)+1:]
|
||||
if suffix == "attn.in_proj_weight":
|
||||
w = state_dict[name]
|
||||
w_q, w_k, w_v = w.chunk(3)
|
||||
for new_suffix, new_w in zip(["self_attn.q_proj.weight", "self_attn.k_proj.weight", "self_attn.v_proj.weight"], [w_q, w_k, w_v]):
|
||||
new_name = hf_clip_resblock_prefix + idx + "." + new_suffix
|
||||
new_state_dict[new_name] = new_w
|
||||
print(f"preprocess {name}{w.size()} => {new_name}{new_w.size()}")
|
||||
elif suffix == "attn.in_proj_bias":
|
||||
w = state_dict[name]
|
||||
w_q, w_k, w_v = w.chunk(3)
|
||||
for new_suffix, new_w in zip(["self_attn.q_proj.bias", "self_attn.k_proj.bias", "self_attn.v_proj.bias"], [w_q, w_k, w_v]):
|
||||
new_name = hf_clip_resblock_prefix + idx + "." + new_suffix
|
||||
@@ -238,20 +247,27 @@ def preprocess(state_dict):
|
||||
else:
|
||||
new_suffix = open_clip_to_hk_clip_resblock[suffix]
|
||||
new_name = hf_clip_resblock_prefix + idx + "." + new_suffix
|
||||
new_state_dict[new_name] = state_dict[name]
|
||||
new_state_dict[new_name] = w
|
||||
print(f"preprocess {name} => {new_name}")
|
||||
continue
|
||||
|
||||
# convert unet transformer linear to conv2d 1x1
|
||||
if name.startswith("model.diffusion_model.") and (name.endswith("proj_in.weight") or name.endswith("proj_out.weight")):
|
||||
w = state_dict[name]
|
||||
if len(state_dict[name].shape) == 2:
|
||||
if len(w.shape) == 2:
|
||||
new_w = w.unsqueeze(2).unsqueeze(3)
|
||||
new_state_dict[name] = new_w
|
||||
print(f"preprocess {name} {w.size()} => {name} {new_w.size()}")
|
||||
continue
|
||||
|
||||
new_state_dict[name] = state_dict[name]
|
||||
# convert vae attn block linear to conv2d 1x1
|
||||
if name.startswith("first_stage_model.") and "attn_1" in name:
|
||||
if len(w.shape) == 2:
|
||||
new_w = w.unsqueeze(2).unsqueeze(3)
|
||||
new_state_dict[name] = new_w
|
||||
print(f"preprocess {name} {w.size()} => {name} {new_w.size()}")
|
||||
continue
|
||||
|
||||
new_state_dict[name] = w
|
||||
return new_state_dict
|
||||
|
||||
def convert(model_path, out_type = None, out_file=None):
|
||||
|
||||
@@ -3706,6 +3706,137 @@ class StableDiffusionGGML {
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case DPM2: {
|
||||
LOG_INFO("sampling using DPM2 method");
|
||||
ggml_set_dynamic(ctx, false);
|
||||
struct ggml_tensor* d = ggml_dup_tensor(ctx, x);
|
||||
struct ggml_tensor* x2 = ggml_dup_tensor(ctx, x);
|
||||
ggml_set_dynamic(ctx, params.dynamic);
|
||||
|
||||
for (int i = 0; i < steps; i++) {
|
||||
// denoise
|
||||
denoise(x, sigmas[i], i + 1);
|
||||
|
||||
// d = (x - denoised) / sigma
|
||||
{
|
||||
float* vec_d = (float*)d->data;
|
||||
float* vec_x = (float*)x->data;
|
||||
float* vec_denoised = (float*)denoised->data;
|
||||
|
||||
for (int j = 0; j < ggml_nelements(x); j++) {
|
||||
vec_d[j] = (vec_x[j] - vec_denoised[j]) / sigmas[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (sigmas[i + 1] == 0) {
|
||||
// Euler step
|
||||
// x = x + d * dt
|
||||
float dt = sigmas[i + 1] - sigmas[i];
|
||||
float* vec_d = (float*)d->data;
|
||||
float* vec_x = (float*)x->data;
|
||||
|
||||
for (int j = 0; j < ggml_nelements(x); j++) {
|
||||
vec_x[j] = vec_x[j] + vec_d[j] * dt;
|
||||
}
|
||||
} else {
|
||||
// DPM-Solver-2
|
||||
float sigma_mid = exp(0.5 * (log(sigmas[i]) + log(sigmas[i + 1])));
|
||||
float dt_1 = sigma_mid - sigmas[i];
|
||||
float dt_2 = sigmas[i + 1] - sigmas[i];
|
||||
|
||||
float* vec_d = (float*)d->data;
|
||||
float* vec_x = (float*)x->data;
|
||||
float* vec_x2 = (float*)x2->data;
|
||||
for (int j = 0; j < ggml_nelements(x); j++) {
|
||||
vec_x2[j] = vec_x[j] + vec_d[j] * dt_1;
|
||||
}
|
||||
|
||||
denoise(x2, sigma_mid, i + 1);
|
||||
float* vec_denoised = (float*)denoised->data;
|
||||
for (int j = 0; j < ggml_nelements(x); j++) {
|
||||
float d2 = (vec_x2[j] - vec_denoised[j]) / sigma_mid;
|
||||
vec_x[j] = vec_x[j] + d2 * dt_2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} break;
|
||||
case DPMPP2S_A: {
|
||||
LOG_INFO("sampling using DPM++ (2s) a method");
|
||||
ggml_set_dynamic(ctx, false);
|
||||
struct ggml_tensor* noise = ggml_dup_tensor(ctx, x);
|
||||
struct ggml_tensor* d = ggml_dup_tensor(ctx, x);
|
||||
struct ggml_tensor* x2 = ggml_dup_tensor(ctx, x);
|
||||
ggml_set_dynamic(ctx, params.dynamic);
|
||||
|
||||
for (int i = 0; i < steps; i++) {
|
||||
// denoise
|
||||
denoise(x, sigmas[i], i + 1);
|
||||
|
||||
// get_ancestral_step
|
||||
float sigma_up = std::min(sigmas[i + 1],
|
||||
std::sqrt(sigmas[i + 1] * sigmas[i + 1] * (sigmas[i] * sigmas[i] - sigmas[i + 1] * sigmas[i + 1]) / (sigmas[i] * sigmas[i])));
|
||||
float sigma_down = std::sqrt(sigmas[i + 1] * sigmas[i + 1] - sigma_up * sigma_up);
|
||||
auto t_fn = [](float sigma) -> float { return -log(sigma); };
|
||||
auto sigma_fn = [](float t) -> float { return exp(-t); };
|
||||
|
||||
if (sigma_down == 0) {
|
||||
// Euler step
|
||||
float* vec_d = (float*)d->data;
|
||||
float* vec_x = (float*)x->data;
|
||||
float* vec_denoised = (float*)denoised->data;
|
||||
|
||||
for (int j = 0; j < ggml_nelements(d); j++) {
|
||||
vec_d[j] = (vec_x[j] - vec_denoised[j]) / sigmas[i];
|
||||
}
|
||||
|
||||
// TODO: If sigma_down == 0, isn't this wrong?
|
||||
// But
|
||||
// https://github.com/crowsonkb/k-diffusion/blob/master/k_diffusion/sampling.py#L525
|
||||
// has this exactly the same way.
|
||||
float dt = sigma_down - sigmas[i];
|
||||
for (int j = 0; j < ggml_nelements(d); j++) {
|
||||
vec_x[j] = vec_x[j] + vec_d[j] * dt;
|
||||
}
|
||||
} else {
|
||||
// DPM-Solver++(2S)
|
||||
float t = t_fn(sigmas[i]);
|
||||
float t_next = t_fn(sigma_down);
|
||||
float h = t_next - t;
|
||||
float s = t + 0.5 * h;
|
||||
|
||||
float* vec_d = (float*)d->data;
|
||||
float* vec_x = (float*)x->data;
|
||||
float* vec_x2 = (float*)x2->data;
|
||||
float* vec_denoised = (float*)denoised->data;
|
||||
|
||||
// First half-step
|
||||
for (int j = 0; j < ggml_nelements(x); j++) {
|
||||
vec_x2[j] = (sigma_fn(s) / sigma_fn(t)) * vec_x[j] - (exp(-h * 0.5) - 1) * vec_denoised[j];
|
||||
}
|
||||
|
||||
denoise(x2, sigmas[i + 1], i + 1);
|
||||
|
||||
// Second half-step
|
||||
for (int j = 0; j < ggml_nelements(x); j++) {
|
||||
vec_x[j] = (sigma_fn(t_next) / sigma_fn(t)) * vec_x[j] - (exp(-h) - 1) * vec_denoised[j];
|
||||
}
|
||||
}
|
||||
|
||||
// Noise addition
|
||||
if (sigmas[i + 1] > 0) {
|
||||
ggml_tensor_set_f32_randn(noise, rng);
|
||||
{
|
||||
float* vec_x = (float*)x->data;
|
||||
float* vec_noise = (float*)noise->data;
|
||||
|
||||
for (int i = 0; i < ggml_nelements(x); i++) {
|
||||
vec_x[i] = vec_x[i] + vec_noise[i] * sigma_up;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case DPMPP2M: // DPM++ (2M) from Karras et al (2022)
|
||||
{
|
||||
LOG_INFO("sampling using DPM++ (2M) method");
|
||||
|
||||
@@ -20,6 +20,8 @@ enum SampleMethod {
|
||||
EULER_A,
|
||||
EULER,
|
||||
HEUN,
|
||||
DPM2,
|
||||
DPMPP2S_A,
|
||||
DPMPP2M,
|
||||
DPMPP2Mv2,
|
||||
N_SAMPLE_METHODS
|
||||
|
||||
Reference in New Issue
Block a user