Adjust MiniMah h3 sizing for videos to downscale to match

This commit is contained in:
Jaret Burkett
2026-08-15 07:50:42 -06:00
parent 70b1089359
commit 151ad0e959
5 changed files with 36 additions and 26 deletions

View File

@@ -1330,7 +1330,7 @@ class MinimaxH3Ref2VAModel(MinimaxH3Model):
blocks = [(1, h, w, 0) for h, w in ref_shapes] blocks = [(1, h, w, 0) for h, w in ref_shapes]
audio_rows = [] audio_rows = []
self._append_video_ref_blocks( self._append_video_ref_blocks(
batch, all_rows, audio_rows, blocks, device, dtype batch, all_rows, audio_rows, blocks, device, dtype, target_h, target_w
) )
if not all_rows: if not all_rows:
return None, None, (), () return None, None, (), ()
@@ -1364,8 +1364,10 @@ class MinimaxH3Ref2VAModel(MinimaxH3Model):
n = packing.align_num_frames_down(max(len(frames), 5)) n = packing.align_num_frames_down(max(len(frames), 5))
frames = frames[:n] frames = frames[:n]
h0, w0 = frames[0].shape[:2] h0, w0 = frames[0].shape[:2]
# ComfyUI reference-video sizing (independent of the sample canvas) # match the sample canvas's pixel area, own aspect kept
ph, pw = packing.reference_video_pixel_size(w0, h0) ph, pw = packing.reference_video_pixel_size(
w0, h0, gen_config.height, gen_config.width
)
pixels = torch.from_numpy(np.stack(frames)).float() / 255.0 * 2.0 - 1.0 pixels = torch.from_numpy(np.stack(frames)).float() / 255.0 * 2.0 - 1.0
pixels = pixels.permute(3, 0, 1, 2)[None] # (1, 3, T, H, W) pixels = pixels.permute(3, 0, 1, 2)[None] # (1, 3, T, H, W)
pixels = ( pixels = (
@@ -1405,10 +1407,11 @@ class MinimaxH3Ref2VAModel(MinimaxH3Model):
return {"latent": latents[0].float(), "audio_rows": audio_rows} return {"latent": latents[0].float(), "audio_rows": audio_rows}
def _append_video_ref_blocks( def _append_video_ref_blocks(
self, batch, all_rows, audio_rows, blocks, device, dtype self, batch, all_rows, audio_rows, blocks, device, dtype, target_h, target_w
): ):
# control videos get dataset-identical treatment (frame count, fps, # control videos get the dataset's temporal treatment (frame count,
# bucket) and one VAE encode, disk-cached next to the video; the # fps), are area-matched to the target, and get one VAE encode
# disk-cached next to the video; the
# resulting latents become multi-frame reference blocks packed after # resulting latents become multi-frame reference blocks packed after
# the image references # the image references
paths_per_item = getattr(batch, "control_video_paths_list", None) paths_per_item = getattr(batch, "control_video_paths_list", None)
@@ -1425,7 +1428,7 @@ class MinimaxH3Ref2VAModel(MinimaxH3Model):
auds = [] auds = []
for per_item in paths_per_item: for per_item in paths_per_item:
entry = load_ref_video_latent( entry = load_ref_video_latent(
self, per_item[ref_idx], batch.dataset_config self, per_item[ref_idx], batch.dataset_config, target_h, target_w
) )
lats.append(entry["latent"].to(device, torch.float32)) lats.append(entry["latent"].to(device, torch.float32))
auds.append(entry.get("audio_rows")) auds.append(entry.get("audio_rows"))

View File

@@ -124,16 +124,17 @@ def reference_pixel_size(
return height, width return height, width
def reference_video_pixel_size(ref_width: int, ref_height: int) -> Tuple[int, int]: def reference_video_pixel_size(
"""Reference VIDEO sizing (ComfyUI): the 768-short-edge canvas with the ref_width: int, ref_height: int, target_height: int, target_width: int
768*1344 area cap, or the native size (rounded to /32) when the source is ) -> Tuple[int, int]:
smaller than that canvas. Returns (height, width).""" """Reference VIDEO sizing: match the TARGET's pixel area with the ref's own
ch, cw = resolve_canvas_size(ref_width, ref_height) aspect kept (same aspect -> exactly the target size; other aspects -> the
if ref_width * ref_height < cw * ch: same pixel budget on the /32 grid). Returns (height, width)."""
m = CANVAS_MULTIPLE scale = math.sqrt((target_height * target_width) / float(ref_width * ref_height))
cw = max(m, round(ref_width / m) * m) m = CANVAS_MULTIPLE
ch = max(m, round(ref_height / m) * m) height = max(m, round(ref_height * scale / m) * m)
return ch, cw width = max(m, round(ref_width * scale / m) * m)
return height, width
def prepare_reference_image( def prepare_reference_image(

View File

@@ -1,8 +1,8 @@
"""Reference-video latents for ref2va, without dataloader machinery. """Reference-video latents for ref2va, without dataloader machinery.
A control VIDEO gets the dataset's temporal treatment — num_frames / A control VIDEO gets the dataset's temporal treatment — num_frames /
auto_frame_count, fps — and ComfyUI's reference sizing (768-short-edge canvas auto_frame_count, fps — and is area-matched to the target (own aspect kept, /32
or native when smaller, aspect kept), then a single VAE encode whose grid), then a single VAE encode whose
result is cached next to the video in ``_latent_cache/`` (keyed like normal result is cached next to the video in ``_latent_cache/`` (keyed like normal
latent caches: file signature + the config values that shape the latent). latent caches: file signature + the config values that shape the latent).
Everything is deterministic (even frame spread, no random start) so the cache Everything is deterministic (even frame spread, no random start) so the cache
@@ -112,7 +112,9 @@ def _cache_path(path: str, hash_dict: dict) -> str:
@torch.no_grad() @torch.no_grad()
def load_ref_video_latent(model, path: str, dataset_config) -> dict: def load_ref_video_latent(
model, path: str, dataset_config, target_height: int, target_width: int
) -> dict:
"""Returns {"latent": (C, T, h, w) cpu tensor, "num_frames": int}, """Returns {"latent": (C, T, h, w) cpu tensor, "num_frames": int},
encoding + disk-caching on first use. ``model`` is the MinimaxH3 model encoding + disk-caching on first use. ``model`` is the MinimaxH3 model
(used for the VAE, audio encode and the frame-count snapper).""" (used for the VAE, audio encode and the frame-count snapper)."""
@@ -145,7 +147,10 @@ def load_ref_video_latent(model, path: str, dataset_config) -> dict:
) )
hash_dict = { hash_dict = {
"signature": get_quick_signature_string(path), "signature": get_quick_signature_string(path),
"ref_sizing": "comfy_canvas", "ref_sizing": "match_target_area",
"target_area": int(target_height * target_width)
if target_height and target_width
else 0,
"num_frames": num_frames, "num_frames": num_frames,
"fps": dataset_config.fps, "fps": dataset_config.fps,
"trim_tail": trim_tail, "trim_tail": trim_tail,
@@ -164,9 +169,10 @@ def load_ref_video_latent(model, path: str, dataset_config) -> dict:
mem_cache[path] = entry mem_cache[path] = entry
return entry return entry
# ComfyUI reference-video sizing: 768-short-edge canvas (768*1344 area # match the target's pixel area (the dataset bucket the target trains at)
# cap) or native size when smaller; aspect-preserving resize, no crop # with the ref's own aspect: same aspect -> identical size; aspect-
out_h, out_w = reference_video_pixel_size(src_w, src_h) # preserving resize, no crop
out_h, out_w = reference_video_pixel_size(src_w, src_h, target_height, target_width)
indices = ref_frame_indices( indices = ref_frame_indices(
total, src_fps, num_frames, dataset_config.fps, trim_tail total, src_fps, num_frames, dataset_config.fps, trim_tail

View File

@@ -922,7 +922,7 @@ export const modelArchs: ModelArch[] = [
<div className="space-y-2"> <div className="space-y-2">
<p> <p>
Reference-to-video: control images condition the output as subject/style references (never as a first frame). Reference-to-video: control images condition the output as subject/style references (never as a first frame).
Reference images keep their aspect and scale down (never up) to the target's pixel area; reference videos use the 768-short-edge canvas (or native size if smaller). Each rides into the packed References keep their own aspect and are matched to the target's pixel area (images scale down only, never up; a same-aspect video reference is exactly the target size). Each rides into the packed
sequence as a reference block, and is also shown to the Qwen3-VL conditioner as a{' '} sequence as a reference block, and is also shown to the Qwen3-VL conditioner as a{' '}
<code>&lt;Picture i&gt;</code> vision block. Training references come from the dataset control path(s); <code>&lt;Picture i&gt;</code> vision block. Training references come from the dataset control path(s);
sampling uses the sample ctrl images — always as references. Image references only for now (no reference sampling uses the sample ctrl images — always as references. Image references only for now (no reference

View File

@@ -1 +1 @@
VERSION = "0.12.22" VERSION = "0.12.23"