Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions app/_launch_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,13 @@ def _check_model_downloaded(model_type: str) -> bool:
return False
if not all(_variant_group_downloaded(g) for g in groups):
return False
# Component-folder models can expose a tiny manifest as their primary
# URL while the family handler downloads the actual assets separately.
# Do not mark a partial install as ready merely because that manifest
# arrived first (MiniMax-Music3 is the first such local audio model).
for relative_path in model_def.get("required_model_assets", []):
if wgp.fl.locate_file(relative_path, error_if_none=False) is None:
return False
# Some edit pipelines split required conditioning weights out of the
# main transformer/text-encoder groups. Krea 2 Edit cannot run without
# its Qwen3-VL vision tower, so do not report it as ready until that
Expand Down Expand Up @@ -1314,6 +1321,7 @@ def list_models():
"lora_compatibility_note": md.get("lora_compatibility_note", ""),
"family": family,
"architecture": architecture,
"resource_requirements": md.get("resource_requirements"),
"is_i2v": wgp.test_class_i2v(mt),
"is_t2v": wgp.test_class_t2v(mt),
"guidance_max_phases": md.get("guidance_max_phases", 1),
Expand Down Expand Up @@ -8255,6 +8263,7 @@ def _ensure_llm_loaded():
_parse_song_output,
_SONG_WRITER_FALLBACK,
_SONG_WRITER_FALLBACK_INSTRUMENTAL,
_SONG_WRITER_FALLBACK_MINIMAX_MUSIC3,
)

