From 32a16645b548608d3ac52dee7adcea9f8a552760 Mon Sep 17 00:00:00 2001 From: Chris Dumas Date: Fri, 4 Sep 2026 12:09:59 +0000 Subject: [PATCH] Use adaptive CUDA latent upscale chunking --- dumas_h3_latent_upscale.py | 24 ++++++++++++++---------- tests/test_dumas_h3_longvideos.py | 19 +++++++++++++++---- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/dumas_h3_latent_upscale.py b/dumas_h3_latent_upscale.py index 406e44d..3711992 100644 --- a/dumas_h3_latent_upscale.py +++ b/dumas_h3_latent_upscale.py @@ -55,12 +55,16 @@ def _uses_cuda_model_upscale(param): return device == "cuda" and (mode == "model" or has_model_name) -def _effective_temporal_params(param): +def _effective_temporal_params(param, frame_count=None): chunk_length = int(param.get("chunk_length", 0) or 0) temporal_overlap = int(param.get("temporal_overlap", 0) or 0) if _uses_cuda_model_upscale(param): - chunk_length = CUDA_MODEL_CHUNK_LENGTH if chunk_length <= 0 else min(chunk_length, CUDA_MODEL_CHUNK_LENGTH) - temporal_overlap = min(max(0, temporal_overlap), max(0, chunk_length - 17)) + if chunk_length <= 0: + chunk_length = 85 + temporal_overlap = 17 + if frame_count is not None and CUDA_MODEL_CHUNK_LENGTH < int(frame_count) <= chunk_length: + chunk_length = CUDA_MODEL_CHUNK_LENGTH + temporal_overlap = 0 return chunk_length, temporal_overlap @@ -771,13 +775,13 @@ def upscale_video_interp(video, param): def _upscale_video_temporal_chunks(video, param, upscaler): if video.device.type != "cpu": video = video.to(device="cpu", copy=True) - chunk_length, temporal_overlap = _effective_temporal_params(param) + t = int(video.shape[2]) + frame_count = _frames_for_tokens(t) + chunk_length, temporal_overlap = _effective_temporal_params(param, frame_count) chunk_param = dict(param) chunk_param["chunk_length"] = chunk_length chunk_param["temporal_overlap"] = temporal_overlap anchor_strength = float(param.get("anchor_strength", 0.999) or 0.999) - t = int(video.shape[2]) - frame_count = _frames_for_tokens(t) if chunk_length <= 0 or frame_count <= chunk_length: return upscaler(video, chunk_param) @@ -918,10 +922,10 @@ class H3LatentUpscaleParams: "tooltip": "Temporal fade schedule over each tile's sampling. Off keeps the fade fixed; narrowing shrinks it over steps; widening grows it over steps."}), "dynamic_fade_min": ("INT", {"default": 32, "min": 0, "max": 4096, "step": 32, "tooltip": "Minimum fade width used by dynamic_fade when it is enabled."}), - "chunk_length": ("INT", {"default": 17, "min": 17, "max": 100000, "step": 17, - "tooltip": "Temporal chunk length for latent upscale. CUDA model upscale is capped to 17 internally so short long-video shots do not bypass splitting and OOM."}), - "temporal_overlap": ("INT", {"default": 0, "min": 0, "max": 100000, "step": 17, - "tooltip": "Temporal overlap between latent chunks. CUDA model upscale uses 0 when capped to one H3 block to minimize peak VRAM."}), + "chunk_length": ("INT", {"default": 85, "min": 17, "max": 100000, "step": 17, + "tooltip": "Temporal chunk length for latent upscale. CUDA model upscale keeps this when it splits the shot, but uses 17/0 if this would otherwise process the whole shot as one OOM-prone batch."}), + "temporal_overlap": ("INT", {"default": 17, "min": 0, "max": 100000, "step": 17, + "tooltip": "Temporal overlap between latent chunks. 17 matches the upstream split example; CUDA model upscale drops overlap only for the emergency 17-frame guard path."}), "resize_conditioning": ("BOOLEAN", {"default": False, "tooltip": "Reserved for upstream split compatibility. Leave OFF unless you need the original fallback behavior."}), "anchor_strength": ("FLOAT", {"default": 0.999, "min": 0.0, "max": 1.0, "step": 0.01, diff --git a/tests/test_dumas_h3_longvideos.py b/tests/test_dumas_h3_longvideos.py index 2293e91..8ff3dbf 100644 --- a/tests/test_dumas_h3_longvideos.py +++ b/tests/test_dumas_h3_longvideos.py @@ -1171,8 +1171,8 @@ class DumasH3LongVideosHelperTests(unittest.TestCase): self.assertFalse(required["brightness_match"][1]["default"]) self.assertEqual(required["dynamic_fade"][1]["default"], "off") self.assertEqual(required["dynamic_fade_min"][1]["default"], 32) - self.assertEqual(required["chunk_length"][1]["default"], 17) - self.assertEqual(required["temporal_overlap"][1]["default"], 0) + self.assertEqual(required["chunk_length"][1]["default"], 85) + self.assertEqual(required["temporal_overlap"][1]["default"], 17) self.assertFalse(required["resize_conditioning"][1]["default"]) self.assertEqual(required["anchor_strength"][1]["default"], 0.999) @@ -1262,14 +1262,25 @@ class DumasH3LongVideosHelperTests(unittest.TestCase): self.assertEqual(smaller["chunk_length"], 17) self.assertEqual(smaller["temporal_overlap"], 0) - def test_cuda_model_temporal_params_cap_saved_workflows(self): + def test_cuda_model_temporal_params_keep_splitting_saved_workflows(self): latent = importlib.import_module("dumas_h3_latent_upscale") chunk_length, temporal_overlap = latent._effective_temporal_params({ "mode": "model", "device": "cuda", "chunk_length": 85, "temporal_overlap": 17, - }) + }, frame_count=124) + self.assertEqual(chunk_length, 85) + self.assertEqual(temporal_overlap, 17) + + def test_cuda_model_temporal_params_cap_single_chunk_saved_workflows(self): + latent = importlib.import_module("dumas_h3_latent_upscale") + chunk_length, temporal_overlap = latent._effective_temporal_params({ + "mode": "model", + "device": "cuda", + "chunk_length": 85, + "temporal_overlap": 17, + }, frame_count=85) self.assertEqual(chunk_length, 17) self.assertEqual(temporal_overlap, 0)