Harden H3 detail pass conditioning
This commit is contained in:
+31
-4
@@ -4031,6 +4031,21 @@ def _copy_sample_latent(out_latent):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _latent_with_replaced_samples(template_latent, sampled_latent):
|
||||||
|
"""Reuse the original latent payload, but swap in freshly sampled tensors."""
|
||||||
|
if not isinstance(template_latent, dict):
|
||||||
|
return sampled_latent
|
||||||
|
out = dict(template_latent)
|
||||||
|
if isinstance(sampled_latent, dict):
|
||||||
|
for key, value in sampled_latent.items():
|
||||||
|
if key != "samples" and key not in out:
|
||||||
|
out[key] = value
|
||||||
|
if "samples" in sampled_latent:
|
||||||
|
out["samples"] = sampled_latent["samples"]
|
||||||
|
return out
|
||||||
|
return sampled_latent
|
||||||
|
|
||||||
|
|
||||||
def _video_only_refined_latent(base_latent, refined_latent):
|
def _video_only_refined_latent(base_latent, refined_latent):
|
||||||
"""Keep the refined video latent, but preserve the original audio latent."""
|
"""Keep the refined video latent, but preserve the original audio latent."""
|
||||||
base = base_latent.get("samples") if isinstance(base_latent, dict) else None
|
base = base_latent.get("samples") if isinstance(base_latent, dict) else None
|
||||||
@@ -4047,6 +4062,16 @@ def _video_only_refined_latent(base_latent, refined_latent):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return refined_latent
|
return refined_latent
|
||||||
return refined_latent
|
return refined_latent
|
||||||
|
|
||||||
|
|
||||||
|
def _coerce_bool_flag(value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
text = value.strip().lower()
|
||||||
|
if text in ("", "0", "false", "no", "off", "none", "null"):
|
||||||
|
return False
|
||||||
|
if text in ("1", "true", "yes", "on"):
|
||||||
|
return True
|
||||||
|
return bool(value)
|
||||||
|
|
||||||
|
|
||||||
# --- ref2va reference conditioning ----------------------------------------
|
# --- ref2va reference conditioning ----------------------------------------
|
||||||
@@ -6148,7 +6173,7 @@ class H3LongVideos:
|
|||||||
"building a separate graph."}),
|
"building a separate graph."}),
|
||||||
"detail_sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler",
|
"detail_sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"default": "euler",
|
||||||
"tooltip": "Sampler used for the optional refinement pass."}),
|
"tooltip": "Sampler used for the optional refinement pass."}),
|
||||||
"detail_scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "karras",
|
"detail_scheduler": (comfy.samplers.KSampler.SCHEDULERS, {"default": "beta",
|
||||||
"tooltip": "Scheduler used for the optional refinement pass."}),
|
"tooltip": "Scheduler used for the optional refinement pass."}),
|
||||||
"detail_steps": ("INT", {"default": 8, "min": 1, "max": 200,
|
"detail_steps": ("INT", {"default": 8, "min": 1, "max": 200,
|
||||||
"tooltip": "Steps for the optional refinement pass."}),
|
"tooltip": "Steps for the optional refinement pass."}),
|
||||||
@@ -6178,7 +6203,7 @@ class H3LongVideos:
|
|||||||
def _render(self, model, clip, vae, audio_vae, negative, prompt, w, h, ln, fps, tiled, sa,
|
def _render(self, model, clip, vae, audio_vae, negative, prompt, w, h, ln, fps, tiled, sa,
|
||||||
handoff, decode_tile_frames=0, decode_tile_size=0,
|
handoff, decode_tile_frames=0, decode_tile_size=0,
|
||||||
refs=None, ref_image_size="match", ref_noise_aug=None, silent=False,
|
refs=None, ref_image_size="match", ref_noise_aug=None, silent=False,
|
||||||
detail_pass=False, detail_sampler_name="euler", detail_scheduler="karras",
|
detail_pass=False, detail_sampler_name="euler", detail_scheduler="beta",
|
||||||
detail_steps=8, detail_denoise=0.4):
|
detail_steps=8, detail_denoise=0.4):
|
||||||
positive, latent = _build_shot_conditioning(clip, vae, prompt, w, h, ln, fps, handoff,
|
positive, latent = _build_shot_conditioning(clip, vae, prompt, w, h, ln, fps, handoff,
|
||||||
ref_images=refs, ref_image_size=ref_image_size,
|
ref_images=refs, ref_image_size=ref_image_size,
|
||||||
@@ -6200,11 +6225,13 @@ class H3LongVideos:
|
|||||||
e._h3_stage = "sampling"
|
e._h3_stage = "sampling"
|
||||||
raise
|
raise
|
||||||
refined_out = out
|
refined_out = out
|
||||||
|
detail_pass = _coerce_bool_flag(detail_pass)
|
||||||
if detail_pass:
|
if detail_pass:
|
||||||
|
detail_latent = _latent_with_replaced_samples(latent, out)
|
||||||
try:
|
try:
|
||||||
(refined_out,) = nodes.common_ksampler(
|
(refined_out,) = nodes.common_ksampler(
|
||||||
model, seed, int(detail_steps), cfg, detail_sampler_name, detail_scheduler,
|
model, seed, int(detail_steps), cfg, detail_sampler_name, detail_scheduler,
|
||||||
positive, negative, out, denoise=float(detail_denoise))
|
positive, negative, detail_latent, denoise=float(detail_denoise))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if _is_oom(e):
|
if _is_oom(e):
|
||||||
e._h3_stage = "sampling"
|
e._h3_stage = "sampling"
|
||||||
@@ -6254,7 +6281,7 @@ class H3LongVideos:
|
|||||||
ref_image_5=None, ref_image_6=None, ref_image_7=None, ref_image_8=None,
|
ref_image_5=None, ref_image_6=None, ref_image_7=None, ref_image_8=None,
|
||||||
ref_image_9=None,
|
ref_image_9=None,
|
||||||
ref_mode="auto ref2v", ref_image_size="match", ref_noise_aug=0.95,
|
ref_mode="auto ref2v", ref_image_size="match", ref_noise_aug=0.95,
|
||||||
detail_pass=False, detail_sampler_name="euler", detail_scheduler="karras",
|
detail_pass=False, detail_sampler_name="euler", detail_scheduler="beta",
|
||||||
detail_steps=8, detail_denoise=0.4,
|
detail_steps=8, detail_denoise=0.4,
|
||||||
graph=None, node_id=None):
|
graph=None, node_id=None):
|
||||||
|
|
||||||
|
|||||||
@@ -226,15 +226,16 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
|||||||
handoff=None,
|
handoff=None,
|
||||||
detail_pass=True,
|
detail_pass=True,
|
||||||
detail_sampler_name="euler",
|
detail_sampler_name="euler",
|
||||||
detail_scheduler="karras",
|
detail_scheduler="beta",
|
||||||
detail_steps=5,
|
detail_steps=5,
|
||||||
detail_denoise=0.4,
|
detail_denoise=0.4,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(len(calls), 2)
|
self.assertEqual(len(calls), 2)
|
||||||
self.assertIs(calls[1][0][8], first_out)
|
self.assertIsNot(calls[1][0][8], first_out)
|
||||||
|
self.assertIs(calls[1][0][8]["samples"], first_out["samples"])
|
||||||
self.assertEqual(calls[1][0][4], "euler")
|
self.assertEqual(calls[1][0][4], "euler")
|
||||||
self.assertEqual(calls[1][0][5], "karras")
|
self.assertEqual(calls[1][0][5], "beta")
|
||||||
self.assertAlmostEqual(calls[1][1]["denoise"], 0.4)
|
self.assertAlmostEqual(calls[1][1]["denoise"], 0.4)
|
||||||
self.assertEqual(result[1], first_out)
|
self.assertEqual(result[1], first_out)
|
||||||
self.assertEqual(result[2][0].name, "v2")
|
self.assertEqual(result[2][0].name, "v2")
|
||||||
@@ -253,6 +254,48 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
self.module.comfy.nested_tensor.NestedTensor = original_nested
|
self.module.comfy.nested_tensor.NestedTensor = original_nested
|
||||||
|
|
||||||
|
def test_detail_pass_treats_falsey_strings_as_disabled(self):
|
||||||
|
calls = []
|
||||||
|
original_common_ksampler = self.module.nodes.common_ksampler
|
||||||
|
original_build = self.module._build_shot_conditioning
|
||||||
|
original_evict = self.module._evict_all_but
|
||||||
|
original_decode_video = self.module._decode_video
|
||||||
|
original_decode_audio = self.module._decode_audio
|
||||||
|
original_cleanup = self.module._deep_cleanup
|
||||||
|
try:
|
||||||
|
self.module.nodes.common_ksampler = lambda *args, **kwargs: (calls.append((args, kwargs)) or {"samples": "latent"},)
|
||||||
|
self.module._build_shot_conditioning = lambda *_args, **_kwargs: ("cond", {"samples": "base"})
|
||||||
|
self.module._evict_all_but = lambda *_args, **_kwargs: None
|
||||||
|
self.module._decode_video = lambda _vae, out_latent, *_args, **_kwargs: out_latent
|
||||||
|
self.module._decode_audio = lambda _vae, out_latent: out_latent
|
||||||
|
self.module._deep_cleanup = lambda: None
|
||||||
|
|
||||||
|
self.module.H3LongVideos()._render(
|
||||||
|
model=object(),
|
||||||
|
clip=object(),
|
||||||
|
vae=object(),
|
||||||
|
audio_vae=object(),
|
||||||
|
negative="negative",
|
||||||
|
prompt="beat",
|
||||||
|
w=128,
|
||||||
|
h=64,
|
||||||
|
ln=24,
|
||||||
|
fps=24,
|
||||||
|
tiled=False,
|
||||||
|
sa=(123, 20, 1.0, "res_multistep", "simple", 1.0),
|
||||||
|
handoff=None,
|
||||||
|
detail_pass="false",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(calls), 1)
|
||||||
|
finally:
|
||||||
|
self.module.nodes.common_ksampler = original_common_ksampler
|
||||||
|
self.module._build_shot_conditioning = original_build
|
||||||
|
self.module._evict_all_but = original_evict
|
||||||
|
self.module._decode_video = original_decode_video
|
||||||
|
self.module._decode_audio = original_decode_audio
|
||||||
|
self.module._deep_cleanup = original_cleanup
|
||||||
|
|
||||||
def test_distribute_generations_canonicalizes_per_shot_audio_and_anchor_directives(self):
|
def test_distribute_generations_canonicalizes_per_shot_audio_and_anchor_directives(self):
|
||||||
generations = self.module.distribute_generations(
|
generations = self.module.distribute_generations(
|
||||||
"",
|
"",
|
||||||
|
|||||||
Reference in New Issue
Block a user