Skip to content

video_sparse_attn_h3

VSA for MiniMax H3's packed mixed-modality self-attention.

H3 runs one joint bidirectional attention over [text | condition keyframes | audio | generated video], so this backend differs from the Wan-tuned video_sparse_attn:

  • Tiles are [segment-pure prefix chunks] + [3D video tiles]; prefix tiles never straddle segment boundaries. The tile size is selectable at metadata build time: 256 tokens (4,8,8) (default) or 64 tokens (4,4,4) (see VSA_H3_TILE_SHAPES).
  • Selection is pure Python on pooled tile scores; the block-sparse kernel consumes an explicit bool mask, so no kernel changes are needed.
  • The compression branch is gated by to_gate_compress, which the base H3 checkpoint does not carry: the loader zero-initializes it, so untrained inference is exactly pure sparse and finetuning can learn the gate. VSA-distilled students (e.g. FastVideo-Minimax-H3-Preview) ship trained gates, which load and activate the branch.
  • Non-video queries are always dense. Non-video keys are either always-selected for every query ("exempt", default) or compete in top-k under a FLOP-matched budget ("compete") — the ablation axis, switched per request via generate_video(..., vsa_mode=...) (default: exempt). Per-request scheduling knobs (vsa_dense_first_n_steps, vsa_dense_layers) let mixed schedules run the diffuse steps/layers dense while pushing the rest harder.

At tile 256 this targets sm10.x through the FA4 CuTe 256-tile path (FASTVIDEO_VSA_CUTEDSL=1); the Triton 256→64 expansion is the fallback and keeps identical mask semantics. At tile 64 the block map is already at the kernels' native 64-token granularity, so both forward and backward run the Triton block-sparse kernels directly (no expansion, FASTVIDEO_VSA_CUTEDSL does not apply). A third, opt-in route exists for the tile-64 FORWARD only: FASTVIDEO_VSA_SM100A=1 sends no-grad forwards through the sm_100a CUDA block-sparse kernel (fastvideo_kernel.block_sparse_attn_sm100a, upstream PR #1719 plus our per-q-tile q2k_num fix) when the extension is built, the device is sm_100, and the geometry qualifies. The CUDA kernel assigns adjacent pairs of query tiles to CTAs, so an odd logical tile count receives one internal, zero-valid partner tile for the no-grad call only. Score search, the trained mask, gate-compress, and the returned packed sequence remain on the original logical tiles. Grad-tracking forwards and every backward stay on Triton unchanged. If the env is set but a precondition fails, the route logs one warning and falls back.

Classes

fastvideo.attention.backends.video_sparse_attn_h3.MiniMaxH3VSAImpl

MiniMaxH3VSAImpl(num_heads: int, head_size: int, causal: bool, softmax_scale: float, num_kv_heads: int | None = None, prefix: str = '', **extra_impl_args)

Bases: AttentionImpl

Source code in fastvideo/attention/backends/video_sparse_attn_h3.py
def __init__(
    self,
    num_heads: int,
    head_size: int,
    causal: bool,
    softmax_scale: float,
    num_kv_heads: int | None = None,
    prefix: str = "",
    **extra_impl_args,
) -> None:
    self.prefix = prefix
    self.layer_idx = layer_idx_from_prefix(prefix, default=-1)
    self.head_size = head_size
    # None means the regional-compile preparation hook has not run.  The
    # eager path deliberately ignores this cache and preserves its
    # request-time env/probe/fallback behavior; only Dynamo capture reads
    # the prepared, static route.
    self._regional_compile_sm100a_enabled: bool | None = None
    self._regional_compile_layer_idx: torch.Tensor | None = None

Methods:

fastvideo.attention.backends.video_sparse_attn_h3.MiniMaxH3VSAImpl.prepare_for_regional_compile
prepare_for_regional_compile(device: device) -> str | None

Resolve the inference-only sm_100a route before fullgraph capture.

The ordinary eager route probes the environment, extension, device, and tensor contract at every call so it can warn and fall back. Those Python/device-capability checks are not safe inside a regional fullgraph=True block. Probe one representative tile-64 input on the loaded model's device now, then let forward specialize on the resulting plain bool while Dynamo is compiling.

Source code in fastvideo/attention/backends/video_sparse_attn_h3.py
def prepare_for_regional_compile(self, device: torch.device) -> str | None:
    """Resolve the inference-only sm_100a route before fullgraph capture.

    The ordinary eager route probes the environment, extension, device,
    and tensor contract at every call so it can warn and fall back.  Those
    Python/device-capability checks are not safe inside a regional
    ``fullgraph=True`` block.  Probe one representative tile-64 input on
    the loaded model's device now, then let ``forward`` specialize on the
    resulting plain bool while Dynamo is compiling.
    """
    requested = os.environ.get(VSA_SM100A_ENV, "0") == "1"
    enabled = False
    reason = None if requested else f"{VSA_SM100A_ENV}=1 is required for compile-safe VSA-H3 attention"
    if requested:
        if _sm100a is None:
            reason = "fastvideo_kernel.block_sparse_attn_sm100a is not installed"
        elif not _sm100a_has_compile_safe_mask_route(_sm100a):
            reason = ("neither a native block_sparse_attn_sm100a_from_mask entry nor the raw sm100a "
                      "kernel plus map_to_index compatibility route is installed")
        else:
            # Two 64-token blocks exercise the exact sm_100a inference
            # specialization while keeping the one-time probe tiny.  The
            # kernel predicate checks extension presence, CUDA capability,
            # dtype/layout, head size, block size, and even block count
            # without reading metadata tensor contents.
            probe_query = torch.empty((1, 1, 128, self.head_size), device=device, dtype=torch.bfloat16)
            probe_block_sizes = torch.full((2, ), 64, device=device, dtype=torch.int32)
            reason = _sm100a_unavailable_reason(
                _sm100a,
                probe_query,
                probe_block_sizes,
                grad_mode=False,
            )
            enabled = reason is None

    self._regional_compile_sm100a_enabled = enabled
    # Keep this marker unset when preparation fails. Generic/training
    # torch.compile must retain the established Triton attention route.
    self._regional_compile_layer_idx = (torch.tensor(self.layer_idx, device=device, dtype=torch.int64)
                                        if enabled else None)
    if enabled:
        route = ("native fastvideo-kernel mask entry" if callable(
            getattr(_sm100a, "block_sparse_attn_sm100a_from_mask", None)) else
                 "FastVideo compatibility mask adapter")
        logger.info_once(f"VSA-H3 regional compile mask route: {route}")
    if requested and reason is not None:
        logger.warning_once(f"VSA-H3 regional compile is unavailable and will stay eager: {reason}")
    return reason
