fix: shadow negative crashed with 'aimdo memory compile error'

It replaces every block's forward, so the model compiler must be off for that
call, as it already is for block repeat and REINS injection. Never seen before
because until d4d4e29 shadow negative never ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-09-27 17:58:00 +03:00
co-authored by Claude Opus 5.5
parent 88e4dbceb5
commit 1cc60d1f75
2 changed files with 60 additions and 7 deletions
+13 -3
View File
@@ -3854,8 +3854,8 @@ class FunPackLTXAVSceneChainSampler:
def _compiler_disabled_for_forward_patches(self, active):
"""Comfy's model compiler (aimdo malloc-graph + CUDA graphs) assumes every
block-scope forward allocates the same pattern call to call. H3 representation
steering while actually injecting (not passive-capture) and block repeat both
patch what a block's forward allocates, which crashes it with
steering while actually injecting (not passive-capture), block repeat and shadow
negative all patch what a block's forward allocates, which crashes it with
"aimdo memory compile error" -- confirmed against a real rental log where the
identical workflow succeeds with REINS in passive-capture-only mode (no
injection branch entered) and fails the moment strength > 0 (injection
@@ -4061,19 +4061,29 @@ class FunPackLTXAVSceneChainSampler:
model, refinement_key, h3_q_steer_strength, _q_steer_capture,
steer_block=h3_q_steer_block)
_shadow_installed = False
if self._is_h3 and h3_shadow_negative:
_model_before_shadow = model
model = self._install_h3_shadow_negative(
model, negative, h3_shadow_negative_video_scale,
h3_shadow_negative_audio_scale, h3_shadow_negative_tau,
h3_shadow_negative_alpha, h3_shadow_negative_start_percent,
h3_shadow_negative_end_percent, compose=h3_shadow_negative_compose)
# Same identity test as block repeat: the installer returns the SAME model when
# it declines (no negative, both scales 1.0, non-finite knob).
_shadow_installed = model is not _model_before_shadow
_phrase_probe = self._install_phrase_probe(model, positive, latent)
if _phrase_probe is not None:
model = _phrase_probe[0]
_repr_injecting = bool(_repr_steering_installed and h3_repr_steering
and float(h3_repr_steering_strength or 0.0) > 0.0)
_compiler_conflict = _block_repeat_installed or _repr_injecting
# Shadow negative replaces every hooked block's forward with its own two-stream one
# (a second attention pass, extra clones), so its allocation pattern differs from
# the stock block's -- the same "aimdo memory compile error" block repeat and REINS
# injection hit. It never tripped this before 2026-09-27 only because, until then,
# it silently never ran.
_compiler_conflict = _block_repeat_installed or _repr_injecting or _shadow_installed
try:
with self._compiler_disabled_for_forward_patches(_compiler_conflict):
sampled = comfy.sample.sample_custom(
@@ -33,7 +33,8 @@ class _FakeModel:
return m
def _run_chunk_with(monkeypatch, seen, refinement_key="fake_key", model=None, **kw):
def _run_chunk_with(monkeypatch, seen, refinement_key="fake_key", model=None, negative=None,
is_h3=False, **kw):
"""Stubs sample_custom to record whether disable_comfy_compiler was set DURING the
call, then _sample_chunk runs for real so the gating logic (h3_repr_steering /
h3_repr_steering_strength / h3_block_repeat) executes exactly as it would in
@@ -48,9 +49,11 @@ def _run_chunk_with(monkeypatch, seen, refinement_key="fake_key", model=None, **
monkeypatch.setattr(sys.modules["comfy.sample"], "sample_custom", _fake_sample_custom)
latent = {"samples": torch.zeros(1, 2, 2, 2, 2)}
sampler = type("F", (), {"extra_options": {}, "sampler_function": None})()
S()._sample_chunk(model if model is not None else object(), sampler,
torch.tensor([1.0, 0.0]), 0, 1.0, [], [], latent,
refinement_key=refinement_key, **kw)
node = S()
node._is_h3 = is_h3
node._sample_chunk(model if model is not None else object(), sampler,
torch.tensor([1.0, 0.0]), 0, 1.0, [], negative or [], latent,
refinement_key=refinement_key, **kw)
def test_reins_actively_injecting_disables_the_compiler(monkeypatch):
@@ -166,3 +169,43 @@ def test_crash_survives_the_sample_call_and_still_restores(monkeypatch):
except RuntimeError:
pass
assert not hasattr(comfy.cli_args.args, "disable_comfy_compiler")
class _FakeH3Model(_FakeModel):
"""Enough for _install_h3_shadow_negative to really install (it needs the diffusion
model's blocks and hidden size), so the compiler test cannot pass on a declined one."""
def __init__(self):
super().__init__()
self._dm = types.SimpleNamespace(blocks=[object(), object()], hidden_size=16)
def clone(self):
m = _FakeH3Model()
m.model_options = dict(self.model_options)
m._dm = self._dm
return m
def get_model_object(self, name):
return self._dm
_SHADOW = dict(h3_shadow_negative=True, h3_shadow_negative_video_scale=3.0,
h3_shadow_negative_audio_scale=1.0, h3_shadow_negative_tau=2.5,
h3_shadow_negative_alpha=0.35, h3_shadow_negative_start_percent=0.0,
h3_shadow_negative_end_percent=0.6)
def test_shadow_negative_disables_the_compiler(monkeypatch):
"""It replaces every block's forward with a two-stream one: the same aimdo compile
error, first seen on a rental 2026-09-27 -- the day it started actually running."""
seen = []
_run_chunk_with(monkeypatch, seen, model=_FakeH3Model(), is_h3=True,
negative=[(torch.randn(1, 3, 16), {})], **_SHADOW)
assert seen == [True]
assert not hasattr(comfy.cli_args.args, "disable_comfy_compiler")
def test_shadow_negative_that_declines_leaves_the_compiler_alone(monkeypatch):
"""No negative prompt: nothing is installed, so nothing conflicts."""
seen = []
_run_chunk_with(monkeypatch, seen, model=_FakeH3Model(), is_h3=True, **_SHADOW)
assert seen == ["MISSING"]