MiniMax Music 3#

MiniMax Music 3 is a text-to-music model from MiniMax. Give it lyrics and a style description; it returns 32 kHz stereo song audio, vocals and instrumental together.

It is not a TTS model with a music voice. Generation runs in two stages. A Qwen3 backbone predicts one audio frame per decode step, and each frame is eight RVQ codebooks deep: the backbone’s own lm_head emits c0, then a four-layer depth decoder walks c1..c7, each code conditioning the next. Every 200 frames are handed to a flow-matching DIT that solves a VAE latent, which a DAC-style decoder turns into waveform. So the knobs that shape a TTS request do nothing here — there is no reference speaker, no voice, no temperature. What you control is the lyrics, the caption, the seed, and the length.

Component

Spec

Backbone

Qwen3 decoder (36 L, hidden=4096, GQA 32/8, vocab 200k)

Depth decoder

4 L, hidden=4096, 16 heads; 7 residual codebooks of 1024

Frame

8 codebooks; c0 in the backbone vocabulary, c1..c7 in the depth decoder

AR guidance

Classifier-free, scale 1.5, c0 masked to the conditioned branch’s top 50

Acoustic stage

Flow-matching DIT (36 L, dim=2048, 32 heads) + DAC decoder, 512x upsampling

Solver

Euler, 30 steps, acoustic CFG scale 1.7

Window

200 frames per acoustic chunk, 100-frame hop

Frame rate

25 frames/second

Context length

10,240 tokens

Output

32 kHz stereo WAV

Prerequisites#

Install sglang-omni from source as in Installation.

git clone git@github.com:sgl-project/sglang-omni.git
cd sglang-omni

uv venv .venv -p 3.12
source .venv/bin/activate

uv pip install -v -e .   # drop -e for a non-editable install

On Intel GPUs, first follow the Intel XPU installation instructions, then replace CUDA_VISIBLE_DEVICES in the commands below with ZE_AFFINITY_MASK (for example, ZE_AFFINITY_MASK=0 or ZE_AFFINITY_MASK=0,1).

Single GPU (colocate both stages):

CUDA_VISIBLE_DEVICES=0 sgl-omni serve --model-path MiniMaxAI/MiniMax-Music3 --port 8000

Dual GPU (AR on the first device, DIT/DAV on the second):

CUDA_VISIBLE_DEVICES=0,1 sgl-omni serve --model-path MiniMaxAI/MiniMax-Music3 --port 8000

Default optimizations that are on without further flags: backbone decode device graph, RVQ depth device graph, compiled DIT blocks, compiled DAV decoder, and batched seeded sampling.

Classifier-free guidance is on in both stages and has no flag. See Guidance for what it costs you, because the AR half changes how much a request occupies.

Apple Silicon: native MLX#

The standalone music3_server runs entirely in C++ under sglang_omni_mlx/native/; it uses the same HTTP request fields as the examples below. Download the pinned converted checkpoint, then build with the native runtime’s existing MLX 0.32.3 toolchain:

python sglang_omni_mlx/native/ci/provision_models.py "$MUSIC3_DATA" \
  --model mlx-community/MiniMax-Music3-mxfp8
sglang_omni_mlx/native/scripts/build_runtime.sh "$MUSIC3_RUNTIME" "$MUSIC3_BUILD"
"$MUSIC3_RUNTIME/bin/music3_server" \
  --model-path "$MUSIC3_DATA/models/mlx-community_MiniMax-Music3-mxfp8" \
  --model-name MiniMaxAI/MiniMax-Music3 --port 8000

Set MUSIC3_DATA, MUSIC3_RUNTIME and MUSIC3_BUILD to separate local directories. Serving needs neither Python nor SGLang’s CUDA package. Model provisioning uses the existing CI Python requirements. The converted weights occupy about 14 GB; allow additional unified memory for the KV cache and acoustic activations. Both stages stay resident by default, with MLX’s free buffer cache limited to 1 GiB.

This path includes Qwen3/RVQ decoding, condition fusion, Euler Flow/DiT, stereo DAV decoding, overlap crops, and Hann sinc conversion from 44.1 to 32 kHz. AR guidance is fixed at 1.5 and top-k at 50. max_new_tokens defaults to 750 frames and accepts 1–9000, subject to the 10,240-token context and 5000-token prompt limit. Before cleanup, input and instructions are each limited to 64 KiB of UTF-8. seed accepts integers in [0, 2**63) and defaults to zero. Optional dit_steps accepts 1–100 (default 30), and dit_cfg_scale accepts finite nonnegative values (default 1.7).

