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
10 changes: 10 additions & 0 deletions include/engine/community_models/minimax_music3/assets.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,14 @@ std::shared_ptr<const MiniMaxMusic3Assets> load_minimax_music3_assets(
const std::filesystem::path & model_path);
void validate_minimax_music3_anchors(const MiniMaxMusic3Assets & assets);

// Conv kernels must not be stored BF16: the CUDA im2col path asserts on BF16 kernel
// types in its naive lowering and silently corrupts through the fast path, which turns
// the whole flow output into clipped noise. Conv weights are a negligible share of the
// package, so BF16 requests (explicit, or Native over a BF16 source tensor) fall back
// to F32 for convolutions.
assets::TensorStorageType conv_safe_storage_type(
const assets::TensorSource & source,
const std::string & tensor_prefix,
assets::TensorStorageType requested);

} // namespace engine::models::minimax_music3
30 changes: 30 additions & 0 deletions scripts/minimax_music3/convert_gguf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@
("vocoder", "vocoder/diffusion_pytorch_model.safetensors", "vocoder.gguf"),
]

# Conv kernels must not be stored BF16: the CUDA conv lowering corrupts on BF16 kernel
# types (and the runtime guards this at load). Keep them F32 on disk for every target
# type.
KEEP_TYPES = {
"transformer": ["preprocess_conv*=f32", "postprocess_conv*=f32"],
"condition_encoder": ["proj.weight=f32"],
"vocoder": ["dec_in_proj*=f32"],
}

# Config sidecars the runtime requires next to the component GGUFs.
CONFIG_SIDECARS = {
"language_model": "language_model/config.json",
"rvq_depth_decoder": "rvq_depth_decoder/config.json",
"condition_encoder": "condition_encoder/config.json",
"transformer": "transformer/config.json",
"vocoder": "vocoder/config.json",
}


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
Expand Down Expand Up @@ -117,6 +135,7 @@ def write_variant_spec(args: argparse.Namespace) -> tempfile.TemporaryDirectory[

output_by_name = {name: output_name(args, component) for name, _input, _base in COMPONENTS for component in [next(c for c in COMPONENTS if c[0] == name)]}
gguf_source = next(source for source in spec["sources"] if source["format"] == "gguf")
gguf_source.setdefault("tensors", {})
gguf_source["tensors"]["language_model_weights"] = f"model:{output_by_name['language_model']}"
gguf_source["tensors"]["depth_decoder_weights"] = f"model:{output_by_name['rvq_depth_decoder']}"
gguf_source["tensors"]["condition_encoder_weights"] = f"model:{output_by_name['condition_encoder']}"
Expand Down Expand Up @@ -151,11 +170,22 @@ def run_conversion(args: argparse.Namespace, component: tuple[str, str, str]) ->
"--model-spec",
str(args.model_spec),
"--no-sidecars",
# The runtime opens component GGUFs by filename rather than through spec tensor
# ids, and the spec's gguf source does not yet match audiocpp_gguf's
# source-matching rules, so conversion proceeds without a matching package spec.
"--allow-missing-model-spec",
]
for keep in KEEP_TYPES.get(name, []):
cmd.extend(["--keep-type", keep])
if args.overwrite:
cmd.append("--overwrite")
print("[convert]", name, "->", output_path, flush=True)
subprocess.run(cmd, cwd=REPO_ROOT, check=True)
sidecar = CONFIG_SIDECARS.get(name)
if sidecar is not None:
config_dir = Path(args.output) / "config"
config_dir.mkdir(parents=True, exist_ok=True)
shutil.copyfile(Path(args.source) / sidecar, config_dir / f"{name}.json")


def main() -> None:
Expand Down
15 changes: 15 additions & 0 deletions src/community_models/minimax_music3/assets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,19 @@ std::shared_ptr<const MiniMaxMusic3Assets> load_minimax_music3_assets(
return assets;
}

