Skip to content

minimax_h3

MiniMax H3 joint audio-video DiT for the Apple Silicon MLX runtime.

A faithful MLX port of the upstream CUDA reference:

  • DiT: fastvideo/models/dits/minimax_h3.py (merged upstream in #1674). Single-stream packed transformer, per-head qk RMSNorm, 3-axis MM-RoPE (96 of 128 head dims rotated), row-indexed AdaLN keyed by (timestep, modality), dual video/audio output heads, 2-block text token refiner.
  • Scheduler: fastvideo/models/schedulers/scheduling_minimax_h3.py. Rectified-flow Euler with H3's clean-time convention: t = 1 - sigma, data-ward velocity (x0 = x_t + sigma * v), exponential-shift sigma grid (video shift 12.0, audio shift 3.0), fp32 Euler blend.
  • Packing: fastvideo/pipelines/basic/minimax_h3/packing.py. [text | condition | audio | video] rows, float64 position grids.

Apple Silicon memory controls:

  • AdaLN precompute cache. ~40% of H3's parameters live in per-block AdaLN projections whose output depends only on (timestep, modality). For a fixed step schedule the full set of timesteps is known at load time, so :meth:MLXMiniMaxH3DiT.precompute_adaln evaluates every modulation table once and (optionally) drops the projection weights. This also removes the repeated AdaLN projection work from each denoising step.
  • Affine INT8, INT6, or INT4 quantization of attention/FFN matrices (group size 64). Modulation, embeddings, norms, and input/output projections remain in higher precision. Quantization is weight-only: attention Q/K/V stay BF16 (or the selected activation dtype).
  • Optional VSA. Dense conversion still drops transformer_blocks.*.attn.to_gate_compress.weight. --include-vsa keeps those 50 projections, quantizes them with the same affine grid, and records vsa.capable in the manifest. Runtime VSA is opt-in and never enabled for dense-only checkpoints.

Checkpoint layout: the released H3 checkpoint uses the diffusers reference module names 1:1 (transformer_blocks.{i}.attn.to_out.0.weight, ff.net.0.proj.weight, time_embedder.linear_1.weight, ...). This module keeps those names as the MLX weight keys — no renaming contract. proj_in / audio_proj_in / time_embedder / proj_out / audio_proj_out are fp32 in the release and are kept fp32 here.

Nothing in this file requires the CUDA stack; it imports fastvideo.logger only. Parity tests against the torch reference live in fastvideo/tests/mlx/test_mlx_minimax_h3_parity.py.

Classes

fastvideo.mlx_runtime.minimax_h3.MLXMiniMaxH3DiT

MLXMiniMaxH3DiT(weights: dict[str, Any], blocks: list[dict[str, Any]], refiner: list[dict[str, Any]], config: dict[str, Any])

MiniMax H3 joint audio-video DiT in MLX (batch-1 packed forward).

Weight dicts keep the released checkpoint's key names. blocks holds the main transformer blocks, refiner the text refiner blocks; each is a dict of arrays / :class:QuantizedMatrix.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def __init__(
    self,
    weights: dict[str, Any],
    blocks: list[dict[str, Any]],
    refiner: list[dict[str, Any]],
    config: dict[str, Any],
) -> None:
    self.weights = weights
    self.blocks = blocks
    self.refiner = refiner
    self.config = config
    self.hidden_size = int(config["hidden_size"])
    self.num_heads = int(config["num_attention_heads"])
    self.head_dim = int(config["attention_head_dim"])
    self.ffn_dim = int(config["ffn_dim"])
    self.in_channels = int(config["in_channels"])
    self.audio_in_channels = int(config["audio_in_channels"])
    self.patch_size = tuple(config["patch_size"])
    self.text_dim = int(config["text_dim"])
    self.freq_dim = int(config["freq_dim"])
    self.time_embed_dim = int(config["time_embed_dim"])
    self.rope_freq_dim = int(config["rope_freq_dim"])
    self.rope_theta = float(config["rope_theta"])
    self.norm_eps = float(config["norm_eps"])
    self.qk_norm_eps = float(config["qk_norm_eps"])
    self.final_norm_eps = float(config["final_norm_eps"])
    self.patch_dim = self.in_channels * math.prod(self.patch_size)
    self._adaln_cache: MiniMaxH3StepCache | None = None
    self.vsa_config = MiniMaxH3VSAConfig()
    self._vsa_geometry: MiniMaxH3VSAGeometry | None = None
    self._vsa_capable = any("attn.to_gate_compress.weight" in block for block in blocks)
    self._gate_active: list[bool | None] = [None] * len(blocks)
    self.last_vsa_stats: MiniMaxH3VSAStats | None = None

Methods:

fastvideo.mlx_runtime.minimax_h3.MLXMiniMaxH3DiT.compute_temb
compute_temb(timesteps)

(n,) timesteps -> (n, time_embed_dim) through time_proj + MLP.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def compute_temb(self, timesteps):
    """(n,) timesteps -> (n, time_embed_dim) through time_proj + MLP."""
    t_freq = timestep_embedding(timesteps,
                                self.freq_dim).astype(weight_dtype(self.weights["time_embedder.linear_1.weight"]))
    temb = linear(
        t_freq,
        self.weights["time_embedder.linear_1.weight"],
        self.weights["time_embedder.linear_1.bias"],
    )
    return linear(
        silu(temb),
        self.weights["time_embedder.linear_2.weight"],
        self.weights["time_embedder.linear_2.bias"],
    )
fastvideo.mlx_runtime.minimax_h3.MLXMiniMaxH3DiT.forward
forward(video_rows, audio_rows, text_rows, *, position_ids, token_tags, timestep_indices, timesteps, video_indices, audio_indices, text_indices)

Faithful port of the torch forward (batch-1, rows already patchified).

Returns (video_output, audio_output) rows. The torch reference projects all packed rows through both heads and then selects; this selects first and projects only the relevant rows — row-wise identical math at a fraction of the cost.

