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
Take the individual models rather than `components` in `_generate_depth_codes` and
`_embed_audio_frame`; `num_codebooks` / `audio_vocab_size` come off the depth decoder's
config, so the two pipeline properties that existed only for them are gone.
Assemble the blockset as three top-level children, each owning models and runnable on its
own — generate the semantics once and denoise it with different settings:
semantic_generator MiniMaxMusic3SemanticGenerationStep [tokenize, generate]
denoise MiniMaxMusic3CoreDenoiseStep [prepare_chunks, denoise]
decode MiniMaxMusic3VocoderDecodeStep
`MiniMaxMusic3TextEncoderStep` is renamed `MiniMaxMusic3TokenizeStep` (it only tokenizes)
and the autoregressive leaf `MiniMaxMusic3AutoregressiveStep`, freeing its old name for
the sequential.
Point the tests at `hf-internal-testing/tiny-minimax-music3-modular-pipe`; the previous
fixture repo's component specs pointed at a local path, so nothing loaded off that machine.
Drop the PR-branch install tip from the docs.
20s and 60s generations are bit-identical to before these changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
yiyixuxu
left a comment
There was a problem hiding this comment.
thanks a lot @apolinario !!
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?