Responses are complete stereo PCM16 wav or raw interleaved pcm at 32 kHz. stream=true, other formats and speed changes return 400. Only one song is generated at a time; a concurrent request returns 503. A request not answered --request-timeout-s seconds (1800 by default) after it is admitted returns 504. That deadline, a client disconnect and supervisor shutdown each cancel generation at its next frame, acoustic window or Euler step, which frees the server for the next request. Voxt’s --supervised lifecycle, /health and /v1/models are supported; this does not add a Music3 interface to Voxt.

Native MLX stage offload#

Pass both components to the native server to load one stage at a time:

"$MUSIC3_RUNTIME/bin/music3_server" \
  --model-path "$MUSIC3_DATA/models/mlx-community_MiniMax-Music3-mxfp8" \
  --model-name MiniMaxAI/MiniMax-Music3 --port 8000 \
  --stage-offload-components ar,dit

Startup loads configuration and the tokenizer and validates checkpoint shapes and stage ownership without evaluating the weights. Every tensor must belong to exactly one stage: AR/RVQ uses language_model and rvq_depth_decoder; the acoustic stage uses condition_encoder, transformer and vocoder. Each accepted request loads AR/RVQ weights, evaluates the hidden-state handoff, synchronizes and releases AR, then loads the acoustic weights. Acoustic weights stay loaded across all windows and are released after completion, cancellation or failure.

The checkpoint directory is resolved once at startup; keep its contents unchanged while the server runs. No second host weight copy is retained. Reloading weights adds disk I/O and latency; file-backed pages can remain in the operating system’s cache.

The existing single-request worker owns both stages and returns 503 for concurrent submissions. Omitting the flag keeps both stages resident. Component names accept either order, surrounding whitespace and mixed case, matching the CUDA CLI. This option belongs to the native executable; CUDA continues to use the sgl-omni serve option described below.

music3_synthesize accepts the same flag and reports per-request elapsed time, AR elapsed time and peak active MLX allocation in bytes. Its diagnostic --check-cancellation flag cancels at the fully evaluated AR handoff before synthesizing a retry, and reports active MLX allocation before and after cancellation. The Mac golden harness runs short and cross-window cases in separate resident and offload processes and requires identical waveform digests and metadata. HTTP seeded-repeat and busy cases run in both modes; the remaining HTTP cases run resident. CI uploads WAVs from both modes for review. The earlier resident validation record below does not establish offload memory or latency results.

Reference parity and Mac CI#

The numerical reference is PR #1990 at 195e4e9058e13af605f13f5c55a26efe651805a6. Reference source hashes are in native/ci/music3_reference.json; checkpoint hashes and revision are in native/ci/model_pins.json. The first feedback frame and FP16 hidden-state handoff follow that reference. The server lifecycle extraction follows PR #2870.

For an independent waveform comparison, use a separate Python reference environment with MLX 0.32.3, mlx-lm 0.31.3, Transformers 5.12.1, NumPy 2.3.5 and SoundFile 0.13.1. The validated Mac environment used Torch 2.13.0 and TorchAudio 2.11.0 for CPU resampling. Python is used only for this check:

python sglang_omni_mlx/native/ci/generate_music3_reference.py \
  --model-path "$MUSIC3_DATA/models/mlx-community_MiniMax-Music3-mxfp8" \
  --runtime-bin "$MUSIC3_RUNTIME/bin" --output-directory "$MUSIC3_PARITY"

It downloads only hash-verified reference source, generates both fixed cases with each implementation, and compares waveform length, peak error (at most 0.001) and RMS error (at most 0.0001). Retain parity.json, the reference pin and both sets of WAVs for review; these tolerances are acceptance thresholds, not measured results.

Add --prompt-only to the same command to compare native prompt text and token IDs with the frozen Python reference without loading model weights or generating audio. It covers nested Markdown bullets, Unicode whitespace and line separators, multilingual tag casing, malformed tags, and both waveform cases. The results are saved in prompt-parity.json.

