From 97a1477b0afbc8f116b3ec9c036738207f31eaa6 Mon Sep 17 00:00:00 2001 From: apolinario Date: Sat, 1 Aug 2026 23:10:53 +0000 Subject: [PATCH 01/21] Add MiniMax-H3 --- docs/source/en/_toctree.yml | 10 + .../en/api/models/autoencoderkl_minimax_h3.md | 38 + .../models/autoencoderkl_minimax_h3_audio.md | 36 + .../en/api/models/minimax_h3_transformer3d.md | 41 + docs/source/en/api/pipelines/minimax_h3.md | 157 +++ docs/source/en/api/schedulers/minimax_h3.md | 23 + scripts/convert_minimax_h3_to_diffusers.py | 956 ++++++++++++++++++ src/diffusers/__init__.py | 20 + src/diffusers/models/__init__.py | 6 + src/diffusers/models/autoencoders/__init__.py | 2 + .../autoencoders/autoencoder_kl_minimax_h3.py | 905 +++++++++++++++++ .../autoencoder_kl_minimax_h3_audio.py | 675 +++++++++++++ src/diffusers/models/transformers/__init__.py | 1 + .../transformers/transformer_minimax_h3.py | 644 ++++++++++++ src/diffusers/modular_pipelines/__init__.py | 12 + .../modular_pipelines/minimax_h3/__init__.py | 47 + .../minimax_h3/before_denoise.py | 426 ++++++++ .../minimax_h3/before_encoder.py | 409 ++++++++ .../modular_pipelines/minimax_h3/decoders.py | 198 ++++ .../modular_pipelines/minimax_h3/denoise.py | 325 ++++++ .../modular_pipelines/minimax_h3/encoders.py | 626 ++++++++++++ .../minimax_h3/modular_blocks_minimax_h3.py | 339 +++++++ .../minimax_h3/modular_pipeline.py | 108 ++ .../modular_pipelines/modular_pipeline.py | 2 + src/diffusers/pipelines/__init__.py | 2 + .../pipelines/minimax_h3/__init__.py | 54 + src/diffusers/pipelines/minimax_h3/packing.py | 538 ++++++++++ .../pipelines/minimax_h3/packing_ref2va.py | 841 +++++++++++++++ .../minimax_h3/pipeline_minimax_h3.py | 663 ++++++++++++ .../minimax_h3/pipeline_minimax_h3_ref2va.py | 903 +++++++++++++++++ .../pipelines/minimax_h3/pipeline_output.py | 26 + src/diffusers/schedulers/__init__.py | 2 + .../schedulers/scheduling_minimax_h3.py | 288 ++++++ src/diffusers/utils/dummy_pt_objects.py | 60 ++ .../dummy_torch_and_transformers_objects.py | 90 ++ .../test_models_autoencoder_kl_minimax_h3.py | 137 +++ ..._models_autoencoder_kl_minimax_h3_audio.py | 132 +++ .../test_models_transformer_minimax_h3.py | 239 +++++ .../modular_pipelines/minimax_h3/__init__.py | 0 .../test_modular_pipeline_minimax_h3.py | 272 +++++ tests/pipelines/minimax_h3/__init__.py | 0 tests/pipelines/minimax_h3/test_minimax_h3.py | 397 ++++++++ .../minimax_h3/test_minimax_h3_ref2va.py | 760 ++++++++++++++ 43 files changed, 11410 insertions(+) create mode 100644 docs/source/en/api/models/autoencoderkl_minimax_h3.md create mode 100644 docs/source/en/api/models/autoencoderkl_minimax_h3_audio.md create mode 100644 docs/source/en/api/models/minimax_h3_transformer3d.md create mode 100644 docs/source/en/api/pipelines/minimax_h3.md create mode 100644 docs/source/en/api/schedulers/minimax_h3.md create mode 100644 scripts/convert_minimax_h3_to_diffusers.py create mode 100644 src/diffusers/models/autoencoders/autoencoder_kl_minimax_h3.py create mode 100644 src/diffusers/models/autoencoders/autoencoder_kl_minimax_h3_audio.py create mode 100644 src/diffusers/models/transformers/transformer_minimax_h3.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/__init__.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/before_denoise.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/before_encoder.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/decoders.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/denoise.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/encoders.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/modular_blocks_minimax_h3.py create mode 100644 src/diffusers/modular_pipelines/minimax_h3/modular_pipeline.py create mode 100644 src/diffusers/pipelines/minimax_h3/__init__.py create mode 100644 src/diffusers/pipelines/minimax_h3/packing.py create mode 100644 src/diffusers/pipelines/minimax_h3/packing_ref2va.py create mode 100644 src/diffusers/pipelines/minimax_h3/pipeline_minimax_h3.py create mode 100644 src/diffusers/pipelines/minimax_h3/pipeline_minimax_h3_ref2va.py create mode 100644 src/diffusers/pipelines/minimax_h3/pipeline_output.py create mode 100644 src/diffusers/schedulers/scheduling_minimax_h3.py create mode 100644 tests/models/autoencoders/test_models_autoencoder_kl_minimax_h3.py create mode 100644 tests/models/autoencoders/test_models_autoencoder_kl_minimax_h3_audio.py create mode 100644 tests/models/transformers/test_models_transformer_minimax_h3.py create mode 100644 tests/modular_pipelines/minimax_h3/__init__.py create mode 100644 tests/modular_pipelines/minimax_h3/test_modular_pipeline_minimax_h3.py create mode 100644 tests/pipelines/minimax_h3/__init__.py create mode 100644 tests/pipelines/minimax_h3/test_minimax_h3.py create mode 100644 tests/pipelines/minimax_h3/test_minimax_h3_ref2va.py diff --git a/docs/source/en/_toctree.yml b/docs/source/en/_toctree.yml index 8466cebe2a8c..2d0bf5707ad9 100644 --- a/docs/source/en/_toctree.yml +++ b/docs/source/en/_toctree.yml @@ -375,6 +375,8 @@ title: Lumina2Transformer2DModel - local: api/models/lumina_nextdit2d title: LuminaNextDiT2DModel + - local: api/models/minimax_h3_transformer3d + title: MiniMaxH3Transformer3DModel - local: api/models/mochi_transformer3d title: MochiTransformer3DModel - local: api/models/motif_video_transformer_3d @@ -459,6 +461,10 @@ title: AutoencoderKLLTXVideo - local: api/models/autoencoderkl_magvit title: AutoencoderKLMagvit + - local: api/models/autoencoderkl_minimax_h3 + title: AutoencoderKLMiniMaxH3 + - local: api/models/autoencoderkl_minimax_h3_audio + title: AutoencoderKLMiniMaxH3Audio - local: api/models/autoencoderkl_mochi title: AutoencoderKLMochi - local: api/models/autoencoderkl_qwenimage @@ -691,6 +697,8 @@ title: LTX-2 - local: api/pipelines/ltx_video title: LTXVideo + - local: api/pipelines/minimax_h3 + title: MiniMax-H3 - local: api/pipelines/mochi title: Mochi - local: api/pipelines/motif_video @@ -770,6 +778,8 @@ title: LCMScheduler - local: api/schedulers/lms_discrete title: LMSDiscreteScheduler + - local: api/schedulers/minimax_h3 + title: MiniMaxH3Scheduler - local: api/schedulers/pndm title: PNDMScheduler - local: api/schedulers/repaint diff --git a/docs/source/en/api/models/autoencoderkl_minimax_h3.md b/docs/source/en/api/models/autoencoderkl_minimax_h3.md new file mode 100644 index 000000000000..2fc7f4a07a69 --- /dev/null +++ b/docs/source/en/api/models/autoencoderkl_minimax_h3.md @@ -0,0 +1,38 @@ + + +# AutoencoderKLMiniMaxH3 + +The video variational autoencoder (VAE) model with KL loss used in [MiniMax-H3](https://huggingface.co/MiniMaxAI) by MiniMax. It pairs a causal 3D CNN encoder with a non-causal ViT decoder and compresses 16x spatially and 4x temporally. + +Three things set it apart from most autoencoders in the library: + +- **Latents are normalized per channel.** There is no `scaling_factor`: a pipeline encodes with `(latent - latents_mean) / latents_std` and decodes with `latent * latents_std + latents_mean`. +- **The pixel convention is ImageNet-normalized RGB over a `[0, 1]` base range**, not the usual `[-1, 1]`. `encode` expects `(pixel - imagenet_mean) / imagenet_std` and `decode` returns values in that same space, so a pipeline applies `sample * imagenet_std + imagenet_mean` and clamps to `[0, 1]` before postprocessing. +- **Spatial tiling is on by default.** MiniMax-H3 was released with tiling enabled for both encoding and decoding and the released frames are the blended-tile ones, so turning it off changes the output. Use `enable_tiling` to change the tile geometry and `disable_tiling` to switch it off. + +The temporal geometry is fixed by `clip_length` (17 pixel frames per encoder chunk) and `token_drop` (3 trailing latent frames dropped per encode), so `17 * n + 5` pixel frames map to `5 * n + 2` latent frames. + +```python +import torch +from diffusers import AutoencoderKLMiniMaxH3 + +vae = AutoencoderKLMiniMaxH3.from_pretrained( + "MiniMaxAI/MiniMax-H3", subfolder="vae", dtype=torch.float32 +).to("cuda") +``` + +## AutoencoderKLMiniMaxH3 + +[[autodoc]] AutoencoderKLMiniMaxH3 + - encode + - decode + - all diff --git a/docs/source/en/api/models/autoencoderkl_minimax_h3_audio.md b/docs/source/en/api/models/autoencoderkl_minimax_h3_audio.md new file mode 100644 index 000000000000..ab78da3f5e32 --- /dev/null +++ b/docs/source/en/api/models/autoencoderkl_minimax_h3_audio.md @@ -0,0 +1,36 @@ + + +# AutoencoderKLMiniMaxH3Audio + +The audio autoencoder used in [MiniMax-H3](https://huggingface.co/MiniMaxAI) by MiniMax. It is waveform in and waveform out, with no mel front-end and no separate vocoder: a DAC-lineage strided convolutional encoder, a causal-attention projection onto the diffusion latent width, and a BigVGAN decoder. + +The encoder hops 800 samples at 32 kHz, i.e. 40 latents per second, so a waveform of `800 * n` samples encodes to `n` latents. Waveforms that are not a whole number of hops are right-padded. + +The causal-attention projection goes through the attention dispatcher, so `set_attention_backend` applies to it; its mask is `is_causal=True`, which every backend honours except `_native_npu`, whose kernel takes no causal flag. + +The autoencoder is **mono**, and it normalizes latents per channel with `latents_mean` / `latents_std` rather than a scalar `scaling_factor`. MiniMax-H3 carries stereo as two *batch* items, and it always consumes the posterior mean (`latent_dist.mode()`), never a sample. + +```python +import torch +from diffusers import AutoencoderKLMiniMaxH3Audio + +audio_vae = AutoencoderKLMiniMaxH3Audio.from_pretrained( + "MiniMaxAI/MiniMax-H3", subfolder="audio_vae", dtype=torch.float32 +).to("cuda") +``` + +## AutoencoderKLMiniMaxH3Audio + +[[autodoc]] AutoencoderKLMiniMaxH3Audio + - encode + - decode + - all diff --git a/docs/source/en/api/models/minimax_h3_transformer3d.md b/docs/source/en/api/models/minimax_h3_transformer3d.md new file mode 100644 index 000000000000..6f867f1fb6c9 --- /dev/null +++ b/docs/source/en/api/models/minimax_h3_transformer3d.md @@ -0,0 +1,41 @@ + + +# MiniMaxH3Transformer3DModel + +A Diffusion Transformer model for joint video and audio generation, introduced in [MiniMax-H3](https://huggingface.co/MiniMaxAI) by MiniMax. + +MiniMax-H3 runs a single stack of blocks over **one packed 1-D sequence** that holds the text conditioning, the conditioning image and video rows, the audio rows and the target video rows at once. Attention is full self-attention over that sequence, so there is no cross-attention and no per-modality block weights. Modality-specific behaviour comes only from the two input patch projections, the per-row modality tag that selects the AdaLN modulation parameters, and the two output heads. + +Building the packed layout is the caller's job, which is why the forward signature takes the layout apart from the latents: the `(t, h, w)` position grid, the per-row modality tags, the per-row timestep indices and the three index tensors that address the video, audio and text rows. [`MiniMaxH3Pipeline`] and [`MiniMaxH3Ref2VAPipeline`] build all of it. + +A layout that carries padding rows (tag `-1`) needs a masked attention backend, since those rows are kept in their own attention document by a boolean mask; a padless sequence needs no mask and keeps every backend available. + +One repository holds both released checkpoint partitions, so the subfolder is what selects the task: `transformer/` for the text and keyframe tasks, `transformer_ref/` for the omni-reference task. + +```python +import torch +from diffusers import MiniMaxH3Transformer3DModel + +transformer = MiniMaxH3Transformer3DModel.from_pretrained( + "MiniMaxAI/MiniMax-H3", subfolder="transformer", dtype=torch.bfloat16 +).to("cuda") +``` + +The checkpoint is mixed precision: the two input patch projections, the timestep MLP and the two output heads are float32 while the block stack is bfloat16. `from_pretrained` keeps that layout through `_keep_in_fp32_modules`, so pass `dtype=torch.bfloat16` and let it place the float32 modules rather than casting the model with `.to(torch.bfloat16)` afterwards. + +## MiniMaxH3Transformer3DModel + +[[autodoc]] MiniMaxH3Transformer3DModel + +## MiniMaxH3TransformerOutput + +[[autodoc]] models.transformers.transformer_minimax_h3.MiniMaxH3TransformerOutput diff --git a/docs/source/en/api/pipelines/minimax_h3.md b/docs/source/en/api/pipelines/minimax_h3.md new file mode 100644 index 000000000000..5de61e3c428d --- /dev/null +++ b/docs/source/en/api/pipelines/minimax_h3.md @@ -0,0 +1,157 @@ + + +# MiniMax-H3 + +MiniMax-H3 generates video and its soundtrack **jointly**. One transformer denoises a single packed sequence that holds the text conditioning, the conditioning image, video and audio rows, the target audio rows and the target video rows at once, with full self-attention over all of it. There is no separate vocoder and no audio post-hoc pass: video and audio come out of the same denoising loop. + +You can find the original MiniMax-H3 checkpoints under the [MiniMaxAI](https://huggingface.co/MiniMaxAI) organization. + +## Checkpoint layout + +MiniMax-H3 was released as two checkpoint partitions that share every component except the transformer, so the diffusers conversion puts both in **one repository**: + +| Subfolder | Pipeline | Tasks | +|---|---|---| +| `transformer/` | [`MiniMaxH3Pipeline`] | `t2va` (text only) and `fl2va` (first and/or last keyframe) | +| `transformer_ref/` | [`MiniMaxH3Ref2VAPipeline`] | `ref2va` (an ordered mix of image, video and audio references) | + +Each pipeline declares only its own transformer, so `from_pretrained` on the same repository id downloads and loads only the partition that pipeline needs. Everything else, the video VAE, the audio VAE, the Qwen3-VL conditioner, its tokenizer and processor, and the two schedulers, is shared. + +The conditioner is a `Qwen3VLForConditionalGeneration`, and MiniMax-H3 reads the *unnormalized* hidden state after its 50th decoder layer rather than the last one, so the full released checkpoint is used with its language-model head unused. + +## Two schedulers + +Video and audio latents step down two different schedules inside a single transformer call per step, which is why both pipelines register two [`MiniMaxH3Scheduler`] instances: `scheduler` for the video latents (`shift=12.0` in the released checkpoints) and `audio_scheduler` for the audio latents (`shift=3.0`). + +The checkpoint is guidance-distilled: there is no `guidance_scale` and no negative prompt, and every step runs exactly one forward pass. + +## Generation constraints + +- **24 fps, 5 to 15 seconds.** `num_frames` is snapped up to the next `17 * n + 5` the video VAE can decode, and the resulting duration has to stay in that window. +- **A 768 pixel short edge.** `height` and `width` default to MiniMax-H3's own canvas for the aspect ratio of the first keyframe (or 16:9 without one) and must be multiples of 32. +- **One generator, three draws.** A request draws the keyframe or reference conditioning noise first, then the video noise, then the audio noise, all from the `generator` it is passed, so two runs from the same generator state return the same video and soundtrack. Passing `latents` or `audio_latents` replaces the corresponding draw. +- **`num_inference_steps` counts sigma grid points**, the terminal `0` included, so it drives one model evaluation less. + +## Text and keyframes + +[`MiniMaxH3Pipeline`] covers text-to-video-and-audio and keyframe conditioning. A keyframe can be the frame the video starts from (`image`), the frame it ends on (`last_image`), or both. + +```py +import torch +from diffusers import MiniMaxH3Pipeline +from diffusers.utils import load_image +from diffusers.utils.export_utils import encode_video + +pipe = MiniMaxH3Pipeline.from_pretrained("MiniMaxAI/MiniMax-H3", dtype=torch.bfloat16) +pipe.enable_model_cpu_offload() + +prompt = "A red fox trotting through a snowy pine forest, snow crunching underfoot" + +# Text to video + audio. +output = pipe(prompt=prompt, generator=torch.Generator().manual_seed(42)) + +# First frame (and optionally last frame) to video + audio. The canvas follows the first keyframe. +image = load_image( + "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/astronaut.jpg" +) +output = pipe(prompt=prompt, image=image, generator=torch.Generator().manual_seed(42)) + +encode_video( + output.frames[0], + fps=24, + output_path="minimax_h3_fl2va.mp4", + audio=output.audio[0], + audio_sample_rate=pipe.audio_sampling_rate, +) +``` + +## Omni-references + +[`MiniMaxH3Ref2VAPipeline`] conditions on an ordered list of references: up to 9 images, 3 videos and 3 audio clips, 12 in total. The order is semantic. It labels the references in the prompt presentation (`""`, `"