# SPDX-License-Identifier: Apache-2.0 """MiniMax H3 Qwen presentation building. Builds the positive presentation token stream: - fl2va: ': ' label + vision block (<|vision_start|> + N*<|image_pad|> + <|vision_end|>) + prompt text. - t2va: prompt text only (no vision block). Prompt text passes through verbatim (no stripping or rewriting). All presentation variants are emitted through the shared ``_Presentation`` accumulator so ids and AdaLN token tags cannot drift apart. """ from __future__ import annotations from collections.abc import Sequence from typing import Any import torch VISION_START = "<|vision_start|>" VISION_END = "<|vision_end|>" IMAGE_PAD = "<|image_pad|>" VIDEO_PAD = "<|video_pad|>" _TEXT_TAG = 1 _VIDEO_TAG = 0 def _text_ids(tokenizer: Any, text: str) -> list[int]: return list(tokenizer(text, add_special_tokens=False)["input_ids"]) def _vision_block_ids(tokenizer: Any, pad_token: str, count: int) -> list[int]: return ( [tokenizer.convert_tokens_to_ids(VISION_START)] + [tokenizer.convert_tokens_to_ids(pad_token)] * int(count) + [tokenizer.convert_tokens_to_ids(VISION_END)] ) class _Presentation: """Accumulates aligned (ids, token_tags) presentation segments.""" def __init__(self) -> None: self.ids: list[int] = [] self.tags: list[int] = [] def text(self, token_ids: list[int]) -> None: self.ids += token_ids self.tags += [_TEXT_TAG] * len(token_ids) def vision(self, token_ids: list[int]) -> None: self.ids += token_ids self.tags += [_VIDEO_TAG] * len(token_ids) def build(self) -> tuple[torch.Tensor, torch.Tensor]: return ( torch.tensor(self.ids, dtype=torch.long), torch.tensor(self.tags, dtype=torch.long), ) def _timestamped_video_blocks( presentation: _Presentation, tokenizer: Any, *, counts: Sequence[int], timestamps: Sequence[float], context: str, ) -> None: """Emit per-temporal-block ``<{t:.1f} seconds>`` text + VIDEO vision.""" counts = [int(value) for value in counts] timestamps = [float(value) for value in timestamps] if not counts or len(counts) != len(timestamps): raise ValueError(f"{context}video block token counts and timestamps must align") for count, timestamp in zip(counts, timestamps): if count <= 0: raise ValueError(f"{context}video block token count must be positive") presentation.text(_text_ids(tokenizer, f"<{timestamp:.1f} seconds>")) presentation.vision(_vision_block_ids(tokenizer, VIDEO_PAD, count)) def minimax_h3_text_only_ids(tokenizer: Any, prompt: str) -> torch.Tensor: """t2va presentation: verbatim prompt, no special tokens.""" if not prompt: raise ValueError("prompt must be non-empty") return torch.tensor(_text_ids(tokenizer, prompt), dtype=torch.long) def _multi_image_presentation( tokenizer: Any, *, prompt: str, image_token_counts: list[int], ) -> tuple[torch.Tensor, torch.Tensor]: if not image_token_counts: raise ValueError("image_token_counts must be non-empty") presentation = _Presentation() for index, count in enumerate(image_token_counts, start=1): if int(count) <= 0: raise ValueError("image_token_count must be positive") presentation.text(_text_ids(tokenizer, f": ")) presentation.vision(_vision_block_ids(tokenizer, IMAGE_PAD, count)) presentation.text(_text_ids(tokenizer, prompt)) return presentation.build() def minimax_h3_multi_image_presentation_ids( tokenizer: Any, *, prompt: str, image_token_counts: list[int], ) -> torch.Tensor: """Positive fl2va presentation with one or more labeled image slots.""" ids, _ = _multi_image_presentation(tokenizer, prompt=prompt, image_token_counts=image_token_counts) return ids def minimax_h3_multi_image_presentation_token_tags( tokenizer: Any, *, prompt: str, image_token_counts: list[int], ) -> torch.Tensor: """AdaLN token tags aligned with minimax_h3_multi_image_presentation_ids.""" _, tags = _multi_image_presentation(tokenizer, prompt=prompt, image_token_counts=image_token_counts) return tags def minimax_h3_ref2va_presentation( tokenizer: Any, *, prompt: str, condition_labels: list[tuple[str, int]], image_token_count: int | list[int] | None, ) -> tuple[torch.Tensor, torch.Tensor]: """ref2va positive presentation: per condition in request order — image i: ``: `` label followed by the vision block; audio j: ``