PyTorch
Diffusers
audio
music
minimax-music-3
rvq
reference-audio
comfyui
bghira commited on
Commit
1203bba
·
verified ·
1 Parent(s): 231ade3

Add v1-v4 RVQ encoder collection and reference-audio integrations

Browse files
LICENSE-CODE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, Any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ library_name: pytorch
4
+ datasets:
5
+ - bghira/minimax-music3-rvq-reverse-distillation
6
+ tags:
7
+ - audio
8
+ - music
9
+ - minimax-music-3
10
+ - rvq
11
+ - reference-audio
12
+ - comfyui
13
+ - diffusers
14
+ ---
15
+
16
+ # Open RVQ Encoders for MiniMax Music 3
17
+
18
+ ## What this release does
19
+
20
+ MiniMax Music 3 normally creates its internal music codes from text and lyrics. The original audio-to-code encoder was not released. These models add an approximate path from a reference recording back to those codes.
21
+
22
+ The useful result is **0.8748 mean condition-replay cosine** on 130 held-out generated tracks. In plain English: after a reference track is reduced to eight integers per 25 Hz frame and replayed through the official language-model path, v4 recovers most of the conditioning signal that the diffusion model would have received from the original codes.
23
+
24
+ This is not 87% exact-token accuracy. It is not an audio-quality score. The RVQ code space is redundant: different code tuples can lead to nearly the same downstream condition. Exact token labels therefore understate functional accuracy.
25
+
26
+ The project started with a 41M-parameter, single-GPU community proof at **0.6633** replay cosine. The SimpleTuner experiments moved through width and music-feature alignment, then reached **0.8748** by making the acoustic codebooks autoregressive across RVQ depth.
27
+
28
+ Use **v4** unless reproducing an experiment.
29
+
30
+ ## Files
31
+
32
+ | File | Parameters | Experiment | Replay cosine |
33
+ |---|---:|---|---:|
34
+ | `minimax_music3_rvq_encoder_v1_41m_independent_heads.safetensors` | 40,978,944 | Baseline; eight independent heads | 0.7624* |
35
+ | `minimax_music3_rvq_encoder_v2_155m_wide_independent_heads.safetensors` | 154,736,064 | Wider shared encoder | 0.7698 |
36
+ | `minimax_music3_rvq_encoder_v3_155m_mert_aligned_independent_heads.safetensors` | 154,736,064 | v2 plus training-only MERT alignment | 0.7703 |
37
+ | `minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.safetensors` | 169,008,576 | Causal acoustic decoder across codebook depth | **0.8748** |
38
+
39
+ Each weight file has a same-named `.json` configuration file in [`encoders/`](encoders/).
40
+
41
+ \* The v1 replay evaluation used the final checkpoint. This collection ships the v1 card-recommended step-17,500 checkpoint. V2-v4 replay scores and shipped checkpoints are final checkpoints.
42
+
43
+ ![Condition replay comparison](assets/condition-embedding-replay-comparison.png)
44
+
45
+ Metric: per-track mean cosine between replayed and stored condition embeddings. Evaluation set: 130 exact-alignment records from the held-out split. True sampled codes score 0.9999 as the pipeline control. Release provenance is in [`experiment-summary.json`](experiment-summary.json). Full summary statistics and quantiles are in [`evaluation/condition-replay-aggregate.json`](evaluation/condition-replay-aggregate.json).
46
+
47
+ ## Experiment arc
48
+
49
+ ### Community proof
50
+
51
+ `Serveurperso` trained a 41M encoder on one GPU and demonstrated that exact token agreement was not required. Their checkpoint reached 0.6633 condition-replay cosine. Their implementation independently identified the 345-latent stitched hop, the extra warm-up code row, and the final partial-chunk interpolation rule. The SimpleTuner dataset and trainer work independently derived the same alignment contract.
52
+
53
+ That result changed the question from "can this work?" to "which errors matter downstream?"
54
+
55
+ ### v1: working baseline
56
+
57
+ - 41M parameters.
58
+ - DAV latents as input.
59
+ - One semantic head and seven independent acoustic heads.
60
+ - Teacher top-50 KL plus hard cross-entropy.
61
+ - μP initialization and μTransfer shape metadata.
62
+ - DDP training on the reverse-distillation corpus.
63
+ - 0.7624 replay cosine at the evaluated final checkpoint.
64
+
65
+ Detailed card: [`SimpleTuner/open-rvq-encoder-minimax-music3-41m-v1`](https://huggingface.co/SimpleTuner/open-rvq-encoder-minimax-music3-41m-v1).
66
+
67
+ ### v2: width
68
+
69
+ - Width increased from 512 to 1,088.
70
+ - Parameters increased from 41M to 155M.
71
+ - Polynomial decay with warm-up.
72
+ - Replay improved from 0.7624 to 0.7698.
73
+
74
+ Width helped token metrics. It barely moved the downstream replay metric. Capacity was not the main limit.
75
+
76
+ Detailed card: [`SimpleTuner/open-rvq-encoder-minimax-music3-155m-v2`](https://huggingface.co/SimpleTuner/open-rvq-encoder-minimax-music3-155m-v2).
77
+
78
+ ### v3: MERT alignment
79
+
80
+ - Same exported architecture as v2.
81
+ - Frozen MERT features used only as a training target.
82
+ - Mid-layer cosine alignment loss annealed to zero.
83
+ - No MERT weights or projection are present in the exported encoder.
84
+ - Replay improved from 0.7698 to 0.7703.
85
+
86
+ The measured gain was too small to justify an inference dependency or a larger MERT experiment on this corpus.
87
+
88
+ Detailed card: [`SimpleTuner/open-rvq-encoder-minimax-music3-155m-v3`](https://huggingface.co/SimpleTuner/open-rvq-encoder-minimax-music3-155m-v3).
89
+
90
+ ### v4: autoregressive depth
91
+
92
+ - Shared encoder kept at v2 width.
93
+ - The seven acoustic heads were replaced by a causal decoder.
94
+ - Codebook `k` is conditioned on the semantic code and acoustic codebooks `< k`.
95
+ - Parameters increased to 169M.
96
+ - Replay improved from 0.7703 to 0.8748.
97
+
98
+ Free-running exact acoustic top-1 decreased slightly. Teacher-forced acoustic top-1 increased to 18.42%. Replay improved by 0.1045. This is the central result: code-tuple compatibility matters more than matching one sampled token tuple.
99
+
100
+ Detailed card: [`SimpleTuner/open-rvq-encoder-minimax-music3-169m-v4`](https://huggingface.co/SimpleTuner/open-rvq-encoder-minimax-music3-169m-v4).
101
+
102
+ ## ComfyUI
103
+
104
+ Requirements:
105
+
106
+ - current ComfyUI with native MiniMax Music 3 support;
107
+ - official ComfyUI MiniMax diffusion model, text encoder, and DAV decoder;
108
+ - original `dav.pth`, because the Comfy DAV file is decoder-only;
109
+ - this repository cloned with Git LFS.
110
+
111
+ ```bash
112
+ git lfs install
113
+ git clone https://huggingface.co/SimpleTuner/open-rvq-encoder-minimax-music3
114
+
115
+ cd ComfyUI
116
+ python3 -m venv .venv
117
+ .venv/bin/pip install -r requirements.txt huggingface_hub
118
+
119
+ .venv/bin/hf download Comfy-Org/MiniMax-Music-3 \
120
+ diffusion_models/minimax_music3_dit_fp16.safetensors \
121
+ text_encoders/minimax_music3_text_encoder_pruned_int8_convrot.safetensors \
122
+ vae/minimax_music3_dav.safetensors \
123
+ --local-dir models
124
+
125
+ .venv/bin/hf download MiniMaxAI/MiniMax-Music3 dav.pth \
126
+ --local-dir models/vae
127
+
128
+ ln -s /path/to/open-rvq-encoder-minimax-music3/comfyui_open_rvq \
129
+ custom_nodes/comfyui_open_rvq
130
+ ```
131
+
132
+ Restart ComfyUI. Load [`comfyui_workflow_example.json`](comfyui_workflow_example.json). Upload a reference audio file. Select v4 in **MiniMax Music3 RVQ Reference Encoder Loader**.
133
+
134
+ The node package reads the encoder files directly from this clone. They can instead be placed in:
135
+
136
+ ```text
137
+ ComfyUI/models/minimax_music3_rvq_encoders/
138
+ ```
139
+
140
+ The tested graph used ComfyUI 0.33.0, one NVIDIA L40S, the pruned int8 text encoder, the fp16 diffusion model, a 32.28-second held-out reference, five Euler steps, and the v4 encoder. It completed and produced a full-length non-silent stereo FLAC. Use 30 steps for normal output.
141
+
142
+ ## Diffusers
143
+
144
+ The MiniMax Music 3 Diffusers integration is currently installed from its integration commit:
145
+
146
+ ```bash
147
+ python3 -m venv .venv
148
+ source .venv/bin/activate
149
+ pip install \
150
+ git+https://github.com/huggingface/diffusers@dafe3733fcfdbf3c48915fe77be3aef65b5d6a2d \
151
+ transformers accelerate safetensors huggingface_hub soundfile torchaudio
152
+ ```
153
+
154
+ Download [`minimax_music3_reference_adapter.py`](minimax_music3_reference_adapter.py) into the project, then:
155
+
156
+ ```python
157
+ import soundfile as sf
158
+ import torch
159
+ from diffusers import ModularPipeline
160
+
161
+ from minimax_music3_reference_adapter import (
162
+ MiniMaxMusic3ReferenceAdapter,
163
+ install_diffusers_reference_adapter,
164
+ )
165
+
166
+ install_diffusers_reference_adapter()
167
+
168
+ pipe = ModularPipeline.from_pretrained(
169
+ "MiniMaxAI/MiniMax-Music3",
170
+ torch_dtype=torch.bfloat16,
171
+ ).to("cuda")
172
+ adapter = MiniMaxMusic3ReferenceAdapter.from_pretrained()
173
+
174
+ audio, sample_rate = sf.read("reference.flac", always_2d=True)
175
+ waveform = torch.from_numpy(audio.T).float()
176
+ generator = torch.Generator(device="cpu").manual_seed(42)
177
+
178
+ frame_hiddens, predicted_codes = adapter.encode_reference(
179
+ pipe,
180
+ waveform,
181
+ sample_rate,
182
+ prompt="rock",
183
+ lyrics="[instrumental]",
184
+ generator=generator,
185
+ device="cuda",
186
+ )
187
+
188
+ result = pipe(
189
+ frame_hiddens=frame_hiddens,
190
+ generator=torch.Generator(device="cpu").manual_seed(42),
191
+ num_inference_steps=30,
192
+ output_type="pt",
193
+ )
194
+ ```
195
+
196
+ The patch only adds a precomputed-`frame_hiddens` bypass to the modular pipeline. It does not replace MiniMax model code.
197
+
198
+ ## Limits
199
+
200
+ - These are approximate encoders, not the original MiniMax encoder.
201
+ - No original encoder weights or source were used.
202
+ - Training data is synthetic MiniMax Music 3 output, not MiniMax's training set.
203
+ - Real-audio generalization is not established.
204
+ - Context is 128 frames, or 5.12 seconds. There is no cross-window encoder state.
205
+ - Reference replay still needs the official MiniMax language model and RVQ depth decoder.
206
+ - v4 uses greedy code selection. Other decoding strategies remain untested.
207
+
208
+ ## Credits
209
+
210
+ - [`bghira`](https://huggingface.co/bghira): reverse-distillation dataset, independent alignment derivation, SimpleTuner trainer, compute, v1-v4 runs, and replay experiments.
211
+ - [`marduk191`](https://huggingface.co/marduk191): WAV/code samples, early mel-encoder proof, corpus-scale observations, and encoder experiments.
212
+ - [`scragnog`](https://huggingface.co/scragnog): SimpleTuner calibration, relative-weight analysis, caption-cache and rollout-seam findings, and GGML interoperability tests.
213
+ - [`Serveurperso`](https://huggingface.co/Serveurperso): independent 41M encoder, single-GPU proof, replay evaluation, and independent stitched-timeline findings.
214
+ - [`dernet`](https://huggingface.co/dernet): RVQ supervision, internal-alignment, and tokenizer reverse-engineering analysis.
215
+
216
+ ## License
217
+
218
+ `minimax_music3_reference_adapter.py` and `comfyui_open_rvq/` are Apache-2.0 licensed. See [`LICENSE-CODE`](LICENSE-CODE).
219
+
220
+ The encoder weights were trained on outputs from MiniMax Music 3. Use of the weights, official model components, and generated output remains subject to the applicable MiniMax Music 3 terms. The MERT license also applies to the v3 training provenance; no MERT weights are distributed here.
assets/condition-embedding-replay-comparison.png ADDED
comfyui_open_rvq/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Copyright 2026 SimpleTuner contributors
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
5
+
6
+ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
comfyui_open_rvq/nodes.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 SimpleTuner contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ from __future__ import annotations
10
+
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ import torch
15
+
16
+ import comfy.model_management
17
+ import comfy.model_prefetch
18
+ import comfy.ops
19
+ import folder_paths
20
+ from comfy.ldm.minimax_music.ar import AUDIO_FRAMES_PER_SECOND, CFG_SCALE, CFG_TOP_K, MiniMaxMusic3AR
21
+ from comfy.text_encoders.minimax_music import MiniMaxMusic3TEModel
22
+
23
+
24
+ REPO_ROOT = Path(__file__).resolve().parents[1]
25
+ if str(REPO_ROOT) not in sys.path:
26
+ sys.path.insert(0, str(REPO_ROOT))
27
+
28
+ from minimax_music3_reference_adapter import MiniMaxMusic3ReferenceAdapter # noqa: E402
29
+
30
+
31
+ ENCODER_FOLDER = "minimax_music3_rvq_encoders"
32
+ folder_paths.add_model_folder_path(ENCODER_FOLDER, str(Path(folder_paths.models_dir) / ENCODER_FOLDER), is_default=True)
33
+ bundled_encoders = REPO_ROOT / "encoders"
34
+ if bundled_encoders.is_dir():
35
+ folder_paths.add_model_folder_path(ENCODER_FOLDER, str(bundled_encoders))
36
+
37
+
38
+ def _teacher_depth_hidden(model: MiniMaxMusic3AR, hidden, codes, execution_dtype):
39
+ decoder = model.model.audio_decoder
40
+ sequence = [decoder.projection(hidden).unsqueeze(1)]
41
+ semantic = model._embed_c0(codes[:, 0], execution_dtype)
42
+ sequence.append(decoder.projection(semantic).unsqueeze(1))
43
+ hidden_parts = []
44
+ for index in range(1, model.num_codebooks):
45
+ depth_hidden = decoder(torch.cat(sequence, dim=1))[:, -1]
46
+ hidden_parts.append(depth_hidden[:1])
47
+ if index < model.num_codebooks - 1:
48
+ embedding = model.model.audio_extra_embedding(
49
+ codes[:, index] + (index - 1) * model.audio_vocab_size,
50
+ out_dtype=execution_dtype,
51
+ )
52
+ sequence.append(decoder.projection(embedding).unsqueeze(1))
53
+ return torch.cat(hidden_parts, dim=-1)
54
+
55
+
56
+ def _run_depth(model: MiniMaxMusic3AR, device, execution_dtype, core):
57
+ decoder = model.model.audio_decoder
58
+ queue = comfy.model_prefetch.make_prefetch_queue(
59
+ [[decoder, model.model.audio_extra_embedding]],
60
+ device,
61
+ {"prefetch_dynamic_vbars": True},
62
+ )
63
+ comfy.model_prefetch.prefetch_queue_pop(
64
+ queue,
65
+ device,
66
+ decoder,
67
+ execution_dtype,
68
+ core=core,
69
+ enable_graph=False,
70
+ )
71
+ comfy.model_prefetch.prefetch_queue_pop(queue, device, None)
72
+
73
+
74
+ @torch.inference_mode()
75
+ def replay_reference_codes(model: MiniMaxMusic3AR, input_ids, codes, seed, device, cfg_scale, top_k):
76
+ if codes.ndim != 2 or codes.shape[1] != model.num_codebooks or codes.shape[0] == 0:
77
+ raise ValueError(f"reference codes must have shape [frames, {model.num_codebooks}]")
78
+ prompt_tokens = int(input_ids.shape[1])
79
+ input_ids = input_ids.to(device)
80
+ codes = codes.to(device=device, dtype=torch.long)
81
+ execution_dtype = torch.bfloat16 if comfy.model_management.should_use_bf16(device) else torch.float32
82
+ unconditioned = input_ids.clone()
83
+ from comfy.ldm.minimax_music.prompt import AUDIO_CODE_OFFSET, SPECIAL_TOKEN_IDS
84
+
85
+ unconditioned[:, 1:-2] = SPECIAL_TOKEN_IDS["<|audio_cfg|>"]
86
+ text_ids = torch.cat((input_ids, unconditioned), dim=0)
87
+ if model.model.pruned_embedding:
88
+ text_embeds = model.model.embed_tokens_prefill(text_ids, out_dtype=execution_dtype)
89
+ else:
90
+ text_embeds = model.model.embed_tokens(text_ids, out_dtype=execution_dtype)
91
+ past = model.model.init_kv_cache(2, prompt_tokens + codes.shape[0] + 2, device, execution_dtype)
92
+ output = model.model(None, embeds=text_embeds, past_key_values=past, dtype=execution_dtype)
93
+ last_hidden, past = output[0][:, -1], output[2]
94
+ from comfy.ldm.minimax_music.ar import derive_seed
95
+
96
+ generator = torch.Generator(device=device).manual_seed(derive_seed(seed, "ar"))
97
+ vocab_mask = None
98
+ if not model.model.pruned_lm_head:
99
+ vocab_mask = torch.ones(model.model.vocab_size, dtype=torch.bool, device=device)
100
+ vocab_mask[AUDIO_CODE_OFFSET : AUDIO_CODE_OFFSET + 16384] = False
101
+ vocab_mask[SPECIAL_TOKEN_IDS["<|audio_end|>"]] = False
102
+
103
+ warmup_c0, warmup_or_stop, stop_token = model._sample_c0(last_hidden, cfg_scale, top_k, generator, vocab_mask)
104
+ if int(warmup_or_stop.item()) == stop_token:
105
+ raise ValueError("the selected seed ended during the required AR warm-up frame")
106
+ warmup_c0 = warmup_c0.repeat(2)
107
+ warmup_embed = model._embed_c0(warmup_c0, execution_dtype)
108
+ warmup: dict[str, torch.Tensor] = {}
109
+
110
+ def warmup_core():
111
+ warmup["codes"], _ = model._depth_codes(
112
+ last_hidden,
113
+ warmup_c0,
114
+ warmup_embed,
115
+ generator,
116
+ execution_dtype,
117
+ cfg_scale,
118
+ top_k,
119
+ )
120
+
121
+ _run_depth(model, device, execution_dtype, warmup_core)
122
+ feedback = model._embed_audio_frame(warmup["codes"], execution_dtype)
123
+ output = model.model(None, embeds=feedback, past_key_values=past, dtype=execution_dtype)
124
+ last_hidden, past = output[0][:, -1], output[2]
125
+
126
+ frames = []
127
+ for frame_index in range(codes.shape[0]):
128
+ comfy.model_management.throw_exception_if_processing_interrupted()
129
+ frame_codes = codes[frame_index].unsqueeze(0).repeat(2, 1)
130
+ result: dict[str, torch.Tensor] = {}
131
+
132
+ def teacher_core():
133
+ result["hidden"] = _teacher_depth_hidden(model, last_hidden, frame_codes, execution_dtype)
134
+
135
+ _run_depth(model, device, execution_dtype, teacher_core)
136
+ frames.append(torch.cat((last_hidden[:1], result["hidden"]), dim=-1)[0].cpu())
137
+ if frame_index + 1 < codes.shape[0]:
138
+ feedback = model._embed_audio_frame(frame_codes, execution_dtype)
139
+ output = model.model(None, embeds=feedback, past_key_values=past, dtype=execution_dtype)
140
+ last_hidden, past = output[0][:, -1], output[2]
141
+ return torch.stack(frames)
142
+
143
+
144
+ _ORIGINAL_ENCODE_TOKEN_WEIGHTS = MiniMaxMusic3TEModel.encode_token_weights
145
+
146
+
147
+ def _encode_token_weights_with_reference(self, token_weight_pairs):
148
+ codes = token_weight_pairs.get("minimax_reference_codes")
149
+ if codes is None:
150
+ return _ORIGINAL_ENCODE_TOKEN_WEIGHTS(self, token_weight_pairs)
151
+ token_ids = [token for token, _ in token_weight_pairs["minimax_music3"][0]]
152
+ input_ids = torch.tensor([token_ids], dtype=torch.long)
153
+ hidden = replay_reference_codes(
154
+ self,
155
+ input_ids,
156
+ codes,
157
+ int(token_weight_pairs["seed"]),
158
+ self.execution_device,
159
+ float(token_weight_pairs["cfg_scale"]),
160
+ int(token_weight_pairs["top_k"]),
161
+ )
162
+ return hidden.unsqueeze(0), None, {}
163
+
164
+
165
+ if not getattr(MiniMaxMusic3TEModel, "_simpletuner_reference_patch", False):
166
+ MiniMaxMusic3TEModel.encode_token_weights = _encode_token_weights_with_reference
167
+ MiniMaxMusic3TEModel._simpletuner_reference_patch = True
168
+
169
+
170
+ class MiniMaxMusic3RVQReferenceEncoderLoader:
171
+ @classmethod
172
+ def INPUT_TYPES(cls):
173
+ encoders = [name for name in folder_paths.get_filename_list(ENCODER_FOLDER) if name.lower().endswith(".safetensors")]
174
+ dav_files = [name for name in folder_paths.get_filename_list("vae") if name.lower().endswith((".pth", ".pt"))]
175
+ return {"required": {"encoder": (encoders,), "dav": (dav_files,)}}
176
+
177
+ RETURN_TYPES = ("MINIMAX_MUSIC3_RVQ_REFERENCE_ENCODER",)
178
+ RETURN_NAMES = ("reference_encoder",)
179
+ FUNCTION = "load"
180
+ CATEGORY = "loaders/minimax music"
181
+
182
+ def load(self, encoder, dav):
183
+ encoder_path = Path(folder_paths.get_full_path_or_raise(ENCODER_FOLDER, encoder))
184
+ config_path = encoder_path.with_suffix(".json")
185
+ if not config_path.is_file():
186
+ raise FileNotFoundError(f"Missing RVQ encoder config next to weights: {config_path.name}")
187
+ dav_path = folder_paths.get_full_path_or_raise("vae", dav)
188
+ return (MiniMaxMusic3ReferenceAdapter.from_files(encoder_path, config_path, dav_path),)
189
+
190
+
191
+ class MiniMaxMusic3ReferenceAudioEncode:
192
+ @classmethod
193
+ def INPUT_TYPES(cls):
194
+ return {
195
+ "required": {
196
+ "clip": ("CLIP",),
197
+ "reference_encoder": ("MINIMAX_MUSIC3_RVQ_REFERENCE_ENCODER",),
198
+ "audio": ("AUDIO",),
199
+ "caption": ("STRING", {"multiline": True, "dynamicPrompts": True}),
200
+ "lyrics": ("STRING", {"multiline": True, "dynamicPrompts": True}),
201
+ "seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
202
+ },
203
+ "optional": {
204
+ "cfg_scale": ("FLOAT", {"default": CFG_SCALE, "min": 0.0, "max": 100.0, "step": 0.1}),
205
+ "top_k": ("INT", {"default": CFG_TOP_K, "min": 1, "max": 16384}),
206
+ },
207
+ }
208
+
209
+ RETURN_TYPES = ("CONDITIONING", "FLOAT")
210
+ RETURN_NAMES = ("conditioning", "seconds")
211
+ FUNCTION = "encode"
212
+ CATEGORY = "conditioning/minimax music"
213
+
214
+ def encode(self, clip, reference_encoder, audio, caption, lyrics, seed, cfg_scale=CFG_SCALE, top_k=CFG_TOP_K):
215
+ codes = reference_encoder.predict_codes(
216
+ audio["waveform"],
217
+ int(audio["sample_rate"]),
218
+ device=comfy.model_management.get_torch_device(),
219
+ )
220
+ tokens = clip.tokenize(
221
+ caption,
222
+ lyrics=lyrics,
223
+ seed=seed,
224
+ max_audio_frames=codes.shape[0],
225
+ cfg_scale=cfg_scale,
226
+ top_k=top_k,
227
+ )
228
+ tokens["minimax_reference_codes"] = codes
229
+ conditioning = clip.encode_from_tokens_scheduled(tokens)
230
+ for cond in conditioning:
231
+ hidden = cond[0]
232
+ cond[1]["conditioning_scale"] = torch.ones(
233
+ (hidden.shape[0], 1, 1),
234
+ device=hidden.device,
235
+ dtype=hidden.dtype,
236
+ )
237
+ return conditioning, codes.shape[0] / AUDIO_FRAMES_PER_SECOND
238
+
239
+
240
+ NODE_CLASS_MAPPINGS = {
241
+ "MiniMaxMusic3RVQReferenceEncoderLoader": MiniMaxMusic3RVQReferenceEncoderLoader,
242
+ "MiniMaxMusic3ReferenceAudioEncode": MiniMaxMusic3ReferenceAudioEncode,
243
+ }
244
+
245
+ NODE_DISPLAY_NAME_MAPPINGS = {
246
+ "MiniMaxMusic3RVQReferenceEncoderLoader": "MiniMax Music3 RVQ Reference Encoder Loader",
247
+ "MiniMaxMusic3ReferenceAudioEncode": "MiniMax Music3 Reference Audio Encode",
248
+ }
comfyui_workflow_example.json ADDED
@@ -0,0 +1,700 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "f9df4058-b0bd-409c-a2e7-c41c8d68d56f",
3
+ "revision": 0,
4
+ "last_node_id": 46,
5
+ "last_link_id": 12,
6
+ "nodes": [
7
+ {
8
+ "id": 6,
9
+ "type": "UNETLoader",
10
+ "pos": [
11
+ 40,
12
+ 80
13
+ ],
14
+ "size": [
15
+ 480,
16
+ 90
17
+ ],
18
+ "flags": {},
19
+ "order": 0,
20
+ "mode": 0,
21
+ "showAdvanced": false,
22
+ "inputs": [
23
+ {
24
+ "localized_name": "unet_name",
25
+ "name": "unet_name",
26
+ "type": "COMBO",
27
+ "widget": {
28
+ "name": "unet_name"
29
+ }
30
+ }
31
+ ],
32
+ "outputs": [
33
+ {
34
+ "localized_name": "MODEL",
35
+ "name": "MODEL",
36
+ "type": "MODEL",
37
+ "links": [
38
+ 9
39
+ ]
40
+ }
41
+ ],
42
+ "properties": {
43
+ "cnr_id": "comfy-core",
44
+ "ver": "0.31.0",
45
+ "Node name for S&R": "UNETLoader",
46
+ "models": [
47
+ {
48
+ "name": "minimax_music3_dit_fp16.safetensors",
49
+ "url": "https://huggingface.co/Comfy-Org/MiniMax-Music-3/resolve/main/diffusion_models/minimax_music3_dit_fp16.safetensors",
50
+ "directory": "diffusion_models"
51
+ }
52
+ ]
53
+ },
54
+ "widgets_values": [
55
+ "minimax_music3_dit_fp16.safetensors",
56
+ "default"
57
+ ]
58
+ },
59
+ {
60
+ "id": 3,
61
+ "type": "CLIPLoader",
62
+ "pos": [
63
+ 40,
64
+ 250
65
+ ],
66
+ "size": [
67
+ 480,
68
+ 120
69
+ ],
70
+ "flags": {},
71
+ "order": 1,
72
+ "mode": 0,
73
+ "inputs": [
74
+ {
75
+ "localized_name": "clip_name",
76
+ "name": "clip_name",
77
+ "type": "COMBO",
78
+ "widget": {
79
+ "name": "clip_name"
80
+ }
81
+ }
82
+ ],
83
+ "outputs": [
84
+ {
85
+ "localized_name": "CLIP",
86
+ "name": "CLIP",
87
+ "type": "CLIP",
88
+ "links": [
89
+ 1
90
+ ]
91
+ }
92
+ ],
93
+ "properties": {
94
+ "cnr_id": "comfy-core",
95
+ "ver": "0.31.0",
96
+ "Node name for S&R": "CLIPLoader",
97
+ "models": [
98
+ {
99
+ "name": "minimax_music3_text_encoder_pruned_int8_convrot.safetensors",
100
+ "url": "https://huggingface.co/Comfy-Org/MiniMax-Music-3/resolve/main/text_encoders/minimax_music3_text_encoder_pruned_int8_convrot.safetensors",
101
+ "directory": "text_encoders"
102
+ }
103
+ ]
104
+ },
105
+ "widgets_values": [
106
+ "minimax_music3_text_encoder_pruned_int8_convrot.safetensors",
107
+ "minimax",
108
+ "default"
109
+ ]
110
+ },
111
+ {
112
+ "id": 7,
113
+ "type": "VAELoader",
114
+ "pos": [
115
+ 40,
116
+ 740
117
+ ],
118
+ "size": [
119
+ 480,
120
+ 70
121
+ ],
122
+ "flags": {},
123
+ "order": 2,
124
+ "mode": 0,
125
+ "inputs": [
126
+ {
127
+ "localized_name": "vae_name",
128
+ "name": "vae_name",
129
+ "type": "COMBO",
130
+ "widget": {
131
+ "name": "vae_name"
132
+ }
133
+ }
134
+ ],
135
+ "outputs": [
136
+ {
137
+ "localized_name": "VAE",
138
+ "name": "VAE",
139
+ "type": "VAE",
140
+ "links": [
141
+ 11
142
+ ]
143
+ }
144
+ ],
145
+ "properties": {
146
+ "cnr_id": "comfy-core",
147
+ "ver": "0.31.0",
148
+ "Node name for S&R": "VAELoader",
149
+ "models": [
150
+ {
151
+ "name": "minimax_music3_dav.safetensors",
152
+ "url": "https://huggingface.co/Comfy-Org/MiniMax-Music-3/resolve/main/vae/minimax_music3_dav.safetensors",
153
+ "directory": "vae"
154
+ }
155
+ ]
156
+ },
157
+ "widgets_values": [
158
+ "minimax_music3_dav.safetensors"
159
+ ]
160
+ },
161
+ {
162
+ "id": 10,
163
+ "type": "ConditioningZeroOut",
164
+ "pos": [
165
+ 1390,
166
+ 440
167
+ ],
168
+ "size": [
169
+ 230,
170
+ 40
171
+ ],
172
+ "flags": {},
173
+ "order": 3,
174
+ "mode": 0,
175
+ "inputs": [
176
+ {
177
+ "localized_name": "conditioning",
178
+ "name": "conditioning",
179
+ "type": "CONDITIONING",
180
+ "link": 5
181
+ }
182
+ ],
183
+ "outputs": [
184
+ {
185
+ "localized_name": "CONDITIONING",
186
+ "name": "CONDITIONING",
187
+ "type": "CONDITIONING",
188
+ "links": [
189
+ 7
190
+ ]
191
+ }
192
+ ],
193
+ "properties": {
194
+ "cnr_id": "comfy-core",
195
+ "ver": "0.31.0",
196
+ "Node name for S&R": "ConditioningZeroOut"
197
+ },
198
+ "widgets_values": []
199
+ },
200
+ {
201
+ "id": 15,
202
+ "type": "EmptyMiniMaxMusic3LatentAudio",
203
+ "pos": [
204
+ 1390,
205
+ 220
206
+ ],
207
+ "size": [
208
+ 350,
209
+ 90
210
+ ],
211
+ "flags": {},
212
+ "order": 4,
213
+ "mode": 0,
214
+ "inputs": [
215
+ {
216
+ "localized_name": "seconds",
217
+ "name": "seconds",
218
+ "type": "FLOAT",
219
+ "widget": {
220
+ "name": "seconds"
221
+ },
222
+ "link": 6
223
+ }
224
+ ],
225
+ "outputs": [
226
+ {
227
+ "localized_name": "LATENT",
228
+ "name": "LATENT",
229
+ "type": "LATENT",
230
+ "links": [
231
+ 8
232
+ ]
233
+ }
234
+ ],
235
+ "properties": {
236
+ "cnr_id": "comfy-core",
237
+ "ver": "0.31.0",
238
+ "Node name for S&R": "EmptyMiniMaxMusic3LatentAudio"
239
+ },
240
+ "widgets_values": [
241
+ 120,
242
+ 1
243
+ ]
244
+ },
245
+ {
246
+ "id": 9,
247
+ "type": "KSampler",
248
+ "pos": [
249
+ 1740,
250
+ 220
251
+ ],
252
+ "size": [
253
+ 370,
254
+ 270
255
+ ],
256
+ "flags": {},
257
+ "order": 5,
258
+ "mode": 0,
259
+ "inputs": [
260
+ {
261
+ "localized_name": "model",
262
+ "name": "model",
263
+ "type": "MODEL",
264
+ "link": 9
265
+ },
266
+ {
267
+ "localized_name": "positive",
268
+ "name": "positive",
269
+ "type": "CONDITIONING",
270
+ "link": 4
271
+ },
272
+ {
273
+ "localized_name": "negative",
274
+ "name": "negative",
275
+ "type": "CONDITIONING",
276
+ "link": 7
277
+ },
278
+ {
279
+ "localized_name": "latent_image",
280
+ "name": "latent_image",
281
+ "type": "LATENT",
282
+ "link": 8
283
+ },
284
+ {
285
+ "localized_name": "seed",
286
+ "name": "seed",
287
+ "type": "INT",
288
+ "widget": {
289
+ "name": "seed"
290
+ }
291
+ }
292
+ ],
293
+ "outputs": [
294
+ {
295
+ "localized_name": "LATENT",
296
+ "name": "LATENT",
297
+ "type": "LATENT",
298
+ "links": [
299
+ 10
300
+ ]
301
+ }
302
+ ],
303
+ "properties": {
304
+ "cnr_id": "comfy-core",
305
+ "ver": "0.31.0",
306
+ "Node name for S&R": "KSampler"
307
+ },
308
+ "widgets_values": [
309
+ 42,
310
+ "fixed",
311
+ 30,
312
+ 1.0,
313
+ "euler",
314
+ "simple",
315
+ 1
316
+ ]
317
+ },
318
+ {
319
+ "id": 12,
320
+ "type": "VAEDecodeAudio",
321
+ "pos": [
322
+ 2160,
323
+ 220
324
+ ],
325
+ "size": [
326
+ 230,
327
+ 60
328
+ ],
329
+ "flags": {},
330
+ "order": 6,
331
+ "mode": 0,
332
+ "inputs": [
333
+ {
334
+ "localized_name": "samples",
335
+ "name": "samples",
336
+ "type": "LATENT",
337
+ "link": 10
338
+ },
339
+ {
340
+ "localized_name": "vae",
341
+ "name": "vae",
342
+ "type": "VAE",
343
+ "link": 11
344
+ }
345
+ ],
346
+ "outputs": [
347
+ {
348
+ "localized_name": "AUDIO",
349
+ "name": "AUDIO",
350
+ "type": "AUDIO",
351
+ "links": [
352
+ 12
353
+ ]
354
+ }
355
+ ],
356
+ "properties": {
357
+ "cnr_id": "comfy-core",
358
+ "ver": "0.31.0",
359
+ "Node name for S&R": "VAEDecodeAudio"
360
+ },
361
+ "widgets_values": []
362
+ },
363
+ {
364
+ "id": 13,
365
+ "type": "MiniMaxMusic3ReferenceAudioEncode",
366
+ "pos": [
367
+ 810,
368
+ 220
369
+ ],
370
+ "size": [
371
+ 520,
372
+ 640
373
+ ],
374
+ "flags": {},
375
+ "order": 8,
376
+ "mode": 0,
377
+ "showAdvanced": true,
378
+ "inputs": [
379
+ {
380
+ "localized_name": "clip",
381
+ "name": "clip",
382
+ "type": "CLIP",
383
+ "link": 1
384
+ },
385
+ {
386
+ "localized_name": "reference_encoder",
387
+ "name": "reference_encoder",
388
+ "type": "MINIMAX_MUSIC3_RVQ_REFERENCE_ENCODER",
389
+ "link": 2
390
+ },
391
+ {
392
+ "localized_name": "audio",
393
+ "name": "audio",
394
+ "type": "AUDIO",
395
+ "link": 3
396
+ },
397
+ {
398
+ "localized_name": "caption",
399
+ "name": "caption",
400
+ "type": "STRING",
401
+ "widget": {
402
+ "name": "caption"
403
+ }
404
+ },
405
+ {
406
+ "localized_name": "lyrics",
407
+ "name": "lyrics",
408
+ "type": "STRING",
409
+ "widget": {
410
+ "name": "lyrics"
411
+ }
412
+ },
413
+ {
414
+ "localized_name": "seed",
415
+ "name": "seed",
416
+ "type": "INT",
417
+ "widget": {
418
+ "name": "seed"
419
+ }
420
+ },
421
+ {
422
+ "localized_name": "cfg_scale",
423
+ "name": "cfg_scale",
424
+ "type": "FLOAT",
425
+ "widget": {
426
+ "name": "cfg_scale"
427
+ }
428
+ },
429
+ {
430
+ "localized_name": "top_k",
431
+ "name": "top_k",
432
+ "type": "INT",
433
+ "widget": {
434
+ "name": "top_k"
435
+ }
436
+ }
437
+ ],
438
+ "outputs": [
439
+ {
440
+ "localized_name": "conditioning",
441
+ "name": "conditioning",
442
+ "type": "CONDITIONING",
443
+ "links": [
444
+ 4,
445
+ 5
446
+ ]
447
+ },
448
+ {
449
+ "localized_name": "seconds",
450
+ "name": "seconds",
451
+ "type": "FLOAT",
452
+ "links": [
453
+ 6
454
+ ]
455
+ }
456
+ ],
457
+ "properties": {
458
+ "cnr_id": "comfyui_open_rvq",
459
+ "Node name for S&R": "MiniMaxMusic3ReferenceAudioEncode"
460
+ },
461
+ "widgets_values": [
462
+ "rock",
463
+ "[instrumental]",
464
+ 299871780,
465
+ 1.5,
466
+ 50
467
+ ]
468
+ },
469
+ {
470
+ "id": 44,
471
+ "type": "MiniMaxMusic3RVQReferenceEncoderLoader",
472
+ "pos": [
473
+ 40,
474
+ 420
475
+ ],
476
+ "size": [
477
+ 650,
478
+ 100
479
+ ],
480
+ "flags": {},
481
+ "order": 9,
482
+ "mode": 0,
483
+ "inputs": [],
484
+ "outputs": [
485
+ {
486
+ "localized_name": "reference_encoder",
487
+ "name": "reference_encoder",
488
+ "type": "MINIMAX_MUSIC3_RVQ_REFERENCE_ENCODER",
489
+ "links": [
490
+ 2
491
+ ]
492
+ }
493
+ ],
494
+ "properties": {
495
+ "cnr_id": "comfyui_open_rvq",
496
+ "Node name for S&R": "MiniMaxMusic3RVQReferenceEncoderLoader",
497
+ "models": [
498
+ {
499
+ "name": "minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.safetensors",
500
+ "url": "https://huggingface.co/SimpleTuner/open-rvq-encoder-minimax-music3/resolve/main/encoders/minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.safetensors",
501
+ "directory": "minimax_music3_rvq_encoders"
502
+ },
503
+ {
504
+ "name": "dav.pth",
505
+ "url": "https://huggingface.co/MiniMaxAI/MiniMax-Music3/resolve/main/dav.pth",
506
+ "directory": "vae"
507
+ }
508
+ ]
509
+ },
510
+ "widgets_values": [
511
+ "minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.safetensors",
512
+ "dav.pth"
513
+ ]
514
+ },
515
+ {
516
+ "id": 45,
517
+ "type": "LoadAudio",
518
+ "pos": [
519
+ 40,
520
+ 570
521
+ ],
522
+ "size": [
523
+ 650,
524
+ 100
525
+ ],
526
+ "flags": {},
527
+ "order": 10,
528
+ "mode": 0,
529
+ "inputs": [],
530
+ "outputs": [
531
+ {
532
+ "localized_name": "AUDIO",
533
+ "name": "AUDIO",
534
+ "type": "AUDIO",
535
+ "links": [
536
+ 3
537
+ ]
538
+ }
539
+ ],
540
+ "properties": {
541
+ "cnr_id": "comfy-core",
542
+ "Node name for S&R": "LoadAudio"
543
+ },
544
+ "widgets_values": [
545
+ "reference_audio.flac"
546
+ ]
547
+ },
548
+ {
549
+ "id": 46,
550
+ "type": "SaveAudioAdvanced",
551
+ "pos": [
552
+ 2480,
553
+ 220
554
+ ],
555
+ "size": [
556
+ 750,
557
+ 160
558
+ ],
559
+ "flags": {},
560
+ "order": 11,
561
+ "mode": 0,
562
+ "inputs": [
563
+ {
564
+ "name": "audio",
565
+ "type": "AUDIO",
566
+ "link": 12
567
+ }
568
+ ],
569
+ "outputs": [
570
+ {
571
+ "name": "audio",
572
+ "type": "AUDIO",
573
+ "links": null
574
+ }
575
+ ],
576
+ "properties": {
577
+ "cnr_id": "comfy-core",
578
+ "ver": "0.31.0"
579
+ },
580
+ "widgets_values": [
581
+ "audio/minimax_music3_rvq_reference",
582
+ "flac"
583
+ ]
584
+ }
585
+ ],
586
+ "links": [
587
+ [
588
+ 1,
589
+ 3,
590
+ 0,
591
+ 13,
592
+ 0,
593
+ "CLIP"
594
+ ],
595
+ [
596
+ 2,
597
+ 44,
598
+ 0,
599
+ 13,
600
+ 1,
601
+ "MINIMAX_MUSIC3_RVQ_REFERENCE_ENCODER"
602
+ ],
603
+ [
604
+ 3,
605
+ 45,
606
+ 0,
607
+ 13,
608
+ 2,
609
+ "AUDIO"
610
+ ],
611
+ [
612
+ 4,
613
+ 13,
614
+ 0,
615
+ 9,
616
+ 1,
617
+ "CONDITIONING"
618
+ ],
619
+ [
620
+ 5,
621
+ 13,
622
+ 0,
623
+ 10,
624
+ 0,
625
+ "CONDITIONING"
626
+ ],
627
+ [
628
+ 6,
629
+ 13,
630
+ 1,
631
+ 15,
632
+ 0,
633
+ "FLOAT"
634
+ ],
635
+ [
636
+ 7,
637
+ 10,
638
+ 0,
639
+ 9,
640
+ 2,
641
+ "CONDITIONING"
642
+ ],
643
+ [
644
+ 8,
645
+ 15,
646
+ 0,
647
+ 9,
648
+ 3,
649
+ "LATENT"
650
+ ],
651
+ [
652
+ 9,
653
+ 6,
654
+ 0,
655
+ 9,
656
+ 0,
657
+ "MODEL"
658
+ ],
659
+ [
660
+ 10,
661
+ 9,
662
+ 0,
663
+ 12,
664
+ 0,
665
+ "LATENT"
666
+ ],
667
+ [
668
+ 11,
669
+ 7,
670
+ 0,
671
+ 12,
672
+ 1,
673
+ "VAE"
674
+ ],
675
+ [
676
+ 12,
677
+ 12,
678
+ 0,
679
+ 46,
680
+ 0,
681
+ "AUDIO"
682
+ ]
683
+ ],
684
+ "groups": [],
685
+ "definitions": {
686
+ "subgraphs": []
687
+ },
688
+ "config": {},
689
+ "extra": {
690
+ "ds": {
691
+ "scale": 0.72,
692
+ "offset": [
693
+ 150,
694
+ 100
695
+ ]
696
+ },
697
+ "frontendVersion": "1.49.6"
698
+ },
699
+ "version": 0.4
700
+ }
encoders/minimax_music3_rvq_encoder_v1_41m_independent_heads.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "codebook_vocab_sizes": [
3
+ 16384,
4
+ 1024,
5
+ 1024,
6
+ 1024,
7
+ 1024,
8
+ 1024,
9
+ 1024,
10
+ 1024
11
+ ],
12
+ "conv_dilations": [
13
+ 1,
14
+ 3,
15
+ 9
16
+ ],
17
+ "d_model": 512,
18
+ "dropout": 0.1,
19
+ "ff_mult": 4,
20
+ "latent_channels": 128,
21
+ "max_position_embeddings": 128,
22
+ "mup": true,
23
+ "mup_attention_multiplier": 8.0,
24
+ "mup_output_mult": 1.0,
25
+ "mup_readout_zero_init": true,
26
+ "num_heads": 8,
27
+ "num_layers": 8
28
+ }
encoders/minimax_music3_rvq_encoder_v1_41m_independent_heads.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9683ba0a719fc8f462fb0013a413e22e59e3b29f50d90f34b25658fb827adb6e
3
+ size 163931632
encoders/minimax_music3_rvq_encoder_v2_155m_wide_independent_heads.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "codebook_vocab_sizes": [
3
+ 16384,
4
+ 1024,
5
+ 1024,
6
+ 1024,
7
+ 1024,
8
+ 1024,
9
+ 1024,
10
+ 1024
11
+ ],
12
+ "conv_dilations": [
13
+ 1,
14
+ 3,
15
+ 9
16
+ ],
17
+ "d_model": 1088,
18
+ "dropout": 0.1,
19
+ "ff_mult": 4,
20
+ "latent_channels": 128,
21
+ "max_position_embeddings": 128,
22
+ "mup": true,
23
+ "mup_attention_multiplier": 8.0,
24
+ "mup_output_mult": 1.0,
25
+ "mup_readout_zero_init": true,
26
+ "num_heads": 17,
27
+ "num_layers": 8
28
+ }
encoders/minimax_music3_rvq_encoder_v2_155m_wide_independent_heads.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:47dfffb7a76d95589752df0c59ff6eaf3496f6fcf5408d324218887e471023c7
3
+ size 618960456
encoders/minimax_music3_rvq_encoder_v3_155m_mert_aligned_independent_heads.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "codebook_vocab_sizes": [
3
+ 16384,
4
+ 1024,
5
+ 1024,
6
+ 1024,
7
+ 1024,
8
+ 1024,
9
+ 1024,
10
+ 1024
11
+ ],
12
+ "conv_dilations": [
13
+ 1,
14
+ 3,
15
+ 9
16
+ ],
17
+ "d_model": 1088,
18
+ "dropout": 0.1,
19
+ "ff_mult": 4,
20
+ "latent_channels": 128,
21
+ "max_position_embeddings": 128,
22
+ "mup": true,
23
+ "mup_attention_multiplier": 8.0,
24
+ "mup_output_mult": 1.0,
25
+ "mup_readout_zero_init": true,
26
+ "num_heads": 17,
27
+ "num_layers": 8
28
+ }
encoders/minimax_music3_rvq_encoder_v3_155m_mert_aligned_independent_heads.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:356e97fea65c486a28814dc778b52645807ab8f784c6021d2403f688a2bfe788
3
+ size 618960456
encoders/minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "codebook_vocab_sizes": [
3
+ 16384,
4
+ 1024,
5
+ 1024,
6
+ 1024,
7
+ 1024,
8
+ 1024,
9
+ 1024,
10
+ 1024
11
+ ],
12
+ "conv_dilations": [
13
+ 1,
14
+ 3,
15
+ 9
16
+ ],
17
+ "d_model": 1088,
18
+ "depth_decoder": true,
19
+ "depth_decoder_dim": 512,
20
+ "depth_decoder_dropout": 0.1,
21
+ "depth_decoder_ff_mult": 4,
22
+ "depth_decoder_heads": 8,
23
+ "depth_decoder_layers": 2,
24
+ "dropout": 0.1,
25
+ "ff_mult": 4,
26
+ "latent_channels": 128,
27
+ "max_position_embeddings": 128,
28
+ "mup": true,
29
+ "mup_attention_multiplier": 8.0,
30
+ "mup_output_mult": 1.0,
31
+ "mup_readout_zero_init": true,
32
+ "num_heads": 17,
33
+ "num_layers": 8
34
+ }
encoders/minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e8fa93a7db2e5a090442d9492ea3f9f07664ce5bdb2ea7890559a4408f049744
3
+ size 676055232
evaluation/condition-replay-aggregate.json ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "dataset": {
3
+ "exact_alignment_records": 130,
4
+ "repo_id": "bghira/minimax-music3-rvq-reverse-distillation",
5
+ "revision": "5029b1e7f1bbfbf028b76b38564fecccda94a111",
6
+ "split": "holdout"
7
+ },
8
+ "format": "simpletuner-minimaxmusic-replay-comparison-v1",
9
+ "metric": "per-record mean stitched condition-embedding cosine",
10
+ "models": [
11
+ {
12
+ "checkpoint": {
13
+ "checkpoint_bytes": 163955786,
14
+ "checkpoint_sha256": "fd7bfefb197cb7b65a6b6d9b1373c1772a15979f1a4b67d820764d610f0fb9a5",
15
+ "checkpoint_url": "https://www.serveurperso.com/ia/ssd/workspace/git/minimaxmusic.cpp/training/checkpoints/v1/best.pt",
16
+ "epoch": 17,
17
+ "reported_validation_loss": 5.292583839459852,
18
+ "source_commit": "d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1",
19
+ "source_url": "https://github.com/ServeurpersoCom/minimaxmusic.cpp/commit/d19efe9f94e41ac4c900aa30d56fe90c8dac7ef1"
20
+ },
21
+ "model_id": "serveurperso-v1",
22
+ "parameter_count": 40978944,
23
+ "predicted_code_condition_cosine": {
24
+ "count": 130,
25
+ "max": 0.7290002703666687,
26
+ "mean": 0.6633286081827604,
27
+ "min": 0.6026018261909485,
28
+ "quantiles": {
29
+ "0.0": 0.6026018261909485,
30
+ "0.05": 0.6280524551868438,
31
+ "0.25": 0.6483299732208252,
32
+ "0.5": 0.6645751297473907,
33
+ "0.75": 0.6784153282642365,
34
+ "0.95": 0.6963281780481339,
35
+ "1.0": 0.7290002703666687
36
+ },
37
+ "std": 0.02217457298933777
38
+ },
39
+ "true_code_control": {
40
+ "count": 130,
41
+ "max": 0.999957263469696,
42
+ "mean": 0.9999072317893688,
43
+ "min": 0.9983662366867065,
44
+ "quantiles": {
45
+ "0.0": 0.9983662366867065,
46
+ "0.05": 0.999821075797081,
47
+ "0.25": 0.9999240636825562,
48
+ "0.5": 0.9999381899833679,
49
+ "0.75": 0.9999445825815201,
50
+ "0.95": 0.9999500513076782,
51
+ "1.0": 0.999957263469696
52
+ },
53
+ "std": 0.00015022638681815362
54
+ }
55
+ },
56
+ {
57
+ "checkpoint": {
58
+ "checkpoint": "full/output/final",
59
+ "checkpoint_bytes": 163931632,
60
+ "checkpoint_sha256": "1cea88dddf7a48583c13ac661a548b7a3535b933a76356bb158420d24da32f22",
61
+ "global_step": 17640
62
+ },
63
+ "model_id": "simpletuner-v1",
64
+ "parameter_count": 40978944,
65
+ "predicted_code_condition_cosine": {
66
+ "count": 130,
67
+ "max": 0.8286042809486389,
68
+ "mean": 0.7624421353523548,
69
+ "min": 0.7123062014579773,
70
+ "quantiles": {
71
+ "0.0": 0.7123062014579773,
72
+ "0.05": 0.7345191180706024,
73
+ "0.25": 0.747437372803688,
74
+ "0.5": 0.7631425857543945,
75
+ "0.75": 0.7765287756919861,
76
+ "0.95": 0.7904498308897019,
77
+ "1.0": 0.8286042809486389
78
+ },
79
+ "std": 0.019549602827450108
80
+ },
81
+ "true_code_control": {
82
+ "count": 130,
83
+ "max": 0.999957263469696,
84
+ "mean": 0.9999072317893688,
85
+ "min": 0.9983662366867065,
86
+ "quantiles": {
87
+ "0.0": 0.9983662366867065,
88
+ "0.05": 0.999821075797081,
89
+ "0.25": 0.9999240636825562,
90
+ "0.5": 0.9999381899833679,
91
+ "0.75": 0.9999445825815201,
92
+ "0.95": 0.9999500513076782,
93
+ "1.0": 0.999957263469696
94
+ },
95
+ "std": 0.00015022638681815362
96
+ }
97
+ },
98
+ {
99
+ "checkpoint": {
100
+ "checkpoint": "v2/output/final",
101
+ "checkpoint_bytes": 618960456,
102
+ "checkpoint_sha256": "47dfffb7a76d95589752df0c59ff6eaf3496f6fcf5408d324218887e471023c7",
103
+ "global_step": 17660
104
+ },
105
+ "model_id": "simpletuner-v2",
106
+ "parameter_count": 154736064,
107
+ "predicted_code_condition_cosine": {
108
+ "count": 130,
109
+ "max": 0.8361330032348633,
110
+ "mean": 0.7698414775041433,
111
+ "min": 0.7206224203109741,
112
+ "quantiles": {
113
+ "0.0": 0.7206224203109741,
114
+ "0.05": 0.7429912149906158,
115
+ "0.25": 0.7548101097345352,
116
+ "0.5": 0.770626574754715,
117
+ "0.75": 0.7835157215595245,
118
+ "0.95": 0.7986360996961593,
119
+ "1.0": 0.8361330032348633
120
+ },
121
+ "std": 0.019062989675575924
122
+ },
123
+ "true_code_control": {
124
+ "count": 130,
125
+ "max": 0.999957263469696,
126
+ "mean": 0.9999072317893688,
127
+ "min": 0.9983662366867065,
128
+ "quantiles": {
129
+ "0.0": 0.9983662366867065,
130
+ "0.05": 0.999821075797081,
131
+ "0.25": 0.9999240636825562,
132
+ "0.5": 0.9999381899833679,
133
+ "0.75": 0.9999445825815201,
134
+ "0.95": 0.9999500513076782,
135
+ "1.0": 0.999957263469696
136
+ },
137
+ "std": 0.00015022638681815362
138
+ }
139
+ },
140
+ {
141
+ "checkpoint": {
142
+ "checkpoint": "v3/output/final",
143
+ "checkpoint_bytes": 618960456,
144
+ "checkpoint_sha256": "356e97fea65c486a28814dc778b52645807ab8f784c6021d2403f688a2bfe788",
145
+ "global_step": 17660
146
+ },
147
+ "model_id": "simpletuner-v3",
148
+ "parameter_count": 154736064,
149
+ "predicted_code_condition_cosine": {
150
+ "count": 130,
151
+ "max": 0.8356426954269409,
152
+ "mean": 0.7702590204202212,
153
+ "min": 0.7236603498458862,
154
+ "quantiles": {
155
+ "0.0": 0.7236603498458862,
156
+ "0.05": 0.7415853887796402,
157
+ "0.25": 0.7547859847545624,
158
+ "0.5": 0.7704743146896362,
159
+ "0.75": 0.7837727516889572,
160
+ "0.95": 0.8004916191101075,
161
+ "1.0": 0.8356426954269409
162
+ },
163
+ "std": 0.019273954474224628
164
+ },
165
+ "true_code_control": {
166
+ "count": 130,
167
+ "max": 0.999957263469696,
168
+ "mean": 0.9999072317893688,
169
+ "min": 0.9983662366867065,
170
+ "quantiles": {
171
+ "0.0": 0.9983662366867065,
172
+ "0.05": 0.999821075797081,
173
+ "0.25": 0.9999240636825562,
174
+ "0.5": 0.9999381899833679,
175
+ "0.75": 0.9999445825815201,
176
+ "0.95": 0.9999500513076782,
177
+ "1.0": 0.999957263469696
178
+ },
179
+ "std": 0.00015022638681815362
180
+ }
181
+ },
182
+ {
183
+ "checkpoint": {
184
+ "checkpoint": "v4/output/final",
185
+ "checkpoint_bytes": 676055232,
186
+ "checkpoint_sha256": "e8fa93a7db2e5a090442d9492ea3f9f07664ce5bdb2ea7890559a4408f049744",
187
+ "global_step": 17660
188
+ },
189
+ "model_id": "simpletuner-v4",
190
+ "parameter_count": 169008576,
191
+ "predicted_code_condition_cosine": {
192
+ "count": 130,
193
+ "max": 0.897747814655304,
194
+ "mean": 0.8747961479883928,
195
+ "min": 0.8091457486152649,
196
+ "quantiles": {
197
+ "0.0": 0.8091457486152649,
198
+ "0.05": 0.8453496336936951,
199
+ "0.25": 0.8675179928541183,
200
+ "0.5": 0.879057914018631,
201
+ "0.75": 0.8857778161764145,
202
+ "0.95": 0.8938389658927918,
203
+ "1.0": 0.897747814655304
204
+ },
205
+ "std": 0.015662041602848998
206
+ },
207
+ "true_code_control": {
208
+ "count": 130,
209
+ "max": 0.999957263469696,
210
+ "mean": 0.9999072317893688,
211
+ "min": 0.9983662366867065,
212
+ "quantiles": {
213
+ "0.0": 0.9983662366867065,
214
+ "0.05": 0.999821075797081,
215
+ "0.25": 0.9999240636825562,
216
+ "0.5": 0.9999381899833679,
217
+ "0.75": 0.9999445825815201,
218
+ "0.95": 0.9999500513076782,
219
+ "1.0": 0.999957263469696
220
+ },
221
+ "std": 0.00015022638681815362
222
+ }
223
+ }
224
+ ]
225
+ }
experiment-summary.json ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "encoders": [
3
+ {
4
+ "bytes": 163931632,
5
+ "condition_replay_checkpoint": "final",
6
+ "condition_replay_mean": 0.7624421353523548,
7
+ "config_filename": "encoders/minimax_music3_rvq_encoder_v1_41m_independent_heads.json",
8
+ "filename": "encoders/minimax_music3_rvq_encoder_v1_41m_independent_heads.safetensors",
9
+ "parameter_count": 40978944,
10
+ "sha256": "9683ba0a719fc8f462fb0013a413e22e59e3b29f50d90f34b25658fb827adb6e",
11
+ "source_checkpoint_path": "checkpoint-17500",
12
+ "source_repo_id": "SimpleTuner/open-rvq-encoder-minimax-music3-41m-v1",
13
+ "source_revision": "e9fa3bba9b5bb3b76e7fd6705f9ca5dbb7638ec4",
14
+ "version": "v1"
15
+ },
16
+ {
17
+ "bytes": 618960456,
18
+ "condition_replay_checkpoint": "final",
19
+ "condition_replay_mean": 0.7698414775041433,
20
+ "config_filename": "encoders/minimax_music3_rvq_encoder_v2_155m_wide_independent_heads.json",
21
+ "filename": "encoders/minimax_music3_rvq_encoder_v2_155m_wide_independent_heads.safetensors",
22
+ "parameter_count": 154736064,
23
+ "sha256": "47dfffb7a76d95589752df0c59ff6eaf3496f6fcf5408d324218887e471023c7",
24
+ "source_checkpoint_path": "final",
25
+ "source_repo_id": "SimpleTuner/open-rvq-encoder-minimax-music3-155m-v2",
26
+ "source_revision": "f317d72466b98cad097c1e6f4dd379f7e344c939",
27
+ "version": "v2"
28
+ },
29
+ {
30
+ "bytes": 618960456,
31
+ "condition_replay_checkpoint": "final",
32
+ "condition_replay_mean": 0.7702590204202212,
33
+ "config_filename": "encoders/minimax_music3_rvq_encoder_v3_155m_mert_aligned_independent_heads.json",
34
+ "filename": "encoders/minimax_music3_rvq_encoder_v3_155m_mert_aligned_independent_heads.safetensors",
35
+ "parameter_count": 154736064,
36
+ "sha256": "356e97fea65c486a28814dc778b52645807ab8f784c6021d2403f688a2bfe788",
37
+ "source_checkpoint_path": "final",
38
+ "source_repo_id": "SimpleTuner/open-rvq-encoder-minimax-music3-155m-v3",
39
+ "source_revision": "7c525705817d8878da02ed41429cad027552a568",
40
+ "version": "v3"
41
+ },
42
+ {
43
+ "bytes": 676055232,
44
+ "condition_replay_checkpoint": "final",
45
+ "condition_replay_mean": 0.8747961479883928,
46
+ "config_filename": "encoders/minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.json",
47
+ "filename": "encoders/minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.safetensors",
48
+ "parameter_count": 169008576,
49
+ "sha256": "e8fa93a7db2e5a090442d9492ea3f9f07664ce5bdb2ea7890559a4408f049744",
50
+ "source_checkpoint_path": "final",
51
+ "source_repo_id": "SimpleTuner/open-rvq-encoder-minimax-music3-169m-v4",
52
+ "source_revision": "b9a9165b99d12cb48cac812776938c8d9332dd59",
53
+ "version": "v4"
54
+ }
55
+ ],
56
+ "format": "simpletuner-minimax-music3-rvq-collection-v1",
57
+ "metric": {
58
+ "dataset_repo_id": "bghira/minimax-music3-rvq-reverse-distillation",
59
+ "dataset_revision": "5029b1e7f1bbfbf028b76b38564fecccda94a111",
60
+ "name": "per-record mean stitched condition-embedding cosine",
61
+ "records": 130,
62
+ "serveurperso_v1_mean": 0.6633286081827604,
63
+ "split": "holdout",
64
+ "true_code_control_mean": 0.9999072317893688
65
+ },
66
+ "notes": [
67
+ "The v1 packaged checkpoint is step 17500; its replay metric was measured on v1 final.",
68
+ "V2, v3, and v4 replay metrics were measured on the packaged final checkpoints."
69
+ ],
70
+ "recommended_version": "v4"
71
+ }
minimax_music3_reference_adapter.py ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 SimpleTuner contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Reference-audio conditioning for MiniMax Music 3.
16
+
17
+ This file is self-contained. It loads a released SimpleTuner RVQ encoder,
18
+ encodes 44.1 kHz audio with the original DAV encoder, predicts eight RVQ codes
19
+ per 25 Hz frame, and teacher-forces those codes through the official MiniMax
20
+ Music 3 language-model path.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import dataclasses
26
+ import math
27
+ import re
28
+ from contextlib import nullcontext
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ import torch
33
+ import torch.nn.functional as F
34
+ from torch import nn
35
+ from torch.nn.utils import weight_norm
36
+
37
+
38
+ COLLECTION_REPO_ID = "SimpleTuner/open-rvq-encoder-minimax-music3"
39
+ OFFICIAL_MODEL_REPO_ID = "MiniMaxAI/MiniMax-Music3"
40
+ DEFAULT_ENCODER_FILE = (
41
+ "encoders/minimax_music3_rvq_encoder_v4_169m_autoregressive_depth_recommended.safetensors"
42
+ )
43
+ DAV_FILE = "dav.pth"
44
+ SAMPLE_RATE = 44_100
45
+ DAV_HOP_SAMPLES = 512
46
+ FRAME_RATE = 25
47
+ LATENT_RATE_NUM = 441
48
+ LATENT_RATE_DEN = 128
49
+ AUDIO_CODE_OFFSET = 151_675
50
+ AUDIO_END_TOKEN_ID = 151_670
51
+ AUDIO_CFG_TOKEN_ID = 151_654
52
+ SEMANTIC_VOCAB_SIZE = 16_384
53
+ AR_CFG_SCALE = 1.5
54
+ AR_TOP_K = 50
55
+ LM_BLOCK_FRAMES = 256
56
+
57
+ _SPECIAL_TAG_RE = re.compile(r"<\|([^|]*)\|>")
58
+ _LEADING_TAGS_RE = re.compile(r"^[ \t]*((?:\[[^\]]+\][ \t]*)+)")
59
+
60
+
61
+ @dataclasses.dataclass(frozen=True)
62
+ class RVQEncoderConfig:
63
+ latent_channels: int = 128
64
+ codebook_vocab_sizes: tuple[int, ...] = (16_384, 1024, 1024, 1024, 1024, 1024, 1024, 1024)
65
+ d_model: int = 512
66
+ num_layers: int = 8
67
+ num_heads: int = 8
68
+ ff_mult: int = 4
69
+ dropout: float = 0.1
70
+ max_position_embeddings: int = 128
71
+ conv_dilations: tuple[int, ...] = (1, 3, 9)
72
+ mup: bool = False
73
+ mup_output_mult: float = 1.0
74
+ mup_readout_zero_init: bool = False
75
+ mup_attention_multiplier: float = 8.0
76
+ depth_decoder: bool = False
77
+ depth_decoder_dim: int = 512
78
+ depth_decoder_layers: int = 2
79
+ depth_decoder_heads: int = 8
80
+ depth_decoder_ff_mult: int = 4
81
+ depth_decoder_dropout: float = 0.1
82
+
83
+ @classmethod
84
+ def from_dict(cls, values: dict[str, Any]) -> "RVQEncoderConfig":
85
+ normalized = dict(values)
86
+ for key in ("codebook_vocab_sizes", "conv_dilations"):
87
+ if key in normalized:
88
+ normalized[key] = tuple(normalized[key])
89
+ known = {field.name for field in dataclasses.fields(cls)}
90
+ unknown = sorted(set(normalized) - known)
91
+ if unknown:
92
+ raise ValueError(f"Unknown RVQ encoder configuration fields: {unknown}")
93
+ return cls(**normalized)
94
+
95
+
96
+ class RVQResBlock(nn.Module):
97
+ def __init__(self, dim: int, dilation: int):
98
+ super().__init__()
99
+ self.norm = nn.GroupNorm(1, dim)
100
+ self.conv1 = nn.Conv1d(dim, dim, kernel_size=3, padding=dilation, dilation=dilation)
101
+ self.conv2 = nn.Conv1d(dim, dim, kernel_size=1)
102
+
103
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
104
+ residual = self.conv1(F.gelu(self.norm(hidden_states)))
105
+ return hidden_states + self.conv2(F.gelu(residual))
106
+
107
+
108
+ class RVQMuTransformerEncoderLayer(nn.Module):
109
+ def __init__(self, config: RVQEncoderConfig):
110
+ super().__init__()
111
+ if config.d_model % config.num_heads:
112
+ raise ValueError("d_model must be divisible by num_heads")
113
+ self.num_heads = config.num_heads
114
+ self.head_dim = config.d_model // config.num_heads
115
+ self.attention_multiplier = config.mup_attention_multiplier
116
+ self.norm1 = nn.LayerNorm(config.d_model)
117
+ self.norm2 = nn.LayerNorm(config.d_model)
118
+ self.q_proj = nn.Linear(config.d_model, config.d_model)
119
+ self.k_proj = nn.Linear(config.d_model, config.d_model)
120
+ self.v_proj = nn.Linear(config.d_model, config.d_model)
121
+ self.out_proj = nn.Linear(config.d_model, config.d_model)
122
+ self.linear1 = nn.Linear(config.d_model, config.d_model * config.ff_mult)
123
+ self.linear2 = nn.Linear(config.d_model * config.ff_mult, config.d_model)
124
+ self.dropout = nn.Dropout(config.dropout)
125
+ self.attn_dropout = nn.Dropout(config.dropout)
126
+
127
+ def _split_heads(self, hidden_states: torch.Tensor) -> torch.Tensor:
128
+ batch, frames, _ = hidden_states.shape
129
+ return hidden_states.view(batch, frames, self.num_heads, self.head_dim).transpose(1, 2)
130
+
131
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
132
+ normalized = self.norm1(hidden_states)
133
+ query = self._split_heads(self.q_proj(normalized))
134
+ key = self._split_heads(self.k_proj(normalized))
135
+ value = self._split_heads(self.v_proj(normalized))
136
+ scores = torch.matmul(query, key.transpose(-2, -1)) * (self.attention_multiplier / self.head_dim)
137
+ probabilities = self.attn_dropout(F.softmax(scores.float(), dim=-1).to(query.dtype))
138
+ attended = torch.matmul(probabilities, value).transpose(1, 2).contiguous()
139
+ attended = attended.view(hidden_states.shape)
140
+ hidden_states = hidden_states + self.dropout(self.out_proj(attended))
141
+ feedforward = self.linear2(self.dropout(F.gelu(self.linear1(self.norm2(hidden_states)))))
142
+ return hidden_states + self.dropout(feedforward)
143
+
144
+
145
+ class RVQDepthDecoderLayer(nn.Module):
146
+ def __init__(self, config: RVQEncoderConfig):
147
+ super().__init__()
148
+ dim = config.depth_decoder_dim
149
+ if dim % config.depth_decoder_heads:
150
+ raise ValueError("depth_decoder_dim must be divisible by depth_decoder_heads")
151
+ self.num_heads = config.depth_decoder_heads
152
+ self.head_dim = dim // config.depth_decoder_heads
153
+ self.norm1 = nn.LayerNorm(dim)
154
+ self.norm2 = nn.LayerNorm(dim)
155
+ self.q_proj = nn.Linear(dim, dim)
156
+ self.k_proj = nn.Linear(dim, dim)
157
+ self.v_proj = nn.Linear(dim, dim)
158
+ self.out_proj = nn.Linear(dim, dim)
159
+ self.linear1 = nn.Linear(dim, dim * config.depth_decoder_ff_mult)
160
+ self.linear2 = nn.Linear(dim * config.depth_decoder_ff_mult, dim)
161
+ self.dropout = nn.Dropout(config.depth_decoder_dropout)
162
+ self.attn_dropout = nn.Dropout(config.depth_decoder_dropout)
163
+
164
+ def _split_heads(self, hidden_states: torch.Tensor) -> torch.Tensor:
165
+ batch, depth, _ = hidden_states.shape
166
+ return hidden_states.view(batch, depth, self.num_heads, self.head_dim).transpose(1, 2)
167
+
168
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
169
+ normalized = self.norm1(hidden_states)
170
+ query = self._split_heads(self.q_proj(normalized))
171
+ key = self._split_heads(self.k_proj(normalized))
172
+ value = self._split_heads(self.v_proj(normalized))
173
+ scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(self.head_dim)
174
+ depth = hidden_states.shape[1]
175
+ scores = scores.masked_fill(torch.ones((depth, depth), dtype=torch.bool, device=scores.device).triu(1), -torch.inf)
176
+ probabilities = self.attn_dropout(F.softmax(scores.float(), dim=-1).to(query.dtype))
177
+ attended = torch.matmul(probabilities, value).transpose(1, 2).contiguous()
178
+ attended = attended.view(hidden_states.shape)
179
+ hidden_states = hidden_states + self.dropout(self.out_proj(attended))
180
+ feedforward = self.linear2(self.dropout(F.gelu(self.linear1(self.norm2(hidden_states)))))
181
+ return hidden_states + self.dropout(feedforward)
182
+
183
+
184
+ class MiniMaxMusicRVQDepthDecoder(nn.Module):
185
+ def __init__(self, config: RVQEncoderConfig):
186
+ super().__init__()
187
+ self.config = config
188
+ self.context_projection = nn.Linear(config.d_model, config.depth_decoder_dim, bias=False)
189
+ self.prior_embeddings = nn.ModuleList(
190
+ nn.Embedding(vocab_size, config.depth_decoder_dim) for vocab_size in config.codebook_vocab_sizes[:-1]
191
+ )
192
+ self.position = nn.Parameter(torch.zeros(1, len(config.codebook_vocab_sizes), config.depth_decoder_dim))
193
+ self.layers = nn.ModuleList(RVQDepthDecoderLayer(config) for _ in range(config.depth_decoder_layers))
194
+ self.norm = nn.LayerNorm(config.depth_decoder_dim)
195
+ self.heads = nn.ModuleList(
196
+ nn.Linear(config.depth_decoder_dim, vocab_size) for vocab_size in config.codebook_vocab_sizes[1:]
197
+ )
198
+
199
+ def _decode(self, sequence: torch.Tensor) -> torch.Tensor:
200
+ hidden_states = sequence + self.position[:, : sequence.shape[1]].to(sequence.dtype)
201
+ for layer in self.layers:
202
+ hidden_states = layer(hidden_states)
203
+ return self.norm(hidden_states)
204
+
205
+ def forward(self, frame_context: torch.Tensor, semantic_codes: torch.Tensor) -> list[torch.Tensor]:
206
+ batch, frames, _ = frame_context.shape
207
+ sequence = torch.cat(
208
+ (
209
+ self.context_projection(frame_context).flatten(0, 1).unsqueeze(1),
210
+ self.prior_embeddings[0](semantic_codes).flatten(0, 1).unsqueeze(1),
211
+ ),
212
+ dim=1,
213
+ )
214
+ logits = []
215
+ for acoustic_index, head in enumerate(self.heads):
216
+ head_logits = head(self._decode(sequence)[:, -1]).view(batch, frames, -1)
217
+ logits.append(head_logits)
218
+ if acoustic_index + 1 < len(self.heads):
219
+ selected = head_logits.argmax(dim=-1)
220
+ prior = self.prior_embeddings[acoustic_index + 1](selected).flatten(0, 1).unsqueeze(1)
221
+ sequence = torch.cat((sequence, prior), dim=1)
222
+ return logits
223
+
224
+
225
+ class MiniMaxMusicRVQEncoder(nn.Module):
226
+ def __init__(self, config: RVQEncoderConfig):
227
+ super().__init__()
228
+ self.config = config
229
+ self.conv_in = nn.Conv1d(config.latent_channels, config.d_model, kernel_size=7, padding=3)
230
+ self.blocks = nn.ModuleList(RVQResBlock(config.d_model, dilation) for dilation in config.conv_dilations)
231
+ self.position = nn.Parameter(torch.zeros(1, config.max_position_embeddings, config.d_model))
232
+ if config.mup:
233
+ self.transformer = nn.ModuleList(RVQMuTransformerEncoderLayer(config) for _ in range(config.num_layers))
234
+ else:
235
+ layer = nn.TransformerEncoderLayer(
236
+ d_model=config.d_model,
237
+ nhead=config.num_heads,
238
+ dim_feedforward=config.d_model * config.ff_mult,
239
+ dropout=config.dropout,
240
+ activation="gelu",
241
+ batch_first=True,
242
+ norm_first=True,
243
+ )
244
+ self.transformer = nn.TransformerEncoder(layer, config.num_layers)
245
+ self.norm_out = nn.LayerNorm(config.d_model)
246
+ readout_sizes = config.codebook_vocab_sizes[:1] if config.depth_decoder else config.codebook_vocab_sizes
247
+ self.heads = nn.ModuleList(nn.Linear(config.d_model, vocab_size) for vocab_size in readout_sizes)
248
+ self.depth_decoder = MiniMaxMusicRVQDepthDecoder(config) if config.depth_decoder else None
249
+
250
+ def forward(self, latents: torch.Tensor, pool: torch.Tensor) -> list[torch.Tensor]:
251
+ hidden_states = self.conv_in(latents.transpose(1, 2))
252
+ for block in self.blocks:
253
+ hidden_states = block(hidden_states)
254
+ hidden_states = torch.bmm(pool.to(hidden_states.dtype), hidden_states.transpose(1, 2))
255
+ hidden_states = hidden_states + self.position[:, : pool.shape[1]].to(hidden_states.dtype)
256
+ layers = self.transformer if isinstance(self.transformer, nn.ModuleList) else self.transformer.layers
257
+ for layer in layers:
258
+ hidden_states = layer(hidden_states)
259
+ hidden_states = self.norm_out(hidden_states)
260
+ semantic = self.heads[0](hidden_states)
261
+ if self.depth_decoder is None:
262
+ return [head(hidden_states) for head in self.heads]
263
+ semantic_codes = semantic.argmax(dim=-1)
264
+ return [semantic, *self.depth_decoder(hidden_states, semantic_codes)]
265
+
266
+
267
+ class MiniMaxMusic3Snake1d(nn.Module):
268
+ def __init__(self, channels: int):
269
+ super().__init__()
270
+ self.alpha = nn.Parameter(torch.ones(1, channels, 1))
271
+
272
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
273
+ return hidden_states + (self.alpha + 1e-9).reciprocal() * torch.sin(self.alpha * hidden_states).pow(2)
274
+
275
+
276
+ class DAVResidualUnit(nn.Module):
277
+ def __init__(self, dim: int, dilation: int):
278
+ super().__init__()
279
+ self.block = nn.Sequential(
280
+ MiniMaxMusic3Snake1d(dim),
281
+ weight_norm(nn.Conv1d(dim, dim, kernel_size=7, dilation=dilation, padding=3 * dilation)),
282
+ MiniMaxMusic3Snake1d(dim),
283
+ weight_norm(nn.Conv1d(dim, dim, kernel_size=1)),
284
+ )
285
+
286
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
287
+ residual = self.block(hidden_states)
288
+ if residual.shape[-1] != hidden_states.shape[-1]:
289
+ padding = (hidden_states.shape[-1] - residual.shape[-1]) // 2
290
+ hidden_states = hidden_states[..., padding : hidden_states.shape[-1] - padding]
291
+ return hidden_states + residual
292
+
293
+
294
+ class DAVEncoderBlock(nn.Module):
295
+ def __init__(self, dim: int, stride: int):
296
+ super().__init__()
297
+ self.block = nn.Sequential(
298
+ DAVResidualUnit(dim // 2, 1),
299
+ DAVResidualUnit(dim // 2, 3),
300
+ DAVResidualUnit(dim // 2, 9),
301
+ MiniMaxMusic3Snake1d(dim // 2),
302
+ weight_norm(
303
+ nn.Conv1d(dim // 2, dim, kernel_size=2 * stride, stride=stride, padding=math.ceil(stride / 2))
304
+ ),
305
+ )
306
+
307
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
308
+ return self.block(hidden_states)
309
+
310
+
311
+ class DAVEncoder(nn.Module):
312
+ def __init__(self, encoder_dim: int = 64, rates: tuple[int, ...] = (2, 4, 8, 8), latent_dim: int = 1024):
313
+ super().__init__()
314
+ layers: list[nn.Module] = [weight_norm(nn.Conv1d(1, encoder_dim, kernel_size=7, padding=3))]
315
+ for stride in rates:
316
+ encoder_dim *= 2
317
+ layers.append(DAVEncoderBlock(encoder_dim, stride))
318
+ layers.extend((MiniMaxMusic3Snake1d(encoder_dim), weight_norm(nn.Conv1d(encoder_dim, latent_dim, 3, padding=1))))
319
+ self.block = nn.Sequential(*layers)
320
+
321
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
322
+ return self.block(hidden_states)
323
+
324
+
325
+ class DAVEncoderOnly(nn.Module):
326
+ def __init__(self):
327
+ super().__init__()
328
+ self.encoder = DAVEncoder()
329
+ self.mean_proj = nn.Conv1d(1024, 64, kernel_size=1)
330
+
331
+ def forward(self, waveform: torch.Tensor) -> torch.Tensor:
332
+ if waveform.ndim == 2:
333
+ waveform = waveform.unsqueeze(0)
334
+ if waveform.ndim != 3 or waveform.shape[1] not in (1, 2):
335
+ raise ValueError("waveform must have shape [channels, samples] or [batch, channels, samples]")
336
+ if waveform.shape[1] == 1:
337
+ waveform = waveform.repeat(1, 2, 1)
338
+ remainder = waveform.shape[-1] % DAV_HOP_SAMPLES
339
+ if remainder:
340
+ waveform = F.pad(waveform, (0, DAV_HOP_SAMPLES - remainder))
341
+ batch, _, samples = waveform.shape
342
+ hidden_states = self.encoder(waveform.reshape(batch * 2, 1, samples))
343
+ return self.mean_proj(hidden_states).reshape(batch, 128, -1)
344
+
345
+
346
+ def frame_latent_starts(frame_count: int) -> list[int]:
347
+ """Map continuous 25 Hz frames to the 44.1 kHz / 512 DAV timeline."""
348
+ if frame_count <= 0:
349
+ raise ValueError("frame_count must be positive")
350
+ return [(index * LATENT_RATE_NUM) // LATENT_RATE_DEN for index in range(frame_count + 1)]
351
+
352
+
353
+ def build_pool_matrix(bounds: list[int]) -> torch.Tensor:
354
+ if len(bounds) < 2:
355
+ raise ValueError("at least two frame boundaries are required")
356
+ origin = bounds[0]
357
+ local = [value - origin for value in bounds]
358
+ pool = torch.zeros((len(local) - 1, local[-1]), dtype=torch.float32)
359
+ for index, (start, end) in enumerate(zip(local[:-1], local[1:])):
360
+ if end <= start:
361
+ raise ValueError(f"invalid DAV span [{start}, {end}) for frame {index}")
362
+ pool[index, start:end] = 1.0 / (end - start)
363
+ return pool
364
+
365
+
366
+ def _clean_caption(caption: str) -> str:
367
+ def rewrite(match: re.Match) -> str:
368
+ parts = match.group(1).strip().split(None, 1)
369
+ return f"{parts[0]} is {parts[1]}" if len(parts) == 2 else parts[0]
370
+
371
+ text = _SPECIAL_TAG_RE.sub(rewrite, caption)
372
+ lines = []
373
+ for line in text.splitlines():
374
+ line = re.sub(r"^\s{0,3}#{1,6}\s+", "", line)
375
+ line = re.sub(r"^\s*[*+-]\s+", "", line)
376
+ while "**" in line:
377
+ updated = re.sub(r"\*\*([^*]+)\*\*", r"\1", line)
378
+ if updated == line:
379
+ break
380
+ line = updated
381
+ lines.append(re.sub(r"(?<!\*)\*([^*\n]+)\*(?!\*)", r"\1", line).rstrip())
382
+ text = re.sub(r"^\s*[-*_]{3,}\s*$", "", "\n".join(lines), flags=re.MULTILINE)
383
+ return re.sub(r"\n{2,}", "\n", text.replace("• ", "").replace(" ", ""))
384
+
385
+
386
+ def _normalize_lyrics(lyrics: str) -> str:
387
+ lines = []
388
+ for line in lyrics.split("\n"):
389
+ match = _LEADING_TAGS_RE.match(line)
390
+ lines.append(match.group(1).strip() if match else line)
391
+ text = "\n".join(lines).replace("] ", "]\n").replace(" [", "\n[").replace(" ^ ", "\n")
392
+ text = re.sub(r"\[([^\]]+)\]", lambda match: f"[{match.group(1).lower()}]", text)
393
+ return f"[start]\n{text}"
394
+
395
+
396
+ def build_text_ids(tokenizer, prompt: str, lyrics: str, device: torch.device) -> torch.Tensor:
397
+ if not prompt.strip() or not lyrics.strip():
398
+ raise ValueError("prompt and lyrics must be non-empty")
399
+ text = (
400
+ f"<|im_start|><|caption_start|>{_clean_caption(prompt)}<|caption_end|>"
401
+ f"<|lyrics_start|>{_normalize_lyrics(lyrics)}<|lyrics_end|><|im_end|><|audio_start|>"
402
+ )
403
+ input_ids = tokenizer(text, return_tensors="pt")["input_ids"]
404
+ unconditional = input_ids.clone()
405
+ unconditional[:, 1:-2] = AUDIO_CFG_TOKEN_ID
406
+ return torch.cat((input_ids, unconditional), dim=0).to(device)
407
+
408
+
409
+ def _sample_top_k(logits: torch.Tensor, generator: torch.Generator | None) -> torch.Tensor:
410
+ values = torch.nan_to_num(logits.float(), nan=-1e9, posinf=1e9, neginf=-1e9)
411
+ threshold = torch.topk(values, min(AR_TOP_K, values.shape[-1]), dim=-1).values[..., -1, None]
412
+ probabilities = torch.softmax(values.masked_fill(values < threshold, -torch.inf), dim=-1)
413
+ sample_device = generator.device if generator is not None else probabilities.device
414
+ return torch.multinomial(probabilities.to(sample_device), 1, generator=generator).squeeze(-1).to(values.device)
415
+
416
+
417
+ def _official_depth_hidden(language_model, depth_decoder, hidden: torch.Tensor, codes: torch.Tensor) -> torch.Tensor:
418
+ sequence = [depth_decoder.projection(hidden).unsqueeze(1)]
419
+ semantic = language_model.model.embed_tokens(codes[:, 0] + AUDIO_CODE_OFFSET)
420
+ sequence.append(depth_decoder.projection(semantic).unsqueeze(1))
421
+ hidden_parts = []
422
+ for index in range(1, codes.shape[1]):
423
+ depth_hidden = depth_decoder(torch.cat(sequence, dim=1))[:, -1]
424
+ hidden_parts.append(depth_hidden)
425
+ if index < codes.shape[1] - 1:
426
+ embedding = depth_decoder.audio_embeddings(codes[:, index] + (index - 1) * 1024)
427
+ sequence.append(depth_decoder.projection(embedding).unsqueeze(1))
428
+ return torch.cat(hidden_parts, dim=-1)
429
+
430
+
431
+ def _embed_official_codes(language_model, depth_decoder, codes: torch.Tensor) -> torch.Tensor:
432
+ semantic = language_model.model.embed_tokens(codes[..., 0] + AUDIO_CODE_OFFSET)
433
+ offsets = torch.arange(codes.shape[-1] - 1, device=codes.device) * 1024
434
+ acoustic = depth_decoder.audio_embeddings(codes[..., 1:] + offsets).sum(dim=-2)
435
+ return (semantic + acoustic.to(semantic.dtype)) * (codes.shape[-1] ** -0.5)
436
+
437
+
438
+ @torch.inference_mode()
439
+ def replay_codes_diffusers(
440
+ pipeline,
441
+ codes: torch.Tensor,
442
+ *,
443
+ prompt: str,
444
+ lyrics: str,
445
+ generator: torch.Generator | None = None,
446
+ ) -> torch.Tensor:
447
+ """Replay predicted codes through official Diffusers LM components."""
448
+ if codes.ndim != 2 or codes.shape[1] != 8 or codes.shape[0] == 0:
449
+ raise ValueError("codes must have shape [frames, 8]")
450
+ language_model = pipeline.language_model
451
+ depth_decoder = pipeline.rvq_depth_decoder
452
+ device = next(language_model.parameters()).device
453
+ codes = codes.to(device=device, dtype=torch.long)
454
+ text_ids = build_text_ids(pipeline.tokenizer, prompt, lyrics, device)
455
+ text_output = language_model.model(inputs_embeds=language_model.model.embed_tokens(text_ids), use_cache=True)
456
+ past = text_output.past_key_values
457
+ last_hidden = text_output.last_hidden_state[:, -1]
458
+
459
+ vocab_mask = torch.ones(language_model.config.vocab_size, dtype=torch.bool, device=device)
460
+ vocab_mask[AUDIO_CODE_OFFSET : AUDIO_CODE_OFFSET + SEMANTIC_VOCAB_SIZE] = False
461
+ vocab_mask[AUDIO_END_TOKEN_ID] = False
462
+ logits = language_model.lm_head(last_hidden).float().masked_fill(vocab_mask, -torch.inf)
463
+ conditioned, unconditioned = logits[:1], logits[1:2]
464
+ guided = unconditioned + (conditioned - unconditioned) * AR_CFG_SCALE
465
+ threshold = torch.topk(conditioned, AR_TOP_K, dim=-1).values[..., -1, None]
466
+ warmup_token = _sample_top_k(guided.masked_fill(conditioned < threshold, -torch.inf), generator)
467
+ if int(warmup_token.item()) == AUDIO_END_TOKEN_ID:
468
+ raise ValueError("the selected seed ended during the required AR warm-up frame")
469
+ warmup_semantic = (warmup_token - AUDIO_CODE_OFFSET).repeat(2)
470
+
471
+ sequence = [depth_decoder.projection(last_hidden).unsqueeze(1)]
472
+ semantic_embed = language_model.model.embed_tokens(warmup_semantic + AUDIO_CODE_OFFSET)
473
+ sequence.append(depth_decoder.projection(semantic_embed).unsqueeze(1))
474
+ warmup_codes = [warmup_semantic]
475
+ for index in range(1, 8):
476
+ depth_hidden = depth_decoder(torch.cat(sequence, dim=1))[:, -1]
477
+ depth_logits = depth_decoder.audio_heads[index - 1](depth_hidden).float()
478
+ depth_guided = depth_logits[1:2] + (depth_logits[:1] - depth_logits[1:2]) * AR_CFG_SCALE
479
+ code = _sample_top_k(depth_guided, generator).repeat(2)
480
+ warmup_codes.append(code)
481
+ if index < 7:
482
+ embedding = depth_decoder.audio_embeddings(code + (index - 1) * 1024)
483
+ sequence.append(depth_decoder.projection(embedding).unsqueeze(1))
484
+ warmup_codes = torch.stack(warmup_codes, dim=1)
485
+ output = language_model.model(
486
+ inputs_embeds=_embed_official_codes(language_model, depth_decoder, warmup_codes).unsqueeze(1),
487
+ past_key_values=past,
488
+ use_cache=True,
489
+ )
490
+ past = output.past_key_values
491
+ first_hidden = output.last_hidden_state[:, -1]
492
+
493
+ hidden_parts = [first_hidden.unsqueeze(1)]
494
+ if codes.shape[0] > 1:
495
+ feedback_codes = codes[:-1].unsqueeze(0).repeat(2, 1, 1)
496
+ feedback = _embed_official_codes(language_model, depth_decoder, feedback_codes)
497
+ for start in range(0, feedback.shape[1], LM_BLOCK_FRAMES):
498
+ output = language_model.model(
499
+ inputs_embeds=feedback[:, start : start + LM_BLOCK_FRAMES],
500
+ past_key_values=past,
501
+ use_cache=True,
502
+ )
503
+ past = output.past_key_values
504
+ hidden_parts.append(output.last_hidden_state)
505
+ global_hidden = torch.cat(hidden_parts, dim=1)
506
+ repeated_codes = codes.unsqueeze(0).repeat(2, 1, 1).reshape(-1, 8)
507
+ depth_hidden = _official_depth_hidden(
508
+ language_model,
509
+ depth_decoder,
510
+ global_hidden.reshape(-1, global_hidden.shape[-1]),
511
+ repeated_codes,
512
+ ).view(2, codes.shape[0], -1)
513
+ return torch.cat((global_hidden[:1], depth_hidden[:1]), dim=-1).cpu()
514
+
515
+
516
+ class MiniMaxMusic3ReferenceAdapter:
517
+ def __init__(self, dav_encoder: DAVEncoderOnly, rvq_encoder: MiniMaxMusicRVQEncoder):
518
+ self.dav_encoder = dav_encoder.eval()
519
+ self.rvq_encoder = rvq_encoder.eval()
520
+
521
+ @classmethod
522
+ def from_files(cls, encoder_file: str | Path, config_file: str | Path, dav_file: str | Path):
523
+ import json
524
+
525
+ from safetensors.torch import load_file
526
+
527
+ config = RVQEncoderConfig.from_dict(json.loads(Path(config_file).read_text(encoding="utf-8")))
528
+ rvq_encoder = MiniMaxMusicRVQEncoder(config)
529
+ rvq_encoder.load_state_dict(load_file(str(encoder_file)), strict=True)
530
+ dav_encoder = DAVEncoderOnly()
531
+ checkpoint = torch.load(dav_file, map_location="cpu", weights_only=True)
532
+ dav_state = {key: value for key, value in checkpoint.items() if key.startswith(("encoder.", "mean_proj."))}
533
+ dav_encoder.load_state_dict(dav_state, strict=True)
534
+ return cls(dav_encoder, rvq_encoder)
535
+
536
+ @classmethod
537
+ def from_pretrained(
538
+ cls,
539
+ repo_id: str = COLLECTION_REPO_ID,
540
+ *,
541
+ encoder_file: str = DEFAULT_ENCODER_FILE,
542
+ revision: str | None = None,
543
+ cache_dir: str | None = None,
544
+ official_model_revision: str | None = None,
545
+ ):
546
+ from huggingface_hub import hf_hub_download
547
+
548
+ encoder_path = hf_hub_download(repo_id, encoder_file, revision=revision, cache_dir=cache_dir)
549
+ config_path = hf_hub_download(
550
+ repo_id,
551
+ str(Path(encoder_file).with_suffix(".json")),
552
+ revision=revision,
553
+ cache_dir=cache_dir,
554
+ )
555
+ dav_path = hf_hub_download(
556
+ OFFICIAL_MODEL_REPO_ID,
557
+ DAV_FILE,
558
+ revision=official_model_revision,
559
+ cache_dir=cache_dir,
560
+ )
561
+ return cls.from_files(encoder_path, config_path, dav_path)
562
+
563
+ @staticmethod
564
+ def _resample(waveform: torch.Tensor, sample_rate: int) -> torch.Tensor:
565
+ if waveform.ndim == 1:
566
+ waveform = waveform.unsqueeze(0)
567
+ if waveform.ndim == 3:
568
+ if waveform.shape[0] != 1:
569
+ raise ValueError("reference audio batch size must be one")
570
+ waveform = waveform[0]
571
+ if waveform.ndim != 2:
572
+ raise ValueError("waveform must have shape [samples], [channels, samples], or [1, channels, samples]")
573
+ if sample_rate != SAMPLE_RATE:
574
+ import torchaudio
575
+
576
+ waveform = torchaudio.functional.resample(waveform, sample_rate, SAMPLE_RATE)
577
+ return waveform.float()
578
+
579
+ @torch.inference_mode()
580
+ def predict_codes(
581
+ self,
582
+ waveform: torch.Tensor,
583
+ sample_rate: int,
584
+ *,
585
+ device: str | torch.device | None = None,
586
+ encoder_dtype: torch.dtype | None = None,
587
+ ) -> torch.Tensor:
588
+ waveform = self._resample(waveform, sample_rate)
589
+ original_samples = waveform.shape[-1]
590
+ if original_samples < SAMPLE_RATE // FRAME_RATE:
591
+ raise ValueError("reference audio must contain at least one 25 Hz frame")
592
+ device = torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
593
+ encoder_dtype = encoder_dtype or (torch.bfloat16 if device.type == "cuda" else torch.float32)
594
+
595
+ self.dav_encoder.to(device=device, dtype=torch.float32)
596
+ latents = self.dav_encoder(waveform.unsqueeze(0).to(device=device, dtype=torch.float32))[0].cpu()
597
+ self.dav_encoder.to("cpu")
598
+
599
+ frame_count = int(original_samples * FRAME_RATE // SAMPLE_RATE)
600
+ bounds = frame_latent_starts(frame_count)
601
+ while frame_count and bounds[-1] > latents.shape[-1]:
602
+ frame_count -= 1
603
+ bounds = frame_latent_starts(frame_count)
604
+ if not frame_count:
605
+ raise ValueError("DAV encoding produced no complete reference frames")
606
+
607
+ window_size = self.rvq_encoder.config.max_position_embeddings
608
+ regular_starts = list(range(0, max(frame_count - window_size + 1, 0), window_size))
609
+ if frame_count >= window_size:
610
+ regular_starts.append(frame_count - window_size if not regular_starts else regular_starts[-1])
611
+ regular_starts = sorted(set(regular_starts))
612
+ tail = frame_count - window_size
613
+ if tail not in regular_starts:
614
+ regular_starts.append(tail)
615
+ else:
616
+ regular_starts = [0]
617
+
618
+ predictions = torch.empty((frame_count, 8), dtype=torch.long)
619
+ assigned = torch.zeros(frame_count, dtype=torch.bool)
620
+ self.rvq_encoder.to(device=device, dtype=encoder_dtype)
621
+ autocast = torch.autocast(device.type, dtype=encoder_dtype) if device.type == "cuda" else nullcontext()
622
+ with autocast:
623
+ for frame_start in regular_starts:
624
+ frame_end = min(frame_start + window_size, frame_count)
625
+ local_bounds = bounds[frame_start : frame_end + 1]
626
+ latent_start, latent_end = local_bounds[0], local_bounds[-1]
627
+ window_latents = latents[:, latent_start:latent_end].transpose(0, 1).to(device, dtype=encoder_dtype)
628
+ pool = build_pool_matrix(local_bounds).to(device)
629
+ logits = self.rvq_encoder(window_latents.unsqueeze(0), pool.unsqueeze(0))
630
+ predicted = torch.stack([head.argmax(dim=-1)[0] for head in logits], dim=-1).cpu()
631
+ take = ~assigned[frame_start:frame_end]
632
+ predictions[frame_start:frame_end][take] = predicted[take]
633
+ assigned[frame_start:frame_end][take] = True
634
+ self.rvq_encoder.to("cpu")
635
+ if not assigned.all():
636
+ raise RuntimeError("RVQ window inference did not cover every reference frame")
637
+ return predictions
638
+
639
+ def encode_reference(
640
+ self,
641
+ pipeline,
642
+ waveform: torch.Tensor,
643
+ sample_rate: int,
644
+ *,
645
+ prompt: str,
646
+ lyrics: str,
647
+ generator: torch.Generator | None = None,
648
+ device: str | torch.device | None = None,
649
+ ) -> tuple[torch.Tensor, torch.Tensor]:
650
+ codes = self.predict_codes(waveform, sample_rate, device=device)
651
+ frame_hiddens = replay_codes_diffusers(
652
+ pipeline,
653
+ codes,
654
+ prompt=prompt,
655
+ lyrics=lyrics,
656
+ generator=generator,
657
+ )
658
+ return frame_hiddens, codes
659
+
660
+
661
+ def install_diffusers_reference_adapter() -> None:
662
+ """Allow the official modular pipeline to accept precomputed frame_hiddens."""
663
+ from diffusers.modular_pipelines.minimax_music3.encoders import (
664
+ MiniMaxMusic3AutoregressiveStep,
665
+ MiniMaxMusic3TokenizeStep,
666
+ )
667
+ from diffusers.modular_pipelines.modular_pipeline_utils import InputParam
668
+
669
+ if getattr(MiniMaxMusic3TokenizeStep, "_simpletuner_reference_patch", False):
670
+ return
671
+
672
+ original_tokenize_call = MiniMaxMusic3TokenizeStep.__call__
673
+ original_generate_call = MiniMaxMusic3AutoregressiveStep.__call__
674
+ original_tokenize_inputs = MiniMaxMusic3TokenizeStep.inputs.fget
675
+ original_generate_inputs = MiniMaxMusic3AutoregressiveStep.inputs.fget
676
+
677
+ def tokenize_inputs(self):
678
+ inputs = original_tokenize_inputs(self)
679
+ for value in inputs:
680
+ if value.name in {"prompt", "lyrics"}:
681
+ value.required = False
682
+ inputs.append(InputParam("frame_hiddens", default=None, type_hint=torch.Tensor))
683
+ return inputs
684
+
685
+ def generate_inputs(self):
686
+ inputs = original_generate_inputs(self)
687
+ for value in inputs:
688
+ if value.name == "text_ids":
689
+ value.required = False
690
+ inputs.append(InputParam("frame_hiddens", default=None, type_hint=torch.Tensor))
691
+ return inputs
692
+
693
+ def tokenize_call(self, components, state):
694
+ block_state = self.get_block_state(state)
695
+ if getattr(block_state, "frame_hiddens", None) is not None:
696
+ return components, state
697
+ return original_tokenize_call(self, components, state)
698
+
699
+ def generate_call(self, components, state):
700
+ block_state = self.get_block_state(state)
701
+ if getattr(block_state, "frame_hiddens", None) is not None:
702
+ return components, state
703
+ return original_generate_call(self, components, state)
704
+
705
+ MiniMaxMusic3TokenizeStep.inputs = property(tokenize_inputs)
706
+ MiniMaxMusic3AutoregressiveStep.inputs = property(generate_inputs)
707
+ MiniMaxMusic3TokenizeStep.__call__ = tokenize_call
708
+ MiniMaxMusic3AutoregressiveStep.__call__ = generate_call
709
+ MiniMaxMusic3TokenizeStep._simpletuner_reference_patch = True
710
+ MiniMaxMusic3AutoregressiveStep._simpletuner_reference_patch = True