adamelliotfields commited on
Commit
f70898c
1 Parent(s): 6360e64

LoRA adapters

Browse files
Files changed (10) hide show
  1. .gitignore +1 -0
  2. DOCS.md +29 -20
  3. app.css +4 -0
  4. app.py +58 -6
  5. cli.py +13 -5
  6. lib/__init__.py +2 -1
  7. lib/config.py +20 -3
  8. lib/inference.py +52 -15
  9. lib/utils.py +30 -0
  10. requirements.txt +5 -3
.gitignore CHANGED
@@ -1,2 +1,3 @@
1
  __pycache__/
2
  .venv/
 
 
1
  __pycache__/
2
  .venv/
3
+ loras/
DOCS.md CHANGED
@@ -10,7 +10,7 @@ Use `+` or `-` to increase the weight of a token. The weight grows exponentially
10
 
11
  For groups of tokens, wrap them in parentheses and multiply by a float between 0 and 2. For example, `a (birthday cake)1.3 on a table` will increase the weight of both `birthday` and `cake` by 1.3x. This also means the entire scene will be more birthday-like, not just the cake. To counteract this, you can use `-` inside the parentheses on specific tokens, e.g., `a (birthday-- cake)1.3`, to reduce the birthday aspect.
12
 
13
- Note that this is also the same syntax used in [InvokeAI](https://invoke-ai.github.io/InvokeAI/features/PROMPTS/) and it differs from AUTOMATIC1111:
14
 
15
  | Compel | AUTOMATIC1111 |
16
  | ----------- | ------------- |
@@ -21,25 +21,47 @@ Note that this is also the same syntax used in [InvokeAI](https://invoke-ai.gith
21
 
22
  #### Arrays
23
 
24
- Arrays allow you to generate multiple different images from a single prompt. For example, `a [[cute,adorable]] [[cat,corgi]]` will expand into `a cute cat` and `a cute corgi`.
25
 
26
- Before generating, make sure `Images` is set to the number of images you want and keep in mind that there is a max of 4. Note that arrays in the negative prompt are ignored. This implementation was inspired by [Fooocus](https://github.com/lllyasviel/Fooocus/pull/1503).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
  ### Embeddings
29
 
30
  Select one or more [textual inversion](https://huggingface.co/docs/diffusers/en/using-diffusers/textual_inversion_inference) embeddings:
31
 
32
  * [`fast_negative`](https://civitai.com/models/71961?modelVersionId=94057): all-purpose (default)
33
- * [`unrealistic_dream`](https://civitai.com/models/72437?modelVersionId=77173): realistic add-on (for RealisticVision)
34
  * [`cyberrealistic_negative`](https://civitai.com/models/77976?modelVersionId=82745): realistic add-on (for CyberRealistic)
 
 
 
35
 
36
  ### Styles
37
 
38
  [Styles](https://huggingface.co/spaces/adamelliotfields/diffusion/blob/main/data/styles.json) are prompt templates that wrap your positive and negative prompts. They were originally derived from the [twri/sdxl_prompt_styler](https://github.com/twri/sdxl_prompt_styler) Comfy node, but have since been entirely rewritten.
39
 
40
- Start by framing a simple subject like `portrait of a young adult woman` or `landscape of a mountain range`. Experiment with different styles and don't forget about the negative prompt.
41
-
42
- > NB: Most styles work best with the Dreamshaper model; however, the "Enhance" style is meant to be universal. The "Photography" styles work especially well with the realistic models.
43
 
44
  ### Scale
45
 
@@ -47,19 +69,6 @@ Rescale up to 4x using [Real-ESRGAN](https://github.com/xinntao/Real-ESRGAN) wit
47
 
48
  > NB: I find this Real-ESRGAN model to work well, so I do not use a _hi-res fix_.
49
 
50
- ### Models
51
-
52
- Each model checkpoint has a different aesthetic:
53
-
54
- * [Comfy-Org/stable-diffusion-v1-5](https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive): base
55
- * [cyberdelia/CyberRealistic_v5](https://huggingface.co/cyberdelia/CyberRealistic): photorealistic
56
- * [Lykon/dreamshaper-8](https://huggingface.co/Lykon/dreamshaper-8): general purpose (default)
57
- * [fluently/Fluently-v4](https://huggingface.co/fluently/Fluently-v4): general purpose
58
- * [Linaqruf/anything-v3-1](https://huggingface.co/Linaqruf/anything-v3-1): anime
59
- * [prompthero/openjourney-v4](https://huggingface.co/prompthero/openjourney-v4): Midjourney-like
60
- * [SG161222/Realistic_Vision_v5.1](https://huggingface.co/SG161222/Realistic_Vision_V5.1_noVAE): photorealistic
61
- * [XpucT/Deliberate_v6](https://huggingface.co/XpucT/Deliberate): general purpose
62
-
63
  ### Image-to-Image
64
 
65
  The `🖼️ Image` tab enables the image-to-image and IP-Adapter pipelines. Either use the image input or select a generation from the gallery. To disable, simply clear the image input (the `x` overlay button).
 
10
 
11
  For groups of tokens, wrap them in parentheses and multiply by a float between 0 and 2. For example, `a (birthday cake)1.3 on a table` will increase the weight of both `birthday` and `cake` by 1.3x. This also means the entire scene will be more birthday-like, not just the cake. To counteract this, you can use `-` inside the parentheses on specific tokens, e.g., `a (birthday-- cake)1.3`, to reduce the birthday aspect.
12
 
13
+ This is the same syntax used in [InvokeAI](https://invoke-ai.github.io/InvokeAI/features/PROMPTS/) and it differs from AUTOMATIC1111:
14
 
15
  | Compel | AUTOMATIC1111 |
16
  | ----------- | ------------- |
 
21
 
22
  #### Arrays
23
 
24
+ Arrays allow you to generate multiple different images from a single prompt. For example, `an adult [[blonde,brunette]] [[man,woman]]` will expand into **4** different prompts. This implementation was inspired by [Fooocus](https://github.com/lllyasviel/Fooocus/pull/1503).
25
 
26
+ > NB: Make sure to set `Images` to the number of images you want to generate. Otherwise, only the first prompt will be used.
27
+
28
+ ### Models
29
+
30
+ Each model checkpoint has a different aesthetic:
31
+
32
+ * [Comfy-Org/stable-diffusion-v1-5](https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive): base
33
+ * [cyberdelia/CyberRealistic_V5](https://huggingface.co/cyberdelia/CyberRealistic): realistic
34
+ * [Lykon/dreamshaper-8](https://huggingface.co/Lykon/dreamshaper-8): general purpose (default)
35
+ * [fluently/Fluently-v4](https://huggingface.co/fluently/Fluently-v4): general purpose stylized
36
+ * [Linaqruf/anything-v3-1](https://huggingface.co/Linaqruf/anything-v3-1): anime
37
+ * [prompthero/openjourney-v4](https://huggingface.co/prompthero/openjourney-v4): Midjourney art style
38
+ * [SG161222/Realistic_Vision_V5](https://huggingface.co/SG161222/Realistic_Vision_V5.1_noVAE): realistic
39
+ * [XpucT/Deliberate_v6](https://huggingface.co/XpucT/Deliberate): general purpose stylized
40
+
41
+ ### LoRA
42
+
43
+ Apply up to 2 LoRA (low-rank adaptation) adapters with adjustable strength:
44
+
45
+ * [Perfection Style](https://civitai.com/models/411088?modelVersionId=486099): attempts to improve aesthetics, use high strength
46
+ * [Detailed Style](https://civitai.com/models/421162?modelVersionId=486110): attempts to improve details, use low strength
47
+
48
+ > NB: The trigger words are automatically appended to the positive prompt for you.
49
 
50
  ### Embeddings
51
 
52
  Select one or more [textual inversion](https://huggingface.co/docs/diffusers/en/using-diffusers/textual_inversion_inference) embeddings:
53
 
54
  * [`fast_negative`](https://civitai.com/models/71961?modelVersionId=94057): all-purpose (default)
 
55
  * [`cyberrealistic_negative`](https://civitai.com/models/77976?modelVersionId=82745): realistic add-on (for CyberRealistic)
56
+ * [`unrealistic_dream`](https://civitai.com/models/72437?modelVersionId=77173): realistic add-on (for RealisticVision)
57
+
58
+ > NB: The trigger token is automatically appended to the negative prompt for you.
59
 
60
  ### Styles
61
 
62
  [Styles](https://huggingface.co/spaces/adamelliotfields/diffusion/blob/main/data/styles.json) are prompt templates that wrap your positive and negative prompts. They were originally derived from the [twri/sdxl_prompt_styler](https://github.com/twri/sdxl_prompt_styler) Comfy node, but have since been entirely rewritten.
63
 
64
+ Start by framing a simple subject like `portrait of a young adult woman` or `landscape of a mountain range` and experiment.
 
 
65
 
66
  ### Scale
67
 
 
69
 
70
  > NB: I find this Real-ESRGAN model to work well, so I do not use a _hi-res fix_.
71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  ### Image-to-Image
73
 
74
  The `🖼️ Image` tab enables the image-to-image and IP-Adapter pipelines. Either use the image input or select a generation from the gallery. To disable, simply clear the image input (the `x` overlay button).
app.css CHANGED
@@ -37,6 +37,10 @@
37
  max-height: none;
38
  }
39
 
 
 
 
 
40
  .icon-button {
41
  max-width: 42px;
42
  }
 
37
  max-height: none;
38
  }
39
 
40
+ .gap-0, .gap-0 * {
41
+ gap: 0px;
42
+ }
43
+
44
  .icon-button {
45
  max-width: 42px;
46
  }
app.py CHANGED
@@ -1,10 +1,11 @@
1
  import argparse
2
  import json
 
3
  import random
4
 
5
  import gradio as gr
6
 
7
- from lib import Config, async_call, download_repo_files, generate, read_file
8
 
9
  # the CSS `content` attribute expects a string so we need to wrap the number in quotes
10
  refresh_seed_js = """
@@ -130,9 +131,8 @@ with gr.Blocks(
130
  with gr.TabItem("⚙️ Settings"):
131
  with gr.Group():
132
  negative_prompt = gr.Textbox(
133
- value=None,
134
  label="Negative Prompt",
135
- placeholder="ugly, bad",
136
  lines=2,
137
  )
138
 
@@ -159,6 +159,7 @@ with gr.Blocks(
159
  style = gr.Dropdown(
160
  value=Config.STYLE,
161
  label="Style",
 
162
  choices=[("None", "none")]
163
  + [(styles[sid]["name"], sid) for sid in style_ids],
164
  )
@@ -171,6 +172,44 @@ with gr.Blocks(
171
  min_width=240,
172
  )
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  with gr.Row():
175
  guidance_scale = gr.Slider(
176
  value=Config.GUIDANCE_SCALE,
@@ -336,7 +375,7 @@ with gr.Blocks(
336
  columns=2,
337
  )
338
  prompt = gr.Textbox(
339
- placeholder="corgi, beach, 8k",
340
  autoscroll=False,
341
  show_label=False,
342
  label="Prompt",
@@ -448,6 +487,10 @@ with gr.Blocks(
448
  image_prompt,
449
  ip_image,
450
  ip_face,
 
 
 
 
451
  embeddings,
452
  style,
453
  seed,
@@ -475,10 +518,19 @@ if __name__ == "__main__":
475
  args = parser.parse_args()
476
 
477
  # download to hub cache
478
- for repo_id, allow_patterns in Config.DOWNLOAD_FILES.items():
479
- print(f"Downloading {repo_id}...")
480
  download_repo_files(repo_id, allow_patterns, token=Config.HF_TOKEN)
481
 
 
 
 
 
 
 
 
 
 
 
482
  # https://www.gradio.app/docs/gradio/interface#interface-queue
483
  demo.queue().launch(
484
  server_name=args.server,
 
1
  import argparse
2
  import json
3
+ import os
4
  import random
5
 
6
  import gradio as gr
7
 
8
+ from lib import Config, async_call, download_civit_file, download_repo_files, generate, read_file
9
 
10
  # the CSS `content` attribute expects a string so we need to wrap the number in quotes
11
  refresh_seed_js = """
 
131
  with gr.TabItem("⚙️ Settings"):
132
  with gr.Group():
133
  negative_prompt = gr.Textbox(
134
+ value="nsfw+",
135
  label="Negative Prompt",
 
136
  lines=2,
137
  )
138
 
 
159
  style = gr.Dropdown(
160
  value=Config.STYLE,
161
  label="Style",
162
+ min_width=240,
163
  choices=[("None", "none")]
164
  + [(styles[sid]["name"], sid) for sid in style_ids],
165
  )
 
172
  min_width=240,
173
  )
174
 
175
+ with gr.Row():
176
+ with gr.Group(elem_classes=["gap-0"]):
177
+ lora_1 = gr.Dropdown(
178
+ min_width=240,
179
+ label="LoRA #1",
180
+ value="none",
181
+ choices=[("None", "none")]
182
+ + [
183
+ (lora["name"], lora_id)
184
+ for lora_id, lora in Config.CIVIT_LORAS.items()
185
+ ],
186
+ )
187
+ lora_1_weight = gr.Slider(
188
+ value=0.0,
189
+ minimum=0.0,
190
+ maximum=1.0,
191
+ step=0.1,
192
+ show_label=False,
193
+ )
194
+ with gr.Group(elem_classes=["gap-0"]):
195
+ lora_2 = gr.Dropdown(
196
+ min_width=240,
197
+ label="LoRA #2",
198
+ value="none",
199
+ choices=[("None", "none")]
200
+ + [
201
+ (lora["name"], lora_id)
202
+ for lora_id, lora in Config.CIVIT_LORAS.items()
203
+ ],
204
+ )
205
+ lora_2_weight = gr.Slider(
206
+ value=0.0,
207
+ minimum=0.0,
208
+ maximum=1.0,
209
+ step=0.1,
210
+ show_label=False,
211
+ )
212
+
213
  with gr.Row():
214
  guidance_scale = gr.Slider(
215
  value=Config.GUIDANCE_SCALE,
 
375
  columns=2,
376
  )
377
  prompt = gr.Textbox(
378
+ placeholder="What do you want to see?",
379
  autoscroll=False,
380
  show_label=False,
381
  label="Prompt",
 
487
  image_prompt,
488
  ip_image,
489
  ip_face,
490
+ lora_1,
491
+ lora_1_weight,
492
+ lora_2,
493
+ lora_2_weight,
494
  embeddings,
495
  style,
496
  seed,
 
518
  args = parser.parse_args()
519
 
520
  # download to hub cache
521
+ for repo_id, allow_patterns in Config.HF_MODELS.items():
 
522
  download_repo_files(repo_id, allow_patterns, token=Config.HF_TOKEN)
523
 
524
+ # download civit loras
525
+ for lora_id, lora in Config.CIVIT_LORAS.items():
526
+ file_path = os.path.join(os.path.dirname(__file__), "loras")
527
+ download_civit_file(
528
+ lora_id,
529
+ lora["model_version_id"],
530
+ file_path=file_path,
531
+ token=Config.CIVIT_TOKEN,
532
+ )
533
+
534
  # https://www.gradio.app/docs/gradio/interface#interface-queue
535
  demo.queue().launch(
536
  server_name=args.server,
cli.py CHANGED
@@ -17,7 +17,7 @@ async def main():
17
  parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
18
  parser.add_argument("prompt", type=str, metavar="PROMPT")
19
  parser.add_argument("-n", "--negative", type=str, metavar="STR", default="")
20
- parser.add_argument("-e", "--embedding", type=str, metavar="STR", default=[], action="append")
21
  parser.add_argument("-s", "--seed", type=int, metavar="INT", default=Config.SEED)
22
  parser.add_argument("-i", "--images", type=int, metavar="INT", default=1)
23
  parser.add_argument("-f", "--filename", type=str, metavar="STR", default="image.png")
@@ -25,12 +25,16 @@ async def main():
25
  parser.add_argument("-h", "--height", type=int, metavar="INT", default=Config.HEIGHT)
26
  parser.add_argument("-m", "--model", type=str, metavar="STR", default=Config.MODEL)
27
  parser.add_argument("-d", "--deepcache", type=int, metavar="INT", default=Config.DEEPCACHE_INTERVAL)
 
 
 
 
28
  parser.add_argument("--scale", type=int, metavar="INT", choices=Config.SCALES, default=Config.SCALE)
29
  parser.add_argument("--style", type=str, metavar="STR", default=Config.STYLE)
30
  parser.add_argument("--scheduler", type=str, metavar="STR", default=Config.SCHEDULER)
31
  parser.add_argument("--guidance", type=float, metavar="FLOAT", default=Config.GUIDANCE_SCALE)
32
  parser.add_argument("--steps", type=int, metavar="INT", default=Config.INFERENCE_STEPS)
33
- parser.add_argument("--strength", type=float, metavar="FLOAT", default=Config.DENOISING_STRENGTH)
34
  parser.add_argument("--image", type=str, metavar="STR")
35
  parser.add_argument("--ip-image", type=str, metavar="STR")
36
  parser.add_argument("--ip-face", action="store_true")
@@ -48,7 +52,11 @@ async def main():
48
  args.image,
49
  args.ip_image,
50
  args.ip_face,
51
- args.embedding,
 
 
 
 
52
  args.style,
53
  args.seed,
54
  args.model,
@@ -57,7 +65,7 @@ async def main():
57
  args.height,
58
  args.guidance,
59
  args.steps,
60
- args.strength,
61
  args.deepcache,
62
  args.scale,
63
  args.images,
@@ -66,7 +74,7 @@ async def main():
66
  args.freeu,
67
  args.clip_skip,
68
  )
69
- await async_call(save_images, images, args.filename)
70
 
71
 
72
  if __name__ == "__main__":
 
17
  parser = argparse.ArgumentParser(add_help=False, allow_abbrev=False)
18
  parser.add_argument("prompt", type=str, metavar="PROMPT")
19
  parser.add_argument("-n", "--negative", type=str, metavar="STR", default="")
20
+ parser.add_argument("-e", "--embeddings", type=str, metavar="STR", default="")
21
  parser.add_argument("-s", "--seed", type=int, metavar="INT", default=Config.SEED)
22
  parser.add_argument("-i", "--images", type=int, metavar="INT", default=1)
23
  parser.add_argument("-f", "--filename", type=str, metavar="STR", default="image.png")
 
25
  parser.add_argument("-h", "--height", type=int, metavar="INT", default=Config.HEIGHT)
26
  parser.add_argument("-m", "--model", type=str, metavar="STR", default=Config.MODEL)
27
  parser.add_argument("-d", "--deepcache", type=int, metavar="INT", default=Config.DEEPCACHE_INTERVAL)
28
+ parser.add_argument("--lora-1", type=str, metavar="STR", default="")
29
+ parser.add_argument("--lora-1-weight", type=float, metavar="FLOAT", default=0.0)
30
+ parser.add_argument("--lora-2", type=str, metavar="STR", default="")
31
+ parser.add_argument("--lora-2-weight", type=float, metavar="FLOAT", default=0.0)
32
  parser.add_argument("--scale", type=int, metavar="INT", choices=Config.SCALES, default=Config.SCALE)
33
  parser.add_argument("--style", type=str, metavar="STR", default=Config.STYLE)
34
  parser.add_argument("--scheduler", type=str, metavar="STR", default=Config.SCHEDULER)
35
  parser.add_argument("--guidance", type=float, metavar="FLOAT", default=Config.GUIDANCE_SCALE)
36
  parser.add_argument("--steps", type=int, metavar="INT", default=Config.INFERENCE_STEPS)
37
+ parser.add_argument("--image-strength", type=float, metavar="FLOAT", default=Config.DENOISING_STRENGTH)
38
  parser.add_argument("--image", type=str, metavar="STR")
39
  parser.add_argument("--ip-image", type=str, metavar="STR")
40
  parser.add_argument("--ip-face", action="store_true")
 
52
  args.image,
53
  args.ip_image,
54
  args.ip_face,
55
+ args.lora_1,
56
+ args.lora_1_weight,
57
+ args.lora_2,
58
+ args.lora_2_weight,
59
+ args.embeddings.split(",") if args.embeddings else [],
60
  args.style,
61
  args.seed,
62
  args.model,
 
65
  args.height,
66
  args.guidance,
67
  args.steps,
68
+ args.image_strength,
69
  args.deepcache,
70
  args.scale,
71
  args.images,
 
74
  args.freeu,
75
  args.clip_skip,
76
  )
77
+ save_images(images, args.filename)
78
 
79
 
80
  if __name__ == "__main__":
lib/__init__.py CHANGED
@@ -2,13 +2,14 @@ from .config import Config
2
  from .inference import generate
3
  from .loader import Loader
4
  from .upscaler import RealESRGAN
5
- from .utils import async_call, download_repo_files, load_json, read_file
6
 
7
  __all__ = [
8
  "Config",
9
  "Loader",
10
  "RealESRGAN",
11
  "async_call",
 
12
  "download_repo_files",
13
  "generate",
14
  "load_json",
 
2
  from .inference import generate
3
  from .loader import Loader
4
  from .upscaler import RealESRGAN
5
+ from .utils import async_call, download_civit_file, download_repo_files, load_json, read_file
6
 
7
  __all__ = [
8
  "Config",
9
  "Loader",
10
  "RealESRGAN",
11
  "async_call",
12
+ "download_civit_file",
13
  "download_repo_files",
14
  "generate",
15
  "load_json",
lib/config.py CHANGED
@@ -14,7 +14,8 @@ from diffusers import (
14
 
15
  Config = SimpleNamespace(
16
  HF_TOKEN=os.environ.get("HF_TOKEN", None),
17
- DOWNLOAD_FILES={
 
18
  "Lykon/dreamshaper-8": [
19
  "feature_extractor/preprocessor_config.json",
20
  "safety_checker/config.json",
@@ -32,6 +33,22 @@ Config = SimpleNamespace(
32
  "model_index.json",
33
  ],
34
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  MONO_FONTS=["monospace"],
36
  SANS_FONTS=[
37
  "sans-serif",
@@ -81,8 +98,8 @@ Config = SimpleNamespace(
81
  "unrealistic_dream",
82
  ],
83
  STYLE="enhance",
84
- WIDTH=448,
85
- HEIGHT=576,
86
  NUM_IMAGES=1,
87
  SEED=-1,
88
  GUIDANCE_SCALE=5,
 
14
 
15
  Config = SimpleNamespace(
16
  HF_TOKEN=os.environ.get("HF_TOKEN", None),
17
+ CIVIT_TOKEN=os.environ.get("CIVIT_TOKEN", None),
18
+ HF_MODELS={
19
  "Lykon/dreamshaper-8": [
20
  "feature_extractor/preprocessor_config.json",
21
  "safety_checker/config.json",
 
33
  "model_index.json",
34
  ],
35
  },
36
+ CIVIT_LORAS={
37
+ # https://civitai.com/models/411088?modelVersionId=486099
38
+ "perfection_style": {
39
+ "model_id": "411088",
40
+ "model_version_id": "486099",
41
+ "name": "Perfection Style",
42
+ "trigger": "perfection style",
43
+ },
44
+ # https://civitai.com/models/421162?modelVersionId=486110
45
+ "detailed_style": {
46
+ "model_id": "421162",
47
+ "model_version_id": "486110",
48
+ "name": "Detailed Style",
49
+ "trigger": "detailed style",
50
+ },
51
+ },
52
  MONO_FONTS=["monospace"],
53
  SANS_FONTS=[
54
  "sans-serif",
 
98
  "unrealistic_dream",
99
  ],
100
  STYLE="enhance",
101
+ WIDTH=512,
102
+ HEIGHT=512,
103
  NUM_IMAGES=1,
104
  SEED=-1,
105
  GUIDANCE_SCALE=5,
lib/inference.py CHANGED
@@ -13,6 +13,7 @@ from compel.prompt_parser import PromptParser
13
  from huggingface_hub.utils import HFValidationError, RepositoryNotFoundError
14
  from PIL import Image
15
 
 
16
  from .loader import Loader
17
  from .utils import load_json
18
 
@@ -39,7 +40,7 @@ def parse_prompt_with_arrays(prompt: str) -> list[str]:
39
  return prompts
40
 
41
 
42
- def apply_style(positive_prompt, negative_prompt, style_id):
43
  if style_id.lower() == "none":
44
  return (positive_prompt, negative_prompt)
45
 
@@ -96,6 +97,10 @@ def generate(
96
  image_prompt=None,
97
  ip_image=None,
98
  ip_face=False,
 
 
 
 
99
  embeddings=[],
100
  style=None,
101
  seed=None,
@@ -176,7 +181,7 @@ def generate(
176
  )
177
 
178
  if loader.pipe is None:
179
- raise Error(f"RuntimeError: Error loading {model}")
180
 
181
  pipe = loader.pipe
182
  upscaler = None
@@ -186,9 +191,36 @@ def generate(
186
  if scale == 4:
187
  upscaler = loader.upscaler_4x
188
 
189
- embeddings_tokens = []
190
- embeddings_dir = os.path.join(os.path.dirname(__file__), "..", "embeddings")
191
- embeddings_dir = os.path.abspath(embeddings_dir)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  for embedding in embeddings:
193
  try:
194
  # wrap embeddings in angle brackets
@@ -196,9 +228,8 @@ def generate(
196
  pretrained_model_name_or_path=f"{embeddings_dir}/{embedding}.pt",
197
  token=f"<{embedding}>",
198
  )
199
- embeddings_tokens.append(f"<{embedding}>")
200
  except (EnvironmentError, HFValidationError, RepositoryNotFoundError):
201
- raise Error(f"Invalid embedding: <{embedding}>")
202
 
203
  # prompt embeds
204
  compel = Compel(
@@ -212,7 +243,6 @@ def generate(
212
 
213
  images = []
214
  current_seed = seed
215
-
216
  for i in range(num_images):
217
  # seeded generator for each iteration
218
  generator = torch.Generator(device=pipe.device).manual_seed(current_seed)
@@ -229,14 +259,18 @@ def generate(
229
  if negative_styled.startswith("(), "):
230
  negative_styled = negative_styled[4:]
231
 
232
- if embeddings_tokens:
233
- negative_styled += ", " + ", ".join(embeddings_tokens)
 
 
 
234
 
 
235
  positive_embeds, negative_embeds = compel.pad_conditioning_tensors_to_same_length(
236
  [compel(positive_styled), compel(negative_styled)]
237
  )
238
  except PromptParser.ParsingException:
239
- raise Error("ValueError: Invalid prompt")
240
 
241
  kwargs = {
242
  "width": width,
@@ -244,8 +278,8 @@ def generate(
244
  "generator": generator,
245
  "prompt_embeds": positive_embeds,
246
  "guidance_scale": guidance_scale,
247
- "negative_prompt_embeds": negative_embeds,
248
  "num_inference_steps": inference_steps,
 
249
  "output_type": "np" if scale > 1 else "pil",
250
  }
251
 
@@ -257,7 +291,7 @@ def generate(
257
  kwargs["image"] = prepare_image(image_prompt, (width, height))
258
 
259
  if IP_ADAPTER:
260
- # don't resize full-face images
261
  size = None if ip_face else (width, height)
262
  kwargs["ip_adapter_image"] = prepare_image(ip_image, size)
263
 
@@ -268,9 +302,12 @@ def generate(
268
  images.append((image, str(current_seed)))
269
  current_seed += 1
270
  except Exception as e:
271
- raise Error(f"RuntimeError: {e}")
272
  finally:
273
- pipe.unload_textual_inversion()
 
 
 
274
  CURRENT_STEP = 0
275
  CURRENT_IMAGE += 1
276
 
 
13
  from huggingface_hub.utils import HFValidationError, RepositoryNotFoundError
14
  from PIL import Image
15
 
16
+ from .config import Config
17
  from .loader import Loader
18
  from .utils import load_json
19
 
 
40
  return prompts
41
 
42
 
43
+ def apply_style(positive_prompt, negative_prompt, style_id="none"):
44
  if style_id.lower() == "none":
45
  return (positive_prompt, negative_prompt)
46
 
 
97
  image_prompt=None,
98
  ip_image=None,
99
  ip_face=False,
100
+ lora_1=None,
101
+ lora_1_weight=0.0,
102
+ lora_2=None,
103
+ lora_2_weight=0.0,
104
  embeddings=[],
105
  style=None,
106
  seed=None,
 
181
  )
182
 
183
  if loader.pipe is None:
184
+ raise Error(f"Error loading {model}")
185
 
186
  pipe = loader.pipe
187
  upscaler = None
 
191
  if scale == 4:
192
  upscaler = loader.upscaler_4x
193
 
194
+ # load loras
195
+ loras = []
196
+ weights = []
197
+ loras_and_weights = [(lora_1, lora_1_weight), (lora_2, lora_2_weight)]
198
+ loras_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "loras"))
199
+ for lora, weight in loras_and_weights:
200
+ if lora and lora.lower() != "none" and lora not in loras:
201
+ config = Config.CIVIT_LORAS.get(lora)
202
+ if config:
203
+ try:
204
+ pipe.load_lora_weights(
205
+ loras_dir,
206
+ adapter_name=lora,
207
+ weight_name=f"{lora}.{config['model_version_id']}.safetensors",
208
+ )
209
+ weights.append(weight)
210
+ loras.append(lora)
211
+ except Exception:
212
+ raise Error(f"Error loading {config['name']} LoRA")
213
+
214
+ # unload after generating or if there was an error
215
+ try:
216
+ if loras:
217
+ pipe.set_adapters(loras, adapter_weights=weights)
218
+ except Exception:
219
+ pipe.unload_lora_weights()
220
+ raise Error("Error setting LoRA weights")
221
+
222
+ # load embeddings
223
+ embeddings_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "embeddings"))
224
  for embedding in embeddings:
225
  try:
226
  # wrap embeddings in angle brackets
 
228
  pretrained_model_name_or_path=f"{embeddings_dir}/{embedding}.pt",
229
  token=f"<{embedding}>",
230
  )
 
231
  except (EnvironmentError, HFValidationError, RepositoryNotFoundError):
232
+ raise Error(f"Invalid embedding: {embedding}")
233
 
234
  # prompt embeds
235
  compel = Compel(
 
243
 
244
  images = []
245
  current_seed = seed
 
246
  for i in range(num_images):
247
  # seeded generator for each iteration
248
  generator = torch.Generator(device=pipe.device).manual_seed(current_seed)
 
259
  if negative_styled.startswith("(), "):
260
  negative_styled = negative_styled[4:]
261
 
262
+ for lora in loras:
263
+ positive_styled += f", {Config.CIVIT_LORAS[lora]['trigger']}"
264
+
265
+ for embedding in embeddings:
266
+ negative_styled += f", <{embedding}>"
267
 
268
+ # print prompts
269
  positive_embeds, negative_embeds = compel.pad_conditioning_tensors_to_same_length(
270
  [compel(positive_styled), compel(negative_styled)]
271
  )
272
  except PromptParser.ParsingException:
273
+ raise Error("Invalid prompt")
274
 
275
  kwargs = {
276
  "width": width,
 
278
  "generator": generator,
279
  "prompt_embeds": positive_embeds,
280
  "guidance_scale": guidance_scale,
 
281
  "num_inference_steps": inference_steps,
282
+ "negative_prompt_embeds": negative_embeds,
283
  "output_type": "np" if scale > 1 else "pil",
284
  }
285
 
 
291
  kwargs["image"] = prepare_image(image_prompt, (width, height))
292
 
293
  if IP_ADAPTER:
294
+ # don't resize full-face images since they are usually square crops
295
  size = None if ip_face else (width, height)
296
  kwargs["ip_adapter_image"] = prepare_image(ip_image, size)
297
 
 
302
  images.append((image, str(current_seed)))
303
  current_seed += 1
304
  except Exception as e:
305
+ raise Error(f"{e}")
306
  finally:
307
+ if embeddings:
308
+ pipe.unload_textual_inversion()
309
+ if loras:
310
+ pipe.unload_lora_weights()
311
  CURRENT_STEP = 0
312
  CURRENT_IMAGE += 1
313
 
lib/utils.py CHANGED
@@ -1,9 +1,11 @@
1
  import functools
2
  import inspect
3
  import json
 
4
  from typing import Callable, TypeVar
5
 
6
  import anyio
 
7
  from anyio import Semaphore
8
  from huggingface_hub._snapshot_download import snapshot_download
9
  from typing_extensions import ParamSpec
@@ -38,6 +40,34 @@ def download_repo_files(repo_id, allow_patterns, token=None):
38
  )
39
 
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  # like the original but supports args and kwargs instead of a dict
42
  # https://github.com/huggingface/huggingface-inference-toolkit/blob/0.2.0/src/huggingface_inference_toolkit/async_utils.py
43
  async def async_call(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
 
1
  import functools
2
  import inspect
3
  import json
4
+ import os
5
  from typing import Callable, TypeVar
6
 
7
  import anyio
8
+ import httpx
9
  from anyio import Semaphore
10
  from huggingface_hub._snapshot_download import snapshot_download
11
  from typing_extensions import ParamSpec
 
40
  )
41
 
42
 
43
+ def download_civit_file(lora_id, version_id, file_path=".", token=None):
44
+ base_url = "https://civitai.com/api/download/models"
45
+ file = f"{file_path}/{lora_id}.{version_id}.safetensors"
46
+
47
+ if os.path.exists(file):
48
+ return
49
+
50
+ try:
51
+ params = {"token": token}
52
+ response = httpx.get(
53
+ f"{base_url}/{version_id}",
54
+ timeout=None,
55
+ params=params,
56
+ follow_redirects=True,
57
+ )
58
+
59
+ response.raise_for_status()
60
+ os.makedirs(file_path, exist_ok=True)
61
+
62
+ with open(file, "wb") as f:
63
+ f.write(response.content)
64
+ except httpx.HTTPStatusError as e:
65
+ print(e.request.url)
66
+ print(f"HTTPError: {e.response.status_code} {e.response.text}")
67
+ except httpx.RequestError as e:
68
+ print(f"RequestError: {e}")
69
+
70
+
71
  # like the original but supports args and kwargs instead of a dict
72
  # https://github.com/huggingface/huggingface-inference-toolkit/blob/0.2.0/src/huggingface_inference_toolkit/async_utils.py
73
  async def async_call(fn: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
requirements.txt CHANGED
@@ -1,11 +1,13 @@
1
- anyio==4.4.0
2
  accelerate
3
- einops==0.8.0
4
  compel==2.0.3
5
  deepcache==0.1.1
6
  diffusers==0.30.2
7
- hf-transfer
8
  gradio==4.41.0
 
 
 
9
  numpy==1.26.4
10
  ruff==0.5.7
11
  spaces
 
 
1
  accelerate
2
+ anyio==4.4.0
3
  compel==2.0.3
4
  deepcache==0.1.1
5
  diffusers==0.30.2
6
+ einops==0.8.0
7
  gradio==4.41.0
8
+ h2
9
+ hf-transfer
10
+ httpx
11
  numpy==1.26.4
12
  ruff==0.5.7
13
  spaces