dikdimon commited on
Commit
d765fa4
·
verified ·
1 Parent(s): 66fad01

Upload sd-webui-chunk-weights using SD-Hub

Browse files
.gitattributes CHANGED
@@ -181,3 +181,7 @@ sd-webui-llul/images/sample3.jpg filter=lfs diff=lfs merge=lfs -text
181
  sd-webui-llul/images/sample4.jpg filter=lfs diff=lfs merge=lfs -text
182
  asymmetric-tiling-sd-webui-2.0/Руководство[[:space:]]Advanced[[:space:]]Tiling[[:space:]]v3.0[[:space:]]-[[:space:]]Новые[[:space:]]функции.pdf filter=lfs diff=lfs merge=lfs -text
183
  asymmetric-tiling-sd-webui-2.0/Руководство[[:space:]]по[[:space:]]новым[[:space:]]функциям.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
181
  sd-webui-llul/images/sample4.jpg filter=lfs diff=lfs merge=lfs -text
182
  asymmetric-tiling-sd-webui-2.0/Руководство[[:space:]]Advanced[[:space:]]Tiling[[:space:]]v3.0[[:space:]]-[[:space:]]Новые[[:space:]]функции.pdf filter=lfs diff=lfs merge=lfs -text
183
  asymmetric-tiling-sd-webui-2.0/Руководство[[:space:]]по[[:space:]]новым[[:space:]]функциям.pdf filter=lfs diff=lfs merge=lfs -text
