Upload nrs_kohaku_enhanced (1).py
Browse files
negative_rejection_steering/scripts/nrs_kohaku_enhanced (1).py
ADDED
|
@@ -0,0 +1,508 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from modules import scripts, script_callbacks, sd_samplers_cfg_denoiser, shared
|
| 4 |
+
|
| 5 |
+
# ==============================================================================
|
| 6 |
+
# УЛУЧШЕНИЕ NRS С ИСПОЛЬЗОВАНИЕМ ГЕОМЕТРИЧЕСКОГО ПРИНЦИПА KOHAKU_LONYU_YOG
|
| 7 |
+
#
|
| 8 |
+
# Оригинальная идея Kohaku: в N-мерном пространстве, если взять антиподальную
|
| 9 |
+
# точку -x и вычислить оттуда градиент d2, то среднее (d+d2)/2 указывает
|
| 10 |
+
# точнее на целевую область A. Применяем этот же принцип к NRS-векторам.
|
| 11 |
+
#
|
| 12 |
+
# Новая функция calc_nrs_antipodal:
|
| 13 |
+
# 1. Вычисляет NRS-вектор из точки x (стандартный NRS)
|
| 14 |
+
# 2. Вычисляет NRS-вектор из антиподальной точки -x
|
| 15 |
+
# 3. Усредняет результаты — убирает смещение, зависящее от позиции x
|
| 16 |
+
#
|
| 17 |
+
# Параметр antipodal_blend (0.0 = чистый NRS, 1.0 = полное антиподальное
|
| 18 |
+
# усреднение) позволяет плавно управлять степенью улучшения.
|
| 19 |
+
# ==============================================================================
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _nrs_core(x_orig, cond, uncond, sigma, skew, stretch, squash):
|
| 23 |
+
"""
|
| 24 |
+
Базовое математическое ядро NRS. Принимает один тензор x_orig
|
| 25 |
+
и возвращает управляемый cond-вектор.
|
| 26 |
+
"""
|
| 27 |
+
is_v_pred = False
|
| 28 |
+
if hasattr(shared.sd_model, 'parameterization'):
|
| 29 |
+
is_v_pred = shared.sd_model.parameterization == "v"
|
| 30 |
+
|
| 31 |
+
if isinstance(sigma, torch.Tensor):
|
| 32 |
+
sig_tens = sigma[0]
|
| 33 |
+
else:
|
| 34 |
+
sig_tens = torch.tensor(sigma, device=cond.device, dtype=cond.dtype)
|
| 35 |
+
|
| 36 |
+
if sig_tens.dtype != cond.dtype:
|
| 37 |
+
sig_tens = sig_tens.to(dtype=cond.dtype)
|
| 38 |
+
|
| 39 |
+
sig_tens = sig_tens.view(1, 1, 1, 1)
|
| 40 |
+
sig_root = (sig_tens ** 2 + 1).sqrt()
|
| 41 |
+
|
| 42 |
+
if is_v_pred:
|
| 43 |
+
nrs_cond, nrs_uncond = cond, uncond
|
| 44 |
+
x_div = None
|
| 45 |
+
else:
|
| 46 |
+
x_div = x_orig / (sig_tens ** 2 + 1)
|
| 47 |
+
factor = sig_tens / sig_root
|
| 48 |
+
nrs_cond = x_orig - (x_div - cond * factor)
|
| 49 |
+
nrs_uncond = x_orig - (x_div - uncond * factor)
|
| 50 |
+
|
| 51 |
+
def _dot(a, b): return (a * b).sum(dim=1, keepdim=True)
|
| 52 |
+
def _nrm2(v): return _dot(v, v)
|
| 53 |
+
|
| 54 |
+
eps_safe = 1e-6
|
| 55 |
+
|
| 56 |
+
c_dot_c = _nrm2(nrs_cond) + eps_safe
|
| 57 |
+
u_dot_c = _dot(nrs_uncond, nrs_cond)
|
| 58 |
+
u_on_c = (u_dot_c / c_dot_c) * nrs_cond
|
| 59 |
+
|
| 60 |
+
proj_diff = nrs_cond - u_on_c
|
| 61 |
+
stretched = nrs_cond + (stretch * proj_diff)
|
| 62 |
+
|
| 63 |
+
u_rej_c = nrs_uncond - u_on_c
|
| 64 |
+
skewed = stretched - (skew * u_rej_c)
|
| 65 |
+
|
| 66 |
+
cond_len = nrs_cond.norm(dim=1, keepdim=True)
|
| 67 |
+
nrs_len = skewed.norm(dim=1, keepdim=True) + eps_safe
|
| 68 |
+
squash_scale = (1 - squash) + (squash * (cond_len / nrs_len))
|
| 69 |
+
x_final = skewed * squash_scale
|
| 70 |
+
|
| 71 |
+
if is_v_pred:
|
| 72 |
+
return x_final
|
| 73 |
+
else:
|
| 74 |
+
return (x_div - (x_orig - x_final)) * (sig_root / sig_tens)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def calc_nrs(x_orig, cond, uncond, sigma, skew, stretch, squash):
|
| 78 |
+
"""
|
| 79 |
+
Оригинальная функция NRS — обёртка над ядром, сохранена для совместимости.
|
| 80 |
+
"""
|
| 81 |
+
return _nrs_core(x_orig, cond, uncond, sigma, skew, stretch, squash)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def calc_nrs_antipodal(x_orig, cond, uncond, sigma, skew, stretch, squash,
|
| 85 |
+
antipodal_blend=0.5, antipodal_steps_half=True,
|
| 86 |
+
current_step=0, total_steps=20):
|
| 87 |
+
"""
|
| 88 |
+
NRS с антиподальным геометрическим улучшением по принципу Kohaku_LoNyu_Yog.
|
| 89 |
+
|
| 90 |
+
Принцип:
|
| 91 |
+
Kohaku показал, что в высокоразмерном пространстве точка -x и точка x
|
| 92 |
+
находятся "по разные стороны" от целевой области A, и среднее их
|
| 93 |
+
градиентов (d+d2)/2 даёт более точный вектор к A.
|
| 94 |
+
|
| 95 |
+
Здесь: вычисляем NRS-вектор из x И из -x, затем смешиваем.
|
| 96 |
+
NRS из -x имеет инвертированные знаки компонент cond/uncond
|
| 97 |
+
относительно пространства x, но их среднее устраняет позиционное
|
| 98 |
+
смещение и даёт более "центрированный" вектор управления.
|
| 99 |
+
|
| 100 |
+
Параметры:
|
| 101 |
+
antipodal_blend : 0.0 = чистый NRS, 1.0 = полное усреднение
|
| 102 |
+
antipodal_steps_half : True = применять только на первой половине
|
| 103 |
+
шагов (как в оригинале Kohaku)
|
| 104 |
+
current_step : текущий шаг сэмплера
|
| 105 |
+
total_steps : всего шагов
|
| 106 |
+
"""
|
| 107 |
+
# Стандартный NRS из x
|
| 108 |
+
nrs_direct = _nrs_core(x_orig, cond, uncond, sigma, skew, stretch, squash)
|
| 109 |
+
|
| 110 |
+
# Если антиподальное улучшение выключено — возвращаем стандартный результат
|
| 111 |
+
if antipodal_blend <= 0.0:
|
| 112 |
+
return nrs_direct
|
| 113 |
+
|
| 114 |
+
# Применять только на первой половине шагов (согласно теории Kohaku)
|
| 115 |
+
if antipodal_steps_half and current_step > total_steps / 2:
|
| 116 |
+
return nrs_direct
|
| 117 |
+
|
| 118 |
+
# ----------------------------------------------------------------
|
| 119 |
+
# Вычисляем NRS из антиподальной точки -x
|
| 120 |
+
#
|
| 121 |
+
# Геометрически: нас интересует не буквально -x как латент, а то,
|
| 122 |
+
# как меняется пространство проекций cond/uncond при инверсии x.
|
| 123 |
+
# Мы инвертируем x_orig и пересчитываем cond/uncond относительно него.
|
| 124 |
+
#
|
| 125 |
+
# Примечание: cond и uncond здесь — это выходы денойзера (x_out),
|
| 126 |
+
# а не промпты. Они тоже имеют смысл только в контексте входа x.
|
| 127 |
+
# При -x логично инвертировать и их знаки.
|
| 128 |
+
# ----------------------------------------------------------------
|
| 129 |
+
x_neg = -x_orig
|
| 130 |
+
cond_neg = -cond
|
| 131 |
+
uncond_neg = -uncond
|
| 132 |
+
|
| 133 |
+
nrs_antipodal_raw = _nrs_core(x_neg, cond_neg, uncond_neg, sigma,
|
| 134 |
+
skew, stretch, squash)
|
| 135 |
+
|
| 136 |
+
# Антиподальный результат возвращаем в пространство x (инвертируем обратно)
|
| 137 |
+
nrs_antipodal = -nrs_antipodal_raw
|
| 138 |
+
|
| 139 |
+
# ----------------------------------------------------------------
|
| 140 |
+
# Двухшаговое уточнение (аналог d3 в Kohaku):
|
| 141 |
+
# x3 = x + (d+d2)/2 * dt → затем вычисляется d3 → итог (d+d3)/2
|
| 142 |
+
# Здесь: вычисляем "уточнённый" NRS из промежуточной точки
|
| 143 |
+
# x_mid = x + (nrs_direct + nrs_antipodal) / 2 * blend_factor
|
| 144 |
+
# ----------------------------------------------------------------
|
| 145 |
+
nrs_avg = (nrs_direct + nrs_antipodal) / 2
|
| 146 |
+
|
| 147 |
+
# Blend: плавное смешивание
|
| 148 |
+
result = nrs_direct + antipodal_blend * (nrs_avg - nrs_direct)
|
| 149 |
+
|
| 150 |
+
return result
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
# ==============================================================================
|
| 154 |
+
# УПРАВЛЕНИЕ ШАГАМИ (без изменений)
|
| 155 |
+
# ==============================================================================
|
| 156 |
+
def should_apply_at_step(current_step, total_steps, start_step, end_step,
|
| 157 |
+
start_frac, end_frac, step_mode):
|
| 158 |
+
if step_mode == "Absolute Steps":
|
| 159 |
+
effective_start = max(0, start_step)
|
| 160 |
+
effective_end = min(total_steps, end_step) if end_step > 0 else total_steps
|
| 161 |
+
return effective_start <= current_step < effective_end
|
| 162 |
+
else:
|
| 163 |
+
effective_start = int(total_steps * max(0.0, min(1.0, start_frac)))
|
| 164 |
+
effective_end = int(total_steps * max(0.0, min(1.0, end_frac)))
|
| 165 |
+
if effective_end == 0:
|
| 166 |
+
effective_end = total_steps
|
| 167 |
+
return effective_start <= current_step < effective_end
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def get_param_value_at_step(base_value, current_step, total_steps, start_step, end_step,
|
| 171 |
+
start_frac, end_frac, step_mode, enabled):
|
| 172 |
+
if not enabled:
|
| 173 |
+
return base_value
|
| 174 |
+
if should_apply_at_step(current_step, total_steps, start_step, end_step,
|
| 175 |
+
start_frac, end_frac, step_mode):
|
| 176 |
+
return base_value
|
| 177 |
+
else:
|
| 178 |
+
return 0.0
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
# ==============================================================================
|
| 182 |
+
# ПЕРЕХВАТ (HOOKING)
|
| 183 |
+
# ==============================================================================
|
| 184 |
+
def hook_cfg_denoiser_params(params):
|
| 185 |
+
if hasattr(params.denoiser, 'p') and getattr(params.denoiser.p, '_nrs_enabled', False):
|
| 186 |
+
params.denoiser.p._nrs_current_sigma = params.sigma
|
| 187 |
+
params.denoiser.p._nrs_current_x_in = params.x
|
| 188 |
+
if hasattr(params, 'sampling_step'):
|
| 189 |
+
params.denoiser.p._nrs_current_step = params.sampling_step
|
| 190 |
+
elif hasattr(params.denoiser, 'step'):
|
| 191 |
+
params.denoiser.p._nrs_current_step = params.denoiser.step
|
| 192 |
+
else:
|
| 193 |
+
params.denoiser.p._nrs_current_step = getattr(params.denoiser.p, '_nrs_current_step', 0)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
script_callbacks.on_cfg_denoiser(hook_cfg_denoiser_params)
|
| 197 |
+
|
| 198 |
+
if not hasattr(sd_samplers_cfg_denoiser.CFGDenoiser, 'original_combine_denoised_nrs_backup'):
|
| 199 |
+
sd_samplers_cfg_denoiser.CFGDenoiser.original_combine_denoised_nrs_backup = \
|
| 200 |
+
sd_samplers_cfg_denoiser.CFGDenoiser.combine_denoised
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def hijacked_combine_denoised(self, x_out, conds_list, uncond, cond_scale):
|
| 204 |
+
if not getattr(self, 'p', None) or not getattr(self.p, '_nrs_enabled', False):
|
| 205 |
+
return sd_samplers_cfg_denoiser.CFGDenoiser.original_combine_denoised_nrs_backup(
|
| 206 |
+
self, x_out, conds_list, uncond, cond_scale)
|
| 207 |
+
|
| 208 |
+
if not hasattr(self.p, '_nrs_current_sigma') or not hasattr(self.p, '_nrs_current_x_in'):
|
| 209 |
+
return sd_samplers_cfg_denoiser.CFGDenoiser.original_combine_denoised_nrs_backup(
|
| 210 |
+
self, x_out, conds_list, uncond, cond_scale)
|
| 211 |
+
|
| 212 |
+
try:
|
| 213 |
+
base_skew, base_stretch, base_squash = self.p._nrs_params
|
| 214 |
+
step_control_enabled = getattr(self.p, '_nrs_step_control_enabled', False)
|
| 215 |
+
step_control_mode = getattr(self.p, '_nrs_step_control_mode', 'Global')
|
| 216 |
+
current_step = getattr(self.p, '_nrs_current_step', 0)
|
| 217 |
+
total_steps = getattr(self.p, 'steps', 20)
|
| 218 |
+
|
| 219 |
+
if step_control_enabled:
|
| 220 |
+
if step_control_mode == 'Global':
|
| 221 |
+
global_settings = getattr(self.p, '_nrs_global_step_settings', {})
|
| 222 |
+
start_step = global_settings.get('start_step', 0)
|
| 223 |
+
end_step = global_settings.get('end_step', total_steps)
|
| 224 |
+
start_frac = global_settings.get('start_frac', 0.0)
|
| 225 |
+
end_frac = global_settings.get('end_frac', 1.0)
|
| 226 |
+
step_mode = global_settings.get('step_mode', 'Absolute Steps')
|
| 227 |
+
if not should_apply_at_step(current_step, total_steps, start_step, end_step,
|
| 228 |
+
start_frac, end_frac, step_mode):
|
| 229 |
+
return sd_samplers_cfg_denoiser.CFGDenoiser.original_combine_denoised_nrs_backup(
|
| 230 |
+
self, x_out, conds_list, uncond, cond_scale)
|
| 231 |
+
skew, stretch, squash = base_skew, base_stretch, base_squash
|
| 232 |
+
else:
|
| 233 |
+
individual_settings = getattr(self.p, '_nrs_individual_step_settings', {})
|
| 234 |
+
skew_s = individual_settings.get('skew', {})
|
| 235 |
+
skew = get_param_value_at_step(
|
| 236 |
+
base_skew, current_step, total_steps,
|
| 237 |
+
skew_s.get('start_step', 0), skew_s.get('end_step', total_steps),
|
| 238 |
+
skew_s.get('start_frac', 0.0), skew_s.get('end_frac', 1.0),
|
| 239 |
+
skew_s.get('step_mode', 'Absolute Steps'), skew_s.get('enabled', True))
|
| 240 |
+
str_s = individual_settings.get('stretch', {})
|
| 241 |
+
stretch = get_param_value_at_step(
|
| 242 |
+
base_stretch, current_step, total_steps,
|
| 243 |
+
str_s.get('start_step', 0), str_s.get('end_step', total_steps),
|
| 244 |
+
str_s.get('start_frac', 0.0), str_s.get('end_frac', 1.0),
|
| 245 |
+
str_s.get('step_mode', 'Absolute Steps'), str_s.get('enabled', True))
|
| 246 |
+
sq_s = individual_settings.get('squash', {})
|
| 247 |
+
squash = get_param_value_at_step(
|
| 248 |
+
base_squash, current_step, total_steps,
|
| 249 |
+
sq_s.get('start_step', 0), sq_s.get('end_step', total_steps),
|
| 250 |
+
sq_s.get('start_frac', 0.0), sq_s.get('end_frac', 1.0),
|
| 251 |
+
sq_s.get('step_mode', 'Absolute Steps'), sq_s.get('enabled', True))
|
| 252 |
+
else:
|
| 253 |
+
skew, stretch, squash = base_skew, base_stretch, base_squash
|
| 254 |
+
|
| 255 |
+
# Антиподальные параметры
|
| 256 |
+
antipodal_blend = getattr(self.p, '_nrs_antipodal_blend', 0.0)
|
| 257 |
+
antipodal_steps_half = getattr(self.p, '_nrs_antipodal_steps_half', True)
|
| 258 |
+
|
| 259 |
+
denoised_uncond = x_out[-uncond.shape[0]:]
|
| 260 |
+
denoised = torch.clone(denoised_uncond)
|
| 261 |
+
x_orig_all = self.p._nrs_current_x_in
|
| 262 |
+
x_orig_uncond = x_orig_all[-uncond.shape[0]:]
|
| 263 |
+
|
| 264 |
+
for i, conds in enumerate(conds_list):
|
| 265 |
+
for idx, (cond_index, weight) in enumerate(conds):
|
| 266 |
+
current_cond = x_out[cond_index]
|
| 267 |
+
if idx == 0:
|
| 268 |
+
current_x_orig = x_orig_uncond[i].unsqueeze(0)
|
| 269 |
+
c_in = current_cond.unsqueeze(0)
|
| 270 |
+
u_in = denoised_uncond[i].unsqueeze(0)
|
| 271 |
+
|
| 272 |
+
# Используем новую функцию с антиподальным улучшением
|
| 273 |
+
nrs_result = calc_nrs_antipodal(
|
| 274 |
+
current_x_orig, c_in, u_in,
|
| 275 |
+
self.p._nrs_current_sigma,
|
| 276 |
+
skew, stretch, squash,
|
| 277 |
+
antipodal_blend=antipodal_blend,
|
| 278 |
+
antipodal_steps_half=antipodal_steps_half,
|
| 279 |
+
current_step=current_step,
|
| 280 |
+
total_steps=total_steps
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
if len(conds) == 1:
|
| 284 |
+
denoised[i] = nrs_result.squeeze(0)
|
| 285 |
+
else:
|
| 286 |
+
delta = nrs_result.squeeze(0) - denoised_uncond[i]
|
| 287 |
+
denoised[i] += delta * weight
|
| 288 |
+
else:
|
| 289 |
+
denoised[i] += (current_cond - denoised_uncond[i]) * (weight * cond_scale)
|
| 290 |
+
|
| 291 |
+
return denoised
|
| 292 |
+
|
| 293 |
+
except Exception as e:
|
| 294 |
+
print(f"!!! NRS Antipodal Error (Fallback): {e}")
|
| 295 |
+
return sd_samplers_cfg_denoiser.CFGDenoiser.original_combine_denoised_nrs_backup(
|
| 296 |
+
self, x_out, conds_list, uncond, cond_scale)
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
# ==============================================================================
|
| 300 |
+
# UI
|
| 301 |
+
# ==============================================================================
|
| 302 |
+
class NRSScript(scripts.Script):
|
| 303 |
+
def title(self):
|
| 304 |
+
return "NRS + Kohaku Antipodal (Enhanced)"
|
| 305 |
+
|
| 306 |
+
def show(self, is_img2img):
|
| 307 |
+
return scripts.AlwaysVisible
|
| 308 |
+
|
| 309 |
+
def ui(self, is_img2img):
|
| 310 |
+
with gr.Accordion("Negative Rejection Steering + Kohaku Antipodal", open=False):
|
| 311 |
+
with gr.Row():
|
| 312 |
+
enabled = gr.Checkbox(label="Включить NRS (Enable)", value=False)
|
| 313 |
+
|
| 314 |
+
with gr.Accordion("❓ Инструкция", open=False):
|
| 315 |
+
gr.Markdown("""
|
| 316 |
+
### Что нового: Antipodal Blend (Антиподальное улучшение)
|
| 317 |
+
|
| 318 |
+
Основано на принципе **Kohaku_LoNyu_Yog**: в высокоразмерном пространстве
|
| 319 |
+
точки `x` и `-x` находятся по разные стороны от целевой области изображения.
|
| 320 |
+
|
| 321 |
+
Стандартный NRS вычисляет управляющий вектор только из текущего `x`.
|
| 322 |
+
Это создаёт **позиционное смещение** — вектор "перекошен" относительно
|
| 323 |
+
истинного направления к цели.
|
| 324 |
+
|
| 325 |
+
**Antipodal Blend** вычисляет NRS также из `-x` и усредняет результаты.
|
| 326 |
+
Это устраняет смещение и даёт более точное управление.
|
| 327 |
+
|
| 328 |
+
### Параметры Antipodal:
|
| 329 |
+
- **Antipodal Blend** (0.0–1.0): Степень антиподального улучшения.
|
| 330 |
+
0.0 = чистый NRS, 0.5 = рекомендуемое значение, 1.0 = полное усреднение.
|
| 331 |
+
- **Only first half of steps**: Применять только на первой половине шагов
|
| 332 |
+
(как в оригинале Kohaku — в конце шагов метод может отклоняться).
|
| 333 |
+
|
| 334 |
+
### Советы:
|
| 335 |
+
- Начните с **Antipodal Blend = 0.3–0.5** с включённым "Only first half"
|
| 336 |
+
- Если результат слишком "мягкий" — уменьшите blend или увеличьте Skew
|
| 337 |
+
- Этот метод особенно эффективен при низком CFG / высоком Squash
|
| 338 |
+
""")
|
| 339 |
+
|
| 340 |
+
gr.HTML("<div style='margin-bottom: 0.5em; opacity: 0.8; font-size: 0.9em;"
|
| 341 |
+
" border-bottom: 1px solid #444;'>Основные настройки NRS</div>")
|
| 342 |
+
|
| 343 |
+
with gr.Row():
|
| 344 |
+
skew = gr.Slider(
|
| 345 |
+
label="Skew (Композиция)",
|
| 346 |
+
minimum=-30.0, maximum=30.0, step=0.05, value=4.0,
|
| 347 |
+
info="Отклонение от Negative prompt. Рекомендуется: 3.0–5.0"
|
| 348 |
+
)
|
| 349 |
+
stretch = gr.Slider(
|
| 350 |
+
label="Stretch (Цвета/Текстура)",
|
| 351 |
+
minimum=-30.0, maximum=30.0, step=0.05, value=2.0,
|
| 352 |
+
info="Притяжение к Positive prompt. Рекомендуется: 2.0–7.0"
|
| 353 |
+
)
|
| 354 |
+
|
| 355 |
+
squash = gr.Slider(
|
| 356 |
+
label="Squash (Защита от пережарки)",
|
| 357 |
+
minimum=0.0, maximum=1.0, step=0.01, value=0.0,
|
| 358 |
+
info="0.0 = максимальный эффект. 1.0 = больше деталей."
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
+
# --- НОВЫЙ БЛОК: АНТИПОДАЛЬНОЕ УЛУЧШЕНИЕ ---
|
| 362 |
+
gr.HTML("<div style='margin: 0.8em 0 0.5em 0; opacity: 0.8; font-size: 0.9em;"
|
| 363 |
+
" border-bottom: 1px solid #444;'>🔮 Kohaku Antipodal Enhancement</div>")
|
| 364 |
+
|
| 365 |
+
antipodal_blend = gr.Slider(
|
| 366 |
+
label="Antipodal Blend",
|
| 367 |
+
minimum=0.0, maximum=1.0, step=0.01, value=0.0,
|
| 368 |
+
info="0.0 = выкл (чистый NRS). 0.5 = рекомендуется. 1.0 = полное антиподальное усреднение."
|
| 369 |
+
)
|
| 370 |
+
|
| 371 |
+
antipodal_steps_half = gr.Checkbox(
|
| 372 |
+
label="Only first half of steps (рекомендуется)",
|
| 373 |
+
value=True,
|
| 374 |
+
info="Применять антиподальное улучшение только на первой половине шагов (как в Kohaku)"
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
# --- STEP CONTROL ---
|
| 378 |
+
with gr.Accordion("⏱️ Step Control", open=False):
|
| 379 |
+
with gr.Row():
|
| 380 |
+
step_control_enabled = gr.Checkbox(label="Включить Step Control", value=False)
|
| 381 |
+
step_control_mode = gr.Radio(
|
| 382 |
+
label="Режим", choices=["Global", "Individual"], value="Global")
|
| 383 |
+
|
| 384 |
+
with gr.Group(visible=True) as global_group:
|
| 385 |
+
gr.HTML("<div style='margin: 0.5em 0; font-weight: bold;'>Глобальные настройки</div>")
|
| 386 |
+
global_step_mode = gr.Radio(
|
| 387 |
+
label="Режим шагов",
|
| 388 |
+
choices=["Absolute Steps", "Fraction of Steps"],
|
| 389 |
+
value="Absolute Steps")
|
| 390 |
+
with gr.Row():
|
| 391 |
+
global_start_step = gr.Slider(label="Start Step", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 392 |
+
global_end_step = gr.Slider(label="End Step (0=конец)", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 393 |
+
with gr.Row():
|
| 394 |
+
global_start_frac = gr.Slider(label="Start (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=0.0, visible=False)
|
| 395 |
+
global_end_frac = gr.Slider(label="End (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=1.0, visible=False)
|
| 396 |
+
|
| 397 |
+
with gr.Group(visible=False) as individual_group:
|
| 398 |
+
gr.HTML("<div style='margin: 0.5em 0; font-weight: bold;'>Индивидуальные настройки</div>")
|
| 399 |
+
with gr.Accordion("Skew - Step Settings", open=False):
|
| 400 |
+
skew_step_enabled = gr.Checkbox(label="Включить для Skew", value=True)
|
| 401 |
+
skew_step_mode = gr.Radio(label="Режим", choices=["Absolute Steps", "Fraction of Steps"], value="Absolute Steps")
|
| 402 |
+
with gr.Row():
|
| 403 |
+
skew_start_step = gr.Slider(label="Start Step", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 404 |
+
skew_end_step = gr.Slider(label="End Step", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 405 |
+
with gr.Row():
|
| 406 |
+
skew_start_frac = gr.Slider(label="Start (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=0.0, visible=False)
|
| 407 |
+
skew_end_frac = gr.Slider(label="End (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=1.0, visible=False)
|
| 408 |
+
with gr.Accordion("Stretch - Step Settings", open=False):
|
| 409 |
+
stretch_step_enabled = gr.Checkbox(label="Включить для Stretch", value=True)
|
| 410 |
+
stretch_step_mode = gr.Radio(label="Режим", choices=["Absolute Steps", "Fraction of Steps"], value="Absolute Steps")
|
| 411 |
+
with gr.Row():
|
| 412 |
+
stretch_start_step = gr.Slider(label="Start Step", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 413 |
+
stretch_end_step = gr.Slider(label="End Step", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 414 |
+
with gr.Row():
|
| 415 |
+
stretch_start_frac = gr.Slider(label="Start (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=0.0, visible=False)
|
| 416 |
+
stretch_end_frac = gr.Slider(label="End (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=1.0, visible=False)
|
| 417 |
+
with gr.Accordion("Squash - Step Settings", open=False):
|
| 418 |
+
squash_step_enabled = gr.Checkbox(label="Включить для Squash", value=True)
|
| 419 |
+
squash_step_mode = gr.Radio(label="Режим", choices=["Absolute Steps", "Fraction of Steps"], value="Absolute Steps")
|
| 420 |
+
with gr.Row():
|
| 421 |
+
squash_start_step = gr.Slider(label="Start Step", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 422 |
+
squash_end_step = gr.Slider(label="End Step", minimum=0, maximum=150, step=1, value=0, visible=True)
|
| 423 |
+
with gr.Row():
|
| 424 |
+
squash_start_frac = gr.Slider(label="Start (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=0.0, visible=False)
|
| 425 |
+
squash_end_frac = gr.Slider(label="End (fraction)", minimum=0.0, maximum=1.0, step=0.01, value=1.0, visible=False)
|
| 426 |
+
|
| 427 |
+
def update_mode_visibility(mode):
|
| 428 |
+
return {
|
| 429 |
+
global_group: gr.update(visible=(mode == "Global")),
|
| 430 |
+
individual_group: gr.update(visible=(mode == "Individual"))
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
step_control_mode.change(
|
| 434 |
+
fn=update_mode_visibility,
|
| 435 |
+
inputs=[step_control_mode],
|
| 436 |
+
outputs=[global_group, individual_group])
|
| 437 |
+
|
| 438 |
+
def _toggle_abs_frac(mode):
|
| 439 |
+
a = mode == "Absolute Steps"
|
| 440 |
+
return gr.update(visible=a), gr.update(visible=a), gr.update(visible=not a), gr.update(visible=not a)
|
| 441 |
+
|
| 442 |
+
global_step_mode.change(fn=_toggle_abs_frac, inputs=[global_step_mode],
|
| 443 |
+
outputs=[global_start_step, global_end_step, global_start_frac, global_end_frac])
|
| 444 |
+
skew_step_mode.change(fn=_toggle_abs_frac, inputs=[skew_step_mode],
|
| 445 |
+
outputs=[skew_start_step, skew_end_step, skew_start_frac, skew_end_frac])
|
| 446 |
+
stretch_step_mode.change(fn=_toggle_abs_frac, inputs=[stretch_step_mode],
|
| 447 |
+
outputs=[stretch_start_step, stretch_end_step, stretch_start_frac, stretch_end_frac])
|
| 448 |
+
squash_step_mode.change(fn=_toggle_abs_frac, inputs=[squash_step_mode],
|
| 449 |
+
outputs=[squash_start_step, squash_end_step, squash_start_frac, squash_end_frac])
|
| 450 |
+
|
| 451 |
+
return [
|
| 452 |
+
enabled, skew, stretch, squash,
|
| 453 |
+
antipodal_blend, antipodal_steps_half,
|
| 454 |
+
step_control_enabled, step_control_mode,
|
| 455 |
+
global_step_mode, global_start_step, global_end_step, global_start_frac, global_end_frac,
|
| 456 |
+
skew_step_enabled, skew_step_mode, skew_start_step, skew_end_step, skew_start_frac, skew_end_frac,
|
| 457 |
+
stretch_step_enabled, stretch_step_mode, stretch_start_step, stretch_end_step, stretch_start_frac, stretch_end_frac,
|
| 458 |
+
squash_step_enabled, squash_step_mode, squash_start_step, squash_end_step, squash_start_frac, squash_end_frac,
|
| 459 |
+
]
|
| 460 |
+
|
| 461 |
+
def process(self, p,
|
| 462 |
+
enabled, skew, stretch, squash,
|
| 463 |
+
antipodal_blend, antipodal_steps_half,
|
| 464 |
+
step_control_enabled, step_control_mode,
|
| 465 |
+
global_step_mode, global_start_step, global_end_step, global_start_frac, global_end_frac,
|
| 466 |
+
skew_step_enabled, skew_step_mode, skew_start_step, skew_end_step, skew_start_frac, skew_end_frac,
|
| 467 |
+
stretch_step_enabled, stretch_step_mode, stretch_start_step, stretch_end_step, stretch_start_frac, stretch_end_frac,
|
| 468 |
+
squash_step_enabled, squash_step_mode, squash_start_step, squash_end_step, squash_start_frac, squash_end_frac):
|
| 469 |
+
|
| 470 |
+
p._nrs_enabled = enabled
|
| 471 |
+
p._nrs_params = (skew, stretch, squash)
|
| 472 |
+
p._nrs_antipodal_blend = antipodal_blend
|
| 473 |
+
p._nrs_antipodal_steps_half = antipodal_steps_half
|
| 474 |
+
p._nrs_step_control_enabled = step_control_enabled
|
| 475 |
+
p._nrs_step_control_mode = step_control_mode
|
| 476 |
+
|
| 477 |
+
p._nrs_global_step_settings = {
|
| 478 |
+
'step_mode': global_step_mode,
|
| 479 |
+
'start_step': global_start_step,
|
| 480 |
+
'end_step': global_end_step,
|
| 481 |
+
'start_frac': global_start_frac,
|
| 482 |
+
'end_frac': global_end_frac
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
p._nrs_individual_step_settings = {
|
| 486 |
+
'skew': {
|
| 487 |
+
'enabled': skew_step_enabled, 'step_mode': skew_step_mode,
|
| 488 |
+
'start_step': skew_start_step, 'end_step': skew_end_step,
|
| 489 |
+
'start_frac': skew_start_frac, 'end_frac': skew_end_frac
|
| 490 |
+
},
|
| 491 |
+
'stretch': {
|
| 492 |
+
'enabled': stretch_step_enabled, 'step_mode': stretch_step_mode,
|
| 493 |
+
'start_step': stretch_start_step, 'end_step': stretch_end_step,
|
| 494 |
+
'start_frac': stretch_start_frac, 'end_frac': stretch_end_frac
|
| 495 |
+
},
|
| 496 |
+
'squash': {
|
| 497 |
+
'enabled': squash_step_enabled, 'step_mode': squash_step_mode,
|
| 498 |
+
'start_step': squash_start_step, 'end_step': squash_end_step,
|
| 499 |
+
'start_frac': squash_start_frac, 'end_frac': squash_end_frac
|
| 500 |
+
}
|
| 501 |
+
}
|
| 502 |
+
|
| 503 |
+
if enabled:
|
| 504 |
+
sd_samplers_cfg_denoiser.CFGDenoiser.combine_denoised = hijacked_combine_denoised
|
| 505 |
+
p._nrs_current_step = 0
|
| 506 |
+
|
| 507 |
+
def postprocess(self, p, processed, *args):
|
| 508 |
+
pass
|