LING commited on
Commit
fade829
·
1 Parent(s): 2b8523a

Deploy Magenta RealTime 2 HARP endpoint

Browse files
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ .DS_Store
4
+ .venv/
5
+ outputs/
6
+ checkpoints/
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
README.md CHANGED
@@ -1,15 +1,22 @@
1
  ---
2
- title: Magenta Rt
3
- emoji: 📊
4
- colorFrom: red
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
  short_description: Text-guided short music generation with Magenta RealTime 2
 
13
  ---
14
 
15
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
1
  ---
2
+ title: Magenta RealTime 2
3
+ emoji: 🎹
4
+ colorFrom: indigo
5
+ colorTo: green
6
  sdk: gradio
7
+ sdk_version: 5.28.0
8
+ python_version: "3.12"
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
12
  short_description: Text-guided short music generation with Magenta RealTime 2
13
+ startup_duration_timeout: 1h
14
  ---
15
 
16
+ Generate short, steerable music clips with the Magenta RealTime 2 small model.
17
+
18
+ This Space exposes a HARP-compatible endpoint for use in audio production
19
+ workflows. Model weights are downloaded from the official Google repository at
20
+ runtime and are not stored in this Space.
21
+
22
+ See [SOURCES.md](SOURCES.md) for source code, model, and license information.
SOURCES.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Sources
2
+
3
+ ## Magenta RealTime 2
4
+
5
+ - Model: https://huggingface.co/google/magenta-realtime-2
6
+ - Source: https://github.com/magenta/magenta-realtime
7
+ - Documentation: https://magenta.github.io/magenta-realtime/
8
+ - Code license: Apache License 2.0
9
+ - Model weights license: Creative Commons Attribution 4.0
10
+
11
+ ## PyTorch ZeroGPU Runtime
12
+
13
+ - Reference Space: https://huggingface.co/spaces/magenta-community/magenta-rt-jam
14
+ - Reference revision: dfd4eb65e8ebf7eaa9be263d9937ddbee1666262
15
+ - AOTI artifacts: https://huggingface.co/magenta-torch/magenta-rt-aoti-small
16
+
17
+ The `magenta_rt` package in this Space is the PyTorch runtime distributed with
18
+ the reference Space. Its source files retain the original Google copyright and
19
+ Apache License 2.0 notices.
app.py ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import uuid
5
+ from pathlib import Path
6
+
7
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
8
+
9
+ import gradio as gr
10
+ import numpy as np
11
+ import soundfile as sf
12
+ import spaces
13
+ import torch
14
+ from huggingface_hub import hf_hub_download
15
+ from pyharp import ModelCard, build_endpoint
16
+
17
+ from magenta_rt import paths
18
+ from magenta_rt.torch import MagentaRT2
19
+ from magenta_rt.torch.musiccoca import MusicCoCa
20
+
21
+
22
+ MODEL_REPO = "google/magenta-realtime-2"
23
+ MODEL_NAME = "mrt2_small"
24
+ CHECKPOINT = f"{MODEL_NAME}.safetensors"
25
+ AOTI_REPO = "magenta-torch/magenta-rt-aoti-small"
26
+ SAMPLE_RATE = 48_000
27
+ FRAMES_PER_SECOND = 25
28
+ OUTPUT_DIR = Path("/tmp/magenta_rt_outputs")
29
+
30
+ model_root = Path("/data" if Path("/data").is_dir() else "/tmp/magenta")
31
+ magenta_home = model_root / "magenta-rt-v2"
32
+ magenta_home.mkdir(parents=True, exist_ok=True)
33
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
34
+
35
+ hf_hub_download(
36
+ repo_id=MODEL_REPO,
37
+ filename=f"checkpoints/{CHECKPOINT}",
38
+ local_dir=magenta_home,
39
+ )
40
+ paths.set_magenta_home(magenta_home)
41
+
42
+ style_model = MusicCoCa(device="cpu")
43
+ model = MagentaRT2(
44
+ size=MODEL_NAME,
45
+ device="cuda",
46
+ dtype=torch.bfloat16,
47
+ style_model=style_model,
48
+ )
49
+
50
+ try:
51
+ model.load_compiled(repo_id=AOTI_REPO)
52
+ except Exception as exc:
53
+ print(f"AOTI loading failed; using eager inference: {exc}", flush=True)
54
+
55
+
56
+ model_card = ModelCard(
57
+ name="Magenta RealTime 2",
58
+ description=(
59
+ "Generate short instrumental music clips from text prompts using "
60
+ "Google's open-weights Magenta RealTime 2 small model."
61
+ ),
62
+ author="Google DeepMind",
63
+ tags=[
64
+ "music-generation",
65
+ "text-to-music",
66
+ "instrument-synthesis",
67
+ "real-time-music",
68
+ ],
69
+ )
70
+
71
+
72
+ @spaces.GPU(duration=45)
73
+ def process_fn(
74
+ prompt: str,
75
+ duration: str,
76
+ temperature: float,
77
+ top_k: float,
78
+ seed: float,
79
+ ) -> str:
80
+ prompt = (prompt or "").strip()
81
+ if not prompt:
82
+ raise gr.Error("Please describe the music you want to generate.")
83
+ if len(prompt) > 300:
84
+ raise gr.Error("The prompt must be 300 characters or fewer.")
85
+
86
+ duration_seconds = int(duration)
87
+ expected_samples = duration_seconds * SAMPLE_RATE
88
+ frames = duration_seconds * FRAMES_PER_SECOND + 1
89
+
90
+ try:
91
+ if style_model.device != "cuda":
92
+ style_model.to("cuda")
93
+ audio, _ = model.generate(
94
+ style=prompt,
95
+ temperature=float(temperature),
96
+ top_k=int(top_k),
97
+ frames=frames,
98
+ seed=int(seed),
99
+ flush=True,
100
+ )
101
+ except Exception as exc:
102
+ raise gr.Error(f"Magenta RealTime 2 inference failed: {exc}") from exc
103
+
104
+ audio = np.asarray(audio, dtype=np.float32)
105
+ if audio.ndim != 2 or audio.shape[1] != 2:
106
+ raise gr.Error("The model returned an unexpected audio shape.")
107
+ if len(audio) < expected_samples:
108
+ raise gr.Error("The model returned less audio than requested.")
109
+
110
+ output_path = OUTPUT_DIR / f"{uuid.uuid4().hex}.wav"
111
+ sf.write(
112
+ output_path,
113
+ audio[:expected_samples],
114
+ SAMPLE_RATE,
115
+ subtype="PCM_16",
116
+ )
117
+ return str(output_path)
118
+
119
+
120
+ with gr.Blocks(title="Magenta RealTime 2") as demo:
121
+ input_components = [
122
+ gr.Textbox(
123
+ value="warm analog synthesizer with a gentle rhythmic pulse",
124
+ label="Music Prompt",
125
+ info="Describe the instruments, texture, style, or mood.",
126
+ lines=2,
127
+ max_lines=4,
128
+ ),
129
+ gr.Dropdown(
130
+ choices=["2", "4", "8"],
131
+ value="4",
132
+ label="Duration (seconds)",
133
+ info="Length of the generated clip.",
134
+ ),
135
+ gr.Slider(
136
+ minimum=0.1,
137
+ maximum=2.0,
138
+ step=0.1,
139
+ value=1.1,
140
+ label="Temperature",
141
+ info="Higher values produce more variation.",
142
+ ),
143
+ gr.Slider(
144
+ minimum=10,
145
+ maximum=100,
146
+ step=5,
147
+ value=50,
148
+ label="Top-k",
149
+ info="Limits each sampling step to the most likely tokens.",
150
+ ),
151
+ gr.Number(
152
+ value=0,
153
+ minimum=0,
154
+ maximum=2_147_483_647,
155
+ precision=0,
156
+ label="Seed",
157
+ info="Use the same seed and controls to reproduce a result.",
158
+ ),
159
+ ]
160
+ output_components = [
161
+ gr.Audio(
162
+ type="filepath",
163
+ label="Generated Music",
164
+ ).set_info("A 48 kHz stereo WAV file."),
165
+ ]
166
+ build_endpoint(
167
+ model_card=model_card,
168
+ input_components=input_components,
169
+ output_components=output_components,
170
+ process_fn=process_fn,
171
+ )
172
+
173
+
174
+ if __name__ == "__main__":
175
+ demo.queue(default_concurrency_limit=1).launch(
176
+ show_error=True,
177
+ pwa=True,
178
+ )
magenta_rt/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """Magenta RealTime 2 (PyTorch) — Spaces package."""
2
+ __version__="2.0.3"
magenta_rt/paths.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """Centralized path resolution for Magenta RT.
16
+
17
+ All paths resolve under MAGENTA_HOME/magenta-rt-v2 (where MAGENTA_HOME defaults to ~/Documents/Magenta).
18
+ Override with the MAGENTA_HOME environment variable.
19
+ """
20
+
21
+ import os
22
+ import pathlib
23
+ from typing import Union
24
+
25
+
26
+ # Configurable root for all downloaded assets and models.
27
+ _MAGENTA_BASE = pathlib.Path(
28
+ os.environ.get("MAGENTA_HOME", pathlib.Path.home() / "Documents" / "Magenta")
29
+ )
30
+ _MAGENTA_HOME = _MAGENTA_BASE / "magenta-rt-v2"
31
+
32
+ # Default model directory name (under ~/Documents/Magenta/magenta-rt-v2/models/).
33
+ DEFAULT_MODEL_NAME = "mrt2_base"
34
+ DEFAULT_CHECKPOINT = "mrt2_base.safetensors"
35
+
36
+
37
+ def magenta_home() -> pathlib.Path:
38
+ """Returns the magenta home directory (default: ~/Documents/Magenta/magenta-rt-v2)."""
39
+ return _MAGENTA_HOME
40
+
41
+
42
+ def set_magenta_home(path: Union[pathlib.Path, str]) -> None:
43
+ """Override the magenta home directory at runtime."""
44
+ global _MAGENTA_HOME
45
+ if isinstance(path, str):
46
+ path = pathlib.Path(path)
47
+ _MAGENTA_HOME = path
48
+
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # Resource directories
52
+ # ---------------------------------------------------------------------------
53
+
54
+
55
+ def resources_dir() -> pathlib.Path:
56
+ """~/Documents/Magenta/magenta-rt-v2/resources — shared resource files (musiccoca, spectrostream)."""
57
+ return _MAGENTA_HOME / "resources"
58
+
59
+
60
+ def musiccoca_dir() -> pathlib.Path:
61
+ """~/Documents/Magenta/magenta-rt-v2/resources/musiccoca — MusicCoCa TFLite models."""
62
+ return resources_dir() / "musiccoca"
63
+
64
+
65
+ def spectrostream_dir() -> pathlib.Path:
66
+ """~/Documents/Magenta/magenta-rt-v2/resources/spectrostream — SpectroStream weights."""
67
+ return resources_dir() / "spectrostream"
68
+
69
+
70
+ def models_dir() -> pathlib.Path:
71
+ """~/Documents/Magenta/magenta-rt-v2/models — exported .mlxfn model directories."""
72
+ return _MAGENTA_HOME / "models"
73
+
74
+
75
+ def default_model_dir() -> pathlib.Path:
76
+ """~/Documents/Magenta/magenta-rt-v2/models/<DEFAULT_MODEL_NAME> — the default model to load."""
77
+ return models_dir() / DEFAULT_MODEL_NAME
78
+
79
+
80
+ def outputs_dir() -> pathlib.Path:
81
+ """~/Documents/Magenta/magenta-rt-v2/outputs — generation and export outputs."""
82
+ d = _MAGENTA_HOME / "outputs"
83
+ d.mkdir(parents=True, exist_ok=True)
84
+ return d
85
+
86
+
87
+ def checkpoints_dir() -> pathlib.Path:
88
+ """~/Documents/Magenta/magenta-rt-v2/checkpoints — full safetensors from Linen models."""
89
+ d = _MAGENTA_HOME / "checkpoints"
90
+ d.mkdir(parents=True, exist_ok=True)
91
+ return d
92
+
93
+
94
+ def resolve_checkpoint(filename: str) -> pathlib.Path:
95
+ """Resolve a checkpoint file path.
96
+
97
+ First check if literal filepath exists; fallback to ~/Documents/Magenta/magenta-rt-v2/checkpoints/<filename>.
98
+
99
+ Args:
100
+ filename: Checkpoint filename ending in `.safetensors`
101
+
102
+ Returns:
103
+ Path to the checkpoint file (may not exist yet).
104
+ """
105
+ if os.path.isfile(filename):
106
+ return filename
107
+ return checkpoints_dir() / filename
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # Path resolution without fallbacks — everything in ~/Documents/Magenta/magenta-rt-v2)
111
+ # ---------------------------------------------------------------------------
112
+
113
+ def resolve_encoder_weights() -> pathlib.Path:
114
+ """Returns ~/Documents/Magenta/magenta-rt-v2/resources/spectrostream/encoder.safetensors."""
115
+ return spectrostream_dir() / "encoder.safetensors"
116
+
117
+
118
+ def resolve_decoder_weights() -> pathlib.Path:
119
+ """Returns ~/Documents/Magenta/magenta-rt-v2/resources/spectrostream/decoder.safetensors."""
120
+ return spectrostream_dir() / "decoder.safetensors"
magenta_rt/torch/__init__.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """Pure-PyTorch port of Magenta RealTime 2."""
16
+ from .system import MagentaRT2 # noqa: F401
magenta_rt/torch/aoti.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """AOTI export specs for the functional per-frame step.
16
+
17
+ The temporal and depth steps are export-clean (no inline constant tensors,
18
+ mask-free incremental KV) and exported with dynamic KV-length dims, so a single
19
+ compiled graph serves every frame. Artifacts are GPU-arch specific — compile on
20
+ the same hardware (e.g. the ZeroGPU Blackwell) you run on.
21
+ """
22
+ import torch
23
+ import torch.nn as nn
24
+ from torch.export import Dim
25
+
26
+
27
+ class TemporalStepModule(nn.Module):
28
+ """forward(prev_frame, self_kv, cross_kv, source) -> (out, new_self, new_cross)."""
29
+
30
+ def __init__(self, decoder):
31
+ super().__init__()
32
+ self.d = decoder
33
+
34
+ def forward(self, prev_frame, self_kv, cross_kv, source):
35
+ return self.d.temporal_step_fn(prev_frame, self_kv, cross_kv, source)
36
+
37
+
38
+ class DepthStepModule(nn.Module):
39
+ """forward(depth_input, depth_kv) -> (logits, new_kv)."""
40
+
41
+ def __init__(self, decoder):
42
+ super().__init__()
43
+ self.d = decoder
44
+
45
+ def forward(self, depth_input, depth_kv):
46
+ return self.d.depth_step_fn(depth_input, depth_kv)
47
+
48
+
49
+ def _params_ref(decoder):
50
+ p = next(decoder.parameters())
51
+ return p.device, p.dtype
52
+
53
+
54
+ def temporal_export_inputs(decoder, kv_len=20):
55
+ """(args, dynamic_shapes) for exporting the temporal step."""
56
+ dev, dt = _params_ref(decoder)
57
+ c = decoder.cfg
58
+ L, nh, uph = c.temporal.num_layers, c.temporal.num_heads, c.temporal.dim_per_head
59
+ prev = torch.randint(c.num_reserved_tokens, c.num_reserved_tokens + c.codebook_size,
60
+ (1, 1, c.num_codebooks), device=dev)
61
+ mk = lambda: [(torch.randn(1, kv_len, nh, uph, device=dev, dtype=dt),
62
+ torch.randn(1, kv_len, nh, uph, device=dev, dtype=dt)) for _ in range(L)]
63
+ self_kv, cross_kv = mk(), mk()
64
+ source = torch.randn(1, 1, c.encoder_model_dims, device=dev, dtype=dt)
65
+ T = Dim("T", min=0, max=c.temporal_max_past + 1)
66
+ ds = (None, [({1: T}, {1: T}) for _ in range(L)], [({1: T}, {1: T}) for _ in range(L)], None)
67
+ return (prev, self_kv, cross_kv, source), ds
68
+
69
+
70
+ def depth_export_inputs(decoder, kv_len=6):
71
+ """(args, dynamic_shapes) for exporting the depth step."""
72
+ dev, dt = _params_ref(decoder)
73
+ c = decoder.cfg
74
+ L, nh, uph = c.depth.num_layers, c.depth.num_heads, c.depth.dim_per_head
75
+ depth_input = torch.randn(1, 1, c.temporal.model_dims, device=dev, dtype=dt)
76
+ depth_kv = [(torch.randn(1, kv_len, nh, uph, device=dev, dtype=dt),
77
+ torch.randn(1, kv_len, nh, uph, device=dev, dtype=dt)) for _ in range(L)]
78
+ Td = Dim("Td", min=0, max=c.num_codebooks)
79
+ ds = (None, [({1: Td}, {1: Td}) for _ in range(L)])
80
+ return (depth_input, depth_kv), ds
81
+
82
+
83
+ def export_temporal(decoder):
84
+ args, ds = temporal_export_inputs(decoder)
85
+ return torch.export.export(TemporalStepModule(decoder), args, dynamic_shapes=ds)
86
+
87
+
88
+ def export_depth(decoder):
89
+ args, ds = depth_export_inputs(decoder)
90
+ return torch.export.export(DepthStepModule(decoder), args, dynamic_shapes=ds)
91
+
92
+
93
+ def temporal_state_dict(decoder):
94
+ return TemporalStepModule(decoder).state_dict()
95
+
96
+
97
+ def depth_state_dict(decoder):
98
+ return DepthStepModule(decoder).state_dict()
99
+
100
+
101
+ # --- ZeroGPU-native compile / save / load (uses the `spaces` library) --------
102
+ # Artifacts are GPU-arch specific: compile and run on the same hardware.
103
+
104
+ def compile_step_archives(decoder, inductor_configs=None):
105
+ """Export + AOTI-compile the temporal and depth steps with spaces.aoti_compile.
106
+ Returns {'temporal': bytes, 'depth': bytes} — weight-less graph .pt2 blobs.
107
+ Must run on a GPU (inside @spaces.GPU on ZeroGPU)."""
108
+ import importlib
109
+ spaces = importlib.import_module("spaces") # optional (ZeroGPU); kept off the import graph
110
+ out = {}
111
+ for name, ep in (("temporal", export_temporal(decoder)),
112
+ ("depth", export_depth(decoder))):
113
+ cm = spaces.aoti_compile(ep, inductor_configs)
114
+ out[name] = bytes(cm.archive_file.getbuffer())
115
+ return out
116
+
117
+
118
+ def load_compiled_steps(decoder, repo_id=None, local_dir=None, filenames=("temporal.pt2", "depth.pt2")):
119
+ """Load weight-less step graphs and bind them to `decoder`'s own weights.
120
+ Returns (temporal_callable, depth_callable) for step_f(temporal_step=, depth_step=)."""
121
+ import importlib, os
122
+ LazyAOTIModel = importlib.import_module("spaces.zero.torch.aoti").LazyAOTIModel
123
+ paths = {}
124
+ keys = ("temporal", "depth")
125
+ if local_dir is not None:
126
+ for k, fn in zip(keys, filenames):
127
+ paths[k] = os.path.join(local_dir, fn)
128
+ else:
129
+ from huggingface_hub import hf_hub_download
130
+ for k, fn in zip(keys, filenames):
131
+ paths[k] = hf_hub_download(repo_id, fn)
132
+ t = LazyAOTIModel(paths["temporal"]).with_weights(temporal_state_dict(decoder))
133
+ d = LazyAOTIModel(paths["depth"]).with_weights(depth_state_dict(decoder))
134
+ return t, d
magenta_rt/torch/depthformer.py ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """PyTorch port of the Magenta RealTime 2 Depthformer (encoder + multivariate decoder)."""
16
+ import math
17
+ from dataclasses import dataclass
18
+
19
+ import torch
20
+ import torch.nn as nn
21
+
22
+ from . import layers as L
23
+
24
+
25
+ @dataclass
26
+ class SpecDims:
27
+ num_layers: int
28
+ model_dims: int
29
+ hidden_dims: int
30
+ num_heads: int
31
+ dim_per_head: int
32
+
33
+
34
+ @dataclass
35
+ class DepthformerConfig:
36
+ # encoder (conditioning embedder) dims
37
+ encoder_model_dims: int
38
+ musiccoca_rvq: int # 12
39
+ musiccoca_per_rvq_vocab: int # 1031
40
+ musiccoca_embed_dim: int # 768
41
+ regular_num_embeddings_per_channel: list # per regular channel vocab sizes
42
+ regular_num_channels: int # 132
43
+ # decoder
44
+ temporal: SpecDims
45
+ depth: SpecDims
46
+ temporal_max_past: int # 41 (small) / 25 (base)
47
+ depth_max_past: int # 12
48
+ num_sinks: int # 1
49
+ num_codebooks: int # 12
50
+ codebook_size: int # 1024
51
+ num_reserved_tokens: int # 6
52
+ vocab_size: int # 12294
53
+ soft_cap_logits: float = 30.0
54
+
55
+
56
+ def _mean_f32(x, axis):
57
+ return x.float().mean(axis).to(x.dtype)
58
+
59
+
60
+ class TransformerStack(nn.Module):
61
+ """Stack of primer-hybrid transformer layers; optional streaming cross-attn."""
62
+
63
+ def __init__(self, spec: SpecDims, max_past, num_sinks, use_cross, source_dim=None):
64
+ super().__init__()
65
+ self.use_cross = use_cross
66
+ self.layers = nn.ModuleList()
67
+ for _ in range(spec.num_layers):
68
+ blk = nn.ModuleDict()
69
+ blk["self_attention"] = L.SelfAttention(
70
+ spec.model_dims, spec.num_heads, spec.dim_per_head, max_past,
71
+ num_sinks=num_sinks)
72
+ if use_cross:
73
+ blk["cross_attention"] = L.CrossAttention(
74
+ spec.model_dims, source_dim, spec.num_heads, spec.dim_per_head,
75
+ max_past, num_sinks=num_sinks)
76
+ blk["ffn"] = L.FFN(spec.model_dims, spec.hidden_dims)
77
+ self.layers.append(blk)
78
+
79
+ def forward(self, x, source=None):
80
+ for blk in self.layers:
81
+ x = blk["self_attention"](x)
82
+ if self.use_cross:
83
+ x = blk["cross_attention"](x, source)
84
+ x = blk["ffn"](x)
85
+ return x
86
+
87
+ # ---- streaming ----
88
+ def init_state(self, batch, device, dtype, source=None):
89
+ st = []
90
+ for blk in self.layers:
91
+ s = {"self": blk["self_attention"].init_state(batch, device, dtype)}
92
+ if self.use_cross:
93
+ s["source_kv"] = (None, None)
94
+ st.append(s)
95
+ return st
96
+
97
+ def step(self, x, state, source_step=None):
98
+ # source_step: newly-encoded source frame [b,1,source_dim] to append to KV.
99
+ for blk, s in zip(self.layers, state):
100
+ x = blk["self_attention"].step(x, s["self"])
101
+ if self.use_cross:
102
+ ca = blk["cross_attention"]
103
+ k, v = ca._kv(source_step)
104
+ pk, pv = s["source_kv"]
105
+ k = k if pk is None else torch.cat([pk, k], dim=1)
106
+ v = v if pv is None else torch.cat([pv, v], dim=1)
107
+ keep = ca.max_past_horizon + 1
108
+ if k.shape[1] > keep:
109
+ k = k[:, -keep:]
110
+ v = v[:, -keep:]
111
+ s["source_kv"] = (k, v)
112
+ x = ca.step(x, (k, v))
113
+ x = blk["ffn"](x)
114
+ return x
115
+
116
+ def step_fn(self, x, self_kv, cross_kv, source_frame):
117
+ """Functional, export-clean per-frame step over all layers.
118
+
119
+ self_kv / cross_kv: lists of (k, v) per layer ([b,T,nh,uph], T may be 0).
120
+ Returns (out[b,1,d], new_self_kv, new_cross_kv) with untrimmed KV."""
121
+ new_self = []
122
+ new_cross = []
123
+ for i, blk in enumerate(self.layers):
124
+ x, k, v = blk["self_attention"].step_fn(x, self_kv[i][0], self_kv[i][1])
125
+ new_self.append((k, v))
126
+ if self.use_cross:
127
+ ca = blk["cross_attention"]
128
+ sk, sv = ca._kv(source_frame)
129
+ sk = torch.cat([cross_kv[i][0], sk], dim=1)
130
+ sv = torch.cat([cross_kv[i][1], sv], dim=1)
131
+ new_cross.append((sk, sv))
132
+ x = ca.attend_fn(x, sk, sv)
133
+ x = blk["ffn"](x)
134
+ return x, new_self, new_cross
135
+
136
+
137
+ class EncoderEmbedding(nn.Module):
138
+ """Embeds the conditioning block [b,t,num_channels] -> source [b,t,enc_dim]."""
139
+
140
+ def __init__(self, cfg: DepthformerConfig):
141
+ super().__init__()
142
+ self.cfg = cfg
143
+ m = cfg.musiccoca_rvq
144
+ self.m = m
145
+ # mulan branch
146
+ self.register_buffer("mulan_offset", torch.arange(m) * cfg.musiccoca_per_rvq_vocab)
147
+ self.mulan_dequantizer = nn.Parameter(
148
+ torch.zeros(m * cfg.musiccoca_per_rvq_vocab, cfg.musiccoca_embed_dim))
149
+ self.mulan_adapter = L.JaxLinear(cfg.musiccoca_embed_dim, cfg.encoder_model_dims, use_bias=False)
150
+ # regular branch (MultiChannelEmbedding)
151
+ per = cfg.regular_num_embeddings_per_channel
152
+ total = sum(per)
153
+ total = (total + 127) // 128 * 128
154
+ self.regular_embedding = nn.Parameter(torch.zeros(total, cfg.encoder_model_dims))
155
+ offs = [0]
156
+ for p in per[:-1]:
157
+ offs.append(offs[-1] + p)
158
+ self.register_buffer("regular_offsets", torch.tensor(offs, dtype=torch.long))
159
+ self.encoder_ln = L.LayerNorm(cfg.encoder_model_dims)
160
+
161
+ def forward(self, x):
162
+ # x: [b,t,num_channels] int
163
+ m = self.m
164
+ mulan = x[..., :m] # [b,t,m]
165
+ regular = x[..., m:] # [b,t,132]
166
+ off = self.mulan_offset.to(x.device)
167
+ idx = mulan + off # [b,t,m]
168
+ emb = self.mulan_dequantizer[idx] # [b,t,m,768]
169
+ emb = emb.sum(dim=-2) # [b,t,768]
170
+ mulan_out = self.mulan_adapter(emb) # [b,t,enc_dim]
171
+ # regular
172
+ ridx = regular + self.regular_offsets.to(x.device)
173
+ remb = self.regular_embedding[ridx] # [b,t,132,enc_dim]
174
+ regular_out = _mean_f32(remb, axis=-2) # mean over channels in fp32
175
+ # branch combine via MEAN
176
+ src = (mulan_out + regular_out) / 2.0
177
+ return self.encoder_ln(src)
178
+
179
+
180
+ class MultivariateDecoder(nn.Module):
181
+ def __init__(self, cfg: DepthformerConfig):
182
+ super().__init__()
183
+ self.cfg = cfg
184
+ td, dd = cfg.temporal, cfg.depth
185
+ self.embedding = nn.Parameter(torch.zeros(cfg.vocab_size, td.model_dims))
186
+ self.embed_scale = math.sqrt(td.model_dims)
187
+ self.temporal_body = TransformerStack(
188
+ td, cfg.temporal_max_past, cfg.num_sinks, use_cross=True,
189
+ source_dim=cfg.encoder_model_dims)
190
+ # depth input adapter (Dense temporal->depth, no bias) or identity
191
+ if td.model_dims != dd.model_dims:
192
+ self.depth_input_adapter = L.JaxLinear(td.model_dims, dd.model_dims, use_bias=False)
193
+ else:
194
+ self.depth_input_adapter = None
195
+ self.depth_body = TransformerStack(
196
+ dd, cfg.depth_max_past, num_sinks=0, use_cross=False)
197
+ self.final_ln = L.LayerNorm(dd.model_dims)
198
+ self.to_logits = L.JaxLinear(dd.model_dims, cfg.vocab_size, use_bias=True)
199
+
200
+ def embed(self, tokens):
201
+ # tokens: [...,] int -> [..., td_dim]
202
+ return self.embedding[tokens] * self.embed_scale
203
+
204
+ def _depth_forward(self, depth_inputs):
205
+ # depth_inputs: [N, Q, td_dim]
206
+ h = depth_inputs
207
+ if self.depth_input_adapter is not None:
208
+ h = self.depth_input_adapter(h)
209
+ h = self.depth_body(h)
210
+ h = self.final_ln(h)
211
+ logits = self.to_logits(h)
212
+ return logits
213
+
214
+ def forward(self, target, source):
215
+ """Teacher-forced. target: [b,T,Q] int; source: [b,Tc,enc_dim]. -> logits [b,T,Q,vocab]."""
216
+ cfg = self.cfg
217
+ b, T, Q = target.shape
218
+ sos = target.new_zeros((b, 1, Q)) # sos_id=0
219
+ x = torch.cat([sos, target], dim=1) # [b,T+1,Q]
220
+ embedded = self.embed(x) # [b,T+1,Q,D]
221
+ temporal_inputs = _mean_f32(embedded, axis=-2)[:, :-1] # [b,T,D]
222
+ temporal_outputs = self.temporal_body(temporal_inputs, source) # [b,T,D]
223
+ depth_inputs = torch.cat(
224
+ [temporal_outputs[..., None, :], embedded[:, 1:, :-1]], dim=-2) # [b,T,Q,D]
225
+ N = b * T
226
+ logits = self._depth_forward(depth_inputs.reshape(N, Q, -1)).reshape(b, T, Q, -1)
227
+ if cfg.soft_cap_logits is not None:
228
+ c = cfg.soft_cap_logits
229
+ logits = torch.tanh(logits / c) * c
230
+ return logits
231
+
232
+ # ---- functional (AOTI-compilable) streaming -------------------------
233
+ def temporal_step_fn(self, prev_frame, self_kv, cross_kv, source_frame):
234
+ """Functional temporal step: prev_frame[b,1,Q] -> (temporal_out, kv...)."""
235
+ embedded = self.embed(prev_frame)
236
+ ti = _mean_f32(embedded, axis=-2)
237
+ return self.temporal_body.step_fn(ti, self_kv, cross_kv, source_frame)
238
+
239
+ def depth_step_fn(self, depth_input, depth_kv):
240
+ """Functional depth step: depth_input[b,1,Dt] + kv -> (logits, new_kv)."""
241
+ h = depth_input
242
+ if self.depth_input_adapter is not None:
243
+ h = self.depth_input_adapter(h)
244
+ h, nk, _ = self.depth_body.step_fn(h, depth_kv, [], None)
245
+ h = self.final_ln(h)
246
+ return self._soft_cap(self.to_logits(h)), nk
247
+
248
+ def init_streaming_f(self, batch, device, dtype=torch.float32):
249
+ td = self.cfg.temporal
250
+ z = torch.zeros(batch, 0, td.num_heads, td.dim_per_head, device=device, dtype=dtype)
251
+ kv = [(z, z) for _ in range(td.num_layers)]
252
+ return {
253
+ "self": [(k, v) for k, v in kv],
254
+ "cross": [(k, v) for k, v in kv],
255
+ "prev": torch.zeros((batch, 1, self.cfg.num_codebooks), dtype=torch.long, device=device),
256
+ }
257
+
258
+ def step_f(self, state, source_frame, sampler=None, forced=None,
259
+ temporal_step=None, depth_step=None):
260
+ """One functional frame. temporal_step/depth_step override the eager fns
261
+ (e.g. with AOTI-compiled callables). Updates state in place; returns [b,1,Q]."""
262
+ cfg = self.cfg
263
+ tstep = temporal_step or self.temporal_step_fn
264
+ dstep = depth_step or self.depth_step_fn
265
+ to, new_self, new_cross = tstep(state["prev"], state["self"], state["cross"], source_frame)
266
+ keep = cfg.temporal_max_past + 1
267
+ state["self"] = [(k[:, -keep:], v[:, -keep:]) for k, v in new_self]
268
+ state["cross"] = [(k[:, -keep:], v[:, -keep:]) for k, v in new_cross]
269
+ dd = cfg.depth
270
+ z = torch.zeros(to.shape[0], 0, dd.num_heads, dd.dim_per_head, device=to.device, dtype=to.dtype)
271
+ depth_kv = [(z, z) for _ in range(dd.num_layers)]
272
+ depth_input = to
273
+ samples = []
274
+ for q in range(cfg.num_codebooks):
275
+ logits, depth_kv = dstep(depth_input, depth_kv)
276
+ lo = cfg.num_reserved_tokens + q * cfg.codebook_size
277
+ hi = lo + cfg.codebook_size
278
+ tok = forced[..., q] if forced is not None else sampler(logits.float(), q, lo, hi)
279
+ samples.append(tok)
280
+ depth_input = self.embed(tok)
281
+ frame = torch.stack(samples, dim=-1)
282
+ state["prev"] = frame
283
+ return frame
284
+
285
+ # ---- streaming generation -------------------------------------------
286
+ def init_streaming(self, batch, device, dtype=torch.float32):
287
+ return {
288
+ "temporal": self.temporal_body.init_state(batch, device, dtype, source=True),
289
+ "prev": torch.zeros((batch, 1, self.cfg.num_codebooks), dtype=torch.long, device=device),
290
+ }
291
+
292
+ def _soft_cap(self, logits):
293
+ c = self.cfg.soft_cap_logits
294
+ return torch.tanh(logits / c) * c if c is not None else logits
295
+
296
+ def _depth_step_logits(self, depth_inputs, depth_state):
297
+ h = depth_inputs
298
+ if self.depth_input_adapter is not None:
299
+ h = self.depth_input_adapter(h)
300
+ h = self.depth_body.step(h, depth_state)
301
+ h = self.final_ln(h)
302
+ return self.to_logits(h)
303
+
304
+ def step(self, state, source_step, sampler=None, forced_frame=None):
305
+ """One streaming frame.
306
+
307
+ state: from init_streaming (updated in place).
308
+ source_step: [b,1,enc_dim] encoded conditioning for this frame.
309
+ sampler: fn(logits[b,1,vocab], rvq_index, valid_lo, valid_hi) -> token[b,1] long.
310
+ forced_frame: [b,1,Q] tokens to force (teacher forcing) instead of sampling.
311
+ Returns sampled frame [b,1,Q] long.
312
+ """
313
+ cfg = self.cfg
314
+ prev = state["prev"]
315
+ embedded = self.embed(prev) # [b,1,Q,D]
316
+ temporal_inputs = _mean_f32(embedded, axis=-2) # [b,1,D]
317
+ temporal_out = self.temporal_body.step(temporal_inputs, state["temporal"], source_step)
318
+ depth_state = self.depth_body.init_state(prev.shape[0], prev.device, temporal_out.dtype)
319
+ depth_inputs = temporal_out
320
+ samples = []
321
+ for q in range(cfg.num_codebooks):
322
+ logits = self._soft_cap(self._depth_step_logits(depth_inputs, depth_state)).float()
323
+ lo = cfg.num_reserved_tokens + q * cfg.codebook_size
324
+ hi = lo + cfg.codebook_size
325
+ if forced_frame is not None:
326
+ tok = forced_frame[..., q] # [b,1]
327
+ else:
328
+ tok = sampler(logits, q, lo, hi) # [b,1]
329
+ samples.append(tok)
330
+ depth_inputs = self.embed(tok.unsqueeze(-1)).squeeze(-2) if tok.dim() == 2 else self.embed(tok)
331
+ frame = torch.stack(samples, dim=-1) # [b,1,Q]
332
+ state["prev"] = frame
333
+ return frame, logits # last logits returned for debugging
334
+
335
+ def streaming_logits(self, target, source):
336
+ """Re-derive per-(t,q) logits via the streaming step path with forced tokens.
337
+ Used to validate the KV-cache step path against teacher forcing."""
338
+ b, T, Q = target.shape
339
+ state = self.init_streaming(b, target.device)
340
+ all_logits = []
341
+ for t in range(T):
342
+ frame_logits = []
343
+ prev = state["prev"]
344
+ embedded = self.embed(prev)
345
+ temporal_inputs = _mean_f32(embedded, axis=-2)
346
+ temporal_out = self.temporal_body.step(temporal_inputs, state["temporal"], source[:, t:t+1])
347
+ depth_state = self.depth_body.init_state(b, target.device, temporal_out.dtype)
348
+ depth_inputs = temporal_out
349
+ for q in range(Q):
350
+ logits = self._soft_cap(self._depth_step_logits(depth_inputs, depth_state))
351
+ frame_logits.append(logits)
352
+ tok = target[:, t:t+1, q]
353
+ depth_inputs = self.embed(tok)
354
+ state["prev"] = target[:, t:t+1, :]
355
+ all_logits.append(torch.stack(frame_logits, dim=2)) # [b,1,Q,vocab]
356
+ return torch.cat(all_logits, dim=1)
357
+
358
+
359
+ class Depthformer(nn.Module):
360
+ def __init__(self, cfg: DepthformerConfig):
361
+ super().__init__()
362
+ self.cfg = cfg
363
+ self.encoder = EncoderEmbedding(cfg)
364
+ self.decoder = MultivariateDecoder(cfg)
365
+
366
+ def encode(self, cond):
367
+ return self.encoder(cond)
368
+
369
+ def forward(self, cond, target):
370
+ source = self.encode(cond)
371
+ return self.decoder(target, source)
372
+
373
+
374
+ # --- config builders ---------------------------------------------------------
375
+
376
+ _REGULAR_PER_CHANNEL = [11] * 128 + [9] * 1 + [47] * 2 + [15] * 1 # 132 channels
377
+
378
+ _COMMON = dict(
379
+ musiccoca_rvq=12, musiccoca_per_rvq_vocab=1031, musiccoca_embed_dim=768,
380
+ regular_num_embeddings_per_channel=_REGULAR_PER_CHANNEL, regular_num_channels=132,
381
+ depth_max_past=12, num_sinks=1, num_codebooks=12, codebook_size=1024,
382
+ num_reserved_tokens=6, vocab_size=12294, soft_cap_logits=30.0,
383
+ )
384
+
385
+
386
+ def config_for(size: str) -> DepthformerConfig:
387
+ if size == "mrt2_small":
388
+ return DepthformerConfig(
389
+ encoder_model_dims=256,
390
+ temporal=SpecDims(12, 1024, 4096, 8, 128),
391
+ depth=SpecDims(2, 768, 3072, 6, 128),
392
+ temporal_max_past=41,
393
+ **_COMMON,
394
+ )
395
+ if size == "mrt2_base":
396
+ return DepthformerConfig(
397
+ encoder_model_dims=1024,
398
+ temporal=SpecDims(20, 3072, 8192, 24, 128),
399
+ depth=SpecDims(6, 1024, 4096, 8, 128),
400
+ temporal_max_past=25,
401
+ **_COMMON,
402
+ )
403
+ raise ValueError(f"unknown size {size}")
magenta_rt/torch/layers.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """PyTorch port of the sequence_layers primitives used by Magenta RealTime 2.
16
+
17
+ Layouts mirror the JAX/Linen checkpoint exactly so weight loading is a direct
18
+ copy (no transposes for kernels stored as [in, ...]). Reductions for norms and
19
+ softmax run in fp32 to match the reference.
20
+ """
21
+ import math
22
+ from typing import Optional
23
+
24
+ import torch
25
+ import torch.nn as nn
26
+ import torch.nn.functional as F
27
+
28
+ _R_SOFTPLUS_0 = 1.442695041 # 1 / softplus(0); from sequence_layers attention.
29
+
30
+
31
+ def gelu_approx(x: torch.Tensor) -> torch.Tensor:
32
+ """tanh-approximation GELU, matching mlx.nn.gelu_approx / jax gelu(approximate=True)."""
33
+ return F.gelu(x, approximate="tanh")
34
+
35
+
36
+ class JaxLinear(nn.Module):
37
+ """Linen Dense: y = x @ kernel + bias, kernel stored as [in, out]."""
38
+
39
+ def __init__(self, in_features, out_features, use_bias=True, activation=None):
40
+ super().__init__()
41
+ self.kernel = nn.Parameter(torch.zeros(in_features, out_features))
42
+ self.bias = nn.Parameter(torch.zeros(out_features)) if use_bias else None
43
+ self.activation = activation
44
+
45
+ def forward(self, x):
46
+ y = torch.matmul(x, self.kernel.to(x.dtype))
47
+ if self.bias is not None:
48
+ y = y + self.bias.to(x.dtype)
49
+ if self.activation is not None:
50
+ y = self.activation(y)
51
+ return y
52
+
53
+
54
+ class RMSNorm(nn.Module):
55
+ """RMS norm with learned scale; reduction in fp32 (eps 1e-6)."""
56
+
57
+ def __init__(self, dim, eps=1e-6, use_scale=True):
58
+ super().__init__()
59
+ self.eps = eps
60
+ self.scale = nn.Parameter(torch.ones(dim)) if use_scale else None
61
+
62
+ def forward(self, x):
63
+ dt = x.dtype
64
+ v = x.float()
65
+ v = v * torch.rsqrt(v.pow(2).mean(-1, keepdim=True) + self.eps)
66
+ v = v.to(dt)
67
+ if self.scale is not None:
68
+ v = v * self.scale.to(dt)
69
+ return v
70
+
71
+
72
+ class LayerNorm(nn.Module):
73
+ """LayerNorm with scale+bias; reduction in fp32 (eps 1e-6)."""
74
+
75
+ def __init__(self, dim, eps=1e-6):
76
+ super().__init__()
77
+ self.eps = eps
78
+ self.scale = nn.Parameter(torch.ones(dim))
79
+ self.bias = nn.Parameter(torch.zeros(dim))
80
+
81
+ def forward(self, x):
82
+ dt = x.dtype
83
+ v = x.float()
84
+ mean = v.mean(-1, keepdim=True)
85
+ var = (v - mean).pow(2).mean(-1, keepdim=True)
86
+ v = (v - mean) * torch.rsqrt(var + self.eps)
87
+ v = v.to(dt)
88
+ return v * self.scale.to(dt) + self.bias.to(dt)
89
+
90
+
91
+ def _query_scale_vector(per_dim_scale, units_per_head, dtype):
92
+ qscale = 1.0 / math.sqrt(units_per_head)
93
+ if per_dim_scale is not None:
94
+ scale = _R_SOFTPLUS_0 * qscale
95
+ softplus = F.softplus(per_dim_scale.to(dtype))
96
+ return scale * softplus
97
+ return torch.tensor(qscale, dtype=dtype, device=per_dim_scale.device if per_dim_scale is not None else None)
98
+
99
+
100
+ def dot_product_attention(q, k, v, per_dim_scale, sink_k, sink_v, mask):
101
+ """q,k,v: [b, t, nh, uph]; mask: [b, 1, tq, tkv] bool (True=attend) or None.
102
+
103
+ sink_k/sink_v: [num_sink, nh, uph] or None. Sink logits use *unscaled* queries.
104
+ Returns context [b, tq, nh, uph].
105
+ """
106
+ qh = q.transpose(1, 2) # [b, nh, tq, uph]
107
+ kh = k.transpose(1, 2)
108
+ vh = v.transpose(1, 2)
109
+
110
+ scale_vec = _query_scale_vector(per_dim_scale, q.shape[-1], qh.dtype) # [uph]
111
+
112
+ if sink_k is not None:
113
+ # [b, nh, tq, num_sink] using unscaled queries.
114
+ sink_logits = torch.einsum("bhqd,shd->bhqs", qh, sink_k.to(qh.dtype))
115
+
116
+ qs = qh * scale_vec
117
+ logits = torch.matmul(qs, kh.transpose(-1, -2)) # [b, nh, tq, tkv]
118
+
119
+ if sink_k is not None:
120
+ logits = torch.cat([sink_logits, logits], dim=-1)
121
+
122
+ if mask is not None:
123
+ # Export-clean: scalar masked_fill (no -1e9 constant tensor). Sink
124
+ # columns (first ns) are always valid, so mask only the kv columns
125
+ # (avoids building a constant `ones` sink mask).
126
+ ns = sink_k.shape[0] if sink_k is not None else 0
127
+ if ns:
128
+ kv = logits[..., ns:].masked_fill(~mask, -1e9)
129
+ logits = torch.cat([logits[..., :ns], kv], dim=-1)
130
+ else:
131
+ logits = logits.masked_fill(~mask, -1e9)
132
+
133
+ weights = torch.softmax(logits.float(), dim=-1).to(vh.dtype)
134
+
135
+ if sink_v is not None:
136
+ b = vh.shape[0]
137
+ sink_vb = sink_v.to(vh.dtype).permute(1, 0, 2).unsqueeze(0).expand(b, -1, -1, -1) # [b,nh,num_sink,uph]
138
+ vh = torch.cat([sink_vb, vh], dim=2)
139
+
140
+ ctx = torch.matmul(weights, vh) # [b, nh, tq, uph]
141
+ return ctx.transpose(1, 2) # [b, tq, nh, uph]
142
+
143
+
144
+ class AttnProjection(nn.Module):
145
+ """q/k/v/out projections stored as [in, nh, uph] (Linen attention kernels)."""
146
+
147
+ def __init__(self, in_dim, num_heads, units_per_head, has_sinks=False, has_per_dim_scale=True):
148
+ super().__init__()
149
+ nh, uph = num_heads, units_per_head
150
+ self.num_heads, self.units_per_head = nh, uph
151
+ self.query_projection_kernel = nn.Parameter(torch.zeros(in_dim, nh, uph))
152
+ self.key_projection_kernel = nn.Parameter(torch.zeros(in_dim, nh, uph))
153
+ self.value_projection_kernel = nn.Parameter(torch.zeros(in_dim, nh, uph))
154
+ self.per_dim_scale = nn.Parameter(torch.zeros(uph)) if has_per_dim_scale else None
155
+ if has_sinks:
156
+ self.sink_key_embeddings = nn.Parameter(torch.zeros(1, nh, uph))
157
+ self.sink_value_embeddings = nn.Parameter(torch.zeros(1, nh, uph))
158
+ else:
159
+ self.sink_key_embeddings = None
160
+ self.sink_value_embeddings = None
161
+
162
+ def project(self, x, kernel):
163
+ return torch.einsum("btd,dnh->btnh", x, kernel.to(x.dtype))
164
+
165
+
166
+ def banded_causal_mask(tq, tkv, past, future, device):
167
+ """[1,1,tq,tkv] bool. Query i (global pos offset+i) attends key j with
168
+ j in [i-past, i+future]. Here tq aligns to the last tq positions of tkv."""
169
+ offset = tkv - tq
170
+ row = torch.arange(tq, device=device)[:, None] + offset
171
+ col = torch.arange(tkv, device=device)[None, :]
172
+ m = (col <= row + future) & (col >= row - past)
173
+ return m.view(1, 1, tq, tkv)
174
+
175
+
176
+ class SelfAttention(nn.Module):
177
+ def __init__(self, model_dim, num_heads, units_per_head, max_past_horizon,
178
+ num_sinks=0, eps=1e-6):
179
+ super().__init__()
180
+ self.pre_norm = RMSNorm(model_dim, eps)
181
+ self.post_norm = RMSNorm(model_dim, eps)
182
+ self.attention = AttnProjection(model_dim, num_heads, units_per_head,
183
+ has_sinks=num_sinks > 0)
184
+ self.output_projection_kernel = nn.Parameter(torch.zeros(model_dim, num_heads, units_per_head))
185
+ self.max_past_horizon = max_past_horizon
186
+ self.num_heads = num_heads
187
+ self.units_per_head = units_per_head
188
+
189
+ def _branch(self, x):
190
+ h = self.pre_norm(x)
191
+ a = self.attention
192
+ q = a.project(h, a.query_projection_kernel)
193
+ k = a.project(h, a.key_projection_kernel)
194
+ v = a.project(h, a.value_projection_kernel)
195
+ t = x.shape[1]
196
+ mask = banded_causal_mask(t, t, self.max_past_horizon, 0, x.device)
197
+ ctx = dot_product_attention(q, k, v, a.per_dim_scale,
198
+ a.sink_key_embeddings, a.sink_value_embeddings, mask)
199
+ out = torch.einsum("btnh,dnh->btd", ctx, self.output_projection_kernel.to(ctx.dtype))
200
+ return self.post_norm(out)
201
+
202
+ def forward(self, x):
203
+ return x + self._branch(x)
204
+
205
+ # ---- streaming step with KV cache ----
206
+ def init_state(self, batch, device, dtype):
207
+ return {"k": None, "v": None} # lazily grown buffers [b, t, nh, uph]
208
+
209
+ def step(self, x, state):
210
+ # x: [b, 1, d]
211
+ h = self.pre_norm(x)
212
+ a = self.attention
213
+ q = a.project(h, a.query_projection_kernel)
214
+ k = a.project(h, a.key_projection_kernel)
215
+ v = a.project(h, a.value_projection_kernel)
216
+ if state["k"] is None:
217
+ kk, vv = k, v
218
+ else:
219
+ kk = torch.cat([state["k"], k], dim=1)
220
+ vv = torch.cat([state["v"], v], dim=1)
221
+ # keep only the window we can attend to (past horizon + current).
222
+ keep = self.max_past_horizon + 1
223
+ if kk.shape[1] > keep:
224
+ kk = kk[:, -keep:]
225
+ vv = vv[:, -keep:]
226
+ state["k"], state["v"] = kk, vv
227
+ # The cache holds only the last `keep` keys, all valid past keys within
228
+ # the window for the single newest query -> no mask needed.
229
+ ctx = dot_product_attention(q, kk, vv, a.per_dim_scale,
230
+ a.sink_key_embeddings, a.sink_value_embeddings, None)
231
+ out = torch.einsum("btnh,dnh->btd", ctx, self.output_projection_kernel.to(ctx.dtype))
232
+ return x + self.post_norm(out)
233
+
234
+ def step_fn(self, x, k_prev, v_prev):
235
+ """Functional, export-clean single-frame step. k_prev/v_prev: [b,T,nh,uph]
236
+ (T may be 0). Returns (out[b,1,d], new_k, new_v) — full, untrimmed KV."""
237
+ h = self.pre_norm(x)
238
+ a = self.attention
239
+ q = a.project(h, a.query_projection_kernel)
240
+ k = torch.cat([k_prev, a.project(h, a.key_projection_kernel)], dim=1)
241
+ v = torch.cat([v_prev, a.project(h, a.value_projection_kernel)], dim=1)
242
+ ctx = dot_product_attention(q, k, v, a.per_dim_scale,
243
+ a.sink_key_embeddings, a.sink_value_embeddings, None)
244
+ out = torch.einsum("btnh,dnh->btd", ctx, self.output_projection_kernel.to(ctx.dtype))
245
+ return x + self.post_norm(out), k, v
246
+
247
+
248
+ class CrossAttention(nn.Module):
249
+ """Streaming cross-attention to an encoded source sequence."""
250
+
251
+ def __init__(self, model_dim, source_dim, num_heads, units_per_head,
252
+ max_past_horizon, num_sinks=0, eps=1e-6):
253
+ super().__init__()
254
+ self.pre_norm = RMSNorm(model_dim, eps)
255
+ self.post_norm = RMSNorm(model_dim, eps)
256
+ # query from decoder (model_dim), key/value from source (source_dim).
257
+ self.attention = _CrossProj(model_dim, source_dim, num_heads, units_per_head,
258
+ has_sinks=num_sinks > 0)
259
+ self.output_projection_kernel = nn.Parameter(torch.zeros(model_dim, num_heads, units_per_head))
260
+ self.max_past_horizon = max_past_horizon
261
+ self.num_heads = num_heads
262
+ self.units_per_head = units_per_head
263
+
264
+ def _kv(self, source):
265
+ a = self.attention
266
+ k = torch.einsum("btd,dnh->btnh", source, a.key_projection_kernel.to(source.dtype))
267
+ v = torch.einsum("btd,dnh->btnh", source, a.value_projection_kernel.to(source.dtype))
268
+ return k, v
269
+
270
+ def _branch(self, x, source):
271
+ h = self.pre_norm(x)
272
+ a = self.attention
273
+ q = torch.einsum("btd,dnh->btnh", h, a.query_projection_kernel.to(h.dtype))
274
+ k, v = self._kv(source)
275
+ tq, tkv = x.shape[1], source.shape[1]
276
+ # query at decoder time i attends source positions within past horizon, causal.
277
+ mask = banded_causal_mask(tq, tkv, self.max_past_horizon, 0, x.device)
278
+ ctx = dot_product_attention(q, k, v, a.per_dim_scale,
279
+ a.sink_key_embeddings, a.sink_value_embeddings, mask)
280
+ out = torch.einsum("btnh,dnh->btd", ctx, self.output_projection_kernel.to(ctx.dtype))
281
+ return self.post_norm(out)
282
+
283
+ def forward(self, x, source):
284
+ return x + self._branch(x, source)
285
+
286
+ def attend_fn(self, x, k, v):
287
+ """Functional cross-attention given precomputed source KV [b,T,nh,uph]."""
288
+ h = self.pre_norm(x)
289
+ a = self.attention
290
+ q = torch.einsum("btd,dnh->btnh", h, a.query_projection_kernel.to(h.dtype))
291
+ ctx = dot_product_attention(q, k, v, a.per_dim_scale,
292
+ a.sink_key_embeddings, a.sink_value_embeddings, None)
293
+ out = torch.einsum("btnh,dnh->btd", ctx, self.output_projection_kernel.to(ctx.dtype))
294
+ return x + self.post_norm(out)
295
+
296
+ def step(self, x, source_kv):
297
+ # x: [b,1,d]; source_kv: (k,v) accumulated [b, tkv, nh, uph]
298
+ h = self.pre_norm(x)
299
+ a = self.attention
300
+ q = torch.einsum("btd,dnh->btnh", h, a.query_projection_kernel.to(h.dtype))
301
+ k, v = source_kv
302
+ tkv = k.shape[1]
303
+ keep = self.max_past_horizon + 1
304
+ if tkv > keep:
305
+ k = k[:, -keep:]
306
+ v = v[:, -keep:]
307
+ ctx = dot_product_attention(q, k, v, a.per_dim_scale,
308
+ a.sink_key_embeddings, a.sink_value_embeddings, None)
309
+ out = torch.einsum("btnh,dnh->btd", ctx, self.output_projection_kernel.to(ctx.dtype))
310
+ return x + self.post_norm(out)
311
+
312
+
313
+ class _CrossProj(nn.Module):
314
+ def __init__(self, q_dim, kv_dim, num_heads, units_per_head, has_sinks=False):
315
+ super().__init__()
316
+ nh, uph = num_heads, units_per_head
317
+ self.query_projection_kernel = nn.Parameter(torch.zeros(q_dim, nh, uph))
318
+ self.key_projection_kernel = nn.Parameter(torch.zeros(kv_dim, nh, uph))
319
+ self.value_projection_kernel = nn.Parameter(torch.zeros(kv_dim, nh, uph))
320
+ self.per_dim_scale = nn.Parameter(torch.zeros(uph))
321
+ if has_sinks:
322
+ self.sink_key_embeddings = nn.Parameter(torch.zeros(1, nh, uph))
323
+ self.sink_value_embeddings = nn.Parameter(torch.zeros(1, nh, uph))
324
+ else:
325
+ self.sink_key_embeddings = None
326
+ self.sink_value_embeddings = None
327
+
328
+
329
+ class FFN(nn.Module):
330
+ def __init__(self, model_dim, hidden_dim, eps=1e-6):
331
+ super().__init__()
332
+ self.pre_norm = RMSNorm(model_dim, eps)
333
+ self.post_norm = RMSNorm(model_dim, eps)
334
+ self.ffn_layer1 = JaxLinear(model_dim, hidden_dim, use_bias=True, activation=gelu_approx)
335
+ self.ffn_layer2 = JaxLinear(hidden_dim, model_dim, use_bias=True)
336
+
337
+ def _branch(self, x):
338
+ h = self.pre_norm(x)
339
+ h = self.ffn_layer1(h)
340
+ h = self.ffn_layer2(h)
341
+ return self.post_norm(h)
342
+
343
+ def forward(self, x):
344
+ return x + self._branch(x)
magenta_rt/torch/modeling_magenta_rt2.py ADDED
@@ -0,0 +1,356 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """Transformers-compatible Magenta RealTime 2 (PyTorch).
16
+
17
+ `MagentaRT2ForConditionalGeneration` is a `PreTrainedModel` wrapping the
18
+ Depthformer LLM + SpectroStream codec decoder. Generation is per-frame RVQ
19
+ autoregression with a depth transformer + streaming codec decode, exposed as
20
+ custom `generate` / `stream` methods (it does not fit `GenerationMixin`, whose
21
+ loop is a single token stream). MusicCoCa style encoding is a separate
22
+ `MusicCoCaProcessor`.
23
+ """
24
+
25
+ import json
26
+ import os
27
+
28
+ import numpy as np
29
+ import torch
30
+ from transformers import PreTrainedModel
31
+
32
+ from .configuration_magenta_rt2 import MagentaRT2Config
33
+ from .depthformer import Depthformer, DepthformerConfig, SpecDims
34
+ from .spectrostream import SpectroStreamDecoder, codes_to_embeddings
35
+
36
+ # Force `trust_remote_code` to bundle every dependency module. transformers only
37
+ # traces `from .X import ...` (not `from . import X`), so we name them explicitly.
38
+ from .layers import JaxLinear as _ensure_layers # noqa: F401
39
+ from .musiccoca import MusicCoCa as _ensure_musiccoca # noqa: F401
40
+ from .processing_musiccoca import MusicCoCaProcessor as _ensure_processor # noqa: F401
41
+ from .aoti import load_compiled_steps as _ensure_aoti # noqa: F401
42
+
43
+ SR = 48000
44
+ FRAME_SAMPLES = 1920
45
+ STREAM_DECODE_CONTEXT = 16
46
+ STREAM_DECODE_MARGIN = 2
47
+
48
+
49
+ def discretize_cfg(value, step, max_bin):
50
+ clamped = max(-1.0, min(7.0, value))
51
+ return max(0, min(max_bin, int(round((clamped - (-1.0)) / step))))
52
+
53
+
54
+ def convert_from_unique_codes(tokens, codebook_size, num_reserved=6):
55
+ return (tokens - num_reserved) % codebook_size
56
+
57
+
58
+ def _float_to_int16(samples, gain=0.5):
59
+ samples = np.clip(gain * samples, -1, 1)
60
+ samples = np.round((np.iinfo(np.int16).max + 0.5) * samples - 0.5)
61
+ return samples.astype(np.int16)
62
+
63
+
64
+ def make_sampler(temperature, top_k, generator):
65
+ """jax-parity: valid-range mask, top-k, gumbel-max. NEG is finite (avoids NaN)."""
66
+ NEG = -1e9
67
+
68
+ def sampler(logits, rvq_index, lo, hi):
69
+ logits = logits.float()
70
+ v = logits.shape[-1]
71
+ idx = torch.arange(v, device=logits.device)
72
+ valid = (idx >= lo) & (idx < hi)
73
+ logits = torch.where(valid, logits, torch.full_like(logits, NEG))
74
+ if top_k is not None and int(top_k) < v:
75
+ k = min(max(int(top_k), 1), v)
76
+ kth = torch.topk(logits, k, dim=-1).values[..., -1:]
77
+ logits = torch.where(logits >= kth, logits, torch.full_like(logits, NEG))
78
+ if temperature and float(temperature) > 0:
79
+ u = torch.rand(logits.shape, generator=generator, device=logits.device)
80
+ gumbel = -torch.log(-torch.log(u.clamp(1e-10, 1 - 1e-7)))
81
+ logits = logits + gumbel * float(temperature)
82
+ return logits.argmax(dim=-1)
83
+
84
+ return sampler
85
+
86
+
87
+ def _depthformer_config(c: MagentaRT2Config) -> DepthformerConfig:
88
+ return DepthformerConfig(
89
+ encoder_model_dims=c.encoder_model_dims,
90
+ musiccoca_rvq=c.musiccoca_rvq,
91
+ musiccoca_per_rvq_vocab=c.musiccoca_per_rvq_vocab,
92
+ musiccoca_embed_dim=c.musiccoca_embed_dim,
93
+ regular_num_embeddings_per_channel=list(c.regular_num_embeddings_per_channel),
94
+ regular_num_channels=c.regular_num_channels,
95
+ temporal=SpecDims(*c.temporal),
96
+ depth=SpecDims(*c.depth),
97
+ temporal_max_past=c.temporal_max_past,
98
+ depth_max_past=c.depth_max_past,
99
+ num_sinks=c.num_sinks,
100
+ num_codebooks=c.num_codebooks,
101
+ codebook_size=c.codebook_size,
102
+ num_reserved_tokens=c.num_reserved_tokens,
103
+ vocab_size=c.vocab_size,
104
+ soft_cap_logits=c.soft_cap_logits,
105
+ )
106
+
107
+
108
+ def _empty_codec(shapes):
109
+ return SpectroStreamDecoder({k: torch.zeros(v) for k, v in shapes.items()})
110
+
111
+
112
+ class MagentaRT2PreTrainedModel(PreTrainedModel):
113
+ config_class = MagentaRT2Config
114
+ base_model_prefix = "magenta_rt2"
115
+ _no_split_modules = ["TransformerStack", "SpectroStreamDecoder"]
116
+ main_input_name = "style_tokens"
117
+
118
+ def _init_weights(self, module):
119
+ pass # weights come from the checkpoint; no random init needed
120
+
121
+
122
+ class MagentaRT2ForConditionalGeneration(MagentaRT2PreTrainedModel):
123
+ """Depthformer LLM + SpectroStream codec. Custom streaming generation."""
124
+
125
+ def __init__(self, config):
126
+ super().__init__(config)
127
+ self.depthformer = Depthformer(_depthformer_config(config))
128
+ shapes = getattr(config, "codec_param_shapes", None)
129
+ if not shapes: # dev/package fallback: file next to the module
130
+ with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "codec_shapes.json")) as f:
131
+ shapes = json.load(f)
132
+ self.codec = _empty_codec(shapes)
133
+ self.register_buffer("quant", torch.zeros(64, config.codebook_size, 256))
134
+ self.num_musiccoca = config.musiccoca_rvq
135
+ self.num_notes = config.num_notes
136
+ self.num_drums = config.num_drums
137
+ self.codebook_size = config.codebook_size
138
+ self.num_reserved_tokens = config.num_reserved_tokens
139
+ self.sample_rate = config.sample_rate
140
+ self._temporal_step = None
141
+ self._depth_step = None
142
+ self.processor = None
143
+ self.post_init()
144
+
145
+ # ---- helpers ----
146
+ @property
147
+ def _dt(self):
148
+ return next(self.depthformer.parameters()).dtype
149
+
150
+ @property
151
+ def _dev(self):
152
+ return next(self.depthformer.parameters()).device
153
+
154
+ def set_processor(self, processor):
155
+ """Attach a MusicCoCaProcessor so `generate(style="text"|audio)` works."""
156
+ self.processor = processor
157
+ return self
158
+
159
+ def load_processor(self, repo_id="magenta-torch/magenta-rt-musiccoca-torch", device=None):
160
+ """Load + attach the MusicCoCa style processor (text/audio -> RVQ tokens)."""
161
+ from .processing_musiccoca import MusicCoCaProcessor
162
+ self.processor = MusicCoCaProcessor.from_pretrained(repo_id, device=device or str(self._dev))
163
+ return self
164
+
165
+ # ---- speedups ----
166
+ def compile_steps(self, dynamic=True, **kwargs):
167
+ """`torch.compile` the hot per-frame step paths (dynamic shapes for the
168
+ growing KV cache; one-time warmup). Portable — works on any CUDA GPU,
169
+ unlike the prebuilt AOTI artifacts, which are GPU-arch-specific."""
170
+ dec = self.depthformer.decoder
171
+ dec.temporal_body.step = torch.compile(dec.temporal_body.step, dynamic=dynamic, **kwargs)
172
+ dec._depth_step_logits = torch.compile(dec._depth_step_logits, dynamic=dynamic, **kwargs)
173
+ return self
174
+
175
+ # ---- AOTI: export your own ahead-of-time graphs (skip runtime compile) ----
176
+ def export_aoti(self, out_dir):
177
+ """AOTInductor-compile the per-frame step to `out_dir` (temporal.pt2 +
178
+ depth.pt2). Run once on your target GPU; the graphs are architecture-
179
+ specific. Reload with `load_aoti(out_dir)` to generate with no compile-time."""
180
+ import os
181
+ from . import aoti
182
+ os.makedirs(out_dir, exist_ok=True)
183
+ dec = self.depthformer.decoder
184
+ torch._inductor.aoti_compile_and_package(
185
+ aoti.export_temporal(dec), package_path=os.path.join(out_dir, "temporal.pt2"))
186
+ torch._inductor.aoti_compile_and_package(
187
+ aoti.export_depth(dec), package_path=os.path.join(out_dir, "depth.pt2"))
188
+ return out_dir
189
+
190
+ def load_aoti(self, out_dir):
191
+ """Load AOTI step graphs produced by `export_aoti` and use them for generation."""
192
+ import os
193
+ t = torch._inductor.aoti_load_package(os.path.join(out_dir, "temporal.pt2"))
194
+ d = torch._inductor.aoti_load_package(os.path.join(out_dir, "depth.pt2"))
195
+ return self.apply_compiled(t, d)
196
+
197
+ def apply_compiled(self, temporal_step=None, depth_step=None):
198
+ if temporal_step is not None:
199
+ self._temporal_step = temporal_step
200
+ if depth_step is not None:
201
+ self._depth_step = depth_step
202
+ return self
203
+
204
+ def load_compiled(self, repo_id=None, local_dir=None):
205
+ from . import aoti
206
+ t, d = aoti.load_compiled_steps(self.depthformer.decoder, repo_id=repo_id, local_dir=local_dir)
207
+ return self.apply_compiled(t, d)
208
+
209
+ # ---- conditioning ----
210
+ def _tokenize_style(self, style):
211
+ if self.processor is None:
212
+ raise ValueError("No MusicCoCaProcessor attached; pass `style` as a list of "
213
+ f"{self.num_musiccoca} RVQ token ids, or call set_processor().")
214
+ return np.asarray(self.processor.tokenize(self.processor.embed(style))).tolist()
215
+
216
+ def _conditioning(self, style_tokens, notes, drums, cfgs):
217
+ offset = self.num_reserved_tokens + 1
218
+ vals = list(style_tokens) + list(notes) + list(drums) + list(cfgs)
219
+ arr = np.array(vals, dtype=np.int64) + offset
220
+ return torch.from_numpy(arr).view(1, 1, -1).to(self._dev)
221
+
222
+ def _resolve_conditioning(self, style, notes, drums, cfg_musiccoca, cfg_notes, cfg_drums):
223
+ c = self.config
224
+ if style is None:
225
+ style_tokens = [-1] * self.num_musiccoca
226
+ elif isinstance(style, (list, np.ndarray)) and np.asarray(style).ndim == 1 \
227
+ and np.asarray(style).dtype.kind in "iu" and len(style) == self.num_musiccoca:
228
+ style_tokens = list(style)
229
+ else:
230
+ style_tokens = self._tokenize_style(style)
231
+ style_tokens = (list(style_tokens) + [-1] * self.num_musiccoca)[:self.num_musiccoca]
232
+ notes = notes if notes is not None else [-1] * self.num_notes
233
+ drums = drums if drums is not None else [-1] * self.num_drums
234
+ cfgs = [
235
+ discretize_cfg(c.cfg_musiccoca if cfg_musiccoca is None else cfg_musiccoca, 0.2, 40),
236
+ discretize_cfg(c.cfg_notes if cfg_notes is None else cfg_notes, 0.2, 40),
237
+ discretize_cfg(c.cfg_drums if cfg_drums is None else cfg_drums, 1.0, 8),
238
+ ]
239
+ return self._conditioning(style_tokens, notes, drums, cfgs)
240
+
241
+ # ---- codec ----
242
+ def _decode_stream(self, history, emitted, context=STREAM_DECODE_CONTEXT,
243
+ margin=STREAM_DECODE_MARGIN, flush=False):
244
+ m = 0 if flush else margin
245
+ Ttot = history.shape[1]
246
+ emittable = (Ttot - 1) - m
247
+ avail = emittable - emitted
248
+ if avail <= 0:
249
+ return history.new_zeros((1, 0, 2), dtype=self._dt), emitted
250
+ w0 = max(0, Ttot - (avail + m + context + 1))
251
+ window = history[:, w0:]
252
+ codes = convert_from_unique_codes(window, self.codebook_size, self.num_reserved_tokens)
253
+ emb = codes_to_embeddings(codes, self.quant)
254
+ wav = self.codec(emb.to(self._dt))
255
+ end = wav.shape[1] - m * FRAME_SAMPLES
256
+ new = wav[:, end - avail * FRAME_SAMPLES: end]
257
+ return new, emitted + avail
258
+
259
+ def init_decode_state(self):
260
+ """Fresh state dict for streaming decode (decode_stream)."""
261
+ return {}
262
+
263
+ def decode_stream(self, new_codes, state):
264
+ """Incremental codec decode of new token frames [b, t_new, Q] -> audio [b, N, 2].
265
+ FLOP-optimal stateful streaming (no overlap-save re-decode); bf16-equivalent to
266
+ _decode_stream/forward, with a 1-frame (40ms) decoder latency. `state` starts as {}."""
267
+ codes = convert_from_unique_codes(new_codes, self.codebook_size, self.num_reserved_tokens)
268
+ emb = codes_to_embeddings(codes, self.quant)
269
+ return self.codec.decode_streaming(emb.to(self._dt), state)
270
+
271
+ # ---- forward: one teacher-forced pass (logits), for parity / training hooks ----
272
+ def forward(self, style_tokens=None, target=None, source=None, **kwargs):
273
+ """If `source` is given, returns per-frame logits for `target` [b,T,Q].
274
+ This is the teacher-forced path; for sampling use `generate`/`stream`."""
275
+ if source is None:
276
+ cond = self._resolve_conditioning(style_tokens, None, None, None, None, None)
277
+ source = self.depthformer.encode(cond).to(self._dt)
278
+ logits = self.depthformer(target, source)
279
+ return {"logits": logits, "source": source}
280
+
281
+ # ---- generation (custom; not GenerationMixin) ----
282
+ @torch.no_grad()
283
+ def generate(self, style=None, notes=None, drums=None, cfg_musiccoca=None,
284
+ cfg_notes=None, cfg_drums=None, temperature=None, top_k=None,
285
+ frames=25, seed=0, state=None, flush=False, return_int16=False):
286
+ c = self.config
287
+ temperature = c.temperature if temperature is None else temperature
288
+ top_k = c.top_k if top_k is None else top_k
289
+ cond = self._resolve_conditioning(style, notes, drums, cfg_musiccoca, cfg_notes, cfg_drums)
290
+ source = self.depthformer.encode(cond).to(self._dt)
291
+ if state is None:
292
+ dstate = self.depthformer.decoder.init_streaming_f(1, self._dev, self._dt)
293
+ gen = torch.Generator(device=self._dev).manual_seed(seed)
294
+ history = torch.zeros((1, 0, c.num_codebooks), dtype=torch.long, device=self._dev)
295
+ emitted = 0
296
+ else:
297
+ dstate, gen, history, emitted = state["dstate"], state["gen"], state["history"], state["emitted"]
298
+ sampler = make_sampler(temperature, top_k, gen)
299
+ toks = [self.depthformer.decoder.step_f(
300
+ dstate, source, sampler=sampler,
301
+ temporal_step=self._temporal_step, depth_step=self._depth_step) for _ in range(frames)]
302
+ history = torch.cat([history] + toks, dim=1)
303
+ audio, emitted = self._decode_stream(history, emitted, flush=flush)
304
+ new_state = {"dstate": dstate, "gen": gen, "history": history, "emitted": emitted}
305
+ wav = audio[0].float().cpu().numpy()
306
+ i16 = _float_to_int16(wav)
307
+ out = i16 if return_int16 else i16.astype(np.float32) / 32768.0
308
+ return out, new_state
309
+
310
+ @torch.no_grad()
311
+ def stream(self, control, chunk_frames=10, max_seconds=55.0, seed=0,
312
+ time_fn=None, sleep_fn=None, notes=None, drums=None):
313
+ """Continuous generation. `control()` returns {style_tokens, temperature,
314
+ top_k, cfg_*} read every chunk for mid-stream steering. Yields int16 [N,2]."""
315
+ import time as _time
316
+ time_fn = time_fn or _time.time
317
+ sleep_fn = sleep_fn or _time.sleep
318
+ c = self.config
319
+ dev, dt = self._dev, self._dt
320
+ notes = notes if notes is not None else [-1] * self.num_notes
321
+ drums = drums if drums is not None else [-1] * self.num_drums
322
+ dstate = self.depthformer.decoder.init_streaming_f(1, dev, dt)
323
+ gen = torch.Generator(device=dev).manual_seed(seed)
324
+ history = torch.zeros((1, 0, c.num_codebooks), dtype=torch.long, device=dev)
325
+ emitted = 0
326
+ cur_tokens = None
327
+ source = None
328
+ t0 = time_fn()
329
+ while time_fn() - t0 < max_seconds:
330
+ ctl = control()
331
+ if ctl is None:
332
+ sleep_fn(0.02)
333
+ continue
334
+ tokens = ctl["style_tokens"]
335
+ if tokens != cur_tokens:
336
+ cur_tokens = tokens
337
+ cfgs = [discretize_cfg(ctl.get("cfg_musiccoca", c.cfg_musiccoca), 0.2, 40),
338
+ discretize_cfg(ctl.get("cfg_notes", c.cfg_notes), 0.2, 40),
339
+ discretize_cfg(ctl.get("cfg_drums", c.cfg_drums), 1.0, 8)]
340
+ cond = self._conditioning((list(tokens) + [-1] * self.num_musiccoca)[:self.num_musiccoca],
341
+ notes, drums, cfgs)
342
+ source = self.depthformer.encode(cond).to(dt)
343
+ sampler = make_sampler(ctl.get("temperature", c.temperature), ctl.get("top_k", c.top_k), gen)
344
+ toks = [self.depthformer.decoder.step_f(
345
+ dstate, source, sampler=sampler,
346
+ temporal_step=self._temporal_step, depth_step=self._depth_step) for _ in range(chunk_frames)]
347
+ history = torch.cat([history] + toks, dim=1)
348
+ audio, emitted = self._decode_stream(history, emitted)
349
+ if audio.shape[1] > 0:
350
+ yield _float_to_int16(audio[0].float().cpu().numpy())
351
+ ahead = (emitted * FRAME_SAMPLES / SR) - (time_fn() - t0)
352
+ if ahead > 1.0:
353
+ sleep_fn(min(ahead - 1.0, 0.5))
354
+
355
+
356
+ __all__ = ["MagentaRT2ForConditionalGeneration", "MagentaRT2PreTrainedModel"]
magenta_rt/torch/musiccoca.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """Pure-PyTorch MusicCoCa style encoder (text path).
16
+
17
+ The upstream MusicCoCa ships only as TFLite. The text tower and RVQ quantizer
18
+ were converted TFLite -> ONNX -> torch and traced to TorchScript (token-exact
19
+ vs the TFLite reference). Runtime deps: torch + sentencepiece only (no
20
+ ai_edge_litert / tflite). The audio-prompt tower is not yet ported.
21
+ """
22
+ import os
23
+
24
+ import numpy as np
25
+ import torch
26
+
27
+ MAX_TEXT_LEN = 128
28
+ TARGET_SOS_ID = 1
29
+ EMBEDDING_DIM = 768
30
+ RVQ_DEPTH = 12
31
+ DEFAULT_REPO = "magenta-torch/magenta-rt-musiccoca-torch"
32
+ AUDIO_SR = 16000
33
+ CLIP_SAMPLES = 160000 # 10s @ 16kHz
34
+ _MEL_FL, _MEL_HOP, _MEL_NFFT, _PREEMPH = 400, 160, 2048, 0.97
35
+
36
+
37
+ class MusicCoCa:
38
+ """Text -> 768-d style embedding -> 12 RVQ style tokens, all in torch."""
39
+
40
+ def __init__(self, repo_id=DEFAULT_REPO, resource_dir=None, device="cpu"):
41
+ import sentencepiece
42
+ if resource_dir is not None:
43
+ te = os.path.join(resource_dir, "text_encoder.pt")
44
+ q = os.path.join(resource_dir, "quantizer.pt")
45
+ spm = os.path.join(resource_dir, "spm.model")
46
+ else:
47
+ from huggingface_hub import hf_hub_download
48
+ te = hf_hub_download(repo_id, "text_encoder.pt")
49
+ q = hf_hub_download(repo_id, "quantizer.pt")
50
+ spm = hf_hub_download(repo_id, "spm.model")
51
+ self.device = device
52
+ self._te = torch.jit.load(te, map_location=device).eval()
53
+ self._q = torch.jit.load(q, map_location=device).eval()
54
+ self._sp = sentencepiece.SentencePieceProcessor()
55
+ self._sp.Load(spm)
56
+ self.embedding_dim = EMBEDDING_DIM
57
+ self.rvq_depth = RVQ_DEPTH
58
+ # Audio tower (mel preprocessor + music_encoder ViT). Lazy.
59
+ self._me = None
60
+ self._mel = None
61
+ self._hann = None
62
+ self._resource_dir = resource_dir
63
+ self._repo_id = repo_id
64
+
65
+ def _ensure_audio(self):
66
+ if self._me is not None:
67
+ return
68
+ if self._resource_dir is not None:
69
+ mep = os.path.join(self._resource_dir, "music_encoder.pt")
70
+ melp = os.path.join(self._resource_dir, "mel_params.npz")
71
+ else:
72
+ from huggingface_hub import hf_hub_download
73
+ mep = hf_hub_download(self._repo_id, "music_encoder.pt")
74
+ melp = hf_hub_download(self._repo_id, "mel_params.npz")
75
+ self._me = torch.jit.load(mep, map_location=self.device).eval()
76
+ d = np.load(melp)
77
+ self._mel = torch.from_numpy(d["mel"]).float().to(self.device)
78
+ self._hann = torch.from_numpy(d["hann"]).float().to(self.device)
79
+
80
+ def to(self, device):
81
+ self.device = device
82
+ self._te = self._te.to(device)
83
+ self._q = self._q.to(device)
84
+ if self._me is not None:
85
+ self._me = self._me.to(device)
86
+ self._mel = self._mel.to(device)
87
+ self._hann = self._hann.to(device)
88
+ return self
89
+
90
+ def _log_mel(self, wav):
91
+ """wav [S] (16kHz mono float) -> log-mel [992,128] (bit-exact vs TFLite)."""
92
+ x = wav.to(self.device).float()
93
+ y = x.clone()
94
+ y[1:] = x[1:] - _PREEMPH * x[:-1]
95
+ xp = torch.nn.functional.pad(y, (0, _MEL_FL))
96
+ nf = (xp.shape[0] - _MEL_FL) // _MEL_HOP + 1
97
+ idx = (torch.arange(_MEL_FL, device=self.device)[None, :]
98
+ + torch.arange(nf, device=self.device)[:, None] * _MEL_HOP)
99
+ fr = xp[idx] * self._hann
100
+ power = torch.fft.rfft(fr, n=_MEL_NFFT, dim=1).abs() ** 2
101
+ return torch.log(power[:, 1:1025] @ self._mel + 0.001)[:992]
102
+
103
+ @torch.no_grad()
104
+ def embed_audio(self, samples, sample_rate):
105
+ """samples [S] or [S,C] float; -> [768] style embedding (clips mean-pooled)."""
106
+ self._ensure_audio()
107
+ x = np.asarray(samples, np.float32)
108
+ if x.ndim == 2:
109
+ x = x.mean(1)
110
+ if sample_rate != AUDIO_SR:
111
+ import importlib
112
+ resampy = importlib.import_module("resampy") # optional; off the import graph
113
+ x = resampy.resample(x, sample_rate, AUDIO_SR)
114
+ # split into 10s clips (pad last), embed each, mean-pool
115
+ embs = []
116
+ for s in range(0, max(len(x), 1), CLIP_SAMPLES):
117
+ clip = x[s:s + CLIP_SAMPLES]
118
+ if len(clip) < CLIP_SAMPLES:
119
+ clip = np.pad(clip, (0, CLIP_SAMPLES - len(clip)))
120
+ mel = self._log_mel(torch.from_numpy(clip))
121
+ embs.append(self._me(mel[None]).reshape(-1))
122
+ return torch.stack(embs).mean(0)
123
+
124
+ def _tokenize_text(self, text):
125
+ labels = self._sp.EncodeAsIds(text.lower())[: MAX_TEXT_LEN - 1]
126
+ ids = [TARGET_SOS_ID] + labels
127
+ n = len(ids)
128
+ ids = ids + [0] * (MAX_TEXT_LEN - len(ids))
129
+ ids_t = torch.tensor([ids], dtype=torch.int32, device=self.device)
130
+ pad = torch.ones(1, MAX_TEXT_LEN, device=self.device)
131
+ pad[0, :n] = 0.0
132
+ return ids_t, pad
133
+
134
+ @torch.no_grad()
135
+ def embed(self, text_or_audio, *args, **kwargs):
136
+ """Text string OR audio (Waveform / (samples, sr) / np array @ 16kHz)
137
+ -> [768] style embedding (torch tensor on self.device)."""
138
+ if isinstance(text_or_audio, str):
139
+ ids, pad = self._tokenize_text(text_or_audio)
140
+ return self._te(ids, pad).reshape(-1)
141
+ # audio: accept a Waveform-like (has .samples/.sample_rate), (samples,sr), or np
142
+ obj = text_or_audio
143
+ if hasattr(obj, "samples") and hasattr(obj, "sample_rate"):
144
+ return self.embed_audio(obj.samples, obj.sample_rate)
145
+ if isinstance(obj, tuple) and len(obj) == 2:
146
+ return self.embed_audio(obj[0], obj[1])
147
+ return self.embed_audio(obj, AUDIO_SR)
148
+
149
+ @torch.no_grad()
150
+ def tokenize(self, embedding):
151
+ """[768] embedding -> [12] int RVQ tokens (np.int64). Accepts np or torch.
152
+ For layering, pass a (weighted) mean of several embeddings."""
153
+ if isinstance(embedding, np.ndarray):
154
+ embedding = torch.from_numpy(embedding)
155
+ embedding = embedding.to(self.device).reshape(1, EMBEDDING_DIM).float()
156
+ return self._q(embedding).reshape(-1).cpu().numpy().astype(np.int64)
157
+
158
+ def embed_tokens(self, text):
159
+ """Convenience: text -> 12 style tokens (list[int])."""
160
+ return self.tokenize(self.embed(text)).tolist()
magenta_rt/torch/spectrostream.py ADDED
@@ -0,0 +1,359 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """PyTorch port of the SpectroStream decoder (codes -> embeddings -> waveform).
16
+
17
+ Feature maps are carried as torch NCHW tensors [b, C, T(time), F(freq)] so
18
+ F.conv2d applies directly. JAX conv kernels are stored [kh, kw, cin, cout]
19
+ (HWIO) and permuted to OIHW. Padding replicates sequence_layers semicausal
20
+ (time) / symmetric (freq) for Conv2D, and causal(time)/same(freq) transpose
21
+ conv via input dilation + explicit pad + valid conv.
22
+ """
23
+ import numpy as np
24
+ import torch
25
+ import torch.nn as nn
26
+ import torch.nn.functional as F
27
+ from safetensors import safe_open
28
+
29
+ # Architecture constants for the 40ms 48kHz stereo config.
30
+ RATIOS = ((1, 2), (1, 2), (1, 3), (1, 2), (1, 2), (2, 2), (2, 1))
31
+ CHANNEL_SPLITS = 2
32
+ INPUT_BINS = 5
33
+ INPUT_CHANNELS = 512
34
+ FRAME_LENGTH = 960
35
+ FRAME_STEP = 480
36
+ FFT_LENGTH = 960
37
+ NUM_BINS = 480
38
+ TOTAL_TIME_STRIDE = 4
39
+ DECODER_LOOKAHEAD = 1
40
+
41
+
42
+ def _semicausal_pad(k, s, d=1):
43
+ eff = (k - 1) * d + 1
44
+ left = max(eff - s, 0)
45
+ return left, (eff - 1) - left
46
+
47
+
48
+ def _sym_freq_pad(kw, sw, dw=1):
49
+ pad = max((kw - 1) * dw + 1 - sw, 0)
50
+ return pad // 2, pad - pad // 2
51
+
52
+
53
+ def _transpose_pad(k, s, mode):
54
+ eff = k
55
+ if mode == "causal":
56
+ amt = eff + s - 2
57
+ left = eff - 1
58
+ return left, amt - left
59
+ elif mode == "same":
60
+ amt = eff + s - 2
61
+ if s > eff - 1:
62
+ left = eff - 1
63
+ else:
64
+ left = int(np.ceil(amt / 2))
65
+ return left, amt - left
66
+ raise ValueError(mode)
67
+
68
+
69
+ def _hann_window(n):
70
+ # periodic raised cosine, a=b=0.5 (matches signal.hann_window)
71
+ even = 1 - n % 2
72
+ denom = n + even - 1 # periodic -> n
73
+ count = np.arange(n)
74
+ return (0.5 - 0.5 * np.cos(2 * np.pi * count / denom)).astype(np.float32)
75
+
76
+
77
+ def _inverse_stft_window(frame_length, frame_step):
78
+ fwd = _hann_window(frame_length)
79
+ denom = fwd ** 2
80
+ overlaps = -(-frame_length // frame_step)
81
+ denom = np.pad(denom, (0, overlaps * frame_step - frame_length))
82
+ denom = denom.reshape(overlaps, frame_step).sum(0, keepdims=True)
83
+ denom = np.tile(denom, (overlaps, 1)).reshape(overlaps * frame_step)[:frame_length]
84
+ return np.where(denom == 0.0, 0.0, fwd / denom).astype(np.float32)
85
+
86
+
87
+ def _overlap_and_add(frames, frame_step):
88
+ """frames: [..., n_frames, frame_length] -> [..., output] (naive, exact)."""
89
+ *outer, n, fl = frames.shape
90
+ out_len = (n - 1) * frame_step + fl
91
+ out = frames.new_zeros(*outer, out_len)
92
+ for i in range(n):
93
+ out[..., i * frame_step: i * frame_step + fl] += frames[..., i, :]
94
+ return out
95
+
96
+
97
+ def _dilate2d(x, strides):
98
+ sh, sw = strides
99
+ b, c, h, w = x.shape
100
+ if sh > 1:
101
+ y = x.new_zeros(b, c, (h - 1) * sh + 1, w)
102
+ y[:, :, ::sh, :] = x
103
+ x = y
104
+ b, c, h, w = x.shape
105
+ if sw > 1:
106
+ y = x.new_zeros(b, c, h, (w - 1) * sw + 1)
107
+ y[:, :, :, ::sw] = x
108
+ x = y
109
+ return x
110
+
111
+
112
+ def elu(x):
113
+ return F.elu(x, alpha=1.0)
114
+
115
+
116
+ class SpectroStreamDecoder(nn.Module):
117
+ """Functional decoder driven by a dict of checkpoint tensors."""
118
+
119
+ def __init__(self, weights: dict):
120
+ super().__init__()
121
+ # weights: name -> torch tensor (kernels in HWIO; conv biases 1d).
122
+ self.w = {k: nn.Parameter(v, requires_grad=False) for k, v in weights.items()}
123
+ self.w = nn.ParameterDict({k.replace("/", "__"): v for k, v in self.w.items()})
124
+ self.register_buffer("inv_window", torch.from_numpy(
125
+ _inverse_stft_window(FRAME_LENGTH, FRAME_STEP)))
126
+
127
+ def _g(self, name):
128
+ return self.w[name.replace("/", "__")]
129
+
130
+ # ---- conv primitives ----
131
+ def _conv1x1(self, x, prefix):
132
+ w = self._g(prefix + "/conv/kernel") # [1,1,cin,cout]
133
+ b = self._g(prefix + "/conv/bias")
134
+ wk = w.permute(3, 2, 0, 1).to(x.dtype)
135
+ return F.conv2d(x, wk, bias=b.to(x.dtype))
136
+
137
+ def _conv2d(self, x, prefix, kh, kw, strides=(1, 1), dil=(1, 1)):
138
+ w = self._g(prefix + "/conv/kernel")
139
+ b = self._g(prefix + "/conv/bias")
140
+ pt = _semicausal_pad(kh, strides[0], dil[0])
141
+ pf = _sym_freq_pad(kw, strides[1], dil[1])
142
+ x = F.pad(x, (pf[0], pf[1], pt[0], pt[1]))
143
+ wk = w.permute(3, 2, 0, 1).to(x.dtype)
144
+ return F.conv2d(x, wk, bias=b.to(x.dtype), stride=strides, dilation=dil)
145
+
146
+ def _conv_transpose(self, x, prefix, kh, kw, strides):
147
+ w = self._g(prefix + "/conv/kernel") # [kh,kw,cin,cout]
148
+ b = self._g(prefix + "/conv/bias")
149
+ x = _dilate2d(x, strides)
150
+ pt = _transpose_pad(kh, strides[0], "causal")
151
+ pf = _transpose_pad(kw, strides[1], "same")
152
+ x = F.pad(x, (pf[0], pf[1], pt[0], pt[1]))
153
+ wk = w.permute(3, 2, 0, 1).to(x.dtype)
154
+ return F.conv2d(x, wk, bias=b.to(x.dtype), stride=1)
155
+
156
+ def _upsample(self, x, strides):
157
+ if strides[0] > 1:
158
+ x = x.repeat_interleave(strides[0], dim=2)
159
+ if strides[1] > 1:
160
+ x = x.repeat_interleave(strides[1], dim=3)
161
+ return x
162
+
163
+ def _residual_unit(self, x, prefix, strides, transposed_resample, kt):
164
+ """act->[convT or conv3x3_a]->act->conv3x3 + shortcut."""
165
+ inp = x
166
+ y = elu(x)
167
+ if transposed_resample:
168
+ kh, kw = kt
169
+ y = self._conv_transpose(y, prefix + "/conv2dtranspose_%dx%d" % (kh, kw), kh, kw, strides)
170
+ else:
171
+ y = self._conv2d(y, prefix + "/conv2d_3x3_a", 3, 3)
172
+ y = elu(y)
173
+ y = self._conv2d(y, prefix + "/conv2d_3x3", 3, 3)
174
+ # shortcut
175
+ sc = inp
176
+ has_conv = (prefix + "/shortcut_layer/conv1x1/conv/kernel").replace("/", "__") in self.w
177
+ if has_conv:
178
+ sc = self._conv1x1(sc, prefix + "/shortcut_layer/conv1x1")
179
+ if strides != (1, 1):
180
+ sc = self._upsample(sc, strides)
181
+ return y + sc
182
+
183
+ def decode_embeddings(self, emb):
184
+ """emb: [b,t,256] -> spectrogram feature map [b,4,T,480] (NCHW)."""
185
+ b, t, _ = emb.shape
186
+ x = emb.permute(0, 2, 1).unsqueeze(-1) # [b,256,t,1]
187
+ # input_layer residual
188
+ main = self._conv1x1(x, "input_layer/conv1x1_first")
189
+ sc = self._conv1x1(x, "input_layer/shortcut_layer/conv1x1_b1")
190
+ sc = elu(sc)
191
+ sc = self._conv1x1(sc, "input_layer/shortcut_layer/conv1x1_b2")
192
+ x = main + sc # [b,2560,t,1]
193
+ # reshape (1,2560)->(5,512): [b,2560,t,1]->[b,5,512,t]->[b,512,t,5]
194
+ x = x.squeeze(-1).view(b, INPUT_BINS, INPUT_CHANNELS, t).permute(0, 2, 3, 1)
195
+ # input_layers_residual_unit (stride1)
196
+ x = self._residual_unit(x, "input_layers_residual_unit", (1, 1), False, None)
197
+ # decoder_0
198
+ rev = RATIOS[::-1]
199
+ kt0 = (max(3, 2 * rev[0][0]), max(3, 2 * rev[0][1]))
200
+ x = self._residual_unit(x, "decoder_0", rev[0], True, kt0)
201
+ # ParallelChannels(2): split channels, shared decoder_1..6 + output, concat
202
+ groups = torch.chunk(x, CHANNEL_SPLITS, dim=1)
203
+ outs = []
204
+ for g in groups:
205
+ h = g
206
+ for i in range(1, len(RATIOS)):
207
+ s = rev[i]
208
+ kt = (max(3, 2 * s[0]), max(3, 2 * s[1]))
209
+ h = self._residual_unit(h, f"decoder_{i}", s, True, kt)
210
+ # output_layer: act -> conv7x7 (->2)
211
+ h = elu(h)
212
+ h = self._conv2d(h, "output_layer/base_conv_last", 7, 7)
213
+ outs.append(h)
214
+ x = torch.cat(outs, dim=1) # [b,4,T,480]
215
+ # lookahead trim
216
+ trim = DECODER_LOOKAHEAD * TOTAL_TIME_STRIDE
217
+ if trim:
218
+ x = x[:, :, trim:, :]
219
+ return x
220
+
221
+ def forward(self, emb):
222
+ x = self.decode_embeddings(emb)
223
+ return self._istft(x)
224
+
225
+ # ---- streaming decode (per-frame, stateful) — bit-exact-in-bf16 vs forward,
226
+ # FLOP-optimal (no overlap-save re-decode). state = mutable dict of caches. ----
227
+ def _s_conv2d(self, x, prefix, kh, kw, st, key, strides=(1, 1), dil=(1, 1)):
228
+ pt = _semicausal_pad(kh, strides[0], dil[0])
229
+ pf = _sym_freq_pad(kw, strides[1], dil[1])
230
+ c = st.get(key)
231
+ if c is None:
232
+ c = x.new_zeros(x.shape[0], x.shape[1], pt[0], x.shape[3])
233
+ xc = torch.cat([c, x], dim=2)
234
+ st[key] = xc[:, :, xc.shape[2] - pt[0]:, :] if pt[0] > 0 else c
235
+ xp = F.pad(xc, (pf[0], pf[1], 0, pt[1]))
236
+ w = self._g(prefix + "/conv/kernel"); b = self._g(prefix + "/conv/bias")
237
+ return F.conv2d(xp, w.permute(3, 2, 0, 1).to(x.dtype), bias=b.to(x.dtype),
238
+ stride=strides, dilation=dil)
239
+
240
+ def _s_conv_transpose(self, x, prefix, kh, kw, strides, st, key):
241
+ sh, sw = strides
242
+ pt = _transpose_pad(kh, sh, "causal"); pf = _transpose_pad(kw, sw, "same")
243
+ ctx = (pt[0] + sh - 1) // sh + 1
244
+ c = st.get(key)
245
+ if c is None:
246
+ c = x.new_zeros(x.shape[0], x.shape[1], ctx, x.shape[3])
247
+ C = x.shape[2]
248
+ xc = torch.cat([c, x], dim=2)
249
+ st[key] = xc[:, :, xc.shape[2] - ctx:, :]
250
+ xp = F.pad(_dilate2d(xc, strides), (pf[0], pf[1], pt[0], pt[1]))
251
+ w = self._g(prefix + "/conv/kernel"); b = self._g(prefix + "/conv/bias")
252
+ out = F.conv2d(xp, w.permute(3, 2, 0, 1).to(x.dtype), bias=b.to(x.dtype), stride=1)
253
+ return out[:, :, out.shape[2] - C * sh:, :]
254
+
255
+ def _s_resunit(self, x, prefix, strides, transposed, kt, st, key):
256
+ inp = x; y = elu(x)
257
+ if transposed:
258
+ kh, kw = kt
259
+ y = self._s_conv_transpose(y, prefix + "/conv2dtranspose_%dx%d" % (kh, kw), kh, kw, strides, st, key + "/ct")
260
+ else:
261
+ y = self._s_conv2d(y, prefix + "/conv2d_3x3_a", 3, 3, st, key + "/a")
262
+ y = elu(y)
263
+ y = self._s_conv2d(y, prefix + "/conv2d_3x3", 3, 3, st, key + "/b")
264
+ sc = inp
265
+ if (prefix + "/shortcut_layer/conv1x1/conv/kernel").replace("/", "__") in self.w:
266
+ sc = self._conv1x1(sc, prefix + "/shortcut_layer/conv1x1")
267
+ if strides != (1, 1):
268
+ sc = self._upsample(sc, strides)
269
+ return y + sc
270
+
271
+ def _s_decode_emb(self, emb_new, st):
272
+ b, t, _ = emb_new.shape
273
+ x = emb_new.permute(0, 2, 1).unsqueeze(-1)
274
+ main = self._conv1x1(x, "input_layer/conv1x1_first")
275
+ sc = self._conv1x1(x, "input_layer/shortcut_layer/conv1x1_b1"); sc = elu(sc)
276
+ sc = self._conv1x1(sc, "input_layer/shortcut_layer/conv1x1_b2")
277
+ x = (main + sc).squeeze(-1).view(b, INPUT_BINS, INPUT_CHANNELS, t).permute(0, 2, 3, 1)
278
+ x = self._s_resunit(x, "input_layers_residual_unit", (1, 1), False, None, st, "ilru")
279
+ rev = RATIOS[::-1]
280
+ x = self._s_resunit(x, "decoder_0", rev[0], True, (max(3, 2 * rev[0][0]), max(3, 2 * rev[0][1])), st, "d0")
281
+ outs = []
282
+ for gi, g in enumerate(torch.chunk(x, CHANNEL_SPLITS, dim=1)):
283
+ h = g
284
+ for i in range(1, len(RATIOS)):
285
+ s = rev[i]
286
+ h = self._s_resunit(h, f"decoder_{i}", s, True, (max(3, 2 * s[0]), max(3, 2 * s[1])), st, f"g{gi}/d{i}")
287
+ h = elu(h)
288
+ h = self._s_conv2d(h, "output_layer/base_conv_last", 7, 7, st, f"g{gi}/out")
289
+ outs.append(h)
290
+ return torch.cat(outs, dim=1)
291
+
292
+ def _s_istft(self, xnew, st):
293
+ v = xnew.permute(0, 2, 3, 1).contiguous(); b, T, nb, nc = v.shape
294
+ if T == 0:
295
+ return xnew.new_zeros(b, 0, 2)
296
+ v = F.pad(v, (0, 0, 0, 1)).float()
297
+ comp = torch.view_as_complex(v.view(b, T, 481, nc // 2, 2).contiguous())
298
+ frames = torch.fft.irfft(comp, n=FFT_LENGTH, dim=2) * self.inv_window.view(1, 1, FRAME_LENGTH, 1)
299
+ fr = frames.permute(0, 3, 1, 2)
300
+ tail = st.get("_tail")
301
+ if tail is None:
302
+ tail = fr.new_zeros(b, 2, FRAME_STEP)
303
+ emits = []
304
+ for i in range(T):
305
+ f = fr[:, :, i, :]; emits.append(tail + f[:, :, :FRAME_STEP]); tail = f[:, :, FRAME_STEP:]
306
+ st["_tail"] = tail
307
+ return torch.cat(emits, dim=2).permute(0, 2, 1)
308
+
309
+ def decode_streaming(self, emb_new, state):
310
+ """Incremental decode. `state` is a mutable dict (start with {}). Returns the
311
+ newly-available audio [b, N, 2] for `emb_new` [b, t_new, 256], carrying overlap
312
+ + per-layer conv state across calls. Output == forward(full_emb), 1 frame latency."""
313
+ x = self._s_decode_emb(emb_new, state)
314
+ wm = state.get("_warm", DECODER_LOOKAHEAD * TOTAL_TIME_STRIDE)
315
+ if wm > 0:
316
+ d = min(wm, x.shape[2]); x = x[:, :, d:, :]; state["_warm"] = wm - d
317
+ return self._s_istft(x, state)
318
+
319
+ def _istft(self, x):
320
+ v = x.permute(0, 2, 3, 1).contiguous() # [b,T,480,4]
321
+ b, T, nb, nc = v.shape
322
+ # pad freq (axis=2) 480 -> 481 (keep_dc: pad right)
323
+ v = F.pad(v, (0, 0, 0, 1)) # pad dim=2 right by 1 -> [b,T,481,4]
324
+ v = v.float()
325
+ comp = torch.view_as_complex(v.view(b, T, 481, nc // 2, 2).contiguous()) # [b,T,481,2]
326
+ frames = torch.fft.irfft(comp, n=FFT_LENGTH, dim=2) # [b,T,960,2]
327
+ frames = frames * self.inv_window.view(1, 1, FRAME_LENGTH, 1)
328
+ # overlap-add over (T,960); move channel out: [b,2,T,960]
329
+ fr = frames.permute(0, 3, 1, 2) # [b,2,T,960]
330
+ wav = _overlap_and_add(fr, FRAME_STEP) # [b,2,samples]
331
+ trim = max(FRAME_LENGTH - FRAME_STEP, 0)
332
+ if trim:
333
+ wav = wav[..., :-trim]
334
+ return wav.permute(0, 2, 1) # [b,samples,2]
335
+
336
+
337
+ def codes_to_embeddings(codes, quantizer_embedding):
338
+ """codes: [b,t,Q] long (0..1023); quantizer_embedding: [64,1024,256]. Sum over levels."""
339
+ Q = codes.shape[-1]
340
+ out = None
341
+ for i in range(Q):
342
+ e = quantizer_embedding[i][codes[..., i]] # [b,t,256]
343
+ out = e if out is None else out + e
344
+ return out
345
+
346
+
347
+ def load_spectrostream_decoder(checkpoint_path, dtype=torch.float32, prefix="params/soundstream"):
348
+ """Load decoder + quantizer tensors from a safetensors file."""
349
+ dec_weights = {}
350
+ quant = None
351
+ with safe_open(str(checkpoint_path), "numpy") as f:
352
+ for k in f.keys():
353
+ if k.startswith(prefix + "/decoder/"):
354
+ name = k[len(prefix + "/decoder/"):]
355
+ dec_weights[name] = torch.from_numpy(np.asarray(f.get_tensor(k))).to(dtype)
356
+ elif k.startswith(prefix + "/quantizer/embedding"):
357
+ quant = torch.from_numpy(np.asarray(f.get_tensor(k))).to(dtype)
358
+ dec = SpectroStreamDecoder(dec_weights)
359
+ return dec, quant
magenta_rt/torch/system.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """Pure-PyTorch Magenta RealTime 2 streaming system.
16
+
17
+ Generation uses the torch Depthformer (verified vs JAX) for autoregressive
18
+ token sampling and the torch SpectroStream decoder (verified vs JAX) for audio.
19
+ MusicCoCa style embedding/tokenization is reused from the framework-agnostic
20
+ TFLite component (same as the upstream JAX/MLX paths).
21
+ """
22
+ import numpy as np
23
+ import torch
24
+
25
+ from .. import paths
26
+ from .depthformer import Depthformer, config_for
27
+ from .weights import load_depthformer
28
+ from .spectrostream import load_spectrostream_decoder, codes_to_embeddings
29
+
30
+ NUM_RESERVED_TOKENS = 6
31
+ SR = 48000 # output sample rate
32
+ FRAME_SAMPLES = 1920 # 48kHz samples per 40ms codec frame
33
+ STREAM_DECODE_CONTEXT = 16 # token frames of left context for overlap-save decode
34
+ STREAM_DECODE_MARGIN = 2 # token frames held back (right context: lookahead + ISTFT overlap)
35
+ _CHECKPOINTS = {"mrt2_small": "mrt2_small.safetensors", "mrt2_base": "mrt2_base.safetensors"}
36
+
37
+
38
+ def discretize_cfg(value, step, max_bin):
39
+ clamped = max(-1.0, min(7.0, value))
40
+ return max(0, min(max_bin, int(round((clamped - (-1.0)) / step))))
41
+
42
+
43
+ def convert_from_unique_codes(tokens, codebook_size=1024):
44
+ return (tokens - NUM_RESERVED_TOKENS) % codebook_size
45
+
46
+
47
+ def _float_to_int16(samples, gain=0.5):
48
+ samples = np.clip(gain * samples, -1, 1)
49
+ samples = np.round((np.iinfo(np.int16).max + 0.5) * samples - 0.5)
50
+ return samples.astype(np.int16)
51
+
52
+
53
+ def make_sampler(temperature, top_k, generator):
54
+ # Matches jax _sample_categorical_with_temperature: valid-range mask, top-k,
55
+ # then gumbel-max (logits + gumbel * temperature, argmax). Uses a finite
56
+ # large-negative for masking (a la get_large_negative_number).
57
+ NEG = -1e9
58
+
59
+ def sampler(logits, rvq_index, lo, hi):
60
+ logits = logits.float()
61
+ v = logits.shape[-1]
62
+ idx = torch.arange(v, device=logits.device)
63
+ valid = (idx >= lo) & (idx < hi)
64
+ logits = torch.where(valid, logits, torch.full_like(logits, NEG))
65
+ if top_k is not None and int(top_k) < v:
66
+ k = min(max(int(top_k), 1), v)
67
+ kth = torch.topk(logits, k, dim=-1).values[..., -1:]
68
+ logits = torch.where(logits >= kth, logits, torch.full_like(logits, NEG))
69
+ if temperature and float(temperature) > 0:
70
+ u = torch.rand(logits.shape, generator=generator, device=logits.device)
71
+ gumbel = -torch.log(-torch.log(u.clamp(1e-10, 1 - 1e-7)))
72
+ logits = logits + gumbel * float(temperature)
73
+ return logits.argmax(dim=-1) # [b,1]
74
+ return sampler
75
+
76
+
77
+ class MagentaRT2:
78
+ def __init__(self, size="mrt2_small", device="cpu", dtype=torch.float32,
79
+ temperature=1.3, top_k=40, cfg_musiccoca=3.0, cfg_notes=1.0,
80
+ cfg_drums=1.0, style_model=None, compile=False):
81
+ self.size = size
82
+ self.device = device
83
+ self.dtype = dtype
84
+ self.temperature = temperature
85
+ self.top_k = top_k
86
+ self.cfg_musiccoca = cfg_musiccoca
87
+ self.cfg_notes = cfg_notes
88
+ self.cfg_drums = cfg_drums
89
+ self._style_model = style_model
90
+
91
+ cfg = config_for(size)
92
+ self.cfg = cfg
93
+ self.model = Depthformer(cfg).eval().to(device)
94
+ ckpt = paths.checkpoints_dir() / _CHECKPOINTS[size]
95
+ load_depthformer(self.model, ckpt, dtype=dtype)
96
+ self.model.to(dtype)
97
+ self.dec, quant = load_spectrostream_decoder(ckpt, dtype=dtype)
98
+ self.dec = self.dec.eval().to(device)
99
+ self.quant = quant.to(device)
100
+
101
+ # AOTI-compiled per-frame step callables (None = eager). Set via
102
+ # load_compiled() or apply_compiled().
103
+ self._temporal_step = None
104
+ self._depth_step = None
105
+
106
+ if compile:
107
+ # Compile the two hot per-frame step paths (dynamic shapes for the
108
+ # growing KV caches). One-time warmup; ~1.8x and past real-time.
109
+ dec = self.model.decoder
110
+ dec.temporal_body.step = torch.compile(dec.temporal_body.step, dynamic=True)
111
+ dec._depth_step_logits = torch.compile(dec._depth_step_logits, dynamic=True)
112
+
113
+ self.num_musiccoca = cfg.musiccoca_rvq # 12
114
+ self.num_notes = 128
115
+ self.num_drums = 1
116
+ self.num_cfg = 3
117
+ self.num_channels = self.num_musiccoca + self.num_notes + self.num_drums + self.num_cfg
118
+ self.sample_rate = 48000
119
+ self.codebook_size = cfg.codebook_size
120
+
121
+ def apply_compiled(self, temporal_step=None, depth_step=None):
122
+ """Wire AOTI-compiled per-frame step callables into generation."""
123
+ if temporal_step is not None:
124
+ self._temporal_step = temporal_step
125
+ if depth_step is not None:
126
+ self._depth_step = depth_step
127
+
128
+ def load_compiled(self, repo_id=None, local_dir=None):
129
+ """Load AOTI artifacts (weight-less, compiled on matching GPU arch) and
130
+ bind them to this model's weights via aokit."""
131
+ from . import aoti
132
+ t, d = aoti.load_compiled_steps(self.model.decoder, repo_id=repo_id, local_dir=local_dir)
133
+ self.apply_compiled(t, d)
134
+ return self
135
+
136
+ # ---- style ----
137
+ @property
138
+ def style_model(self):
139
+ if self._style_model is None:
140
+ from .. import musiccoca
141
+ self._style_model = musiccoca.MusicCoCa()
142
+ return self._style_model
143
+
144
+ def embed_style(self, text_or_audio, **kw):
145
+ return self.style_model.embed(text_or_audio, **kw)
146
+
147
+ def tokenize_style(self, embedding):
148
+ return self.style_model.tokenize(embedding)
149
+
150
+ # ---- conditioning ----
151
+ def _conditioning(self, style_tokens, notes, drums, cfgs):
152
+ offset = NUM_RESERVED_TOKENS + 1
153
+ vals = list(style_tokens) + list(notes) + list(drums) + list(cfgs)
154
+ arr = np.array(vals, dtype=np.int64) + offset
155
+ return torch.from_numpy(arr).view(1, 1, -1).to(self.device)
156
+
157
+ def _resolve_conditioning(self, style, notes, drums, cfg_musiccoca, cfg_notes, cfg_drums):
158
+ if style is None:
159
+ style_tokens = [-1] * self.num_musiccoca
160
+ elif isinstance(style, (list, np.ndarray)) and len(np.asarray(style).shape) == 1 and np.asarray(style).dtype.kind in "iu" and len(style) == self.num_musiccoca:
161
+ style_tokens = list(style)
162
+ else:
163
+ style_tokens = self.tokenize_style(style).tolist()
164
+ style_tokens = (style_tokens + [-1] * self.num_musiccoca)[:self.num_musiccoca]
165
+ notes = notes if notes is not None else [-1] * self.num_notes
166
+ drums = drums if drums is not None else [-1] * self.num_drums
167
+ cfgs = [
168
+ discretize_cfg(self.cfg_musiccoca if cfg_musiccoca is None else cfg_musiccoca, 0.2, 40),
169
+ discretize_cfg(self.cfg_notes if cfg_notes is None else cfg_notes, 0.2, 40),
170
+ discretize_cfg(self.cfg_drums if cfg_drums is None else cfg_drums, 1.0, 8),
171
+ ]
172
+ return self._conditioning(style_tokens, notes, drums, cfgs)
173
+
174
+ def _decode_stream(self, history, emitted, context=STREAM_DECODE_CONTEXT,
175
+ margin=STREAM_DECODE_MARGIN, flush=False):
176
+ """Overlap-save: decode recent token context, emit only the safe new tail.
177
+
178
+ The SpectroStream decoder has a small left receptive field and ~1 frame
179
+ of lookahead + ISTFT overlap on the right. Decoding `[left-context ...
180
+ new ... right-margin]` and emitting the interior gives output identical
181
+ to a fully stateful streaming codec. `margin` frames are held back until
182
+ their future context exists (flush=True emits them at stream end).
183
+ Returns (new_samples [1,N,2], new_emitted)."""
184
+ m = 0 if flush else margin
185
+ Ttot = history.shape[1]
186
+ emittable = (Ttot - 1) - m
187
+ avail = emittable - emitted
188
+ if avail <= 0:
189
+ return history.new_zeros((1, 0, 2), dtype=self.dtype), emitted
190
+ w0 = max(0, Ttot - (avail + m + context + 1))
191
+ window = history[:, w0:]
192
+ codes = convert_from_unique_codes(window, self.codebook_size)
193
+ emb = codes_to_embeddings(codes, self.quant)
194
+ wav = self.dec(emb.to(self.dtype)) # [1, (len(window)-1)*1920, 2]
195
+ end = wav.shape[1] - m * FRAME_SAMPLES
196
+ new = wav[:, end - avail * FRAME_SAMPLES: end]
197
+ return new, emitted + avail
198
+
199
+ def init_decode_state(self):
200
+ """Fresh state dict for streaming decode (decode_stream)."""
201
+ return {}
202
+
203
+ @torch.no_grad()
204
+ def decode_stream(self, new_codes, state):
205
+ """Incremental codec decode of new token frames [b, t_new, Q] -> audio [b, N, 2].
206
+ FLOP-optimal stateful streaming (no overlap-save re-decode); bf16-equivalent to
207
+ _decode_stream, with a 1-frame (40ms) decoder latency. `state` starts as {}."""
208
+ codes = convert_from_unique_codes(new_codes, self.codebook_size)
209
+ emb = codes_to_embeddings(codes, self.quant)
210
+ return self.dec.decode_streaming(emb.to(self.dtype), state)
211
+
212
+ @torch.no_grad()
213
+ def stream_session(self, control, chunk_frames=10, max_seconds=55.0,
214
+ seed=0, time_fn=None, sleep_fn=None, notes=None, drums=None):
215
+ """Continuous generation for an interactive session. `control()` returns a
216
+ dict {style_tokens, temperature, top_k, cfg_*} read every chunk, so the
217
+ prompt can change mid-stream. Yields int16 [N,2] audio chunks. Keeps LLM
218
+ state across chunks; re-encodes the conditioning `source` when style changes.
219
+ Paces to ~real-time so steering stays responsive."""
220
+ import time as _time
221
+ time_fn = time_fn or _time.time
222
+ sleep_fn = sleep_fn or _time.sleep
223
+ dev, dt = self.device, self.dtype
224
+ notes = notes if notes is not None else [-1] * self.num_notes
225
+ drums = drums if drums is not None else [-1] * self.num_drums
226
+ dstate = self.model.decoder.init_streaming_f(1, dev, dt)
227
+ gen = torch.Generator(device=dev).manual_seed(seed)
228
+ history = torch.zeros((1, 0, self.cfg.num_codebooks), dtype=torch.long, device=dev)
229
+ emitted = 0
230
+ cur_tokens = None
231
+ source = None
232
+ t0 = time_fn()
233
+ while time_fn() - t0 < max_seconds:
234
+ c = control()
235
+ if c is None:
236
+ sleep_fn(0.02)
237
+ continue
238
+ tokens = c["style_tokens"]
239
+ if tokens != cur_tokens:
240
+ cur_tokens = tokens
241
+ cfgs = [discretize_cfg(c.get("cfg_musiccoca", self.cfg_musiccoca), 0.2, 40),
242
+ discretize_cfg(c.get("cfg_notes", self.cfg_notes), 0.2, 40),
243
+ discretize_cfg(c.get("cfg_drums", self.cfg_drums), 1.0, 8)]
244
+ cond = self._conditioning((list(tokens) + [-1] * self.num_musiccoca)[:self.num_musiccoca],
245
+ notes, drums, cfgs)
246
+ source = self.model.encode(cond).to(dt)
247
+ sampler = make_sampler(c.get("temperature", self.temperature), c.get("top_k", self.top_k), gen)
248
+ toks = []
249
+ for _ in range(chunk_frames):
250
+ toks.append(self.model.decoder.step_f(
251
+ dstate, source, sampler=sampler,
252
+ temporal_step=self._temporal_step, depth_step=self._depth_step))
253
+ history = torch.cat([history] + toks, dim=1)
254
+ audio, emitted = self._decode_stream(history, emitted)
255
+ if audio.shape[1] > 0:
256
+ yield _float_to_int16(audio[0].float().cpu().numpy())
257
+ # pace: keep generated audio ~1s ahead of wall-clock
258
+ ahead = (emitted * FRAME_SAMPLES / SR) - (time_fn() - t0)
259
+ if ahead > 1.0:
260
+ sleep_fn(min(ahead - 1.0, 0.5))
261
+
262
+ @torch.no_grad()
263
+ def generate(self, style=None, notes=None, drums=None, cfg_musiccoca=None,
264
+ cfg_notes=None, cfg_drums=None, temperature=None, top_k=None,
265
+ frames=25, seed=0, state=None, flush=False, return_int16=False):
266
+ """Generate `frames` of audio. Pass the returned `state` back in to
267
+ continue seamlessly (continuous/live generation); conditioning args may
268
+ change between calls to steer the stream. Audio is emitted incrementally
269
+ (only the newly-available chunk is returned each call). Set flush=True on
270
+ the final call to emit the held-back tail frames."""
271
+ temperature = self.temperature if temperature is None else temperature
272
+ top_k = self.top_k if top_k is None else top_k
273
+ cond = self._resolve_conditioning(style, notes, drums, cfg_musiccoca, cfg_notes, cfg_drums)
274
+ source = self.model.encode(cond).to(self.dtype) # constant per frame this call
275
+
276
+ if state is None:
277
+ dstate = self.model.decoder.init_streaming_f(1, self.device, self.dtype)
278
+ gen = torch.Generator(device=self.device).manual_seed(seed)
279
+ history = torch.zeros((1, 0, self.cfg.num_codebooks), dtype=torch.long, device=self.device)
280
+ emitted = 0
281
+ else:
282
+ dstate, gen, history, emitted = state["dstate"], state["gen"], state["history"], state["emitted"]
283
+
284
+ sampler = make_sampler(temperature, top_k, gen)
285
+ toks = []
286
+ for _ in range(frames):
287
+ frame = self.model.decoder.step_f(
288
+ dstate, source, sampler=sampler,
289
+ temporal_step=self._temporal_step, depth_step=self._depth_step)
290
+ toks.append(frame)
291
+ history = torch.cat([history] + toks, dim=1)
292
+ audio, emitted = self._decode_stream(history, emitted, flush=flush)
293
+
294
+ new_state = {"dstate": dstate, "gen": gen, "history": history, "emitted": emitted}
295
+ wav = audio[0].float().cpu().numpy()
296
+ i16 = _float_to_int16(wav) # 0.5 gain + clip, pointwise (chunk-safe)
297
+ out = i16 if return_int16 else i16.astype(np.float32) / 32768.0
298
+ return out, new_state
magenta_rt/torch/weights.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 Google LLC
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
+ """Map JAX/Linen safetensors checkpoint keys onto the torch Depthformer."""
16
+ import re
17
+
18
+ import numpy as np
19
+ import torch
20
+ from safetensors import safe_open
21
+
22
+
23
+ def _jax_key_to_torch(k: str):
24
+ """Translate a 'params/depthformer/...' key to a torch named_parameter path,
25
+ or return None to skip (e.g. soundstream)."""
26
+ if not k.startswith("params/depthformer/"):
27
+ return None
28
+ s = k[len("params/depthformer/"):]
29
+
30
+ # Encoder (conditioning embedders).
31
+ if s.startswith("encoder/"):
32
+ if s == "encoder/body/encoder_ln/scale":
33
+ return "encoder.encoder_ln.scale"
34
+ if s == "encoder/body/encoder_ln/bias":
35
+ return "encoder.encoder_ln.bias"
36
+ if s.endswith("mulan_dequantizer/embedding"):
37
+ return "encoder.mulan_dequantizer"
38
+ if s.endswith("mulan_embedder/depth_input_adapter/kernel"):
39
+ return "encoder.mulan_adapter.kernel"
40
+ if s.endswith("regular_embedder/embedding"):
41
+ return "encoder.regular_embedding"
42
+ return None
43
+
44
+ # Decoder.
45
+ if s == "decoder/decoder_embedding/embedding/embedding":
46
+ return "decoder.embedding"
47
+ if s == "decoder/depth_body/depth_input_adapter/kernel":
48
+ return "decoder.depth_input_adapter.kernel"
49
+ if s.startswith("decoder/depth_body/final_ln/"):
50
+ return "decoder.final_ln." + s.split("/")[-1]
51
+ if s.startswith("decoder/depth_body/to_logits/"):
52
+ return "decoder.to_logits." + s.split("/")[-1]
53
+
54
+ m = re.match(r"decoder/(temporal_body|depth_body)/transformer/x_layers_(\d+)/(.*)", s)
55
+ if m:
56
+ body, i, rest = m.group(1), int(m.group(2)), m.group(3)
57
+ prefix = f"decoder.{body}.layers.{i}."
58
+ return prefix + _layer_subkey(rest)
59
+
60
+ return None
61
+
62
+
63
+ def _layer_subkey(rest: str):
64
+ # rest like 'self_attention/attention/query_projection/kernel'
65
+ parts = rest.split("/")
66
+ sub = parts[0] # self_attention | cross_attention | ffn
67
+ tail = parts[1:]
68
+ if sub in ("self_attention", "cross_attention"):
69
+ if tail[0] == "attention":
70
+ name = tail[1]
71
+ if name.endswith("_projection"): # query/key/value_projection/kernel
72
+ return f"{sub}.attention.{name}_kernel"
73
+ # per_dim_scale, sink_key_embeddings, sink_value_embeddings
74
+ return f"{sub}.attention.{name}"
75
+ if tail[0] == "output_projection": # output_projection/kernel
76
+ return f"{sub}.output_projection_kernel"
77
+ if tail[0] in ("pre_norm", "post_norm"):
78
+ return f"{sub}.{tail[0]}.scale"
79
+ if sub == "ffn":
80
+ if tail[0] in ("ffn_layer1", "ffn_layer2"):
81
+ return f"ffn.{tail[0]}.{tail[1]}" # kernel | bias
82
+ if tail[0] in ("pre_norm", "post_norm"):
83
+ return f"ffn.{tail[0]}.scale"
84
+ raise KeyError(f"unhandled layer subkey: {rest}")
85
+
86
+
87
+ def load_depthformer(model, checkpoint_path, dtype=torch.float32, strict=True, verbose=False):
88
+ """Load checkpoint into a torch Depthformer (module with .encoder/.decoder)."""
89
+ params = dict(model.named_parameters())
90
+ seen = set()
91
+ with safe_open(str(checkpoint_path), "numpy") as f:
92
+ for k in f.keys():
93
+ tname = _jax_key_to_torch(k)
94
+ if tname is None:
95
+ continue
96
+ if tname not in params:
97
+ raise KeyError(f"{k} -> {tname} not found in model")
98
+ arr = f.get_tensor(k)
99
+ t = torch.from_numpy(np.asarray(arr)).to(dtype)
100
+ p = params[tname]
101
+ if tuple(p.shape) != tuple(t.shape):
102
+ raise ValueError(f"shape mismatch {tname}: model {tuple(p.shape)} ckpt {tuple(t.shape)} ({k})")
103
+ with torch.no_grad():
104
+ p.copy_(t)
105
+ seen.add(tname)
106
+ missing = [n for n in params if n not in seen]
107
+ if verbose:
108
+ print(f"loaded {len(seen)} params, {len(missing)} missing")
109
+ if strict and missing:
110
+ raise RuntimeError(f"missing params not loaded: {missing[:20]} ... ({len(missing)} total)")
111
+ return model
model.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Magenta RealTime 2",
3
+ "model_size": "mrt2_small",
4
+ "parameters": 230000000,
5
+ "source_repository": "https://github.com/magenta/magenta-realtime",
6
+ "model_repository": "google/magenta-realtime-2",
7
+ "checkpoint": "checkpoints/mrt2_small.safetensors",
8
+ "model_license": "CC BY 4.0",
9
+ "runtime": {
10
+ "backend": "PyTorch",
11
+ "source": "magenta-community/magenta-rt-jam",
12
+ "revision": "dfd4eb65e8ebf7eaa9be263d9937ddbee1666262",
13
+ "license": "Apache-2.0",
14
+ "aoti_repository": "magenta-torch/magenta-rt-aoti-small"
15
+ }
16
+ }
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio==5.28.0
2
+ torch==2.11.0
3
+ git+https://github.com/TEAMuP-dev/pyharp.git@develop
4
+ huggingface-hub>=0.30
5
+ numpy>=1.26
6
+ resampy>=0.4
7
+ safetensors>=0.4
8
+ sentencepiece>=0.2
9
+ soundfile>=0.12