Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a70d1d09bb | ||
|
|
b034c3f09f | ||
|
|
2583c237f2 | ||
|
|
c83958c457 | ||
|
|
a0b2d99bc7 | ||
|
|
ba145c4722 | ||
|
|
c21dbfe1e5 | ||
|
|
fc38b5c998 | ||
|
|
d26fcf6fc8 |
@@ -172,3 +172,8 @@ cython_debug/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
|
||||
# AI agents
|
||||
.claude/settings.local.json
|
||||
.claude/docs/**
|
||||
|
||||
+84
-16
@@ -13,6 +13,11 @@ class PredictionType(Enum):
|
||||
_RAW_TO_ENUM = {
|
||||
"eps": PredictionType.EPS,
|
||||
"epsilon": PredictionType.EPS,
|
||||
"flux": PredictionType.EPS,
|
||||
"chroma": PredictionType.EPS,
|
||||
"flow": PredictionType.EPS, # FLOW models (WAN, etc.) are EPS-compatible
|
||||
"wan": PredictionType.EPS, # WAN21 is FLOW-based
|
||||
"const": PredictionType.EPS, # CONST prediction class used in FLOW models
|
||||
"v": PredictionType.V,
|
||||
"v_prediction": PredictionType.V,
|
||||
"x0": PredictionType.X0,
|
||||
@@ -22,16 +27,21 @@ _RAW_TO_ENUM = {
|
||||
class NRS:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model": ("MODEL",),
|
||||
"skew": ("FLOAT", {"default": 4.0, "min": -30.0, "max": 30.0, "step": 0.01}),
|
||||
"stretch": ("FLOAT", {"default": 2.0, "min": -30.0, "max": 30.0, "step": 0.01}),
|
||||
"squash": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
return {"required": { "model": ("MODEL", {"tooltip": "Input model to apply NRS to"}),
|
||||
"skew": ("FLOAT", {"default": 2.00, "min": -30.0, "max": 30.0, "step": 0.01,
|
||||
"tooltip": "Changes the 'direction' of generation, steering away from negative prompt elements. Start with CFG/2."}),
|
||||
"stretch": ("FLOAT", {"default": 5.00, "min": -30.0, "max": 30.0, "step": 0.01,
|
||||
"tooltip": "Intensifies positive prompt elements. Start with your normal CFG value."}),
|
||||
"squash": ("FLOAT", {"default": 0.75, "min": 0.0, "max": 1.0, "step": 0.01,
|
||||
"tooltip": "Softens Skew/Stretch effects, adding micro-detailing. Keep low initially."}),
|
||||
}}
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "patch"
|
||||
|
||||
CATEGORY = "advanced/model"
|
||||
|
||||
DESCRIPTION = "Negative Rejection Steering (NRS) replaces CFG with more nuanced guidance. IMPORTANT: Set your KSampler CFG to any value (it will be ignored). Connect your model through this node before sampling."
|
||||
|
||||
def _get_pred_type(self, model) -> PredictionType:
|
||||
"""
|
||||
In order to support Comfy, Forge, and possibly other models
|
||||
@@ -59,7 +69,10 @@ class NRS:
|
||||
for attr in ("model_type", "prediction_type", "parameterization"):
|
||||
p = _canon(getattr(obj, attr, None))
|
||||
if p:
|
||||
return _RAW_TO_ENUM.get(p, PredictionType.UNKNOWN)
|
||||
pred_type = _RAW_TO_ENUM.get(p, PredictionType.UNKNOWN)
|
||||
if pred_type != PredictionType.UNKNOWN:
|
||||
logging.debug(f"NRS._get_pred_type: Found prediction type '{p}' from attribute '{attr}' -> {pred_type}")
|
||||
return pred_type
|
||||
|
||||
# 2) enqueue child containers we care about -------------------
|
||||
for attr in ("model", "diffusion_model", "config", "scheduler", "inner_model", "model_sampling"):
|
||||
@@ -68,8 +81,46 @@ class NRS:
|
||||
seen.add(id(child))
|
||||
queue.append(child)
|
||||
|
||||
# 3) default ------------------------------------------------------
|
||||
return PredictionType.UNKNOWN
|
||||
# 3) enhanced detection for FLOW models (WAN, Flux, etc.) -------
|
||||
try:
|
||||
# Check model_sampling class type for FLOW models
|
||||
if hasattr(model, "model_sampling") and model.model_sampling is not None:
|
||||
sampling_class_name = type(model.model_sampling).__name__.lower()
|
||||
logging.debug(f"NRS._get_pred_type: Found model_sampling class: {sampling_class_name}")
|
||||
|
||||
# CONST class is used by FLOW models (WAN21, Flux, etc.)
|
||||
if "const" in sampling_class_name:
|
||||
logging.debug(f"NRS._get_pred_type: Detected FLOW model via CONST sampling class -> EPS")
|
||||
return PredictionType.EPS
|
||||
elif "v_prediction" in sampling_class_name:
|
||||
logging.debug(f"NRS._get_pred_type: Detected V-prediction model via sampling class -> V")
|
||||
return PredictionType.V
|
||||
elif "eps" in sampling_class_name:
|
||||
logging.debug(f"NRS._get_pred_type: Detected EPS model via sampling class -> EPS")
|
||||
return PredictionType.EPS
|
||||
|
||||
# Check model.model.model_type enum for newer models
|
||||
if hasattr(model, "model") and hasattr(model.model, "model_type"):
|
||||
model_type_str = _canon(str(model.model.model_type))
|
||||
logging.debug(f"NRS._get_pred_type: Found model.model.model_type: {model_type_str}")
|
||||
|
||||
if "flow" in model_type_str or "flux" in model_type_str:
|
||||
logging.debug(f"NRS._get_pred_type: Detected FLOW/Flux model via model_type -> EPS")
|
||||
return PredictionType.EPS
|
||||
elif "v_prediction" in model_type_str:
|
||||
logging.debug(f"NRS._get_pred_type: Detected V-prediction model via model_type -> V")
|
||||
return PredictionType.V
|
||||
elif "eps" in model_type_str:
|
||||
logging.debug(f"NRS._get_pred_type: Detected EPS model via model_type -> EPS")
|
||||
return PredictionType.EPS
|
||||
|
||||
except Exception as e:
|
||||
logging.debug(f"NRS._get_pred_type: Exception during enhanced detection: {e}")
|
||||
|
||||
# 4) safe default (matches docstring promise) --------------------
|
||||
logging.warning(f"NRS._get_pred_type: Could not determine prediction type for model. Using EPS as fallback.")
|
||||
logging.debug(f"NRS._get_pred_type: Model structure: {[attr for attr in dir(model) if not attr.startswith('_')]}")
|
||||
return PredictionType.EPS
|
||||
|
||||
def _convert_to_eps_space(self, x_orig, sig_root, sigma, cond, uncond):
|
||||
x_div = None
|
||||
@@ -88,7 +139,9 @@ class NRS:
|
||||
elif self.__pred_type == PredictionType.X0:
|
||||
raise NotImplementedError("NRS._convert_to_eps_space: x0-prediction not supported yet.")
|
||||
else:
|
||||
raise RuntimeError("NRS._convert_to_eps_space: Could not determine prediction type for this model.")
|
||||
# Fallback: treat UNKNOWN as EPS (should not happen with enhanced detection)
|
||||
logging.warning(f"NRS._convert_to_eps_space: Unknown prediction type {self.__pred_type}, treating as EPS")
|
||||
pass
|
||||
|
||||
return x_div, eps_cond, eps_uncond
|
||||
|
||||
@@ -105,7 +158,9 @@ class NRS:
|
||||
elif self.__pred_type == PredictionType.X0:
|
||||
raise NotImplementedError("NRS._finalize_from_eps_space: x0-prediction not supported yet.")
|
||||
else:
|
||||
raise RuntimeError("NRS._finalize_from_eps_space: Could not determine prediction type for this model.")
|
||||
# Fallback: treat UNKNOWN as EPS (should not happen with enhanced detection)
|
||||
logging.warning(f"NRS._finalize_from_eps_space: Unknown prediction type {self.__pred_type}, treating as EPS")
|
||||
pass
|
||||
return nrs_result
|
||||
|
||||
def _convert_to_v_space(self, x_orig, sig_root, sigma, cond, uncond):
|
||||
@@ -126,7 +181,13 @@ class NRS:
|
||||
elif self.__pred_type == PredictionType.X0:
|
||||
raise NotImplementedError("NRS._convert_to_v_space: x0-prediction not supported yet.")
|
||||
else:
|
||||
raise RuntimeError("NRS._convert_to_v_space: Could not determine prediction type for this model.")
|
||||
# Fallback: treat UNKNOWN as EPS and convert to V-space
|
||||
logging.warning(f"NRS._convert_to_v_space: Unknown prediction type {self.__pred_type}, treating as EPS")
|
||||
logging.debug(f"NRS._convert_to_v_space: generating x_div, v_cond, and v_uncond for eps (fallback)")
|
||||
x_div = x_orig / (sigma ** 2 + 1)
|
||||
factor = sigma / sig_root
|
||||
v_cond = x_orig - (x_div - cond * factor)
|
||||
v_uncond = x_orig - (x_div - uncond * factor)
|
||||
|
||||
return x_div, v_cond, v_uncond
|
||||
|
||||
@@ -143,7 +204,10 @@ class NRS:
|
||||
elif self.__pred_type == PredictionType.X0:
|
||||
raise NotImplementedError("NRS._finalize_from_v_space: x0-prediction not supported yet.")
|
||||
else:
|
||||
raise RuntimeError("NRS._finalize_from_v_space: Could not determine prediction type for this model.")
|
||||
# Fallback: treat UNKNOWN as EPS and convert from V-space
|
||||
logging.warning(f"NRS._finalize_from_v_space: Unknown prediction type {self.__pred_type}, treating as EPS")
|
||||
logging.debug(f"NRS._finalize_from_v_space: generating cfg_result for eps (fallback)")
|
||||
nrs_result = (x_div - (x_orig - x_final)) * (sig_root / sigma)
|
||||
return nrs_result
|
||||
|
||||
def patch(self, model, skew, stretch, squash):
|
||||
@@ -170,9 +234,11 @@ class NRS:
|
||||
case PredictionType.X0:
|
||||
raise RuntimeError("NRS.nrs: x0-prediction not supported yet.")
|
||||
case PredictionType.UNKNOWN:
|
||||
raise RuntimeError("NRS.nrs: Could not determine prediction type for this operation.")
|
||||
# Fallback: treat as EPS (should not happen with enhanced detection)
|
||||
logging.warning(f"NRS.nrs: Unknown operation space, treating as EPS for conversion")
|
||||
x_div, nrs_cond, nrs_uncond = self._convert_to_eps_space(x_orig, sig_root, sigma, cond, uncond)
|
||||
case _:
|
||||
raise RuntimeError("NRS.nrs: Invalid PredictionType used.")
|
||||
raise RuntimeError(f"NRS.nrs: Invalid PredictionType used for operation space: {self.__OPERATION_SPACE}")
|
||||
|
||||
def _dot(a, b):
|
||||
return (a*b).sum(dim=1, keepdim=True) # [B,C,W,H] => [B,1,W,H]
|
||||
@@ -208,9 +274,11 @@ class NRS:
|
||||
case PredictionType.X0:
|
||||
raise RuntimeError("NRS.nrs: x0-prediction not supported yet.")
|
||||
case PredictionType.UNKNOWN:
|
||||
raise RuntimeError("NRS.nrs: Could not determine prediction type for this operation.")
|
||||
# Fallback: treat as EPS (should not happen with enhanced detection)
|
||||
logging.warning(f"NRS.nrs: Unknown operation space, treating as EPS for finalization")
|
||||
return self._finalize_from_eps_space(x_orig, x_div, x_final, sig_root, sigma)
|
||||
case _:
|
||||
raise RuntimeError("NRS.nrs: Invalid PredictionType used.")
|
||||
raise RuntimeError(f"NRS.nrs: Invalid PredictionType used for operation space: {self.__OPERATION_SPACE}")
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_sampler_cfg_function(nrs, True)
|
||||
@@ -218,4 +286,4 @@ class NRS:
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"NRS": NRS,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ NRS seeks to replace the 'naive' linear interpolation of Classifier Free Guidanc
|
||||
**Squash** is the speed limit. At 0.0 Squash, each diffusion step receives the full intensity you set from Skew and Stretch, while 1.0 Squash ensures each step has only the original step size output by the model. This setting will only remove intensity unless you have a non-zero Skew value. Squash will 'soften' the effects of Skew and Stretch as it's raised, but the 'removed' Skew and Stretch intensity is replaced by enhanced micro-detailing and 'burn'. Squash should generally be left low and used as a 'finishing' step after dialing in a decent Skew and Stretch value.
|
||||
|
||||
## Beginner How-To
|
||||
1. Set Skew to your normal CFG Scale setting and Stretch to 1/2 your normal CFG Scale. Set Squash to 0.0.<br>
|
||||
*Alternatively, try starting at 1/1/0.0 to get a baseline.*
|
||||
1. Set Skew to 1/2 of your normal CFG Scale setting and Stretch to your full normal CFG Scale. Set Squash to 0.0.<br>
|
||||
*Alternatively, try starting with the default of 2/5/0.75, or at 1/1/1 to get a baseline.*
|
||||
2. Test some outputs. Results should be similar in quality to CFG.
|
||||
3. Adjust Skew to change the intensity of your outputs adherence to your positive and negative prompts. This primarily effects composition of the output.
|
||||
4. Adjust Stretch to intensify your positive prompt's aspects and colors where they differ from the negative prompt. This primarily effects color and texture.
|
||||
@@ -73,6 +73,50 @@ NRS seeks to replace the 'naive' linear interpolation of Classifier Free Guidanc
|
||||
> [!WARNING]
|
||||
> Don't set NRS values to negatives if there are things in your negative prompt you **actually** don't want to see.
|
||||
|
||||
## Setup & Installation
|
||||
|
||||
### ComfyUI
|
||||
<details>
|
||||
<summary>ComfyUI Setup Instructions</summary>
|
||||
|
||||
#### Installation
|
||||
Install via ComfyUI Manager or manually clone this repository into your `ComfyUI/custom_nodes/` directory.
|
||||
|
||||
#### Usage
|
||||
1. **Important**: Ignore the CFG setting on your KSampler node - NRS replaces CFG entirely
|
||||
2. Connect your model through the **Negative Rejection Steering** node before sampling
|
||||
3. Configure NRS parameters (Skew/Stretch/Squash) instead of using CFG
|
||||
|
||||
#### Basic Workflow
|
||||
```
|
||||
Model → NRS Node → KSampler
|
||||
```
|
||||
|
||||
**Pro tip**: To verify NRS is working correctly, set CFG to an extremely high value (like 30). If your output looks normal, NRS is functioning properly. If the output appears "turbo fried," check your node connections.
|
||||
|
||||
**Sampler Compatibility**: NRS now supports advanced samplers including WanKSamplerAdvanced, RES4LYF samplers, and FLOW models (WAN21, Flux) with enhanced prediction type detection.
|
||||
|
||||

|
||||
</details>
|
||||
|
||||
### Automatic1111 / Forge / reForge
|
||||
<details>
|
||||
<summary>WebUI Setup Instructions</summary>
|
||||
|
||||
#### Installation
|
||||
1. Install the extension through the Extensions tab in your WebUI
|
||||
2. Enable the extension and restart your WebUI
|
||||
|
||||
#### Usage
|
||||
Once installed and enabled, the NRS settings panel will appear in your generation interface. When NRS is active:
|
||||
- **CFG Scale is ignored** - the WebUI may still show the CFG setting, but it has no effect
|
||||
- Use the NRS parameters (Skew/Stretch/Squash) to control generation instead
|
||||
- Follow the same parameter guidelines from the [Beginner How-To](#beginner-how-to) section
|
||||
</details>
|
||||
|
||||
### StabilityMatrix Integration
|
||||
NRS is available as a **natively supported module** in [StabilityMatrix](https://lykos.ai/), providing an easy installation and management option for users of that platform.
|
||||
|
||||
## Submitted User Examples
|
||||
| User | CFG | NRS |
|
||||
| --- | --- | --- |
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
name = "negative_rejection_steering"
|
||||
description = "NRS seeks to replace the 'naive' linear interpolation of Classifier Free Guidance with a more nuanced and composable steering of the generation process with better mathematical basis."
|
||||
authors = [{name = "Bryan O'Malley", email = "bo122081@hotmail.com"}]
|
||||
version = "0.7.0"
|
||||
version = "0.7.4"
|
||||
license = {file = "LICENSE"}
|
||||
readme = "README.md"
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ class NRSScript(scripts.Script):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.enabled = False
|
||||
self.skew = 4.0
|
||||
self.stretch = 2.0
|
||||
self.squash = 0.0
|
||||
self.skew = 2.00
|
||||
self.stretch = 5.00
|
||||
self.squash = 0.75
|
||||
|
||||
sorting_priority = 5
|
||||
|
||||
|
||||
Reference in New Issue
Block a user