Add MiniMax Music 3 - #14456
Conversation
672f410 to
5698322
Compare
|
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. |
…I/MiniMax-Music-3
| @dataclass | ||
| class MiniMaxMusic3TransformerOutput(BaseOutput): | ||
| sample: torch.Tensor |
There was a problem hiding this comment.
Is it not possible to use the Transformer2DModelOutput here?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Consider folding into forward() so that things stay explicitly inline.
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
| self.set_processor(processor or MiniMaxMusic3AttnProcessor()) | |
| if processor is None: | |
| processor = self._default_processor_cls() | |
| self.set_processor(processor) |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
46564b9 to
bcc6bbf
Compare
|
Converted to a modular pipeline per @yiyixuxu's suggestion. Also applied Sayak's comments. |
There was a problem hiding this comment.
🤗 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, anddecoders.pyall 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. MiniMaxMusic3SemanticGenerationStepraises a misleading "the prompt ended generation immediately" error whenaudio_durationis shorter than one AR frame (< 0.04 s), sincemax_framesbecomes 0.
Style / docs
MiniMaxMusic3ChunkDenoiseInneruses rawtqdm(one new bar per chunk) instead of routing through the pipeline'sprogress_bar/set_progress_bar_configmachinery; 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__.pyand a wrong autodoc path forTransformer2DModelOutputin the transformer docstring.
Description vs. diff
- The PR description's usage example (
from diffusers import MiniMaxMusic3Pipeline,.audios[0]) does not match the diff: onlyMiniMaxMusic3ModularPipeline/MiniMaxMusic3Blocksexist; there is no standardMiniMaxMusic3Pipeline. The in-repo docs correctly useModularPipeline.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 tohf-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
…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
|
dafe373 is a bug-fix for offloading. it fixes |
yiyixuxu
left a comment
There was a problem hiding this comment.
thanks @apolinario i left some small comments
| def _generate_depth_codes( | ||
| components: MiniMaxMusic3ModularPipeline, |
There was a problem hiding this comment.
| 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: |
There was a problem hiding this comment.
| 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): |
There was a problem hiding this comment.
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"] |
There was a problem hiding this comment.
| 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)
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(inmodels/condition_embedders/),MiniMaxMusic3RVQDepthDecoder,MiniMaxMusic3Vocoder, andMiniMaxMusic3ModularPipeline(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 existingFlowMatchEulerDiscreteScheduler(num_train_timesteps=1, shift=1.0, invert_sigmas=True), which reproduces the reference Euler loop exactly.Usage:
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-Music3when the model is outSome choices I made that would be great to double check:
generatordrives the whole pipeline - the reference derives a separate seed per stage and per chunk, imo it's fine to keep the diffusers convention here.prompt/lyricsare 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?