This entrypoint is dense-only. VSA requires forward_with_cache with a packed layout and step index.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def forward(
    self,
    video_rows,
    audio_rows,
    text_rows,
    *,
    position_ids,
    token_tags,
    timestep_indices,
    timesteps,
    video_indices,
    audio_indices,
    text_indices,
):
    """Faithful port of the torch forward (batch-1, rows already patchified).

    Returns (video_output, audio_output) rows. The torch reference
    projects *all* packed rows through both heads and then selects; this
    selects first and projects only the relevant rows — row-wise
    identical math at a fraction of the cost.

    This entrypoint is dense-only. VSA requires ``forward_with_cache``
    with a packed layout and step index.
    """
    import mlx.core as mx

    if self.vsa_config.enabled:
        raise ValueError("VSA requires forward_with_cache() with layout and step_index; forward() is dense-only.")
    sequence_length = position_ids.shape[0]
    cos, sin = rope_cos_sin(position_ids, self.rope_freq_dim, self.rope_theta)

    video_embeds = linear(
        video_rows.astype(weight_dtype(self.weights["proj_in.weight"])),
        self.weights["proj_in.weight"],
        self.weights["proj_in.bias"],
    )
    audio_embeds = linear(
        audio_rows.astype(weight_dtype(self.weights["audio_proj_in.weight"])),
        self.weights["audio_proj_in.weight"],
        self.weights["audio_proj_in.bias"],
    )
    text_embeds = self.refine_text(text_rows)

    packed = mx.zeros((sequence_length, self.hidden_size), dtype=text_embeds.dtype)
    packed[_as_mx_indices(text_indices)] = text_embeds
    packed[_as_mx_indices(video_indices)] = video_embeds.astype(text_embeds.dtype)
    packed[_as_mx_indices(audio_indices)] = audio_embeds.astype(text_embeds.dtype)

    temb = self.compute_temb(timesteps)
    adaln_indices = (timestep_indices * MINIMAX_H3_MODALITY_NUM + token_tags).astype(mx.int32)

    for block_index, block in enumerate(self.blocks):
        tables = _adaln_tables(block, temb)
        packed = _transformer_block(
            block,
            packed,
            tables,
            adaln_indices,
            cos,
            sin,
            num_heads=self.num_heads,
            head_dim=self.head_dim,
            norm_eps=self.norm_eps,
            qk_norm_eps=self.qk_norm_eps,
            **self._vsa_block_kwargs(block_index, 0),
        )
        mx.eval(packed)  # per-block sync: see forward_with_cache note

    shift_scale = linear(
        silu(temb).astype(weight_dtype(self.weights["norm_out.linear.weight"])),
        self.weights["norm_out.linear.weight"],
        self.weights["norm_out.linear.bias"],
    )
    shift_rows, scale_rows = mx.split(shift_scale, 2, axis=-1)
    normed = _h3_rms_norm(packed, self.weights["norm_out.norm.weight"], self.final_norm_eps)
    normed = normed * (1.0 + scale_rows[timestep_indices]) + shift_rows[timestep_indices]
    video_output = linear(
        normed[_as_mx_indices(video_indices)].astype(weight_dtype(self.weights["proj_out.weight"])),
        self.weights["proj_out.weight"],
        self.weights["proj_out.bias"],
    )
    audio_output = linear(
        normed[_as_mx_indices(audio_indices)].astype(weight_dtype(self.weights["audio_proj_out.weight"])),
        self.weights["audio_proj_out.weight"],
        self.weights["audio_proj_out.bias"],
    )
    return video_output, audio_output
fastvideo.mlx_runtime.minimax_h3.MLXMiniMaxH3DiT.forward_with_cache
forward_with_cache(video_rows, audio_rows, text_rows, *, layout: MiniMaxH3PackedLayout, step_timesteps: ndarray, row_timestep_inverse: ndarray, step_index: int = 0)

Denoise-step forward served entirely from the AdaLN cache.

step_timesteps are this step's unique timesteps (sorted); row_timestep_inverse maps each packed row to one of them (the build_row_timesteps inverse). The cache must cover every value in step_timesteps.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def forward_with_cache(
    self,
    video_rows,
    audio_rows,
    text_rows,
    *,
    layout: MiniMaxH3PackedLayout,
    step_timesteps: np.ndarray,
    row_timestep_inverse: np.ndarray,
    step_index: int = 0,
):
    """Denoise-step forward served entirely from the AdaLN cache.

    ``step_timesteps`` are this step's unique timesteps (sorted);
    ``row_timestep_inverse`` maps each packed row to one of them (the
    ``build_row_timesteps`` inverse). The cache must cover every value in
    ``step_timesteps``.
    """
    import mlx.core as mx

    if self._adaln_cache is None:
        raise RuntimeError("precompute_adaln() must run before forward_with_cache().")
    if self.vsa_config.enabled:
        self.prepare_vsa_geometry(layout)
    cache = self._adaln_cache
    positions = mx.array(cache.positions(step_timesteps))

    sequence_length = layout.sequence_length
    position_ids = mx.array(layout.position_ids)
    cos, sin = rope_cos_sin(position_ids, self.rope_freq_dim, self.rope_theta)

    video_embeds = linear(
        video_rows.astype(weight_dtype(self.weights["proj_in.weight"])),
        self.weights["proj_in.weight"],
        self.weights["proj_in.bias"],
    )
    audio_embeds = linear(
        audio_rows.astype(weight_dtype(self.weights["audio_proj_in.weight"])),
        self.weights["audio_proj_in.weight"],
        self.weights["audio_proj_in.bias"],
    )
    text_embeds = self.refine_text(text_rows)

    packed = mx.zeros((sequence_length, self.hidden_size), dtype=text_embeds.dtype)
    packed[mx.array(layout.text_indices)] = text_embeds
    packed[mx.array(layout.video_indices)] = video_embeds.astype(text_embeds.dtype)
    packed[mx.array(layout.audio_indices)] = audio_embeds.astype(text_embeds.dtype)

    token_tags = mx.array(layout.token_tags)
    row_inverse = mx.array(row_timestep_inverse)
    adaln_indices = (positions[row_inverse] * MINIMAX_H3_MODALITY_NUM + token_tags).astype(mx.int32)

    # Sync per block: without this, MLX enqueues the entire 50-block graph
    # while the GPU lags behind, allocating every intermediate at once
    # (observed as an OOM kill on 36 GiB Macs at 480x832).
    for block_index, (block, tables) in enumerate(zip(self.blocks, cache.block_tables, strict=True)):
        vsa_kwargs = self._vsa_block_kwargs(block_index, step_index)
        packed = _transformer_block(
            block,
            packed,
            tables,
            adaln_indices,
            cos,
            sin,
            num_heads=self.num_heads,
            head_dim=self.head_dim,
            norm_eps=self.norm_eps,
            qk_norm_eps=self.qk_norm_eps,
            **vsa_kwargs,
        )
        mx.eval(packed)
        if self.last_vsa_stats is not None:
            self.last_vsa_stats.record(vsa_kwargs["vsa_stats"])

    row_positions = positions[row_inverse]
    normed = _h3_rms_norm(packed, self.weights["norm_out.norm.weight"], self.final_norm_eps)
    normed = normed * (1.0 + cache.norm_out_scale[row_positions]) + cache.norm_out_shift[row_positions]
    video_output = linear(
        normed[mx.array(layout.video_indices)].astype(weight_dtype(self.weights["proj_out.weight"])),
        self.weights["proj_out.weight"],
        self.weights["proj_out.bias"],
    )
    audio_output = linear(
        normed[mx.array(layout.audio_indices)].astype(weight_dtype(self.weights["audio_proj_out.weight"])),
        self.weights["audio_proj_out.weight"],
        self.weights["audio_proj_out.bias"],
    )
    return video_output, audio_output
