MiniMax H3 Video Replacer V5.8.2 — Handbook
Files that matter (source of truth):
| file | role | goes to |
|---|---|---|
SoulCraft Video Replacer (MiniMax H3).json |
the canvas: the akatz lane as a workflow (§2), generated by _build/build_workflow.py from app/prompts/akatz.api.json through _build/apiflow.py |
RunPod (/workspace/runpod-slim) |
video-replacer-minimax-h3-script.sh |
the thin package script (199 lines): 2 packs + the three vendored packs (app, content checks, render speed), 19 files + 2 Qwen snapshots, the SageAttention hook, the app install, declared and handed to ComfyUI Base, which does the work | RunPod, beside the JSON |
suite.py + pytest.ini |
the suite: 137 test functions across 12 tiers, on the base’s basetest plugin |
RunPod, beside the JSON |
app/ |
the customer app (§1b): index.html, app.js, progress.js (what a run is doing, pure and tested), app.css, manifest.json, fonts/ (Outfit and Lora Italic, OFL), and the generated bindings.json + prompts/{fast,best}.api.json (_build/export_app.py) |
the server’s user folder, default/apps/video-replacer/ |
app/appmode/ |
ComfyUI’s own App Mode (§1h), one workflow per customer lane, generated by _build/appmode.py from the lane prompts through _build/apiflow.py (the builder’s last step) |
the server’s workflow list, default/workflows/ |
ComfyUI-App-Front/ |
the vendored, brand-neutral node pack (publisher Generate-AI, no nodes) that serves app/ at /apps/video-replacer/ and queues it |
custom_nodes/ (the base mirrors it) |
ComfyUI-Content-Gate/ |
the vendored, brand-neutral pack of the 🛡 safety checks (§1c): five nodes, the rules pure Python in content_gate/policy.py |
custom_nodes/ (the base mirrors it) |
ComfyUI-Render-Speed/ |
the vendored, brand-neutral pack that makes Best faster with the same pictures (§1g): the cuDNN-first attention node and one shared copy of a kept ThinkingLLM model | custom_nodes/ (the base mirrors it) |
video-replacer-minimax-h3-handbook.md |
this document: the quick start, the design and the decisions behind it (§2–§8 are V4’s, still true), the V5 record | your machine / next Cowork session |
video-replacer-minimax-h3-runpod.zip is the five files above plus every file of app/, ComfyUI-App-Front/, ComfyUI-Content-Gate/ and ComfyUI-Render-Speed/ (the script’s ZIP_EXTRA), built by $COMFY_BASE/_build/package.py. comfyui-base/
is step one on a fresh pod; this package is step two. Until V4 the whole system was one Python file — installer, 77
checks and the workflow JSON embedded together (MiniMax H3 Video Replacer Script.py, deleted in V5; it is in git
history). Its embed / export / --baseline commands are gone with it: the JSON beside the script is the workflow.
1. Deploy on RunPod (B200, else B300; CUDA 13; RTX PRO 6000 Blackwell for Quality only)
The GPU. An NVIDIA B200 first (RunPod type NVIDIA B200, sm_100, 180 GB), else a B300 (NVIDIA B300 SXM6 AC, sm_103, 288 GB; not NVIDIA B300 SXM6 AC MIG 1g.34gb, a 34 GB slice). The script declares
GPU_PREFER="B200 B300"; base 3.8.0 warns on any other GPU and goes on (an older base ignores the line). In RunPod’s
filters pick CUDA 13.0 (driver 580+). A network volume pins its datacenter: on 2026-10-16 B200s were offered in
EU-RO-1, US-CA-2, US-NC-2, US-NE-1 and US-TX-6, B300s with network volumes in EUR-IS-1 (stock moves).
podctl create --name <n> --image <img> --data-gb <N> (base 3.8.0) asks for a B200, then a B300, CUDA floor 13.0.
Neither chip has NVENC (NVIDIA’s matrix: HGX B200/B300, 0 NVENC, 7 NVDEC); the one save is core SaveVideo
(node 482, codec auto, PyAV’s software encoder) and nothing references h264_nvenc. Keep it that way. Sage runs its
Triton mode (sageattn_qk_int8_pv_fp16_triton) in 🅵 until the base’s Sage report names a CUDA mode that works on
sm_100/sm_103; Best runs no Sage since V5.6.0 (exact attention, cuDNN first, §1g). An RTX PRO 6000 Blackwell stays the choice for 🅠 Quality only, as before.
Step one — ComfyUI Base, once per pod. Upload comfyui-base-runpod.zip, extract it, run it: uv, the newest Python
and torch cu130, ComfyUI at its newest release tag (V4 pinned v0.34.0 by hand; this package floors at COMFY_MIN=0.41.0 (core SAM 3 and Track Guard), which Kijai’s int8 video VAE needs for its speed path
and tracks releases), a venv that survives a pod stop/start, rgthree / KJNodes / VideoHelperSuite / cg-use-everywhere
at their newest, the model library layout, one deletion prompt, one ledger.
cd /workspace/runpod-slimpython3 -m zipfile -e "comfyui-base-runpod.zip" .bash "comfyui-base/comfyui-base-script.sh"Step two — this package. Upload video-replacer-minimax-h3-runpod.zip beside it:
cd /workspace/runpod-slimpython3 -m zipfile -e "video-replacer-minimax-h3-runpod.zip" .bash "video-replacer-minimax-h3-script.sh" # packs + the app pack → SageAttention build → 19 files + 2 snapshots → the app → checks → suite → launch linebash "video-replacer-minimax-h3-script.sh" sage # later, or any time: "Sage: OK" → the ⚡ toggle may go ONSafe to re-run; a second run is a no-op. ≈ 201 GB downloaded (every models.py row; rule: no optional models). 5.8.0
dropped what only the retired Fast/Best canvas loaded: the 🅼 bf16 engine (66 GB), the v0.1 Ref2V turbo LoRA and the
Qwen3-VL-32B snapshot (67 GB), and with them ThinkingLLM, MaskVidExperiments and faster-whisper.
--check is the dry run; test [tier] runs the suite; --latest is the same as a plain run (base 3.0.0 takes every
pack to its remote’s HEAD on every run);
BASE_YES=1 answers the one deletion prompt; BASE_RESTART=1 lets the base restart ComfyUI (by default it prints the
launch line — a shared pod may be mid-render). Exit 0 = everything present and verified; 1 = something is missing, named.
What it does, in order (the base’s pipeline, with this package’s hooks where they belong): reads the pod · updates
ComfyUI · clones or updates the packs to their remotes’ HEAD — the base’s four plus h3-relay and ComfyUI-Viggle-Animate-H3, and mirrors the vendored ComfyUI-App-Front · reuses or rebuilds
the venv · builds SageAttention from its newest source for this GPU’s sm (pkg_post_venv calls the base’s
base_build_sageattention; a failed build FAILS the run — no fallbacks) · installs the packs’ requirements (and
accelerate by name, PIP_EXTRA: Content Gate’s device_map load needs it) · indexes every model file on the volume and MOVES known
ones into the library (exact name + exact byte size) · downloads what is missing · copies app/ to the server’s user folder (pkg_post_models; a copy that fails fails the install) · offers duplicates for deletion · syncs
the workflow’s loader paths · import-checks every pack plus accelerate and faster_whisper · applies hygiene —
--preview-method auto, --disable-api-nodes, Manager security_level normal, --use-sage-attention removed
(Sage is the ⚡ node in the graph, never a launch flag; H3 Video Creator renders noise with the flag) · writes the ledger ·
runs the suite · prints the next-steps checklist.
The model library (ComfyUI/models/…, the base’s <category>/<Family>[/<Purpose>]/<file>; V4 filed these flat):
| dest | file | used by |
|---|---|---|
| diffusion_models/MiniMax-H3 | minimax_h3_ref2va_pruned_int8_convrot.safetensors (21 GB) |
🅵 🅸 🅠 |
| diffusion_models/MiniMax-H3 | minimax_h3_ref2va_bf16.safetensors (66 GB) |
🅼 |
| text_encoders/MiniMax-H3 | qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors (16 GB) |
all (nvfp4 = Blackwell) |
| vae/MiniMax-H3 | minimax_h3_video_vae_int8_convrot.safetensors (Kijai, 3.2 GB), minimax_h3_audio_vae_fp32.safetensors |
all; the same two files MiniMax H3 Video Creator and Continuer install; on a shared pod they download once |
| checkpoints/SAM3 | sam3.1_multiplex_fp16.safetensors (1.7 GB) |
mask (inside the segmentation subgraph) |
| vae_approx/ (flat) | taeh3.safetensors (madebyollin/taehv, MIT, 23 MB, pinned to the commit that added it) |
📺 the live preview (V5.4.0): ComfyUI’s preview loader reads this folder’s root |
| loras/MiniMax-H3/Turbo | minimax_h3_ref2v_turbo_4step_v0.1_comfyui_bf16.safetensors (2 GB) |
🅵 🅸 |
| embeddings/ (flat) | the 10 minimaxh3_*.safetensors style embeddings |
🎨 style box — ComfyUI resolves embedding:<name> in the folder root only, so these have no Family |
| LLM/Qwen-VL | Qwen3-VL-32B-Instruct/ (stock snapshot, 66.7 GB) |
🧠 describers + reviewer ([local] Qwen3-VL-32B-Instruct) |
| LLM/Qwen-VL | Qwen3-VL-4B-Instruct/ (stock snapshot, 8.9 GB) |
the 🛡 content checks in every mode (§1c) and, on the same loaded copy, 🅵’s two describers (Content Gate’s describe, V5.5.0) |
The Qwen snapshots are stock Hub repos (V5.1.0). Until V5.0.1 the describers ran on Qwen3-VL-32B-Heretic-v2, a
LOCAL row because its repo (rpDungeon/...) left the Hub; a fresh volume could not install it, and an abliterated model
does not belong in an SFW product. V5.1.0 uses Qwen/Qwen3-VL-32B-Instruct, the model the Heretic was cut from (MiniMax’s
own H3 text_encoder/ is bit-identical to it, so the describer shares the encoder’s lineage). ThinkingLLM’s catalog does
not list it, but its local scan lists any snapshot folder under models/LLM/Qwen-VL/ as [local] <folder>, which is the
value the three 32B nodes carry. The 🅵 describer is the catalog’s own Qwen3-VL-4B-Instruct (locked commercial model),
declared as a row so it downloads at install rather than on a customer’s first run. Two cautions: the base cannot pin a
Hub revision for a snapshot (main only; the byte total is the guard), and ThinkingLLM shows an installed catalog model as
... [installed], a different combo value than the one saved in the workflow; the app’s server reconciles that at run
time (§1b).
In the browser: hard-refresh, load SoulCraft Video Replacer (MiniMax H3).json, upload the clip (🎬 1, any fps) and a
full-body photo (🧍 2), name the person in 🎯 3, set ⏱ Duration, pick a MODE, Run ▶. §2–§4 explain the boxes and the
graph; §6 is the first-run check list.
1b. The customer app (V5.1.0)
Customers do not need the canvas. The pod’s own ComfyUI serves a one-page app at
https://<pod id>-8188.proxy.runpod.net/apps/video-replacer/. RunPod’s console lists ports, never paths, so since
V5.2.6 the app also has a port of its own: the Connect tab’s Video Replacer App link, port 8189,
https://<pod id>-8189.proxy.runpod.net/ (APP_PORTS in the script; ComfyUI Base 3.9.0’s forwarder serves the app at
that port’s root and podctl create, status and tunnel name it). The install also ends with the 8188 address in a
box, and the page shows its own address under the headline for bookmarking. There is no login:
anyone with the address can make videos and open ComfyUI itself (every video made on the pod), so share it like a
password. My videos lists only the videos made in the same browser (V5.2.3, below). The page offers
1 · Your video, 2 · The new person, who to swap, a likeness consent tick, Fast · about 3 min or
Best · about 30 min, and one Make my video button; then plain progress stages, the result with a before/after
slider, Download, Try again, Change photo, Change video, and a “My videos” list.
How it works, in one line each:
ComfyUI-App-Front/(vendored, brand-neutral, publisher Generate-AI, no nodes) adds routes to ComfyUI’s own server:GET /apps/{slug}/(the page, path-contained),GET /apps/{slug}/api/status,POST /apps/{slug}/api/run,GET /apps/{slug}/api/history[/<prompt_id>](this browser’s runs only).- Uploads go through ComfyUI’s own
POST /upload/image(videos too); progress through ComfyUI’s/ws; the result through the app’s ownapi/historyand ComfyUI’s/view. One origin, so nothing else is hosted and no CORS is involved. - My videos is per browser (V5.2.3). The page makes one random key the first time it opens (32 bytes, kept in
localStorageasvideo-replacer:owner) and sends it in theX-App-Ownerheader with every run and every history read, never in a URL. A run stores only a SHA-256 tag of the key;GET api/historyandapi/history/<id>return only this key’s runs of this app, without the graph or the tag. The page never reads ComfyUI’s shared/history. Another browser, a private window or cleared site data starts with an empty list (the videos stay on the pod). What it is not: a lock./view,/historyand the canvas are on the same address, so anyone holding it can still reach every file; App Front does not gate/view(it would break the canvas and hide nothing/historylists). - Delete (V5.2.7).
DELETE api/history/<id>deletes one of the visitor’s own finished videos,DELETE api/historyall of them: the history entry, the uploaded video and photo, and the result and any preview the run wrote. Real removals, no trash, so the page asks once first. A file another run or a queued job still uses stays (a photo reused by Try again, a canvas job naming the same upload). A video still being made answers 409 (stop it first). Nothing of what was deleted is written down: the log line has counts only; the run’s timing record stays, as it holds times and node names, never a name or text. The buttons are the app redesign’s. runloads the lane’s API prompt (app/prompts/<lane>.api.json; 5.7.1: the swap lanes), writes the customer’s values into the inputsapp/bindings.jsonnames, reconciles ThinkingLLM’s model labels against the live list (... [installed]), and queues it withpartial_execution_targets: ["482"]so the 👀 previews do not run.statusrefuses to run (and the page says “Getting the studio ready…”) when a node class, a model file or folder, or SageAttention (needed by 🅵) is missing.- The two prompts and
bindings.jsonare generated:uv run --upgrade _build/export_app.pyloads the workflow into the testbed’s real frontend, sets each lane’s radio state, waits for the rgthree switches to type their inputs, callsgraphToPrompt(), and checks every switch’s sources and every binding. Rebuild them after any graph change. It refuses to export (exit 5) while a pack of this package’sPACKSsits on the testbed at another commit than its row (bash testbed.sh --packsmoves them to their remotes’ HEADs, which is what every install takes; then move the row to that commit), and it writes_build/pack_inputs.json, each prompt class’s declared inputs from the server’s/object_infoand the commit they came from. The contract tier checks the prompts against it and its commits against the rows; the server tier checks them against the machine’s own/object_info(V5.5.1). - The RunPod proxy cuts a request at 100 s and drops idle websockets, so the page keeps its socket alive, reconnects with
the same client id and re-reads its run from
api/history; uploads are capped at 90 MB (ComfyUI’s default limit is 100 MB).
A run a 🛡 safety check stops (§1c, §1d) never reaches “Something went wrong”. A stop in the words puts the customer back on the form, everything they typed kept, with Please reword this. We can only make videos that are safe for everyone. under the field the check named. A photo, video or result stop shows the failure screen titled with the check’s own name (“Safety check 2 · your photo”) and one kind sentence with the button that fixes it. Nothing is saved, so My videos never lists it, and there is no control of any kind that skips a check.
Lanes: fast = 🅵 on, 🧠 off, 🎭 on (target: a 15 s clip in about 3 minutes on a warm B200); best = 🅠 on, 🧠 on, 🎭 on, ⚡ on. A cold pod first loads ~120 GB of weights from the network volume (5-9 minutes on any GPU).
The look (V5.4.0) is the SoulCraft brand foundation’s “Cosmic / Soul” system (SoulCraft-Brand,
01_Brand/brand-foundation.md): a Deep Space Navy field with a soft Soul Violet nebula and two sparse CSS star layers,
the gold star mark and the Outfit wordmark (“Soul” in Starlight, “Craft” in the gold gradient), Luminous Gold only on
the one call to action, Starlight text and Nebula Grey for the quiet text, Lora Italic for the tagline line. Dark only.
The fonts are served from app/fonts/ (Outfit as a Latin subset, Lora Italic whole because its licence reserves the
name; fonts/OFL.txt carries both licences); nothing is fetched from anywhere else. Every text pair is at least 4.5:1
on the panel it sits on and field borders at least 3:1; motion stops under prefers-reduced-motion, and
forced-colors drops the decoration. Ids, copy keys and behaviour are those of V5.1.0 and V5.2.0; the one addition is
the live view below; the help lines sit behind an (i) beside each title (Paul: “little to no text”), still read as
each field’s description by a screen reader.
The live view (V5.4.0). Paul: “users are going to want to see every update”. The progress screen shows what the server sends, calmly: a big stage (the source frame with the new person inset; then the person found, as a glow; then every sampling step as the studio renders it, with “Step k of N”), eight short stages with their own times (Uploading, Safety check with one shield per check, Finding the person, Your new person and Reading the scene with the describers’ words, Warming up only when models load, Swapping, Polishing), “reused” for cached work, and an Activity log, closed until opened, with every node in plain words and its time. aria-live hears the stages, never the frames. Where each piece comes from:
- the frames: 📺
ModelPreviewOverrideKJ(node 1031, on the guider’s model) decodes each step with madebyollin’s TAEH3 (models/vae_approx/taeh3.safetensors, MIT) and sendskj_preview_overrideover/ws(16 frames at 12 fps as an animated preview, at most 768 px). The base’s--preview-method autoalone would give only ComfyUI’s RGB projection (latent_preview.py: Auto becomes Latent2RGB); the app also shows those binary previews (event 1 or 4) if the override is ever removed; - node states:
executing,progress_state,execution_cached,executed; the step counter:progresson the sampler; - the mask and the words:
bindings.jsonlists per lane a fewpreviews(📱 one frame of the person mask, and the identity and scene words: 👀 3 and 👀 4 in Best, 📱 🅵 nodes in Fast), found by title by_build/export_app.py; App Front adds them topartial_execution_targets, so they come back inexecutedmessages. None of them runs per frame.
What a stage IS lives in app/progress.js (pure, no DOM; the suite replays a recorded Best run through it with node).
ComfyUI runs nodes in dependency order, not stage order (the 🎭 edge softener 1021 runs right after the mask), so a stage
is done only when every node of it the run executes has finished or was reused; the current stage is the stage of the
node executing now; an early node counts toward its own stage without moving the pointer. The current stage always
shows the node running in plain words, its time against what it usually takes on this pod, and a gentle pulse while
a model loads; “Loading the models” shows only while a loader really runs, and goes the moment it is done.
Time left is a range (“8-11 min left”), never one falsely precise number: each stage still to come takes its
p50 to p90 from this pod’s own past runs (App Front’s GET api/estimate, cold or warm, scaled to the clip); the
stage in progress takes what is left of it; while sampling, the swap takes the steps left times the measured seconds
per step; finished stages re-scale the rest, so it re-learns after every stage. With no history yet it is the
manifest’s number as a range.
Out of memory. App Front frees the GPU after an out-of-memory error and says so afterwards (an
app_front_recovery message on /ws, or GET api/run/<id> once recovery is no longer pending). The page waits for it
(at most 45 s), then shows one line with Try again as the button and every input kept: “Freed up the studio. Try
again.” for memory_reset, “The studio is busy. Try again in a minute.” for out_of_memory, its usual failure
screen otherwise. Mock: &scenario=memory, &scenario=busy.
Delete (Paul: “the user should be able to DELETE their uploads and outputs”). A trash button on each My videos
tile, Delete on the result screen and Delete all beside My videos, each after one confirm (“Delete this video and its
uploads? This can’t be undone.”). They call App Front’s owner-keyed DELETE api/history/<id> and DELETE api/history
(the X-App-Owner key, never in the URL): the run, its uploads and every file it wrote go, for real. A 404 with App
Front’s answer means already gone; anything else says “Couldn’t delete that.” and deletes nothing in the page. After a
delete the form drops the uploads that went with it, and a deleted result sends the page back to the form.
1c. The safety checks (V5.2.0)
SoulCraft is SFW with no switch: no nudity, no sexual content, no brutal gore or violence (realistic blood is fine);
children may appear in ordinary contexts. Until V5.1.0 that held by construction only (stock models, no toggles, the
consent tick): any clip or photo was swapped as given and the free-text fields were not read. V5.2.0 checks every run
with the vendored ComfyUI-Content-Gate pack, in ONE canvas group, 🛡 Safety checks · always on (under ⚙️ Sampling),
whose title carries none of the markers 🎛 MODE or 🎛 OPTIONS match: nothing in it is ever bypassed, in any mode or
either app lane, and no field, toggle or button turns it off. A note in the group says all of this on the canvas. The
rules follow the Krea 2 Image Creator’s house pattern (yes/no facts asked of a small VLM, a fail-closed reading, the
measured age fact), re-written brand-neutral for this pack.
| node | id | asks about | runs |
|---|---|---|---|
| 🛡 checker model | 1024 | loads models/LLM/Qwen-VL/Qwen3-VL-4B-Instruct (the 🅵 describers’ snapshot; no new download), once per process |
first |
| 🛡 1 · your words | 1025 | 🎯 who, 🎽 keep, ✍️ details as one request sentence, text only | right after the model, alongside the render |
| 🛡 2 · your photo | 1026 | the 🧍 photo at ≤1024 px, then any 📸 extras as one sheet | alongside the render (998 reads the photo from 🧍 2, V5.4.3) |
| 🛡 3 · your video | 1027 | a sheet of 6 frames spread over the 🎬 clip | alongside the render (956 reads the frames from 🎬 1, V5.4.3) |
| 🛡 4 · the result | 1028 | a sheet of 6 frames of the finished video, plus what 1 to 3 found | before CreateVideo and the 👀 8 preview, which read the frames THROUGH it |
| 👀 Safety report | 1029 | every check’s answers and verdict for the last run: passed, STOPPED, or not reached | canvas only |
The checks are chained (1 → 2 → 3 → 4), so they always run in that order, and every check is an async node: ComfyUI
starts a ready async node before any other ready node. Check 1 does not even wait for its three text nodes: it reads
the values from the queued prompt when they are plain text primitives, so the words are judged right after the model
node. Since V5.4.3 the checks never wait in front of the render (Paul: they “CANNOT be over bearing and block
renderings”). 998 and 956 read the photo and the frames from their loaders, so checks 1 to 3 run alongside SAM3, the
describers and the encodes, each on a worker thread and on the checker’s own CUDA stream (ComfyUI goes on with every
other ready node, and the GPU runs the check’s kernels beside theirs). A stop there still raises from the check that
found it and ends the run, in practice long before sampling (a check takes about a second, SAM3 alone tens of
seconds). Check 4 is the one gate in front of what is kept or shown: CreateVideo and 👀 8 read the result through
1028, and 1028 holds what 1 to 3 found (checked), so a finished video is never saved or shown unless every check
passed. What runs on an input before its check has answered: SAM3, the encodes and the working frames, all on the
customer’s own upload; nothing is saved. The one link from a check into the render is 🛡 3’s fingerprint, the
describers’ privacy seed (V5.4.2): the describers start once check 3 has answered, which it has in practice before
923’s resize of the frames ends; with CONTENT_GATE_PRELOAD=0, a cold first run’s describers wait for the checker’s
load.
Nothing on the canvas weakens a check (V5.2.2). The five 🛡 nodes have no widgets: they take links only. The
checker model is fixed in the pack (MODEL, the 4B folder above: the questions were worded and measured on it, so
there is no dropdown to point at another folder), and every contact sheet has 6 frames (SHEET_FRAMES: no frame count
to lower to one). A hand-written API prompt that names model or frames on these nodes has them ignored, because
ComfyUI passes a node only the inputs it declares. The suite pins this on every instance (the workflow, both exported
lanes, the pack’s classes and the live server’s /object_info), with a planted frame-count fault that must fail.
What stops (the brand rule, no wider). Nudity or sexual content, or brutal gore / graphic violence, in the words or any picture. Someone who may be under 18 stops a run only together with underwear, lingerie or a sexually suggestive pose anywhere in it (in the words or a picture): a child’s photo plus “wear lingerie” in the details stops at check 2. What passes, and the questions say so to the model: swimwear, a kiss, a hug, realistic or stage blood, sports, staged fights, children in everyday clothes or swimwear, an adult in lingerie. Any fact the model does not answer with a clear labelled yes/no stops the run as “unsure” (fail closed); an error while asking is the same. A missing model folder is a plain error that names it.
Why not ThinkingLLM nodes. Each ThinkingLLM node holds its own copy of the model, samples with a temperature, and can return a disk-cached or per-node “saved” answer keyed by seed (seed 1 ignores image changes). A safety answer must come from the exact input, greedy. So the pack loads the same folder itself, once, and decodes greedy.
Cost (estimated from measured token counts; each check now times itself, see below). Measured with the real Qwen3-VL-4B processor: the words call is 222 prompt tokens and a 22-token answer; the photo (1024×768) 1013 → 33; each 6-frame sheet (1536×576) 1124 → 33. That is 3.5k prompt tokens and 121 answer tokens per run. At HF transformers’ greedy decode of a 4B model (~25 ms a token, bs 1; Krea 2 measured 0.7 s for an 8-token fact call on the same 4B) plus a prefill of tens of milliseconds per call on a B200, the four calls take about 3.5 to 4 s warm: about 2 % of the 🅵 3-minute target, 0.2 % of Best. A stop costs a few seconds more: to say where, the stopping check asks again per field, per extra photo or per frame (up to 6 calls). The first run after a server start also loads 8.9 GB (about 3 s from the page cache, which the 🅵 describers fill anyway; 10 to 15 s cold from the volume, and about 40 s measured on kmgqu8yf0pscts’s network volume, 2026-10-16). VRAM: one more bf16 copy of the 4B (~9 GB) stays loaded.
Speed (V5.4.3). Paul, 2026-10-16: “The Safety checks are a 1.5 minutes … they need to be like 5 seconds.” That run (5.2.6, cold, B200) spent about 40 s loading the checker from the volume, then about 60 s with the GPU at 0 % and one CPU thread at 100 % before SAM3. Read from the code, the four checks are about 1 s each warm; in that window ComfyUI also ran the clip’s decode, the lanczos resizes 923 and 1013 (PIL, one thread) and the two ThinkingLLM describers, each loading its own 4B copy from the volume (the render-speed work moves those). What changed in the pack (Content Gate 1.4.0), with the checks themselves unchanged (the same model, questions, frames, sizes and fail-closed reading):
- No run waits for the load. On a CUDA server the checker loads straight onto the GPU and runs two tiny warm-up
calls as soon as ComfyUI starts serving (
CONTENT_GATE_PRELOAD=0turns that off). Without it, the model node starts the load on its own thread and returns, so the loaders and the clip’s decode run alongside and only check 1 waits. - Off ComfyUI’s loop, on its own stream. Each check runs on a worker thread (
asyncio.to_thread) and its own CUDA stream, so ComfyUI keeps executing the other ready nodes, and their GPU work, while it asks. Warm, checks 1 to 3 hide behind the clip’s decode and SAM3; check 4 has nothing to hide behind (it reads the finished frames). - No CPU picture work. The six frames go to the GPU as they are; fitting (antialiased bicubic, the filter the model’s processor resizes with afterwards anyway; at most 2 of 255 levels from PIL’s Lanczos, 0.2 on average, measured on 640 px and 1080p frames), the contact sheet and the processor’s resize, normalise and patching all run there.
- Each check times itself. A node that awaits a thread reads as almost nothing in App Front’s per-node timings, so
every check prints
[Content Gate] 3 · your video: passed in 0.84 s (1 call, model 0.81 s, 1124 + 33 tokens)and writessecondsintochecks.jsonl; the load prints its own seconds and “checker ready at start-up”. - Off the render’s path (graph). See above: only check 4 sits in front of anything, and it has to (it reads the finished frames).
- False stops are rare (Paul: “the safety checks CANNOT be over bearing”). A fact the first answer leaves unclear
(no labelled line, a contradiction, an error) is asked once more, alone and constrained: one forward pass, the
model’s next-word odds of yes against no (
policy.read_yes_no: clear when yes and no together are at least half the odds and one is at least twice the other). The run stops as unsure only when a fact is unclear twice, so one stray line of formatting no longer costs a customer a run; it still never passes an unclear fact. Everyday requests that must pass arepolicy.EVERYDAY_WORDS(a kiss, a hug, swimwear, sports, stage blood, a fight, children in everyday clothes and at the beach, and ordinary requests) andpolicy.EVERYDAY_PHOTOSnames the photo categories;ComfyUI-Content-Gate/self_check.py --server <host:port> [--photos DIR]queues every one through a running ComfyUI and lists any false stop (exit 1). - One plain line on a stop. The app shows the check’s title, one sentence and the one button that fixes it, keeps every input (a words stop lands on the field to reword), and no longer puts the stop’s own message (what was found where) behind the details disclosure.
- Not done, on purpose. Asking the words, the photo and the video in one call would change the questions that were measured (one subject per call) for about half a second; shorter keys or fewer tokens would change the measured wording. Expected warm: check 1 about 0.5 s, check 2 about 0.8 s, check 3 about 0.8 s, check 4 about 0.8 s (a re-ask adds about 0.05 s a fact), about 3 s of check time of which only check 4’s ~0.8 s is on the critical path. The pod measurement is owed (HANDOFF).
The fingerprint (V5.4.2). Checks 2 and 3 also fold every value of every picture they pass (the photo, each 📸
extra, every frame of the clip, as the 8-bit pixels they were decoded from) into one SHA-256, on a worker thread while
the model answers, and check 3 gives it out as fingerprint, an INT. Since V5.5.0 the describers’ seed is a node of its
own (Content Gate · fingerprint, 1043: the same SHA-256 fold over the photo, the extras and the clip, with no model), so
no describer waits for the checker to load; it seeds every describer (916, 921, 976 and 🅵‘s 1017, 1018). ThinkingLLM keeps each answer in prompt_cache.json and a per-node
node_prompt_state.json, keyed by the seed and a picture hash made of only the picture’s size and its first ten values,
and returns a match with no model call. With a fixed seed, two customers’ clips of the same size that open on the same
corner pixels shared one action script. Seeded by the fingerprint, a cached answer can only be the one for these exact
pictures. Cost: about 0.5 s for a 15 s clip at 640x640, 2 to 3 s at 1080p, mostly hidden behind the check’s model call.
Nothing a customer typed is written into a file. ComfyUI embeds the queued prompt (the three text fields included)
and the workflow in every saved video and preview image unless it runs with --disable-metadata, and SaveVideo has no
switch of its own. The script puts that flag on the launch line (HYGIENE_ARGS), so the MP4 a customer downloads and
shares carries no one’s words. It holds for every workflow on that server: a canvas user who wants the workflow inside
a PNG drags the .json instead.
The log. Every check, passed or stopped, adds one line to <ComfyUI user dir>/content-gate/checks.jsonl (on the
pod runpod-slim/ComfyUI/user/content-gate/checks.jsonl): time, check, what was looked at, outcome, reason. No words,
no pictures, no answers. It is what the questions get tuned from (HANDOFF’s measurement plan).
1d. If a safety check stops your run
- On the canvas: the check that stopped is outlined red, and its error says which check, what it found and where, and what to change, for example 🛡 3 · your video stopped this run: in the video, frames 48 and 96 of 345 show brutal gore or graphic violence. Choose a different video. Nothing was saved. (frames are counted after the 24 fps load and the trim). 👀 Safety report lists every check’s answers for that run, including the ones not reached.
- In the app: check 1 puts the customer back on the form with the message under the field to reword; checks 2 to 4 show the failure screen titled “Safety check N · …” with one sentence and the button that fixes it (Change photo, Change video). An “unsure” stop says the check could not finish and offers Try again.
- What to change: reword the named field; choose a different photo or video. A result stop usually follows from the photo or the clip; try a different one. Retrying the same inputs gives the same answer (the checks are greedy).
- What never happens: nothing of a stopped run is saved or previewed (check 4 sits before the encoder and 👀 8), and nothing skips a check. A false stop is a measurement for the questions (§1c, the log), not a reason for a switch.
1e. If a run runs out of GPU memory
The failure screen says the studio ran out of memory (ComfyUI’s torch.OutOfMemoryError). Since V5.2.1 no lane keeps
two copies of one model, which was the Best lane’s cause (three kept 62 GiB copies of the 32B describer). If it happens
anyway:
- In the app: once App Front’s out-of-memory recovery lands (the per-stage timing work), the screen frees the GPU itself and Try again runs on a clean card. Until then, use the canvas step below, then Try again.
- On the canvas or from a terminal: ComfyUI’s own
POST /freeunloads its models and frees cached memory:curl -X POST http://127.0.0.1:8188/free -H 'Content-Type: application/json' -d '{"unload_models": true, "free_memory": true}'. A ThinkingLLM node that was ever run withkeep_model_loadedon keeps its copy outside ComfyUI’s model manager. Since V5.6.0 the Best lane’s 32B is ONE copy shared by its three nodes (ComfyUI-Render-Speed), and/freelets go of it too; if memory still stays high after/free, restart ComfyUI.
1g. Best, faster (V5.6.0)
Best spends most of its time sampling (25 steps at about 210k tokens a step, attention about 85 % of a step), then three Qwen3-VL-32B passes. Five changes, the first three shipped, the last two behind a builder constant until the pod A/B shows the same faces, hands and mouth timing (HANDOFF lists the arms):
| change | where | the picture | |
|---|---|---|---|
| A | exact attention, cuDNN first: ComfyUI forces FlashAttention-2 ahead of cuDNN on NVIDIA (ComfyUI #15190); on a B200 FA2 runs about 430 TFLOPS and cuDNN 9.24 (torch 2.14.1+cu130) about 1,500. Best no longer runs ⚡ Sage | ComfyUI-Render-Speed SDPABackendOrder (1036) on the guider’s model, the Best state without ⚡ |
the same (exact BF16, no int8) |
| B | one 32B for 916, 921 and 976 instead of three loads (ThinkingLLM keeps a copy per node), with exact SDPA attention (auto took Sage’s Hopper kernel on the B200) |
keep_model_loaded on in all three; the pack shares one copy and frees it when ComfyUI needs the room or unloads |
the same |
| C | models kept loaded: the 32B stays loaded between Best runs (B); the boot warm-up is App Front’s (V5.5.0), which warms 🅵 and, once it takes a list of lanes, Best too. --highvram and --gpu-only stay off: on ComfyUI 0.41 they switch DynamicVRAM off |
KEEP_32B |
the same |
| D | the reference video at 0.5 MP while the output stays 0.98 MP (🅵 already uses 0.3 MP) | BEST_REF_MP (off) |
to be shown by the A/B |
| E | 16 to 20 steps instead of 25, same sampler | BEST_STEPS (25) |
to be shown by the A/B |
| A in 🅵 | the same cuDNN-first order for Fast (its steps are compute-bound: 37.7 s/it at 100 % GPU on the pod with Sage) | FAST_ATTENTION (“sage”; “cudnn” turns 🅵’s Sage node off) |
to be shown by the A/B |
Expected (estimate, warm 15 s clip, B200): about 30 min today; about 20 to 25 min with A to C (a first Best run after a restart also loses its 2 to 6 minutes of loading once the warm-up covers Best); about 13 to 16 min if D and E (20 steps) pass the A/B. The 32B’s own writing (about 4 min) is untouched.
1f. Speed (V5.5.0)
The render-speed research (2026-10-16) estimated where a warm 15 s Fast run’s 3 min 51 s went: SAM3 about 50 s, the two 4B describers about 40 s (a fresh 8.9 GB load every run included), the mask cleanup about 15 s, and sampling about 45 s. Most of it ran one stage after another although the stages do not wait for each other. V5.5.0 takes the low-risk set. Every number below is an estimate until the pod measurements in HANDOFF replace it.
| change | where | expected, warm Fast 15 s | risk |
|---|---|---|---|
🅵‘s describers are Content Gate’s describe (1017, 1018) on the checks’ one loaded 4B, each on a worker thread and its own CUDA stream, while SAM3, the cleanup and the loaders run |
🅵 | -30 to -40 s | none to low: same model, prompts and sampling (0.7, top-p 0.8); the words differ, not their kind |
SAM3 refine_iterations 2 to 0 (910) |
every mode | -25 to -30 s | low: 963’s 40 px grow and 32 px token snap are coarser than a refine pass |
| the mask cleanup at a quarter of SAM3’s size (KJ ResizeMask down, 962 shrink and grow 4 to 1, back up), 1039-1042 | every mode | -8 to -12 s, -40 to -80 s at 1080p | low |
| SAM3 sees the working frames (1038, in series: bypassed it passes the clip’s own to 🅸 🅠 🅼) | 🅵 | a share of the line above at 1080p | low: SAM3 resizes to 1008 itself |
| working size = the clip’s own under 0.5 MP, 0.3 MP floor (1036, 1037 into 942) | 🅵 | -15 to -20 s on clips under 0.5 MP | low: never an upscaled background |
the 0.3 MP reference video (1013) on area |
🅵 | about -3 s | none: never shown, H3 resizes it again |
one warm-up run of the Fast lane queued by App Front at boot (warmup in bindings.json) |
the app | 2 to 6 min off the first customer after a restart | none: no visitor sees it; its outputs are deleted |
Warm-up at boot (App Front 1.6.0). After every ComfyUI restart, App Front queues one Fast run by itself as soon as
the server answers, on a 2 s mid-grey placeholder clip and photo it writes itself, with one sampling step, so the H3
engine and its LoRA, the text encoder, both VAEs, SAM3 and the describer are loaded before the first customer clicks
Run. It only runs when the Fast lane is ready and the queue is empty; a customer who arrives meanwhile queues behind
it, and the page’s status says warming. Its history entry, outputs and placeholders are deleted when it ends; no
visitor’s My videos lists it. APP_FRONT_WARMUP=0 switches it off. The server log says
[App Front] warm-up of <slug>/fast finished in N s (ok).
A new interface needs no restart (V5.5.0). App Front reads the page’s files (index.html, app.js, app.css,
progress.js, fonts, manifest, bindings, prompts) from disk on every request, and the app port’s proxy only forwards
bytes, so bash video-replacer-minimax-h3-script.sh app (a copy into the server’s user folder) is live on the next page
load. Only a change to App Front’s own Python needs ComfyUI restarted. Each finished run logs
[App Front] timings of <prompt_id> (...) -> <file>, and api/timings reports in_flight, so a missing record shows
whether the run had not ended yet or where its file went.
🅵’s attention arm (FAST_ATTENTION in the builder). Fast sampling is compute-bound on the B200 (pod, 2026-10-16:
100 % GPU, 37.7 s/it with Sage’s Triton kernel). "sage" ships. "cudnn" turns 1011’s Sage off and puts Render
Speed’s SDPA backend order (cuDNN first, exact attention) in its place. It needs the ComfyUI-Render-Speed pack (PR #21)
and a re-export, and becomes the default only after the pod A/B (same clip, photo and seed; s/it and the frames).
Not changed, on purpose: no --highvram or --gpu-only (on ComfyUI 0.41 either turns DynamicVRAM off, and with it
the model compiler path the H3 forward uses); the attention backend (Sage vs cuDNN-first is the pod A/B’s decision).
Target after this set: about 2:15 to 2:30 warm (from about 3:55).
1h. ComfyUI’s own App Mode (5.8.0)
A second way in, for buyers who know ComfyUI. The server’s workflow list offers one workflow per customer lane, each opening straight in App view with the custom app’s form:
| workflow | lane | form |
|---|---|---|
| SoulCraft Video Replacer - MiniMax H3 - App - Full swap, 1 person | akatz (routing “one”, every length) | Your video · Start of the part we use · Seconds to use · Full body · Who to swap |
| … - App - Full swap, 2+ people | akatz-guard (“many”; the app sends a clip of up to 124 frames to akatz-guard-max) | the same, then Frame to pick from · Tap across · Tap down |
| … - App - Action | viggle-guard-06 (the Action lane) | the same five, then the tap |
One source. Each file is the lane’s own API prompt (app/prompts/<lane>.api.json) turned into a workflow by
_build/apiflow.py, which refuses a workflow that does not convert back to that prompt exactly (every node, link and
value). A node’s shape (slot order, widget order, the hidden seed control and upload values, which the page reads BY
POSITION) comes from a real save of that class where one exists in the pinned packs’ examples, the lane authors’
graphs, the V5.0.1 source or the shared intake pool, and its widgets match the pinned source; otherwise from the pinned
source itself (tier 0’s static schema). apiflow.py --refresh records the choice per class in
_build/apiflow_nodes.json (49 of 65 classes from saves). Labels and help are the manifest’s (video_card_title,
trim_label, duration_label, photo_main, who_label + who_help, pick_scrub, pick_x_label, pick_y_label);
a field without a copy key stops the build. Each file has its own graph id and draws a fresh seed per run, as the app
does.
What App Mode cannot do, and says where it matters. It has no picture to tap: viggle-guard finds the person by the who text without a tap, akatz-guard starts SAM 3 at the frame’s centre (its Tap across help says so), so the guard lanes show the tap as numbers. It takes the Full body photo only: the app stitches photos 2-4 into one sheet and a LoadImage cannot be left empty. It cannot check that the part fits the clip, prepare the clip, or ask for consent as a tick box (the photo’s help carries the consent line). The swap lanes carry no Content Gate checks (5.7.0, “output first”), so App Mode has none to hide or show either.
2. The canvas (5.8.0)
A second way in, for buyers who know ComfyUI. The server’s workflow list offers one workflow per customer lane, each opening straight in App view with the custom app’s form:
| workflow | lane | form |
|---|---|---|
| SoulCraft Video Replacer - MiniMax H3 - App - Full swap, 1 person | akatz (routing “one”, every length) | Your video · Start of the part we use · Seconds to use · Full body · Who to swap |
| … - App - Full swap, 2+ people | akatz-guard (“many”; the app sends a clip of up to 124 frames to akatz-guard-max) | the same, then Frame to pick from · Tap across · Tap down |
| … - App - Action | viggle-guard-06 (the Action lane) | the same five, then the tap |
One source. Each file is the lane’s own API prompt (app/prompts/<lane>.api.json) turned into a workflow by
_build/apiflow.py, which refuses a workflow that does not convert back to that prompt exactly (every node, link and
value). A node’s shape (slot order, widget order, the hidden seed control and upload values, which the page reads BY
POSITION) comes from a real save of that class where one exists in the pinned packs’ examples, the lane authors’
graphs, the V5.0.1 source or the shared intake pool, and its widgets match the pinned source; otherwise from the pinned
source itself (tier 0’s static schema). apiflow.py --refresh records the choice per class in
_build/apiflow_nodes.json (49 of 65 classes from saves). Labels and help are the manifest’s (video_card_title,
trim_label, duration_label, photo_main, who_label + who_help, pick_scrub, pick_x_label, pick_y_label);
a field without a copy key stops the build. Each file has its own graph id and draws a fresh seed per run, as the app
does.
What App Mode cannot do, and says where it matters. It has no picture to tap: viggle-guard finds the person by the who text without a tap, akatz-guard starts SAM 3 at the frame’s centre (its Tap across help says so), so the guard lanes show the tap as numbers. It takes the Full body photo only: the app stitches photos 2-4 into one sheet and a LoadImage cannot be left empty. It cannot check that the part fits the clip, prepare the clip, or ask for consent as a tick box (the photo’s help carries the consent line). The swap lanes carry no Content Gate checks (5.7.0, “output first”), so App Mode has none to hide or show either.
2. The canvas (5.8.0)
SoulCraft Video Replacer (MiniMax H3).json is the default customer lane, akatz (the manifest’s routing for one
person since the 5.8.0 pod A/B), the same graph the app queues: _build/build_workflow.py turns app/prompts/akatz.api.json into a workflow
with _build/apiflow.py (§1h) and refuses one that does not convert back to that prompt exactly. A start card on the
left names the App Mode workflows. To run it: the clip in 🎬 985, the photo in 🧍 496, who in 🎯 931, seconds in
⏱ 480, Run; the video lands in ✅ 482. Nodes stand in columns by their distance from the inputs.
SoulCraft Video Replacer (MiniMax H3).json is the default customer lane, akatz (the manifest’s routing for one
person since the 5.8.0 pod A/B), the same graph the app queues: _build/build_workflow.py turns app/prompts/akatz.api.json into a workflow
with _build/apiflow.py (§1h) and refuses one that does not convert back to that prompt exactly. A start card on the
left names the App Mode workflows. To run it: the clip in 🎬 985, the photo in 🧍 496, who in 🎯 931, seconds in
⏱ 480, Run; the video lands in ✅ 482. Nodes stand in columns by their distance from the inputs.
Until 5.7.2 the canvas was V5.0.1’s hand-made Fast/Best graph with 🎛 MODE and 🎛 OPTIONS radios, toggled frames and a
designed layout (V5.3.0), while the app had moved to the swap lanes in 5.7.0. It retired in 5.8.0 with its builder
passes, _build/layout.py, _build/export_app.py, _build/ab_best.py and its A/B arms, and the suite’s tests that
pinned it; git history has all of them, and the changelog below records what each version did to it.
5. Decisions and the evidence behind them
- Quality sampling —
res_multistep+beta+ 25 steps: Comfy-Org’s R2V template notesbeta/normalbeatsimplefor reference-heavy prompts; docs say 20 default, 25 for better motion. Master 40 is a reasonable ceiling (diminishing returns beyond). - Turbo LoRA — the v1 file (
minimax_h3_turbo_v4_step600_ema_pruned, larryvrh/drbaph) is an FL2VA LoRA; the ref2va model needs lightx2v’sref2v_turbo_4step_v0.1(shipped in Comfy-Org’s repo, used by the template’s “Lightning LoRA” checkbox). Recipe: 4 steps,simple, no CFG. Never stack a cache on 4 steps. - Ref sizing —
max(2048 px short edge) is Comfy-Org’s identity-fidelity setting and “several times slower”;matchfor Iterate. Photos pre-scaled to ≤1.5 MP somaxstays affordable (note: the core scaler also upscales small photos — see open items). - Pixel-lock inpaint (default ON) — PR #15375: mask 1 = regenerate, 0 = keep; kept rows are pinned clean like keyframe anchors, so background and soundtrack are bit-identical. The PR author’s reference workflow does exactly a person swap from reference pictures; his note: you don’t even need the full ref2va template because the mask focuses attention (we keep the template; it costs nothing).
- Sol-Attn deleted — approximate sparse attention (conflicts with the “near-lossless in Quality” decision); kijai’s node only tested on 4090/5090; Saganaki22’s fork has sm_100 kernels but untested on hardware. If wanted later: install
Saganaki22/ComfyUI-sol-attn, place its H3 node after the Sage node, run oncestrict=true. - The paste-back mask is the repaint area (V5.2.1) — the B200 test showed a soft halo on the shoulder: the paste
used the SAM mask grown 32 px (blur 12) while 963 lets the sampler repaint a 40 px grow snapped to 32 px token blocks,
so whatever the new person drew in that band was blended back into the wall. The paste now uses 963’s own mask:
outside it the sampler held the latent to the source’s, so the source pixels go back exactly, and the new person cannot
reach past it, so it already holds the old mask and the new person. Cost: a few tensor ops, no model. Rejected: SAM3
on the decoded frames (seconds per run, another “which person” in a changed frame), a difference mask (VAE drift makes
it noisy and keeps drifted background), a wider grow alone (a guess). Raising 963’s
grow_spatialwidens both. - Whisper on the CPU (V5.2.1) — CTranslate2 (faster-whisper’s engine; 4.8.3, 2026-10-13) builds against CUDA 12.8
only; CUDA 13 support is an open request (OpenNMT/CTranslate2#1933). On the CUDA 13 venv its CUDA path stops with
libcublas.so.12 is not found(Paul’s Best run). faster-whisper’s README route (cu12 cuBLAS + cuDNN wheels andLD_LIBRARY_PATHbefore Python starts) would be a second ~1 GB CUDA stack beside torch’s and a launch change. CPU int8 costs ~20 s of a 20-37 min Best run. ThinkingLLM’s node exposes nocpu_threads, so faster-whisper uses 4 threads unlessOMP_NUM_THREADSis set (HANDOFF requests). - No model kept twice (V5.2.1) — ThinkingLLM holds
self.modelper node instance and frees it only inrun()’sfinallywhenkeep_model_loadedis off (thinkingllm_core/hf_runtime.py); its saved-prompt and prompt-cache returns skip even that. 916, 921 and 976 each kept their own 62 GiB 32B, so Best stopped at 976 (“allocated 124.44 GiB, requested 62.07 GiB, limit 178.35 GiB”, Paul’s B200). All three now load and free; Best re-reads the 32B three times (from the page cache after the first run). In 🅵, 1017 keeps its 4B (one load saved per run, ~9 GB held) and 1018 frees its own, so two 4B copies are never held. The 🛡 checker is a third 4B: its own loader, ONE copy for the whole process (all four checks share it, every run uses it), loaded after ComfyUI frees its own models. Held at most: the 🛡 4B and 1017’s 4B, about 18 GB. - faster-whisper from main (V5.2.1) — PyAV 19 (2026-09-29) removed
av.open(metadata_errors=); faster-whisper 1.2.1, its newest release, still passes it, and main guards it but still reports 1.2.1, so pip sees nothing to do. The base installs with uv, which compares the git commit:PIP_EXTRAnames the repository bare (the base drops any@ref). - Sage on B200 — no PyPI wheel has sm_100 kernels (1.0.6 crashes there); built from source (
TORCH_CUDA_ARCH_LIST=10.0 pip install --no-build-isolation git+https://github.com/thu-ml/SageAttention). Comfy-Org: ~2×, minimal loss; controlled by the graph node, not a launch flag (no double patching). - Not used — EasyCache (~25 % on >100 frames, approximate), Spectrum (~15 % wall-clock, motion effect, 6 GiB CPU history), Comfy Kitchen attention (0 % on Blackwell), NVFP4 community diffusion quants (unverified quality), LCM+beta (reports of destroyed audio), the old inverted-silhouette reference hint (no basis in the H3 guide; the noise mask does that job), AI-generated-audio output (original audio is always kept).
- Frame grid — H3 needs 17k+5 frames at 24 fps; VAE-encoded source,
<Video 1>,lengthand audio must agree, hence VHSforce_rate=24+ cap +TrimAudioDuration, and duration snapping down. - Vision LLM — 4B → 32B is the single biggest prompt-quality lever; ThinkingLLM’s own preset table says thinking OFF and 2048/3072 tokens for H3 formats; v1 had thinking ON at 1024 tokens (budget burned on reasoning, truncated scripts) and rep-penalty 1.2 (suppresses the repeated
<Subject 1>labels you need).
6. Open items / first-run checks (no GPU here — verify on the pod, in this order)
- The app first (V5.1.0): open
/apps/video-replacer/, run 🅵 Fast on a 15 s clip through the page (§1b); time it cold and warm, check the before/after and that the background outside the person is unchanged. Then the halo (V5.2.1): a clip where the new person is broader than the old one (a jacket over a T-shirt): the shoulders and hood stay solid against the background in every frame, and the wall outside the person matches the source. Then 🅠 Best once: 🎙 973 finishes on the CPU (device=cpu; compute_type=int8in 👀5b’s notes). Then the 🛡 checks (V5.2.0): the server log prints[Content Gate] loaded Qwen3-VL-4B-Instruct on cuda; an ordinary clip passes anduser/content-gate/checks.jsonlgains four “passed” lines; “wear lingerie” in the details with a child’s photo stops at 🛡 2 before “Finding the person”; time the “Checking your words, photo and video” stage warm (estimate ≤ 4 s for all four checks); then HANDOFF’s measurement set. - 🅸 + 🎭 on a short clip: 👀2 mask covers the person; 👀7 has
<d>lines with(S1); the sampler starts — this validates the VHS widget layout (formatis set to “None”; if your VHS build lists other options the widget shows red — pick any), the inpaint latent shapes (encoded audio latent length must equal the empty latent’s), and the switch-fed COMBO. - 🅠 is your default final. If a Master run disappoints, re-roll the seed in 🅠 first, then 🅼 once.
sage→ OK → ⚡ on, re-run 🅠, compare time/quality.- 🅼 once
minimax_h3_ref2va_bf16is present: check 👀1 shows the person large (crop working) and the crop seam in 👀8 (raisefeather24–32). Two-fighter clips: the opponent inside the crop is re-rendered unless 🎭 is on (keep it on). - ThinkingLLM must list
[local] Qwen3-VL-32B-InstructandQwen3-VL-4B-Instruct (locked commercial model) [installed]; if either downloads again on first use, the folder name or byte total is off. - Photos smaller than 1.5 MP are upscaled by the pre-scaler (core has no downscale-only node) — harmless for phone photos; swap in KJ resize
total_pixelsif you feed small crops. - Disk ≈ 186 GB downloaded (both Qwen snapshots included) + ~3 GB Whisper cache; MiniMax H3 Video Creator’s and Continuer’s files on the same pod share the two VAEs.
7. Changelog
-
V5.8.2 (2026-10-16): ComfyUI App Mode: Run now works; the video loader’s settings loaded in the wrong order. A VideoHelperSuite loader saved without its
videopreviewentry reaches its own loader code as a list, which it reads in an older order: “Start of the part we use” showed 192 and the format reset, so Run failed._build/apiflow.pynow writes the entry; the canvas, the App Mode forms and the run workflows are rebuilt; a tier-0 check guards every shipped file. Proven in a real frontend on the CPU testbed (all three forms run, the video node loads its frames). -
V5.8.1 (2026-10-16): an open ComfyUI tab can no longer freeze the App. An open ComfyUI tab that stopped reading (a dialog, a paused debugger, a slow link) could freeze the App’s live view and steps for everyone: ComfyUI’s server sends one message at a time and waits for each socket, and 5.8.0’s copies of an App run to ComfyUI tabs went through that same queue. Tab copies now run on their own bounded queues (App Front 1.10.1,
core.TabMirror): a full queue drops the copy, a tab that does not read within 2 s is paused, previews to a tab go at most once a second, and nothing can block the App. Nothing else changed. -
V5.8.0 (2026-10-16): the photos, fixed (bug-hunt’s review). The Full-body slot takes any frame shape (a landscape full-body photo is fine; the face slot keeps its rule). Photos with an ICC profile (a CMYK print export) are converted to sRGB before the sheet is stitched. Two people in the photo: only the largest is cut onto grey (ComfyUI-Track-Guard 1.5.0’s TrackGuardLargestMask after BiRefNet, on every lane; small gaps are closed first, so an arm or a hand stays). One 384 px floor for one photo and for a sheet (one photo was unchecked, a sheet asked 512). Two views on one plain background no longer merge as one shot (
DUP_BITS8). The live view keeps showing the clip until the first visible preview (the first previews decode the empty latent, black). With more photos, add a face close-up: the tiles share the 0.47 MP canvasmatchbrings the sheet to, so the full-body tile’s face is smaller than in a one-photo run, and the layout gives the face close-up the most pixels. A photo slot is live only when the lane the run routes to binds that photo (bindings.json): in Action today (viggle-guard, the full body only) the Back, Side and Face slots show “Not in Action”; a lane that binds the Face photo itself is chosen only when the Face slot has one. Each lane says how many frames it renders in one prompt (bindingswindows.max_frames): the relay lanes (akatz,akatz-4,akatz-guard) 685 (~28.5 s, the relay windowing it with its own history; one constant, raised to 1433 once a pod measures a 60 s take’s memory), the viggle lanes 345,akatz-guard-maxits one 124-frame window; a longer part is a job of several runs. On a photo sheet one figure is kept per tile (Track-Guard 1.5.1’s regions, written by App Front). Every lane’s front now guards its own media for App Mode: the clip’s long side at most 1920 and silence when it has no sound (two App Front nodes; both untouched on App Front’s prepared clip). A long part’s prepared copy gets 5 s of ffmpeg per second of clip (at least 75 s). Hidden arms for the release pod:akatz-guard-fpandviggle-guard-fpadd the face pass (Track-Guard 1.6.0’s face nodes, the Ref2V v0.1 turbo),akatz-guard-8is akatz-guard on the author’s 8-step recipe. While it renders, the app names each step from the lane’s own nodes (app/stages.json, made by_build/stages.py), and a window reads “Window 2 of 3”. Two or more people always go to a guard lane, long or short (a long clip on a lane without the guard clones the bystander). Defaults from the release pod’s A/B: one person goes toakatz(the author’s 8-step recipe) at every length (akatz-4pasted the sheet’s standing figure in, then reverted to the original person, so it is now a hidden arm); two or more go toakatz-guard-maxfor a clip of up to 124 frames (a touch crisper at the same speed) and toakatz-guardabove that (one boundary, the manifest’srouting.short_frames); Action goes toviggle-guard-06(0.6 MP working size: the sharpest, most natural faces, the face-down frame too), withviggle-guardbehind it when -06 cannot run.viggle-guard-face(uncanny eyes, hair changing between frames) and the-fparms (a ghosted face) stay hidden. App Front warmsakatzat boot. Clips over 15 s: coming in 5.8.1. The long-clip job failed its pod pass (the guard’s track switched to the bystander for most of part 1) and a one-person long clip was never rendered, so the manifest’slong.max_clip_sis 15: a longer clip gets the 15 s part picker, as in 5.7.2. The job runner ships, dormant. The canvas is the default customer lane (§2): akatz converted from its API prompt, with a start card naming the App Mode workflows; the V5.0.1 Fast/Best graph, its tools and its tests retired. With it went what only that canvas loaded: the bf16 ref2va engine, the v0.1 Ref2V turbo LoRA, the Qwen3-VL-32B snapshot (about 135 GB per install), the ThinkingLLM and MaskVidExperiments packs and faster-whisper;accelerateis now named inPIP_EXTRA(Content Gate needs it; it used to come with ThinkingLLM). ComfyUI’s own App Mode, one workflow per customer lane (§1h):_build/apiflow.pyturns a lane’s API prompt into a workflow the page loads (shapes from real saves or the pinned source, proven by a round trip);_build/appmode.pyadds the custom app’s form per lane; the install copiesapp/appmode/*.jsontodefault/workflows/. New copy keysduration_label,who_help,pick_x_label,pick_y_label. -
V5.7.2 (2026-10-16): Action is back, on
viggle-guard; up to four photos of the new person. Action routes toviggle-guard(Viggle-Animate plus guard v3: the tapped person swapped, everyone else the original; it passed the grapple on a B200);vigglestays bound but hidden, andviggle-lead(an A/B arm) is not shipped. The akatz lanes take one to four photos (full body, back, side, face) as one character sheet in that slot order (App Front 1.10.0,photo_sheet); the viggle lanes take the full-body photo alone (their repaint copied the sheet’s layout on the pod). One photo queues exactly the 5.7.1 prompt on every lane. ComfyUI-Track-Guard 1.3.1 (the 1.2.2 per-stage timing and the exact, separable grow; TrackGuardPeople, each other person whole). Base floor 3.19.1 (one-click Connect links;BASE_LINK_LOCK=1is the opt-in lock). Known limits: a clip over 124 frames with two or more people renders onakatz, without the bystander guard. -
V5.7.1 (2026-10-16): the action swap, working through the App (5.7.0 never shipped). Lanes:
akatz-4(one person),akatz-guard(two or more: akatz-4 plus guard v3, ComfyUI-Track-Guard re-finding the tapped person and failing open),akatz(backup, clips over 124 frames),viggle(Action, its tap mask the whole person); the pod test lanesviggle-guardandviggle-handsare bound but hidden. Fast and Best left the app (Fast needs Sage, which the install skips; Best’s 32B is a choice no flow needs); their workflow and rows stay for the canvas, fetched after the server is up. App Front warmsakatz-4at boot. The app: Action is set only by the customer’s switch; two or more people need a tap before Make. Every swap lane runs PyTorch attention. ComfyUI floor 0.41.0 (core SAM 3). Known limits: a clip over 124 frames with two or more people renders onakatz, without the bystander guard; invigglea bystander’s clothes can drift with the seed (reroll); “Keep their outfit” is not offered. -
V5.7.0 (2026-10-16, not shipped): the action swap (R1), in the app. Release lanes (gate 2, after the B200 validation run):
akatz-4(one person),akatz-guard(two or more),akatz(backup) andviggle(Action), plus Fast and Best;akatz-max,viggle-r64,viggle-klein,bfs,bfs-dmadandbfs-headleft with their ten rows (bfs-headrecoloured a close-up’s clothes, so “Keep their outfit” is hidden in 5.7.0), and a plain install fetches the release lanes’ files first (PKG_LANESdefaults to them;PKG_LANES=takes every row first). Every swap lane uses PyTorch attention (cuDNN first; 1.65x per step on a B200, exact). Known limits of 5.7.0: in a tight clinch the 2+ person guard can lose the target in the last frames (5.7.1); a clip over 124 frames with two or more people renders onakatz, without the bystander guard; invigglea bystander’s clothes can drift with the seed (reroll); “Keep their outfit” is not in 5.7.0. What follows is the R1 build. Seven new app lanes, each the faithful port of its author’s own graph, sampling an empty H3 latent at denoise 1.0 with a trained swap model:akatz(Akatz Character Swap V1.5 + lightx2v’s FL2V 8-step turbo, the author’s h3-relay windowing inside the prompt; the default),viggle(Viggle-Animate’s finetune and DMD LoRA, one frame repainted with Qwen-Image-Edit 2511; “Action”),bfs(Alissonerdx’s Character Swap, guide latent; the backup),bfs-head(BFS Head Swap: “Keep their outfit”), and the A/B armsakatz-max,akatz-4,bfs-dmad. Every lane renders the whole clip in ONE prompt, up to 345 frames (14.4 s); no job runner and no window history in R1. A shared front (_build/lanes/shared.py) loads the clip at 24 fps, pads it UP to the next 17n+5 count by repeating its last frame and trims the result back (no source frame is dropped), cuts the photo onto #808080 with core ComfyUI’s BiRefNet (MIT), and has Qwen3-VL-4B describe the NEW person from the photo only. App Front 1.9.0 prepares every run’s clip (a silent track when it has none, the long side 1920 or less), keeps one shot per run (hard cuts detected at upload, the longest shot by default), empties the relay’s frame cache after each run, swaps int8 engines for fp8/bf16 files on a B300, and serves the who-picker (a tapped frame with a ring -> the phrase inwho). No pixel lock, no paste-back, no Content Gate checks in the new lanes (Paul: output first). Tier 0 (no GPU):_build/lanes.py --check,_build/tier0.py(every prompt against the exact sources the pod installs),_build/faithful.py(each lane against its author’s graph),_build/lora_keys.py(every LoRA patches its engine),_build/cpu_validate.py(zero node_errors on a CPU ComfyUI, on the free runner). -
V5.6.0 (2026-10-16): Best faster, the same pictures (§1g). Paul approved five changes for the Best lane; three ship: (A) the new vendored pack ComfyUI-Render-Speed 1.0.0 (Generate-AI, MIT) puts cuDNN ahead of FlashAttention-2 in ComfyUI’s SDPA order while H3 runs (node 1047 before 📺, always live), and the Best lane no longer turns ⚡ on, so its attention is exact; (B) 916, 921 and 976 keep the 32B and the pack’s shared loading makes that one copy (released when ComfyUI frees memory for its own models or unloads them), so (C) a kept 32B stays loaded between Best runs; the boot warm-up itself is App Front’s (V5.5.0). (D) the 0.5 MP reference video and (E) fewer steps are
BEST_REF_MPandBEST_STEPSin the builder, off until the pod A/B;build_workflow.py --best-ref-mp/--best-steps --outbuilds an arm and_build/ab_best.pywrites every arm’s Best prompt for the pod. -
V5.5.3 (2026-10-16): every model row pinned to a Hub commit. The platform’s model-pin pass: the script’s
MODELSrows now come from a newmodels.py(python3 models.pyprints them; a test fails on drift), and every Hugging Face row names its commit (resolve/<commit>/...,hf://owner/repo@<commit>) asbase.sh gen-models --pinprinted them on 2026-10-16, matching the notes’ sha256 record. A repository that changes onmaincan no longer break an install.BASE_MINis 3.12.0, the first base that reads the pins. -
V5.5.2 (2026-10-16): a visitor can delete their uploads too (Paul: delete uploaded images and videos as well as output videos). The page uploads through App Front 1.7.0 (
api/upload, owner-keyed); each upload card has Remove, which deletes that upload when no video uses it, and “Delete all” also sweeps every unused upload of that browser. Nobody can delete another owner’s file, and a file a video still uses stays (delete the video). No graph change. -
V5.5.1 (2026-10-16): every frame is swapped. Paul’s pod tests showed the ORIGINAL person: 15 s clip, frames 41-184 (1.7-7.7 s); a fight clip, every frame (a man’s photo for a woman performer). Measured frame by frame: the background was exact everywhere (the paste-back worked), SAM3 had masked her in every frame, and the person area was a near-copy of the source lagging it by up to 3 frames: H3 itself redrew her from
<Video 1>, the full clip it was shown next to the person-only inpaint latent. A pod A/B on both clips: greying out her exact silhouette swapped every frame but kept her outline (a bob, a ponytail, a gi); dropping the inpaint latent swapped too but drifted from the motion and ended on the reference photo. 🎭 now greys out a looser shape (grown 48 px, blurred 24; see Pixel-lock). On the pod it swaps every frame of both clips with the motion and background exact, but her outline and clothes still carry over (bob-length hair on the 15 s clip; ponytail and black gi on the fight clip). Neither the grey<Video 1>nor a greyed inpaint latent (v4) removed them; the action describers (920, 🅵 1016) now may not write the performer’s hair, clothes or any sex word, since the 4B’s “a woman with a ponytail in a black gi” reached H3’s prompt. That rule is not yet tried on the pod. Also: 5.4.0’s app prompts had been exported on a testbed still at MaskVidExperiments d98cc89 and lackedgrow_temp_mode, which c32ed8c (what every install takes) requires, so every 5.4.0 app run failed validation on the pod. 963 carries it in the builder (5.4.2’s export and 5.4.3’s pin already had c32ed8c), the ThinkingLLM row moves to its HEAD too (4729c79),export_app.pyrefuses a testbed whose packs are off their rows and writes_build/pack_inputs.json, and three new checks: the contract tier holds every lane prompt to the declared inputs, the server tier to the machine’s own/object_info, and the runpod tier (BASE_SWAP_CLIP,BASE_SWAP_RESULT) fails a result whose frames still show the original person or whose background moved. -
V5.5.0 (2026-10-16): faster. The render-speed research’s low-risk set (§1f): 🅵‘s describers on the checks’ own 4B (Content Gate 1.5.0’s
describe, threads and a side CUDA stream, no ThinkingLLM in 🅵), SAM3 without refine passes, the mask cleanup at a quarter size, 🅵’s SAM3 on the working frames, 🅵’s working size capped at the clip’s own,areafor the reference video, and App Front’s warm-up run at boot; an interface update is a copy with no restart (bash <script> app), and every run’s timings say where they went. Each is pinned in the workflow and both lanes, three with a planted fault. Per-stage pod measurements are owed (HANDOFF). -
V5.4.3 (2026-10-16): the safety checks in seconds, beside the render. Content Gate 1.4.0: the checker loads straight onto the GPU and, on a CUDA server, loads and warms as ComfyUI starts; every check runs on a worker thread and its own CUDA stream, so the clip’s decode and the loaders run beside it while what it guards still waits for it; the pictures are fitted and tiled on the GPU and prepared there by the model’s processor; every check prints and logs its own seconds. An unclear fact is asked once more (constrained yes/no) before a run stops as unsure;
policy.EVERYDAY_WORDSandself_check.pyare the list that must pass. Graph: 998 and 956 read the photo and frames from their loaders, so checks 1 to 3 run alongside the render and only check 4 holds the finished video back. The app’s stop screen is one line and a button, with the inputs kept. Same model, questions, frames and sizes (§1c “Speed”). -
V5.4.2 (2026-10-16): two privacy fixes. (1) ThinkingLLM’s prompt cache keyed a picture by its size and first ten values, and every describer had a fixed seed, so a customer whose clip opened like an earlier customer’s got that customer’s description with no model call. Content Gate 1.3.0’s checks 2 and 3 now fold every pixel of the photos and the clip into a SHA-256, and check 3 gives it out as
fingerprint, the seed of every describer in every mode and both app lanes (§1c). (2)--disable-metadataon the launch line (HYGIENE_ARGS): the saved MP4 and the previews no longer carry the prompt with the customer’s words. The suite pins both on every instance (the workflow, both lanes, the pack’s hashing rules, the script), with a planted fault (a describer back on a fixed seed). Upstream: ThinkingLLM is asked to hash the whole picture (HANDOFF). -
V5.4.0 (2026-10-16): the app in the SoulCraft brand. Paul: the app “looks archaic”. It now wears the brand foundation’s “Cosmic / Soul” look (§1b): navy field, violet nebula, a sparse star field (CSS only), the gold mark and wordmark, glass-edged cards with big drop targets (the whole empty zone opens the picker; the camera button stays its own), one gold button with a soft glow, a calm luminous stage list under the two sources, a reveal for the result, a gold before/after divider with a round handle. Outfit and Lora Italic ship in
app/fonts/(OFL, listed inZIP_EXTRA, and the install copies them beside the page). The progress screen became a live view (§1b): every node, step and preview the server sends, the mask and the describers’ words, with short stage names, times, “reused” and a closed Activity log; help lines moved behind (i) buttons; “memory_reset” reads “Freed up the studio. Try again.”. Graph:add_app_previewsadds 📺ModelPreviewOverrideKJ(TAEH3, new MODELS rowvae_approx/taeh3) on the guider’s model and three 📱 outputs (one mask frame; the 🅵 describers’ words); the exporter gives each lane short stages and apreviewslist; App Front 1.5.0 runs a lane’s previews with its output. Mock (?mock=1) fakes all of it, including binary previews and&scenario=memory. -
V5.3.0 (2026-10-16): the canvas. A design pass for people who read the workflow (§2b). Six numbered stages left to right in the app’s order, the toggled frames in their stage’s column, 🏁 at the end with the 🛡 report under it; colour means a role; a start card (what it does, where the app is, the modes, the options, the safety checks, the legend, keys 1 to 4), one or two lines per stage, four bookmarks, plain titles for class-named nodes. Fixes V5.1.0’s 🎛 MODE listing 🧠 as a fifth row. 10,730 px tall before, under the 5,000 budget now; the suite collects the base’s canvas tests (two new tiers, snapshot and config). No wiring or widget changed: the exported prompts differ only in node titles (
_meta.title), which the app does not read. -
V5.2.7 (2026-10-16): a visitor can delete their own videos. App Front 1.4.0 adds
DELETE api/history/<id>andDELETE api/history, answered only for the visitor’s owner key: the history entry, the uploads and the outputs go, except a file another run or a queued job still names. No graph change. The page’s buttons come with the app redesign. -
V5.2.6 (2026-10-16): the app is a named link of its own.
APP_PORTS=("8189|video-replacer|Video Replacer App")in the script: on ComfyUI Base 3.9.0 the machine serves the app at the root of port 8189, and RunPod’s Connect tab,podctl statusandpodctl tunnelname it Video Replacer App beside ComfyUI and Jupyter Terminal. The app needed no change: it reaches ComfyUI throughnew URL("../../", HERE), which is/at a port’s root. A base before 3.9.0 ignores the line, soBASE_MINis unchanged. No graph change. -
V5.2.5 (2026-10-16): the two vendored packs are ready for the Comfy Registry. Checked against docs.comfy.org/registry (specifications, standards, publishing) and comfy-cli’s own
comfy node validate.ComfyUI-Content-Gate1.2.0: registry idcontent-gate(the spec: no “ComfyUI” in a name, and a name is permanent once published), Documentation and Bug Tracker URLs, an icon,requires-comfyui >=0.36.0,transformers>=4.57declared (ComfyUI’s own floor is 4.50.3, below Qwen3-VL), a tooltip on every input and output and search aliases, an example workflow ComfyUI’s template browser lists (example_workflows/content-gate-photo.json: words, a photo and the result, core nodes only), and the checker model loaded withlocal_files_only(a missing file never reaches the network).ComfyUI-App-Front: registry idapp-front, the same URLs, icon and floor, an install section, and the two semicolon statements App Front 1.3.0 added split in two (the registry’s validator flags them); its version is left to the App Front releases in flight. Nothing a run does changes. New tests: both packs meet the registry spec, the example wires the pack as declared, the model is never fetched. -
V5.2.4 (2026-10-16): App Front 1.3.0. The vendored
ComfyUI-App-Frontpack gains, with no change to the workflow’s graph: timings (every run the app queues is timed node by node and rolled up into the lane’s stages, one JSON record per run under<user dir>/default/apps/video-replacer/runs/, newest 500 kept, owner-only, no customer text;GET /apps/video-replacer/api/timings?last=N), an estimate endpoint (GET /apps/video-replacer/api/estimate?lane=&clip=: per-stage p50 and p90 from this pod’s own runs, cold and warm apart, same GPU only, scaled to the clip length, with the manifest’seta_sas a flagged guess when there is no history) and out-of-memory self-healing (a run that ends intorch.OutOfMemoryErroron an otherwise idle queue queues a tiny eviction prompt that drops the stale node instances, then posts/free; the app learns the outcome as the reasonmemory_resetorout_of_memoryfromGET .../api/run/<prompt_id>and anapp_front_recoverywebsocket event, so a customer never needs a terminal). It sits beside 5.2.3’s owner key and history routes. The pack’s README is the record. Not yet run on a live pod. -
V5.2.3 (2026-10-16): My videos is private to each browser. The page keeps a random key per browser and sends it in the
X-App-Ownerheader; App Front 1.2.0 stores a SHA-256 tag of it with each run and servesapi/historyandapi/history/<id>with that key’s runs only, so the page no longer reads ComfyUI’s shared/history(where every visitor saw, opened and downloaded every video made on the pod)./viewstays ungated on purpose (§1b). The install box says the address opens ComfyUI itself and that My videos is per browser. No graph change (the workflow’s version stamp only). -
V5.2.2 (2026-10-16): no setting on the canvas can weaken a safety check.
ComfyUI-Content-Gate1.1.0: the checker model node has no model dropdown (the pack always loadsQwen-VL/Qwen3-VL-4B-Instruct, the folder the questions were measured on) and 🛡 3 · your video and 🛡 4 · the result have noframeswidget (always 6 frames; V5.2.0 allowed 1 to 16). Privacy: 🧍 2’s photo loader (496) shipped a testbed upload’s file name as its default; it now ships ComfyUI’s ownexample.png, like the 📸 slots. The exported prompts differ from V5.2.1 only there: 1024 losesmodel, 1027 and 1028 loseframes, 496’simageisexample.png. New tests: the pack’s classes take links only, the frame count and model are fixed (run takes neither), the workflow and both lanes carry no 🛡 setting and no upload name, the server offers no 🛡 setting, plus two planted faults (§1c). -
V5.2.1 (2026-10-16): the B200 test’s owed items. The 🎭 paste-back mask is the area the sampler could repaint (
MVEx Latent Mask To Maskon 963’s mask, node 1030), which removes the shoulder halo; 1021 now only softens the edge (expand 0, blur 16) and note 980 says the paste followsgrow_spatial. 🎙 Whisper runs on the CPU in int8 (CTranslate2 has no CUDA 13 build), faster-whisper comes from its main branch (PyAV 19), and the install proves both by decoding and transcribing a silent clip on the device the Best prompt sets (CPU: onetinyrun; CUDA: the library load only, never a GPU process beside ComfyUI). The consent checkbox is named by its sentence explicitly (for,aria-labelledby). The B200 test’s “on” was the agent browser tool, which names inputs by theirvalue; Chrome’s accessibility tree gives the sentence. The install ends with the app’s address in a box, the page shows its own address, and App Front’s README says how an address is formed. B200, then B300:GPU_PREFER, the header and §1.keep_model_loadedoff on the three 32B describers (each kept node held its own 62 GiB copy: Best ran out of memory at 976); in 🅵 only 1017 keeps its 4B. §1e: what to do after an out-of-memory screen. Suite: the paste-back wiring and a planted fault for it, Whisper on cpu/int8 in the graph and the Best prompt, every app control named, the address printed and shown,GPU_PREFER, the faster-whisper line and its behaviour check, a pod test that decodes and transcribes, and no model kept by two nodes (the canvas, both lane prompts, a planted fault). -
V5.2.0 (2026-10-16): the safety checks. Every run is checked before anything samples and again before anything is saved (§1c, §1d), by the vendored
ComfyUI-Content-Gatepack (Generate-AI, five nodes, the rules pure Python incontent_gate/policy.py) on the 4B snapshot the 🅵 describers already use (now needed by Best too, sobindings.jsonlists it for both lanes). One always-on group, 🛡 Safety checks · always on, with a note: 🛡 1 · your words, 🛡 2 · your photo, 🛡 3 · your video, 🛡 4 · the result, chained in run order, async so each runs the moment it can, the words read from the queued prompt so they are judged first; 👀 8 now reads the frames through check 4; 👀 Safety report shows every check’s answers (pushed to the canvas on a stop too). The questions are the brand rule and say what passes (swimwear, a kiss, a hug, blood, sports, staged fights, children in everyday scenes); the age fact stops only with underwear, lingerie or a suggestive pose. Fail closed. A stop raises from its own node with what was found where (field, photo, frame numbers) and a[content-gate:<gate>:<reason>(:<field>)]tag; the app shows one sentence per check fromcopy.stop_*(the words on the field itself); every check is logged without content. Mock:?mock=1&scenario=stop&stop=<gate>:<reason>[:<field>]. The exporter gives the checks a stage (“Checking your words, photo and video”) and checks their wiring in both lanes.pkg_import_checkalso importstransformers.models.qwen3_vl. Suite: five graph checks (the group is always on and matches neither bypasser, the chain runs in order, nothing reads the photo, frames or result around a check, the checks come before sampling and saving, the model is the 4B row), two more planted faults, unit tests for every pure rule including the “this is fine” scenes, the app and the canvas saying the same thing, the brand rule over both vendored packs. -
V5.1.0 (2026-10-15): the customer app and the 🅵 Fast lane. Paul’s goal: a non-technical customer swaps the person in a 15 s clip in about 3 minutes on their own pod. Research showed no GPU does that with the shipped modes (Quality is 20-37 min on a B200: three 32B LLM passes plus Whisper, and the working frames fed to H3 twice, ~214k tokens a step). So: a 🅵 FAST mode group (0.5 MP, a 0.3 MP reference video, its own ref2v turbo LoRA and Sage node, 4 simple steps,
matchrefs, two short Qwen3-VL-4B calls into a fixed six-section template; recipe borrowed from the shared head-swap and lomote examples); the auto-prompting group became the 🧠 Deep prompt toggle; a prompt switch on H3’s prompt input (the Fast template first, then the reviewer), whose template takes a blank guard on slotaso a bypassed template resolves to nothing instead of an image; a background composite in 🎭 (the “every other pixel kept” claim was not true before: no composite followed the decode); the stockQwen/Qwen3-VL-32B-Instructreplaces the LOCAL Heretic snapshot (its repo is gone and it does not suit an SFW product) andQwen3-VL-4B-Instructis a row; Kijai’s int8 video VAE replaces fp16 (encoder identical, decoder ~2x faster; the same file as the Creator and Continuer, which closes the shared-VAE question V5.0.1 left open);COMFY_MIN0.36.0,BASE_MIN3.6.0. The workflow is now generated by_build/build_workflow.pyfrom_build/source/replacer-v5.0.1.json. The customer app (§1b):app/plus the vendoredComfyUI-App-Frontpack, its prompts and bindings exported from the real frontend by_build/export_app.py. The script reaches the base through$COMFY_BASEalone: the monorepo’s deadbase/testbed.shlookup is gone, and the suite’s by-hand line reads$COMFY_BASE/py. Suite: the Fast lane, the guard, the composite, the snapshots, the app files, the pack’s brand rule, the bindings against both lane prompts, a sixth planted fault;filterwarnings = errorinpytest.ini. -
V5.0.1 (2026-09-25), onto ComfyUI Base 3.0.0, which is always the newest and retired
SAGE_WHEEL. The package’s own SageAttention hook installedSAGE_WHEELwhen set and returned early whenever any build already imported, so a reused venv never followed upstream.pkg_post_venvnow callsbase_build_sageattention, as Krea 2 does (cached by the resolved upstream commit, rebuilt whenever main, Python, torch or the sm moves);ninjaleavesPIP_EXTRA(the base installs it, at its newest, before a build);sagereads the version from the package metadata (SageAttention 2 sets no__version__);--latestis described as the plain run it now is;BASE_MINis 3.0.0. The install tier reads the base-ownedstate/comfyui_args.txt(the image’srunpod-slim/copy is imported once and never edited). No graph change. Still red: the shared-VAE check, because the Video Creator and the Continuer now ship Kijai’s int8 video VAE and this package still ships the fp16 one. Which VAE the Replacer should ship is a model decision, left open. -
V5.0.0 (2026-09-04) — onto ComfyUI Base. The Python wrapper (setup bash + 77 checks + embedded JSON, 511 lines) is a 117-line declaration on the shared base plus
suite.py. Library dests moved from flat toMiniMax-H3/…,SAM3/…,MiniMax-H3/Turbo/…(the base’s sync rewrote the seven loader widgets, the two bypassed engines included). The two VAEs are now the same files H3 Video Creator installs.--dedupeis gone: deletion is the base’s one prompt, scoped so a file another package claims is never offered. The restart is opt-in (BASE_RESTART=1). SageAttention is a hook that FAILS the run when the build fails (V4 built in the background and merely reported). The Qwen snapshot is LOCAL because its repo left the Hub (§1). Version keyextra.package_version. Suite: 95 test functions in 10 tiers, the V4 checks one-to-one plus the base contract and a fake-pod install tier; the mutation self-test gained a fifth fault (an engine left on its pre-base flat path). -
v4 bug pass (final):
TrimAudioDurationwidget renamed to its real namestart_index; redundantImageResizeKJv2/GetImageSizeremoved from Master (Subject Uncrop resizes internally); mask scaler usesarea; setup message no longer prints$0;python3 "…Script.py" some.jsonrunstest;ninjainstalled for the Sage build. 77/77 strict, 24/24 mode-combination bypass simulation clean. Installer: locate-anywhere → move → Hub size check → download → duplicate report (--dedupedeletes); style embeddings and ffmpeg added; tested against a fake pod tree (sibling install with a node pack, a good and a partial model, a stray Qwen folder). -
v1 → v2: thinking OFF + budgets on both describers; duration wired; Ref2V turbo LoRA @4 steps; identity/action prompts and six-section template aligned to the MiniMax ref guide (outfit made explicit).
-
v2 → v3: deleted AI-audio output, test-links note, inverted-silhouette composite, SolAttn, inert inpaint stub; exact 17k+5 slicing; one set of working frames (lanczos, ×32); Qwen3-VL-32B; reviewer pass + Whisper; 🎽/✍️ transfer controls; pixel-lock inpaint wired (default ON); Sage node; 7-column layout; found and fixed the v1 half-bypassed Draft group (quality runs were 8
simplesteps at 0.5 MP). -
v3 → v4: radio MODE 🅸/🅠/🅼 with Master = bf16 + 40 steps + subject-crop pass; OPTIONS box; VHS 24-fps loader; SAM on full-res frames; photo pre-scale + contact strip + 📸 group; ComfyUI pinned; Sage from-source build +
sagecommand; single-file packaging (export/embed).
8. Sources
ComfyUI H3 tutorial · MiniMaxH3ReferenceToVideo · MiniMaxH3SigmaShift · nodes_minimax_h3.py · official R2V template · MiniMax full-reference prompt guide · official H3 skills · PR #15375 noise masks · MaskVidExperiments · ComfyUI-ThinkingLLM · preset settings · KJNodes · VideoHelperSuite · rgthree · drbaph turbo LoRA card · lightx2v Minimax-h3-Turbo · awesome-minimax-H3 · Comfy-Org/MiniMax-H3 benchmark thread · Saganaki22 sol-attn · kijai SolAttn · Spectrum H3 · SageAttention (woct0rdho) · community quants · ComfyUI changelog