diff --git a/README.md b/README.md index 496d84b..ddef1a6 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ The pack also has a second node, **Minimax H3 Long Generation**, for long text-t ## What's new +**1.1.2** +- Much faster start with tiles + `plate`: all plate pieces are now encoded first (video VAE), then all prompts (text encoder). Before, the two big models were swapped onto the GPU once per window × tile, which on long, tiled shots could take a very long time before the first sampling step. +- Progress before sampling: the console shows `encoding plate piece 3/12 …` / `encoding prompt 5/12 …`, and the node's progress bar moves, so a long start no longer looks stuck. + **1.1.1** - Long Generation: every action that falls inside a window is listed in that window's timeline again, even one that starts near the window's end. Holding those back (1.1.0) made the next window jump to the new action, which caused more cuts; listing them lets H3 set the action up inside the same shot. Actions may start a little earlier than scheduled — space your timeline a bit more if they do. @@ -132,7 +136,7 @@ Everything else is the stock H3 workflow: Reference to Video (with your video as - `plate` must be **the same clip**: same first frame, same frames, at least as long — just at higher resolution. - Feed it at about **your output resolution**. A 4K plate works but takes a lot of RAM (about 100 MB per frame). -**Cost.** Each tile's plate piece is encoded once with the VAE before sampling, and bigger plate pieces mean more reference tokens at every sampling step. At `plate_scale` 1.0, expect a run to take about 1.6× as long as without the plate (in testing: 1330 s vs 812 s); lower `plate_scale` to get most of that time back. +**Cost.** Before the first sampling step, the node encodes every window × tile once: first all plate pieces, then all prompts (the console shows the progress). With many tiles and windows this takes a while. Each tile's plate piece is encoded once with the VAE before sampling, and bigger plate pieces mean more reference tokens at every sampling step. At `plate_scale` 1.0, expect a run to take about 1.6× as long as without the plate (in testing: 1330 s vs 812 s); lower `plate_scale` to get most of that time back. ## Tips diff --git a/h3_windows.py b/h3_windows.py index 60ecc7b..29a4257 100644 --- a/h3_windows.py +++ b/h3_windows.py @@ -370,7 +370,8 @@ def plate_refs(orig, refs, k0, k1, ys, xs, lat_h, lat_w, plate, warned, cache): return refs -def piece_conds(conds, k0, k1, ys, xs, lat_h, lat_w, crop_images, carry, warned, text_cache, plate=None, gen=None): +def piece_conds(conds, k0, k1, ys, xs, lat_h, lat_w, crop_images, carry, warned, text_cache, plate=None, gen=None, + text=True): out = {} split_space = ys is not None f0, frames = frames_for_tokens(k0), frames_for_tokens(k1) - frames_for_tokens(k0) @@ -393,7 +394,7 @@ def piece_conds(conds, k0, k1, ys, xs, lat_h, lat_w, crop_images, carry, warned, kfs = kfs + [carry] if kfs or "minimax_keyframes" in c: c["minimax_keyframes"] = kfs - source = c.get(TEXT_SOURCE) + source = c.get(TEXT_SOURCE) if text else None if source is not None: key = (id(source), k0, k1, ys, xs) if key not in text_cache: @@ -573,12 +574,37 @@ class WindowsWrapper: gen = self.generation_state(windows, base_conds, latent_t) if self.schedule is not None else None - # encode every piece's prompt up front, before the DiT is loaded + # Encode everything up front, before the DiT is loaded, one model at a time: first every plate piece + # (video VAE), then every prompt (text encoder). Interleaving them made the two big models swap for + # every piece. text_cache = {} - for t0, tn in windows: - for yi, xi in tiles: + pieces = [(t0, tn, yi, xi) for t0, tn in windows for yi, xi in tiles] + has_source = any(c.get(TEXT_SOURCE) is not None for lst in base_conds.values() for c in lst) + stages = [] + if self.plate is not None and split_space: + stages.append(("plate piece", False)) + if has_source: + stages.append(("prompt", True)) + total_jobs = len(pieces) * len(stages) + pbar = comfy.utils.ProgressBar(total_jobs) if total_jobs and len(pieces) > 1 else None + done_jobs = 0 + for label, with_text in stages: + for i, (t0, tn, yi, xi) in enumerate(pieces): + comfy.model_management.throw_exception_if_processing_interrupted() + if len(pieces) > 1: + logging.info("MiniMax H3 windows: encoding {} {}/{} (window {}/{}, tile {}/{})".format( + label, i + 1, len(pieces), windows.index((t0, tn)) + 1, len(windows), + tiles.index((yi, xi)) + 1, len(tiles))) piece_conds(base_conds, t0, t0 + tn, ys[yi] if split_space else None, xs[xi] if split_space else None, - lat_h, lat_w, self.crop_images, None, self.warned, text_cache, self.plate, gen) + lat_h, lat_w, self.crop_images, None, self.warned, text_cache, self.plate, gen, text=with_text) + done_jobs += 1 + if pbar is not None: + pbar.update_absolute(done_jobs, total_jobs) + if not stages: + for t0, tn in windows: + for yi, xi in tiles: + piece_conds(base_conds, t0, t0 + tn, ys[yi] if split_space else None, xs[xi] if split_space else None, + lat_h, lat_w, self.crop_images, None, self.warned, text_cache, self.plate, gen) has_video = any(b.get("kind") in ("video", "video_audio") for lst in base_conds.values() for c in lst for b in (c.get("minimax_refs") or [])) if any(k[0] != "plate" for k in text_cache): diff --git a/pyproject.toml b/pyproject.toml index c808329..0ee6318 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-minimax-h3-temporal-window-spatial-tiles" description = "Minimax H3 V2V Temporal Window & Spatial Tiles: long and high-resolution video-to-video for the open-weights MiniMax H3, as overlapping temporal windows sampled one after another plus spatial tiles that follow their exact crop of the plate. Also includes Minimax H3 Long Generation: long text/image-to-video with a per-window prompt timeline." -version = "1.1.1" +version = "1.1.2" license = { file = "LICENSE" } dependencies = [] diff --git a/tests/test_windows.py b/tests/test_windows.py index e4e5f7b..96f9254 100644 --- a/tests/test_windows.py +++ b/tests/test_windows.py @@ -451,6 +451,21 @@ def test_full_res_plate(model): check("full-res plate: every tile's