"""MiniMax-H3 Turbo: video AND its soundtrack, from one .cmf file. Two things make this Space unusual, and one of them is a warning. The good one: video and synchronized stereo audio are denoised together, in one packed sequence, by one transformer, in four steps — and the whole thing (33 B DiT, a Qwen3-VL-32B prompt encoder, a ViT3D video decoder and a BigVGAN vocoder) is one 23.9 GB file read by one 8 MB Rust binary. The AVI and the WAV are written by that binary too; there is no ffmpeg in this pipeline, and the GIF preview below is assembled here by pulling the JPEG frames straight out of the AVI rather than shelling out to one. The warning: this is a CPU Space. On 48 cores a 512x288 clip takes 346 s and on one RTX PRO 6000 it takes 172 s. On the hardware here even the smallest setting is minutes. So the gallery comes first — those clips were rendered on the real thing — and generating your own is opt-in, with the cost stated before you press the button. """ import os import subprocess import tarfile import tempfile import time import urllib.request import uuid from pathlib import Path import gradio as gr from huggingface_hub import hf_hub_download from PIL import Image RELEASE = ( "https://github.com/infosave2007/cmf/releases/latest/download/" "cortiq-x86_64-unknown-linux-gnu.tar.gz" ) REPO = "infosave/MiniMax-H3-Turbo-cmf" FILE = "mmh3-turbo-q4tp.cmf" # t2va: text in, video and audio out BIN = Path("bin/cortiq") WORK = Path(tempfile.gettempdir()) / "animate" WORK.mkdir(parents=True, exist_ok=True) # No CMF_GPU here on purpose. Mesa ships a SOFTWARE Vulkan driver # (lavapipe/llvmpipe) in most container images, and until 0.5.61 wgpu # would enumerate it, the engine would report "GPU path: on", and every # shader would run through an LLVM rasteriser on the same cores the # native kernels were already using. 0.5.61 declines a DeviceType::Cpu # adapter and keeps the CPU path, so this Space now exercises that # decision on a real container rather than pinning it with an env var. _model: str | None = None def cores() -> str: """How many cores this container may actually use. `os.cpu_count()` reports the HOST's, which on a Space is wildly wrong: a cpu-basic container saw 16 while its quota was 2. The truth is the cgroup v2 quota — " " in microseconds, or "max" when uncapped. """ try: q, p = Path("/sys/fs/cgroup/cpu.max").read_text().split() if q != "max": return f"{int(q) / int(p):g} vCPU" except Exception: # noqa: BLE001 — cgroup v1, macOS, anything else pass return f"{os.cpu_count()} vCPU" def binary() -> str: if not BIN.exists(): BIN.parent.mkdir(parents=True, exist_ok=True) tgz = BIN.parent / "c.tar.gz" urllib.request.urlretrieve(RELEASE, tgz) with tarfile.open(tgz) as t: t.extractall(BIN.parent) tgz.unlink() BIN.chmod(0o755) return str(BIN) # ZeroGPU was tried and does not work for this binary, which is worth # recording because the reason is not the obvious one. A startup probe on # `zero-a10g` found the GPUs present (/dev/nvidia0..7 on a 192-core host) # but the Vulkan ICD directory holding only MESA drivers — # intel_icd, intel_hasvk_icd, radeon_icd and lvp_icd (a software # rasteriser). There is no nvidia_icd.json, and NVIDIA_DRIVER_CAPABILITIES # is unset, so the container runtime never injected the graphics half of # the driver. cortiq reaches a GPU through wgpu -> Vulkan, not CUDA, so a # card it cannot enumerate is a card it cannot use: `@spaces.GPU` would # hand this process a device that the only API it speaks cannot see. # CUDA being present says nothing. Hence CPU. def sample(name: str) -> str: return hf_hub_download(REPO, f"samples/{name}") def avi_to_gif(avi: Path, out: Path, fps: int = 12) -> Path | None: """Pull the JPEG frames out of an MJPEG AVI without a decoder. Every frame in this file is a complete JPEG, so the frames are just the byte ranges between each SOI (FFD8FF) and the EOI (FFD9) that follows it. Scanning for them is a few lines and keeps this page honest: a demo whose whole claim is "no ffmpeg" should not need one to show its own output. """ data = avi.read_bytes() frames, i = [], 0 while True: s = data.find(b"\xff\xd8\xff", i) if s < 0: break e = data.find(b"\xff\xd9", s + 3) if e < 0: break try: frames.append(Image.open(__import__("io").BytesIO(data[s : e + 2])).convert("RGB")) except Exception: # noqa: BLE001 — a stray marker is not fatal pass i = e + 2 if not frames: return None frames[0].save( out, save_all=True, append_images=frames[1:], duration=int(1000 / fps), loop=0, optimize=True) return out def model() -> str: global _model if _model is None: _model = hf_hub_download(REPO, FILE) return _model def animate(prompt, width, height, frames, steps, progress=gr.Progress()): if not (prompt or "").strip(): yield None, None, None, "Write a prompt first." return progress(0.02, desc="binary") exe = binary() progress(0.05, desc="model — 23.9 GB on a cold start, this is the slow part") path = model() tag = uuid.uuid4().hex[:8] out = WORK / f"{tag}.avi" cmd = [ exe, "animate", path, "--prompt", prompt, "--width", str(int(width)), "--height", str(int(height)), "--frames", str(int(frames)), "--steps", str(int(steps)), "--out", str(out), ] log = f"$ cortiq animate {FILE} --prompt {prompt!r} \\\n" log += f" --width {int(width)} --height {int(height)} --frames {int(frames)} --steps {int(steps)}\n\n" yield None, None, None, log t0 = time.time() p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1) for line in p.stdout: log += line yield None, None, None, log p.wait() if p.returncode != 0 or not out.exists(): yield None, None, None, log + f"\nRender failed (exit {p.returncode}).\n" return wav = out.with_suffix(".wav") gif = avi_to_gif(out, WORK / f"{tag}.gif") log += f"\n{time.time() - t0:.0f} s on {cores()}.\n" yield str(gif) if gif else None, str(wav) if wav.exists() else None, str(out), log with gr.Blocks(title="MiniMax-H3 Turbo from one .cmf") as demo: gr.Markdown( """# Video **and its soundtrack**, from one file One prompt, one transformer, four steps — and out comes a clip *and* the stereo audio that goes with it, denoised together in one packed sequence on two different flow schedules. Four files and a ComfyUI checkout — 124.4 GB — become one **23.9 GB `.cmf`**: the 33 B DiT, its Qwen3-VL-32B prompt encoder, the ViT3D video decoder, the BigVGAN vocoder. The 4-step Turbo LoRA is merged in, so the file *is* the turbo model. The AVI and the WAV are written by the same 8 MB binary — no ffmpeg anywhere in the pipeline. [Model](https://huggingface.co/infosave/MiniMax-H3-Turbo-cmf) · [Format](https://huggingface.co/infosave/cmf) · [Source](https://github.com/infosave2007/cmf) """ ) with gr.Tab("Gallery"): gr.Markdown( "Rendered on the real hardware — 512×288, 39 frames, 4 steps, " "**172 s** on one RTX PRO 6000 (346 s on 48 CPU cores)." ) with gr.Row(): with gr.Column(): gr.Markdown("### Text to video, with sound") gr.Image(sample("corgi_512x288_4step.gif"), label="corgi_512x288_4step", height=300) gr.Audio(sample("corgi_512x288_4step.wav"), label="its soundtrack") with gr.Column(): gr.Markdown("### Keyframe to video — one picture in, a clip out") gr.Image(sample("i2v_corgi_flip.gif"), label="i2v_corgi_flip", height=300) gr.Audio(sample("i2v_corgi_flip.wav"), label="its soundtrack") gr.Markdown("### Four bits against two — same prompt, same seed, same steps") with gr.Row(): gr.Image(sample("ab_q4tp.gif"), label="q4tp — 23.9 GB, recommended", height=260) gr.Image(sample("ab_q2tp.gif"), label="q2tp — 18.7 GB, faster, stops following the prompt", height=260) with gr.Tab("Render your own"): gr.Markdown( """**Read this before pressing the button.** This Space has no GPU. The model is 23.9 GB and is fetched on the first render of a cold Space, which alone takes a few minutes; the render itself is several more, even at the smallest size. The settings below are capped accordingly. On your own machine there is no cap: ```sh cargo install cortiq-cli hf download infosave/MiniMax-H3-Turbo-cmf mmh3-turbo-q4tp.cmf --local-dir . CMF_MMH3_GPU=1 cortiq animate mmh3-turbo-q4tp.cmf \\ --prompt "a corgi in a chef hat flipping a pancake" --out clip.avi ``` """ ) prompt = gr.Textbox( label="Prompt", lines=2, value="A corgi in a chef hat flipping a pancake, sizzling sounds and a cheerful bark.") with gr.Row(): width = gr.Radio([256], value=256, label="Width") height = gr.Radio([160], value=160, label="Height") nframes = gr.Radio([13, 22], value=13, label="Frames") steps = gr.Radio([4], value=4, label="Steps (the LoRA is trained for 4)") go = gr.Button("Render — expect minutes", variant="primary") with gr.Row(): gif = gr.Image(label="Clip", height=300) wav = gr.Audio(label="Soundtrack") avi = gr.File(label="The AVI, as the binary wrote it") log = gr.Textbox(label="Log", lines=14, max_lines=14) go.click(animate, [prompt, width, height, nframes, steps], [gif, wav, avi, log]) demo.queue(max_size=4).launch()