api.include_router(create_llm_router(
Expand Down Expand Up @@ -8304,6 +8313,22 @@ def _build_music_gen_params(model_type: str, lyrics: str, style: str, duration_s
return params


def _music3_writer_duration_instruction(duration_seconds) -> str:
"""Keep local MiniMax-Music3 song writing proportional to its target runtime."""
try:
duration = min(300.0, max(5.0, float(duration_seconds or 120)))
except (TypeError, ValueError):
duration = 120.0
label = f"{duration:g}"
return (
"TARGET RUNTIME CONTRACT:\n"
f"- The generated track is {label} seconds long.\n"
"- Scale sections, lyric density, repetitions, transitions and instrumental "
"space to this runtime; do not write a full song that will be cut off.\n"
f"- In ### Arrangement, plan material from 0:00 to approximately {label} seconds."
)


@api.post("/api/v1/director/generate-music")
async def director_generate_music(request: Request):
"""Generate a music track for Director Music Video mode. Dual-mode:
Expand Down Expand Up @@ -8337,8 +8362,10 @@ async def director_generate_music(request: Request):
except execution_mode.ExecutionModeError as exc:
raise HTTPException(status_code=409, detail=str(exc)) from exc

if wgp.get_model_def(model_type) is None:
selected_model = wgp.get_model_def(model_type)
if selected_model is None:
raise HTTPException(status_code=400, detail=f"Unknown model: {model_type}")
is_minimax_music3 = str(selected_model.get("architecture") or model_type) == "minimax_music3"

image_paths = body.get("image_paths") or []
if not image_paths and body.get("reference_image_path"):
Expand All @@ -8353,16 +8380,22 @@ async def director_generate_music(request: Request):
from services import llm_service
from services.guide_loader import load_guide
_ensure_llm_loaded()
if instrumental:
if is_minimax_music3 and instrumental:
system_prompt = load_guide("music", "song_writer_minimax_music3_instrumental") or _SONG_WRITER_FALLBACK_MINIMAX_MUSIC3
elif is_minimax_music3:
system_prompt = load_guide("music", "song_writer_minimax_music3") or _SONG_WRITER_FALLBACK_MINIMAX_MUSIC3
elif instrumental:
system_prompt = load_guide("music", "song_writer_instrumental") or _SONG_WRITER_FALLBACK_INSTRUMENTAL
else:
system_prompt = load_guide("music", "song_writer") or _SONG_WRITER_FALLBACK
if is_minimax_music3:
system_prompt = f"{system_prompt.rstrip()}\n\n{_music3_writer_duration_instruction(duration_seconds)}"
try:
raw = await asyncio.to_thread(
llm_service.generate,
prompt=description,
system_prompt=system_prompt,
max_new_tokens=body.get("max_new_tokens", 1024),
max_new_tokens=body.get("max_new_tokens", 1536 if is_minimax_music3 else 1024),
temperature=body.get("temperature", 0.85),
top_p=body.get("top_p", 0.9),
seed=body.get("seed"),
Expand Down Expand Up @@ -8393,7 +8426,10 @@ async def director_generate_music(request: Request):
output_files = await asyncio.to_thread(
_submit_and_wait,
gen_params,
timeout_s=1800,
# Music3 can decode several minutes of stereo audio on a single
# consumer GPU; keep the HTTP worker alive long enough for the
# local job while retaining the shorter guard for ACE-Step.
timeout_s=3600 if is_minimax_music3 else 1800,
workspace=workspace,
out_dir=out_dir,
provenance=provenance,
Expand Down
41 changes: 41 additions & 0 deletions app/defaults/minimax_music3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"model": {
"name": "MiniMax-Music3",
"architecture": "minimax_music3",
"description": "MiniMax-Music3 creates complete stereo songs from lyrics and a detailed music caption, with expressive vocals, evolving arrangements, and long-form structure up to five minutes. The official converted components require about 28 GB of storage and are streamed through HocusPocus's single-GPU memory manager.",
"selector_help": "Best for full songs with detailed lyric sections and arrangement control. First use downloads about 28 GB. 24 GB VRAM is the practical target with offload; lower VRAM is possible only with aggressive CPU offload and much slower generation. Model weights use the MiniMax-Music3 Community License.",
"URLs": [
"https://huggingface.co/MiniMaxAI/MiniMax-Music3/resolve/bd348f9c49ea3c1b39f33ace3436f8fad435f24e/modular_model_index.json"
],
"source_repo": "https://huggingface.co/MiniMaxAI/MiniMax-Music3",
"license_name": "MiniMax-Music3 Community License",
"license_url": "https://huggingface.co/MiniMaxAI/MiniMax-Music3/blob/main/LICENSE",
"resource_requirements": {
"storage_gb": 28,
"vram_gb": 24,
"platform": "NVIDIA CUDA",
"backend": "Diffusers modular pipeline",
"tier": "stable",
"note": "Con CPU offload puede caber en menos VRAM, pero la generación será mucho más lenta."
},
"model_size_gb": 28,
"required_model_assets": [
"minimax_music3/LICENSE",
"minimax_music3/tokenizer/tokenizer.json",
"minimax_music3/language_model/model-00001-of-00004.safetensors",
"minimax_music3/language_model/model-00002-of-00004.safetensors",
"minimax_music3/language_model/model-00003-of-00004.safetensors",
"minimax_music3/language_model/model-00004-of-00004.safetensors",
"minimax_music3/rvq_depth_decoder/diffusion_pytorch_model.safetensors",
"minimax_music3/condition_encoder/diffusion_pytorch_model.safetensors",
"minimax_music3/transformer/diffusion_pytorch_model-00001-of-00002.safetensors",
"minimax_music3/transformer/diffusion_pytorch_model-00002-of-00002.safetensors",
"minimax_music3/vocoder/diffusion_pytorch_model.safetensors"
]
Comment thread
cursor[bot] marked this conversation as resolved.
Comment thread
cursor[bot] marked this conversation as resolved.
},
"prompt": "[Verse]\nMorning light filters through the pines\nEvery quiet road is yours and mine\n[Chorus]\nSoftly the whole world starts to breathe\nStay for one more song with me\n[Outro]",
"alt_prompt": "### Global Metadata\nWarm acoustic pop at 96 BPM in C major; intimate and hopeful, growing into a wide final chorus; polished natural production.\n\n### Vocal Details\nSoft, close female lead with breathy verses, clear diction, and light stacked harmonies in the chorus.\n\n### Arrangement\nFingerpicked acoustic guitar and soft piano open the song. Brushed drums and upright bass enter in the chorus; strings bloom gently before a sparse outro.",
"duration_seconds": 120,
"num_inference_steps": 30,
"guidance_scale": 1.7
}
1 change: 1 addition & 0 deletions app/models/TTS/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
heartmula_handler,
index_tts2_handler,
kugelaudio_handler,
minimax_music3_handler,
qwen3_handler,
yue_handler,
)
12 changes: 12 additions & 0 deletions app/models/TTS/minimax_music3/NOTICE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# MiniMax-Music3 notices

The neural-network class definitions in this directory are adapted from the
MiniMax-Music3 integration contributed by the MiniMax and Hugging Face teams
to Diffusers and retain their Apache License 2.0 headers.

MiniMax-Music3 model weights are not distributed in the HocusPocus repository.
They are downloaded on first use from
<https://huggingface.co/MiniMaxAI/MiniMax-Music3> and are governed by the
[MiniMax-Music3 Community License](https://huggingface.co/MiniMaxAI/MiniMax-Music3/blob/main/LICENSE)
and its Acceptable Use Policy. HocusPocus downloads a copy of that license next
to the model components.
21 changes: 21 additions & 0 deletions app/models/TTS/minimax_music3/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Native MiniMax-Music3 components used by HocusPocus.

The neural-network definitions are adapted from the Apache-2.0 Diffusers
integration contributed by the MiniMax and Hugging Face teams. Model weights
remain governed by MiniMax's Music3 Community License and are downloaded from
the official MiniMaxAI Hugging Face repository on first use.
"""

from .condition_encoder import MiniMaxMusic3ConditionEncoder
from .pipeline import MiniMaxMusic3Pipeline
from .rvq_depth_decoder import MiniMaxMusic3RVQDepthDecoder
from .transformer import MiniMaxMusic3Transformer1DModel
from .vocoder import MiniMaxMusic3Vocoder

__all__ = [
"MiniMaxMusic3ConditionEncoder",
"MiniMaxMusic3Pipeline",
"MiniMaxMusic3RVQDepthDecoder",
"MiniMaxMusic3Transformer1DModel",
"MiniMaxMusic3Vocoder",
]
76 changes: 76 additions & 0 deletions app/models/TTS/minimax_music3/condition_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright 2026 The MiniMax Team and The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import torch
import torch.nn as nn
import torch.nn.functional as F

from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.models.modeling_utils import ModelMixin


class MiniMaxMusic3ConditionEncoder(ModelMixin, ConfigMixin):
r"""
Projects the per-frame hidden states of the autoregressive stage onto the Flow-VAE latent timeline.

Each generated frame carries `num_condition_layers` hidden states of size `condition_hidden_dim` (one from the
language model and one per residual codebook step). They are mixed with learned softmax weights, projected, and
resampled from the language-model frame rate to the latent frame rate with nearest-neighbor interpolation.
"""

@register_to_config
def __init__(
self,
condition_hidden_dim: int = 4096,
num_condition_layers: int = 8,
out_dim: int = 2048,
input_sampling_rate: int = 24000,
input_hop_length: int = 960,
output_sampling_rate: int = 44100,
output_hop_length: int = 512,
):
super().__init__()
self.layer_weight_logits = nn.Parameter(torch.zeros(num_condition_layers))
self.layer_scale = nn.Parameter(torch.ones(1))
self.proj = nn.Conv1d(condition_hidden_dim, out_dim, kernel_size=3, padding=1)

def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
r"""
Args:
hidden_states (`torch.Tensor` of shape `(batch, frames, num_condition_layers * condition_hidden_dim)`):
Concatenated per-frame hidden states from the autoregressive stage.

Returns:
`torch.Tensor` of shape `(batch, latent_length, out_dim)`: the latent-aligned conditioning sequence.
"""
batch_size, num_frames, _ = hidden_states.shape
num_layers = self.config.num_condition_layers
hidden_states = hidden_states.transpose(1, 2)
hidden_states = hidden_states.reshape(batch_size, num_layers, self.config.condition_hidden_dim, num_frames)
layer_weights = torch.softmax(self.layer_weight_logits, dim=0).to(hidden_states.dtype)
hidden_states = torch.einsum("blht,l->bht", hidden_states, layer_weights)
hidden_states = self.layer_scale.to(hidden_states.dtype) * hidden_states
hidden_states = self.proj(hidden_states)
latent_length = max(
1,
int(
num_frames
* self.config.output_sampling_rate
/ self.config.input_sampling_rate
* self.config.input_hop_length
/ self.config.output_hop_length
),
)
hidden_states = F.interpolate(hidden_states, size=latent_length, mode="nearest")
return hidden_states.transpose(1, 2)
Loading