A chunk that samples to completion and returns NaN/Inf currently sails through
everything: the scene blend spreads it into the already-finished scenes, the VAE
decodes it, and the first thing that objects is ffmpeg's AAC encoder — "Input
contains (near) NaN/+-Inf", naming nothing that produced it, after the whole
montage has been paid for. The video half never objects at all: NaN through
astype(np.uint8) is undefined but silent, so it degrades with a warning.
_sample_chunk now checks the returned latent and raises with the run-phase label
("scene 2/3 · pass 1 of 2"), which stream, how many values and what dtype, plus
the causes worth testing in order — mismatched LoRA, a mis-inferred checkpoint
layout, a dtype the model cannot hold. It raises rather than warns because a
non-finite latent is unrecoverable and blending it forward destroys the scenes
that were still good. One isfinite() per chunk against a multi-second sample is
not a cost worth weighing; a latent the helper cannot read is ignored, never
fatal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
63 lines
2.1 KiB
Python
63 lines
2.1 KiB
Python
"""A chunk that comes back non-finite stops the run where it happened.
|
|
|
|
Nothing downstream notices a NaN latent on its own: the scene blend spreads it into every
|
|
already-finished scene, the VAE decodes it, and the first thing to object is ffmpeg's AAC
|
|
encoder — naming nothing that produced it, after the whole montage has been paid for. The
|
|
video half never objects at all (NaN through astype(np.uint8) is undefined but silent).
|
|
"""
|
|
import sys
|
|
import types
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
sys.modules.setdefault("folder_paths", types.SimpleNamespace(models_dir=""))
|
|
|
|
from samplers import FunPackLTXAVSceneChainSampler # noqa: E402
|
|
|
|
|
|
@pytest.fixture
|
|
def node():
|
|
return FunPackLTXAVSceneChainSampler()
|
|
|
|
|
|
def test_a_clean_latent_passes_silently(node):
|
|
node._assert_finite_sample(torch.zeros(1, 4, 2, 8, 8))
|
|
|
|
|
|
def test_nan_in_the_video_stream_raises(node):
|
|
x = torch.zeros(1, 4, 2, 8, 8)
|
|
x[0, 0, 0, 0, 0] = float("nan")
|
|
with pytest.raises(RuntimeError) as e:
|
|
node._assert_finite_sample(x)
|
|
assert "non-finite latent" in str(e.value)
|
|
assert "video" in str(e.value)
|
|
|
|
|
|
def test_inf_counts_too(node):
|
|
"""+-Inf is what the AAC encoder actually names, and it is just as unrecoverable."""
|
|
x = torch.zeros(1, 4, 2, 8, 8)
|
|
x[0, 0, 0, 0, 0] = float("inf")
|
|
with pytest.raises(RuntimeError):
|
|
node._assert_finite_sample(x)
|
|
|
|
|
|
def test_the_message_says_how_many_and_which_stream(node):
|
|
x = torch.zeros(2, 4, 2, 4, 4)
|
|
x[0, 0, 0, 0, 0] = float("nan")
|
|
x[0, 0, 0, 0, 1] = float("nan")
|
|
with pytest.raises(RuntimeError) as e:
|
|
node._assert_finite_sample(x)
|
|
msg = str(e.value)
|
|
assert "2/" in msg # count of bad values
|
|
assert "LoRA" in msg # the first thing worth testing
|
|
assert str(x.dtype) in msg
|
|
|
|
|
|
def test_the_check_never_fails_the_run_on_its_own(node):
|
|
"""A latent shape the helper cannot read is not a reason to kill a good render."""
|
|
node._assert_finite_sample("not a tensor")
|
|
node._assert_finite_sample(None)
|