Skip to content

Add MiniMax Music 3 - #14456

Open
apolinario wants to merge 13 commits into
mainfrom
minimax-music3-integration
Open

Add MiniMax Music 3#14456
apolinario wants to merge 13 commits into
mainfrom
minimax-music3-integration

Conversation

@apolinario

@apolinario apolinario commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Adds MiniMax Music 3 (lyrics + music description → complete songs up to 5 minutes, 44.1 kHz stereo). An 8B Qwen3 autoregressive stage predicts per-frame audio codes; its hidden states condition a 2.4B flow-matching transformer that produces Flow-VAE latents in overlapping chunks; a DAC-style decoder renders the waveform. Structurally close to Ace-Step (DiT + pipeline-local submodels) and AudioLDM2 (language model as a pipeline component).

New: MiniMaxMusic3Transformer1DModel, MiniMaxMusic3ConditionEncoder (in models/condition_embedders/), MiniMaxMusic3RVQDepthDecoder, MiniMaxMusic3Vocoder, and MiniMaxMusic3ModularPipeline (modular blocks: text encode → AR semantic generation → chunked flow-matching with guider-abstracted CFG → vocode/stitch), plus conversion script, docs, and model- and modular-pipeline tests. The scheduler is the existing FlowMatchEulerDiscreteScheduler(num_train_timesteps=1, shift=1.0, invert_sigmas=True), which reproduces the reference Euler loop exactly.

Usage:

import soundfile as sf
import torch
from diffusers import ModularPipeline

pipe = ModularPipeline.from_pretrained("MiniMaxAI/MiniMax-Music3")
pipe.load_components(dtype=torch.bfloat16)
pipe.to("cuda")

lyrics = """[verse]
Morning light filtering through the pine
Every quiet street is yours and mine
[chorus]
Softly the world begins to breathe"""

prompt = (
    "Genre: acoustic pop. BPM: 96. Key: C major. Warm and intimate, building gently into the chorus. "
    "Vocals: soft female lead, close and breathy, light stacked harmonies in the chorus. "
    "Arrangement: fingerpicked guitar and soft piano; brushed drums and upright bass enter in the chorus."
)

audio = pipe(
    prompt=prompt,
    lyrics=lyrics,
    audio_duration=60.0,
    generator=torch.Generator("cuda").manual_seed(7),
    output="audios",
)[0]

sf.write("song.wav", audio.T.float().cpu().numpy(), pipe.sampling_rate)

Parity vs the reference implementation: every converted component matches bitwise on CPU/fp32; full 30-step chunked generation matches at 57.8 dB SNR on GPU (residual comes from the fused→split QKV projections).

Weights: final home will be MiniMaxAI/MiniMax-Music3 when the model is out

Some choices I made that would be great to double check:

  1. One generator drives the whole pipeline - the reference derives a separate seed per stage and per chunk, imo it's fine to keep the diffusers convention here.
  2. Output is the vocoder's native 44.1 kHz; the reference server post-resamples to 32 kHz - I don't see a reason to downsample something higher quality, but let me know if following the original would be fundamental here
  3. Generation is single-sample (prompt/lyrics are strings; the AR stage is internally batch-2 for CFG). If we want to optimize batched generation I think it may need an AR-loop redesign, which I would defer to a follow-up if someone would be interested?

@github-actions github-actions Bot added size/L PR with diff > 200 LOC documentation Improvements or additions to documentation models tests utils pipelines and removed size/L PR with diff > 200 LOC labels Aug 12, 2026
@apolinario
apolinario force-pushed the minimax-music3-integration branch from 672f410 to 5698322 Compare August 12, 2026 15:41
@github-actions github-actions Bot added the size/L PR with diff > 200 LOC label Aug 12, 2026
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@apolinario apolinario changed the title Add MiniMax Music 3 pipeline for text-and-lyrics-to-music generation Add MiniMax Music 3 Aug 12, 2026

