FervorCreative AI
Live Latest 08.09.26 · morning 50 tools tracked 65 workflows indexed 111 topics Hot: MiniMax H3, ComfyUI, Foundry

Publishing the exchange rate for every speed shortcut, rather than only the win, is what turns an optimization document into a tool you can actually tune to your own quality floor.

h3.cMiniMax H3Apple SiliconMetalvideo-genlocal-creative-aiopen-weightscreative-workflowsprompt-craft

MiniMax H3 on Apple Silicon: Every Shortcut in h3.c Ships With the Number It Cost

How to tune a 33B video model on a Mac by moving one dial at a time, and the one flag combination the docs tell you not to build.

There is a number in the h3.c documentation that most projects would never print.

The fast path, the one that gets a video out of a 33B model in a few seconds instead of half a minute, scores 0.556 full-video SSIM against a 29-pass reference render. An independent surfer test scores 0.547. In a paper, 0.55 structural similarity is the number you bury or the number a reviewer uses to reject you. Here it sits in the tutorial, in the section teaching you how to make your first fast video, right next to the timing that makes it worth having: 3.5 seconds of denoise versus 26.4 for the reference.

That is not a lab publishing a win. That is somebody handing you an exchange rate and expecting you to decide.

Why the exchange rate matters more than the speedup

antirez/h3.c describes itself as "native MiniMax-H3 inference for Apple Silicon," built as a sequence of working vertical slices. I would put it more bluntly than the README does: this is a reimplementation, not a conversion. It reads the released BF16 checkpoint directly, runs it through hand-written Metal kernels, carries its own sampler schedule search, and publishes its own measured disagreements with the MLX reference. It sits at 2.6k stars.

The reason to care is not that it exists. Plenty of ports exist. The reason to care is that the entire README is structured as a set of independent dials, each with a published cost, and that structure is what makes local video generation usable rather than merely possible.

Here is the problem it solves. Every local generative tool gives you knobs: steps, layers, resolution, some quantization toggle. Almost none of them tell you what each knob costs. So you do what everyone does, which is find a preset on a forum, run it forever, and never learn whether you are paying for quality you cannot see or throwing away quality you needed. You have no way to locate your own floor.

This documentation gives you the floor-finding procedure directly. Change one control at a time. Restore all layers first, then all denoiser evaluations, then raise the step count. When a fast mode changes the subject, anatomy, motion or composition, the 50-pass path is described not as "better" but as "the right oracle." That is a working procedure, not a recommendation.

The four dials, and what each one buys

Denoising passes. --steps N runs exactly N passes. No hidden multiplier, no schedule that does something else behind the flag. Default is 20. The aggressive range is 4 to 7, and the docs say 4 through 7 share the same schedule, with detail and motion improving as you climb. The four-pass measurement is the 0.556 SSIM above.

Whole-denoiser reuse. --reuse 2 at 20 steps evaluates the first and last pass plus every second interval, then extrapolates the skipped video and audio velocities on their independent schedules. Twenty steps becomes eleven fresh model evaluations. At --reuse 3 it becomes eight. At very small step counts the docs tell you to keep --reuse 1, because there is nothing left to skip.

Active blocks. --layers 45 runs 45 of the 50 transformer blocks. This is the dial with the most interesting mechanism, and it connects to something in MiniMax's own architecture notes.

Internal canvas. --render-width and --render-height run the model and VAE at a smaller same-aspect size, then scale the RGB frames up with vImage before encoding. A measured 384-to-512 render cut M5 transformer time by 33% and video VAE time by 18%. The aggressive 320-to-512 point produced a coherent walking fox at 8.02 seconds of transformer time against about 15.82 natively.

Layer thinning reads the model's own confession

MiniMax's H3 model card contains a sentence that most readers skip: the H3-Omni-Transformer is 33B parameters, "with approximately 13B parameters residing in AdaLN-related branches," and because the AdaLN modulation outputs can be precomputed and cached, those parameters "do not need to be loaded for inference-only deployment."

AdaLN is adaptive layer normalization. In a diffusion transformer it is the machinery that lets each block modulate its behavior based on the timestep, which is to say it is where a block decides how hard to push at this point in the denoise. Nearly 40% of this model's weights are in that decision.

So when h3.c thins layers, it does not drop the last five blocks or pick at random. It ranks the checkpoint's actual AdaLN gates, and protects the structurally important first and final blocks from being dropped regardless of rank. The importance signal is the model's own statement about how much each block modulates. Unused weights and schedule tensors are then not retained, so --layers 45 cuts resident memory as well as compute.