fastvideo.attention.backends.video_sparse_attn_h3.MiniMaxH3VSAImpl.tile
tile(x: Tensor, attn_metadata: MiniMaxH3VSAMetadata) -> Tensor

Scatter rows into the padded tile buffer (pad positions stay zero).

The returned tensor aliases the builder-owned buffer; callers must consume it before the next tile() (both call sites in forward() read it immediately). Odd tile-64 no-grad sm100a requests carry one additional all-zero tile internally; metadata and all observable outputs retain the logical geometry.

Source code in fastvideo/attention/backends/video_sparse_attn_h3.py
def tile(self, x: torch.Tensor, attn_metadata: MiniMaxH3VSAMetadata) -> torch.Tensor:
    """Scatter rows into the padded tile buffer (pad positions stay zero).

    The returned tensor aliases the builder-owned buffer; callers must
    consume it before the next ``tile()`` (both call sites in
    ``forward()`` read it immediately). Odd tile-64 no-grad sm100a
    requests carry one additional all-zero tile internally; metadata and
    all observable outputs retain the logical geometry.
    """
    if x.shape[1] != attn_metadata.total_seq_length:
        raise ValueError(f"VSA-H3 metadata was built for sequence length {attn_metadata.total_seq_length}, "
                         f"got {x.shape[1]}. A non-packed sequence (e.g. the token refiner) is "
                         "routed to the VSA-H3 backend; exclude it from the supported backends.")
    n_tiles = attn_metadata.variable_block_sizes.numel()
    grad_mode = torch.is_grad_enabled() and x.requires_grad
    compiling = torch.compiler.is_compiling()
    regional_compiling = compiling and self._regional_compile_layer_idx is not None
    if regional_compiling:
        sm100a_requested = bool(self._regional_compile_sm100a_enabled)
    elif compiling:
        # Training/generic compile keeps the long-standing Triton route.
        sm100a_requested = False
    else:
        sm100a_requested = os.environ.get(VSA_SM100A_ENV, "0") == "1"
    needs_sm100a_pair = (attn_metadata.tile_elems == 64 and n_tiles % 2 != 0 and not grad_mode and sm100a_requested)
    kernel_tiles = n_tiles + int(needs_sm100a_pair)
    target_shape = (x.shape[0], kernel_tiles * attn_metadata.tile_elems, x.shape[-2], x.shape[-1])

    # ``untile_combined_index`` maps each packed row to a logical tile
    # slot. Different geometries can share one transport shape; clear a
    # reused allocation once when the mapping identity changes so no old
    # valid row can survive as padding.
    holder = attn_metadata.tile_buf_holder
    if holder is None:
        raise RuntimeError("VSA-H3 metadata has no builder-owned tile buffer holder")
    buffer_matches = (holder.buffer is not None and holder.buffer.shape == target_shape
                      and holder.buffer.dtype == x.dtype and holder.buffer.device == x.device)
    if buffer_matches and holder.untile_geometry is not attn_metadata.untile_combined_index:
        holder.buffer.zero_()
    holder.buffer = scatter_into_tile_buf(x, target_shape, attn_metadata.untile_combined_index, holder.buffer)
    holder.untile_geometry = attn_metadata.untile_combined_index
    if needs_sm100a_pair:
        # A prior even geometry can reuse this allocation and may have
        # written the last tile as logical data.
        holder.buffer[:, n_tiles * attn_metadata.tile_elems:].zero_()
    return holder.buffer

Functions:

fastvideo.attention.backends.video_sparse_attn_h3.token_tile_and_valid

token_tile_and_valid(variable_block_sizes: Tensor, tile_elems: int = _TILE_ELEMS) -> tuple[Tensor, Tensor]

Per padded-token tile id and pad-validity mask.

The single encoding of the padding contract, shared by the probe and the test oracle so they cannot drift from the backend's tile geometry. tile_elems must match the metadata the sizes came from (MiniMaxH3VSAMetadata.tile_elems).

Source code in fastvideo/attention/backends/video_sparse_attn_h3.py
def token_tile_and_valid(variable_block_sizes: torch.Tensor,
                         tile_elems: int = _TILE_ELEMS) -> tuple[torch.Tensor, torch.Tensor]:
    """Per padded-token tile id and pad-validity mask.

    The single encoding of the padding contract, shared by the probe and the
    test oracle so they cannot drift from the backend's tile geometry.
    ``tile_elems`` must match the metadata the sizes came from
    (``MiniMaxH3VSAMetadata.tile_elems``).
    """
    device = variable_block_sizes.device
    token_tile = torch.arange(variable_block_sizes.numel(), device=device).repeat_interleave(tile_elems)
    token_valid = (torch.arange(tile_elems, device=device)[None, :] < variable_block_sizes[:, None]).reshape(-1)
    return token_tile, token_valid