fastvideo.mlx_runtime.minimax_h3.MLXMiniMaxH3DiT.precompute_adaln
precompute_adaln(timesteps: ndarray, *, drop_weights: bool = True) -> MiniMaxH3StepCache

Evaluate every modulation table for a fixed schedule, once.

timesteps is the union (sorted, unique) of all per-step video/audio/condition timesteps the sampler will use. With drop_weights=True the per-block adaln_proj.linear weights are released afterwards — the memory win this runtime exists for.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def precompute_adaln(self, timesteps: np.ndarray, *, drop_weights: bool = True) -> MiniMaxH3StepCache:
    """Evaluate every modulation table for a fixed schedule, once.

    ``timesteps`` is the union (sorted, unique) of all per-step
    video/audio/condition timesteps the sampler will use. With
    ``drop_weights=True`` the per-block ``adaln_proj.linear`` weights are
    released afterwards — the memory win this runtime exists for.
    """
    import mlx.core as mx

    timesteps = np.unique(np.asarray(timesteps, dtype=np.float32))
    temb = self.compute_temb(mx.array(timesteps))
    block_tables = [_adaln_tables(block, temb) for block in self.blocks]
    shift_scale = linear(
        silu(temb).astype(weight_dtype(self.weights["norm_out.linear.weight"])),
        self.weights["norm_out.linear.weight"],
        self.weights["norm_out.linear.bias"],
    )
    norm_out_shift, norm_out_scale = mx.split(shift_scale, 2, axis=-1)
    mx.eval(block_tables, norm_out_scale, norm_out_shift)
    self._adaln_cache = MiniMaxH3StepCache(
        timesteps=timesteps,
        block_tables=block_tables,
        norm_out_shift=norm_out_shift,
        norm_out_scale=norm_out_scale,
    )
    if drop_weights:
        for block in self.blocks:
            block["adaln_proj.linear.weight"] = None
            block["adaln_proj.linear.bias"] = None
    return self._adaln_cache

fastvideo.mlx_runtime.minimax_h3.MiniMaxH3PackedLayout dataclass

MiniMaxH3PackedLayout(sequence_length: int, position_ids: ndarray, token_tags: ndarray, video_indices: ndarray, audio_indices: ndarray, text_indices: ndarray, num_condition_video_rows: int, num_condition_audio_rows: int, num_video_latent_frames: int, latent_height: int, latent_width: int, num_audio_latents: int)

One packed joint sequence and the geometry needed to interpret it.

Arrays are NumPy (position_ids in float64, indices int64) and converted to MLX at the model boundary.

fastvideo.mlx_runtime.minimax_h3.MiniMaxH3SchedulerState dataclass

MiniMaxH3SchedulerState(shift: float, sigmas: ndarray, timesteps: ndarray)

One rectified-flow scheduler (use two: video shift 12, audio shift 3).

Methods:

fastvideo.mlx_runtime.minimax_h3.MiniMaxH3SchedulerState.scale_noise
scale_noise(sample, timestep: float, noise)

Conditioning noise-aug: tsample + (1-t)noise (t=0.999 for keyframes).

Source code in fastvideo/mlx_runtime/minimax_h3.py
def scale_noise(self, sample, timestep: float, noise):
    """Conditioning noise-aug: t*sample + (1-t)*noise (t=0.999 for keyframes)."""
    return timestep * sample + (1.0 - timestep) * noise
fastvideo.mlx_runtime.minimax_h3.MiniMaxH3SchedulerState.step
step(model_output, step_index: int, sample)

Data-ward Euler: x0 = x_t + sigma*v; blend toward x0 by sigma ratio.

fp32 blend for fp16/bf16 samples, matching the reference.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def step(self, model_output, step_index: int, sample):
    """Data-ward Euler: x0 = x_t + sigma*v; blend toward x0 by sigma ratio.

    fp32 blend for fp16/bf16 samples, matching the reference.
    """
    import mlx.core as mx

    sigma_from_timestep = float(1.0 - self.timesteps[step_index])
    denoised = sample + sigma_from_timestep * model_output
    sigma = float(self.sigmas[step_index])
    sigma_next = float(self.sigmas[step_index + 1])
    ratio = sigma_next / sigma
    prev = ratio * sample.astype(mx.float32) + (1.0 - ratio) * denoised.astype(mx.float32)
    return prev.astype(sample.dtype)

fastvideo.mlx_runtime.minimax_h3.MiniMaxH3StepCache dataclass

MiniMaxH3StepCache(timesteps: ndarray, block_tables: list[tuple[Any, ...]], norm_out_shift: Any, norm_out_scale: Any)

Precomputed AdaLN tables + norm_out modulation for a fixed schedule.