Mac CI runs the Music3 HTTP checks and includes native/ci/model_golden/minimax-music3.json in its existing golden loop. Its cases cover English and Chinese, including a 250-frame acoustic overlap. Music3 HTTP tests run in a separate pytest process so the module-scoped ASR servers release their weights first. The runner logs its unified memory size. Apple M5 outputs are recorded after comparison with the frozen reference. For another chip, review and import the runner’s golden-outputs/minimax-music3.json artifact using the existing workflow:

python sglang_omni_mlx/native/ci/check_model_golden.py \
  --golden sglang_omni_mlx/native/ci/model_golden/minimax-music3.json \
  --import "$MUSIC3_GOLDEN_OUTPUT"

Exact PCM digests are checked per Apple chip; an unrecorded chip fails and uploads its WAVs and metadata for review.

Local validation on 2026-10-18 used an Apple M5 with 24 GiB of unified memory, macOS 26.6.2 and MLX 0.32.3. The native CMake build, 21 selected Music3 tests, seven golden-harness tests, six server model-kind checks and two existing Whisper supervisor lifecycle checks passed. The Music3 tests cover WAV/PCM responses, exact seeded repeats, invalid requests and a concurrent request refused as busy. They also cover the busy, deadline and disconnect paths above, each checking that the server returns to idle.

Reference case

Frames

Samples per channel

Peak PCM error

RMS PCM error

English short

50

63,902

3.052e-5

1.207e-7

Chinese acoustic overlap

250

319,879

3.052e-5

2.443e-7

Both outputs are finite, non-silent 32 kHz stereo. The errors above compare normalized PCM16 samples against the frozen Python reference; the maximum is one PCM16 level. The original acceptance thresholds were unchanged. A fresh native process reproduced both recorded golden hashes exactly. These checks cover fixed-input waveform parity.

To rerun the real-checkpoint HTTP and per-chip golden checks:

NATIVE_RUNTIME_BIN="$MUSIC3_RUNTIME/bin" CI_DATA_ROOT="$MUSIC3_DATA" \
  python -m pytest -q sglang_omni_mlx/native/ci/test_model_servers.py -k music3
python sglang_omni_mlx/native/ci/check_model_golden.py \
  --runtime-bin "$MUSIC3_RUNTIME/bin" --data-root "$MUSIC3_DATA" \
  --golden sglang_omni_mlx/native/ci/model_golden/minimax-music3.json \
  --outputs "$MUSIC3_GOLDEN_OUTPUTS"

Serial weight offload#

To alternate AR and DIT/DAV weights on one GPU, enable both offload components:

CUDA_VISIBLE_DEVICES=0 sgl-omni serve --model-path MiniMaxAI/MiniMax-Music3 --port 8000 \
  --stage-offload-components ar,dit

This CUDA mode serves one request at a time (one CFG pair, two engine rows), even when a higher concurrency is requested. AR completes the request before handing the GPU to DIT/DAV. Both stages share one process and device. torch_memory_saver==0.0.10 releases physical weight memory while retaining CUDA addresses; decode and RVQ graphs and acoustic compilation remain enabled. Acoustic breakable graphs remain opt-in. Explicit eager selections are respected.

The default --minimax_music3_ar.factory.serial_offload_source mmap snapshots finalized weights to bounded safetensors shards, preserving shared storage and strided views. Two shared 256 MiB pinned buffers upload mapped shards. The AR KV cache and graph workspaces remain resident. Startup pauses AR before loading acoustic weights, folds DAV normalization on CUDA, initializes acceleration, caches and pauses acoustic weights, then restores AR.

The cache directory defaults to music3-runtime-weights beside the Hugging Face Hub cache. Override it with --minimax_music3_ar.factory.serial_offload_cache_dir /path/on/disk. It must be writable, non-tmpfs storage with space for finalized AR and acoustic weights. Cache identity includes checkpoint content, runtime versions, dtype, layout and folding backend; caches publish atomically under a lock and invalid caches fail visibly. There is no automatic RAM fallback.

Hosts with sufficient RAM can explicitly select --minimax_music3_ar.factory.serial_offload_source ram, retaining pageable runtime weights and using the same staging buffers. Give KV memory an explicit budget, for example --minimax_music3_ar.engine.kv_cache_bytes 4294967296 for 4 GiB.

Validate repeated requests, audio output, GPU residency and host RSS on the target hardware under its intended memory limit before relying on this mode.