assets::TensorStorageType conv_safe_storage_type(
const assets::TensorSource & source,
const std::string & tensor_prefix,
assets::TensorStorageType requested) {
if (requested == assets::TensorStorageType::BF16) {
return assets::TensorStorageType::F32;
}
if (requested == assets::TensorStorageType::Native &&
assets::ggml_type_for_tensor_dtype(source.require_metadata(tensor_prefix + ".weight").dtype) ==
GGML_TYPE_BF16) {
return assets::TensorStorageType::F32;
}
return requested;
}

} // namespace engine::models::minimax_music3
2 changes: 1 addition & 1 deletion src/community_models/minimax_music3/condition_encoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ MiniMaxMusic3ConditionWeights load_condition_weights(
*out.store,
source,
"proj",
storage_type,
conv_safe_storage_type(source, "proj", storage_type),
config.out_dim,
config.condition_hidden_dim,
3,
Expand Down
4 changes: 2 additions & 2 deletions src/community_models/minimax_music3/flow_transformer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ MiniMaxMusic3FlowWeights load_flow_weights(
*out.store,
source,
"preprocess_conv",
storage_type,
conv_safe_storage_type(source, "preprocess_conv", storage_type),
concat_channels,
concat_channels,
1,
Expand Down Expand Up @@ -84,7 +84,7 @@ MiniMaxMusic3FlowWeights load_flow_weights(
*out.store,
source,
"postprocess_conv",
storage_type,
conv_safe_storage_type(source, "postprocess_conv", storage_type),
config.in_channels,
config.in_channels,
1,
Expand Down
2 changes: 1 addition & 1 deletion src/community_models/minimax_music3/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ MiniMaxMusic3Request MiniMaxMusic3Session::parse_request(const runtime::TaskRequ
if (out.lyrics.empty()) {
throw std::runtime_error("MiniMax Music 3 requires lyrics");
}
if (const auto value = runtime::parse_finite_float_option(request.options, {"duration_sec"})) {
if (const auto value = runtime::parse_finite_float_option(request.options, {"duration_sec", "duration_seconds"})) {
out.duration_sec = *value;
}
if (const auto value = runtime::parse_i64_option(request.options, {"num_inference_steps"})) {
Expand Down
2 changes: 1 addition & 1 deletion src/community_models/minimax_music3/vocoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Music3VocoderWeights load_vocoder_weights(
*out.store,
source,
"dec_in_proj",
storage_type,
conv_safe_storage_type(source, "dec_in_proj", storage_type),
config.decoder_input_dim,
config.latent_channels / 2,
1,
Expand Down
7 changes: 7 additions & 0 deletions webui/configs/model_params.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@
{"name": "timesignature", "type": "text", "label": "【曲谱】timesignature", "default": "", "placeholder": "如 4"}
],

"minimax_music3": [
{"name": "num_inference_steps", "type": "number", "label": "Flow steps per window", "default": 30, "minimum": 1, "maximum": 200, "step": 1, "precision": 0, "info": "Flow-matching Euler steps per 200-frame denoising window."},
{"name": "guidance_scale", "type": "slider", "label": "Flow guidance scale", "default": 1.7, "minimum": 0.0, "maximum": 10.0, "step": 0.1},
{"name": "ar_guidance_scale", "type": "slider", "label": "AR guidance scale", "default": 1.5, "minimum": 0.0, "maximum": 10.0, "step": 0.1, "info": "Classifier-free guidance of the semantic and residual code sampling."},
{"name": "top_k", "type": "number", "label": "top_k", "default": 50, "minimum": 1, "maximum": 1024, "step": 1, "precision": 0}
],

"minimax_h3": [
{"name": "num_inference_steps", "type": "number", "label": "Denoising steps", "default": 12, "minimum": 1, "maximum": 50, "step": 1, "precision": 0, "info": "Twelve denoising steps provide a practical quality and performance balance."},
{"name": "num_frames", "type": "number", "label": "Output frames", "default": 241, "minimum": 5, "maximum": 1441, "step": 4, "precision": 0, "info": "Approximately 24 frames per output second; 241 frames produces about 10 seconds of audio."},
Expand Down
1 change: 1 addition & 0 deletions webui/configs/models_catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
{ "id": "chatterbox", "display_name": "Chatterbox (voice clone)", "family": "chatterbox", "path": "models/chatterbox", "task": "clon", "mode": "offline", "download_id": "chatterbox", "min_vram_gb": 12 },

{ "id": "ace-step", "display_name": "ACE-Step 1.5 (music gen)", "family": "ace_step", "path": "models/Ace-Step1.5", "task": "gen", "mode": "offline", "download_id": "ace_step", "session_options": { "ace_step.mem_saver": "true", "ace_step.dit_weight_type": "q8_0", "ace_step.text_encoder_weight_type": "q8_0", "ace_step.planner_weight_type": "q8_0" }, "min_vram_gb": 8 },
{ "id": "minimax-music3", "display_name": "MiniMax-Music3 (song gen)", "family": "minimax_music3", "path": "models/MiniMax-Music3-GGUF/language_model_q4_k.gguf", "task": "gen", "mode": "offline", "download_id": "minimax_music3_q4_k", "min_vram_gb": 12 },
{ "id": "stable-audio-small-music","display_name": "Stable Audio 3 Small Music (gen)", "family": "stable_audio", "path": "models/stable-audio-3-small-music", "task": "gen", "mode": "offline", "download_id": "stable_audio_3_small_music", "min_vram_gb": 4 },
{ "id": "stable-audio-small-sfx", "display_name": "Stable Audio 3 Small SFX (gen)", "family": "stable_audio", "path": "models/stable-audio-3-small-sfx", "task": "gen", "mode": "offline", "download_id": "stable_audio_3_small_sfx", "min_vram_gb": 4 },
{ "id": "stable-audio-medium", "display_name": "Stable Audio 3 Medium (gen)", "family": "stable_audio", "path": "models/stable-audio-3-medium", "task": "gen", "mode": "offline", "download_id": "stable_audio_3_medium", "session_options": { "stable_audio.mem_saver": "true" }, "min_vram_gb": 10 },
Expand Down
16 changes: 8 additions & 8 deletions webui/native/dist/index.html

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions webui/native/src/lib/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ function packageModelPath(entry: PackageEntry): string {
if (entry.format === 'gguf' && entry.family === 'minimax_h3') {
const entryName = entry.id.includes('int8_dit') ? 'dit_int8.gguf' : 'dit.gguf';
modelFile = entry.files?.find((file) => file.toLowerCase().endsWith(`/${entryName}`));
} else if (entry.format === 'gguf' && entry.family === 'minimax_music3') {
// Multi-component package; the language_model_*.gguf matching the package
// precision is the entry file.
modelFile = entry.files?.find((file) =>
new RegExp(`/language_model_${entry.precision}\\.gguf$`).test(file.toLowerCase())) ||
entry.files?.find((file) => /\/language_model_[^/]*\.gguf$/.test(file.toLowerCase()));
} else if (entry.format === 'gguf') {
modelFile = entry.files?.find((file) => file.toLowerCase().endsWith('.gguf'));
}
Expand Down