Convert Skimmed_CFG nodes to V3 schema with bug fixes.

- Migrate all nodes from V1 to V3 API (io.ComfyNode, define_schema, execute)
- Fix sigma boundary check (use >= instead of >)
- Add division by zero guards in interpolated_scales and CFG calculations
- Expose previously hidden parameters (start_at_percentage, end_at_percentage, flip_at_percentage)
- Fix return value consistency in SkimReplacePreCFG
- Add type hints to execute methods
This commit is contained in:
Taithrah
2026-01-02 22:28:48 -05:00
parent 2712803a8b
commit 9cb1e17360
2 changed files with 469 additions and 170 deletions
+2 -10
View File
@@ -1,11 +1,3 @@
from .skimmed_CFG import *
from .skimmed_CFG import comfy_entrypoint
NODE_CLASS_MAPPINGS = {
"Skimmed CFG": CFG_skimming_single_scale_pre_cfg_node,
"Skimmed CFG - replace": skimReplacePreCFGNode,
"Skimmed CFG - linear interpolation": SkimmedCFGLinInterpCFGPreCFGNode,
"Skimmed CFG - linear interpolation dual scales": SkimmedCFGLinInterpDualScalesCFGPreCFGNode,
"Skimmed CFG - Difference CFG": differenceCFGPreCFGNode,
"Skimmed CFG - Timed flip": skimFlipPreCFGNode,
"Skimmed CFG - Clean Skim": constantSkimPreCFGNode,
}
__all__ = ["comfy_entrypoint"]
+467 -160
View File
@@ -1,129 +1,309 @@
import torch
from comfy_api.latest import ComfyExtension, io
from typing_extensions import override
MAX_SCALE = 10
STEP_STEP = 2
@torch.no_grad()
def get_skimming_mask(x_orig, cond, uncond, cond_scale, return_denoised=False, disable_flipping_filter=False):
denoised = x_orig - ((x_orig - uncond) + cond_scale * ((x_orig - cond) - (x_orig - uncond)))
def get_skimming_mask(
x_orig,
cond,
uncond,
cond_scale,
return_denoised=False,
disable_flipping_filter=False,
):
denoised = x_orig - (
(x_orig - uncond) + cond_scale * ((x_orig - cond) - (x_orig - uncond))
)
matching_pred_signs = (cond - uncond).sign() == cond.sign()
matching_diff_after = cond.sign() == (cond * cond_scale - uncond * (cond_scale - 1)).sign()
matching_diff_after = (
cond.sign() == (cond * cond_scale - uncond * (cond_scale - 1)).sign()
)
if disable_flipping_filter:
outer_influence = matching_pred_signs & matching_diff_after
else:
deviation_influence = (denoised.sign() == (denoised - x_orig).sign())
outer_influence = matching_pred_signs & matching_diff_after & deviation_influence
deviation_influence = denoised.sign() == (denoised - x_orig).sign()
outer_influence = (
matching_pred_signs & matching_diff_after & deviation_influence
)
if return_denoised:
return outer_influence, denoised
else:
return outer_influence
@torch.no_grad()
def skimmed_CFG(x_orig, cond, uncond, cond_scale, skimming_scale, disable_flipping_filter=False):
outer_influence, denoised = get_skimming_mask(x_orig, cond, uncond, cond_scale, True, disable_flipping_filter)
low_cfg_denoised_outer = x_orig - ((x_orig - uncond) + skimming_scale * ((x_orig - cond) - (x_orig - uncond)))
def skimmed_CFG(
x_orig, cond, uncond, cond_scale, skimming_scale, disable_flipping_filter=False
):
outer_influence, denoised = get_skimming_mask(
x_orig, cond, uncond, cond_scale, True, disable_flipping_filter
)
low_cfg_denoised_outer = x_orig - (
(x_orig - uncond) + skimming_scale * ((x_orig - cond) - (x_orig - uncond))
)
low_cfg_denoised_outer_difference = denoised - low_cfg_denoised_outer
cond[outer_influence] = cond[outer_influence] - (low_cfg_denoised_outer_difference[outer_influence] / cond_scale)
cond[outer_influence] = cond[outer_influence] - (
low_cfg_denoised_outer_difference[outer_influence] / cond_scale
)
return cond
class CFG_skimming_single_scale_pre_cfg_node:
@torch.no_grad()
def interpolated_scales(
x_orig, cond, uncond, cond_scale, small_scale, squared=False, root_dist=False
):
deltacfg_normal = x_orig - cond_scale * cond - (cond_scale - 1) * uncond
deltacfg_small = x_orig - small_scale * cond - (small_scale - 1) * uncond
absdiff = (deltacfg_normal - deltacfg_small).abs()
# Fix division by zero
diff_range = absdiff.max() - absdiff.min()
if diff_range > 0:
absdiff = (absdiff - absdiff.min()) / diff_range
else:
absdiff = torch.zeros_like(absdiff)
if squared:
absdiff = absdiff**2
elif root_dist:
absdiff = absdiff**0.5
new_scale = (small_scale - 1) / (cond_scale - 1) if cond_scale > 1 else 0.0
smaller_uncond = cond * (1 - new_scale) + uncond * new_scale
new_uncond = smaller_uncond * (1 - absdiff) + uncond * absdiff
return new_uncond
class CFG_Skimming_Single_Scale_Pre_CFG(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {"required": {"model": ("MODEL",),
"Skimming_CFG": ("FLOAT", {"default": 7, "min": 0, "max": MAX_SCALE, "step": 1 / STEP_STEP, "round": 1/100, "tooltip":"The fallback scale for the ''bad'' values."}),
"full_skim_negative" : ("BOOLEAN", {"default": False}),
"disable_flipping_filter" : ("BOOLEAN", {"default": False}),
}}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "model_patches/Pre CFG"
def patch(self, model, Skimming_CFG=-1, full_skim_negative=True, disable_flipping_filter=False, start_at_percentage=0,end_at_percentage=1,flip_at_percentage=0):
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="CFG_Skimming_Single_Scale_Pre_CFG",
display_name="Skimmed CFG",
category="model_patches/Pre CFG",
description="Apply skimming CFG with a single scale. Skims 'bad' values to a fallback scale.",
inputs=[
io.Model.Input("model"),
io.Float.Input(
"skimming_cfg",
default=7.0,
min=0.0,
max=MAX_SCALE,
step=1.0 / STEP_STEP,
tooltip="The fallback scale for the 'bad' values. Set to -1 to use the current CFG scale.",
),
io.Boolean.Input(
"full_skim_negative",
default=False,
tooltip="If enabled, fully skim negative conditioning (set to 0).",
),
io.Boolean.Input(
"disable_flipping_filter",
default=False,
tooltip="Disable the flipping filter for skimming detection.",
),
io.Float.Input(
"start_at_percentage",
default=0.0,
min=0.0,
max=1.0,
step=0.01,
tooltip="Start applying skimming at this percentage of the denoising process (0 = start, 1 = end).",
),
io.Float.Input(
"end_at_percentage",
default=1.0,
min=0.0,
max=1.0,
step=0.01,
tooltip="Stop applying skimming at this percentage of the denoising process (0 = start, 1 = end).",
),
io.Float.Input(
"flip_at_percentage",
default=0.0,
min=0.0,
max=1.0,
step=0.01,
tooltip="Flip the flipping filter at this percentage. Set to 0 to disable.",
),
],
outputs=[
io.Model.Output(),
],
)
@classmethod
def execute(
cls,
model,
skimming_cfg: float,
full_skim_negative: bool,
disable_flipping_filter: bool,
start_at_percentage: float,
end_at_percentage: float,
flip_at_percentage: float,
) -> io.NodeOutput:
model_sampling = model.get_model_object("model_sampling")
start_at_sigma = model_sampling.percent_to_sigma(start_at_percentage)
end_at_sigma = model_sampling.percent_to_sigma(end_at_percentage)
flip_at_sigma = model_sampling.percent_to_sigma(flip_at_percentage)
end_at_sigma = model_sampling.percent_to_sigma(end_at_percentage)
flip_at_sigma = model_sampling.percent_to_sigma(flip_at_percentage)
if 1 > flip_at_percentage > 0:
print(f" \033[92mFlip at sigma: {round(flip_at_sigma, 2)}\033[0m")
@torch.no_grad()
def pre_cfg_patch(args):
conds_out = args["conds_out"]
conds_out = args["conds_out"]
cond_scale = args["cond_scale"]
x_orig = args['input']
sigma = args["sigma"][0].item()
if not torch.any(conds_out[1]) or sigma <= end_at_sigma or sigma > start_at_sigma:
x_orig = args["input"]
sigma = args["sigma"][0].item()
# Fix: Use >= instead of > for proper boundary checking
if (
not torch.any(conds_out[1])
or sigma <= end_at_sigma
or sigma >= start_at_sigma
):
return conds_out
practical_scale = cond_scale if Skimming_CFG < 0 else Skimming_CFG
practical_scale = cond_scale if skimming_cfg < 0 else skimming_cfg
flip_filter = disable_flipping_filter
if flip_at_percentage > 0 and sigma > flip_at_sigma:
flip_filter = not disable_flipping_filter
conds_out[1] = skimmed_CFG(x_orig, conds_out[1], conds_out[0], cond_scale, practical_scale if not full_skim_negative else 0, flip_filter)
conds_out[0] = skimmed_CFG(x_orig, conds_out[0], conds_out[1], cond_scale - 1, practical_scale, flip_filter)
conds_out[1] = skimmed_CFG(
x_orig,
conds_out[1],
conds_out[0],
cond_scale,
practical_scale if not full_skim_negative else 0,
flip_filter,
)
conds_out[0] = skimmed_CFG(
x_orig,
conds_out[0],
conds_out[1],
cond_scale - 1,
practical_scale,
flip_filter,
)
return conds_out
m = model.clone()
m.set_model_sampler_pre_cfg_function(pre_cfg_patch)
return (m, )
return io.NodeOutput(m)
class skimFlipPreCFGNode:
class SkimFlipPreCFG(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL",),
"flip_at": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 1/20, "round": 1/100, "tooltip":"Relative to the step progression.\nCompletely at 0 will give smoother results\nCompletely at one will give noisier results.\nThe influence is more important from 0% to 30%"}),
"reverse" : ("BOOLEAN", {"default": False, "tooltip":"If turned on you will obtain a composition closer to what you would normally get with no modification."}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="SkimFlipPreCFG",
display_name="Skimmed CFG - Timed flip",
category="model_patches/Pre CFG",
description="Flip the skimming filter at a specific point in the denoising process.",
inputs=[
io.Model.Input("model"),
io.Float.Input(
"flip_at",
default=0.3,
min=0.0,
max=1.0,
step=1.0 / 20,
tooltip="Relative to the step progression. Completely at 0 will give smoother results. Completely at one will give noisier results. The influence is more important from 0% to 30%.",
),
io.Boolean.Input(
"reverse",
default=False,
tooltip="If turned on you will obtain a composition closer to what you would normally get with no modification.",
),
],
outputs=[
io.Model.Output(),
],
)
CATEGORY = "model_patches/Pre CFG"
def patch(self, model, flip_at, reverse):
ssspcn = CFG_skimming_single_scale_pre_cfg_node()
m, = ssspcn.patch(model=model,Skimming_CFG=-1,full_skim_negative=True,disable_flipping_filter=reverse,flip_at_percentage=flip_at)
return (m, )
class constantSkimPreCFGNode:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL",),
"enabled" : ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "model_patches/Pre CFG"
def patch(self, model, enabled):
def execute(cls, model, flip_at: float, reverse: bool) -> io.NodeOutput:
# Use the main node with specific parameters
return CFG_Skimming_Single_Scale_Pre_CFG.execute(
model=model,
skimming_cfg=-1.0,
full_skim_negative=True,
disable_flipping_filter=reverse,
start_at_percentage=0.0,
end_at_percentage=1.0,
flip_at_percentage=flip_at,
)
class ConstantSkimPreCFG(io.ComfyNode):
@classmethod
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="ConstantSkimPreCFG",
display_name="Skimmed CFG - Clean Skim",
category="model_patches/Pre CFG",
description="Apply constant skimming CFG. Can be enabled/disabled.",
inputs=[
io.Model.Input("model"),
io.Boolean.Input(
"enabled",
default=True,
tooltip="Enable constant skimming CFG.",
),
],
outputs=[
io.Model.Output(),
],
)
@classmethod
def execute(cls, model, enabled: bool) -> io.NodeOutput:
if not enabled:
return model,
ssspcn = CFG_skimming_single_scale_pre_cfg_node()
m, = ssspcn.patch(model=model,Skimming_CFG=-1,full_skim_negative=True,disable_flipping_filter=False)
return (m, )
return io.NodeOutput(model)
class skimReplacePreCFGNode:
return CFG_Skimming_Single_Scale_Pre_CFG.execute(
model=model,
skimming_cfg=-1.0,
full_skim_negative=True,
disable_flipping_filter=False,
start_at_percentage=0.0,
end_at_percentage=1.0,
flip_at_percentage=0.0,
)
class SkimReplacePreCFG(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL",),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="SkimReplacePreCFG",
display_name="Skimmed CFG - replace",
category="model_patches/Pre CFG",
description="Replace negative conditioning with positive where skimming mask is detected.",
inputs=[
io.Model.Input("model"),
],
outputs=[
io.Model.Output(),
],
)
CATEGORY = "model_patches/Pre CFG"
def patch(self, model):
@classmethod
def execute(cls, model) -> io.NodeOutput:
@torch.no_grad()
def pre_cfg_patch(args):
conds_out = args["conds_out"]
conds_out = args["conds_out"]
cond_scale = args["cond_scale"]
x_orig = args['input']
x_orig = args["input"]
if not torch.any(conds_out[1]):
return conds_out
@@ -137,146 +317,273 @@ class skimReplacePreCFGNode:
skim_mask = get_skimming_mask(x_orig, uncond, cond, cond_scale - 1)
uncond[skim_mask] = cond[skim_mask]
return [cond,uncond]
# Fix: Return consistent format
return conds_out
m = model.clone()
m.set_model_sampler_pre_cfg_function(pre_cfg_patch)
return (m, )
return io.NodeOutput(m)
class SkimmedCFGLinInterpCFGPreCFGNode:
class SkimmedCFG_LinInterp_CFG_PreCFG(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL",),
"Skimming_CFG": ("FLOAT", {"default": 5.0, "min": 0.0, "max": MAX_SCALE, "step": 1 / STEP_STEP, "round": 1/100}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "model_patches/Pre CFG"
def patch(self, model, Skimming_CFG):
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="SkimmedCFG_LinInterp_CFG_PreCFG",
display_name="Skimmed CFG - linear interpolation",
category="model_patches/Pre CFG",
description="Apply skimming CFG with linear interpolation between scales.",
inputs=[
io.Model.Input("model"),
io.Float.Input(
"skimming_cfg",
default=5.0,
min=0.0,
max=MAX_SCALE,
step=1.0 / STEP_STEP,
tooltip="The fallback CFG scale for linear interpolation.",
),
],
outputs=[
io.Model.Output(),
],
)
@classmethod
def execute(cls, model, skimming_cfg: float) -> io.NodeOutput:
@torch.no_grad()
def pre_cfg_patch(args):
conds_out = args["conds_out"]
conds_out = args["conds_out"]
cond_scale = args["cond_scale"]
x_orig = args['input']
x_orig = args["input"]
if not torch.any(conds_out[1]):
return conds_out
fallback_weight = (Skimming_CFG - 1) / (cond_scale - 1)
# Fix: Prevent division by zero
if cond_scale <= 1:
return conds_out
skim_mask = get_skimming_mask(x_orig, conds_out[0], conds_out[1], cond_scale)
conds_out[1][skim_mask] = conds_out[0][skim_mask] * (1 - fallback_weight) + conds_out[1][skim_mask] * fallback_weight
fallback_weight = (skimming_cfg - 1) / (cond_scale - 1)
skim_mask = get_skimming_mask(x_orig, conds_out[1], conds_out[0], cond_scale)
conds_out[1][skim_mask] = conds_out[0][skim_mask] * (1 - fallback_weight) + conds_out[1][skim_mask] * fallback_weight
skim_mask = get_skimming_mask(
x_orig, conds_out[0], conds_out[1], cond_scale
)
conds_out[1][skim_mask] = (
conds_out[0][skim_mask] * (1 - fallback_weight)
+ conds_out[1][skim_mask] * fallback_weight
)
skim_mask = get_skimming_mask(
x_orig, conds_out[1], conds_out[0], cond_scale
)
conds_out[1][skim_mask] = (
conds_out[0][skim_mask] * (1 - fallback_weight)
+ conds_out[1][skim_mask] * fallback_weight
)
return conds_out
m = model.clone()
m.set_model_sampler_pre_cfg_function(pre_cfg_patch)
return (m, )
return io.NodeOutput(m)
class SkimmedCFGLinInterpDualScalesCFGPreCFGNode:
class SkimmedCFG_LinInterp_DualScales_CFG_PreCFG(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL",),
"Skimming_CFG_positive": ("FLOAT", {"default": 5.0, "min": 0.0, "max": MAX_SCALE, "step": 1 / STEP_STEP, "round": 1/100}),
"Skimming_CFG_negative": ("FLOAT", {"default": 5.0, "min": 0.0, "max": MAX_SCALE, "step": 1 / STEP_STEP, "round": 1/100}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
CATEGORY = "model_patches/Pre CFG"
def patch(self, model, Skimming_CFG_positive, Skimming_CFG_negative):
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="SkimmedCFG_LinInterp_DualScales_CFG_PreCFG",
display_name="Skimmed CFG - linear interpolation dual scales",
category="model_patches/Pre CFG",
description="Apply skimming CFG with linear interpolation using separate scales for positive and negative conditioning.",
inputs=[
io.Model.Input("model"),
io.Float.Input(
"skimming_cfg_positive",
default=5.0,
min=0.0,
max=MAX_SCALE,
step=1.0 / STEP_STEP,
tooltip="The fallback CFG scale for positive conditioning.",
),
io.Float.Input(
"skimming_cfg_negative",
default=5.0,
min=0.0,
max=MAX_SCALE,
step=1.0 / STEP_STEP,
tooltip="The fallback CFG scale for negative conditioning.",
),
],
outputs=[
io.Model.Output(),
],
)
@classmethod
def execute(
cls, model, skimming_cfg_positive: float, skimming_cfg_negative: float
) -> io.NodeOutput:
@torch.no_grad()
def pre_cfg_patch(args):
conds_out = args["conds_out"]
conds_out = args["conds_out"]
cond_scale = args["cond_scale"]
x_orig = args['input']
x_orig = args["input"]
if not torch.any(conds_out[1]):
return conds_out
fallback_weight_positive = (Skimming_CFG_positive - 1) / (cond_scale - 1)
fallback_weight_negative = (Skimming_CFG_negative - 1) / (cond_scale - 1)
# Fix: Prevent division by zero
if cond_scale <= 1:
return conds_out
skim_mask = get_skimming_mask(x_orig, conds_out[1], conds_out[0], cond_scale)
conds_out[1][skim_mask] = conds_out[0][skim_mask] * (1 - fallback_weight_negative) + conds_out[1][skim_mask] * fallback_weight_negative
fallback_weight_positive = (skimming_cfg_positive - 1) / (cond_scale - 1)
fallback_weight_negative = (skimming_cfg_negative - 1) / (cond_scale - 1)
skim_mask = get_skimming_mask(x_orig, conds_out[0], conds_out[1], cond_scale)
conds_out[1][skim_mask] = conds_out[0][skim_mask] * (1 - fallback_weight_positive) + conds_out[1][skim_mask] * fallback_weight_positive
skim_mask = get_skimming_mask(
x_orig, conds_out[1], conds_out[0], cond_scale
)
conds_out[1][skim_mask] = (
conds_out[0][skim_mask] * (1 - fallback_weight_negative)
+ conds_out[1][skim_mask] * fallback_weight_negative
)
skim_mask = get_skimming_mask(
x_orig, conds_out[0], conds_out[1], cond_scale
)
conds_out[1][skim_mask] = (
conds_out[0][skim_mask] * (1 - fallback_weight_positive)
+ conds_out[1][skim_mask] * fallback_weight_positive
)
return conds_out
m = model.clone()
m.set_model_sampler_pre_cfg_function(pre_cfg_patch)
return (m, )
return io.NodeOutput(m)
class differenceCFGPreCFGNode:
class DifferenceCFG_PreCFG(io.ComfyNode):
@classmethod
def INPUT_TYPES(s):
return {"required": {
"model": ("MODEL",),
"reference_CFG": ("FLOAT", {"default": 5.0, "min": 0.0, "max": MAX_SCALE, "step": 1 / STEP_STEP, "round": 1/100}),
"method" : (["linear_distance","squared_distance","root_distance","absolute_sum"],),
"end_at_percentage": ("FLOAT", {"default": 0.80, "min": 0.0, "max": 1.0, "step": 1/100, "round": 1/100, "tooltip":"Relative to the step progression. 0 means disabled, 1 means active until the end."}),
}
}
RETURN_TYPES = ("MODEL",)
FUNCTION = "patch"
def define_schema(cls) -> io.Schema:
return io.Schema(
node_id="DifferenceCFG_PreCFG",
display_name="Skimmed CFG - Difference CFG",
category="model_patches/Pre CFG",
description="Apply CFG based on difference between reference and current CFG scales.",
inputs=[
io.Model.Input("model"),
io.Float.Input(
"reference_cfg",
default=5.0,
min=0.0,
max=MAX_SCALE,
step=1.0 / STEP_STEP,
tooltip="The reference CFG scale to compare against.",
),
io.Combo.Input(
"method",
options=[
"linear_distance",
"squared_distance",
"root_distance",
"absolute_sum",
],
default="linear_distance",
tooltip="The method to calculate the difference.",
),
io.Float.Input(
"end_at_percentage",
default=0.80,
min=0.0,
max=1.0,
step=0.01,
tooltip="Relative to the step progression. 0 means disabled, 1 means active until the end.",
),
],
outputs=[
io.Model.Output(),
],
)
CATEGORY = "model_patches/Pre CFG"
def patch(self, model, reference_CFG, method, end_at_percentage):
@classmethod
def execute(
cls,
model,
reference_cfg: float,
method: str,
end_at_percentage: float,
) -> io.NodeOutput:
model_sampling = model.get_model_object("model_sampling")
end_at_sigma = model_sampling.percent_to_sigma(end_at_percentage)
print(f" \033[92mDifference CFG method: {method} / Reference Scale: {reference_CFG} / End at percent/sigma: {round(end_at_percentage,2)}/{round(end_at_sigma,2)}\033[0m")
end_at_sigma = model_sampling.percent_to_sigma(end_at_percentage)
print(
f" \033[92mDifference CFG method: {method} / Reference Scale: {reference_cfg} / End at percent/sigma: {round(end_at_percentage, 2)}/{round(end_at_sigma, 2)}\033[0m"
)
@torch.no_grad()
def pre_cfg_patch(args):
conds_out = args["conds_out"]
conds_out = args["conds_out"]
cond_scale = args["cond_scale"]
x_orig = args['input']
sigma = args["sigma"][0]
x_orig = args["input"]
sigma = args["sigma"][0]
if not torch.any(conds_out[1]) or sigma <= end_at_sigma:
return conds_out
if method == "absolute_sum":
ref_norm = (conds_out[0] * reference_CFG - conds_out[1] * (reference_CFG - 1)).norm(p=1)
cfg_norm = (conds_out[0] * cond_scale - conds_out[1] * (cond_scale - 1)).norm(p=1)
ref_norm = (
conds_out[0] * reference_cfg - conds_out[1] * (reference_cfg - 1)
).norm(p=1)
cfg_norm = (
conds_out[0] * cond_scale - conds_out[1] * (cond_scale - 1)
).norm(p=1)
# Fix: Prevent division by zero
if cfg_norm == 0:
return conds_out
new_scale = cond_scale * ref_norm / cfg_norm
# Fix: Prevent division by zero
if cond_scale <= 1:
return conds_out
fallback_weight = (new_scale - 1) / (cond_scale - 1)
conds_out[1] = conds_out[0] * (1 - fallback_weight) + conds_out[1] * fallback_weight
elif method in ["linear_distance","squared_distance","root_distance"]:
conds_out[1] = interpolated_scales(x_orig,conds_out[0],conds_out[1],cond_scale,reference_CFG,method=="squared_distance",method=="root_distance")
conds_out[1] = (
conds_out[0] * (1 - fallback_weight)
+ conds_out[1] * fallback_weight
)
elif method in ["linear_distance", "squared_distance", "root_distance"]:
conds_out[1] = interpolated_scales(
x_orig,
conds_out[0],
conds_out[1],
cond_scale,
reference_cfg,
method == "squared_distance",
method == "root_distance",
)
return conds_out
m = model.clone()
m.set_model_sampler_pre_cfg_function(pre_cfg_patch)
return (m, )
return io.NodeOutput(m)
@torch.no_grad()
def interpolated_scales(x_orig,cond,uncond,cond_scale,small_scale,squared=False,root_dist=False):
deltacfg_normal = x_orig - cond_scale * cond - (cond_scale - 1) * uncond
deltacfg_small = x_orig - small_scale * cond - (small_scale - 1) * uncond
absdiff = (deltacfg_normal - deltacfg_small).abs()
absdiff = (absdiff-absdiff.min()) / (absdiff.max()-absdiff.min())
if squared:
absdiff = absdiff ** 2
elif root_dist:
absdiff = absdiff ** 0.5
new_scale = (small_scale - 1) / (cond_scale - 1)
smaller_uncond = cond * (1 - new_scale) + uncond * new_scale
new_uncond = smaller_uncond * (1 - absdiff) + uncond * absdiff
return new_uncond
class SkimmedCFGExtension(ComfyExtension):
@override
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
CFG_Skimming_Single_Scale_Pre_CFG,
SkimFlipPreCFG,
ConstantSkimPreCFG,
SkimReplacePreCFG,
SkimmedCFG_LinInterp_CFG_PreCFG,
SkimmedCFG_LinInterp_DualScales_CFG_PreCFG,
DifferenceCFG_PreCFG,
]
async def comfy_entrypoint() -> SkimmedCFGExtension:
return SkimmedCFGExtension()