Skip to content

Commit 062490a

Browse files
akleineleejet
andauthored
feat: add SSD1B and tiny-sd support (#897)
* feat: add code and doc for running SSD1B models * Added some more lines to support SD1.x with TINY U-Nets too. * support SSD-1B.safetensors * fix sdv1.5 diffusers format loader --------- Co-authored-by: leejet <[email protected]>
1 parent faabc5a commit 062490a

File tree

6 files changed

+177
-21
lines changed

6 files changed

+177
-21
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ API and command-line option may change frequently.***
3535
- Image Models
3636
- SD1.x, SD2.x, [SD-Turbo](https://huggingface.co/stabilityai/sd-turbo)
3737
- SDXL, [SDXL-Turbo](https://huggingface.co/stabilityai/sdxl-turbo)
38+
- [some SD1.x and SDXL distilled models](./docs/distilled_sd.md)
3839
- [SD3/SD3.5](./docs/sd3.md)
3940
- [Flux-dev/Flux-schnell](./docs/flux.md)
4041
- [Chroma](./docs/chroma.md)

docs/distilled_sd.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Running distilled models: SSD1B and SD1.x with tiny U-Nets
2+
3+
## Preface
4+
5+
This kind of models have a reduced U-Net part.
6+
Unlike other SDXL models the U-Net of SSD1B has only one middle block and lesser attention layers in up and down blocks, resulting in relatively smaller files. Running these models saves more than 33% of the time. For more details, refer to Segmind's paper on https://arxiv.org/abs/2401.02677v1 .
7+
Unlike other SD 1.x models Tiny-UNet models consist of only 6 U-Net blocks, resulting in relatively smaller files (approximately 1 GB). Running these models saves almost 50% of the time. For more details, refer to the paper: https://arxiv.org/pdf/2305.15798.pdf .
8+
9+
## SSD1B
10+
11+
Unfortunately not all of this models follow the standard model parameter naming mapping.
12+
Anyway there are some very useful SSD1B models available online, such as:
13+
14+
* https://huggingface.co/segmind/SSD-1B/resolve/main/SSD-1B-A1111.safetensors
15+
* https://huggingface.co/hassenhamdi/SSD-1B-fp8_e4m3fn/resolve/main/SSD-1B_fp8_e4m3fn.safetensors
16+
17+
Also there are useful LORAs available:
18+
19+
* https://huggingface.co/seungminh/lora-swarovski-SSD-1B/resolve/main/pytorch_lora_weights.safetensors
20+
* https://huggingface.co/kylielee505/mylcmlorassd/resolve/main/pytorch_lora_weights.safetensors
21+
22+
You can use this files **out-of-the-box** - unlike models in next section.
23+
24+
25+
## SD1.x with tiny U-Nets
26+
27+
There are some Tiny SD 1.x models available online, such as:
28+
29+
* https://huggingface.co/segmind/tiny-sd
30+
* https://huggingface.co/segmind/portrait-finetuned
31+
* https://huggingface.co/nota-ai/bk-sdm-tiny
32+
33+
These models need some conversion, for example because partially tensors are **non contiguous** stored. To create a usable checkpoint file, follow these **easy** steps:
34+
35+
### Download model from Hugging Face
36+
37+
Download the model using Python on your computer, for example this way:
38+
39+
```python
40+
import torch
41+
from diffusers import StableDiffusionPipeline
42+
pipe = StableDiffusionPipeline.from_pretrained("segmind/tiny-sd")
43+
unet=pipe.unet
44+
for param in unet.parameters():
45+
param.data = param.data.contiguous() # <- important here
46+
pipe.save_pretrained("segmindtiny-sd", safe_serialization=True)
47+
```
48+
49+
### Convert that to a ckpt file
50+
51+
To convert the downloaded model to a checkpoint file, you need another Python script. Download the conversion script from here:
52+
53+
* https://raw.githubusercontent.com/huggingface/diffusers/refs/heads/main/scripts/convert_diffusers_to_original_stable_diffusion.py
54+
55+
56+
### Run convert script
57+
58+
Now, run that conversion script:
59+
60+
```bash
61+
python convert_diffusers_to_original_stable_diffusion.py \
62+
--model_path ./segmindtiny-sd \
63+
--checkpoint_path ./segmind_tiny-sd.ckpt --half
64+
```
65+
66+
The file **segmind_tiny-sd.ckpt** will be generated and is now ready to use with sd.cpp
67+
68+
You can follow a similar process for other models mentioned above from Hugging Face.
69+
70+
71+
### Another ckpt file on the net
72+
73+
There is another model file available online:
74+
75+
* https://huggingface.co/ClashSAN/small-sd/resolve/main/tinySDdistilled.ckpt
76+
77+
If you want to use that, you have to adjust some **non-contiguous tensors** first:
78+
79+
```python
80+
import torch
81+
ckpt = torch.load("tinySDdistilled.ckpt", map_location=torch.device('cpu'))
82+
for key, value in ckpt['state_dict'].items():
83+
if isinstance(value, torch.Tensor):
84+
ckpt['state_dict'][key] = value.contiguous()
85+
torch.save(ckpt, "tinySDdistilled_fixed.ckpt")
86+
```

model.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,10 @@ std::string convert_cond_model_name(const std::string& name) {
330330
return new_name;
331331
}
332332

333+
if (new_name == "model.text_projection.weight") {
334+
new_name = "transformer.text_model.text_projection";
335+
}
336+
333337
if (open_clip_to_hf_clip_model.find(new_name) != open_clip_to_hf_clip_model.end()) {
334338
new_name = open_clip_to_hf_clip_model[new_name];
335339
}
@@ -623,6 +627,14 @@ std::string convert_tensor_name(std::string name) {
623627
if (starts_with(name, "diffusion_model")) {
624628
name = "model." + name;
625629
}
630+
if (starts_with(name, "model.diffusion_model.up_blocks.0.attentions.0.")) {
631+
name.replace(0, sizeof("model.diffusion_model.up_blocks.0.attentions.0.") - 1,
632+
"model.diffusion_model.output_blocks.0.1.");
633+
}
634+
if (starts_with(name, "model.diffusion_model.up_blocks.0.attentions.1.")) {
635+
name.replace(0, sizeof("model.diffusion_model.up_blocks.0.attentions.1.") - 1,
636+
"model.diffusion_model.output_blocks.1.1.");
637+
}
626638
// size_t pos = name.find("lora_A");
627639
// if (pos != std::string::npos) {
628640
// name.replace(pos, strlen("lora_A"), "lora_up");
@@ -1776,6 +1788,7 @@ SDVersion ModelLoader::get_sd_version() {
17761788
bool is_wan = false;
17771789
int64_t patch_embedding_channels = 0;
17781790
bool has_img_emb = false;
1791+
bool has_middle_block_1 = false;
17791792

17801793
for (auto& tensor_storage : tensor_storages) {
17811794
if (!(is_xl || is_flux)) {
@@ -1822,6 +1835,10 @@ SDVersion ModelLoader::get_sd_version() {
18221835
return VERSION_SVD;
18231836
}
18241837
}
1838+
if (tensor_storage.name.find("model.diffusion_model.middle_block.1.") != std::string::npos ||
1839+
tensor_storage.name.find("unet.mid_block.resnets.1.") != std::string::npos) {
1840+
has_middle_block_1 = true;
1841+
}
18251842
if (tensor_storage.name == "cond_stage_model.transformer.text_model.embeddings.token_embedding.weight" ||
18261843
tensor_storage.name == "cond_stage_model.model.token_embedding.weight" ||
18271844
tensor_storage.name == "text_model.embeddings.token_embedding.weight" ||
@@ -1834,7 +1851,7 @@ SDVersion ModelLoader::get_sd_version() {
18341851
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") {
18351852
input_block_weight = tensor_storage;
18361853
input_block_checked = true;
1837-
if (is_xl || is_flux) {
1854+
if (is_flux) {
18381855
break;
18391856
}
18401857
}
@@ -1858,6 +1875,9 @@ SDVersion ModelLoader::get_sd_version() {
18581875
if (is_ip2p) {
18591876
return VERSION_SDXL_PIX2PIX;
18601877
}
1878+
if (!has_middle_block_1) {
1879+
return VERSION_SDXL_SSD1B;
1880+
}
18611881
return VERSION_SDXL;
18621882
}
18631883

@@ -1881,6 +1901,9 @@ SDVersion ModelLoader::get_sd_version() {
18811901
if (is_ip2p) {
18821902
return VERSION_SD1_PIX2PIX;
18831903
}
1904+
if (!has_middle_block_1) {
1905+
return VERSION_SD1_TINY_UNET;
1906+
}
18841907
return VERSION_SD1;
18851908
} else if (token_embedding_weight.ne[0] == 1024) {
18861909
if (is_inpaint) {

model.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ enum SDVersion {
2323
VERSION_SD1,
2424
VERSION_SD1_INPAINT,
2525
VERSION_SD1_PIX2PIX,
26+
VERSION_SD1_TINY_UNET,
2627
VERSION_SD2,
2728
VERSION_SD2_INPAINT,
2829
VERSION_SDXL,
2930
VERSION_SDXL_INPAINT,
3031
VERSION_SDXL_PIX2PIX,
32+
VERSION_SDXL_SSD1B,
3133
VERSION_SVD,
3234
VERSION_SD3,
3335
VERSION_FLUX,
@@ -42,7 +44,7 @@ enum SDVersion {
4244
};
4345

4446
static inline bool sd_version_is_sd1(SDVersion version) {
45-
if (version == VERSION_SD1 || version == VERSION_SD1_INPAINT || version == VERSION_SD1_PIX2PIX) {
47+
if (version == VERSION_SD1 || version == VERSION_SD1_INPAINT || version == VERSION_SD1_PIX2PIX || version == VERSION_SD1_TINY_UNET) {
4648
return true;
4749
}
4850
return false;
@@ -56,7 +58,7 @@ static inline bool sd_version_is_sd2(SDVersion version) {
5658
}
5759

5860
static inline bool sd_version_is_sdxl(SDVersion version) {
59-
if (version == VERSION_SDXL || version == VERSION_SDXL_INPAINT || version == VERSION_SDXL_PIX2PIX) {
61+
if (version == VERSION_SDXL || version == VERSION_SDXL_INPAINT || version == VERSION_SDXL_PIX2PIX || version == VERSION_SDXL_SSD1B) {
6062
return true;
6163
}
6264
return false;

stable-diffusion.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ const char* model_version_to_str[] = {
2828
"SD 1.x",
2929
"SD 1.x Inpaint",
3030
"Instruct-Pix2Pix",
31+
"SD 1.x Tiny UNet",
3132
"SD 2.x",
3233
"SD 2.x Inpaint",
3334
"SDXL",
3435
"SDXL Inpaint",
3536
"SDXL Instruct-Pix2Pix",
37+
"SDXL (SSD1B)",
3638
"SVD",
3739
"SD3.x",
3840
"Flux",

unet.hpp

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ class UnetModelBlock : public GGMLBlock {
204204
adm_in_channels = 768;
205205
num_head_channels = 64;
206206
num_heads = -1;
207+
} else if (version == VERSION_SD1_TINY_UNET) {
208+
num_res_blocks = 1;
209+
channel_mult = {1, 2, 4};
207210
}
208211
if (sd_version_is_inpaint(version)) {
209212
in_channels = 9;
@@ -270,13 +273,22 @@ class UnetModelBlock : public GGMLBlock {
270273
n_head = ch / d_head;
271274
}
272275
std::string name = "input_blocks." + std::to_string(input_block_idx) + ".1";
273-
blocks[name] = std::shared_ptr<GGMLBlock>(get_attention_layer(ch,
274-
n_head,
275-
d_head,
276-
transformer_depth[i],
277-
context_dim));
276+
int td = transformer_depth[i];
277+
if (version == VERSION_SDXL_SSD1B) {
278+
if (i == 2) {
279+
td = 4;
280+
}
281+
}
282+
blocks[name] = std::shared_ptr<GGMLBlock>(get_attention_layer(ch,
283+
n_head,
284+
d_head,
285+
td,
286+
context_dim));
278287
}
279288
input_block_chans.push_back(ch);
289+
if (version == VERSION_SD1_TINY_UNET) {
290+
input_block_idx++;
291+
}
280292
}
281293
if (i != len_mults - 1) {
282294
input_block_idx += 1;
@@ -295,14 +307,17 @@ class UnetModelBlock : public GGMLBlock {
295307
d_head = num_head_channels;
296308
n_head = ch / d_head;
297309
}
298-
blocks["middle_block.0"] = std::shared_ptr<GGMLBlock>(get_resblock(ch, time_embed_dim, ch));
299-
blocks["middle_block.1"] = std::shared_ptr<GGMLBlock>(get_attention_layer(ch,
300-
n_head,
301-
d_head,
302-
transformer_depth[transformer_depth.size() - 1],
303-
context_dim));
304-
blocks["middle_block.2"] = std::shared_ptr<GGMLBlock>(get_resblock(ch, time_embed_dim, ch));
305-
310+
if (version != VERSION_SD1_TINY_UNET) {
311+
blocks["middle_block.0"] = std::shared_ptr<GGMLBlock>(get_resblock(ch, time_embed_dim, ch));
312+
if (version != VERSION_SDXL_SSD1B) {
313+
blocks["middle_block.1"] = std::shared_ptr<GGMLBlock>(get_attention_layer(ch,
314+
n_head,
315+
d_head,
316+
transformer_depth[transformer_depth.size() - 1],
317+
context_dim));
318+
blocks["middle_block.2"] = std::shared_ptr<GGMLBlock>(get_resblock(ch, time_embed_dim, ch));
319+
}
320+
}
306321
// output_blocks
307322
int output_block_idx = 0;
308323
for (int i = (int)len_mults - 1; i >= 0; i--) {
@@ -324,12 +339,27 @@ class UnetModelBlock : public GGMLBlock {
324339
n_head = ch / d_head;
325340
}
326341
std::string name = "output_blocks." + std::to_string(output_block_idx) + ".1";
327-
blocks[name] = std::shared_ptr<GGMLBlock>(get_attention_layer(ch, n_head, d_head, transformer_depth[i], context_dim));
342+
int td = transformer_depth[i];
343+
if (version == VERSION_SDXL_SSD1B) {
344+
if (i == 2 && (j == 0 || j == 1)) {
345+
td = 4;
346+
}
347+
if (i == 1 && (j == 1 || j == 2)) {
348+
td = 1;
349+
}
350+
}
351+
blocks[name] = std::shared_ptr<GGMLBlock>(get_attention_layer(ch, n_head, d_head, td, context_dim));
328352

329353
up_sample_idx++;
330354
}
331355

332356
if (i > 0 && j == num_res_blocks) {
357+
if (version == VERSION_SD1_TINY_UNET) {
358+
output_block_idx++;
359+
if (output_block_idx == 2) {
360+
up_sample_idx = 1;
361+
}
362+
}
333363
std::string name = "output_blocks." + std::to_string(output_block_idx) + "." + std::to_string(up_sample_idx);
334364
blocks[name] = std::shared_ptr<GGMLBlock>(new UpSampleBlock(ch, ch));
335365

@@ -463,6 +493,9 @@ class UnetModelBlock : public GGMLBlock {
463493
}
464494
hs.push_back(h);
465495
}
496+
if (version == VERSION_SD1_TINY_UNET) {
497+
input_block_idx++;
498+
}
466499
if (i != len_mults - 1) {
467500
ds *= 2;
468501
input_block_idx += 1;
@@ -477,10 +510,13 @@ class UnetModelBlock : public GGMLBlock {
477510
// [N, 4*model_channels, h/8, w/8]
478511

479512
// middle_block
480-
h = resblock_forward("middle_block.0", ctx, h, emb, num_video_frames); // [N, 4*model_channels, h/8, w/8]
481-
h = attention_layer_forward("middle_block.1", ctx, backend, h, context, num_video_frames); // [N, 4*model_channels, h/8, w/8]
482-
h = resblock_forward("middle_block.2", ctx, h, emb, num_video_frames); // [N, 4*model_channels, h/8, w/8]
483-
513+
if (version != VERSION_SD1_TINY_UNET) {
514+
h = resblock_forward("middle_block.0", ctx, h, emb, num_video_frames); // [N, 4*model_channels, h/8, w/8]
515+
if (version != VERSION_SDXL_SSD1B) {
516+
h = attention_layer_forward("middle_block.1", ctx, backend, h, context, num_video_frames); // [N, 4*model_channels, h/8, w/8]
517+
h = resblock_forward("middle_block.2", ctx, h, emb, num_video_frames); // [N, 4*model_channels, h/8, w/8]
518+
}
519+
}
484520
if (controls.size() > 0) {
485521
auto cs = ggml_scale_inplace(ctx, controls[controls.size() - 1], control_strength);
486522
h = ggml_add(ctx, h, cs); // middle control
@@ -516,6 +552,12 @@ class UnetModelBlock : public GGMLBlock {
516552
}
517553

518554
if (i > 0 && j == num_res_blocks) {
555+
if (version == VERSION_SD1_TINY_UNET) {
556+
output_block_idx++;
557+
if (output_block_idx == 2) {
558+
up_sample_idx = 1;
559+
}
560+
}
519561
std::string name = "output_blocks." + std::to_string(output_block_idx) + "." + std::to_string(up_sample_idx);
520562
auto block = std::dynamic_pointer_cast<UpSampleBlock>(blocks[name]);
521563

0 commit comments

Comments
 (0)