Music3 defaults to SGLang deterministic inference so attention reductions agree between eager and graph execution. The same seed is reproducible within one execution mode. Compare decoded waveforms numerically across eager, graph and compiled execution modes.

Run the opt-in HTTP integration test with a local checkpoint:

CUDA_VISIBLE_DEVICES=0 MINIMAX_MUSIC3_TEST_CHECKPOINT=/path/to/MiniMax-Music3 \
  pytest -q tests/test_model/test_minimax_music3_serial_offload.py

The test covers resident/offload execution, RAM/file sources, eager/graph/compiled execution and requested acoustic breakable graphs. It checks 100-frame and 250-frame stereo audio, waveform parity (relative RMS difference below 0.5% and correlation above 0.9999), exact repeated output, two overlapping submissions, 20 short requests and a final 100-frame request. Set MINIMAX_MUSIC3_TEST_REPEATED_REQUESTS to change the repetition count or MINIMAX_MUSIC3_TEST_MODES to select comma-separated mode names from the test. It records anonymous RSS, cgroup memory, physical GPU usage and wake latency alongside audio artifacts.

Set MINIMAX_MUSIC3_TEST_REQUIRE_32G_LIMIT=1 to require a cgroup memory limit of at most 32 GiB for file-backed modes. Set MINIMAX_MUSIC3_TEST_COLD_WAKE=1 to evict runtime shard pages before the first request. The memory context artifact records the limit and OOM event counters before and after each mode.

Validation Record#

Validated on one NVIDIA H200 on 2026-10-07 using the newest published hongccc/sglang-omni:dev image (published 2026-09-08), digest sha256:ebe4239e29a764ee3a2806385c061c5fd438a26f01458e503d3822dcba5790df. The container used repository pin sglang==0.5.21, torch_memory_saver==0.0.10, and PyTorch 2.13.0+cu130. The checkpoint revision was fbdf52fbaaca799592917417eb05f1899f1255ec, with a 4 GiB AR KV budget.

The six-mode file-backed run passed under a verified 32 GiB cgroup limit. Each offload mode completed 27 requests, including overlaps, 20 short repeats and the final reload. RAM eager and accelerated modes passed the same lifecycle under 64 GiB. Resident eager, graph-only, compilation-only, combined acceleration and requested breakable modes passed waveform validation. All modes repeated exactly within their execution mode. Graph-only and eager offload matched resident eager byte for byte. The largest cross-mode relative RMS difference was 0.3497%, with minimum correlation 0.99999399. Decode/RVQ capture and acoustic acceleration initialized once per server across repeated handoffs.

Mode

Peak Anonymous RSS (GiB)

Peak Cgroup Memory (GiB)

Peak Physical GPU (GiB)

Resident eager

14.63

29.53

36.04

Resident accelerated

14.66

29.40

35.77

File-backed eager

14.65

31.20

26.72

File-backed accelerated

14.71

32.00

26.43

File-backed breakable

14.74

31.75

27.21

RAM eager

41.21

57.71

26.72

RAM accelerated

41.28

57.83

26.43

These sampled peaks include startup. Anonymous RSS sums server descendants; cgroup memory also includes reclaimable file cache. No OOM or OOM-kill events occurred. PyTorch allocator statistics retain virtual allocations during offload, so physical GPU usage is measured through NVML. Warm median AR/acoustic wakes were 4.83/2.66 seconds for file-backed acceleration and 3.05/1.71 seconds for RAM acceleration. The file-backed runtime cache occupied about 27 GiB on disk.

A separate accelerated file-backed run evicted runtime shard pages before its first request under the 32 GiB limit. Cold AR/acoustic wakes took 6.38/4.09 seconds; subsequent warm medians were 3.93/2.42 seconds. Its parity, overlap, three short repeats and final reload checks passed without OOM events.

Focused validation passed 103 tests, including physical release, unchanged addresses, exact restoration, aliases, bounded staging, cache failures/publication, cancellation and failed transitions. pre-commit run --all-files passed.

H200 validation does not establish RTX 3090 compatibility. BF16 acoustic weights, quantization, layer streaming and request batching are outside this offload milestone.

Generating Music#

Two fields carry the request. input is the lyrics; instructions is the caption that describes style, instrumentation, tempo and mood. Both are required, and both matter: the caption is what decides genre and arrangement, and the lyrics are what gets sung.

