---
title: minimax_h3_conditioner
---

<div class="python-reference">
<a id="fastvideo.mlx_runtime.minimax_h3_conditioner-classes"></a>
<a id="fastvideo.mlx_runtime.minimax_h3_conditioner-functions"></a>
<a id="fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner-functions"></a>
<a id="fastvideo.mlx_runtime.minimax_h3_conditioner"></a>
<p>Streamed Qwen3-VL prompt conditioner for MiniMax-H3 on Apple Silicon MLX.</p>
<p>Produces exactly the conditioning the H3 DiT consumes: the language-model
hidden states after the first 50 language-model layers plus the per-token
modality tags for text prompts.</p>
<p>Memory contract for the 36 GiB tier: the released conditioner is ~66 GB of
BF16 and never becomes resident. Tensors are read per-key from the safetensors
shards and only the pieces a given forward needs are materialized:</p>
<ul>
<li>token embedding table row-gathered per batch (full table never copied);</li>
<li>one decoder layer (~1 GB BF16) resident at a time, computed in FP32,
  released before the next layer loads;
The forward pass uses MLX. Tokenization uses the Transformers tokenizer API.</li>
</ul>
<h2 id="fastvideo.mlx_runtime.minimax_h3_conditioner.init_logger"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.init_logger</code></h2>
<pre class="doc-signature"><code>init_logger(name: str) -&gt; _FastvideoLogger</code></pre>
<p>The main purpose of this function is to ensure that loggers are
retrieved in such a way that we can be sure the root fastvideo logger has
already been configured.</p>
<h2 id="fastvideo.mlx_runtime.minimax_h3_conditioner.ConditionerConfig"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.ConditionerConfig</code></h2>
<pre class="doc-signature"><code>ConditionerConfig(hidden_size: int = 5120, num_layers: int = 64, num_attention_heads: int = 64, num_key_value_heads: int = 8, head_dim: int = 128, intermediate_size: int = 25600, rms_norm_eps: float = 1e-06, rope_theta: float = 5000000.0, mrope_section: tuple&#91;int, ...&#93; = (24, 20, 20), vocab_size: int = 151936, vision_start_token_id: int = 151652, image_token_id: int = 151655, video_token_id: int = 151656, vision_spatial_merge_size: int = 2)</code></pre>


<details><summary>Source</summary><pre><code>@dataclass
class ConditionerConfig:
    hidden_size: int = 5120
    num_layers: int = 64
    num_attention_heads: int = 64
    num_key_value_heads: int = 8
    head_dim: int = 128
    intermediate_size: int = 25600
    rms_norm_eps: float = 1e-6
    rope_theta: float = 5_000_000.0
    mrope_section: tuple&#91;int, ...&#93; = (24, 20, 20)
    vocab_size: int = 151936
    vision_start_token_id: int = 151652
    image_token_id: int = 151655
    video_token_id: int = 151656
    # Vision merge size drives the MRoPE image/video layout; the vision tower
    # itself re-reads its own ``vision_config`` from config.json.
    vision_spatial_merge_size: int = 2

    @classmethod
    def from_config_json(cls, path: str | Path) -&gt; ConditionerConfig:
        raw = json.loads(Path(path).read_text())
        text = raw.get(&quot;text_config&quot;, raw)
        vision = raw.get(&quot;vision_config&quot;, &#123;&#125;)
        scaling = text.get(&quot;rope_scaling&quot;, &#123;&#125;) or &#123;&#125;
        return cls(
            hidden_size=int(text&#91;&quot;hidden_size&quot;&#93;),
            num_layers=int(text&#91;&quot;num_hidden_layers&quot;&#93;),
            num_attention_heads=int(text&#91;&quot;num_attention_heads&quot;&#93;),
            num_key_value_heads=int(text&#91;&quot;num_key_value_heads&quot;&#93;),
            head_dim=int(text.get(&quot;head_dim&quot;, text&#91;&quot;hidden_size&quot;&#93; // text&#91;&quot;num_attention_heads&quot;&#93;)),
            intermediate_size=int(text&#91;&quot;intermediate_size&quot;&#93;),
            rms_norm_eps=float(text&#91;&quot;rms_norm_eps&quot;&#93;),
            rope_theta=float(text&#91;&quot;rope_theta&quot;&#93;),
            mrope_section=tuple(int(v) for v in scaling.get(&quot;mrope_section&quot;, (24, 20, 20))),
            vocab_size=int(text.get(&quot;vocab_size&quot;, 151936)),
            vision_start_token_id=int(raw.get(&quot;vision_start_token_id&quot;, text.get(&quot;vision_start_token_id&quot;, 151652))),
            image_token_id=int(raw.get(&quot;image_token_id&quot;, text.get(&quot;image_token_id&quot;, 151655))),
            video_token_id=int(raw.get(&quot;video_token_id&quot;, text.get(&quot;video_token_id&quot;, 151656))),
            vision_spatial_merge_size=int(vision.get(&quot;spatial_merge_size&quot;, 2)),
        )</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_conditioner.ConditionerConfig.from_config_json"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.ConditionerConfig.from_config_json</code></h3>
<pre class="doc-signature"><code>from_config_json(path: str | Path) -&gt; ConditionerConfig</code></pre>

<details><summary>Source</summary><pre><code>@classmethod
def from_config_json(cls, path: str | Path) -&gt; ConditionerConfig:
    raw = json.loads(Path(path).read_text())
    text = raw.get(&quot;text_config&quot;, raw)
    vision = raw.get(&quot;vision_config&quot;, &#123;&#125;)
    scaling = text.get(&quot;rope_scaling&quot;, &#123;&#125;) or &#123;&#125;
    return cls(
        hidden_size=int(text&#91;&quot;hidden_size&quot;&#93;),
        num_layers=int(text&#91;&quot;num_hidden_layers&quot;&#93;),
        num_attention_heads=int(text&#91;&quot;num_attention_heads&quot;&#93;),
        num_key_value_heads=int(text&#91;&quot;num_key_value_heads&quot;&#93;),
        head_dim=int(text.get(&quot;head_dim&quot;, text&#91;&quot;hidden_size&quot;&#93; // text&#91;&quot;num_attention_heads&quot;&#93;)),
        intermediate_size=int(text&#91;&quot;intermediate_size&quot;&#93;),
        rms_norm_eps=float(text&#91;&quot;rms_norm_eps&quot;&#93;),
        rope_theta=float(text&#91;&quot;rope_theta&quot;&#93;),
        mrope_section=tuple(int(v) for v in scaling.get(&quot;mrope_section&quot;, (24, 20, 20))),
        vocab_size=int(text.get(&quot;vocab_size&quot;, 151936)),
        vision_start_token_id=int(raw.get(&quot;vision_start_token_id&quot;, text.get(&quot;vision_start_token_id&quot;, 151652))),
        image_token_id=int(raw.get(&quot;image_token_id&quot;, text.get(&quot;image_token_id&quot;, 151655))),
        video_token_id=int(raw.get(&quot;video_token_id&quot;, text.get(&quot;video_token_id&quot;, 151656))),
        vision_spatial_merge_size=int(vision.get(&quot;spatial_merge_size&quot;, 2)),
    )</code></pre></details>
<h2 id="fastvideo.mlx_runtime.minimax_h3_conditioner.NVFP4Matrix"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.NVFP4Matrix</code></h2>
<pre class="doc-signature"><code>NVFP4Matrix(weight: mx.array, scales: mx.array, global_scale: float)</code></pre>
<p>MLX row-major E2M1/E4M3 weights and the export's inverse global scale.</p>

<details><summary>Source</summary><pre><code>@dataclass(frozen=True)
class NVFP4Matrix:
    &quot;&quot;&quot;MLX row-major E2M1/E4M3 weights and the export&#x27;s inverse global scale.&quot;&quot;&quot;

    weight: mx.array
    scales: mx.array
    global_scale: float

    def matmul(self, x):
        return mx.quantized_matmul(x, self.weight, self.scales, mode=&quot;nvfp4&quot;) / self.global_scale</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_conditioner.NVFP4Matrix.matmul"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.NVFP4Matrix.matmul</code></h3>
<pre class="doc-signature"><code>matmul(x)</code></pre>

<details><summary>Source</summary><pre><code>def matmul(self, x):
    return mx.quantized_matmul(x, self.weight, self.scales, mode=&quot;nvfp4&quot;) / self.global_scale</code></pre></details>
<h2 id="fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner</code></h2>
<pre class="doc-signature"><code>StreamedMiniMaxH3TextConditioner(component_dir: str | Path, tokenizer_dir: str | Path | None = None)</code></pre>
<p>Layer-streaming Qwen3-VL text stack -&gt; H3 conditioning hidden states.</p>

<details><summary>Source</summary><pre><code>class StreamedMiniMaxH3TextConditioner:
    &quot;&quot;&quot;Layer-streaming Qwen3-VL text stack -&gt; H3 conditioning hidden states.&quot;&quot;&quot;

    def __init__(self, component_dir: str | Path, tokenizer_dir: str | Path | None = None):
        self.component_dir = Path(component_dir)
        self.config = ConditionerConfig.from_config_json(self.component_dir / &quot;config.json&quot;)
        self.index: Any = _ShardIndex(self.component_dir)
        self.tokenizer = self._load_tokenizer(tokenizer_dir)
        # Vision weights load on first Ref2VA encode; text-only callers never
        # pay for the tower.
        self._vision: Any = None

    def _load_tokenizer(self, tokenizer_dir: str | Path | None):
        from transformers import AutoTokenizer

        candidates = &#91;Path(tokenizer_dir)&#93; if tokenizer_dir else &#91;
            self.component_dir.parent / &quot;tokenizer&quot;,
            self.component_dir,
        &#93;
        last_error = None
        for candidate in candidates:
            try:
                return AutoTokenizer.from_pretrained(str(candidate))
            except Exception as error:  # noqa: BLE001 - fall through to next candidate
                last_error = error
        raise RuntimeError(f&quot;Could not load an H3 tokenizer from &#123;candidates&#125;: &#123;last_error&#125;&quot;)

    # -- public API ------------------------------------------------------

    def tokenize(self, prompt: str) -&gt; list&#91;int&#93;:
        encoded = self.tokenizer(prompt, add_special_tokens=False)
        input_ids = encoded&#91;&quot;input_ids&quot;&#93;
        if input_ids and isinstance(input_ids&#91;0&#93;, list):
            if len(input_ids) != 1:
                raise ValueError(&quot;H3 conditioning expects exactly one sequence.&quot;)
            input_ids = input_ids&#91;0&#93;
        return &#91;int(t) for t in input_ids&#93;

    def encode_prompt(self, prompt: str) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;:
        &quot;&quot;&quot;prompt -&gt; (hidden states (S, hidden), token tags (S,)) both fp32.&quot;&quot;&quot;
        token_ids = self.tokenize(prompt)
        return self.encode_tokens(token_ids)

    def encode_tokens(self, token_ids: list&#91;int&#93;) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;:
        import mlx.core as mx

        cfg = self.config
        seq_len = len(token_ids)
        positions = np.stack(&#91;
            np.arange(seq_len, dtype=np.float64),
            np.arange(seq_len, dtype=np.float64),
            np.arange(seq_len, dtype=np.float64),
        &#93;)
        cos, sin = _mrope_cos_sin(positions, cfg)

        hidden = self._embed_tokens(token_ids)

        if cfg.num_layers &lt; TEXT_ENCODER_LAYER:
            raise ValueError(f&quot;Conditioner needs at least &#123;TEXT_ENCODER_LAYER&#125; layers, has &#123;cfg.num_layers&#125;.&quot;)
        for layer in range(TEXT_ENCODER_LAYER):
            hidden = self._decoder_layer(layer, hidden, cos, sin)
            # Per-layer sync keeps the 50-layer activation graph bounded.
            mx.eval(hidden)
            gc.collect()

        tags = np.full((seq_len, ), 1, dtype=np.int64)  # MINIMAX_H3_TEXT_TAG
        return np.asarray(hidden).astype(np.float32), tags

    def encode_tokens_with_vision(
        self,
        token_ids: list&#91;int&#93;,
        token_tags,
        *,
        pixel_values: np.ndarray | None = None,
        image_grid_thw: np.ndarray | None = None,
        pixel_values_videos: np.ndarray | None = None,
        video_grid_thw: np.ndarray | None = None,
    ) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;:
        &quot;&quot;&quot;Ref2VA encode: splice visual tokens into the prompt, then run the stack.

        Mirrors ``MiniMaxH3Qwen3VLConditioner.encode_ids``: image/video features
        replace the placeholder rows (in placeholder order), the mixed
        text/image/video MRoPE positions replace the plain ``arange``, and each
        of the first ``len(deepstack)`` layers adds its DeEPSTACK block back onto
        the visual rows. Returns ``(hidden (S, hidden) fp32, token_tags (S,) int64)``
        with the caller&#x27;s tags passed through unchanged.
        &quot;&quot;&quot;
        import mlx.core as mx

        from fastvideo.mlx_runtime.minimax_h3_vision import qwen3_vl_rope_index

        if (pixel_values is None) != (image_grid_thw is None):
            raise ValueError(&quot;pixel_values and image_grid_thw must be provided together&quot;)
        if (pixel_values_videos is None) != (video_grid_thw is None):
            raise ValueError(&quot;pixel_values_videos and video_grid_thw must be provided together&quot;)

        cfg = self.config
        seq_len = len(token_ids)
        ids = np.asarray(token_ids, dtype=np.int64)
        has_vision = pixel_values is not None or pixel_values_videos is not None
        if has_vision:
            positions = qwen3_vl_rope_index(
                ids,
                image_grid_thw,
                video_grid_thw,
                vision_start_token_id=cfg.vision_start_token_id,
                image_token_id=cfg.image_token_id,
                video_token_id=cfg.video_token_id,
                spatial_merge_size=cfg.vision_spatial_merge_size,
            )
        else:
            positions = np.stack(&#91;np.arange(seq_len, dtype=np.float64)&#93; * 3)
        cos, sin = _mrope_cos_sin(positions, cfg)

        hidden = self._embed_tokens(token_ids)
        hidden, deepstack, visual_rows = self._splice_visual_tokens(hidden, ids, pixel_values, image_grid_thw,
                                                                    pixel_values_videos, video_grid_thw)

        if cfg.num_layers &lt; TEXT_ENCODER_LAYER:
            raise ValueError(f&quot;Conditioner needs at least &#123;TEXT_ENCODER_LAYER&#125; layers, has &#123;cfg.num_layers&#125;.&quot;)
        for layer in range(TEXT_ENCODER_LAYER):
            hidden = self._decoder_layer(layer, hidden, cos, sin)
            if layer &lt; len(deepstack):
                merged = np.asarray(hidden, dtype=np.float32)
                merged&#91;visual_rows&#93; += deepstack&#91;layer&#93;
                hidden = mx.array(merged)
            # Per-layer sync keeps the 50-layer activation graph bounded.
            mx.eval(hidden)
            gc.collect()

        return np.asarray(hidden).astype(np.float32), np.asarray(token_tags, dtype=np.int64)

    def _vision_model(self):
        if self._vision is None:
            from fastvideo.mlx_runtime.minimax_h3_vision import MLXQwen3VLVisionModel

            weights = getattr(self.index, &quot;weights&quot;, None)
            if weights is not None and _VISUAL_PROBE_KEY in weights:
                # A resident index (and a format-2 cache) carries the whole tower.
                self._vision = MLXQwen3VLVisionModel.from_arrays(self.component_dir / &quot;config.json&quot;, weights)
            else:
                self._vision = MLXQwen3VLVisionModel.from_safetensors_dir(self._vision_source_dir())
        return self._vision

    def _vision_source_dir(self) -&gt; Path:
        &quot;&quot;&quot;Where the vision tower&#x27;s weights are read from when the encoder index lacks its biases.

        Caches written before format 2 stored only the ``.weight`` tensors the
        resident loader kept, so the tower&#x27;s LayerNorm, patch-embed and
        projection biases are read from the checkpoint the cache records. Newer
        caches carry the whole tower and never reach this.
        &quot;&quot;&quot;
        manifest_path = self.component_dir / MLX_NVFP4_ENCODER_MANIFEST
        if not manifest_path.is_file():
            return self.component_dir
        source = json.loads(manifest_path.read_text(encoding=&quot;utf-8&quot;)).get(&quot;source_dir&quot;)
        if not source:
            raise ValueError(f&quot;&#123;manifest_path&#125; records no source_dir, so the vision tower cannot be read &quot;
                             &quot;from the encoder cache.&quot;)
        if not Path(source).is_dir():
            raise FileNotFoundError(f&quot;The checkpoint this older encoder cache was built from, &#123;source&#125;, is gone; &quot;
                                    &quot;the vision tower&#x27;s biases live there. Re-export the cache to include the &quot;
                                    &quot;vision tower, or point --conditioner-dir at the checkpoint.&quot;)
        return Path(source)

    def _splice_visual_tokens(self, hidden, ids, pixel_values, image_grid_thw, pixel_values_videos, video_grid_thw):
        &quot;&quot;&quot;Return ``(hidden, deepstack blocks, visual rows)`` with features spliced in.&quot;&quot;&quot;
        cfg = self.config
        image_features = image_deepstack = video_features = video_deepstack = None
        if pixel_values is not None:
            image_features, image_deepstack = self._vision_model().encode(pixel_values, image_grid_thw)
        if pixel_values_videos is not None:
            video_features, video_deepstack = self._vision_model().encode(pixel_values_videos, video_grid_thw)

        embedded = np.asarray(hidden, dtype=np.float32)
        image_rows = video_rows = None
        if image_features is not None:
            image_rows = np.where(ids == cfg.image_token_id)&#91;0&#93;
            if image_rows.size != image_features.shape&#91;0&#93;:
                raise ValueError(f&quot;Qwen3-VL image features and placeholders disagree: &quot;
                                 f&quot;placeholders=&#123;image_rows.size&#125;, features=&#123;image_features.shape&#91;0&#93;&#125;&quot;)
            embedded&#91;image_rows&#93; = image_features
        if video_features is not None:
            video_rows = np.where(ids == cfg.video_token_id)&#91;0&#93;
            if video_rows.size != video_features.shape&#91;0&#93;:
                raise ValueError(f&quot;Qwen3-VL video features and placeholders disagree: &quot;
                                 f&quot;placeholders=&#123;video_rows.size&#125;, features=&#123;video_features.shape&#91;0&#93;&#125;&quot;)
            embedded&#91;video_rows&#93; = video_features
        hidden = mx.array(embedded)

        if image_rows is None and video_rows is None:
            return hidden, &#91;&#93;, np.empty((0, ), dtype=np.int64)
        if image_rows is not None and video_rows is not None:
            assert image_deepstack is not None and video_deepstack is not None
            visual_rows = np.sort(np.concatenate(&#91;image_rows, video_rows&#93;))
            image_joint = np.isin(visual_rows, image_rows)
            video_joint = np.isin(visual_rows, video_rows)
            blocks = &#91;&#93;
            for image_block, video_block in zip(image_deepstack, video_deepstack, strict=True):
                combined = np.zeros((visual_rows.size, image_block.shape&#91;-1&#93;), dtype=np.float32)
                combined&#91;image_joint&#93; = image_block
                combined&#91;video_joint&#93; = video_block
                blocks.append(combined)
            return hidden, blocks, visual_rows
        if image_rows is not None:
            return hidden, &#91;np.asarray(block, dtype=np.float32) for block in image_deepstack&#93;, image_rows
        return hidden, &#91;np.asarray(block, dtype=np.float32) for block in video_deepstack&#93;, video_rows

    def _embed_tokens(self, token_ids: list&#91;int&#93;):
        # Embedding rows gathered individually; the full table is never
        # materialized by the BF16 streaming path.
        rows = &#91;&#93;
        for token in token_ids:
            key = &quot;model.language_model.embed_tokens.weight&quot;
            rows.append(self.index.get_row(key, token))
        hidden = mx.array(np.stack(rows).astype(np.float32))
        del rows
        gc.collect()
        return hidden

    # -- layers ----------------------------------------------------------

    def _decoder_layer(self, index: int, hidden, cos, sin):
        cfg = self.config
        prefix = f&quot;model.language_model.layers.&#123;index&#125;.&quot;

        def w(name):
            return self.index.get_mlx(prefix + name)

        # Self-attention block.
        residual = hidden
        normed = _rms_norm(hidden, w(&quot;input_layernorm.weight&quot;), cfg.rms_norm_eps)
        query = _linear(normed, w(&quot;self_attn.q_proj.weight&quot;))
        key = _linear(normed, w(&quot;self_attn.k_proj.weight&quot;))
        value = _linear(normed, w(&quot;self_attn.v_proj.weight&quot;))

        heads = cfg.num_attention_heads
        kv_heads = cfg.num_key_value_heads
        head_dim = cfg.head_dim
        seq_len = hidden.shape&#91;0&#93;

        def split_heads(t, count):
            return t.reshape(seq_len, count, head_dim)

        query = split_heads(query, heads)
        key = split_heads(key, kv_heads)
        value = split_heads(value, kv_heads)
        # Per-head QK RMSNorm (Qwen3), weights only.
        query = _rms_norm(query, w(&quot;self_attn.q_norm.weight&quot;), cfg.rms_norm_eps)
        key = _rms_norm(key, w(&quot;self_attn.k_norm.weight&quot;), cfg.rms_norm_eps)

        # Interleaved MRoPE.
        query = _apply_mrope(query, cos, sin)
        key = _apply_mrope(key, cos, sin)

        # Fused causal attention with native GQA. A reference request carries
        # thousands of vision tokens; explicit fp32 scores would need
        # heads * seq^2 * 4 bytes (~14 GB at 7.5k tokens) per layer.
        attended = mx.fast.scaled_dot_product_attention(query.transpose(1, 0, 2)&#91;None&#93;,
                                                        key.transpose(1, 0, 2)&#91;None&#93;,
                                                        value.transpose(1, 0, 2)&#91;None&#93;,
                                                        scale=head_dim**-0.5,
                                                        mask=&quot;causal&quot;)&#91;0&#93;
        # Attention is head-major here: (heads, sequence, head_dim).  Restore
        # sequence-major ordering before concatenating heads for o_proj.
        attended = attended.transpose(1, 0, 2)
        attn_out = _linear(attended.reshape(seq_len, -1), w(&quot;self_attn.o_proj.weight&quot;))
        hidden = residual + attn_out

        # MLP block (SwiGLU).
        residual = hidden
        normed = _rms_norm(hidden, w(&quot;post_attention_layernorm.weight&quot;), cfg.rms_norm_eps)
        gate = _linear(normed, w(&quot;mlp.gate_proj.weight&quot;))
        up = _linear(normed, w(&quot;mlp.up_proj.weight&quot;))
        down = _linear(gate * mx.sigmoid(gate) * up, w(&quot;mlp.down_proj.weight&quot;))
        return residual + down

    def close(self) -&gt; None:
        self._vision = None
        self.index.close()</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.tokenize"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.tokenize</code></h3>
<pre class="doc-signature"><code>tokenize(prompt: str) -&gt; list&#91;int&#93;</code></pre>

<details><summary>Source</summary><pre><code>def tokenize(self, prompt: str) -&gt; list&#91;int&#93;:
    encoded = self.tokenizer(prompt, add_special_tokens=False)
    input_ids = encoded&#91;&quot;input_ids&quot;&#93;
    if input_ids and isinstance(input_ids&#91;0&#93;, list):
        if len(input_ids) != 1:
            raise ValueError(&quot;H3 conditioning expects exactly one sequence.&quot;)
        input_ids = input_ids&#91;0&#93;
    return &#91;int(t) for t in input_ids&#93;</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.encode_prompt"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.encode_prompt</code></h3>
<pre class="doc-signature"><code>encode_prompt(prompt: str) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;</code></pre>
<p>prompt -&gt; (hidden states (S, hidden), token tags (S,)) both fp32.</p>
<details><summary>Source</summary><pre><code>def encode_prompt(self, prompt: str) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;:
    &quot;&quot;&quot;prompt -&gt; (hidden states (S, hidden), token tags (S,)) both fp32.&quot;&quot;&quot;
    token_ids = self.tokenize(prompt)
    return self.encode_tokens(token_ids)</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.encode_tokens"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.encode_tokens</code></h3>
<pre class="doc-signature"><code>encode_tokens(token_ids: list&#91;int&#93;) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;</code></pre>

<details><summary>Source</summary><pre><code>def encode_tokens(self, token_ids: list&#91;int&#93;) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;:
    import mlx.core as mx

    cfg = self.config
    seq_len = len(token_ids)
    positions = np.stack(&#91;
        np.arange(seq_len, dtype=np.float64),
        np.arange(seq_len, dtype=np.float64),
        np.arange(seq_len, dtype=np.float64),
    &#93;)
    cos, sin = _mrope_cos_sin(positions, cfg)

    hidden = self._embed_tokens(token_ids)

    if cfg.num_layers &lt; TEXT_ENCODER_LAYER:
        raise ValueError(f&quot;Conditioner needs at least &#123;TEXT_ENCODER_LAYER&#125; layers, has &#123;cfg.num_layers&#125;.&quot;)
    for layer in range(TEXT_ENCODER_LAYER):
        hidden = self._decoder_layer(layer, hidden, cos, sin)
        # Per-layer sync keeps the 50-layer activation graph bounded.
        mx.eval(hidden)
        gc.collect()

    tags = np.full((seq_len, ), 1, dtype=np.int64)  # MINIMAX_H3_TEXT_TAG
    return np.asarray(hidden).astype(np.float32), tags</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.encode_tokens_with_vision"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.encode_tokens_with_vision</code></h3>
<pre class="doc-signature"><code>encode_tokens_with_vision(token_ids: list&#91;int&#93;, token_tags, *, pixel_values: np.ndarray | None = None, image_grid_thw: np.ndarray | None = None, pixel_values_videos: np.ndarray | None = None, video_grid_thw: np.ndarray | None = None) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;</code></pre>
<p>Ref2VA encode: splice visual tokens into the prompt, then run the stack.</p>
<p>Mirrors <code>MiniMaxH3Qwen3VLConditioner.encode_ids</code>: image/video features
replace the placeholder rows (in placeholder order), the mixed
text/image/video MRoPE positions replace the plain <code>arange</code>, and each
of the first <code>len(deepstack)</code> layers adds its DeEPSTACK block back onto
the visual rows. Returns <code>(hidden (S, hidden) fp32, token_tags (S,) int64)</code>
with the caller's tags passed through unchanged.</p>
<details><summary>Source</summary><pre><code>def encode_tokens_with_vision(
    self,
    token_ids: list&#91;int&#93;,
    token_tags,
    *,
    pixel_values: np.ndarray | None = None,
    image_grid_thw: np.ndarray | None = None,
    pixel_values_videos: np.ndarray | None = None,
    video_grid_thw: np.ndarray | None = None,
) -&gt; tuple&#91;np.ndarray, np.ndarray&#93;:
    &quot;&quot;&quot;Ref2VA encode: splice visual tokens into the prompt, then run the stack.

    Mirrors ``MiniMaxH3Qwen3VLConditioner.encode_ids``: image/video features
    replace the placeholder rows (in placeholder order), the mixed
    text/image/video MRoPE positions replace the plain ``arange``, and each
    of the first ``len(deepstack)`` layers adds its DeEPSTACK block back onto
    the visual rows. Returns ``(hidden (S, hidden) fp32, token_tags (S,) int64)``
    with the caller&#x27;s tags passed through unchanged.
    &quot;&quot;&quot;
    import mlx.core as mx

    from fastvideo.mlx_runtime.minimax_h3_vision import qwen3_vl_rope_index

    if (pixel_values is None) != (image_grid_thw is None):
        raise ValueError(&quot;pixel_values and image_grid_thw must be provided together&quot;)
    if (pixel_values_videos is None) != (video_grid_thw is None):
        raise ValueError(&quot;pixel_values_videos and video_grid_thw must be provided together&quot;)

    cfg = self.config
    seq_len = len(token_ids)
    ids = np.asarray(token_ids, dtype=np.int64)
    has_vision = pixel_values is not None or pixel_values_videos is not None
    if has_vision:
        positions = qwen3_vl_rope_index(
            ids,
            image_grid_thw,
            video_grid_thw,
            vision_start_token_id=cfg.vision_start_token_id,
            image_token_id=cfg.image_token_id,
            video_token_id=cfg.video_token_id,
            spatial_merge_size=cfg.vision_spatial_merge_size,
        )
    else:
        positions = np.stack(&#91;np.arange(seq_len, dtype=np.float64)&#93; * 3)
    cos, sin = _mrope_cos_sin(positions, cfg)

    hidden = self._embed_tokens(token_ids)
    hidden, deepstack, visual_rows = self._splice_visual_tokens(hidden, ids, pixel_values, image_grid_thw,
                                                                pixel_values_videos, video_grid_thw)

    if cfg.num_layers &lt; TEXT_ENCODER_LAYER:
        raise ValueError(f&quot;Conditioner needs at least &#123;TEXT_ENCODER_LAYER&#125; layers, has &#123;cfg.num_layers&#125;.&quot;)
    for layer in range(TEXT_ENCODER_LAYER):
        hidden = self._decoder_layer(layer, hidden, cos, sin)
        if layer &lt; len(deepstack):
            merged = np.asarray(hidden, dtype=np.float32)
            merged&#91;visual_rows&#93; += deepstack&#91;layer&#93;
            hidden = mx.array(merged)
        # Per-layer sync keeps the 50-layer activation graph bounded.
        mx.eval(hidden)
        gc.collect()

    return np.asarray(hidden).astype(np.float32), np.asarray(token_tags, dtype=np.int64)</code></pre></details>
<h3 id="fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.close"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.StreamedMiniMaxH3TextConditioner.close</code></h3>
<pre class="doc-signature"><code>close() -&gt; None</code></pre>

<details><summary>Source</summary><pre><code>def close(self) -&gt; None:
    self._vision = None
    self.index.close()</code></pre></details>
<h2 id="fastvideo.mlx_runtime.minimax_h3_conditioner.unswizzle_nvfp4_scales"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.unswizzle_nvfp4_scales</code></h2>
<pre class="doc-signature"><code>unswizzle_nvfp4_scales(scale: np.ndarray, rows: int, cols: int) -&gt; np.ndarray</code></pre>
<p>FlashInfer 128x4 scale bytes -&gt; MLX row-major group-16 scale bytes.</p>
<details><summary>Source</summary><pre><code>def unswizzle_nvfp4_scales(scale: np.ndarray, rows: int, cols: int) -&gt; np.ndarray:
    &quot;&quot;&quot;FlashInfer 128x4 scale bytes -&gt; MLX row-major group-16 scale bytes.&quot;&quot;&quot;
    pad_rows, pad_cols = -(-rows // 128) * 128, -(-cols // 4) * 4
    if scale.size &lt; pad_rows * pad_cols:
        raise ValueError(f&quot;NVFP4 scales need &#123;pad_rows * pad_cols&#125; bytes, got &#123;scale.size&#125;.&quot;)
    tiles = scale.reshape(-1)&#91;:pad_rows * pad_cols&#93;.reshape(pad_rows // 128, pad_cols // 4, 32, 4, 4)
    return np.ascontiguousarray(tiles.transpose(0, 3, 2, 1, 4).reshape(pad_rows, pad_cols)&#91;:rows, :cols&#93;)</code></pre></details>
<h2 id="fastvideo.mlx_runtime.minimax_h3_conditioner.export_mlx_h3_nvfp4_encoder"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.export_mlx_h3_nvfp4_encoder</code></h2>
<pre class="doc-signature"><code>export_mlx_h3_nvfp4_encoder(component_dir: str | Path, output_dir: str | Path) -&gt; Path</code></pre>
<p>Cache the released packed encoder in MLX layout, without requantization.</p>
<p>Keep original packed nibbles, row-major scale bytes, global scales and
embedding/norm values. Later loads skip CPU scale unswizzling and staging.</p>
<details><summary>Source</summary><pre><code>def export_mlx_h3_nvfp4_encoder(component_dir: str | Path, output_dir: str | Path) -&gt; Path:
    &quot;&quot;&quot;Cache the released packed encoder in MLX layout, without requantization.

    Keep original packed nibbles, row-major scale bytes, global scales and
    embedding/norm values. Later loads skip CPU scale unswizzling and staging.
    &quot;&quot;&quot;
    raw = _read_nvfp4_encoder_config(component_dir)
    final_dir = Path(output_dir)
    if final_dir.exists() and any(final_dir.iterdir()):
        raise FileExistsError(f&quot;Encoder cache output must be empty: &#123;final_dir&#125;&quot;)
    # Build in a sibling staging directory and publish it with one rename, so an
    # out-of-memory or out-of-disk failure never leaves a partial cache behind.
    final_dir.parent.mkdir(parents=True, exist_ok=True)
    output_dir = Path(tempfile.mkdtemp(prefix=f&quot;.&#123;final_dir.name&#125;.partial-&quot;, dir=final_dir.parent))
    index = _ResidentNVFP4Index(_ShardIndex(Path(component_dir)))
    published = False
    try:
        arrays = &#123;&#125;
        matrices = &#123;&#125;
        dense_keys = &#91;&#93;
        for key, value in index.weights.items():
            if isinstance(value, NVFP4Matrix):
                arrays&#91;key&#93; = value.weight
                arrays&#91;key + &quot;.scales&quot;&#93; = value.scales
                matrices&#91;key&#93; = &#123;&quot;global_scale&quot;: value.global_scale&#125;
            else:
                arrays&#91;key&#93; = value
                dense_keys.append(key)
        mx.save_safetensors(str(output_dir / &quot;model.safetensors&quot;), arrays)
        (output_dir / &quot;config.json&quot;).write_text(json.dumps(raw, indent=2) + &quot;\n&quot;)
        manifest = &#123;
            &quot;format_version&quot;: _NVFP4_CACHE_FORMAT_VERSION,
            &quot;language_layers&quot;: TEXT_ENCODER_LAYER,
            &quot;matrices&quot;: matrices,
            &quot;dense_keys&quot;: dense_keys,
            &quot;source_dir&quot;: str(Path(component_dir).resolve())
        &#125;
        (output_dir / MLX_NVFP4_ENCODER_MANIFEST).write_text(json.dumps(manifest, indent=2) + &quot;\n&quot;)
        umask = os.umask(0)
        os.umask(umask)
        output_dir.chmod(0o777 &amp; ~umask)  # mkdtemp is owner-only; shared model dirs need the umask mode
        if final_dir.exists():
            final_dir.rmdir()  # empty, checked above
        output_dir.rename(final_dir)
        published = True
    finally:
        index.close()
        if not published:
            shutil.rmtree(output_dir, ignore_errors=True)
    return final_dir</code></pre></details>
<h2 id="fastvideo.mlx_runtime.minimax_h3_conditioner.ResidentNVFP4MiniMaxH3TextConditioner"><code>fastvideo.mlx_runtime.minimax_h3_conditioner.ResidentNVFP4MiniMaxH3TextConditioner</code></h2>
<pre class="doc-signature"><code>ResidentNVFP4MiniMaxH3TextConditioner(component_dir: str | Path, tokenizer_dir: str | Path | None = None)</code></pre>
<p>Released NVFP4 encoder weights with floating-point MLX activations.</p>
<p>The packed weights and embedding table stay resident. CUDA quantizes
activations to FP4; this path keeps FP32 activations, so hidden states are
not expected to be bit-exact with the CUDA encoder.</p>

<details><summary>Source</summary><pre><code>class ResidentNVFP4MiniMaxH3TextConditioner(StreamedMiniMaxH3TextConditioner):
    &quot;&quot;&quot;Released NVFP4 encoder weights with floating-point MLX activations.

    The packed weights and embedding table stay resident. CUDA quantizes
    activations to FP4; this path keeps FP32 activations, so hidden states are
    not expected to be bit-exact with the CUDA encoder.
    &quot;&quot;&quot;

    def __init__(self, component_dir: str | Path, tokenizer_dir: str | Path | None = None):
        _read_nvfp4_encoder_config(component_dir)
        # Fail on an older MLX before reading the encoder&#x27;s large shards.
        try:
            packed, scales = mx.quantize(mx.ones((1, 64)), mode=&quot;nvfp4&quot;)
            mx.eval(mx.quantized_matmul(mx.ones((1, 64)), packed, scales, mode=&quot;nvfp4&quot;))
        except (ValueError, RuntimeError) as error:
            raise RuntimeError(&quot;Native NVFP4 conditioning requires an MLX build with nvfp4 matmul support.&quot;) from error
        super().__init__(component_dir, tokenizer_dir)
        if (Path(component_dir) / MLX_NVFP4_ENCODER_MANIFEST).exists():
            self.index.close()
            self.index = _ResidentNVFP4Index.from_mlx_checkpoint(component_dir)
        else:
            self.index = _ResidentNVFP4Index(self.index)

    def _embed_tokens(self, token_ids: list&#91;int&#93;):
        table = self.index.get_mlx(&quot;model.language_model.embed_tokens.weight&quot;)
        return table&#91;mx.array(token_ids, dtype=mx.int32)&#93;.astype(mx.float32)</code></pre></details>
</div>
