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.
Three graphs, three jobs. Picking wrong is the most common way to waste a render.
| You want | Use |
|---|---|
| A specific opening or closing frame, or timed anchors | H3_Keyframes.json |
| Identity carried by reference material — a person, a place, a voice | this graph |
| 2–5 minute runtime | H3_Multishot_MEMORY.json |
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.H3_HardMode_R2V.json. It opens clean — the image slots point
at ComfyUI's bundled example.png so nothing is missing on load.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.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.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.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>.
<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.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.
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.
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.n % 17 == 5Reference 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:
| Setting | Time | Identity margin |
|---|---|---|
ref_image_size = match | 2.9 min | +9.42 |
ref_image_size = max | 4.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.
| Symptom | Cause / fix |
|---|---|
| Render crawling, GPU shows ~98% utilisation | Check 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 mismatch | You wired references and keyframes. Pick one. |
| Dies deep in the model, no useful error, audio connected | Mono 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 reference | Ordinal shift — a ref video's soundtrack took <Audio 1>. |
| Timing looks impossibly good, then never repeats | Warm session. POST /free before any timing you intend to compare. |
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.
Rather than by spending renders on it — comfy_extras/nodes_minimax_h3.py
and comfy/model_base.py:
| Claim | Where |
|---|---|
| minimum 5 frames or it raises | nodes_minimax_h3.py:249 |
truncate to generation length, then trim to n % 17 == 5 | nodes_minimax_h3.py:246-253 |
| shown to the encoder at 2 fps | nodes_minimax_h3.py:260-262 |
a ref video's soundtrack takes an <Audio j> before standalone clips | nodes_minimax_h3.py:256-274 |
| refs and keyframes collide | model_base.py:2094 and 2098 |