184
+ sd-webui-chunk-weights/example/1.jpg filter=lfs diff=lfs merge=lfs -text
185
+ sd-webui-chunk-weights/example/2.jpg filter=lfs diff=lfs merge=lfs -text
186
+ sd-webui-chunk-weights/example/3.jpg filter=lfs diff=lfs merge=lfs -text
187
+ sd-webui-chunk-weights/example/4.jpg filter=lfs diff=lfs merge=lfs -text
sd-webui-chunk-weights/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ __pycache__
sd-webui-chunk-weights/LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Haoming
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
sd-webui-chunk-weights/README.md ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SD Forge Chunk Weights
2
+ This is an Extension for [Forge Classic](https://github.com/Haoming02/sd-webui-forge-classic), which allows you to control the weighting for each chunk of prompts *(**i.e.** every 75 tokens)*.
3
+
4
+ > [!Tip]
5
+ > In the WebUI, you can use the keyword **`BREAK`** to manually separate prompts into different chunks to group similar concepts together
6
+
7
+ ## How to Use
8
+ In the `Weighting` text field, enter a list of **comma-separated floats**, corresponding to the weights of each chunk in order *(the default weight is `1.0`)*
9
+
10
+ ## Examples
11
+
12
+ <table>
13
+ <tr align="center">
14
+ <td>
15
+ <img src="./example/1.jpg" width=384><br>
16
+ a photo of a dog, a house<br>
17
+ <b>Extension:</b> <code>Disabled</code>
18
+ </td>
19
+ <td>
20
+ <img src="./example/2.jpg" width=384><br>
21
+ a photo of a dog, BREAK, a house<br>
22
+ <b>Extension:</b> <code>Disabled</code>
23
+ </td>
24
+ </tr>
25
+ <tr align="center">
26
+ <td>
27
+ <img src="./example/3.jpg" width=384><br>
28
+ a photo of a dog, BREAK, a house<br>
29
+ <b>Weights:</b> <code>1.5, 1.0</code>
30
+ </td>
31
+ <td>
32
+ <img src="./example/4.jpg" width=384><br>
33
+ a photo of a dog, BREAK, a house<br>
34
+ <b>Weights:</b> <code>1.0, 1.5</code>
35
+ </td>
36
+ </tr>
37
+ </table>
38
+
39
+ <hr>
40
+
41
+ - Idea by. **[@jeanhadrien](https://github.com/jeanhadrien)** in [#89](https://github.com/Haoming02/sd-webui-forge-classic/issues/89), based on this [Extension](https://github.com/klimaleksus/stable-diffusion-webui-embedding-merge/)
sd-webui-chunk-weights/example/1.jpg ADDED

Git LFS Details

  • SHA256: ef650df9273ea36a09aa62de219f26ca0007f7facebc7ec8f002f3ae23eb6c2c
  • Pointer size: 131 Bytes
  • Size of remote file: 189 kB
sd-webui-chunk-weights/example/2.jpg ADDED

Git LFS Details

  • SHA256: 09e62f5b1764f78c85ec235ab5e863b63d0537e62469f92c3365c3c2195206a6
  • Pointer size: 131 Bytes
  • Size of remote file: 199 kB
sd-webui-chunk-weights/example/3.jpg ADDED

Git LFS Details

  • SHA256: b7a3d14ad47ad552a15412fc94667780f145eeae7d4511a9dad48c770f24e072
  • Pointer size: 131 Bytes
  • Size of remote file: 117 kB
sd-webui-chunk-weights/example/4.jpg ADDED

Git LFS Details

  • SHA256: aace8a20a06bcef678e5bca9e98c6f60d4edb92d58bc1e8156b26c676a0fc6fe
  • Pointer size: 131 Bytes
  • Size of remote file: 186 kB
sd-webui-chunk-weights/scripts/chunk_weighting.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sys
3
+ from functools import wraps
4
+ from typing import List
5
+
6
+ import gradio as gr
7
+ from modules import scripts
8
+ from modules.infotext_utils import PasteField
9
+ from modules.processing import StableDiffusionProcessing, StableDiffusionProcessingTxt2Img
10
+ from modules.script_callbacks import on_app_started, on_script_unloaded
11
+ from modules.ui_components import InputAccordion
12
+
13
+ # ==============================================================================
14
+ # ЧАСТЬ 1: ЛОГГЕР (Встроено, чтобы избежать ошибок импорта)
15
+ # ==============================================================================
16
+ class ColorCode:
17
+ RESET = "\033[0m"
18
+ BLACK = "\033[0;90m"
19
+ CYAN = "\033[0;36m"
20
+ YELLOW = "\033[0;33m"
21
+ RED = "\033[0;31m"
22
+
23
+ MAP = {
24
+ "DEBUG": BLACK,
25
+ "INFO": CYAN,
26
+ "WARNING": YELLOW,
27
+ "ERROR": RED,
28
+ }
29
+
30
+ class ColoredFormatter(logging.Formatter):
31
+ def format(self, record):
32
+ levelname = record.levelname
33
+ if levelname in ColorCode.MAP:
34
+ record.levelname = f"{ColorCode.MAP[levelname]}{levelname}{ColorCode.RESET}"
35
+ return super().format(record)
36
+
37
+ logger = logging.getLogger("ChunkWeight")
38
+ logger.setLevel(logging.INFO)
39
+ logger.propagate = False
40
+
41
+ if not logger.handlers:
42
+ handler = logging.StreamHandler(sys.stdout)
43
+ handler.setFormatter(ColoredFormatter("[%(name)s] %(levelname)s - %(message)s"))
44
+ logger.addHandler(handler)
45
+
46
+ # ==============================================================================
47
+ # ЧАСТЬ 2: СКРИПТ (Логика весов)
48
+ # ==============================================================================
49
+
50
+ # Поиск целевого класса для патчинга.
51
+ # Мы ищем базовый класс, от которого наследуются и SD1.5, и SDXL эмбеддеры.
52
+ target_classes = []
53
+
54
+ try:
55
+ from modules.sd_hijack_clip import FrozenCLIPEmbedderWithCustomWordsBase
56
+ target_classes.append(FrozenCLIPEmbedderWithCustomWordsBase)
57
+ except ImportError:
58
+ # Фолбэк для очень старых версий или если архитектура изменится
59
+ logger.warning("Base class FrozenCLIPEmbedderWithCustomWordsBase not found. Trying individual classes.")
60
+ try:
61
+ from modules.sd_hijack_clip import FrozenCLIPEmbedderWithCustomWords
62
+ target_classes.append(FrozenCLIPEmbedderWithCustomWords)
63
+ except ImportError: pass
64
+
65
+ try:
66
+ from modules.sd_hijack_open_clip import FrozenOpenCLIPEmbedderWithCustomWords
67
+ target_classes.append(FrozenOpenCLIPEmbedderWithCustomWords)
68
+ except ImportError: pass
69
+
70
+ IS_NEGATIVE_PROMPT: bool = False
71
+ WEIGHTS: List[float] = []
72
+ INDEX: int = 0
73
+ PATCHED = False
74
+
75
+ class ChunkWeight(scripts.Script):
76
+ _error_logged: bool = False
77
+
78
+ def title(self):
79
+ return "Chunk Weight"
80
+
81
+ def show(self, is_img2img):
82
+ return scripts.AlwaysVisible
83
+
84
+ def ui(self, is_img2img):
85
+ with InputAccordion(False, label=self.title()) as enable:
86
+ weights = gr.Textbox(
87
+ label="Weighting (comma separated floats)",
88
+ placeholder="1.0, 1.5, 0.8",
89
+ value="",
90
+ lines=1,
91
+ max_lines=1,
92
+ )
93
+ weights.do_not_save_to_config = True
94
+
95
+ self.infotext_fields = [PasteField(weights, "Chunk Weights")]
96
+ return [enable, weights]
97
+
98
+ def setup(self, p: "StableDiffusionProcessing", enable: bool, weights: str):
99
+ WEIGHTS.clear()
100
+ ChunkWeight._error_logged = False
101
+
102
+ if not enable:
103
+ return
104
+
105
+ # Парсинг весов
106
+ for v in weights.split(","):
107
+ v = v.strip()
108
+ if not v: continue
109
+ try:
110
+ WEIGHTS.append(float(v))
111
+ except ValueError:
112
+ logger.error(f'Failed to parse "{v}" as number...')
113
+ continue
114
+
115
+ p.extra_generation_params["Chunk Weights"] = ", ".join(str(v) for v in WEIGHTS)
116
+
117
+ # Сброс кэшей A1111. Это критически важно, иначе изменение весов
118
+ # не применится к повторным генерациям с тем же промптом.
119
+ p.cached_c = [None, None]
120
+ p.cached_uc = [None, None]
121
+
122
+ # Поддержка Hires Fix (Highres. fix кэширует свои кондишены отдельно)
123
+ if hasattr(p, 'cached_hr_c'):
124
+ p.cached_hr_c = [None, None]
125
+ p.cached_hr_uc = [None, None]
126
+
127
+ def postprocess(self, *args):
128
+ # Очистка глобальных кэшей класса после генерации для надежности
129
+ StableDiffusionProcessing.cached_c = [None, None]
130
+ StableDiffusionProcessing.cached_uc = [None, None]
131
+ StableDiffusionProcessingTxt2Img.cached_hr_c = [None, None]
132
+ StableDiffusionProcessingTxt2Img.cached_hr_uc = [None, None]
133
+
134
+
135
+ # ==============================================================================
136
+ # ЧАСТЬ 3: ПАТЧИНГ (Внедрение в ядро)
137
+ # ==============================================================================
138
+
139
+ def patch_embedder(cls):
140
+ """Патчит класс Embedder, добавляя умножение весов."""
141
+
142
+ if hasattr(cls, '_chunk_weight_patched'):
143
+ return
144
+
145
+ original_process_texts = cls.process_texts
146
+ original_process_tokens = cls.process_tokens
147
+
148
+ @wraps(original_process_texts)
149
+ def patched_process_texts(self, texts: List[str]):
150
+ global IS_NEGATIVE_PROMPT, INDEX
151
+
152
+ # A1111 передает флаг is_negative_prompt как атрибут списка texts.
153
+ # Это работает благодаря классу SdConditioning в modules/prompt_parser.py
154
+ if hasattr(texts, "is_negative_prompt"):
155
+ IS_NEGATIVE_PROMPT = texts.is_negative_prompt
156
+ else:
157
+ # Если атрибут не найден (редкий случай), считаем это позитивным промптом
158
+ # чтобы веса применились.
159
+ IS_NEGATIVE_PROMPT = False
160
+
161
+ # Сбрасываем индекс чанка перед обработкой нового текста
162
+ INDEX = 0
163
+ return original_process_texts(self, texts)
164
+
165
+ @wraps(original_process_tokens)
166
+ def patched_process_tokens(self, remade_batch_tokens: list, batch_multipliers: list):
167
+ global INDEX, WEIGHTS, IS_NEGATIVE_PROMPT
168
+
169
+ # Если веса не заданы пользователем, работаем как оригинальный метод
170
+ if not WEIGHTS:
171
+ return original_process_tokens(self, remade_batch_tokens, batch_multipliers)
172
+
173
+ # Применяем веса только к позитивным промптам
174
+ if INDEX >= 0 and not IS_NEGATIVE_PROMPT:
175
+ batches: int = len(batch_multipliers)
176
+
177
+ if INDEX < len(WEIGHTS):
178
+ current_weight = WEIGHTS[INDEX]
179
+ logger.debug(f"Applying weight {current_weight}x to Chunk {INDEX}")
180
+
181
+ for b in range(batches):
182
+ # batch_multipliers[b] - это список множителей (по умолчанию 1.0) для каждого токена
183
+ for i in range(len(batch_multipliers[b])):
184
+ batch_multipliers[b][i] *= current_weight
185
+ else:
186
+ # Если весов меньше, чем чанков, остальные получают 1.0 (стандарт)
187
+ if not ChunkWeight._error_logged:
188
+ logger.warning(f"Not enough weights provided! Chunk {INDEX} uses default weight 1.0.")
189
+ ChunkWeight._error_logged = True
190
+
191
+ # Увеличиваем индекс, так как process_tokens вызывается для каждого чанка (BREAK) по очереди
192
+ INDEX += 1
193
+
194
+ return original_process_tokens(self, remade_batch_tokens, batch_multipliers)
195
+
196
+ # Применяем подмену методов
197
+ cls.process_texts = patched_process_texts
198
+ cls.process_tokens = patched_process_tokens
199
+ cls._chunk_weight_patched = True
200
+
201
+ # Сохраняем оригиналы для возможности отключения
202
+ cls._original_process_texts = original_process_texts
203
+ cls._original_process_tokens = original_process_tokens
204
+
205
+
206
+ def apply_patches():
207
+ global PATCHED
208
+ if PATCHED: return
209
+
210
+ patched_count = 0
211
+ for cls in target_classes:
212
+ try:
213
+ patch_embedder(cls)
214
+ patched_count += 1
215
+ logger.info(f"Chunk Weight logic attached to {cls.__name__}.")
216
+ except Exception as e:
217
+ logger.error(f"Failed to patch {cls}: {e}")
218
+
219
+ if patched_count > 0:
220
+ PATCHED = True
221
+ else:
222
+ logger.error("COULD NOT FIND EMBEDDER CLASSES. Chunk Weights will not work.")
223
+
224
+
225
+ def remove_patches():
226
+ global PATCHED
227
+ if not PATCHED: return
228
+
229
+ for cls in target_classes:
230
+ if hasattr(cls, '_chunk_weight_patched'):
231
+ cls.process_texts = cls._original_process_texts
232
+ cls.process_tokens = cls._original_process_tokens
233
+ del cls._chunk_weight_patched
234
+ del cls._original_process_texts
235
+ del cls._original_process_tokens
236
+
237
+ PATCHED = False
238
+ logger.info("Chunk Weight patches removed.")
239
+
240
+ # Регистрируем коллбэки для автозапуска
241
+ on_app_started(apply_patches)
242
+ on_script_unloaded(remove_patches)