|
| 1 | +# Decode H3 previews with TAEH3 on MLX |
| 2 | + |
| 3 | +TAEH3 is an optional tiny video decoder for MiniMax H3. It replaces only |
| 4 | +video reconstruction. The denoiser, sampler, resolution, frame count, and |
| 5 | +audio decoder stay unchanged. The full H3 VAE remains the default. |
| 6 | + |
| 7 | +TAEH3 produces a different reconstruction. Fine fur, fabric, vegetation, |
| 8 | +and surface textures can look softer. Use it for previews or when you accept |
| 9 | +that tradeoff. It is not a lossless acceleration of the full VAE. |
| 10 | + |
| 11 | +## Generate a video |
| 12 | + |
| 13 | +Use your existing MLX FastH3 environment and converted checkpoint: |
| 14 | + |
| 15 | +```bash |
| 16 | +python examples/inference/basic/mlx_fasth3.py \ |
| 17 | + --model-root ~/models/FastH3-Preview-v0.2 \ |
| 18 | + --mlx-checkpoint ~/models/FastH3-MLX/int6 \ |
| 19 | + --prompt 'A red panda beside a mountain lake at sunrise.' \ |
| 20 | + --height 480 --width 832 --num-frames 124 --steps 4 --seed 2027 \ |
| 21 | + --video-decode-backend taeh3 --vae-dtype fp32 \ |
| 22 | + --output-path video_samples/taeh3_preview.mp4 |
| 23 | +``` |
| 24 | + |
| 25 | +The first run downloads a 22.7 MB safetensors checkpoint from an immutable |
| 26 | +upstream revision and verifies its SHA-256 digest. No remote Python code runs. |
| 27 | +The cache is `~/.cache/fastvideo/taehv/taeh3.safetensors`. |
| 28 | +Use `--taeh3-checkpoint /path/to/taeh3.safetensors` for offline use or a custom |
| 29 | +trained checkpoint. Custom files must match the decoder architecture; they |
| 30 | +are not required to match the upstream digest. |
| 31 | + |
| 32 | +| Option | Default | Behavior | |
| 33 | +| --- | --- | --- | |
| 34 | +| `--video-decode-backend` | `h3-vae` | Select `taeh3` for approximate decoding. | |
| 35 | +| `--taeh3-checkpoint` | Unset | Use the pinned upstream checkpoint from cache. | |
| 36 | +| `--taeh3-chunk-size` | `5` | Latent frames per execution chunk. Smaller chunks reduce feature memory. | |
| 37 | +| `--vae-dtype` | `fp32` | Decoder computation dtype. FP16 and BF16 are separate numerical tradeoffs. | |
| 38 | + |
| 39 | +`--tiled-video-decode` controls the full VAE only. TAEH3 uses the whole spatial |
| 40 | +canvas and bounded temporal chunks. Its memory blocks carry state across |
| 41 | +chunks. The pipeline reports `video_decode_backend`, decode timing, and MLX |
| 42 | +peak memory alongside the existing generation metrics. |
| 43 | + |
| 44 | +The mode composes with `--fast-spatial` and temporal `--fast`. Those options |
| 45 | +change the denoising workload and have additional quality costs. A TAEH3-only |
| 46 | +measurement does not establish the quality or speed of a combined mode. |
| 47 | + |
| 48 | +## Latent contract |
| 49 | + |
| 50 | +The native decoder reads normalized diffusion latents in NCTHW layout through |
| 51 | +`decode_latents_taeh3_mlx`. Do not apply the full VAE's mean, standard deviation, |
| 52 | +or pixel denormalization. Its 24 latent channels reconstruct RGB at 16 times |
| 53 | +the latent spatial dimensions. |
| 54 | + |
| 55 | +H3 uses latent lengths `5*k-3`, such as 2, 7, and 37. TAEH3 removes three raw |
| 56 | +frames from each group of 20 decoder outputs. Thus 37 latent frames produce |
| 57 | +124 RGB frames. The port validates that contract before returning output. |
| 58 | + |
| 59 | +## Provenance and validation |
| 60 | + |
| 61 | +Architecture and weights come from Ollin Boer Bohan's MIT-licensed |
| 62 | +[TAEHV H3 implementation](https://github.com/madebyollin/taehv/commit/62f7591f59dfbb4c3c02b7a621d180a9eeaba26c). |
| 63 | +The [Aryan fork](https://github.com/aryan5v/taehv/tree/aryan/first-class-taeh3) |
| 64 | +adds an explicit `TAEH3` API and checkpoint-loading tests. The fork is not an |
| 65 | +official MiniMax release and does not contain newly trained H3 weights. |
| 66 | + |
| 67 | +Run the numerical tests against a local TAEHV checkout containing the released |
| 68 | +weights: |
| 69 | + |
| 70 | +```bash |
| 71 | +TAEH3_REFERENCE_DIR=/path/to/taehv \ |
| 72 | + python -m pytest fastvideo/tests/mlx/test_mlx_taeh3.py -q |
| 73 | +``` |
| 74 | + |
| 75 | +Tests compare MLX FP32 with upstream sequential FP32 and parallel FP64 at |
| 76 | +`atol=1e-5, rtol=1e-5`. The initial parallel CPU FP32 comparison failed that |
| 77 | +strict gate, reaching about `4e-5` maximum error on a 37-latent small fixture. |
| 78 | +CPU convolution rounding changes with its batch size. The FP64 reference |
| 79 | +and sequential FP32 checks distinguish this from a temporal chunking error. |
| 80 | +The original failed comparison is not reported as a pass. |
| 81 | + |
| 82 | +Passing these tests means the MLX port agrees with the tiny decoder within |
| 83 | +the specified tolerance. It does not mean TAEH3 matches the full H3 VAE. |
| 84 | + |
| 85 | +## Compare decoders without another denoising run |
| 86 | + |
| 87 | +Save the normalized packed `video_rows` returned by `pipeline.denoise` as |
| 88 | +`np.savez("latents.npz", video=video_rows)`. Then run: |
| 89 | + |
| 90 | +```bash |
| 91 | +python examples/inference/basic/mlx_h3_decode_benchmark.py \ |
| 92 | + --latents latents.npz \ |
| 93 | + --model-root ~/models/FastH3-Preview-v0.2 \ |
| 94 | + --mlx-checkpoint ~/models/FastH3-MLX/int6 \ |
| 95 | + --height 480 --width 832 --num-frames 124 \ |
| 96 | + --output-dir outputs/taeh3_comparison |
| 97 | +``` |
| 98 | + |
| 99 | +Use a fresh output directory. The benchmark writes the first decoded frame |
| 100 | +arrays and a JSON report with the input digest, MLX version, device, per-trial |
| 101 | +latency, MLX peak active memory, lifetime process peak RSS, and swap snapshots. |
| 102 | +Decoder loading is included; first-time checkpoint downloading is excluded. |
| 103 | +Only run one MLX workload at a time. `--repeats` reverses the decoder order on |
| 104 | +alternate trials. Do not treat two memory counters as additive or infer zero |
| 105 | +page-outs from unchanged swap snapshots. |
| 106 | + |
| 107 | +## Measured decoder results |
| 108 | + |
| 109 | +On an Apple M4 Max with 36 GB unified memory, MLX 0.32.2, FP32 decoding, |
| 110 | +and five-latent execution chunks: |
| 111 | + |
| 112 | +| Workload | Full H3 VAE | TAEH3 | |
| 113 | +| --- | --- | --- | |
| 114 | +| Saved 37-frame latents to 124 RGB frames, 832x480 | 107.90 s | 1.44 s | |
| 115 | +| MLX peak active memory for that decode | 11.03 GiB | 3.62 GiB | |
| 116 | + |
| 117 | +These are one matched pair using the same seed-2027 production latent file, |
| 118 | +including decoder loading. Both swap snapshots stayed unchanged. The decoded |
| 119 | +images differ: PSNR against the full VAE was 29.86 dB, and inspected frames |
| 120 | +showed softer fine detail. The approximately 75x ratio applies only to this |
| 121 | +decoder comparison, not the entire generation pipeline. |
| 122 | + |
| 123 | +Eight additional TAEH3-only decodes measured 0.96 s on first use and a 0.98 s median across seven warm trials, ranging from 0.96 to 0.99 s. Decoder construction and weight loading were included in each trial. The full VAE was not repeated eight times. |
| 124 | + |
| 125 | +### Native resolution with temporal fast |
| 126 | + |
| 127 | +A separate uncached run with `--fast --video-decode-backend taeh3`, without |
| 128 | +spatial fast, completed in **205.47 s wall time**. It kept the native 832x480 |
| 129 | +canvas, denoised 73 source frames, and used RIFE to produce 124 output frames |
| 130 | +with full-duration audio. Seed 2027, four steps, dense attention, INT6. |
| 131 | + |
| 132 | +Prompt encoding took 16.66 s, denoising 181.14 s, TAEH3 decoding 0.64 s, RIFE |
| 133 | +5.23 s, audio decoding 0.73 s, and muxing 0.40 s. Peak denoise MLX allocation |
| 134 | +was 17.87 GiB. System swap use rose from 1231.19 to 2753.75 MiB. |
| 135 | +The output has 124 H.264 frames at 832x480 and stereo AAC at 32 kHz. |
| 136 | + |
| 137 | +This is one combined-mode measurement. It preserves spatial resolution but |
| 138 | +still combines frame interpolation with approximate decoding. Frame samples |
| 139 | +retain more fine detail than the spatial fast experiment below. Motion and |
| 140 | +speech need human review; do not infer native dense generation parity. |
| 141 | + |
| 142 | +### Spatial fast experiment, not the preferred quality path |
| 143 | + |
| 144 | +A separate uncached generation with `--fast-spatial --video-decode-backend |
| 145 | +taeh3` produced a 124-frame, 832x480 MP4 with stereo audio in **95.75 s wall |
| 146 | +time**. Seed 2028, four denoising calls, INT6 weights, dense attention, and no |
| 147 | +temporal fast mode were used. The internal canvas was 416x256, then cropped |
| 148 | +and upscaled to the requested output size. |
| 149 | + |
| 150 | +That run spent 16.66 s encoding the prompt, 77.05 s denoising, 0.34 s decoding |
| 151 | +video, 0.30 s upscaling, 0.73 s decoding audio, and 0.40 s muxing. Peak denoise |
| 152 | +MLX allocation was 16.85 GiB. System swap use rose from 1041.94 to 1247.19 MiB; |
| 153 | +this measurement does not attribute that increase to a particular process. |
| 154 | +It is one end-to-end result, not a repeated benchmark or native-resolution |
| 155 | +quality comparison. The reduced canvas visibly loses detail, especially in |
| 156 | +the opening frames. Speech intelligibility and motion quality need human |
| 157 | +review before treating this combination as a final-output preset. |
0 commit comments