Structure tags in the lyrics ([Verse], [Chorus], [Bridge], [Outro]) steer the arrangement and are part of the prompt contract.

Put a tag on its own line. Normalization keeps only the tags on a line that starts with one and drops the rest of that line, so lyrics written next to a tag are silently lost:

"[Verse]\nWalking down the street"   ->   [start] [verse] Walking down the street
"[Verse] Walking down the street"    ->   [start] [verse]

The second form generates a song with that line missing, and nothing warns you.

A first song#

curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMaxAI/MiniMax-Music3",
    "input": "[Verse]\nSunday morning, quiet and slow\nSunlight through the half-drawn blinds\nCoffee cooling by the window\nNothing on my mind\n[Chorus]\nTake it easy, take it slow\nWe got nowhere else to go",
    "instructions": "A melancholic lo-fi hip-hop track with a mellow piano riff, soft vinyl crackle, and a slow steady drum beat at 85 BPM",
    "seed": 42,
    "max_new_tokens": 750
  }' \
  --output song_1.wav

max_new_tokens counts audio frames at 25 frames per second, so 750 frames is at most 30 seconds. It is a cap, not a target: the model ends the song itself when it emits the audio-end token. The request above typically fills the cap (~30 seconds). Raise the cap when you want room for an earlier natural ending; a response that stops short of the cap is the model finishing, not a truncation.

The response body is a 32 kHz stereo WAV.

In Python:

import requests

resp = requests.post(
    "http://localhost:8000/v1/audio/speech",
    json={
        "model": "MiniMaxAI/MiniMax-Music3",
        "input": "[Verse]\nSunday morning, quiet and slow\nSunlight through the half-drawn blinds\n[Chorus]\nTake it easy, take it slow\nWe got nowhere else to go",
        "instructions": "A melancholic lo-fi hip-hop track with a mellow piano riff at 85 BPM",
        "seed": 42,
        "max_new_tokens": 750,
    },
    timeout=600,
)
resp.raise_for_status()
with open("song_2.wav", "wb") as f:
    f.write(resp.content)

Or with the OpenAI client, since this is the standard speech endpoint:

from openai import OpenAI

client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY")

with client.audio.speech.with_streaming_response.create(
    model="MiniMaxAI/MiniMax-Music3",
    voice="default",
    input="[Verse]\nSunday morning, quiet and slow\nSunlight through the half-drawn blinds\n[Chorus]\nTake it easy, take it slow",
    instructions="A melancholic lo-fi hip-hop track with a mellow piano riff at 85 BPM",
    response_format="wav",
    extra_body={"seed": 42, "max_new_tokens": 750},
) as response:
    response.stream_to_file("song_3.wav")

Writing the caption#

The caption is the strongest control you have. Vague captions give generic arrangements; naming instruments, tempo and production character gives you the track you asked for.

# Genre, instrumentation, tempo, and a production note
curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMaxAI/MiniMax-Music3",
    "input": "[Chorus]\nWe are the fire that never dies\nBurning bright against the sky",
    "instructions": "An energetic arena rock anthem with distorted electric guitars, punchy live drums and a soaring male vocal at 130 BPM, wide stereo image, lightly compressed",
    "seed": 7,
    "max_new_tokens": 750
  }' \
  --output rock_1.wav

Long structured captions work too. The model was trained on descriptions that cover global attributes, emotional progression, and vocal detail, and it uses all of it:

import requests

caption = """Basic Attributes: bpm is 92, key is E minor, Electric Blues / Blues Rock.
Emotional Progression: confident and gritty from the outset, building tension through
call-and-response verses before releasing into an extended lead guitar solo.
Sonics: live and organic, warm mid-range, tube grit, relatively uncompressed so the
drums keep their natural attack.
Vocal: male baritone, gravelly and textured, conversational phrasing in the verses
turning melodic in the refrain."""

resp = requests.post(
    "http://localhost:8000/v1/audio/speech",
    json={
        "model": "MiniMaxAI/MiniMax-Music3",
        "input": "[Verse]\nI came up on a dirt road, nothing but a name\n[Chorus]\nAnd the thunder rolls the same",
        "instructions": caption,
        "seed": 11,
        "max_new_tokens": 1500,
    },
    timeout=600,
)
resp.raise_for_status()
with open("blues_1.wav", "wb") as f:
    f.write(resp.content)