That is the difference between a shortcut and an informed shortcut. The gate magnitudes were sitting in the checkpoint the whole time. Somebody read them.

The dial that makes a Mac with modest RAM work

--ssd-streaming is the flag that changes who can run this at all. It keeps two transformer blocks in memory and reads the next off the SSD while the GPU works the current one, with Darwin uncached reads so the filesystem cache does not retain a second copy. Measured throughput reached about 13 to 14.6 GiB/s from the internal drive.

The result: tracked transformer storage dropped from about 36.5 GiB to 2.0 GiB at 512 square, and 2.1 GiB at 864×480. The outputs were byte-identical in both checks, which is the claim that makes this a memory tradeoff and not a quality tradeoff.

The cost is time, and the cost is uneven in a way worth knowing. A warm 50-block forward measured 1.35 seconds against 2.49 at 512 square, which is 84% slower. At 864×480 it was 2.14 against 2.68, only 26% slower. The larger the work per block, the more of the read latency hides behind the compute. If you are streaming from SSD, generating at a slightly larger canvas costs proportionally less than you would guess.

Two honest caveats the docs supply without being asked. The 2.0 GiB figure is the transformer's tracked tensor storage, not total system RAM; prompt encoding and the two VAEs run in separate phases rather than adding their peaks to it. And --ssd-streaming cannot be combined with --use-int8-row-fc2.

The combination you are told not to build

Here is where the arithmetic stops working, and this is the most useful paragraph in the whole document.

Each dial has a published cost. Token reduction, which pairs adjacent horizontal video tokens inside the middle blocks, took a 512-square profile from 39.13 to 28.06 seconds, a 28.3% cut, with final video and audio latent relative L2 at 5.56% and 15.14%. Composed with the validated --layers 45 --reuse 2 settings it took that profile from 16.69 to 12.60 seconds, and independent fox and surfer renders stayed coherent.

So you might reasonably assume you can keep stacking. Add token reduction to --layers 40 and --reuse 3 and you should get something very fast and slightly worse.

You get chromatic ringing, outlines and ghosted limbs. The docs name this combination explicitly and tell you not to build it. The detail that matters most is the qualifier: this happened "despite acceptable latent norms." The numbers looked fine. The video did not.

That is the whole lesson of this project compressed into one warning. Published measurements let you navigate. They do not let you skip looking at the output. Anyone shipping an automated quality gate on latent norms alone should read that line twice.

Put this into practice

You need an Apple Silicon Mac, FFmpeg and FFprobe on your PATH, and a local MiniMax-H3 snapshot. The snapshot is the expensive part; budget the download time before you budget anything else.

  1. Build and inspect before you generate. make -j8, then ./h3 --info -d ./MiniMax-H3. This checks the model layout and prints the Metal device it selected without mapping all the weights. If something is wrong with your snapshot, you find out in seconds rather than after a 40 GB memory load.

  2. Run the balanced preset first. This is the tutorial's validated starting point:

    ./h3 --profile -d ./MiniMax-H3 \
      -p "A red fox walks through fresh snow in a pine forest. Medium tracking shot, natural winter light, realistic fur, soft footsteps and wind." \
      --width 512 --height 512 --frames 22 --steps 20 \
      --layers 45 --reuse 2 --show -o outputs/fox-fast.mp4
    

    --profile reports each Metal-backed phase separately: wall time, CPU-side command encoding, complete commit-to-fence wait, root-command GPU timestamps, peak live tensor storage, cumulative allocation and dispatch counts. --show displays a preview frame after every transition in Kitty, Ghostty, iTerm2, WezTerm or Konsole. Skip --show on a low-memory run; it keeps a preview VAE resident and adds roughly 10 GiB.

  3. Ignore your first timing. The first invocation pays model loading and filesystem cache costs, and this workload is sensitive to thermal throttling. Compare repeated runs, and alternate variants while the machine warms.

  4. Drop to four passes for iteration. --steps 4 --layers 50 --reuse 1. Keep reuse at 1 at small step counts so every requested pass actually runs the model. This is your draft mode: same subject, same setting, same motion as the reference, softer detail.

  5. Use the interactive session for seed rolls. Run ./h3 -d ./MiniMax-H3 --width 512 --height 512 --steps 6 with no -p. It keeps the BF16 prompt conditioning, the prepared transformer and the video decoder in memory, so re-rolling the same prompt with a new seed skips loading and encoding entirely. !seed random, !seconds 2, !save output.mp4, !status, and !ssd-streaming on all work inside the session.

  6. Add memory relief only when you need it. --ssd-streaming when unified memory is the constraint. Drop --show first, since it is the cheapest 10 GiB you will ever recover.

  7. Change exactly one control when quality goes wrong. Restore layers to 50. Then reuse to 1. Then steps to 50. The 50-pass path is expensive and is meant to be used as an oracle, not a production setting.

