Streaming MiniMax H3 RAVEN text-to-video-and-audio nodes for ComfyUI
Python
10
11 commits
updated Aug 20, 2026
Streaming text-to-video-and-audio (T2VA) custom nodes for MiniMax H3, built on top of ComfyUI's official H3 implementation and a mandatory RAVEN LoRA.
Status: implemented, locally validated, source and Comfy Registry package published. The nodes, the chunk-major sampler, the streaming preview and the example workflow in
example_workflows/run end to end on real weights: 1376x768 / 192 frames inside a simulated 24 GiB VRAM envelope, the 362-frame maximum, the real Qwen3-VL text lane, a public third-party H3 LoRA through the officialLoraLoaderModelOnly, and CPU-pinned/GPU KV modes with bitwise-identicalLATENT/IMAGE/AUDIO. A delivered-chunk cancellation followed by three normal runs also leaves no buffered media and produces bitwise-identical outputs with zero CUDA plateau growth. Every measurement is listed indocs/validation.md. The package is published on the Comfy Registry ascomfyui-minimax-h3-raven-streamingunder publisheroliveryanzuolu; the source repository is public on GitHub.
comfyui-minimax-h3-raven-streamingraven_streaming/0.1.0LICENSE) — model weights are licensed separately, see belowThe published package is active on the Comfy Registry:
https://registry.comfy.org/nodes/comfyui-minimax-h3-raven-streaming
Install it with ComfyUI-Manager by searching for MiniMax H3 RAVEN Streaming
or comfyui-minimax-h3-raven-streaming, then restart ComfyUI. You can also
install it from a checkout of ComfyUI with the CLI:
cd ComfyUI
comfy node install comfyui-minimax-h3-raven-streaming
The package has no model downloads. After installation, put the model files in the standard folders listed in Models and restart ComfyUI.
Install the source checkout directly into ComfyUI's custom_nodes directory:
cd ComfyUI/custom_nodes
git clone https://github.com/YanzuoLu/ComfyUI-MiniMax-H3-RAVEN-Streaming.git
To update later:
cd ComfyUI/custom_nodes/ComfyUI-MiniMax-H3-RAVEN-Streaming
git pull --ff-only
Then restart ComfyUI. The nodes register through the repository root
__init__.py (V1 mappings plus WEB_DIRECTORY), so no extra step is needed to
get the in-node preview widget.
After that restart the example graph is available from the frontend itself:
Workflow -> Browse Templates -> Extensions, then either pick this pack by
its folder / module name (ComfyUI-MiniMax-H3-RAVEN-Streaming) or type
minimax_h3_raven_streaming_t2va into the template search box — the template
title is the file name in example_workflows/. Clicking it opens the same
graph described in The workflow, ready to run once the model
files are in place.
The manual route still works and needs no restart: drag
example_workflows/minimax_h3_raven_streaming_t2va.json
onto the canvas, or use Workflow -> Open.
| Python | >= 3.10 (follows upstream's requires-python) |
| PyAV | av >= 16.0.0 — the only runtime dependency this package adds, used for fMP4 muxing of the preview stream |
| Everything else | ComfyUI's own requirements; nothing is vendored |
PyAV ships with ComfyUI's requirements at the pinned baseline, so in a normal ComfyUI environment there is nothing to install. If you maintain a stripped environment:
pip install "av>=16.0.0"
The preview lane is the only thing PyAV is needed for. If PyAV, the H.264
encoder or the AAC encoder is missing, the node logs the reason and samples
anyway; the LATENT / IMAGE / AUDIO outputs are unaffected.
| Component | Version | Status |
|---|---|---|
| ComfyUI | 0.33.0 @ c67885b14556cf3e4e061862925282d403d09862 | pinned and verified — every measurement in docs/validation.md was taken here |
comfyui-frontend-package | 1.49.6 | pinned and verified — the preview protocol was read and tested against this frontend |
| PyAV | av >= 16.0.0 (measured on 17.1.0 and 18.1.0) | verified |
ComfyUI 0.30 | declared as requires-comfyui = ">=0.30.0" | target only, NOT verified. Never run. The feature probe in COMPATIBILITY.md decides, not the version string. |
Weights are not distributed with this repository, are not downloaded automatically, and are not covered by this repository's MIT license. MiniMax H3 weights and the RAVEN LoRA carry their own upstream licenses and usage terms; read and accept them before use.
Everything goes in the standard ComfyUI model directories — no custom folder, no custom path config:
ComfyUI/models/
├── diffusion_models/ # MiniMax H3 DiT (full, non-pruned BF16)
├── text_encoders/ # Qwen3-VL-32B NVFP4 AWQ text encoder
├── vae/ # H3 video VAE + H3 audio VAE
└── loras/ # RAVEN streaming LoRA (mandatory) + optional official LoRAs
RAVEN streaming LoRA (mandatory, public) -> models/loras/minimax_h3_raven_streaming_lora_4nfe_preview.safetensors
H3 DiT (official Comfy-Org repackage) -> models/diffusion_models/minimax_h3_fl2va_bf16.safetensors
time_embedder for the RAVEN
adapter's 266-module mapping to attach to.Text encoder -> models/text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
Video VAE -> models/vae/minimax_h3_video_vae_fp16.safetensors
Audio VAE -> models/vae/minimax_h3_audio_vae_fp32.safetensors
This package adds exactly two nodes. Everything else is stock ComfyUI.
[RAVEN Model Loader]
(full non-pruned BF16 DiT + mandatory RAVEN LoRA, strength fixed at 1.0)
│
└─> MODEL ──> (optional) LoraLoaderModelOnly ... ─────────────────┐
│
CLIPLoader (type: minimax) ──> CLIP ──┐ │
├─> MiniMaxH3ImageToVideo │
VAELoader (video VAE) ────────────────┘ (T2VA: no first/last frame) │
│ ├─> CONDITIONING ────────────>┤
│ └─> LATENT (empty AV) ───────>┤
├───────────────────────────────────────────── video_vae ────────────>┤
VAELoader (audio VAE) ──────────────────────────── audio_vae ────────────>┤
│
[RAVEN Streaming Sampler] <───────┘
├─> LATENT
├─> IMAGE -> SaveAnimatedWEBP / CreateVideo
└─> AUDIO -> SaveAudio / CreateVideo
Ready-made graphs, one per format:
example_workflows/minimax_h3_raven_streaming_t2va.json
— the UI workflow. ComfyUI scans example_workflows/ and publishes every
.json in it as a template, so this one also shows up under Workflow ->
Browse Templates -> Extensions as minimax_h3_raven_streaming_t2va. Drag it
onto the canvas or use Workflow -> Open to get the same graph without going
through the template browser.api_workflows/raven_t2va_streaming_api.json
— the same graph in API prompt format, for POST /prompt. This one does
not open in the UI, which is exactly why it lives outside
example_workflows/: anything dropped in there is offered as a template, and
an API prompt loaded as a template is a broken graph.Both reference the public filenames listed above; if your files are named
differently, re-pick them in the combo widgets. See
example_workflows/README.md for the wiring
notes.
RAVEN Model LoaderLoads the full non-pruned BF16 H3 DiT together with the mandatory RAVEN PEFT
LoRA and returns a standard MODEL.
unet_name (from diffusion_models), lora_name (from loras),
weight_dtype.strength= to
loader.load_raven_diffusion_model.)B @ A into the BF16
weights. That is what survives partial CPU offload, where base weights and
adapter can sit on different devices.MODEL is a stock static ModelPatcher
(disable_dynamic=True), so stock LoraLoaderModelOnly chains after it and
Comfy's own offload accounting applies unchanged.MODEL, you can also wire it into other official
H3 workflows and experiment there. Nothing in this loader restricts the
model to one generation mode. What is restricted is the RAVEN Streaming Sampler in this pack: it has not implemented the causal packed layout for
condition/reference rows, so it refuses conditioning that carries them (see
below). That is a limit of this sampler's implementation, not a statement
about the RAVEN LoRA. Anything you run outside this sampler is unverified
here — no compatibility, quality or reproducibility guarantee is made for it.weight_dtype: comfy.ops fuses
quantised linears and would silently skip the residual.RAVEN Streaming SamplerRuns the chunk-major RAVEN rollout and produces the final media.
MODEL, official CONDITIONING, the official H3 AV LATENT, the
video VAE and the audio VAE, plus the parameters below.MiniMaxH3ImageToVideo node used in
T2VA form (no first_frame / last_frame connected).LATENT, so there is exactly one place they are
set.MiniMaxH3VideoVAE /
MiniMaxH3AudioVAE, channel counts, sample rate, the decoder entry points the
streaming lane drives). Swapping the two sockets is reported as exactly that.VAE.decode on the whole clip. The IMAGE and AUDIO outputs come
out of those per-chunk decodes; see Outputs.| Official node | Role |
|---|---|
CLIPLoader (type: minimax) | Loads the H3 (Qwen3-VL) text encoder. |
MiniMaxH3ImageToVideo (T2VA: no frame inputs) | Prompt encoding -> CONDITIONING + empty AV LATENT. Its vae input takes the video VAE. |
VAELoader x2 | Video VAE and audio VAE, one loader each. |
LoraLoaderModelOnly (optional) | Third-party / additional official H3 LoRAs, chained after RAVEN Model Loader and before the sampler. Stack as many as you like; the mandatory RAVEN adapter is already inside the MODEL and is not affected by their strengths. |
SaveAnimatedWEBP, SaveAudio (or CreateVideo -> SaveVideo) | Saving the IMAGE and AUDIO outputs, see below. |
| Parameter | Default | Constraint |
|---|---|---|
seed | 0 | Seeds a private generator; global RNG is never touched. |
steps | 4 | Consistency NFEs per chunk. RAVEN's published preview adapter is distilled for 4; more is not a free quality win. |
video_shift | 12.0 | Shift of the video stream's trailing sigma grid. |
audio_shift | 3.0 | Shift of the audio stream's own grid. The two streams run independent grids. |
sink | 2 | Attention-sink cache chunks pinned from the start. Chunk 0 is the text prefill, so 2 means text + the first media chunk. |
window | 2 | Most recent cache chunks kept besides the sinks. 0 keeps only the sinks. |
kv_cache_storage | cpu_pinned | Where the retained chunk KV cache lives: cpu_pinned (default, page-locked host memory), cpu, or gpu. It changes where bytes live, not what is computed. At 1376x768 / 192 frames the default holds one layer's slot on the card — 0.561 GiB — instead of the whole cache's ~21.1 GiB steady / ~28.0 GiB peak. |
width / height | 1376 / 768 (set on the H3 latent node) | Multiples of 32, and width * height <= 1376 * 768. |
frames | 192 (set on the H3 latent node) | Must satisfy 17k + 5 with k >= 1: minimum 22, hard maximum 362. k = 0 (5 frames / 2 latents) is not supported and fails loud. Above 192 is allowed but experimental and warns. |
| batch size | 1 | Fixed. |
| sampler / scheduler | — | Not selectable. The node runs the RAVEN chunk-major consistency loop; stock samplers and schedulers do not apply. |
| CFG / negative prompt | — | Not exposed. One conditioning branch only. |
Scope limits that are part of the contract:
docs/validation.md are text-to-video-and-audio, and
that is the only path this pack claims to have verified.minimax_keyframes (fl2va) and minimax_refs (ref2va) conditioning
extras are rejected with an explicit error because the streaming sampler has
not implemented or verified the causal packed layout for condition rows.
Refusing is deliberate: silently dropping them would look like a quality bug.
This is a runtime limit of this sampler, not a claim that the RAVEN LoRA
is text-only. If you want to explore image- or reference-conditioned H3 modes,
take the standard MODEL from RAVEN Model Loader and build your own
workflow around the official H3 nodes — untested here, with no compatibility,
quality or reproducibility guarantee.SourceBuffer and can never be revised.The node returns LATENT, IMAGE, AUDIO. There is no whole-clip VAE decode
anywhere in this package — not for the video, not for the audio. Both media
outputs are built incrementally, in the same per-chunk callback that feeds the
preview:
| Output | How it is produced |
|---|---|
LATENT | The finished AV latent, the same nested (video, audio) structure as the input. |
IMAGE | The video collector. Each chunk's latents are decoded in the official order (to(device, vae_dtype) -> denormalize -> _adaptive_decode -> blend -> _finalize_pixels) and the finalized frames are memcpy'd straight into a pre-allocated [T, H, W, 3] host buffer. That buffer is the IMAGE. |
AUDIO | The audio collector. Each chunk's audio latents are decoded overlap-save and copied into a pre-allocated [1, 2, N] host waveform. At the end that raw waveform gets the official whole-clip normalisation — std = std(x, dim=[1,2]) * 5, std[std < 1] = 1, x /= std, the tail of comfy_extras.nodes_audio.vae_decode_audio, reproduced expression for expression. |
Why there is no full decode: both of the calls this replaced died on real
hardware. video_vae.decode OOMed at 39 frames on a 141 GiB card (130.22 GiB
allocated), and at 192 frames on a 24 GiB card the whole-clip
vae_decode_audio OOMed even with the DiT and the video VAE already evicted
— and its own OOM fallback made it worse, because the generic tiled path is
written for 4-D latents and the H3 audio latent is [B, 32, 2, T]. Both calls
only re-derived data these lanes already had, chunk by chunk.
The two collectors are therefore not optional and not best-effort. They run
on every chunk whether or not a browser is attached, and a failure in either one
fails the run — with no full decode to fall back on, dropping a chunk would mean
silently returning a partly-black IMAGE or a waveform with a hole in it.
The preview is a third lane, on top of them. It reads frames and PCM back
out of the two collectors, muxes fMP4 and sends it; it is lossy on purpose
(H.264 + AAC) and exists to show progress. It can fail at any point without
touching LATENT / IMAGE / AUDIO.
One consequence worth knowing: the preview's audio is the raw PCM, before
normalisation. The /= max(1, std*5) divisor is a whole-clip statistic that
simply does not exist yet while the clip is still being made, so a
sample-for-sample comparison of what was streamed against what was returned
differs by exactly that divisor. The video has no such split — the frames the
preview encodes are the same frames the IMAGE buffer holds.
The example graphs end in the official SaveAnimatedWEBP (video,
24 fps) and SaveAudio (FLAC), because those two take IMAGE and AUDIO
directly and carry only plain widgets. Note that upstream marks SaveAudio
deprecated at the pinned baseline — it still loads and still writes FLAC;
SaveAudioAdvanced is its replacement if you want to pick the format.
For a single muxed file instead, replace both with the official CreateVideo
(images + audio, fps = 24) -> SaveVideo pair: CreateVideo is exactly
the node upstream provides for an IMAGE + AUDIO pair.
The sampler streams into its own node widget while it samples. Contract:
< 2.5e-6, measured at M0).block + margin further latents exist. The last block - 1 + margin =
44 latents (1.1 s) of a clip therefore cannot be decoded until
finish(), and because the lane never substitutes silence, the video frames
covering that span wait with them. finish() flushes exactly that last
boundary and nothing else — everything before it already went out during
sampling. Measured at 1376x768 / 192 frames: 216 of 251 fragments (86 %)
and 168 of 192 muxed frames (88 %) were already sent when finish() was
called, the first fragment landing after chunk 1 at 51.46 s. Shorter
clips pay proportionally more for the same fixed tail: at 22 frames nothing
streams before finish() at all.frag_every_frame+empty_moov+default_base_moof and
min_frag_duration=1, delivered as video/mp4; codecs="avc1.640028,mp4a.40.2"
to a Media Source Extensions SourceBuffer. One fragment per frame means the
first picture reaches the browser one frame after it is muxed instead of one
segment. Those fragments are decodable in order only, which is exactly what
a sequentially appended SourceBuffer needs; one forced IDR per 17-frame chunk
keeps a late joiner resynchronisable. Silence is inserted automatically when a
segment carries no audio, so the two timelines stay aligned.raven.preview). n bytes cost ceil(n/3)*4 characters plus a ~200–300 byte
envelope. This is a deliberate, documented trade — see
web/PROTOCOL.md §2.LATENT / IMAGE / AUDIO are byte-for-byte
what they would have been with no browser attached — they come from the two
collectors, which are a layer below the preview and always run.AUDIO is that same buffer after
the official whole-clip /= max(1, std*5). See Outputs above.reason: cancelled; the client drops its queue, aborts the source buffer,
revokes the object URL and removes its listeners, and the backend releases the
session's resources through its own context manager on every exit path,
including exceptions.The causal DiT resolves its attention backend once, in RAVEN's own priority
order: FlashAttention 3 (flash_attn_interface) -> FlashAttention 2
(flash_attn) -> PyTorch SDPA, the packed transcription of RAVEN's
_sdpa_varlen. All three are optional; SDPA is always available, so a box
without FlashAttention runs without any configuration. FLASH_ATTN_3_AVAILABLE=0
/ FLASH_ATTN_2_AVAILABLE=0 force a backend down the chain.
1x NVIDIA H200, single GPU — that is the box every measurement in
docs/validation.md was taken on. The 192-frame reference run did not run
on a physically small card: an external process
(.cache/hold_vram.py) allocated VRAM on the H200 until only 24.100 GiB
was free, so upstream saw, and had to plan against, a real 24 GiB of headroom.
That is the envelope this package sizes itself against; it is a simulated
ceiling on a big card, not a 24 GiB GPU.
System RAM: budget 160 GiB available, 192 GB physical. This is the number most likely to bite you, and the older ">= 128 GB" figure is not good enough — measured peak host RSS was 129.854 GiB at 192 frames and 135.025 GiB at 362, both of which clear 128 GiB outright. On a 128 GiB box expect swapping or an OOM kill.
Where it goes: ~66 GB of BF16 base weights plus ~5 GB of FP32 adapter (which
live on the host whenever Comfy offloads them), the load path, the KV cache
— 22.62 GB steady / 30.10 GB peak at 192 frames, on the host by default
— and the output buffers, 2.43 GB of IMAGE at 192 frames and 4.59 GB at 362.
Those last two are the trade this architecture makes deliberately: the outputs
are built in host RAM instead of being decoded from VRAM at the end. Comfy's
reserve accounts for none of it.
Stock Comfy partial CPU offload is the memory strategy, and the only one
v0.1 makes a claim about. The loader returns a static ModelPatcher; nothing
in this package calls .to(), patch_model(), partially_load() or
cleanup_models(). Comfy owns residency. Measured on vr-1 in all three
residency states — fully resident, fully offloaded, and split across
CPU/GPU — with the cross-device LoRA residual working in the split state
(docs/validation.md, M1).
The DiT and the two VAEs are never co-resident. Every chunk alternates two
phases through ordinary comfy.model_management.load_models_gpu calls: a
DiT phase for the forwards, then a VAE phase for the decode inside
on_chunk. Each phase asks for its own memory_required and upstream decides
what to evict; this package never moves a weight itself. The last chunk stops
in the VAE phase on purpose, because finish() still has the tail flush to
decode. Measured at 192 frames: 12 chunks, 59 forward passes, 12 DiT-phase
loads and 12 VAE-phase loads.
The KV cache lives on the host by default. kv_cache_storage defaults to
cpu_pinned: the committed chunks sit in page-locked host memory and one
layer's retained rows are copied back per block. That is 0.561 GiB of VRAM
at 1376x768 / 192 frames instead of the ~21.1 GiB the whole cache would hold
steady (~28.0 GiB at its peak) on the card. gpu is available for cards with
room to spare; it changes where bytes live, not what is computed.
The reserve is the rollout workspace, not the weights. The node prices the
KV slot, the rollout buffers, the widest forward and the decode workspace
before it loads anything, and hands that number to Comfy as memory_required.
For the published request (1376x768, 192 frames, sink 2, window 2,
cpu_pinned) it comes to 2.296 GiB:
2.296 GiB = KV 0.000 + buffers 0.074 + max(forward 1.529, decode 0.635) + safety 0.692
KV is 0.000 because with cpu_pinned the cache is not on the card at all.
The number is an estimate computed from this repository's own structures and
logged at INFO on every run — and it has now been run against real weights:
the same request peaked at 21.342 GiB allocated / 21.721 GiB reserved,
inside a 24 GiB envelope. Term-by-term detail in docs/validation.md.
There is deliberately no VRAM-cap and no weight-residency widget. A "max resident GB" input would be a second, worse estimate of free memory sitting in front of upstream's real one; every time the two disagreed, the user's number would be the one turning a run that fitted into an OOM, or a run that fitted into a refusal. The 24 GiB reference figure is a constant used for reporting ("would this have fitted the smallest card we target?"), and the 24 GiB path is tested by making the device actually that small from outside the process, never by the node pretending a bigger card is smaller.
The NVFP4 text encoder runs its encode on the GPU and is then offloaded to
CPU by ComfyUI, so it holds no VRAM during sampling. Measured, not assumed:
after the encode, loaded_size = 0 and all 51 509 571 044 B of its
parameters are back on cpu — but they are back on the host, which is part
of why the RAM figure above is what it is (that run peaked at 131.632 GiB RSS
on a 39-frame clip).
DynamicVRAM / aimdo is not supported in v0.1. The loader forces the static
patcher and the sampler rejects a ModelPatcherDynamic MODEL outright. It
was never exercised: on the cu128 measurement box the aimdo path is
unavailable, and a cu130 build has no DynamicVRAM support either.
docs/validation.md — what was measured, with the numbers and artifacts. Read this before believing anything else.docs/requirements.md — the confirmed requirements contract.docs/architecture.md — module layout, sampler, KV cache, collectors and memory design.COMPATIBILITY.md — pinned baseline, feature probes and known risks.web/PROTOCOL.md — preview wire protocol v1.NOTICE — third-party attribution.The documentation links are absolute so they remain valid when the Registry
archive omits docs/ and COMPATIBILITY.md via .comfyignore.
example_workflows/, api_workflows/, web/, LICENSE and NOTICE ship with
the package.
Streaming MiniMax H3 RAVEN text-to-video-and-audio nodes for ComfyUI
Python
10
11 commits
updated Aug 20, 2026
Streaming text-to-video-and-audio (T2VA) custom nodes for MiniMax H3, built on top of ComfyUI's official H3 implementation and a mandatory RAVEN LoRA.
Status: implemented, locally validated, source and Comfy Registry package published. The nodes, the chunk-major sampler, the streaming preview and the example workflow in
example_workflows/run end to end on real weights: 1376x768 / 192 frames inside a simulated 24 GiB VRAM envelope, the 362-frame maximum, the real Qwen3-VL text lane, a public third-party H3 LoRA through the officialLoraLoaderModelOnly, and CPU-pinned/GPU KV modes with bitwise-identicalLATENT/IMAGE/AUDIO. A delivered-chunk cancellation followed by three normal runs also leaves no buffered media and produces bitwise-identical outputs with zero CUDA plateau growth. Every measurement is listed indocs/validation.md. The package is published on the Comfy Registry ascomfyui-minimax-h3-raven-streamingunder publisheroliveryanzuolu; the source repository is public on GitHub.
comfyui-minimax-h3-raven-streamingraven_streaming/0.1.0LICENSE) — model weights are licensed separately, see belowThe published package is active on the Comfy Registry:
https://registry.comfy.org/nodes/comfyui-minimax-h3-raven-streaming
Install it with ComfyUI-Manager by searching for MiniMax H3 RAVEN Streaming
or comfyui-minimax-h3-raven-streaming, then restart ComfyUI. You can also
install it from a checkout of ComfyUI with the CLI:
cd ComfyUI
comfy node install comfyui-minimax-h3-raven-streaming
The package has no model downloads. After installation, put the model files in the standard folders listed in Models and restart ComfyUI.
Install the source checkout directly into ComfyUI's custom_nodes directory:
cd ComfyUI/custom_nodes
git clone https://github.com/YanzuoLu/ComfyUI-MiniMax-H3-RAVEN-Streaming.git
To update later:
cd ComfyUI/custom_nodes/ComfyUI-MiniMax-H3-RAVEN-Streaming
git pull --ff-only
Then restart ComfyUI. The nodes register through the repository root
__init__.py (V1 mappings plus WEB_DIRECTORY), so no extra step is needed to
get the in-node preview widget.
After that restart the example graph is available from the frontend itself:
Workflow -> Browse Templates -> Extensions, then either pick this pack by
its folder / module name (ComfyUI-MiniMax-H3-RAVEN-Streaming) or type
minimax_h3_raven_streaming_t2va into the template search box — the template
title is the file name in example_workflows/. Clicking it opens the same
graph described in The workflow, ready to run once the model
files are in place.
The manual route still works and needs no restart: drag
example_workflows/minimax_h3_raven_streaming_t2va.json
onto the canvas, or use Workflow -> Open.
| Python | >= 3.10 (follows upstream's requires-python) |
| PyAV | av >= 16.0.0 — the only runtime dependency this package adds, used for fMP4 muxing of the preview stream |
| Everything else | ComfyUI's own requirements; nothing is vendored |
PyAV ships with ComfyUI's requirements at the pinned baseline, so in a normal ComfyUI environment there is nothing to install. If you maintain a stripped environment:
pip install "av>=16.0.0"
The preview lane is the only thing PyAV is needed for. If PyAV, the H.264
encoder or the AAC encoder is missing, the node logs the reason and samples
anyway; the LATENT / IMAGE / AUDIO outputs are unaffected.
| Component | Version | Status |
|---|---|---|
| ComfyUI | 0.33.0 @ c67885b14556cf3e4e061862925282d403d09862 | pinned and verified — every measurement in docs/validation.md was taken here |
comfyui-frontend-package | 1.49.6 | pinned and verified — the preview protocol was read and tested against this frontend |
| PyAV | av >= 16.0.0 (measured on 17.1.0 and 18.1.0) | verified |
ComfyUI 0.30 | declared as requires-comfyui = ">=0.30.0" | target only, NOT verified. Never run. The feature probe in COMPATIBILITY.md decides, not the version string. |
Weights are not distributed with this repository, are not downloaded automatically, and are not covered by this repository's MIT license. MiniMax H3 weights and the RAVEN LoRA carry their own upstream licenses and usage terms; read and accept them before use.
Everything goes in the standard ComfyUI model directories — no custom folder, no custom path config:
ComfyUI/models/
├── diffusion_models/ # MiniMax H3 DiT (full, non-pruned BF16)
├── text_encoders/ # Qwen3-VL-32B NVFP4 AWQ text encoder
├── vae/ # H3 video VAE + H3 audio VAE
└── loras/ # RAVEN streaming LoRA (mandatory) + optional official LoRAs
RAVEN streaming LoRA (mandatory, public) -> models/loras/minimax_h3_raven_streaming_lora_4nfe_preview.safetensors
H3 DiT (official Comfy-Org repackage) -> models/diffusion_models/minimax_h3_fl2va_bf16.safetensors
time_embedder for the RAVEN
adapter's 266-module mapping to attach to.Text encoder -> models/text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors
Video VAE -> models/vae/minimax_h3_video_vae_fp16.safetensors
Audio VAE -> models/vae/minimax_h3_audio_vae_fp32.safetensors
This package adds exactly two nodes. Everything else is stock ComfyUI.
[RAVEN Model Loader]
(full non-pruned BF16 DiT + mandatory RAVEN LoRA, strength fixed at 1.0)
│
└─> MODEL ──> (optional) LoraLoaderModelOnly ... ─────────────────┐
│
CLIPLoader (type: minimax) ──> CLIP ──┐ │
├─> MiniMaxH3ImageToVideo │
VAELoader (video VAE) ────────────────┘ (T2VA: no first/last frame) │
│ ├─> CONDITIONING ────────────>┤
│ └─> LATENT (empty AV) ───────>┤
├───────────────────────────────────────────── video_vae ────────────>┤
VAELoader (audio VAE) ──────────────────────────── audio_vae ────────────>┤
│
[RAVEN Streaming Sampler] <───────┘
├─> LATENT
├─> IMAGE -> SaveAnimatedWEBP / CreateVideo
└─> AUDIO -> SaveAudio / CreateVideo
Ready-made graphs, one per format:
example_workflows/minimax_h3_raven_streaming_t2va.json
— the UI workflow. ComfyUI scans example_workflows/ and publishes every
.json in it as a template, so this one also shows up under Workflow ->
Browse Templates -> Extensions as minimax_h3_raven_streaming_t2va. Drag it
onto the canvas or use Workflow -> Open to get the same graph without going
through the template browser.api_workflows/raven_t2va_streaming_api.json
— the same graph in API prompt format, for POST /prompt. This one does
not open in the UI, which is exactly why it lives outside
example_workflows/: anything dropped in there is offered as a template, and
an API prompt loaded as a template is a broken graph.Both reference the public filenames listed above; if your files are named
differently, re-pick them in the combo widgets. See
example_workflows/README.md for the wiring
notes.
RAVEN Model LoaderLoads the full non-pruned BF16 H3 DiT together with the mandatory RAVEN PEFT
LoRA and returns a standard MODEL.
unet_name (from diffusion_models), lora_name (from loras),
weight_dtype.strength= to
loader.load_raven_diffusion_model.)B @ A into the BF16
weights. That is what survives partial CPU offload, where base weights and
adapter can sit on different devices.MODEL is a stock static ModelPatcher
(disable_dynamic=True), so stock LoraLoaderModelOnly chains after it and
Comfy's own offload accounting applies unchanged.MODEL, you can also wire it into other official
H3 workflows and experiment there. Nothing in this loader restricts the
model to one generation mode. What is restricted is the RAVEN Streaming Sampler in this pack: it has not implemented the causal packed layout for
condition/reference rows, so it refuses conditioning that carries them (see
below). That is a limit of this sampler's implementation, not a statement
about the RAVEN LoRA. Anything you run outside this sampler is unverified
here — no compatibility, quality or reproducibility guarantee is made for it.weight_dtype: comfy.ops fuses
quantised linears and would silently skip the residual.RAVEN Streaming SamplerRuns the chunk-major RAVEN rollout and produces the final media.
MODEL, official CONDITIONING, the official H3 AV LATENT, the
video VAE and the audio VAE, plus the parameters below.MiniMaxH3ImageToVideo node used in
T2VA form (no first_frame / last_frame connected).LATENT, so there is exactly one place they are
set.MiniMaxH3VideoVAE /
MiniMaxH3AudioVAE, channel counts, sample rate, the decoder entry points the
streaming lane drives). Swapping the two sockets is reported as exactly that.VAE.decode on the whole clip. The IMAGE and AUDIO outputs come
out of those per-chunk decodes; see Outputs.| Official node | Role |
|---|---|
CLIPLoader (type: minimax) | Loads the H3 (Qwen3-VL) text encoder. |
MiniMaxH3ImageToVideo (T2VA: no frame inputs) | Prompt encoding -> CONDITIONING + empty AV LATENT. Its vae input takes the video VAE. |
VAELoader x2 | Video VAE and audio VAE, one loader each. |
LoraLoaderModelOnly (optional) | Third-party / additional official H3 LoRAs, chained after RAVEN Model Loader and before the sampler. Stack as many as you like; the mandatory RAVEN adapter is already inside the MODEL and is not affected by their strengths. |
SaveAnimatedWEBP, SaveAudio (or CreateVideo -> SaveVideo) | Saving the IMAGE and AUDIO outputs, see below. |
| Parameter | Default | Constraint |
|---|---|---|
seed | 0 | Seeds a private generator; global RNG is never touched. |
steps | 4 | Consistency NFEs per chunk. RAVEN's published preview adapter is distilled for 4; more is not a free quality win. |
video_shift | 12.0 | Shift of the video stream's trailing sigma grid. |
audio_shift | 3.0 | Shift of the audio stream's own grid. The two streams run independent grids. |
sink | 2 | Attention-sink cache chunks pinned from the start. Chunk 0 is the text prefill, so 2 means text + the first media chunk. |
window | 2 | Most recent cache chunks kept besides the sinks. 0 keeps only the sinks. |
kv_cache_storage | cpu_pinned | Where the retained chunk KV cache lives: cpu_pinned (default, page-locked host memory), cpu, or gpu. It changes where bytes live, not what is computed. At 1376x768 / 192 frames the default holds one layer's slot on the card — 0.561 GiB — instead of the whole cache's ~21.1 GiB steady / ~28.0 GiB peak. |
width / height | 1376 / 768 (set on the H3 latent node) | Multiples of 32, and width * height <= 1376 * 768. |
frames | 192 (set on the H3 latent node) | Must satisfy 17k + 5 with k >= 1: minimum 22, hard maximum 362. k = 0 (5 frames / 2 latents) is not supported and fails loud. Above 192 is allowed but experimental and warns. |
| batch size | 1 | Fixed. |
| sampler / scheduler | — | Not selectable. The node runs the RAVEN chunk-major consistency loop; stock samplers and schedulers do not apply. |
| CFG / negative prompt | — | Not exposed. One conditioning branch only. |
Scope limits that are part of the contract:
docs/validation.md are text-to-video-and-audio, and
that is the only path this pack claims to have verified.minimax_keyframes (fl2va) and minimax_refs (ref2va) conditioning
extras are rejected with an explicit error because the streaming sampler has
not implemented or verified the causal packed layout for condition rows.
Refusing is deliberate: silently dropping them would look like a quality bug.
This is a runtime limit of this sampler, not a claim that the RAVEN LoRA
is text-only. If you want to explore image- or reference-conditioned H3 modes,
take the standard MODEL from RAVEN Model Loader and build your own
workflow around the official H3 nodes — untested here, with no compatibility,
quality or reproducibility guarantee.SourceBuffer and can never be revised.The node returns LATENT, IMAGE, AUDIO. There is no whole-clip VAE decode
anywhere in this package — not for the video, not for the audio. Both media
outputs are built incrementally, in the same per-chunk callback that feeds the
preview:
| Output | How it is produced |
|---|---|
LATENT | The finished AV latent, the same nested (video, audio) structure as the input. |
IMAGE | The video collector. Each chunk's latents are decoded in the official order (to(device, vae_dtype) -> denormalize -> _adaptive_decode -> blend -> _finalize_pixels) and the finalized frames are memcpy'd straight into a pre-allocated [T, H, W, 3] host buffer. That buffer is the IMAGE. |
AUDIO | The audio collector. Each chunk's audio latents are decoded overlap-save and copied into a pre-allocated [1, 2, N] host waveform. At the end that raw waveform gets the official whole-clip normalisation — std = std(x, dim=[1,2]) * 5, std[std < 1] = 1, x /= std, the tail of comfy_extras.nodes_audio.vae_decode_audio, reproduced expression for expression. |
Why there is no full decode: both of the calls this replaced died on real
hardware. video_vae.decode OOMed at 39 frames on a 141 GiB card (130.22 GiB
allocated), and at 192 frames on a 24 GiB card the whole-clip
vae_decode_audio OOMed even with the DiT and the video VAE already evicted
— and its own OOM fallback made it worse, because the generic tiled path is
written for 4-D latents and the H3 audio latent is [B, 32, 2, T]. Both calls
only re-derived data these lanes already had, chunk by chunk.
The two collectors are therefore not optional and not best-effort. They run
on every chunk whether or not a browser is attached, and a failure in either one
fails the run — with no full decode to fall back on, dropping a chunk would mean
silently returning a partly-black IMAGE or a waveform with a hole in it.
The preview is a third lane, on top of them. It reads frames and PCM back
out of the two collectors, muxes fMP4 and sends it; it is lossy on purpose
(H.264 + AAC) and exists to show progress. It can fail at any point without
touching LATENT / IMAGE / AUDIO.
One consequence worth knowing: the preview's audio is the raw PCM, before
normalisation. The /= max(1, std*5) divisor is a whole-clip statistic that
simply does not exist yet while the clip is still being made, so a
sample-for-sample comparison of what was streamed against what was returned
differs by exactly that divisor. The video has no such split — the frames the
preview encodes are the same frames the IMAGE buffer holds.
The example graphs end in the official SaveAnimatedWEBP (video,
24 fps) and SaveAudio (FLAC), because those two take IMAGE and AUDIO
directly and carry only plain widgets. Note that upstream marks SaveAudio
deprecated at the pinned baseline — it still loads and still writes FLAC;
SaveAudioAdvanced is its replacement if you want to pick the format.
For a single muxed file instead, replace both with the official CreateVideo
(images + audio, fps = 24) -> SaveVideo pair: CreateVideo is exactly
the node upstream provides for an IMAGE + AUDIO pair.
The sampler streams into its own node widget while it samples. Contract:
< 2.5e-6, measured at M0).block + margin further latents exist. The last block - 1 + margin =
44 latents (1.1 s) of a clip therefore cannot be decoded until
finish(), and because the lane never substitutes silence, the video frames
covering that span wait with them. finish() flushes exactly that last
boundary and nothing else — everything before it already went out during
sampling. Measured at 1376x768 / 192 frames: 216 of 251 fragments (86 %)
and 168 of 192 muxed frames (88 %) were already sent when finish() was
called, the first fragment landing after chunk 1 at 51.46 s. Shorter
clips pay proportionally more for the same fixed tail: at 22 frames nothing
streams before finish() at all.frag_every_frame+empty_moov+default_base_moof and
min_frag_duration=1, delivered as video/mp4; codecs="avc1.640028,mp4a.40.2"
to a Media Source Extensions SourceBuffer. One fragment per frame means the
first picture reaches the browser one frame after it is muxed instead of one
segment. Those fragments are decodable in order only, which is exactly what
a sequentially appended SourceBuffer needs; one forced IDR per 17-frame chunk
keeps a late joiner resynchronisable. Silence is inserted automatically when a
segment carries no audio, so the two timelines stay aligned.raven.preview). n bytes cost ceil(n/3)*4 characters plus a ~200–300 byte
envelope. This is a deliberate, documented trade — see
web/PROTOCOL.md §2.LATENT / IMAGE / AUDIO are byte-for-byte
what they would have been with no browser attached — they come from the two
collectors, which are a layer below the preview and always run.AUDIO is that same buffer after
the official whole-clip /= max(1, std*5). See Outputs above.reason: cancelled; the client drops its queue, aborts the source buffer,
revokes the object URL and removes its listeners, and the backend releases the
session's resources through its own context manager on every exit path,
including exceptions.The causal DiT resolves its attention backend once, in RAVEN's own priority
order: FlashAttention 3 (flash_attn_interface) -> FlashAttention 2
(flash_attn) -> PyTorch SDPA, the packed transcription of RAVEN's
_sdpa_varlen. All three are optional; SDPA is always available, so a box
without FlashAttention runs without any configuration. FLASH_ATTN_3_AVAILABLE=0
/ FLASH_ATTN_2_AVAILABLE=0 force a backend down the chain.
1x NVIDIA H200, single GPU — that is the box every measurement in
docs/validation.md was taken on. The 192-frame reference run did not run
on a physically small card: an external process
(.cache/hold_vram.py) allocated VRAM on the H200 until only 24.100 GiB
was free, so upstream saw, and had to plan against, a real 24 GiB of headroom.
That is the envelope this package sizes itself against; it is a simulated
ceiling on a big card, not a 24 GiB GPU.
System RAM: budget 160 GiB available, 192 GB physical. This is the number most likely to bite you, and the older ">= 128 GB" figure is not good enough — measured peak host RSS was 129.854 GiB at 192 frames and 135.025 GiB at 362, both of which clear 128 GiB outright. On a 128 GiB box expect swapping or an OOM kill.
Where it goes: ~66 GB of BF16 base weights plus ~5 GB of FP32 adapter (which
live on the host whenever Comfy offloads them), the load path, the KV cache
— 22.62 GB steady / 30.10 GB peak at 192 frames, on the host by default
— and the output buffers, 2.43 GB of IMAGE at 192 frames and 4.59 GB at 362.
Those last two are the trade this architecture makes deliberately: the outputs
are built in host RAM instead of being decoded from VRAM at the end. Comfy's
reserve accounts for none of it.
Stock Comfy partial CPU offload is the memory strategy, and the only one
v0.1 makes a claim about. The loader returns a static ModelPatcher; nothing
in this package calls .to(), patch_model(), partially_load() or
cleanup_models(). Comfy owns residency. Measured on vr-1 in all three
residency states — fully resident, fully offloaded, and split across
CPU/GPU — with the cross-device LoRA residual working in the split state
(docs/validation.md, M1).
The DiT and the two VAEs are never co-resident. Every chunk alternates two
phases through ordinary comfy.model_management.load_models_gpu calls: a
DiT phase for the forwards, then a VAE phase for the decode inside
on_chunk. Each phase asks for its own memory_required and upstream decides
what to evict; this package never moves a weight itself. The last chunk stops
in the VAE phase on purpose, because finish() still has the tail flush to
decode. Measured at 192 frames: 12 chunks, 59 forward passes, 12 DiT-phase
loads and 12 VAE-phase loads.
The KV cache lives on the host by default. kv_cache_storage defaults to
cpu_pinned: the committed chunks sit in page-locked host memory and one
layer's retained rows are copied back per block. That is 0.561 GiB of VRAM
at 1376x768 / 192 frames instead of the ~21.1 GiB the whole cache would hold
steady (~28.0 GiB at its peak) on the card. gpu is available for cards with
room to spare; it changes where bytes live, not what is computed.
The reserve is the rollout workspace, not the weights. The node prices the
KV slot, the rollout buffers, the widest forward and the decode workspace
before it loads anything, and hands that number to Comfy as memory_required.
For the published request (1376x768, 192 frames, sink 2, window 2,
cpu_pinned) it comes to 2.296 GiB:
2.296 GiB = KV 0.000 + buffers 0.074 + max(forward 1.529, decode 0.635) + safety 0.692
KV is 0.000 because with cpu_pinned the cache is not on the card at all.
The number is an estimate computed from this repository's own structures and
logged at INFO on every run — and it has now been run against real weights:
the same request peaked at 21.342 GiB allocated / 21.721 GiB reserved,
inside a 24 GiB envelope. Term-by-term detail in docs/validation.md.
There is deliberately no VRAM-cap and no weight-residency widget. A "max resident GB" input would be a second, worse estimate of free memory sitting in front of upstream's real one; every time the two disagreed, the user's number would be the one turning a run that fitted into an OOM, or a run that fitted into a refusal. The 24 GiB reference figure is a constant used for reporting ("would this have fitted the smallest card we target?"), and the 24 GiB path is tested by making the device actually that small from outside the process, never by the node pretending a bigger card is smaller.
The NVFP4 text encoder runs its encode on the GPU and is then offloaded to
CPU by ComfyUI, so it holds no VRAM during sampling. Measured, not assumed:
after the encode, loaded_size = 0 and all 51 509 571 044 B of its
parameters are back on cpu — but they are back on the host, which is part
of why the RAM figure above is what it is (that run peaked at 131.632 GiB RSS
on a 39-frame clip).
DynamicVRAM / aimdo is not supported in v0.1. The loader forces the static
patcher and the sampler rejects a ModelPatcherDynamic MODEL outright. It
was never exercised: on the cu128 measurement box the aimdo path is
unavailable, and a cu130 build has no DynamicVRAM support either.
docs/validation.md — what was measured, with the numbers and artifacts. Read this before believing anything else.docs/requirements.md — the confirmed requirements contract.docs/architecture.md — module layout, sampler, KV cache, collectors and memory design.COMPATIBILITY.md — pinned baseline, feature probes and known risks.web/PROTOCOL.md — preview wire protocol v1.NOTICE — third-party attribution.The documentation links are absolute so they remain valid when the Registry
archive omits docs/ and COMPATIBILITY.md via .comfyignore.
example_workflows/, api_workflows/, web/, LICENSE and NOTICE ship with
the package.