The caption and lyrics are cleaned deterministically before tokenizing — Markdown residue is stripped and <|tag value|> forms are rewritten to tag is value — and the tokenized prompt is capped at 5,000 tokens.

Instrumental and short clips#

Lyrics are required and must be non-empty, so ask for an instrumental in the caption and keep the lyric line minimal:

curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMaxAI/MiniMax-Music3",
    "input": "[Intro]\n(instrumental)",
    "instructions": "An instrumental ambient piece, no vocals: warm analog pads, slow evolving texture, distant piano, 70 BPM",
    "seed": 3,
    "max_new_tokens": 250
  }' \
  --output ambient_1.wav

250 frames caps the clip at 10 seconds, which is the fastest way to audition a caption before committing to a full-length render. Short caps are usually reached rather than ended early, so these come back at the full length.

Reproducibility and variations#

A request is deterministic in its seed: the same lyrics, caption, seed and length return byte-identical audio. Changing only the seed gives another take of the same song — the way to explore alternatives without touching the prompt.

import requests

for seed in (1, 2, 3):
    resp = requests.post(
        "http://localhost:8000/v1/audio/speech",
        json={
            "model": "MiniMaxAI/MiniMax-Music3",
            "input": "[Verse]\nCity lights are calling out my name",
            "instructions": "A dreamy synthwave track with analog pads and a driving bassline at 110 BPM",
            "seed": seed,
            "max_new_tokens": 500,
        },
        timeout=600,
    )
    resp.raise_for_status()
    with open(f"take_{seed}.wav", "wb") as f:
        f.write(resp.content)

Omitting seed uses 0, which is still deterministic — it is a fixed seed, not a random one.

Reference outputs#

Five captions across five genres, rendered on a single H200 with the defaults this page documents and nothing else set. Each request is given in full, so pasting one back reproduces its clip.

Four of the five fill the 750-frame cap at 30.0 seconds. The J-pop one stops itself at 26.2 seconds, which is max_new_tokens behaving as a cap rather than a target — the model ended the song.

Lo-fi hip-hop

curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d "$(cat <<'EOF'
{
  "model": "MiniMaxAI/MiniMax-Music3",
  "input": "[Verse]\nWalking down the empty street at midnight\nStreetlights flicker like a broken dream\nI've got nothing but the sound of my own heartbeat\nEchoing through the silent concrete stream\n[Chorus]\nAnd I keep on walking\nTill the morning finds me\nLeave the night behind me",
  "instructions": "A melancholic lo-fi hip-hop track at 85 BPM in F minor: mellow Rhodes piano riff, soft vinyl crackle, dusty boom-bap drums with a laid-back swing, warm upright bass. Intimate bedroom production, gentle tape saturation, no bright cymbals.",
  "seed": 1,
  "max_new_tokens": 750,
  "response_format": "wav",
  "stream": false
}
EOF
)" \
  --output 00_lofi_hiphop.wav

J-pop

curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d "$(cat <<'EOF'
{
  "model": "MiniMaxAI/MiniMax-Music3",
  "input": "[Verse]\nMorning light is spilling through the curtain\nEvery colour waking up with me\n[Chorus]\nRun into the day and never look back\nEverything we wanted is ahead of us",
  "instructions": "A cheerful J-pop song at 128 BPM in C major: bright acoustic piano, chiming electric guitar, punchy four-on-the-floor drums, and a clear female lead vocal. Polished modern pop production, wide stereo, energetic and uplifting.",
  "seed": 2,
  "max_new_tokens": 750,
  "response_format": "wav",
  "stream": false
}
EOF
)" \
  --output 01_jpop_bright.wav

Synthwave, instrumental

curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d "$(cat <<'EOF'
{
  "model": "MiniMaxAI/MiniMax-Music3",
  "input": "[Intro]\n(instrumental)\n[Outro]\n(instrumental)",
  "instructions": "A moody synthwave instrumental at 100 BPM in D minor: pulsing analog bass arpeggio, gated reverb drum machine, wide atmospheric pads, and a soaring lead synth melody. Retro 1980s production, heavy chorus effect, cinematic and nocturnal.",
  "seed": 3,
  "max_new_tokens": 750,
  "response_format": "wav",
  "stream": false
}
EOF
)" \
  --output 02_synthwave_moody.wav

