Skip to content

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.

Terminal window
cd /workspace/runpod-slim
python3 -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:

Terminal window
cd /workspace/runpod-slim
python3 -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 line
bash "video-replacer-minimax-h3-script.sh" sage # later, or any time: "Sage: OK" → the ⚡ toggle may go ON

Safe 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 own api/history and 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 localStorage as video-replacer:owner) and sends it in the X-App-Owner header with every run and every history read, never in a URL. A run stores only a SHA-256 tag of the key; GET api/history and api/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, /history and 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 /history lists).
  • Delete (V5.2.7). DELETE api/history/<id> deletes one of the visitor’s own finished videos, DELETE api/history all 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.
  • run loads the lane’s API prompt (app/prompts/<lane>.api.json; 5.7.1: the swap lanes), writes the customer’s values into the inputs app/bindings.json names, reconciles ThinkingLLM’s model labels against the live list (... [installed]), and queues it with partial_execution_targets: ["482"] so the 👀 previews do not run.
  • status refuses 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.json are generated: uv run --upgrade _build/export_app.py loads the workflow into the testbed’s real frontend, sets each lane’s radio state, waits for the rgthree switches to type their inputs, calls graphToPrompt(), 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’s PACKS sits on the testbed at another commit than its row (bash testbed.sh --packs moves 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_info and 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 sends kj_preview_override over /ws (16 frames at 12 fps as an animated preview, at most 768 px). The base’s --preview-method auto alone 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: progress on the sampler;
  • the mask and the words: bindings.json lists per lane a few previews (📱 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 to partial_execution_targets, so they come back in executed messages. 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=0 turns 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 writes seconds into checks.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 are policy.EVERYDAY_WORDS (a kiss, a hug, swimwear, sports, stage blood, a fight, children in everyday clothes and at the beach, and ordinary requests) and policy.EVERYDAY_PHOTOS names 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 /free unloads 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 with keep_model_loaded on 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 /free lets 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 notes beta/normal beat simple for 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’s ref2v_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”; match for Iterate. Photos pre-scaled to ≤1.5 MP so max stays 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 once strict=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_spatial widens 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 and LD_LIBRARY_PATH before 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 no cpu_threads, so faster-whisper uses 4 threads unless OMP_NUM_THREADS is set (HANDOFF requests).
  • No model kept twice (V5.2.1) — ThinkingLLM holds self.model per node instance and frees it only in run()’s finally when keep_model_loaded is 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_EXTRA names 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>, length and audio must agree, hence VHS force_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)

  1. 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=int8 in 👀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 and user/content-gate/checks.jsonl gains 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.
  2. 🅸 + 🎭 on a short clip: 👀2 mask covers the person; 👀7 has <d> lines with (S1); the sampler starts — this validates the VHS widget layout (format is 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.
  3. 🅠 is your default final. If a Master run disappoints, re-roll the seed in 🅠 first, then 🅼 once.
  4. sage → OK → ⚡ on, re-run 🅠, compare time/quality.
  5. 🅼 once minimax_h3_ref2va_bf16 is present: check 👀1 shows the person large (crop working) and the crop seam in 👀8 (raise feather 24–32). Two-fighter clips: the opponent inside the crop is re-rendered unless 🎭 is on (keep it on).
  6. ThinkingLLM must list [local] Qwen3-VL-32B-Instruct and Qwen3-VL-4B-Instruct (locked commercial model) [installed]; if either downloads again on first use, the folder name or byte total is off.
  7. 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_pixels if you feed small crops.
  8. 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 videopreview entry 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.py now 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_BITS 8). 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 canvas match brings 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 (bindings windows.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-max its 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-fp and viggle-guard-fp add the face pass (Track-Guard 1.6.0’s face nodes, the Ref2V v0.1 turbo), akatz-guard-8 is 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 to akatz (the author’s 8-step recipe) at every length (akatz-4 pasted the sheet’s standing figure in, then reverted to the original person, so it is now a hidden arm); two or more go to akatz-guard-max for a clip of up to 124 frames (a touch crisper at the same speed) and to akatz-guard above that (one boundary, the manifest’s routing.short_frames); Action goes to viggle-guard-06 (0.6 MP working size: the sharpest, most natural faces, the face-down frame too), with viggle-guard behind it when -06 cannot run. viggle-guard-face (uncanny eyes, hair changing between frames) and the -fp arms (a ghosted face) stay hidden. App Front warms akatz at 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’s long.max_clip_s is 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; accelerate is now named in PIP_EXTRA (Content Gate needs it; it used to come with ThinkingLLM). ComfyUI’s own App Mode, one workflow per customer lane (§1h): _build/apiflow.py turns 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.py adds the custom app’s form per lane; the install copies app/appmode/*.json to default/workflows/. New copy keys duration_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 to viggle-guard (Viggle-Animate plus guard v3: the tapped person swapped, everyone else the original; it passed the grapple on a B200); viggle stays bound but hidden, and viggle-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=1 is the opt-in lock). Known limits: a clip over 124 frames with two or more people renders on akatz, 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 lanes viggle-guard and viggle-hands are 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 warms akatz-4 at 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 on akatz, without the bystander guard; in viggle a 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) and viggle (Action), plus Fast and Best; akatz-max, viggle-r64, viggle-klein, bfs, bfs-dmad and bfs-head left with their ten rows (bfs-head recoloured 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_LANES defaults 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 on akatz, without the bystander guard; in viggle a 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 arms akatz-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 in who). 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_MP and BEST_STEPS in the builder, off until the pod A/B; build_workflow.py --best-ref-mp/--best-steps --out builds an arm and _build/ab_best.py writes 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 MODELS rows now come from a new models.py (python3 models.py prints them; a test fails on drift), and every Hugging Face row names its commit (resolve/<commit>/..., hf://owner/repo@<commit>) as base.sh gen-models --pin printed them on 2026-10-16, matching the notes’ sha256 record. A repository that changes on main can no longer break an install. BASE_MIN is 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 lacked grow_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.py refuses 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, area for 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_WORDS and self_check.py are 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-metadata on 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 in ZIP_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_previews adds 📺 ModelPreviewOverrideKJ (TAEH3, new MODELS row vae_approx/taeh3) on the guider’s model and three 📱 outputs (one mask frame; the 🅵 describers’ words); the exporter gives each lane short stages and a previews list; 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> and DELETE 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 status and podctl tunnel name it Video Replacer App beside ComfyUI and Jupyter Terminal. The app needed no change: it reaches ComfyUI through new URL("../../", HERE), which is / at a port’s root. A base before 3.9.0 ignores the line, so BASE_MIN is 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-Gate 1.2.0: registry id content-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.57 declared (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 with local_files_only (a missing file never reaches the network). ComfyUI-App-Front: registry id app-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-Front pack 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’s eta_s as a flagged guess when there is no history) and out-of-memory self-healing (a run that ends in torch.OutOfMemoryError on 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 reason memory_reset or out_of_memory from GET .../api/run/<prompt_id> and an app_front_recovery websocket 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-Owner header; App Front 1.2.0 stores a SHA-256 tag of it with each run and serves api/history and api/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). /view stays 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-Gate 1.1.0: the checker model node has no model dropdown (the pack always loads Qwen-VL/Qwen3-VL-4B-Instruct, the folder the questions were measured on) and 🛡 3 · your video and 🛡 4 · the result have no frames widget (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 own example.png, like the 📸 slots. The exported prompts differ from V5.2.1 only there: 1024 loses model, 1027 and 1028 lose frames, 496’s image is example.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 Mask on 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 follows grow_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: one tiny run; 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 their value; 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_loaded off 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-Gate pack (Generate-AI, five nodes, the rules pure Python in content_gate/policy.py) on the 4B snapshot the 🅵 describers already use (now needed by Best too, so bindings.json lists 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 from copy.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_check also imports transformers.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, match refs, 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 slot a so 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 stock Qwen/Qwen3-VL-32B-Instruct replaces the LOCAL Heretic snapshot (its repo is gone and it does not suit an SFW product) and Qwen3-VL-4B-Instruct is 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_MIN 0.36.0, BASE_MIN 3.6.0. The workflow is now generated by _build/build_workflow.py from _build/source/replacer-v5.0.1.json. The customer app (§1b): app/ plus the vendored ComfyUI-App-Front pack, its prompts and bindings exported from the real frontend by _build/export_app.py. The script reaches the base through $COMFY_BASE alone: the monorepo’s dead base/testbed.sh lookup 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 = error in pytest.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 installed SAGE_WHEEL when set and returned early whenever any build already imported, so a reused venv never followed upstream. pkg_post_venv now calls base_build_sageattention, as Krea 2 does (cached by the resolved upstream commit, rebuilt whenever main, Python, torch or the sm moves); ninja leaves PIP_EXTRA (the base installs it, at its newest, before a build); sage reads the version from the package metadata (SageAttention 2 sets no __version__); --latest is described as the plain run it now is; BASE_MIN is 3.0.0. The install tier reads the base-owned state/comfyui_args.txt (the image’s runpod-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 to MiniMax-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. --dedupe is 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 key extra.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): TrimAudioDuration widget renamed to its real name start_index; redundant ImageResizeKJv2/GetImageSize removed from Master (Subject Uncrop resizes internally); mask scaler uses area; setup message no longer prints $0; python3 "…Script.py" some.json runs test; ninja installed for the Sage build. 77/77 strict, 24/24 mode-combination bypass simulation clean. Installer: locate-anywhere → move → Hub size check → download → duplicate report (--dedupe deletes); 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 simple steps 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 + sage command; 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