Two mechanical rules will bite you before any of the above. Width and height must each be a multiple of 32, and their product cannot exceed 768 × 1344. Frame counts snap upward to 5 + 17n, so --frames 23 becomes 39 and --seconds 10 becomes 243 frames at 10.125 seconds. Do not fight this; pick from the table.

Honest limitations

Do not expect pixel parity with anything. The docs say it directly: numerical pixel identity with the MLX reference is not expected, because the random-number and execution engines differ. The depicted content and motion should agree. If you are looking for a reproducible frame hash across implementations, this is not that.

The fast paths change composition, and the docs say which ones. Token reduction "changes composition and is therefore opt-in rather than the close-reference default." The 320-square internal canvas "loses fine detail and can change framing." Core reuse above 6 is not exposed at all because validation lost subject fidelity. These are not hedges; they are boundaries somebody walked into.

Native 256 is a preview, not an output. At 256 square the model has an 8×8 effective spatial token grid, so there is very little room for fine detail or complex composition. The implementation auto-halves spatial RoPE coordinates at exactly that size, which removed repeating lattice artifacts, but it remains a composition check. Native 128 square is unsupported outright; a 4×4 token grid did not recover a recognizable subject even with adjusted coordinates.

The memory floor is still high. The clean end-to-end reference renders quoted in the docs, 74.58 seconds for image-plus-audio and 76.99 for embedded-video-plus-audio, ran on a 128 GB M5 Max at roughly 40.1 GB peak physical footprint with zero swaps. SSD streaming lowers the transformer's share, not the whole system's.

Some of the best paths are M5-only. The native int8 MLP engine, which took a fixed 50-layer render from 36.30 to 25.80 seconds and cut peak tensor storage from 36.4 to 25.9 GiB, plus the int8 QKV path that took it to 19.32, are Metal 4 TensorOps features. Older hardware falls back automatically to the portable BF16 path, which works and is slower.

You are running H3-Base alone. MiniMax's full pipeline has three modules, and the two that are not open-sourced are the ones that handle prompt preparation and 2K regeneration. MiniMax's own card calls the prompt-preparation module "critical to the quality of the final output." Local means 768p and means you write the structured description yourself.

The part that made me trust it

Buried in the implementation notes is a sentence that says the native audio encoder matches "the corrected MLX oracle" at relative L2 of 3.59e-6, and then explains the correction: the original MLX reshape interleaved left and right samples, while the official PyTorch and SGLang path folds intact stereo channels into the batch dimension.

Read that again. The port found a bug in the thing it was validating against, fixed the reference, and then reported its own agreement with the fixed version rather than with the broken one it could have matched more easily.

There is a version of this project that quotes agreement with the original MLX implementation, scores well, and ships. Nobody would have caught it. A stereo channel interleave produces audio that plays, has the right length, and sounds roughly correct on casual listening. That is the same failure shape as the prompt-conditioning flag in the two-GPU build I looked at this week, and the same shape as a Core ML port of Kokoro whose card reports that a build carrying a known modulo bug still scores 0.801 spectral correlation while producing audio of the right length, the right envelope and the right word timing. Local generative AI is full of bugs that render successfully.

So the question I would put to anyone building on top of ports like this: what is your equivalent of the 0.556? Not the number you would publish. The number you would have to publish if you documented your fast path the way this one does. If you do not know it, you are running a preset you inherited, and you have no idea what it cost you.

Go find out. It takes an evening and one control at a time.


Sources: antirez/h3.c · MiniMaxAI/MiniMax-H3 · toddkrabach/Kokoro-82M-CoreML


Medium metadata

Kicker for social: The fast path scores 0.556 SSIM against the reference. Most projects would bury that number. This one puts it in the getting-started tutorial, next to the 7x speedup that makes it worth taking.