v4 read the prompt length from transformer_options['c_crossattn'], which ComfyUI never sets, so every block fell back to stock. It also stood down on any i2v scene (per-token mod rows). Both fixed; tested on a real tiny H3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
3.8 KiB
Python
104 lines
3.8 KiB
Python
"""Shadow negative on a real H3 forward.
|
|
|
|
The sampling run is ComfyUI's CFGGuider in real life; here the OUTER_SAMPLE
|
|
wrapper is called the way comfy/samplers.py calls it -- an executor whose
|
|
class_obj is the guider, with `conds` already copied -- around a real forward.
|
|
"""
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _needs_comfy(comfyui):
|
|
"""Imports comfy."""
|
|
|
|
|
|
class _Guider:
|
|
def __init__(self, negative):
|
|
self.conds = {"positive": [{"cross_attn": torch.zeros(1, 5, 48)}],
|
|
"negative": [] if negative is None else [{"cross_attn": negative}]}
|
|
|
|
|
|
class _Executor:
|
|
def __init__(self, guider, fn):
|
|
self.class_obj, self._fn = guider, fn
|
|
|
|
def __call__(self, *a, **k):
|
|
return self._fn()
|
|
|
|
|
|
def _sample(tiny, patched, negative, **run):
|
|
from comfy.patcher_extension import WrappersMP
|
|
result = {}
|
|
|
|
def go():
|
|
result["out"] = tiny.run(patched, **run)
|
|
|
|
wrappers = [w for ws in patched.wrappers.get(WrappersMP.OUTER_SAMPLE, {}).values() for w in ws]
|
|
call = go
|
|
for w in wrappers:
|
|
call = (lambda w, inner: (lambda: w(_Executor(_Guider(negative), inner))))(w, call)
|
|
call()
|
|
return result["out"]
|
|
|
|
|
|
def _load(tiny, **values):
|
|
from modules.sampling.modifiers.nodes import FunPackLoadModifiers
|
|
return FunPackLoadModifiers.execute(
|
|
tiny.patcher, {"shadow_negative": {"enabled": True, "end": 1.0, **values}}).result
|
|
|
|
|
|
def test_it_pushes_the_picture_when_there_is_a_negative(tiny_h3):
|
|
base = tiny_h3.run()
|
|
patched, status = _load(tiny_h3, video_scale=4.0, alpha=1.0)
|
|
assert "shadow_negative: picture 4" in status
|
|
out = _sample(tiny_h3, patched, torch.randn(1, 4, 48))
|
|
assert not torch.allclose(out[0], base[0]), "installed and changed nothing (v4's bug)"
|
|
# sound push is 1 = off, but attention mixes modalities through later blocks
|
|
assert torch.isfinite(out[0]).all() and torch.isfinite(out[1]).all()
|
|
|
|
|
|
def test_with_no_negative_prompt_the_output_is_untouched(tiny_h3):
|
|
base = tiny_h3.run()
|
|
patched, _ = _load(tiny_h3, video_scale=4.0, alpha=1.0)
|
|
out = _sample(tiny_h3, patched, None)
|
|
assert torch.allclose(out[0], base[0])
|
|
|
|
|
|
def test_a_different_negative_gives_a_different_result(tiny_h3):
|
|
patched, _ = _load(tiny_h3, video_scale=4.0, alpha=1.0)
|
|
a = _sample(tiny_h3, patched, torch.randn(1, 4, 48))
|
|
b = _sample(tiny_h3, patched, torch.randn(1, 4, 48))
|
|
assert not torch.allclose(a[0], b[0]), "the negative text does not reach the push"
|
|
|
|
|
|
def test_outside_the_step_window_it_does_nothing(tiny_h3):
|
|
base_late = tiny_h3.run(sigma=0.3, sigmas=torch.tensor([1.0, 0.7, 0.3, 0.0]))
|
|
patched, _ = _load(tiny_h3, video_scale=4.0, alpha=1.0, start=0.0, end=0.4)
|
|
late = _sample(tiny_h3, patched, torch.randn(1, 4, 48),
|
|
sigma=0.3, sigmas=torch.tensor([1.0, 0.7, 0.3, 0.0]))
|
|
assert torch.allclose(late[0], base_late[0])
|
|
|
|
|
|
def test_compose_leaves_a_block_another_feature_holds(tiny_h3):
|
|
from core import dit_hooks
|
|
held = tiny_h3.patcher.clone()
|
|
ran = []
|
|
dit_hooks.add_block_hook(held, "other", 2,
|
|
lambda a, e: (ran.append(1), e["original_block"](a))[1])
|
|
from modules.sampling import shadow_negative as m
|
|
note = m.install(held, {**{k: v["default"] for k, v in m.SETTINGS.items()},
|
|
"enabled": True, "compose": True, "end": 1.0}, key="funpack.sn")
|
|
assert "leaves block(s) 2" in note
|
|
_sample(tiny_h3, held, torch.randn(1, 4, 48))
|
|
assert ran == [1]
|
|
|
|
|
|
def test_it_runs_on_an_i2v_scene_with_a_pinned_first_frame(tiny_h3):
|
|
mask = tiny_h3.pinned_first_frame()
|
|
base = tiny_h3.run(denoise_mask=mask)
|
|
patched, _ = _load(tiny_h3, video_scale=4.0, alpha=1.0)
|
|
out = _sample(tiny_h3, patched, torch.randn(1, 4, 48), denoise_mask=mask)
|
|
assert not torch.allclose(out[0], base[0]), "per-token mod rows made it stand down"
|