Hard Mode — reference to video

Identity from reference images, video and audio instead of a start frame. Up to 9 images, 3 videos, 3 soundtracks and 3 standalone audio clips.

Is this the workflow you want?

Three graphs, three jobs. Picking wrong is the most common way to waste a render.

You wantUse
A specific opening or closing frame, or timed anchorsH3_Keyframes.json
Identity carried by reference material — a person, a place, a voicethis graph
2–5 minute runtimeH3_Multishot_MEMORY.json
References and keyframes are mutually exclusive. Not a style preference — a hard limit in ComfyUI's core. model_base.py writes cond_video_latents for keyframes, then assigns it again for references, so the keyframe latents are discarded while the keyframe layout rows survive. The sequence desyncs and the sampler dies on a shape mismatch. There is no "reference images + start frame" mode, and asking for both crashes rather than degrading gracefully.

First render, start to finish

  1. Load the graph. H3_HardMode_R2V.json. It opens clean — the image slots point at ComfyUI's bundled example.png so nothing is missing on load.
  2. Swap in your own reference images. The three LoadImage nodes are the entire point of this mode. The zip ships three views of one subject in reference_images/ — drop them into ComfyUI/input/ to try it immediately, then replace them with your own. Two or three angles of the same subject is the sweet spot; a third reference costs nothing measurable.
  3. Pick the model. On a 24 GB card use MiniMax-H3-ref2va-curve-Q8_0.gguf — measured on a 24 GB card at 6.8 min for 123 frames, sitting resident at 21.7 GB of 24.0, not streaming. It is the highest quality tier that fits, and it is both smaller and faster than the original-form Q4_0 people usually run.
  4. Write the prompt so it names the references. This is the part that is not guessable — see the section below. If you skip it the model has references but no instruction about what they are.
  5. Leave the audio branch muted for the first run. LoadAudio and the stereo guard ship muted because there is no example clip to point them at. Identity from images alone works fine. Add voice later.
  6. Queue it. Expect roughly 7 minutes at 124 frames / 480×864 / 20 steps. If it is dramatically slower, jump to Troubleshooting — the cause is almost always VRAM, and the tell is power draw, not utilisation.

Naming your references in the prompt

Reference blocks are labelled in the prompt text, and the numbering is 1-based while the input slots are 0-based. So ref_image_0 is <Picture 1>. Write prompts that actually refer to them:

<Picture 1> is the woman. <Picture 2> is the room she is standing in.
She walks to the window in the style of <Video 1>.

Ordering is fixed by the node no matter how you wire it: all images, then videos, then standalone audios. Empty slots are skipped and the numbering compacts — leaving ref_image_0 empty and filling ref_image_1 still gives you <Picture 1>.

The ordinal trap. A reference video with a soundtrack consumes an <Audio j> ordinal before your standalone clips. One ref video with sound plus one ref_audio_0 makes your voice clip <Audio 2>, not <Audio 1>. Get this wrong and the model binds the wrong voice with no error.

Three rules that will bite you

1. Reference audio must be STEREO

The audio VAE encodes [B, 2, L] and the layout reserves exactly two channels. A mono reference produces half the rows it reserved and dies deep in the model with no useful error message. The stereo-guard node forces 2 channels at 32 kHz and trims length — keep it wired whenever you unmute the audio branch.

2. The eviction node is ON — leave it on

H3FreeTextEncoder (node 146) frees the ~16.5 GB text encoder once conditioning is computed, before the DiT loads. The encoder and the ~24 GB DiT do not co-fit in 32 GB, so without it the DiT loads partially and streams the remainder from system RAM on every sampling step.

Measured on a 32 GB card, images only, POST /free before every run, runs alternated so machine drift could not fake the result:

24% faster, and bit-identical output — every pairing scored 0.00 mean pixel distance with motion 7.19 across all four runs. It costs nothing and changes nothing.

With a reference video the gap is far wider, because a reference video goes through the encoder's vision path:

Clip length is not the driver — a 24-frame reference thrashed just as badly as a 124-frame one. The video's mere presence is what does it.

If you ever disable it, use Bypass — never Mute. A muted node produces nothing, so H3ConditionStrength loses its conditioning input entirely and the graph stops validating. Bypass passes conditioning straight through, which is the correct idiom for a pass-through node.

3. Reference video has shape rules

What the settings cost

Reference rows sit in the packed sequence on every sampling step, not once up front — so more references and a bigger ref_image_size both cost time per step.

Measured, 124 frames at 480×864, two reference images:

SettingTimeIdentity margin
ref_image_size = match2.9 min+9.42
ref_image_size = max4.0 min+9.10

So 1.38×, not the "several times slower" core's own tooltip warns about — and max did not improve identity here, scoring fractionally worse while costing 38% more. It does change the output, so it is doing something; it just did not buy fidelity at this resolution. Start with match and 2–3 images.

Caveat worth knowing: that margin is a blunt measure (distance to the reference images versus distance to a control person the model never saw). It confirms identity carried, but may be too coarse to see the fine facial fidelity max targets. At higher output resolutions it is likelier to earn its cost.

Reference audio, same test: about 14% extra wall clock, and it trims motion roughly 17–27%. The audio conditioning constrains the performance rather than stalling it — it does not freeze the render.

Troubleshooting

SymptomCause / fix
Render crawling, GPU shows ~98% utilisationCheck power draw, not utilisation. A card thrashing weights between system RAM and VRAM still reports ~98% while pulling a fraction of its rated watts. 140–240 W means it did not fit. 355–600 W means it is genuinely sampling.
Sample more than once. The encoder/conditioning phase at the start of every render is legitimately low-power — about 100 W at ~35% utilisation with only the encoder resident. One unlucky reading there looks exactly like thrashing. Watch for 20–30 s: if it climbs past 400 W once sampling begins, nothing is wrong.
Sampler dies on a shape mismatchYou wired references and keyframes. Pick one.
Dies deep in the model, no useful error, audio connectedMono reference audio. Wire the stereo guard.
"Required input is missing → conditioning"You muted a pass-through node instead of bypassing it.
Wrong voice bound to the wrong referenceOrdinal shift — a ref video's soundtrack took <Audio 1>.
Timing looks impossibly good, then never repeatsWarm session. POST /free before any timing you intend to compare.

Settled late, and it reversed the default

Does eviction cost anything on the ordinary images-only path? No — it saves. Bypassed 4.5 min, active 3.4 min, with bit-identical output. So the node now ships active, and the old "turn it on only for reference video" instruction is gone.

The previous default rested on a single run that stalled at 19 minutes and was killed before the cause could be isolated. That run followed an interrupt, so it inherited resident models — the same contamination that produced the discredited "2.9 min" baseline below. It was never evidence about eviction at all.

Also note: a warm-session baseline of "2.9 min" for the images-only graph did not survive a restart — the same graph cold took 17.5 min. Wall-clock numbers taken mid-session are best-case. Pixel comparisons are unaffected: re-running the baseline after restart gave a distance of 0.00, so sampling is deterministic across restarts.

Everything above that was verified by reading the source

Rather than by spending renders on it — comfy_extras/nodes_minimax_h3.py and comfy/model_base.py:

ClaimWhere
minimum 5 frames or it raisesnodes_minimax_h3.py:249
truncate to generation length, then trim to n % 17 == 5nodes_minimax_h3.py:246-253
shown to the encoder at 2 fpsnodes_minimax_h3.py:260-262
a ref video's soundtrack takes an <Audio j> before standalone clipsnodes_minimax_h3.py:256-274
refs and keyframes collidemodel_base.py:2094 and 2098