Spaces:
Paused
Paused
LING commited on
Commit ·
fade829
1
Parent(s): 2b8523a
Deploy Magenta RealTime 2 HARP endpoint
Browse files- .gitignore +6 -0
- LICENSE +202 -0
- README.md +14 -7
- SOURCES.md +19 -0
- app.py +178 -0
- magenta_rt/__init__.py +2 -0
- magenta_rt/paths.py +120 -0
- magenta_rt/torch/__init__.py +16 -0
- magenta_rt/torch/aoti.py +134 -0
- magenta_rt/torch/depthformer.py +403 -0
- magenta_rt/torch/layers.py +344 -0
- magenta_rt/torch/modeling_magenta_rt2.py +356 -0
- magenta_rt/torch/musiccoca.py +160 -0
- magenta_rt/torch/spectrostream.py +359 -0
- magenta_rt/torch/system.py +298 -0
- magenta_rt/torch/weights.py +111 -0
- model.json +16 -0
- requirements.txt +9 -0
.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
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version:
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|