1.1.2: encode plate pieces then prompts (no model ping-pong), progress before sampling

This commit is contained in:
mo-akkakk
2026-11-08 21:46:14 +01:00
parent 6be3267d6f
commit 5b1b8f06ea
4 changed files with 53 additions and 8 deletions
+5 -1
View File
@@ -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
+32 -6
View File
@@ -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):
+1 -1
View File
@@ -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 = []
+15
View File
@@ -451,6 +451,21 @@ def test_full_res_plate(model):
check("full-res plate: every tile's <Video 1> is encoded from its exact crop of the plate ({} pieces sampled)".format(len(pieces)),
bool(ok and ok_shape and ok_px))
# encoding order: every plate piece (VAE) first, then every prompt (text encoder) - no model ping-pong
order = []
o_pl, o_pt = hw.plate_latent, hw.piece_text
hw.plate_latent = lambda *a, **k: (order.append("vae"), o_pl(*a, **k))[1]
hw.piece_text = lambda *a, **k: (order.append("text"), o_pt(*a, **k))[1]
try:
m, c = patch(model, FakeClip(), frames=73, overlap=22, tiles=True, tw=tw, th=th, tov=tov, plate=plate2x, vae=FakeVAE())
pos, lat = conditioning(c, plate, ref, W, H, N, guide=False)
run(m, pos, lat, sigmas)
finally:
hw.plate_latent, hw.piece_text = o_pl, o_pt
n_v, n_t = order.count("vae"), order.count("text")
check("encoding order: all {} plate pieces, then all {} prompts (one model switch, not one per piece)".format(n_v, n_t),
n_v > 1 and n_t > 1 and order == ["vae"] * n_v + ["text"] * n_t)
m, c = patch(model, FakeClip(), frames=73, overlap=22, tiles=True, tw=tw, th=th, tov=tov, plate=plate2x, vae=FakeVAE(), plate_scale=0.5)
pos, lat = conditioning(c, plate, ref, W, H, N, guide=False)
seen.clear()