Acoustic folk

curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d "$(cat <<'EOF'
{
  "model": "MiniMaxAI/MiniMax-Music3",
  "input": "[Verse]\nI came up on a dirt road, nothing but a name\nCarried all my summers in a canvas bag\n[Chorus]\nAnd the river keeps on running\nLike it never learned to stay",
  "instructions": "A gentle acoustic folk ballad at 76 BPM in G major: fingerpicked steel-string guitar, soft brushed snare, subtle cello underneath, and a warm male vocal close to the microphone. Sparse and organic, natural room sound, very little compression.",
  "seed": 4,
  "max_new_tokens": 750,
  "response_format": "wav",
  "stream": false
}
EOF
)" \
  --output 03_acoustic_folk.wav

Cinematic orchestral

curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d "$(cat <<'EOF'
{
  "model": "MiniMaxAI/MiniMax-Music3",
  "input": "[Intro]\n(instrumental)\n[Chorus]\nRise above the ashes of the fallen sky\nWe were never meant to say goodbye",
  "instructions": "An epic cinematic orchestral piece at 90 BPM in E minor: sweeping string ostinato, powerful brass swells, timpani and taiko percussion, and a distant choir. Wide concert-hall reverb, dynamic build from restrained to triumphant, no drum kit.",
  "seed": 5,
  "max_new_tokens": 750,
  "response_format": "wav",
  "stream": false
}
EOF
)" \
  --output 04_orchestral_epic.wav

Guidance#

Both stages run classifier-free guidance, and neither is optional. There is no request field and no serve flag for it: the scale, the mask width and the solver’s own scale are fixed in the model, because this is how the reference implementation samples this checkpoint, and dropping guidance makes the model follow your caption and lyrics noticeably less closely.

The two halves are separate mechanisms that happen to share a name:

  • The AR stage runs each request twice per decode step. One row sees your real prompt; the other sees the same prompt with the whole caption-and-lyrics span, delimiters included, overwritten by <|audio_cfg|> — only the chat markers and <|audio_start|> survive. Every code is then drawn from uncond + (cond - uncond) * 1.5. For c0 the candidate set is additionally masked to the conditioned row’s own top 50 before the ordinary top-k sampling runs, so the guided draw cannot wander outside what the conditioned branch already considered plausible.

  • The acoustic stage applies its own guidance at scale 1.7 inside every one of the 30 solver steps.

The consequence you have to plan for is on the AR side: one request occupies two rows in the engine, not one. Both rows hold their own KV cache for the whole song, so a request costs twice the KV it would unguided. The visible follow-on is in Concurrency.

What it does not change is the request contract or where the randomness comes from: the guided draw is still keyed by the same per-request seed and frame position, so seeding behaves as described above. Guidance does shift which codes get drawn, so audio from a build without it is not comparable take-for-take — expect a different arrangement and a different length from the same seed, not a cleaned-up version of the same song.

Concurrency#

The server batches continuously. Admission defaults to 16 concurrent requests (max_running_requests=16), which is 32 decode rows, because guidance gives every request a second row. Raise admission at serve time with --minimax_music3_ar.engine.max_running_requests; the row count, the decode device graph and the RVQ depth device graph are all derived from it:

On Intel GPUs, first follow the Intel XPU installation instructions, then replace CUDA_VISIBLE_DEVICES in the commands below with ZE_AFFINITY_MASK (for example, ZE_AFFINITY_MASK=0 or ZE_AFFINITY_MASK=0,1).

CUDA_VISIBLE_DEVICES=0,1 sgl-omni serve --model-path MiniMaxAI/MiniMax-Music3 --port 8000 \
  --minimax_music3_ar.engine.max_running_requests 32

That serves 32 concurrent requests as 64 rows. Do not pass engine.cuda_graph_max_bs here: this model computes the cap itself so the graphs always cover the doubled batch, and a value you supply is discarded rather than honoured.

FP32 flex attention#

On CUDA, the default FP32 acoustic DIT uses PyTorch flex attention while retaining FP32 inputs and TF32 matmuls. To restore the previous SDPA path:

CUDA_VISIBLE_DEVICES=0,1 sgl-omni serve --model-path MiniMaxAI/MiniMax-Music3 --port 8000 \
  --dit_dav.factory.fp32_flex_attention false