Holds one row set per distinct timestep in timesteps (the union of every denoise step's video/audio/condition timesteps). Blocks then index tables directly and the per-block adaln_proj weights can be freed — ~40% of H3's parameters never need to be resident during denoise.

Methods:

fastvideo.mlx_runtime.minimax_h3.MiniMaxH3StepCache.positions
positions(step_timesteps: ndarray) -> ndarray

Map a step's unique timesteps to rows in the cached union.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def positions(self, step_timesteps: np.ndarray) -> np.ndarray:
    """Map a step's unique timesteps to rows in the cached union."""
    positions = np.searchsorted(self.timesteps, step_timesteps)
    if (positions.size and (np.any(positions >= len(self.timesteps))
                            or not np.allclose(self.timesteps[positions], step_timesteps, atol=1e-6))):
        raise ValueError(f"Step timesteps {step_timesteps} are not in the cached schedule union.")
    return positions.astype(np.int64)

Functions:

fastvideo.mlx_runtime.minimax_h3.apply_h3_rotary

apply_h3_rotary(x, cos, sin)

Rotate the RoPE prefix of each head, preserving the rest.

x: (S, H, D); cos/sin: (S, R) with R <= D. Half-split (GPT-NeoX style) rotation inside the rotary prefix, computed in fp32.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def apply_h3_rotary(x, cos, sin):
    """Rotate the RoPE prefix of each head, preserving the rest.

    x: (S, H, D); cos/sin: (S, R) with R <= D. Half-split (GPT-NeoX style)
    rotation inside the rotary prefix, computed in fp32.
    """
    import mlx.core as mx

    rotary_dim = cos.shape[-1]
    x_rotary = x[..., :rotary_dim].astype(mx.float32)
    x_pass = x[..., rotary_dim:]
    cos_b = cos[:, None, :].astype(mx.float32)
    sin_b = sin[:, None, :].astype(mx.float32)
    first_half, second_half = mx.split(x_rotary, 2, axis=-1)
    rotated = mx.concatenate([-second_half, first_half], axis=-1)
    out = x_rotary * cos_b + rotated * sin_b
    return mx.concatenate([out.astype(x.dtype), x_pass], axis=-1)

fastvideo.mlx_runtime.minimax_h3.build_packed_layout

build_packed_layout(num_text_tokens: int, num_latent_frames: int, latent_height: int, latent_width: int, num_audio_latents: int, patch_size: tuple[int, int, int] = (1, 2, 2), keyframe_anchors: tuple[str, ...] = (), text_token_tags: ndarray | None = None, video_temporal_scale: float = 1.0) -> MiniMaxH3PackedLayout

Build the [text | condition | audio | video] layout (T2VA / FL2VA).

Source code in fastvideo/mlx_runtime/minimax_h3.py
def build_packed_layout(
    num_text_tokens: int,
    num_latent_frames: int,
    latent_height: int,
    latent_width: int,
    num_audio_latents: int,
    patch_size: tuple[int, int, int] = (1, 2, 2),
    keyframe_anchors: tuple[str, ...] = (),
    text_token_tags: np.ndarray | None = None,
    video_temporal_scale: float = 1.0,
) -> MiniMaxH3PackedLayout:
    """Build the ``[text | condition | audio | video]`` layout (T2VA / FL2VA)."""
    _, patch_h, patch_w = patch_size
    if text_token_tags is None:
        text_token_tags = np.full(num_text_tokens, MINIMAX_H3_TEXT_TAG, dtype=np.int64)
    if text_token_tags.shape != (num_text_tokens, ):
        raise ValueError(f"text_token_tags must have shape ({num_text_tokens},), got {text_token_tags.shape}.")
    if not np.isin(text_token_tags, (MINIMAX_H3_TEXT_TAG, MINIMAX_H3_VIDEO_TAG)).all():
        raise ValueError("text_token_tags may contain only text and vision tags.")
    if not np.isfinite(video_temporal_scale) or video_temporal_scale <= 0:
        raise ValueError(f"video_temporal_scale must be positive and finite, got {video_temporal_scale}.")
    if keyframe_anchors and video_temporal_scale != 1.0:
        raise ValueError("video_temporal_scale is currently supported only for T2VA without keyframe anchors.")

    rows_per_frame = (latent_height // patch_h) * (latent_width // patch_w)
    num_condition_rows = len(keyframe_anchors) * rows_per_frame
    num_audio_rows = num_audio_latents * MINIMAX_H3_AUDIO_CHANNELS
    num_video_rows = num_latent_frames * rows_per_frame
    sequence_length = num_text_tokens + num_condition_rows + num_audio_rows + num_video_rows

    condition_start = num_text_tokens
    audio_start = condition_start + num_condition_rows
    video_start = audio_start + num_audio_rows

    position_ids = np.zeros((sequence_length, 3), dtype=np.float64)
    position_ids[:num_text_tokens, 0] = np.arange(num_text_tokens, dtype=np.float64)

    sqrt_area = float(np.sqrt(latent_height * latent_width))
    height_grid = spatial_position_grid(latent_height, patch_h, sqrt_area)
    width_grid = spatial_position_grid(latent_width, patch_w, sqrt_area)
    mesh_h, mesh_w = np.meshgrid(height_grid, width_grid, indexing="ij")
    frame_grid = np.stack([mesh_h.reshape(-1), mesh_w.reshape(-1)], axis=-1)

    for index, anchor in enumerate(keyframe_anchors):
        if anchor == "first":
            anchor_time = float(num_text_tokens)
        elif anchor == "last":
            anchor_time = (float(num_text_tokens) + _temporal_position_span(num_latent_frames) -
                           MINIMAX_H3_ROPE_FRAME_RESCALE)
        else:
            raise ValueError(f"A keyframe anchor must be 'first' or 'last', got {anchor!r}.")
        rows = slice(condition_start + index * rows_per_frame, condition_start + (index + 1) * rows_per_frame)
        position_ids[rows, 0] = anchor_time
        position_ids[rows, 1:] = frame_grid

    audio_time = float(num_text_tokens) + np.arange(num_audio_latents, dtype=np.float64)
    position_ids[audio_start:video_start, 0] = np.tile(audio_time, MINIMAX_H3_AUDIO_CHANNELS)
    position_ids[audio_start:video_start, 2] = np.concatenate([
        np.full(num_audio_latents, float(width_grid[0]), dtype=np.float64),
        np.full(num_audio_rows - num_audio_latents, float(width_grid[-1]), dtype=np.float64),
    ])

    frame_time = temporal_position_grid(num_latent_frames, 0.0) * video_temporal_scale + float(num_text_tokens)
    position_ids[video_start:, 0] = np.repeat(frame_time, rows_per_frame)
    position_ids[video_start:, 1:] = np.tile(frame_grid, (num_latent_frames, 1))

    video_indices = np.concatenate([
        np.arange(condition_start, audio_start),
        np.arange(video_start, sequence_length),
    ])
    audio_indices = np.arange(audio_start, video_start)
    text_indices = np.arange(num_text_tokens)
    token_tags = np.empty(sequence_length, dtype=np.int64)
    token_tags[text_indices] = text_token_tags
    token_tags[audio_indices] = MINIMAX_H3_AUDIO_TAG
    token_tags[video_indices] = MINIMAX_H3_VIDEO_TAG

    return MiniMaxH3PackedLayout(
        sequence_length=sequence_length,
        position_ids=position_ids,
        token_tags=token_tags,
        video_indices=video_indices,
        audio_indices=audio_indices,
        text_indices=text_indices,
        num_condition_video_rows=num_condition_rows,
        num_condition_audio_rows=0,
        num_video_latent_frames=num_latent_frames,
        latent_height=latent_height,
        latent_width=latent_width,
        num_audio_latents=num_audio_latents,
    )

fastvideo.mlx_runtime.minimax_h3.build_row_timesteps

build_row_timesteps(layout: MiniMaxH3PackedLayout, video_timestep: float, audio_timestep: float, condition_video_timestep: float = 1.0, condition_audio_timestep: float = 1.0) -> tuple[ndarray, ndarray]

Per-row timesteps -> (unique timesteps sorted ascending, per-row indices).

Matches torch.unique(..., sorted=True, return_inverse=True).

Source code in fastvideo/mlx_runtime/minimax_h3.py
def build_row_timesteps(
    layout: MiniMaxH3PackedLayout,
    video_timestep: float,
    audio_timestep: float,
    condition_video_timestep: float = 1.0,
    condition_audio_timestep: float = 1.0,
) -> tuple[np.ndarray, np.ndarray]:
    """Per-row timesteps -> (unique timesteps sorted ascending, per-row indices).

    Matches ``torch.unique(..., sorted=True, return_inverse=True)``.
    """
    row_timesteps = np.full(layout.sequence_length, video_timestep, dtype=np.float64)
    if layout.num_condition_video_rows:
        row_timesteps[layout.video_indices[:layout.num_condition_video_rows]] = condition_video_timestep
    row_timesteps[layout.audio_indices[layout.num_condition_audio_rows:]] = audio_timestep
    if layout.num_condition_audio_rows:
        row_timesteps[layout.audio_indices[:layout.num_condition_audio_rows]] = condition_audio_timestep
    unique, inverse = np.unique(row_timesteps, return_inverse=True)
    return unique.astype(np.float32), inverse.astype(np.int64)

fastvideo.mlx_runtime.minimax_h3.linear

linear(x, weight, bias=None)

Run an H3 linear with its measured wide-row affine dispatch enabled.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def linear(x, weight, bias=None):
    """Run an H3 linear with its measured wide-row affine dispatch enabled."""
    return _shared_linear(x, weight, bias, use_affine_dq_gemm=True)

fastvideo.mlx_runtime.minimax_h3.load_mlx_h3_checkpoint

load_mlx_h3_checkpoint(checkpoint_dir: str | Path) -> MLXMiniMaxH3DiT

Rebuild an H3 DiT saved by :func:save_mlx_h3_checkpoint.

Refuses a quantization grid the installed MLX cannot execute, loudly — never silently dequantizes onto a different grid.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def load_mlx_h3_checkpoint(checkpoint_dir: str | Path) -> MLXMiniMaxH3DiT:
    """Rebuild an H3 DiT saved by :func:`save_mlx_h3_checkpoint`.

    Refuses a quantization grid the installed MLX cannot execute, loudly —
    never silently dequantizes onto a different grid.
    """
    import mlx.core as mx

    checkpoint_dir = Path(checkpoint_dir)
    manifest_path = checkpoint_dir / H3_MANIFEST_FILENAME
    weights_path = checkpoint_dir / H3_WEIGHTS_FILENAME
    if not manifest_path.exists() or not weights_path.exists():
        raise FileNotFoundError(f"Not an MLX H3 checkpoint directory: {checkpoint_dir} "
                                f"(expected {H3_MANIFEST_FILENAME} and {H3_WEIGHTS_FILENAME}).")

    manifest = json.loads(manifest_path.read_text())
    version = manifest.get("format_version")
    if version != H3_FORMAT_VERSION:
        raise ValueError(f"MLX H3 checkpoint {checkpoint_dir} has format_version={version}; "
                         f"this build reads version {H3_FORMAT_VERSION}. Re-export the checkpoint.")

    spec = None
    if manifest["quantization"] is not None:
        spec = MLXQuantizationSpec(**manifest["quantization"])
        ensure_quantization_supported(spec)

    arrays = mx.load(str(weights_path))
    quantized_keys: dict[str, dict[str, Any]] = manifest["quantized_keys"]

    def rebuild(key: str):
        if key not in quantized_keys:
            return arrays[key]
        info = quantized_keys[key]
        assert spec is not None, f"Quantized key '{key}' in a checkpoint without a quantization spec"
        return QuantizedMatrix(
            weight=arrays[key],
            scales=arrays[f"{key}.scales"],
            biases=arrays[f"{key}.biases"] if info["has_biases"] else None,
            spec=spec,
            dequantized_dtype=_name_to_dtype(info["dequantized_dtype"]),
        )

    weights: dict[str, Any] = {}
    blocks: list[dict[str, Any] | None] = [None] * int(manifest["num_blocks"])
    refiner: list[dict[str, Any] | None] = [None] * int(manifest["num_refiner_blocks"])
    for key in arrays:
        if key.startswith("__adaln_cache."):
            continue
        if key.endswith(".scales") or key.endswith(".biases"):
            continue
        value = rebuild(key)
        if key.startswith("blocks."):
            _, index_str, sub = key.split(".", 2)
            index = int(index_str)
            block = blocks[index]
            if block is None:
                block = {}
                blocks[index] = block
            block[sub] = value
        elif key.startswith("refiner."):
            _, index_str, sub = key.split(".", 2)
            index = int(index_str)
            refiner_block = refiner[index]
            if refiner_block is None:
                refiner_block = {}
                refiner[index] = refiner_block
            refiner_block[sub] = value
        else:
            weights[key] = value

    if any(block is None for block in blocks) or any(block is None for block in refiner):
        raise ValueError(f"MLX H3 checkpoint {checkpoint_dir} is missing block weights.")
    dit = MLXMiniMaxH3DiT(
        weights,
        [block for block in blocks if block is not None],
        [block for block in refiner if block is not None],
        manifest["config"],
    )
    cache_info = manifest.get("adaln_cache")
    if cache_info is not None:
        block_tables = []
        for block_index in range(int(cache_info["num_blocks"])):
            block_tables.append(
                tuple(arrays[f"__adaln_cache.block.{block_index}.table.{table_index}"]
                      for table_index in range(int(cache_info["tables_per_block"]))))
        dit._adaln_cache = MiniMaxH3StepCache(
            timesteps=np.asarray(cache_info["timesteps"], dtype=np.float32),
            block_tables=block_tables,
            norm_out_shift=arrays["__adaln_cache.norm_out_shift"],
            norm_out_scale=arrays["__adaln_cache.norm_out_scale"],
        )
    return dit

fastvideo.mlx_runtime.minimax_h3.minimax_h3_sigmas

minimax_h3_sigmas(shift: float, num_denoise_steps: int) -> ndarray

Sigma grid of length num_denoise_steps + 1 (descending, ending at 0).

The reference set_timesteps(num_inference_steps=N) builds N sigmas and N-1 timesteps; this wrapper takes the denoise step count directly, which is what a sampler actually schedules.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def minimax_h3_sigmas(shift: float, num_denoise_steps: int) -> np.ndarray:
    """Sigma grid of length ``num_denoise_steps + 1`` (descending, ending at 0).

    The reference ``set_timesteps(num_inference_steps=N)`` builds N sigmas and
    N-1 timesteps; this wrapper takes the *denoise step count* directly, which
    is what a sampler actually schedules.
    """
    if num_denoise_steps < 1:
        raise ValueError(f"num_denoise_steps must be >= 1, got {num_denoise_steps}.")
    base = np.linspace(1.0, 0.0, num_denoise_steps + 1, dtype=np.float64)
    sigmas = shift * base / (1.0 + (shift - 1.0) * base)
    return _unique_consecutive(sigmas).astype(np.float32)

fastvideo.mlx_runtime.minimax_h3.mlx_h3_checkpoint_vsa_capable

mlx_h3_checkpoint_vsa_capable(checkpoint_dir: str | Path) -> bool

True when a saved MLX H3 checkpoint retained the VSA gate projections.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def mlx_h3_checkpoint_vsa_capable(checkpoint_dir: str | Path) -> bool:
    """True when a saved MLX H3 checkpoint retained the VSA gate projections."""
    manifest_path = Path(checkpoint_dir) / H3_MANIFEST_FILENAME
    if not manifest_path.is_file():
        return False
    manifest = json.loads(manifest_path.read_text())
    if bool((manifest.get("vsa") or {}).get("capable")):
        return True
    quantized_keys = manifest.get("quantized_keys") or {}
    return any("attn.to_gate_compress.weight" in key for key in quantized_keys)

fastvideo.mlx_runtime.minimax_h3.mlx_h3_dit_from_diffusers_safetensors

mlx_h3_dit_from_diffusers_safetensors(transformer_path: str | Path, config: dict[str, Any] | None = None, *, dtype: str = 'fp16', num_blocks: int | None = None, quantization: str | MLXQuantizationSpec | None = None, adaln_cache_timesteps: ndarray | None = None, include_vsa: bool = False) -> MLXMiniMaxH3DiT

Load the released H3 transformer (diffusers layout) into MLX.

transformer_path is the transformer/ directory of the HF repo (or a single safetensors file, e.g. a student checkpoint). config defaults to config.json next to the weights. fp32-release modules stay fp32; attention/FFN matrices are quantized when quantization is set.

When adaln_cache_timesteps is provided, AdaLN tables are computed one block at a time while the checkpoint is read. The 13B projection weights are never retained together and are omitted from the returned model. This is the memory-bounded build path for the released 33B student.

include_vsa keeps attn.to_gate_compress matrices and quantizes them with the same affine grid as the other linear weights. Dense conversion continues to drop them.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def mlx_h3_dit_from_diffusers_safetensors(
    transformer_path: str | Path,
    config: dict[str, Any] | None = None,
    *,
    dtype: str = "fp16",
    num_blocks: int | None = None,
    quantization: str | MLXQuantizationSpec | None = None,
    adaln_cache_timesteps: np.ndarray | None = None,
    include_vsa: bool = False,
) -> MLXMiniMaxH3DiT:
    """Load the released H3 transformer (diffusers layout) into MLX.

    ``transformer_path`` is the ``transformer/`` directory of the HF repo (or
    a single safetensors file, e.g. a student checkpoint). ``config`` defaults
    to ``config.json`` next to the weights. fp32-release modules stay fp32;
    attention/FFN matrices are quantized when ``quantization`` is set.

    When ``adaln_cache_timesteps`` is provided, AdaLN tables are computed one
    block at a time while the checkpoint is read. The 13B projection weights
    are never retained together and are omitted from the returned model. This
    is the memory-bounded build path for the released 33B student.

    ``include_vsa`` keeps ``attn.to_gate_compress`` matrices and quantizes them
    with the same affine grid as the other linear weights. Dense conversion
    continues to drop them.
    """
    import mlx.core as mx
    transformer_path = Path(transformer_path)
    if config is None:
        config_path = transformer_path / "config.json" if transformer_path.is_dir() else transformer_path.with_name(
            "config.json")
        config = json.loads(Path(config_path).read_text())
    total_blocks = int(config["num_layers"])
    if num_blocks is None:
        num_blocks = total_blocks
    num_refiner = int(config["num_refiner_layers"])
    cast_dtype = {"fp16": mx.float16, "bf16": mx.bfloat16, "fp32": mx.float32}[dtype]
    spec = MLXQuantizationSpec.from_name(quantization) if (quantization is None
                                                           or isinstance(quantization, str)) else quantization
    ensure_quantization_supported(spec)

    weights: dict[str, Any] = {}
    blocks: list[dict[str, Any] | None] = [None] * num_blocks
    refiner: list[dict[str, Any] | None] = [None] * num_refiner
    cache_timesteps = None
    temb = None
    cached_block_tables: list[tuple[Any, ...] | None] | None = None
    pending_adaln: dict[int, dict[str, Any]] = {}

    def assign(key: str, value) -> None:
        if key.startswith("transformer_blocks."):
            _, index_str, sub = key.split(".", 2)
            index = int(index_str)
            if index < num_blocks:
                block = blocks[index]
                if block is None:
                    block = {}
                    blocks[index] = block
                block[sub] = value
        elif key.startswith("token_refiner.refiner_blocks."):
            _, _, index_str, sub = key.split(".", 3)
            index = int(index_str)
            refiner_block = refiner[index]
            if refiner_block is None:
                refiner_block = {}
                refiner[index] = refiner_block
            refiner_block[sub] = value
        else:
            weights[key] = value

    if adaln_cache_timesteps is not None:
        cache_timesteps = np.unique(np.asarray(adaln_cache_timesteps, dtype=np.float32))
        for shard in _safetensors_shards(transformer_path):
            shard_arrays = mx.load(str(shard))
            for key, source in shard_arrays.items():
                if not key.startswith("time_embedder."):
                    continue
                keep_fp32 = key.split(".", 1)[0] in FP32_MODULE_PREFIXES
                target_dtype = mx.float32 if keep_fp32 else cast_dtype
                assign(key, _load_array(source, target_dtype))
            del shard_arrays
        required_time_keys = {
            "time_embedder.linear_1.weight",
            "time_embedder.linear_1.bias",
            "time_embedder.linear_2.weight",
            "time_embedder.linear_2.bias",
        }
        missing_time_keys = sorted(required_time_keys - weights.keys())
        if missing_time_keys:
            raise KeyError(f"Missing time-embedder weights needed for the AdaLN cache: {missing_time_keys}")
        timestep_rows = mx.array(cache_timesteps)
        t_freq = timestep_embedding(timestep_rows, int(config["freq_dim"])).astype(
            weight_dtype(weights["time_embedder.linear_1.weight"]))
        temb = linear(t_freq, weights["time_embedder.linear_1.weight"], weights["time_embedder.linear_1.bias"])
        temb = linear(silu(temb), weights["time_embedder.linear_2.weight"], weights["time_embedder.linear_2.bias"])
        mx.eval(temb)
        cached_block_tables = [None] * num_blocks

    for shard in _safetensors_shards(transformer_path):
        shard_arrays = mx.load(str(shard))
        for key, source in shard_arrays.items():
            if _is_ignored_dense_key(key, include_vsa=include_vsa):
                continue
            if temb is not None and key.startswith("time_embedder."):
                continue
            if key.startswith("transformer_blocks."):
                index = int(key.split(".")[1])
                if index >= num_blocks:
                    continue
            if key.startswith("rope."):
                continue  # non-persistent analytic buffer, rebuilt on the fly
            keep_fp32 = key.split(".", 1)[0] in FP32_MODULE_PREFIXES
            target_dtype = mx.float32 if keep_fp32 else cast_dtype
            array = _load_array(source, target_dtype)
            if temb is not None and ".adaln_proj.linear." in key:
                _, index_str, sub = key.split(".", 2)
                index = int(index_str)
                block_pending = pending_adaln.setdefault(index, {})
                block_pending[sub] = array
                if {"adaln_proj.linear.weight", "adaln_proj.linear.bias"} <= block_pending.keys():
                    tables = _adaln_tables(block_pending, temb)
                    mx.eval(tables)
                    assert cached_block_tables is not None
                    cached_block_tables[index] = tables
                    block = blocks[index]
                    if block is None:
                        block = {}
                        blocks[index] = block
                    block["adaln_proj.linear.weight"] = None
                    block["adaln_proj.linear.bias"] = None
                    del pending_adaln[index]
                continue
            if spec is not None and _is_quantizable(key, include_vsa=include_vsa) and not keep_fp32:
                value = quantize_matrix(array, spec)
                del array
            else:
                value = array
            _eval_value(value)
            assign(key, value)
        del shard_arrays

    cache = None
    if temb is not None:
        if pending_adaln:
            raise KeyError(f"Incomplete AdaLN projection pairs for blocks {sorted(pending_adaln)}")
        assert cache_timesteps is not None and cached_block_tables is not None
        missing_cache_blocks = [index for index, tables in enumerate(cached_block_tables) if tables is None]
        if missing_cache_blocks:
            raise KeyError(f"Missing AdaLN cache tables for blocks {missing_cache_blocks}")
        shift_scale = linear(
            silu(temb).astype(weight_dtype(weights["norm_out.linear.weight"])),
            weights["norm_out.linear.weight"],
            weights["norm_out.linear.bias"],
        )
        norm_out_shift, norm_out_scale = mx.split(shift_scale, 2, axis=-1)
        mx.eval(norm_out_shift, norm_out_scale)
        cache = MiniMaxH3StepCache(
            timesteps=cache_timesteps,
            block_tables=[tables for tables in cached_block_tables if tables is not None],
            norm_out_shift=norm_out_shift,
            norm_out_scale=norm_out_scale,
        )

    if any(block is None for block in blocks):
        missing = [i for i, block in enumerate(blocks) if block is None]
        raise KeyError(f"Missing transformer block weights for indices {missing}.")
    if any(block is None for block in refiner):
        raise KeyError("Missing token refiner weights.")
    loaded_blocks = [block for block in blocks if block is not None]
    if include_vsa:
        missing_gates = [
            index for index, block in enumerate(loaded_blocks) if "attn.to_gate_compress.weight" not in block
        ]
        if missing_gates:
            raise KeyError("VSA conversion requested but gate projections are missing for transformer blocks "
                           f"{missing_gates}. The source checkpoint must contain "
                           f"`transformer_blocks.*.{VSA_GATE_KEY_SUFFIX}`.")
        logger.info("Retained %d VSA gate projections (%s).", len(loaded_blocks), VSA_GATE_KEY_SUFFIX)
    dit = MLXMiniMaxH3DiT(
        weights,
        loaded_blocks,
        [block for block in refiner if block is not None],
        config,
    )
    dit._adaln_cache = cache
    return dit

fastvideo.mlx_runtime.minimax_h3.patchify_video_latents

patchify_video_latents(latents: ndarray, patch_size: tuple[int, int, int]) -> ndarray

(B, C, T, H, W) -> (BT'H'W', Cptphpw), channel-major patch features.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def patchify_video_latents(latents: np.ndarray, patch_size: tuple[int, int, int]) -> np.ndarray:
    """(B, C, T, H, W) -> (B*T'*H'*W', C*pt*ph*pw), channel-major patch features."""
    patch_t, patch_h, patch_w = patch_size
    batch_size, channels, num_frames, height, width = latents.shape
    if num_frames % patch_t or height % patch_h or width % patch_w:
        raise ValueError(f"Latents of shape {latents.shape} are not divisible by the patch {patch_size}.")
    latents = latents.reshape(
        batch_size,
        channels,
        num_frames // patch_t,
        patch_t,
        height // patch_h,
        patch_h,
        width // patch_w,
        patch_w,
    )
    latents = latents.transpose(0, 2, 4, 6, 1, 3, 5, 7)
    return np.ascontiguousarray(latents.reshape(-1, channels * patch_t * patch_h * patch_w))

fastvideo.mlx_runtime.minimax_h3.rope_cos_sin

rope_cos_sin(position_ids, rope_freq_dim: int, rope_theta: float)

(S, 3) positions -> (cos, sin) each (S, 6*rope_freq_dim), fp32.

Shared 16-freq inv_freq across the three axes; the (t, h, w) blocks are concatenated and doubled, so the rotary width is 6 * rope_freq_dim (96 of the 128 head dims at full size).

Source code in fastvideo/mlx_runtime/minimax_h3.py
def rope_cos_sin(position_ids, rope_freq_dim: int, rope_theta: float):
    """(S, 3) positions -> (cos, sin) each (S, 6*rope_freq_dim), fp32.

    Shared 16-freq inv_freq across the three axes; the (t, h, w) blocks are
    concatenated and doubled, so the rotary width is 6 * rope_freq_dim (96 of
    the 128 head dims at full size).
    """
    import mlx.core as mx

    positions = position_ids.astype(mx.float32)
    inv_freq = 1.0 / (rope_theta**(mx.arange(0, 2 * rope_freq_dim, 2, dtype=mx.float32) / (2 * rope_freq_dim)))
    freqs = positions[:, :, None] * inv_freq[None, None, :]  # (S, 3, F)
    freqs_t, freqs_h, freqs_w = freqs[:, 0], freqs[:, 1], freqs[:, 2]
    freqs = mx.concatenate([freqs_t, freqs_h, freqs_w], axis=-1)
    freqs = mx.concatenate([freqs, freqs], axis=-1)
    return mx.cos(freqs), mx.sin(freqs)

fastvideo.mlx_runtime.minimax_h3.save_mlx_h3_checkpoint

save_mlx_h3_checkpoint(dit: MLXMiniMaxH3DiT, checkpoint_dir: str | Path) -> Path

Persist a (possibly quantized, possibly AdaLN-dropped) H3 DiT.

Source code in fastvideo/mlx_runtime/minimax_h3.py
def save_mlx_h3_checkpoint(dit: MLXMiniMaxH3DiT, checkpoint_dir: str | Path) -> Path:
    """Persist a (possibly quantized, possibly AdaLN-dropped) H3 DiT."""
    import mlx.core as mx

    checkpoint_dir = Path(checkpoint_dir)
    checkpoint_dir.mkdir(parents=True, exist_ok=True)

    arrays: dict[str, Any] = {}
    quantized: dict[str, dict[str, Any]] = {}
    spec: MLXQuantizationSpec | None = None
    for key, value in _flatten_h3_weights(dit).items():
        if isinstance(value, QuantizedMatrix):
            if spec is not None and value.spec != spec:
                raise ValueError(f"Mixed quantization specs in one checkpoint ({spec} vs {value.spec} at '{key}').")
            spec = value.spec
            arrays[key] = value.weight
            arrays[f"{key}.scales"] = value.scales
            if value.biases is not None:
                arrays[f"{key}.biases"] = value.biases
            quantized[key] = {
                "dequantized_dtype": _dtype_name(value.dequantized_dtype),
                "has_biases": value.biases is not None,
            }
        else:
            arrays[key] = value

    cache_manifest = None
    if dit._adaln_cache is not None:
        cache = dit._adaln_cache
        cache_manifest = {
            "timesteps": cache.timesteps.tolist(),
            "num_blocks": len(cache.block_tables),
            "tables_per_block": 6,
        }
        for block_index, tables in enumerate(cache.block_tables):
            for table_index, table in enumerate(tables):
                arrays[f"__adaln_cache.block.{block_index}.table.{table_index}"] = table
        arrays["__adaln_cache.norm_out_shift"] = cache.norm_out_shift
        arrays["__adaln_cache.norm_out_scale"] = cache.norm_out_scale

    manifest = {
        "format_version": H3_FORMAT_VERSION,
        "config": dit.config,
        "num_blocks": len(dit.blocks),
        "num_refiner_blocks": len(dit.refiner),
        "quantization": None if spec is None else {
            "mode": spec.mode,
            "bits": spec.bits,
            "group_size": spec.group_size,
        },
        "quantized_keys": quantized,
        "adaln_cache": cache_manifest,
        "vsa": {
            "capable":
            bool(dit.vsa_capable),
            "num_gate_matrices":
            sum(1 for block in dit.blocks if "attn.to_gate_compress.weight" in block),
            "gate_key_suffix":
            VSA_GATE_KEY_SUFFIX,
            "attention_activations":
            "bf16",
            "note": ("Gate projections are quantized with the checkpoint's affine INT8/INT6/INT4 "
                     "weight-only grid. VSA attention Q/K/V remain unquantized activations."),
        },
    }
    weights_path = checkpoint_dir / H3_WEIGHTS_FILENAME
    mx.save_safetensors(str(weights_path), arrays)
    (checkpoint_dir / H3_MANIFEST_FILENAME).write_text(json.dumps(manifest, indent=2))
    logger.info("Saved MLX H3 DiT checkpoint (%d arrays, quantization=%s) to %s", len(arrays),
                spec.label if spec else "none", checkpoint_dir)
    return checkpoint_dir

fastvideo.mlx_runtime.minimax_h3.unpack_audio_tokens

unpack_audio_tokens(rows: ndarray, num_audio_latents: int) -> ndarray

(2*num_audio_latents, feat) channel-major rows -> (2, feat, num_audio_latents).

Source code in fastvideo/mlx_runtime/minimax_h3.py
def unpack_audio_tokens(rows: np.ndarray, num_audio_latents: int) -> np.ndarray:
    """(2*num_audio_latents, feat) channel-major rows -> (2, feat, num_audio_latents)."""
    rows = rows.reshape(MINIMAX_H3_AUDIO_CHANNELS, num_audio_latents, rows.shape[-1])
    return np.ascontiguousarray(rows.transpose(0, 2, 1))