Files
olivv-cs-ComfyUI-FunPack/tests/test_h3_shadow_negative_install.py
T
digital-garbageandClaude Sonnet 5 68d1657a50 feat: H3 shadow negative -- optional coexistence with REINS/Q-steer
h3_shadow_negative's non-composing install was silently starving REINS/
Q-steer of their own per-block capture, so ratings never trained them while
shadow negative was on (0/0 liked/disliked no matter how much was rated).

Add h3_shadow_negative_compose ("Allow when REINS/Q-steer are active"): when
on, shadow negative skips any block another dit-patch mechanism already
claimed, leaving it to keep capturing/steering there, and only pushes on the
rest. Structural coexistence, not per-block math blending -- disclosed as
such in the docstring and tooltip. Off by default, unchanged behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 14:37:01 +03:00

236 lines
9.9 KiB
Python

"""_install_h3_shadow_negative: install/no-op behaviour at the model-patching level.
Exercised with fake model/diffusion-model stand-ins -- no real H3 weights or GPU needed.
The per-block math itself (nag_blend, presentation-span detection, ...) is covered in
tests/test_h3_shadow_negative.py; this file is about whether the installer clones the
right model, hooks the right blocks, and is honest about not composing with the other
dit-patch mechanisms.
"""
import sys
from pathlib import Path
import torch
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
sys.path.insert(0, ".")
import _comfy_stubs # noqa: E402, F401
from samplers import FunPackLTXAVSceneChainSampler as S # noqa: E402
class _FakeDiffusionModel:
def __init__(self, n_blocks=4):
self.blocks = [object() for _ in range(n_blocks)]
self.hidden_size = 16
class _FakeModel:
def __init__(self, dm=None):
self.model_options = {}
self._dm = dm if dm is not None else _FakeDiffusionModel()
def clone(self):
m = _FakeModel(self._dm)
m.model_options = dict(self.model_options)
return m
def get_model_object(self, name):
if name == "diffusion_model":
return self._dm
raise KeyError(name)
def _negative(tensor=None):
return [(tensor if tensor is not None else torch.randn(1, 3, 16), {})]
def test_both_scales_at_one_is_a_true_noop():
model = _FakeModel()
node = S()
out = node._install_h3_shadow_negative(model, _negative(), 1.0, 1.0, 2.5, 0.35, 0.0, 0.6)
assert out is model
def test_unparseable_scale_is_a_noop():
model = _FakeModel()
node = S()
out = node._install_h3_shadow_negative(model, _negative(), "nope", 1.0, 2.5, 0.35, 0.0, 0.6)
assert out is model
def test_nan_scale_is_a_noop_not_a_silent_corruption(capsys):
"""NaN fails `video_scale == 1.0`, so that alone would let it through and NAG-blend
every row to NaN with no warning -- must be caught explicitly, same as the codebase's
other dit-patch strength guards (av_decouple, q_steering)."""
model = _FakeModel()
node = S()
out = node._install_h3_shadow_negative(model, _negative(), float("nan"), 1.0,
2.5, 0.35, 0.0, 0.6)
assert out is model
assert "NaN or infinite" in capsys.readouterr().out
def test_inf_tau_is_a_noop():
model = _FakeModel()
node = S()
out = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0,
float("inf"), 0.35, 0.0, 0.6)
assert out is model
def test_nan_percent_is_a_noop():
model = _FakeModel()
node = S()
out = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0,
2.5, 0.35, float("nan"), 0.6)
assert out is model
def test_missing_negative_is_a_noop():
model = _FakeModel()
node = S()
out = node._install_h3_shadow_negative(model, [], 3.0, 1.0, 2.5, 0.35, 0.0, 0.6)
assert out is model
def test_negative_with_empty_tensor_is_a_noop():
model = _FakeModel()
node = S()
out = node._install_h3_shadow_negative(model, _negative(torch.zeros(1, 0, 16)),
3.0, 1.0, 2.5, 0.35, 0.0, 0.6)
assert out is model
def test_a_model_with_no_double_blocks_is_a_noop():
class _NoBlocks:
pass
model = _FakeModel(dm=_NoBlocks())
node = S()
out = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6)
assert out is model
def test_enabled_clones_and_hooks_every_block():
model = _FakeModel(dm=_FakeDiffusionModel(n_blocks=5))
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6)
assert patched is not model
dit = patched.model_options["transformer_options"]["patches_replace"]["dit"]
assert {k for k in dit if k[0] == "double_block"} == {("double_block", i) for i in range(5)}
def test_swapped_start_and_end_percent_are_normalized():
"""A user typing 0.6/0.0 instead of 0.0/0.6 should not silently produce an empty
(start > end) active window -- the installer should sort them."""
import h3_shadow_negative as sn
model = _FakeModel()
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.6, 0.0)
hook = patched.model_options["transformer_options"]["patches_replace"]["dit"][("double_block", 0)]
state = next(c.cell_contents for c, name in
zip(hook.__closure__, hook.__code__.co_freevars)
if name == "state" and isinstance(c.cell_contents, sn.ShadowState))
assert state.start_percent == 0.0 and state.end_percent == 0.6
def test_supersedes_an_existing_block_patch_rather_than_composing(capsys):
"""Non-composing by explicit design: it must fully replace whatever dit-patch mechanism
(REINS/Q-steer/av_decouple/block-repeat/attn_temperature) already hooked a block, and
say so loudly rather than silently overwrite it."""
model = _FakeModel(dm=_FakeDiffusionModel(n_blocks=2))
model.model_options["transformer_options"] = {"patches_replace": {"dit": {
("double_block", 0): lambda args, extra: {"img": args["img"] * 0.0},
}}}
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6)
out = capsys.readouterr().out
assert "supersedes" in out
dit = patched.model_options["transformer_options"]["patches_replace"]["dit"]
# The old hook must be GONE, not chained through -- calling the new one must not
# reduce to zero the way the old one did.
img = torch.ones(3, 4)
result = dit[("double_block", 0)]({"img": img, "mod_segments": []},
{"original_block": lambda a: {"img": a["img"]}})
assert torch.allclose(result["img"], img), "should fall through to original_block, not the old hook"
def test_a_hooked_block_with_no_mod_segments_falls_through_untouched():
model = _FakeModel(dm=_FakeDiffusionModel(n_blocks=1))
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6)
dit = patched.model_options["transformer_options"]["patches_replace"]["dit"]
img = torch.ones(3, 4)
out = dit[("double_block", 0)]({"img": img, "mod_segments": []},
{"original_block": lambda a: {"img": a["img"] * 2.0}})
assert torch.allclose(out["img"], img * 2.0)
# --- compose=True: coexist with, rather than supersede, an already-claimed block ----------
def test_compose_leaves_an_already_claimed_block_untouched(capsys):
"""The whole point of compose: REINS' own block keeps its ORIGINAL hook (whatever it
was), completely unreplaced, so REINS keeps capturing/steering there."""
model = _FakeModel(dm=_FakeDiffusionModel(n_blocks=3))
original_reins_hook = lambda args, extra: {"img": args["img"] * 0.0}
model.model_options["transformer_options"] = {"patches_replace": {"dit": {
("double_block", 1): original_reins_hook,
}}}
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6,
compose=True)
dit = patched.model_options["transformer_options"]["patches_replace"]["dit"]
assert dit[("double_block", 1)] is original_reins_hook, "claimed block must be untouched"
assert "supersedes" not in capsys.readouterr().out
def test_compose_still_hooks_every_unclaimed_block():
model = _FakeModel(dm=_FakeDiffusionModel(n_blocks=3))
model.model_options["transformer_options"] = {"patches_replace": {"dit": {
("double_block", 1): lambda args, extra: {"img": args["img"]},
}}}
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6,
compose=True)
dit = patched.model_options["transformer_options"]["patches_replace"]["dit"]
import h3_shadow_negative as sn
for i in (0, 2):
hook = dit[("double_block", i)]
state = next((c.cell_contents for c, name in
zip(hook.__closure__, hook.__code__.co_freevars)
if name == "state" and isinstance(c.cell_contents, sn.ShadowState)), None)
assert state is not None, f"block {i} should have been hooked by shadow negative"
def test_compose_off_still_supersedes_by_default(capsys):
"""compose defaults to False, so existing behaviour (and existing tests/workflows) is
unchanged unless the new checkbox is explicitly turned on."""
model = _FakeModel(dm=_FakeDiffusionModel(n_blocks=2))
model.model_options["transformer_options"] = {"patches_replace": {"dit": {
("double_block", 0): lambda args, extra: {"img": args["img"] * 0.0},
}}}
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6)
out = capsys.readouterr().out
assert "supersedes" in out
dit = patched.model_options["transformer_options"]["patches_replace"]["dit"]
assert dit[("double_block", 0)] is not None
img = torch.ones(3, 4)
result = dit[("double_block", 0)]({"img": img, "mod_segments": []},
{"original_block": lambda a: {"img": a["img"]}})
assert torch.allclose(result["img"], img), "default is still supersede, not compose"
def test_compose_with_nothing_claimed_hooks_everything_like_the_default():
model = _FakeModel(dm=_FakeDiffusionModel(n_blocks=4))
node = S()
patched = node._install_h3_shadow_negative(model, _negative(), 3.0, 1.0, 2.5, 0.35, 0.0, 0.6,
compose=True)
dit = patched.model_options["transformer_options"]["patches_replace"]["dit"]
assert {k for k in dit if k[0] == "double_block"} == {("double_block", i) for i in range(4)}
if __name__ == "__main__":
test_both_scales_at_one_is_a_true_noop()
test_enabled_clones_and_hooks_every_block()
print("ok (run via pytest for the rest)")