@sayakpaul sayakpaul left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Comment on lines +31 to +33
@dataclass
class MiniMaxMusic3TransformerOutput(BaseOutput):
sample: torch.Tensor

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it not possible to use the Transformer2DModelOutput here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — now returns Transformer2DModelOutput.

return freqs.cos().contiguous(), freqs.sin().contiguous()

def forward(self, seq_len: int, device: torch.device) -> Tuple[torch.Tensor, torch.Tensor]:
return self._build(seq_len, device)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider folding into forward() so that things stay explicitly inline.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — folded into forward() with the cache decorator on it directly.

self.to_k = nn.Linear(dim, self.inner_dim, bias=False)
self.to_v = nn.Linear(dim, self.inner_dim, bias=False)
self.to_out = nn.ModuleList([nn.Linear(self.inner_dim, dim, bias=False), nn.Dropout(0.0)])
self.set_processor(processor or MiniMaxMusic3AttnProcessor())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.set_processor(processor or MiniMaxMusic3AttnProcessor())
if processor is None:
processor = self._default_processor_cls()
self.set_processor(processor)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (also applied to the depth decoder's attention).

previous_latent = None
previous_condition = None
global_step = 0
with self.progress_bar(total=self._num_timesteps) as progress_bar:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do self.num_inference_steps or put num_inference_steps * len(chunk_starts) in a separate variable and use it here? This feels like an antipattern to me.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone with the modular conversion — this file is removed; step accounting now lives in the loop blocks.

Per review: the conditioner moves to models/condition_embedders, the RVQ depth
decoder and vocoder to models/transformers and models/autoencoders, and the
standard pipeline is replaced by MiniMaxMusic3ModularPipeline (anima-style
blocks, helios-style chunk loop, guider-abstracted CFG with a zeros
unconditional branch). Index configs now reference all components under the
diffusers library. Also applies the transformer review suggestions
(Transformer2DModelOutput, inlined rotary forward, explicit processor default).
@apolinario

apolinario commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Converted to a modular pipeline per @yiyixuxu's suggestion. Also applied Sayak's comments.

@sergereview sergereview Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤗 Serge says:

Overall this is a well-structured modular integration: the block decomposition (text encoder → AR semantic generation → chunk bookkeeping → per-chunk loop with condition/prepare/set-timesteps/denoise/update sub-blocks → vocoder decode) follows the established LoopSequentialPipelineBlocks pattern, the guider is wired the same way as other modular pipelines, and I verified the chunk geometry is self-consistent (200-frame windows at ~3.445 latents/frame → 689-latent windows, ~344.5-latent hop; the 86/258 crop constants tile the song exactly and the [L-344, L-172) carry aligns with the next window's first 172 latents).

Correctness / clarity

  • The chunking comments in before_denoise.py, denoise.py, and decoders.py all state that neighboring windows "share 172 latent frames", but at ~3.445 latents per frame a 100-frame hop on 200-frame windows gives ~344.5 shared latent frames; 172 is only the blended/carried prefix. The code is right, the comments are wrong — worth fixing since future readers will re-derive this.
  • MiniMaxMusic3SemanticGenerationStep raises a misleading "the prompt ended generation immediately" error when audio_duration is shorter than one AR frame (< 0.04 s), since max_frames becomes 0.

Style / docs

  • MiniMaxMusic3ChunkDenoiseInner uses raw tqdm (one new bar per chunk) instead of routing through the pipeline's progress_bar/set_progress_bar_config machinery; helios has the same pattern, but a single bar or config-respecting bar would be nicer for a 14-chunk song.
  • Minor import-ordering slip in src/diffusers/models/__init__.py and a wrong autodoc path for Transformer2DModelOutput in the transformer docstring.

Description vs. diff

  • The PR description's usage example (from diffusers import MiniMaxMusic3Pipeline, .audios[0]) does not match the diff: only MiniMaxMusic3ModularPipeline / MiniMaxMusic3Blocks exist; there is no standard MiniMaxMusic3Pipeline. The in-repo docs correctly use ModularPipeline.from_pretrained, but the description should be updated to avoid confusion.

Tests

  • pretrained_model_name_or_path = "diffusers-internal-dev/tiny-minimax-music3" is flagged as a placeholder (TODO to move to hf-internal-testing/); please make sure the tiny repo exists and the CI tests actually run before merge. Also note the fast tests only ever exercise a single chunk (audio_duration=0.2 → 5 frames), so the multi-chunk overlap/crop path — the trickiest part of the modular loop — has no automated coverage. A small multi-chunk shape test (e.g. enough frames for 2–3 windows with tiny configs) would be valuable.

serge v0.1.0 · model: claude-fable-5 · 18 LLM turns · 33 tool calls · 1708.7s · 1520108 in / 129503 out tokens

Comment thread src/diffusers/modular_pipelines/minimax_music3/denoise.py Outdated
Comment thread src/diffusers/modular_pipelines/minimax_music3/denoise.py Outdated
Comment thread src/diffusers/modular_pipelines/minimax_music3/encoders.py
Comment thread src/diffusers/models/__init__.py Outdated
Comment thread src/diffusers/models/transformers/transformer_minimax_music3.py Outdated
…e-level progress bar, zero-frame duration guard, import ordering, docstring cross-reference
…ubmodule calls (minimax_h3 workaround), clear error when the AR models cannot co-reside
@apolinario

apolinario commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

dafe373 is a bug-fix for offloading. it fixes ComponentsManager CPU offloading: the AR step calls LM/depth-decoder submodules, which was bypassing the offload hook on the top-level forward. Applied the same manual pre_forward trigger minimax_h3 uses. Measured on A100: auto offload runs a generation in ~21 GB free (peak 17.3 GB); with apply_group_offloading on the language model it fits in 8 GB (peak 5.5 GB).

@yiyixuxu yiyixuxu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @apolinario i left some small comments

Comment on lines +118 to +119
def _generate_depth_codes(
components: MiniMaxMusic3ModularPipeline,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _generate_depth_codes(
components: MiniMaxMusic3ModularPipeline,
def _generate_depth_codes(
language_model,
rvq_depth_decoder,

it's better to take the individual components as inputs

return torch.multinomial(probs.to(sample_device), 1, generator=generator).squeeze(-1).to(probs.device)


def _embed_audio_frame(components: MiniMaxMusic3ModularPipeline, frame_codes: torch.Tensor) -> torch.Tensor:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _embed_audio_frame(components: MiniMaxMusic3ModularPipeline, frame_codes: torch.Tensor) -> torch.Tensor:
def _embed_audio_frame(
language_model: Qwen3ForCausalLM,
rvq_depth_decoder: MiniMaxMusic3RVQDepthDecoder
frame_codes: torch.Tensor) -> torch.Tensor:

return torch.stack(codes, dim=1), torch.cat(hidden_parts, dim=-1)


class MiniMaxMusic3TextEncoderStep(ModularPipelineBlocks):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ohh it's actually an tokenize step, can we rename and then in the final assembled MiniMaxMusic3Block, merge it into the MiniMaxMusic3SemanticGenerationStep

MiniMaxMusic3ChunkDenoiseStep,
MiniMaxMusic3VocoderDecodeStep,
]
block_names = ["text_encoder", "semantic_generator", "prepare_chunks", "denoise", "decode"]

@yiyixuxu yiyixuxu Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
block_names = ["text_encoder", "semantic_generator", "prepare_chunks", "denoise", "decode"]
block_names = ["semantic_generator", "denoise", "decode"]

I think each child block here should be ones that contain models and makes sense to be run standalone.e.g. user can use semantic_generator once and resue the output to denoise with different configs

I think we can merge MiniMaxMusic3TextEncoderStep and MiniMaxMusic3SemanticGenerationStep into one sequential and then prepare_chunks + "denoise" into the "MiniMaxMusic3CoreDenoiseStep" (same as MiniMaxH3 etc)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation models modular-pipelines size/L PR with diff > 200 LOC tests utils

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants