dikdimon commited on
Commit
0e80345
·
verified ·
1 Parent(s): 5258f22

Upload sd-webui-kohya-hiresfix-saveable2.2 using SD-Hub

Browse files
sd-webui-kohya-hiresfix-saveable2.2/.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
sd-webui-kohya-hiresfix-saveable2.2/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+
2
+ *.pyc
3
+ config.yaml
sd-webui-kohya-hiresfix-saveable2.2/README.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # Implementation Kohya Hires.fix for Auto1111 webui
2
+
3
+ #### Stop step - at which sampling step disable fix, increase at higher resolution
4
+ #### Depth - on which layer fix will be applied
5
+ #### Downsampling scale - decrease at higher resolution, helps preserve composition at very high resolutions
6
+ #### Upsampling scale - increasing can slightly improve quality at cost of VRAM
7
+
8
+ ## Source:
9
+ https://gist.github.com/kohya-ss/3f774da220df102548093a7abc8538ed
sd-webui-kohya-hiresfix-saveable2.2/scripts/__pycache__/khrfix.cpython-310.pyc ADDED
Binary file (27.8 kB). View file
 
sd-webui-kohya-hiresfix-saveable2.2/scripts/khrfix.py ADDED
@@ -0,0 +1,1134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # kohya_hires_fix_unified.py
2
+ # Версия: 2.1 (Unified)
3
+ # Совместимость: A1111 / modules.scripts API, PyTorch >= 1.12
4
+ # Объединяет функционал Kohya Hires Fix RU+ и Original Legacy Mode
5
+
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+ from typing import Any, Dict, List, Optional, Tuple
10
+
11
+ import gradio as gr
12
+ import torch
13
+ import torch.nn.functional as F
14
+ from omegaconf import DictConfig, OmegaConf
15
+ from modules import scripts, script_callbacks
16
+
17
+ CONFIG_PATH = Path(__file__).with_suffix(".yaml")
18
+ PRESETS_PATH = Path(__file__).with_name(Path(__file__).stem + ".presets.yaml")
19
+
20
+ # ---- Предустановленные разрешения ----
21
+
22
+ RESOLUTION_GROUPS = {
23
+ "Квадрат": [(1024, 1024)],
24
+ "Портрет": [(640, 1536), (768, 1344), (832, 1216), (896, 1152), (768, 1152)],
25
+ "Альбом": [(1536, 640), (1344, 768), (1216, 832), (1152, 896), (1024, 1536)],
26
+ }
27
+ RESOLUTION_CHOICES: List[str] = ["— не применять —"]
28
+ for group, dims in RESOLUTION_GROUPS.items():
29
+ for w, h in dims:
30
+ RESOLUTION_CHOICES.append(f"{group}: {w}x{h}")
31
+
32
+
33
+ def parse_resolution_label(label: str) -> Optional[Tuple[int, int]]:
34
+ if not label or label.startswith("—"):
35
+ return None
36
+ try:
37
+ _, wh = label.split(":")
38
+ w, h = wh.strip().lower().split("x")
39
+ return int(w), int(h)
40
+ except Exception:
41
+ return None
42
+
43
+
44
+ # ---- Вспомогательные утилиты ----
45
+
46
+ def _safe_mode(mode: str) -> str:
47
+ if mode == "nearest-exact":
48
+ return mode
49
+ if mode in {"bicubic", "bilinear", "nearest"}:
50
+ return mode
51
+ return "bilinear"
52
+
53
+
54
+ def _load_yaml(path: Path, default: dict) -> dict:
55
+ try:
56
+ return OmegaConf.to_container(OmegaConf.load(path), resolve=True) or default
57
+ except Exception:
58
+ return default
59
+
60
+
61
+ def _atomic_save_yaml(path: Path, data: dict) -> None:
62
+ try:
63
+ tmp = path.with_suffix(path.suffix + ".tmp")
64
+ OmegaConf.save(DictConfig(data), tmp)
65
+ tmp.replace(path)
66
+ except Exception:
67
+ pass
68
+
69
+
70
+ def _load_presets() -> Dict[str, dict]:
71
+ data = _load_yaml(PRESETS_PATH, {})
72
+ return {str(k): dict(v) for k, v in data.items()}
73
+
74
+
75
+ def _save_presets(presets: Dict[str, dict]) -> None:
76
+ _atomic_save_yaml(PRESETS_PATH, presets)
77
+
78
+
79
+ def _clamp(x: float, lo: float, hi: float) -> float:
80
+ return float(max(lo, min(hi, x)))
81
+
82
+
83
+ def _norm_mode_choice(value: str, default_: str = "auto") -> str:
84
+ """Нормализация строкового значения."""
85
+ try:
86
+ v = str(value).strip().lower()
87
+ except Exception:
88
+ v = ""
89
+ if v in {"true", "t", "1", "yes", "y"}:
90
+ return "true"
91
+ if v in {"false", "f", "0", "no", "n"}:
92
+ return "false"
93
+ if v in {"auto", "a", "авто"}:
94
+ return "auto"
95
+ return str(default_).strip().lower()
96
+
97
+
98
+ def _compute_adaptive_params(
99
+ width: int,
100
+ height: int,
101
+ profile: str,
102
+ base_s1: float,
103
+ base_s2: float,
104
+ base_d1: int,
105
+ base_d2: int,
106
+ base_down: float,
107
+ base_up: float,
108
+ keep_unitary_product: bool,
109
+ ) -> Tuple[float, float, int, int, float, float]:
110
+ """Адаптировать (s1, s2, d1, d2, downscale, upscale) под MPix и аспект."""
111
+ rel_mpx = (max(1, int(width)) * max(1, int(height))) / float(1024 * 1024)
112
+ aspect = max(width, height) / float(min(width, height))
113
+
114
+ prof = (profile or "").strip().lower()
115
+ s1 = float(base_s1)
116
+ s2 = float(base_s2)
117
+ d1 = int(base_d1)
118
+ d2 = int(base_d2)
119
+ down = float(base_down)
120
+ up = float(base_up)
121
+
122
+ if prof.startswith("конс"):
123
+ s_add = -0.02
124
+ d_add = 0
125
+ elif prof.startswith("агре"):
126
+ s_add = 0.05
127
+ d_add = 1
128
+ else:
129
+ s_add = 0.0
130
+ d_add = 0
131
+
132
+ if rel_mpx >= 1.5:
133
+ s_add += 0.08
134
+ down -= 0.10
135
+ elif rel_mpx >= 1.1:
136
+ s_add += 0.05
137
+ down -= 0.05
138
+ elif rel_mpx <= 0.8:
139
+ s_add -= 0.02
140
+ down += 0.05
141
+
142
+ if aspect >= 1.6:
143
+ d_add += 1
144
+ elif aspect <= 1.1:
145
+ d_add -= 1
146
+
147
+ s1 = _clamp(s1 + s_add * 0.7, 0.0, 0.5)
148
+ s2 = _clamp(s2 + s_add, 0.0, 0.5)
149
+
150
+ d1 = max(1, d1 + d_add)
151
+ d2 = max(1, d2 + d_add)
152
+
153
+ down = _clamp(down, 0.1, 1.0)
154
+ if keep_unitary_product:
155
+ up = 1.0 / max(1e-6, down)
156
+ else:
157
+ up = _clamp(up * (base_down / max(1e-6, down)), 1.0, 4.0)
158
+
159
+ return s1, s2, d1, d2, down, up
160
+
161
+
162
+ # ---- Класс пресета ----
163
+
164
+ class HiresPreset:
165
+ def __init__(self, **kwargs: Any) -> None:
166
+ self.category: str = "Общие"
167
+ self.algo_mode: str = "Enhanced (RU+)" # Новый/старый режим
168
+
169
+ self.d1: int = 3
170
+ self.d2: int = 4
171
+ self.s1: float = 0.15
172
+ self.s2: float = 0.30
173
+
174
+ self.scaler: str = "bicubic"
175
+ self.downscale: float = 0.5
176
+ self.upscale: float = 2.0
177
+
178
+ # Два раздельных smooth_scaling
179
+ self.smooth_scaling_enh: bool = True
180
+ self.smooth_scaling_legacy: bool = True
181
+
182
+ self.smoothing_curve: str = "Линейная"
183
+
184
+ self.early_out: bool = False
185
+
186
+ # Два раздельных only_one_pass
187
+ self.only_one_pass_enh: bool = True
188
+ self.only_one_pass_legacy: bool = True
189
+
190
+ self.keep_unitary_product: bool = False
191
+ self.align_corners_mode: str = "False"
192
+ self.recompute_scale_factor_mode: str = "False"
193
+
194
+ self.resolution_choice: str = RESOLUTION_CHOICES[0]
195
+ self.apply_resolution: bool = False
196
+ self.adaptive_by_resolution: bool = True
197
+ self.adaptive_profile: str = "Сбалансированный"
198
+
199
+ # Поддержка старых ключей из YAML (smooth_scaling/only_one_pass)
200
+ legacy_smooth = kwargs.pop("smooth_scaling", None)
201
+ legacy_one = kwargs.pop("only_one_pass", None)
202
+
203
+ for k, v in kwargs.items():
204
+ if hasattr(self, k):
205
+ setattr(self, k, v)
206
+
207
+ if legacy_smooth is not None:
208
+ self.smooth_scaling_enh = bool(legacy_smooth)
209
+ self.smooth_scaling_legacy = bool(legacy_smooth)
210
+ if legacy_one is not None:
211
+ self.only_one_pass_enh = bool(legacy_one)
212
+ self.only_one_pass_legacy = bool(legacy_one)
213
+
214
+ def to_dict(self) -> Dict[str, Any]:
215
+ return {
216
+ "category": self.category,
217
+ "algo_mode": self.algo_mode,
218
+ "d1": self.d1,
219
+ "d2": self.d2,
220
+ "s1": self.s1,
221
+ "s2": self.s2,
222
+ "scaler": self.scaler,
223
+ "downscale": self.downscale,
224
+ "upscale": self.upscale,
225
+ "smooth_scaling_enh": self.smooth_scaling_enh,
226
+ "smooth_scaling_legacy": self.smooth_scaling_legacy,
227
+ "smoothing_curve": self.smoothing_curve,
228
+ "early_out": self.early_out,
229
+ "only_one_pass_enh": self.only_one_pass_enh,
230
+ "only_one_pass_legacy": self.only_one_pass_legacy,
231
+ "keep_unitary_product": self.keep_unitary_product,
232
+ "align_corners_mode": self.align_corners_mode,
233
+ "recompute_scale_factor_mode": self.recompute_scale_factor_mode,
234
+ "resolution_choice": self.resolution_choice,
235
+ "apply_resolution": self.apply_resolution,
236
+ "adaptive_by_resolution": self.adaptive_by_resolution,
237
+ "adaptive_profile": self.adaptive_profile,
238
+ }
239
+
240
+
241
+ DEFAULT_PRESETS: Dict[str, Dict[str, Any]] = {
242
+ "XL · портрет (безопасный)": {
243
+ "category": "XL",
244
+ "algo_mode": "Enhanced (RU+)",
245
+ "resolution_choice": "Портрет: 832x1216",
246
+ "apply_resolution": True,
247
+ "adaptive_by_resolution": True,
248
+ "adaptive_profile": "Сбалансированный",
249
+ "d1": 3,
250
+ "s1": 0.18,
251
+ "d2": 5,
252
+ "s2": 0.32,
253
+ "scaler": "bicubic",
254
+ "downscale": 0.6,
255
+ "upscale": 1.8,
256
+ "smooth_scaling_enh": True,
257
+ "smooth_scaling_legacy": True,
258
+ "smoothing_curve": "Smoothstep",
259
+ "early_out": False,
260
+ "only_one_pass_enh": True,
261
+ "only_one_pass_legacy": True,
262
+ "keep_unitary_product": True,
263
+ },
264
+ "SD15 · Legacy Old Style": {
265
+ "category": "SD15",
266
+ "algo_mode": "Legacy (Original)",
267
+ "resolution_choice": "— не применять —",
268
+ "apply_resolution": False,
269
+ "adaptive_by_resolution": False,
270
+ "d1": 3,
271
+ "s1": 0.15,
272
+ "d2": 4,
273
+ "s2": 0.30,
274
+ "scaler": "bicubic",
275
+ "downscale": 0.5,
276
+ "upscale": 2.0,
277
+ "smooth_scaling_enh": True,
278
+ "smooth_scaling_legacy": True,
279
+ "early_out": False,
280
+ "only_one_pass_enh": True,
281
+ "only_one_pass_legacy": True,
282
+ },
283
+ }
284
+
285
+
286
+ class PresetManager:
287
+ def __init__(self) -> None:
288
+ self._cache: Dict[str, HiresPreset] = {}
289
+ self.reload()
290
+
291
+ def reload(self) -> None:
292
+ raw = _load_presets()
293
+ if raw is None:
294
+ raw = {}
295
+ if not raw:
296
+ raw = {name: pdata.copy() for name, pdata in DEFAULT_PRESETS.items()}
297
+
298
+ self._cache.clear()
299
+ for name, data in raw.items():
300
+ base = HiresPreset().to_dict()
301
+ if isinstance(data, dict):
302
+ base.update(data or {})
303
+ try:
304
+ self._cache[str(name)] = HiresPreset(**base)
305
+ except Exception:
306
+ continue
307
+
308
+ def _save(self) -> None:
309
+ raw = {name: preset.to_dict() for name, preset in self._cache.items()}
310
+ _save_presets(raw)
311
+
312
+ def names(self) -> List[str]:
313
+ return sorted(self._cache.keys())
314
+
315
+ def get(self, name: str) -> Optional[HiresPreset]:
316
+ return self._cache.get(name)
317
+
318
+ def upsert(self, name: str, preset: HiresPreset) -> None:
319
+ self._cache[name] = preset
320
+ self._save()
321
+
322
+ def delete(self, name: str) -> None:
323
+ if name in self._cache:
324
+ del self._cache[name]
325
+ self._save()
326
+
327
+ def categories(self) -> List[str]:
328
+ cats = {(p.category or "Общие") for p in self._cache.values()}
329
+ return sorted(cats) if cats else []
330
+
331
+ def names_for_category(self, category: Optional[str]) -> List[str]:
332
+ if not category or category == "Все":
333
+ return self.names()
334
+ cat = category or "Общие"
335
+ return sorted(
336
+ name for name, preset in self._cache.items()
337
+ if (preset.category or "Общие") == cat
338
+ )
339
+
340
+
341
+ class Scaler(torch.nn.Module):
342
+ """Универсальная обёртка. Поддерживает и логику New, и логику Old (через настройки)."""
343
+
344
+ def __init__(
345
+ self,
346
+ scale: float,
347
+ block: torch.nn.Module,
348
+ scaler: str,
349
+ align_mode: str = "false",
350
+ recompute_mode: str = "false",
351
+ ) -> None:
352
+ super().__init__()
353
+ self.scale: float = float(scale)
354
+ self.block: torch.nn.Module = block
355
+ self.scaler: str = _safe_mode(scaler)
356
+ self.align_mode: str = _norm_mode_choice(align_mode, "false")
357
+ self.recompute_mode: str = _norm_mode_choice(recompute_mode, "false")
358
+
359
+ def forward(self, x: torch.Tensor, *args, **kwargs):
360
+ if self.scale == 1.0:
361
+ return self.block(x, *args, **kwargs)
362
+
363
+ h, w = x.shape[-2:]
364
+ new_h = max(1, int(h * self.scale))
365
+ new_w = max(1, int(w * self.scale))
366
+
367
+ align_corners = None
368
+ if self.scaler in {"bilinear", "bicubic", "linear", "trilinear"}:
369
+ if self.align_mode == "true":
370
+ align_corners = True
371
+ elif self.align_mode == "false":
372
+ align_corners = False
373
+ else:
374
+ align_corners = None
375
+
376
+ x_scaled = F.interpolate(
377
+ x,
378
+ size=(new_h, new_w),
379
+ mode=self.scaler,
380
+ align_corners=align_corners,
381
+ )
382
+
383
+ out = self.block(x_scaled, *args, **kwargs)
384
+ return out
385
+
386
+
387
+ class KohyaHiresFix(scripts.Script):
388
+ def __init__(self) -> None:
389
+ super().__init__()
390
+ self.config: DictConfig = DictConfig(_load_yaml(CONFIG_PATH, {}))
391
+ self.disable: bool = False
392
+ self.step_limit: int = 0
393
+ self.infotext_fields = []
394
+ self._cb_registered: bool = False
395
+
396
+ def title(self) -> str:
397
+ return "Kohya Hires.fix · Unified"
398
+
399
+ def show(self, is_img2img: bool):
400
+ return scripts.AlwaysVisible
401
+
402
+ @staticmethod
403
+ def _unwrap_all(model) -> None:
404
+ if not model:
405
+ return
406
+ for i, b in enumerate(getattr(model, "input_blocks", [])):
407
+ if isinstance(b, Scaler):
408
+ model.input_blocks[i] = b.block
409
+ for i, b in enumerate(getattr(model, "output_blocks", [])):
410
+ if isinstance(b, Scaler):
411
+ model.output_blocks[i] = b.block
412
+
413
+ @staticmethod
414
+ def _map_output_index(model, in_idx: int, early_out: bool) -> Optional[int]:
415
+ outs = getattr(model, "output_blocks", None)
416
+ if not outs:
417
+ return None
418
+ n = len(outs)
419
+ if early_out:
420
+ return max(0, min(int(in_idx), n - 1))
421
+ mirror = (n - 1) - int(in_idx)
422
+ return max(0, min(mirror, n - 1))
423
+
424
+ def ui(self, is_img2img: bool):
425
+ self.infotext_fields = []
426
+ pm = PresetManager()
427
+ cfg = self.config
428
+
429
+ last_algo_mode = cfg.get("algo_mode", "Enhanced (RU+)")
430
+ last_resolution_choice = cfg.get("resolution_choice", RESOLUTION_CHOICES[0])
431
+ last_apply_resolution = cfg.get("apply_resolution", False)
432
+ last_adaptive_by_resolution = cfg.get("adaptive_by_resolution", True)
433
+ last_adaptive_profile = cfg.get("adaptive_profile", "Сбалансированный")
434
+ last_s1 = cfg.get("s1", 0.15)
435
+ last_s2 = cfg.get("s2", 0.30)
436
+ last_d1 = cfg.get("d1", 3)
437
+ last_d2 = cfg.get("d2", 4)
438
+ last_scaler = cfg.get("scaler", "bicubic")
439
+ last_downscale = cfg.get("downscale", 0.5)
440
+ last_upscale = cfg.get("upscale", 2.0)
441
+
442
+ # Раздельные флаги, плюс поддержка старых ключей
443
+ last_smooth_enh = cfg.get("smooth_scaling_enh", True)
444
+ last_smooth_leg = cfg.get("smooth_scaling_legacy", True)
445
+ last_only_enh = cfg.get("only_one_pass_enh", True)
446
+ last_only_leg = cfg.get("only_one_pass_legacy", True)
447
+
448
+ legacy_smooth = cfg.get("smooth_scaling", None)
449
+ if legacy_smooth is not None:
450
+ last_smooth_enh = bool(legacy_smooth)
451
+ last_smooth_leg = bool(legacy_smooth)
452
+ legacy_one = cfg.get("only_one_pass", None)
453
+ if legacy_one is not None:
454
+ last_only_enh = bool(legacy_one)
455
+ last_only_leg = bool(legacy_one)
456
+
457
+ last_smoothing_curve = cfg.get("smoothing_curve", "Линейная")
458
+ last_early_out = cfg.get("early_out", False)
459
+ last_keep1 = cfg.get("keep_unitary_product", False)
460
+ last_align_mode = cfg.get("align_corners_mode", "False")
461
+ last_recompute_mode = cfg.get("recompute_scale_factor_mode", "False")
462
+ last_simple_mode = cfg.get("simple_mode", True)
463
+
464
+ with gr.Accordion(label="Kohya Hires.fix", open=False):
465
+ with gr.Row():
466
+ enable = gr.Checkbox(label="Включить расширение", value=False)
467
+ algo_mode = gr.Radio(
468
+ choices=["Enhanced (RU+)", "Legacy (Original)"],
469
+ value=last_algo_mode,
470
+ label="Алгоритм работы / Algorithm Mode",
471
+ )
472
+
473
+ with gr.Row():
474
+ simple_mode = gr.Checkbox(
475
+ label="Простой режим (скрыть продвинутые настройки)",
476
+ value=last_simple_mode,
477
+ )
478
+
479
+ # ---- Предустановленные разрешения ----
480
+ with gr.Group():
481
+ gr.Markdown("**Предустановленные разрешения**")
482
+ with gr.Row():
483
+ resolution_choice = gr.Dropdown(
484
+ choices=RESOLUTION_CHOICES,
485
+ value=last_resolution_choice,
486
+ label="Выбрать разрешение",
487
+ )
488
+ apply_resolution = gr.Checkbox(
489
+ label="Применить разрешение к width/height",
490
+ value=last_apply_resolution,
491
+ )
492
+
493
+ # ---- Базовые параметры ----
494
+ with gr.Group():
495
+ gr.Markdown("**Базовые параметры hires.fix**")
496
+ with gr.Row():
497
+ s1 = gr.Slider(0.0, 0.5, step=0.01, label="Остановить на (доля шага) — Пара 1", value=last_s1)
498
+ d1 = gr.Slider(1, 10, step=1, label="Глубина блока — Пара 1", value=last_d1)
499
+ with gr.Row():
500
+ s2 = gr.Slider(0.0, 0.5, step=0.01, label="Остановить на (доля шага) — Пара 2", value=last_s2)
501
+ d2 = gr.Slider(1, 10, step=1, label="Глубина блока — Пара 2", value=last_d2)
502
+
503
+ with gr.Row():
504
+ scaler = gr.Dropdown(
505
+ choices=["bicubic", "bilinear", "nearest", "nearest-exact"],
506
+ label="Режим интерполяции слоя",
507
+ value=last_scaler,
508
+ )
509
+ downscale = gr.Slider(0.1, 1.0, step=0.05, label="Коэффициент даунскейла (вход)", value=last_downscale)
510
+ upscale = gr.Slider(1.0, 4.0, step=0.1, label="Коэффициент апскейла (выход)", value=last_upscale)
511
+
512
+ with gr.Row():
513
+ smooth_scaling_enh = gr.Checkbox(
514
+ label="Плавное изменение масштаба (Enhanced)",
515
+ value=last_smooth_enh,
516
+ visible=(last_algo_mode == "Enhanced (RU+)")
517
+ )
518
+ smooth_scaling_legacy = gr.Checkbox(
519
+ label="Плавное изменение масштаба (Legacy old)",
520
+ value=last_smooth_leg,
521
+ visible=(last_algo_mode == "Legacy (Original)")
522
+ )
523
+ smoothing_curve = gr.Dropdown(
524
+ choices=["Линейная", "Smoothstep"],
525
+ value=last_smoothing_curve,
526
+ label="Кривая сглаживания (только Enhanced)",
527
+ visible=True
528
+ )
529
+ keep_unitary_product = gr.Checkbox(
530
+ label="Сохранять суммарный масштаб = 1 (только Enhanced)",
531
+ value=last_keep1,
532
+ )
533
+
534
+ with gr.Row():
535
+ early_out = gr.Checkbox(label="Ранний апскейл (Early Out)", value=last_early_out)
536
+ only_one_pass_enh = gr.Checkbox(
537
+ label="Только один проход (Enhanced)",
538
+ value=last_only_enh,
539
+ visible=(last_algo_mode == "Enhanced (RU+)")
540
+ )
541
+ only_one_pass_legacy = gr.Checkbox(
542
+ label="Только один проход (Legacy old)",
543
+ value=last_only_leg,
544
+ visible=(last_algo_mode == "Legacy (Original)")
545
+ )
546
+
547
+ # ---- Продвинутые настройки ----
548
+ with gr.Group(visible=not last_simple_mode) as advanced_group:
549
+ with gr.Group():
550
+ gr.Markdown("**Интерполяция (только Enhanced)**")
551
+ with gr.Row():
552
+ align_corners_mode = gr.Dropdown(
553
+ choices=["False", "True", "Авто"],
554
+ value=last_align_mode,
555
+ label="align_corners режим",
556
+ )
557
+ recompute_scale_factor_mode = gr.Dropdown(
558
+ choices=["False", "True", "Авто"],
559
+ value=last_recompute_mode,
560
+ label="recompute_scale_factor режим",
561
+ )
562
+
563
+ with gr.Group():
564
+ gr.Markdown("**Адаптация под разрешение**")
565
+ with gr.Row():
566
+ adaptive_by_resolution = gr.Checkbox(
567
+ label="Адаптировать параметры под текущее разрешение",
568
+ value=last_adaptive_by_resolution,
569
+ )
570
+ adaptive_profile = gr.Dropdown(
571
+ choices=["Консервативный", "Сбалансированный", "Агрессивный"],
572
+ value=last_adaptive_profile,
573
+ label="Профиль адаптации",
574
+ )
575
+ preview_md = gr.Markdown("Нажмите кнопку ниже для предпросмотра параметров.")
576
+ btn_preview = gr.Button("Показать рассчитанные параметры", variant="secondary")
577
+
578
+ # Пресеты
579
+ with gr.Group():
580
+ gr.Markdown("**Пресеты**")
581
+ with gr.Row():
582
+ preset_category_filter = gr.Dropdown(
583
+ choices=["Все"] + pm.categories(),
584
+ value="Все",
585
+ label="Категория (фильтр)",
586
+ )
587
+ preset_select = gr.Dropdown(
588
+ choices=pm.names_for_category(None),
589
+ value=None,
590
+ label="Выбрать пресет",
591
+ )
592
+
593
+ with gr.Row():
594
+ preset_name = gr.Textbox(label="Имя пресета", placeholder="имя...", value="")
595
+ preset_category_input = gr.Textbox(label="Категория", placeholder="Общие...", value="Общие")
596
+
597
+ with gr.Row():
598
+ btn_save = gr.Button("Сохранить", variant="primary")
599
+ btn_load = gr.Button("Загрузить")
600
+ btn_delete = gr.Button("Удалить", variant="stop")
601
+
602
+ preset_status = gr.Markdown("")
603
+
604
+ # --- Логика UI ---
605
+
606
+ def _toggle_mode(is_simple: bool):
607
+ return gr.update(visible=not is_simple)
608
+
609
+ simple_mode.change(_toggle_mode, inputs=[simple_mode], outputs=[advanced_group])
610
+
611
+ def _toggle_algo_vis(mode: str):
612
+ is_enh = (mode == "Enhanced (RU+)")
613
+ return (
614
+ gr.update(visible=is_enh), # smooth_scaling_enh
615
+ gr.update(visible=not is_enh), # smooth_scaling_legacy
616
+ gr.update(visible=is_enh), # smoothing_curve
617
+ gr.update(visible=is_enh), # keep_unitary_product
618
+ gr.update(visible=is_enh), # align_corners_mode
619
+ gr.update(visible=is_enh), # recompute_scale_factor_mode
620
+ gr.update(visible=is_enh), # only_one_pass_enh
621
+ gr.update(visible=not is_enh), # only_one_pass_legacy
622
+ )
623
+
624
+ algo_mode.change(
625
+ _toggle_algo_vis,
626
+ inputs=[algo_mode],
627
+ outputs=[
628
+ smooth_scaling_enh,
629
+ smooth_scaling_legacy,
630
+ smoothing_curve,
631
+ keep_unitary_product,
632
+ align_corners_mode,
633
+ recompute_scale_factor_mode,
634
+ only_one_pass_enh,
635
+ only_one_pass_legacy,
636
+ ]
637
+ )
638
+
639
+ # Пресеты
640
+ def _update_preset_list_for_category(cat: str):
641
+ pm.reload()
642
+ return gr.update(choices=pm.names_for_category(cat), value=None)
643
+
644
+ preset_category_filter.change(
645
+ _update_preset_list_for_category, inputs=[preset_category_filter], outputs=[preset_select]
646
+ )
647
+
648
+ def _save_preset_cb(
649
+ name, cat_in, cat_filt,
650
+ mode, d1_v, d2_v, s1_v, s2_v,
651
+ scl, dw, up,
652
+ sm_enh, sm_leg,
653
+ sm_c, eo,
654
+ one_enh, one_leg,
655
+ k1, al, rc,
656
+ res, app, ad, ad_p
657
+ ):
658
+ name = (name or "").strip()
659
+ if not name:
660
+ return gr.update(), gr.update(), "⚠️ Имя?"
661
+ cat = (cat_in or "").strip() or (cat_filt if cat_filt != "Все" else "Общие")
662
+
663
+ base = HiresPreset().to_dict()
664
+ base.update({
665
+ "category": cat, "algo_mode": mode,
666
+ "d1": int(d1_v), "d2": int(d2_v),
667
+ "s1": float(s1_v), "s2": float(s2_v),
668
+ "scaler": str(scl), "downscale": float(dw), "upscale": float(up),
669
+ "smooth_scaling_enh": bool(sm_enh),
670
+ "smooth_scaling_legacy": bool(sm_leg),
671
+ "smoothing_curve": str(sm_c),
672
+ "early_out": bool(eo),
673
+ "only_one_pass_enh": bool(one_enh),
674
+ "only_one_pass_legacy": bool(one_leg),
675
+ "keep_unitary_product": bool(k1),
676
+ "align_corners_mode": str(al),
677
+ "recompute_scale_factor_mode": str(rc),
678
+ "resolution_choice": str(res),
679
+ "apply_resolution": bool(app),
680
+ "adaptive_by_resolution": bool(ad),
681
+ "adaptive_profile": str(ad_p),
682
+ })
683
+ pm.upsert(name, HiresPreset(**base))
684
+ cats = ["Все"] + pm.categories()
685
+ return (
686
+ gr.update(choices=cats, value=cat),
687
+ gr.update(choices=pm.names_for_category(cat), value=name),
688
+ f"✅ Сохранён «{name}».",
689
+ )
690
+
691
+ def _load_preset_cb(name):
692
+ name = (name or "").strip()
693
+ pm.reload()
694
+ preset = pm.get(name)
695
+ if not preset:
696
+ # 24 полей + текст
697
+ return (*[gr.update()]*24, f"⚠️ Не найден: {name}")
698
+ p = preset.to_dict()
699
+ return (
700
+ p.get("algo_mode", "Enhanced (RU+)"),
701
+ int(p.get("d1", 3)),
702
+ int(p.get("d2", 4)),
703
+ float(p.get("s1", 0.15)),
704
+ float(p.get("s2", 0.30)),
705
+ str(p.get("scaler", "bicubic")),
706
+ float(p.get("downscale", 0.5)),
707
+ float(p.get("upscale", 2.0)),
708
+ bool(p.get("smooth_scaling_enh", True)),
709
+ bool(p.get("smooth_scaling_legacy", True)),
710
+ str(p.get("smoothing_curve", "Линейная")),
711
+ bool(p.get("early_out", False)),
712
+ bool(p.get("only_one_pass_enh", True)),
713
+ bool(p.get("only_one_pass_legacy", True)),
714
+ bool(p.get("keep_unitary_product", False)),
715
+ str(p.get("align_corners_mode", "False")),
716
+ str(p.get("recompute_scale_factor_mode", "False")),
717
+ str(p.get("resolution_choice", RESOLUTION_CHOICES[0])),
718
+ bool(p.get("apply_resolution", False)),
719
+ bool(p.get("adaptive_by_resolution", True)),
720
+ str(p.get("adaptive_profile", "Сбалансированный")),
721
+ gr.update(value=name),
722
+ gr.update(value=p.get("category", "Общие")),
723
+ f"✅ Загружен «{name}».",
724
+ )
725
+
726
+ def _delete_preset_cb(name, cat_filt):
727
+ pm.delete(name)
728
+ cats = ["Все"] + pm.categories()
729
+ return (
730
+ gr.update(choices=cats, value=cat_filt),
731
+ gr.update(choices=pm.names_for_category(cat_filt), value=None),
732
+ f"🗑️ Удалён «{name}».",
733
+ )
734
+
735
+ btn_save.click(
736
+ _save_preset_cb,
737
+ inputs=[
738
+ preset_name, preset_category_input, preset_category_filter,
739
+ algo_mode, d1, d2, s1, s2,
740
+ scaler, downscale, upscale,
741
+ smooth_scaling_enh, smooth_scaling_legacy,
742
+ smoothing_curve, early_out,
743
+ only_one_pass_enh, only_one_pass_legacy,
744
+ keep_unitary_product, align_corners_mode, recompute_scale_factor_mode,
745
+ resolution_choice, apply_resolution,
746
+ adaptive_by_resolution, adaptive_profile,
747
+ ],
748
+ outputs=[preset_category_filter, preset_select, preset_status],
749
+ )
750
+
751
+ btn_load.click(
752
+ _load_preset_cb,
753
+ inputs=[preset_select],
754
+ outputs=[
755
+ algo_mode,
756
+ d1, d2,
757
+ s1, s2,
758
+ scaler,
759
+ downscale, upscale,
760
+ smooth_scaling_enh, smooth_scaling_legacy,
761
+ smoothing_curve,
762
+ early_out,
763
+ only_one_pass_enh, only_one_pass_legacy,
764
+ keep_unitary_product,
765
+ align_corners_mode, recompute_scale_factor_mode,
766
+ resolution_choice, apply_resolution,
767
+ adaptive_by_resolution, adaptive_profile,
768
+ preset_name, preset_category_input,
769
+ preset_status,
770
+ ],
771
+ )
772
+
773
+ btn_delete.click(
774
+ _delete_preset_cb,
775
+ inputs=[preset_select, preset_category_filter],
776
+ outputs=[preset_category_filter, preset_select, preset_status],
777
+ )
778
+
779
+ def _preview_cb(
780
+ res_v, adapt_v, adapt_prof_v,
781
+ s1_v, s2_v, d1_v, d2_v,
782
+ down_v, up_v, keep1_v, mode_v
783
+ ):
784
+ wh = parse_resolution_label(res_v)
785
+ w, h = wh if wh else (1024, 1024)
786
+
787
+ if adapt_v:
788
+ try:
789
+ u_s1, u_s2, u_d1, u_d2, u_down, u_up = _compute_adaptive_params(
790
+ w, h, adapt_prof_v,
791
+ s1_v, s2_v, d1_v, d2_v,
792
+ down_v, up_v, keep1_v
793
+ )
794
+ except Exception:
795
+ u_s1, u_s2, u_d1, u_d2, u_down, u_up = s1_v, s2_v, d1_v, d2_v, down_v, up_v
796
+ else:
797
+ u_s1, u_s2, u_d1, u_d2, u_down, u_up = s1_v, s2_v, d1_v, d2_v, down_v, up_v
798
+
799
+ return (
800
+ f"**Mode:** {mode_v}\n"
801
+ f"**Res:** {w}x{h}\n"
802
+ f"**S1:** {u_s1:.2f}, **D1:** {u_d1}\n"
803
+ f"**S2:** {u_s2:.2f}, **D2:** {u_d2}\n"
804
+ f"**Down:** {u_down:.2f}, **Up:** {u_up:.2f}"
805
+ )
806
+
807
+ btn_preview.click(
808
+ _preview_cb,
809
+ inputs=[
810
+ resolution_choice,
811
+ adaptive_by_resolution, adaptive_profile,
812
+ s1, s2, d1, d2,
813
+ downscale, upscale,
814
+ keep_unitary_product,
815
+ algo_mode,
816
+ ],
817
+ outputs=[preview_md],
818
+ )
819
+
820
+ # Infotext
821
+ self.infotext_fields.append((enable, lambda d: d.get("DSHF_s1", False)))
822
+ for k, el in {
823
+ "DSHF_mode": algo_mode,
824
+ "DSHF_s1": s1,
825
+ "DSHF_d1": d1,
826
+ "DSHF_s2": s2,
827
+ "DSHF_d2": d2,
828
+ "DSHF_scaler": scaler,
829
+ "DSHF_down": downscale,
830
+ "DSHF_up": upscale,
831
+ "DSHF_smooth_enh": smooth_scaling_enh,
832
+ "DSHF_smooth_legacy": smooth_scaling_legacy,
833
+ "DSHF_early": early_out,
834
+ "DSHF_one_enh": only_one_pass_enh,
835
+ "DSHF_one_legacy": only_one_pass_legacy,
836
+ }.items():
837
+ self.infotext_fields.append((el, k))
838
+
839
+ return [
840
+ enable,
841
+ simple_mode,
842
+ algo_mode,
843
+ only_one_pass_enh,
844
+ only_one_pass_legacy,
845
+ d1, d2,
846
+ s1, s2,
847
+ scaler,
848
+ downscale, upscale,
849
+ smooth_scaling_enh, smooth_scaling_legacy,
850
+ smoothing_curve,
851
+ early_out,
852
+ keep_unitary_product,
853
+ align_corners_mode, recompute_scale_factor_mode,
854
+ resolution_choice, apply_resolution,
855
+ adaptive_by_resolution, adaptive_profile,
856
+ ]
857
+
858
+ def process(
859
+ self, p,
860
+ enable, simple, algo_mode,
861
+ only_one_pass_enh, only_one_pass_legacy,
862
+ d1, d2, s1, s2,
863
+ scaler, downscale, upscale,
864
+ smooth_scaling_enh, smooth_scaling_legacy,
865
+ smoothing_curve,
866
+ early_out,
867
+ keep_unitary_product,
868
+ align_ui, recompute_ui,
869
+ res_choice, apply_res,
870
+ adapt, adapt_prof
871
+ ):
872
+ self.step_limit = 0
873
+ self.config = DictConfig({
874
+ "algo_mode": algo_mode,
875
+ "simple_mode": simple,
876
+ "s1": s1,
877
+ "s2": s2,
878
+ "d1": d1,
879
+ "d2": d2,
880
+ "scaler": scaler,
881
+ "downscale": downscale,
882
+ "upscale": upscale,
883
+ "smooth_scaling_enh": smooth_scaling_enh,
884
+ "smooth_scaling_legacy": smooth_scaling_legacy,
885
+ "smoothing_curve": smoothing_curve,
886
+ "early_out": early_out,
887
+ "only_one_pass_enh": only_one_pass_enh,
888
+ "only_one_pass_legacy": only_one_pass_legacy,
889
+ "keep_unitary_product": keep_unitary_product,
890
+ "align_corners_mode": align_ui,
891
+ "recompute_scale_factor_mode": recompute_ui,
892
+ "resolution_choice": res_choice,
893
+ "apply_resolution": apply_res,
894
+ "adaptive_by_resolution": adapt,
895
+ "adaptive_profile": adapt_prof,
896
+ })
897
+
898
+ if apply_res:
899
+ wh = parse_resolution_label(res_choice)
900
+ if wh:
901
+ p.width, p.height = wh
902
+
903
+ # Если выключено или скрипт помечен как сломанный — снимаем всё и выходим
904
+ if not enable or self.disable:
905
+ try:
906
+ script_callbacks.remove_current_script_callbacks()
907
+ except Exception:
908
+ pass
909
+ self._cb_registered = False
910
+ try:
911
+ model_container = getattr(p.sd_model, "model", None)
912
+ if model_container is not None and hasattr(model_container, "diffusion_model"):
913
+ KohyaHiresFix._unwrap_all(model_container.diffusion_model)
914
+ except Exception:
915
+ pass
916
+ return
917
+
918
+ # --- Адаптация параметров ---
919
+ use_s1, use_s2 = float(s1), float(s2)
920
+ use_d1, use_d2 = int(d1), int(d2)
921
+ use_down, use_up = float(downscale), float(upscale)
922
+
923
+ if adapt:
924
+ try:
925
+ use_s1, use_s2, use_d1, use_d2, use_down, use_up = _compute_adaptive_params(
926
+ int(getattr(p, "width", 1024)),
927
+ int(getattr(p, "height", 1024)),
928
+ adapt_prof,
929
+ s1, s2, d1, d2,
930
+ downscale, upscale,
931
+ keep_unitary_product,
932
+ )
933
+ except Exception:
934
+ pass
935
+
936
+ if use_s1 > use_s2:
937
+ use_s2 = use_s1
938
+
939
+ model_container = getattr(p.sd_model, "model", None)
940
+ if not model_container or not hasattr(model_container, "diffusion_model"):
941
+ return
942
+ model = model_container.diffusion_model
943
+ max_inp = len(getattr(model, "input_blocks", [])) - 1
944
+
945
+ d1_idx = max(0, min(int(use_d1) - 1, max_inp))
946
+ d2_idx = max(0, min(int(use_d2) - 1, max_inp))
947
+ scaler_mode = _safe_mode(scaler)
948
+
949
+ if algo_mode == "Legacy (Original)":
950
+ align_mode = "false"
951
+ recompute_mode = "false"
952
+ else:
953
+ align_mode = _norm_mode_choice(align_ui, "auto")
954
+ recompute_mode = _norm_mode_choice(recompute_ui, "auto")
955
+
956
+ use_smooth_enh = bool(smooth_scaling_enh)
957
+ use_smooth_legacy = bool(smooth_scaling_legacy)
958
+ use_one_enh = bool(only_one_pass_enh)
959
+ use_one_legacy = bool(only_one_pass_legacy)
960
+
961
+ def denoiser_callback(params: script_callbacks.CFGDenoiserParams):
962
+ total = max(1, int(params.total_sampling_steps))
963
+ current = params.sampling_step
964
+
965
+ try:
966
+ # ========================= LEGACY (ORIGINAL) MODE =========================
967
+ if algo_mode == "Legacy (Original)":
968
+ if use_one_legacy and self.step_limit:
969
+ return
970
+
971
+ legacy_pairs = [(use_s1, d1_idx), (use_s2, d2_idx)]
972
+
973
+ n_out = len(model.output_blocks)
974
+
975
+ for s_stop, d_idx in legacy_pairs:
976
+ if s_stop <= 0:
977
+ continue
978
+
979
+ if d_idx < 0 or d_idx >= len(model.input_blocks):
980
+ continue
981
+
982
+ if early_out:
983
+ out_idx = d_idx
984
+ else:
985
+ out_idx = n_out - 1 - d_idx
986
+
987
+ if out_idx < 0 or out_idx >= n_out:
988
+ continue
989
+
990
+ if current < total * s_stop:
991
+ # вставить Scaler
992
+ if not isinstance(model.input_blocks[d_idx], Scaler):
993
+ model.input_blocks[d_idx] = Scaler(
994
+ use_down,
995
+ model.input_blocks[d_idx],
996
+ scaler_mode,
997
+ align_mode,
998
+ recompute_mode,
999
+ )
1000
+ model.output_blocks[out_idx] = Scaler(
1001
+ use_up,
1002
+ model.output_blocks[out_idx],
1003
+ scaler_mode,
1004
+ align_mode,
1005
+ recompute_mode,
1006
+ )
1007
+ elif use_smooth_legacy:
1008
+ # старая математика smooth_scaling (как в khrfix_old.py)
1009
+ scale_ratio = current / (total * s_stop)
1010
+ cur_down = min((1.0 - use_down) * scale_ratio + use_down, 1.0)
1011
+ model.input_blocks[d_idx].scale = cur_down
1012
+ model.output_blocks[out_idx].scale = use_up * (
1013
+ use_down / max(1e-6, cur_down)
1014
+ )
1015
+ return
1016
+
1017
+ elif isinstance(model.input_blocks[d_idx], Scaler):
1018
+ is_p2 = (s_stop == use_s2 and d_idx == d2_idx)
1019
+ if d1_idx != d2_idx or is_p2:
1020
+ model.input_blocks[d_idx] = model.input_blocks[d_idx].block
1021
+ model.output_blocks[out_idx] = model.output_blocks[out_idx].block
1022
+
1023
+ if use_one_legacy and current > 0:
1024
+ if current >= total * max(use_s1, use_s2):
1025
+ self.step_limit = 1
1026
+
1027
+ # ========================= ENHANCED (RU+) MODE =========================
1028
+ else:
1029
+ if use_one_enh and self.step_limit:
1030
+ return
1031
+
1032
+ combined: Dict[int, float] = {}
1033
+ for s_stop, d_i in ((float(use_s1), d1_idx), (float(use_s2), d2_idx)):
1034
+ if s_stop > 0:
1035
+ combined[d_i] = max(combined.get(d_i, 0.0), s_stop)
1036
+
1037
+ max_stop = max(combined.values()) if combined else 0.0
1038
+
1039
+ for d_i, s_stop in combined.items():
1040
+ out_i = KohyaHiresFix._map_output_index(model, d_i, early_out)
1041
+ if out_i is None:
1042
+ continue
1043
+
1044
+ if current < total * s_stop:
1045
+ if not isinstance(model.input_blocks[d_i], Scaler):
1046
+ model.input_blocks[d_i] = Scaler(
1047
+ use_down,
1048
+ model.input_blocks[d_i],
1049
+ scaler_mode,
1050
+ align_mode,
1051
+ recompute_mode,
1052
+ )
1053
+ model.output_blocks[out_i] = Scaler(
1054
+ use_up,
1055
+ model.output_blocks[out_i],
1056
+ scaler_mode,
1057
+ align_mode,
1058
+ recompute_mode,
1059
+ )
1060
+
1061
+ if use_smooth_enh:
1062
+ ratio = float(max(0.0, min(1.0, current / (total * s_stop))))
1063
+ if (smoothing_curve or "").lower().startswith("smooth"):
1064
+ ratio = ratio * ratio * (3.0 - 2.0 * ratio)
1065
+
1066
+ cur_down = min((1.0 - use_down) * ratio + use_down, 1.0)
1067
+ model.input_blocks[d_i].scale = cur_down
1068
+
1069
+ if keep_unitary_product:
1070
+ cur_up = 1.0 / max(1e-6, cur_down)
1071
+ else:
1072
+ cur_up = use_up * (use_down / max(1e-6, cur_down))
1073
+ cur_up = _clamp(cur_up, 1.0, 4.0)
1074
+
1075
+ model.output_blocks[out_i].scale = cur_up
1076
+ else:
1077
+ model.input_blocks[d_i].scale = use_down
1078
+ model.output_blocks[out_i].scale = use_up
1079
+ else:
1080
+ if isinstance(model.input_blocks[d_i], Scaler):
1081
+ model.input_blocks[d_i] = model.input_blocks[d_i].block
1082
+ model.output_blocks[out_i] = model.output_blocks[out_i].block
1083
+
1084
+ if use_one_enh and max_stop > 0 and current >= total * max_stop:
1085
+ self.step_limit = 1
1086
+
1087
+ except Exception as e:
1088
+ # Если что-то пошло не так — аккуратно откатываемся и выключаем скрипт
1089
+ try:
1090
+ KohyaHiresFix._unwrap_all(model)
1091
+ except Exception:
1092
+ pass
1093
+ try:
1094
+ script_callbacks.remove_current_script_callbacks()
1095
+ except Exception:
1096
+ pass
1097
+ self._cb_registered = False
1098
+ self.disable = True
1099
+ print(f"[KohyaHiresFix Unified] Disabled after error: {type(e).__name__}: {e}")
1100
+
1101
+ if self._cb_registered:
1102
+ try:
1103
+ script_callbacks.remove_current_script_callbacks()
1104
+ except Exception:
1105
+ pass
1106
+
1107
+ script_callbacks.on_cfg_denoiser(denoiser_callback)
1108
+ self._cb_registered = True
1109
+
1110
+ p.extra_generation_params.update({
1111
+ "DSHF_mode": algo_mode,
1112
+ "DSHF_s1": use_s1,
1113
+ "DSHF_s2": use_s2,
1114
+ "DSHF_down": use_down,
1115
+ "DSHF_up": use_up,
1116
+ })
1117
+
1118
+ def postprocess(self, p, processed, *args):
1119
+ try:
1120
+ model_container = getattr(p.sd_model, "model", None)
1121
+ if model_container and hasattr(model_container, "diffusion_model"):
1122
+ KohyaHiresFix._unwrap_all(model_container.diffusion_model)
1123
+ finally:
1124
+ try:
1125
+ _atomic_save_yaml(
1126
+ CONFIG_PATH,
1127
+ OmegaConf.to_container(self.config, resolve=True) or {},
1128
+ )
1129
+ except Exception:
1130
+ pass
1131
+ self._cb_registered = False
1132
+
1133
+ def process_batch(self, p, *args, **kwargs):
1134
+ self.step_limit = 0
sd-webui-kohya-hiresfix-saveable2.2/scripts/khrfix.yaml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ algo_mode: Enhanced (RU+)
2
+ simple_mode: true
3
+ s1: 0.15
4
+ s2: 0.3
5
+ d1: 3
6
+ d2: 4
7
+ scaler: bicubic
8
+ downscale: 0.85
9
+ upscale: 2
10
+ smooth_scaling_enh: true
11
+ smooth_scaling_legacy: true
12
+ smoothing_curve: Smoothstep
13
+ early_out: true
14
+ only_one_pass_enh: true
15
+ only_one_pass_legacy: true
16
+ keep_unitary_product: false
17
+ align_corners_mode: Авто
18
+ recompute_scale_factor_mode: Авто
19
+ resolution_choice: — не применять —
20
+ apply_resolution: false
21
+ adaptive_by_resolution: false
22
+ adaptive_profile: Консервативный