---
title: minimax_h3_taeh3
---

<div class="python-reference">
<a id="fastvideo.mlx_runtime.minimax_h3_taeh3-classes"></a>
<a id="fastvideo.mlx_runtime.minimax_h3_taeh3-functions"></a>
<a id="fastvideo.mlx_runtime.minimax_h3_taeh3.MLXTAEH3Decoder-functions"></a>
<a id="fastvideo.mlx_runtime.minimax_h3_taeh3"></a>
<p>Optional, approximate MiniMax H3 tiny decoder, executed entirely in MLX.</p>
<p>Architecture and temporal mapping adapted from madebyollin/taehv at
62f7591f59dfbb4c3c02b7a621d180a9eeaba26c (MIT, Ollin Boer Bohan).
See fastvideo/third_party/taehv/LICENSE. Weights remain in the upstream format.
This decoder consumes normalized diffusion latents; it does not use the full
H3 VAE's latent mean/std or pixel denormalization.</p>
<h2 id="fastvideo.mlx_runtime.minimax_h3_taeh3.ensure_taeh3_checkpoint"><code>fastvideo.mlx_runtime.minimax_h3_taeh3.ensure_taeh3_checkpoint</code></h2>
<pre class="doc-signature"><code>ensure_taeh3_checkpoint(checkpoint_path: str | Path | None = None) -&gt; Path</code></pre>
<p>Fetch only pinned weights, atomically; never download executable code.</p>
<p>Explicit local safetensors paths may contain custom trained weights.
Managed cache entries must match the upstream SHA-256 digest.</p>
<details><summary>Source</summary><pre><code>def ensure_taeh3_checkpoint(checkpoint_path: str | Path | None = None) -&gt; Path:
    &quot;&quot;&quot;Fetch only pinned weights, atomically; never download executable code.

    Explicit local safetensors paths may contain custom trained weights.
    Managed cache entries must match the upstream SHA-256 digest.
    &quot;&quot;&quot;
    if checkpoint_path is not None:
        path = Path(checkpoint_path).expanduser()
        if not path.is_file():
            raise FileNotFoundError(f&quot;TAEH3 checkpoint not found: &#123;path&#125;&quot;)
        if path.suffix != &quot;.safetensors&quot;:
            raise ValueError(&quot;The MLX TAEH3 decoder requires a .safetensors checkpoint.&quot;)
        return path
    path = Path.home() / &quot;.cache/fastvideo/taehv/taeh3.safetensors&quot;

    def verify(candidate: Path) -&gt; None:
        with candidate.open(&quot;rb&quot;) as handle:
            hasher = hashlib.sha256()
            for chunk in iter(lambda: handle.read(1 &lt;&lt; 20), b&quot;&quot;):
                hasher.update(chunk)
            digest = hasher.hexdigest()
        if digest != TAEH3_SHA256:
            raise RuntimeError(f&quot;TAEH3 checkpoint failed SHA-256 verification: &#123;candidate&#125;&quot;)

    if path.exists():
        verify(path)
        return path
    path.parent.mkdir(parents=True, exist_ok=True)
    with tempfile.NamedTemporaryFile(dir=path.parent, suffix=&quot;.safetensors&quot;, delete=False) as temporary_file:
        temporary = Path(temporary_file.name)
    try:
        with urllib.request.urlopen(TAEH3_URL, timeout=60) as response, temporary.open(&quot;wb&quot;) as handle:
            while chunk := response.read(1 &lt;&lt; 20):
                handle.write(chunk)
        verify(temporary)
        temporary.replace(path)
    finally:
        temporary.unlink(missing_ok=True)
    return path</code></pre></details>
<h2 id="fastvideo.mlx_runtime.minimax_h3_taeh3.MLXTAEH3Decoder"><code>fastvideo.mlx_runtime.minimax_h3_taeh3.MLXTAEH3Decoder</code></h2>
<pre class="doc-signature"><code>MLXTAEH3Decoder(checkpoint_path: str | Path, *, dtype: str = &#x27;fp32&#x27;)</code></pre>
<p>Decode H3 NTCHW latents with bounded temporal feature memory.</p>
<p>Chunks carry the last input of every MemBlock into the next chunk.
Chunking limits activation memory without resetting the causal state.
The full H3 VAE remains the quality reference, not a numerical oracle for
this independently trained tiny decoder.</p>

<details><summary>Source</summary><pre><code>class MLXTAEH3Decoder:
    &quot;&quot;&quot;Decode H3 NTCHW latents with bounded temporal feature memory.

    Chunks carry the last input of every MemBlock into the next chunk.
    Chunking limits activation memory without resetting the causal state.
    The full H3 VAE remains the quality reference, not a numerical oracle for
    this independently trained tiny decoder.
    &quot;&quot;&quot;

    def __init__(self, checkpoint_path: str | Path, *, dtype: str = &quot;fp32&quot;) -&gt; None:
        import mlx.core as mx

        if dtype not in (&quot;fp32&quot;, &quot;fp16&quot;, &quot;bf16&quot;):
            raise ValueError(f&quot;Unsupported TAEH3 dtype: &#123;dtype&#125;&quot;)
        self.dtype = &#123;&quot;fp32&quot;: mx.float32, &quot;fp16&quot;: mx.float16, &quot;bf16&quot;: mx.bfloat16&#125;&#91;dtype&#93;
        raw = mx.load(str(checkpoint_path))
        expected: dict&#91;str, tuple&#91;int, ...&#93;&#93; = &#123;
            &quot;decoder.1.weight&quot;: (256, 24, 3, 3),
            &quot;decoder.1.bias&quot;: (256, ),
            &quot;decoder.7.conv.weight&quot;: (256, 256, 1, 1),
            &quot;decoder.8.weight&quot;: (128, 256, 3, 3),
            &quot;decoder.13.conv.weight&quot;: (256, 128, 1, 1),
            &quot;decoder.14.weight&quot;: (64, 128, 3, 3),
            &quot;decoder.19.conv.weight&quot;: (128, 64, 1, 1),
            &quot;decoder.20.weight&quot;: (64, 64, 3, 3),
            &quot;decoder.22.weight&quot;: (12, 64, 3, 3),
            &quot;decoder.22.bias&quot;: (12, ),
        &#125;
        for index, channels in ((3, 256), (4, 256), (5, 256), (9, 128), (10, 128), (11, 128), (15, 64), (16, 64), (17,
                                                                                                                   64)):
            for layer in (0, 2, 4):
                prefix = f&quot;decoder.&#123;index&#125;.conv.&#123;layer&#125;&quot;
                expected&#91;f&quot;&#123;prefix&#125;.weight&quot;&#93; = (channels, channels * (2 if layer == 0 else 1), 3, 3)
                expected&#91;f&quot;&#123;prefix&#125;.bias&quot;&#93; = (channels, )
        actual = &#123;key for key in raw if key.startswith(&quot;decoder.&quot;)&#125;
        if actual != set(expected):
            raise ValueError(f&quot;TAEH3 decoder keys mismatch: missing=&#123;set(expected) - actual&#125;, &quot;
                             f&quot;unexpected=&#123;actual - set(expected)&#125;&quot;)
        self.weights = &#123;&#125;
        for key, shape in expected.items():
            value = raw&#91;key&#93;
            if tuple(value.shape) != shape:
                raise ValueError(f&quot;TAEH3 weight &#123;key&#125; has shape &#123;value.shape&#125;, expected &#123;shape&#125;&quot;)
            # Keep convolutions and features channels-last throughout execution.
            value = value.transpose(0, 2, 3, 1) if value.ndim == 4 else value
            self.weights&#91;key&#93; = mx.contiguous(value.astype(self.dtype))
        mx.eval(*self.weights.values())

    def _conv(self, x: Any, name: str) -&gt; Any:
        import mlx.core as mx

        weight = self.weights&#91;f&quot;&#123;name&#125;.weight&quot;&#93;
        y = mx.conv2d(x, weight, padding=weight.shape&#91;1&#93; // 2)
        bias = self.weights.get(f&quot;&#123;name&#125;.bias&quot;)
        return y if bias is None else y + bias

    def _chunk(self, x: Any, memory: dict&#91;int, Any&#93;) -&gt; Any:
        import mlx.core as mx

        n, t, h, w, c = x.shape
        x = mx.maximum(self._conv(mx.tanh(x.reshape(n * t, h, w, c) / 3.0) * 3.0, &quot;decoder.1&quot;), 0)
        for indices, grow, projection, stride in (((3, 4, 5), 7, 8, 1), ((9, 10, 11), 13, 14, 2), ((15, 16, 17), 19, 20,
                                                                                                   2)):
            for index in indices:
                _, h, w, c = x.shape
                sequence = x.reshape(n, -1, h, w, c)
                previous = memory.get(index)
                if previous is None:
                    previous = mx.zeros_like(sequence&#91;:, :1&#93;)
                past = mx.concatenate(&#91;previous, sequence&#91;:, :-1&#93;&#93;, axis=1).reshape(x.shape)
                # Preserve the final feature frame for the next execution chunk.
                memory&#91;index&#93; = mx.contiguous(sequence&#91;:, -1:&#93;)
                y = mx.concatenate(&#91;x, past&#93;, axis=-1)
                for layer in (0, 2, 4):
                    y = self._conv(y, f&quot;decoder.&#123;index&#125;.conv.&#123;layer&#125;&quot;)
                    if layer != 4:
                        y = mx.maximum(y, 0)
                x = mx.maximum(x + y, 0)
            nt, h, w, c = x.shape
            x = mx.broadcast_to(x.reshape(nt, h, 1, w, 1, c), (nt, h, 2, w, 2, c))
            x = x.reshape(nt, h * 2, w * 2, c)
            x = self._conv(x, f&quot;decoder.&#123;grow&#125;.conv&quot;)
            # Torch TGrow splits its channel-major output into temporal frames.
            x = x.reshape(nt, h * 2, w * 2, stride, c).transpose(0, 3, 1, 2, 4)
            x = self._conv(x.reshape(nt * stride, h * 2, w * 2, c), f&quot;decoder.&#123;projection&#125;&quot;)
        x = self._conv(mx.maximum(x, 0), &quot;decoder.22&quot;)
        nt, h, w, _ = x.shape
        # Torch pixel_shuffle stores channels as (RGB, patch_y, patch_x).
        x = x.reshape(nt, h, w, 3, 2, 2).transpose(0, 1, 4, 2, 5, 3)
        return mx.clip(x.reshape(n, -1, h * 2, w * 2, 3), 0, 1)

    def decode_ntchw(self, latents: Any, *, chunk_size: int = 5) -&gt; Any:
        &quot;&quot;&quot;Return NTCHW RGB in &#91;0, 1&#93; for H3&#x27;s valid 5*k-3 latent lengths.&quot;&quot;&quot;
        import mlx.core as mx

        if latents.ndim != 5 or latents.shape&#91;2&#93; != 24 or min(latents.shape) &lt;= 0:
            raise ValueError(f&quot;Expected nonempty NTCHW H3 latents with 24 channels, got &#123;latents.shape&#125;&quot;)
        if latents.shape&#91;1&#93; % 5 != 2:
            raise ValueError(&quot;H3 latent time must be 5*k-3, for example 2, 7, or 37.&quot;)
        if chunk_size &lt; 1:
            raise ValueError(&quot;TAEH3 chunk_size must be positive.&quot;)
        x = latents.astype(self.dtype).transpose(0, 1, 3, 4, 2)
        memory: dict&#91;int, Any&#93; = &#123;&#125;
        frames = &#91;&#93;
        for start in range(0, x.shape&#91;1&#93;, chunk_size):
            decoded = self._chunk(x&#91;:, start:start + chunk_size&#93;, memory)
            # Remove three raw frames from each five-latent (20-frame) group.
            keep = &#91;i for i in range(decoded.shape&#91;1&#93;) if (start * 4 + i) % 20 &gt;= 3&#93;
            decoded = decoded&#91;:, mx.array(keep, dtype=mx.int32)&#93;
            mx.eval(decoded, *memory.values())
            frames.append(decoded)
        return mx.concatenate(frames, axis=1).transpose(0, 1, 4, 2, 3)</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_taeh3.MLXTAEH3Decoder.decode_ntchw"><code>fastvideo.mlx_runtime.minimax_h3_taeh3.MLXTAEH3Decoder.decode_ntchw</code></h3>
<pre class="doc-signature"><code>decode_ntchw(latents: Any, *, chunk_size: int = 5) -&gt; Any</code></pre>
<p>Return NTCHW RGB in [0, 1] for H3's valid 5*k-3 latent lengths.</p>
<details><summary>Source</summary><pre><code>def decode_ntchw(self, latents: Any, *, chunk_size: int = 5) -&gt; Any:
    &quot;&quot;&quot;Return NTCHW RGB in &#91;0, 1&#93; for H3&#x27;s valid 5*k-3 latent lengths.&quot;&quot;&quot;
    import mlx.core as mx

    if latents.ndim != 5 or latents.shape&#91;2&#93; != 24 or min(latents.shape) &lt;= 0:
        raise ValueError(f&quot;Expected nonempty NTCHW H3 latents with 24 channels, got &#123;latents.shape&#125;&quot;)
    if latents.shape&#91;1&#93; % 5 != 2:
        raise ValueError(&quot;H3 latent time must be 5*k-3, for example 2, 7, or 37.&quot;)
    if chunk_size &lt; 1:
        raise ValueError(&quot;TAEH3 chunk_size must be positive.&quot;)
    x = latents.astype(self.dtype).transpose(0, 1, 3, 4, 2)
    memory: dict&#91;int, Any&#93; = &#123;&#125;
    frames = &#91;&#93;
    for start in range(0, x.shape&#91;1&#93;, chunk_size):
        decoded = self._chunk(x&#91;:, start:start + chunk_size&#93;, memory)
        # Remove three raw frames from each five-latent (20-frame) group.
        keep = &#91;i for i in range(decoded.shape&#91;1&#93;) if (start * 4 + i) % 20 &gt;= 3&#93;
        decoded = decoded&#91;:, mx.array(keep, dtype=mx.int32)&#93;
        mx.eval(decoded, *memory.values())
        frames.append(decoded)
    return mx.concatenate(frames, axis=1).transpose(0, 1, 4, 2, 3)</code></pre></details>
<h2 id="fastvideo.mlx_runtime.minimax_h3_taeh3.decode_latents_taeh3_mlx"><code>fastvideo.mlx_runtime.minimax_h3_taeh3.decode_latents_taeh3_mlx</code></h2>
<pre class="doc-signature"><code>decode_latents_taeh3_mlx(latents: np.ndarray, *, checkpoint_path: str | Path | None = None, dtype: str = &#x27;fp32&#x27;, chunk_size: int = 5) -&gt; np.ndarray</code></pre>
<p>Decode normalized NCTHW diffusion latents into NTHWC float RGB.</p>
<details><summary>Source</summary><pre><code>def decode_latents_taeh3_mlx(latents: np.ndarray,
                             *,
                             checkpoint_path: str | Path | None = None,
                             dtype: str = &quot;fp32&quot;,
                             chunk_size: int = 5) -&gt; np.ndarray:
    &quot;&quot;&quot;Decode normalized NCTHW diffusion latents into NTHWC float RGB.&quot;&quot;&quot;
    import mlx.core as mx

    if latents.ndim != 5:
        raise ValueError(f&quot;Expected NCTHW latents, got &#123;latents.shape&#125;&quot;)
    checkpoint = ensure_taeh3_checkpoint(checkpoint_path)
    decoder = MLXTAEH3Decoder(checkpoint, dtype=dtype)
    output = decoder.decode_ntchw(mx.array(latents.transpose(0, 2, 1, 3, 4)), chunk_size=chunk_size)
    mx.eval(output)
    return np.asarray(output.astype(mx.float32)).transpose(0, 1, 3, 4, 2)</code></pre></details>
</div>