The option resolves to flex attention only on CUDA with dtype=float32 and attention_backend=torch_sdpa; MUSA, XPU, and other dtype/backend combinations retain their existing attention path. Flex-attention output is not byte-identical to SDPA, so validate generated audio for your workload when comparing the two paths.

Send clients in parallel rather than in sequence:

from concurrent.futures import ThreadPoolExecutor

import requests

PROMPTS = [
    ("[Verse]\nMorning breaks over the harbour", "A gentle acoustic folk song with fingerpicked guitar at 90 BPM"),
    ("[Chorus]\nDance until the record stops", "A disco house track with four-on-the-floor drums and funk guitar at 122 BPM"),
    ("[Verse]\nSnow falls quiet on the pines", "A cinematic orchestral piece with strings and soft horns at 60 BPM"),
]


def render(index_and_prompt):
    index, (lyrics, caption) = index_and_prompt
    resp = requests.post(
        "http://localhost:8000/v1/audio/speech",
        json={
            "model": "MiniMaxAI/MiniMax-Music3",
            "input": lyrics,
            "instructions": caption,
            "seed": index,
            "max_new_tokens": 750,
        },
        timeout=900,
    )
    resp.raise_for_status()
    with open(f"track_{index}.wav", "wb") as f:
        f.write(resp.content)


with ThreadPoolExecutor(max_workers=4) as pool:
    list(pool.map(render, enumerate(PROMPTS)))

Concurrency is where this model is most efficient. The depth decoder costs the same per step whether one request or eight share the batch, so its per-request share falls as concurrency rises.

That flatness is also why guidance is cheap in time and expensive in memory. The AR step is bound by reading the backbone’s weights rather than by arithmetic, so the second row rides along in the same pass with little extra wall time, while doubling the KV each request holds. Size the pool for the rows, not the requests.

Request Parameters#

Parameter

Type

Default

Description

model

string

served model

Served model identifier

input

string

(required)

Lyrics. Non-empty. Use [Verse] / [Chorus] / [Bridge] / [Outro] on their own lines

instructions

string

(required)

Caption describing genre, instrumentation, tempo, mood and production. Non-empty

seed

int

0

Non-negative 64-bit integer. Fixes the output for a given request

max_new_tokens

int

9000

Cap on audio frames, 25 per second. Maximum 9,000 (six minutes). The model may finish earlier

response_format

string

"wav"

Output container

stream

bool

false

Must be false; this model’s external API is non-streaming

Reaching the cap returns the audio generated so far. Ending on the audio-end token stops the song early.

Parameters That The Model Rejects#

The request is refused rather than silently ignored, so a mistake is visible immediately:

Parameter

Reason

temperature, top_p, top_k, repetition_penalty

Sampling is fixed: guidance at scale 1.5, then top-k 50 with a per-request seeded draw. Rejected when set explicitly

voice

There is no speaker to select; the vocal comes from the caption

ref_audio, ref_text, language, task_type

No reference-audio conditioning and no language tag in this contract

speed

Only 1.0. Tempo belongs in the caption, e.g. “at 92 BPM”

stream: true

Not supported. This model’s external API is non-streaming

Notes#

Length and cost. Generation time scales with max_new_tokens. A 10-second clip is the cheapest way to iterate on a caption; render the full song once the style is right.

Prompt sensitivity. The prompt’s token ids and the position of <|audio_start|> seed the backbone KV cache, so whitespace and tag rewrites are not cosmetic — they change the audio. If you need a result to be reproducible, keep the lyrics and caption byte-identical.

Memory. mem_fraction_static defaults to 0.50 and budgets the SGLang backbone KV cache only; the acoustic stage’s DIT and DAV weights sit outside that fraction. It is a share of total device memory, so the absolute KV pool shrinks on smaller cards automatically. Lowering it does not reliably help — measured at 0.35 the pipeline is about 6% slower. Budget against rows rather than requests: guidance means the pool has to hold two sequences per concurrent request, so raising max_running_requests costs KV twice as fast as the number suggests.

Attention backends. The DIT accepts auto, torch_sdpa, fa and sage_attn through the dit_dav stage’s factory group; torch_sdpa is the default and measured the fastest of them for these shapes. cache_dit is an approximate, default-off option that trades audio quality for speed, and reducing the solver’s dit_steps below 30 is measurably a quality reduction rather than a free win.