OzzyGT HF Staff commited on
Commit
2577656
·
1 Parent(s): 7d3b8b0

initial commit

Browse files
Files changed (11) hide show
  1. .gitignore +1 -0
  2. LICENSE +202 -0
  3. README.md +65 -0
  4. blocks.py +516 -0
  5. cuda_graph.py +195 -0
  6. guidance.py +19 -0
  7. modular_config.json +10 -0
  8. modular_model_index.json +4 -0
  9. nar.py +96 -0
  10. protocol.py +108 -0
  11. sampling.py +200 -0
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ __pycache__/
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 ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - en
4
+ - zh
5
+ license: apache-2.0
6
+ base_model:
7
+ - m-a-p/YuE2-3B
8
+ - m-a-p/YuE2-Vae
9
+ tags:
10
+ - music-generation
11
+ - yue2
12
+ - modular-diffusers
13
+ pipeline_tag: text-to-audio
14
+ library_name: diffusers
15
+ ---
16
+ # YuE2 Modular Diffusers
17
+
18
+ <audio controls src="https://huggingface.co/datasets/OzzyGT/diffusers-examples/resolve/main/yue2/city_lights.mp3"></audio>
19
+
20
+ *A 69-second song generated with the code below, seed `831001`.*
21
+
22
+ Modular Diffusers blocks for [m-a-p/YuE2-3B](https://huggingface.co/m-a-p/YuE2-3B): full songs from a style description and lyrics, planned as an ABC score first, as 48 kHz stereo audio. The model code and weights are in [OzzyGT/YuE2-3B-Diffusers](https://huggingface.co/OzzyGT/YuE2-3B-Diffusers).
23
+
24
+ Note: This model requires `tiktoken` and the example uses `soundfile`, install them with `pip install tiktoken soundfile`.
25
+
26
+ ## Licensing
27
+
28
+ The code is Apache-2.0, adapted from [multimodal-art-projection/YuE](https://github.com/multimodal-art-projection/YuE). The weights are CC BY-NC 4.0 (non-commercial); see the [model repository](https://huggingface.co/OzzyGT/YuE2-3B-Diffusers).
29
+
30
+ ## Sample song
31
+
32
+ The song above was generated with the following code:
33
+
34
+ ```python
35
+ import soundfile as sf
36
+ import torch
37
+ from diffusers import ModularPipelineBlocks
38
+
39
+
40
+ blocks = ModularPipelineBlocks.from_pretrained( # load the blocks first to avoid warnings
41
+ "OzzyGT/YuE2-Modular",
42
+ trust_remote_code=True,
43
+ components_repo="OzzyGT/YuE2-3B-Diffusers",
44
+ trust_components_code=True,
45
+ )
46
+ pipe = blocks.init_pipeline()
47
+ pipe.load_components(dtype={"transformer": torch.bfloat16, "vae": torch.float32})
48
+ pipe.to("cuda")
49
+
50
+ style = "English, warm piano pop, expressive female voice, acoustic piano, rounded bass and light drums, lyrical memorable melody, unhurried phrasing, 88 BPM"
51
+ lyrics = """[Verse]
52
+ Neon fades along the lane
53
+ Footsteps keep the time of rain
54
+ Fold the night and leave it here
55
+ Morning has a sky to clear
56
+
57
+ [Chorus]
58
+ Let the day come into view
59
+ Every road begins with you
60
+ Hold a little room for light
61
+ We will sing beyond the night"""
62
+
63
+ state = pipe(style=style, lyrics=lyrics, seed=831001, use_cuda_graph=True)
64
+ sf.write("song.flac", state.get("audios")[0].T.numpy(), state.get("sample_rate"), subtype="PCM_24")
65
+ ```
blocks.py ADDED
@@ -0,0 +1,516 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted for diffusers from multimodal-art-projection/YuE at commit ef1936f2ee39fe8de486a0f47a481c95f8d4da87.
2
+ # Licensed under Apache-2.0; see LICENSE.
3
+ from dataclasses import replace
4
+
5
+ import torch
6
+ from diffusers import ModelMixin
7
+ from diffusers.configuration_utils import FrozenDict
8
+ from diffusers.modular_pipelines import (
9
+ ComponentSpec,
10
+ InputParam,
11
+ LoopSequentialPipelineBlocks,
12
+ ModularPipelineBlocks,
13
+ OutputParam,
14
+ SequentialPipelineBlocks,
15
+ )
16
+ from diffusers.utils.dynamic_modules_utils import get_class_from_dynamic_module
17
+ from transformers import PreTrainedTokenizer
18
+
19
+ from .cuda_graph import GraphAR
20
+ from .guidance import YuE2SemanticGuider
21
+ from .nar import YuE2PrefixKVCache, solve_midpoint, song_chunks
22
+ from .protocol import (
23
+ ABC_SAMPLING,
24
+ CODEC_OFFSET,
25
+ CONTEXT,
26
+ SEMANTIC_SAMPLING,
27
+ SongRequest,
28
+ negative_prefix,
29
+ resolve_sampling,
30
+ token_prefixes,
31
+ )
32
+ from .sampling import generate_tokens
33
+
34
+
35
+ def callback_inputs():
36
+ return [
37
+ InputParam(
38
+ "cancelled", type_hint=object, default=None, description="Optional callable returning whether to cancel."
39
+ ),
40
+ InputParam(
41
+ "on_token", type_hint=object, default=None, description="Optional callback receiving stage and token ID."
42
+ ),
43
+ ]
44
+
45
+
46
+ def check_cancelled(callback):
47
+ if callback is not None and callback():
48
+ raise InterruptedError("Generation cancelled")
49
+
50
+
51
+ class YuE2PrepareInputsStep(ModularPipelineBlocks):
52
+ model_name = "yue2"
53
+
54
+ @property
55
+ def description(self):
56
+ return "Validates the song request."
57
+
58
+ @property
59
+ def inputs(self):
60
+ return [
61
+ InputParam("style", type_hint=str, required=True, description="Music style description."),
62
+ InputParam("lyrics", type_hint=str, required=True, description="Lyrics with song section tags."),
63
+ InputParam("cot", type_hint=str, default="full", description="Score mode: full, melody, or off."),
64
+ InputParam(
65
+ "seed",
66
+ type_hint=int,
67
+ default=831001,
68
+ description="Request seed. Each stage reseeds from it, as the original release does.",
69
+ ),
70
+ InputParam("abc", type_hint=str, default=None, description="Optional supplied ABC score."),
71
+ ]
72
+
73
+ @property
74
+ def intermediate_outputs(self):
75
+ return [OutputParam("request", type_hint=SongRequest, description="Validated song request.")]
76
+
77
+ def __call__(self, components, state):
78
+ block_state = self.get_block_state(state)
79
+ block_state.request = SongRequest(
80
+ style=block_state.style,
81
+ lyrics=block_state.lyrics,
82
+ cot=block_state.cot,
83
+ seed=block_state.seed,
84
+ abc=block_state.abc,
85
+ )
86
+ self.set_block_state(state, block_state)
87
+ return components, state
88
+
89
+
90
+ class YuE2PlanStep(ModularPipelineBlocks):
91
+ model_name = "yue2"
92
+ _requirements = {"tiktoken": ">=0.12.0"}
93
+
94
+ @property
95
+ def description(self):
96
+ return "Plans the song as an ABC score (or uses the supplied one) and builds the semantic-stage prompt."
97
+
98
+ @property
99
+ def expected_components(self):
100
+ return [ComponentSpec("transformer", ModelMixin), ComponentSpec("tokenizer", PreTrainedTokenizer)]
101
+
102
+ @property
103
+ def inputs(self):
104
+ return [
105
+ InputParam("request", type_hint=SongRequest, required=True, description="Validated song request."),
106
+ InputParam(
107
+ "abc_sampling", type_hint=dict, default=None, description="Overrides for the ABC sampling defaults."
108
+ ),
109
+ InputParam("use_cuda_graph", type_hint=bool, default=False, description="Decode tokens with CUDA graphs."),
110
+ ] + callback_inputs()
111
+
112
+ @property
113
+ def intermediate_outputs(self):
114
+ return [
115
+ OutputParam("score", type_hint=str, description="Generated or supplied ABC score; None with cot='off'."),
116
+ OutputParam("abc_ids", type_hint=list, description="Exact score token IDs."),
117
+ OutputParam("prefix", type_hint=list, description="Semantic-stage prompt token IDs."),
118
+ OutputParam("abc_truncated", type_hint=bool, description="Whether planning hit its token limit."),
119
+ OutputParam("abc_timing", type_hint=dict, description="Planning timings."),
120
+ ]
121
+
122
+ @torch.inference_mode()
123
+ def __call__(self, components, state):
124
+ block_state = self.get_block_state(state)
125
+ check_cancelled(block_state.cancelled)
126
+ request = block_state.request
127
+ score, abc_ids, timing, truncated = None, [], {}, False
128
+ if request.cot != "off":
129
+ if request.abc is not None:
130
+ score = request.abc
131
+ abc_ids = components.tokenizer.encode(score)
132
+ else:
133
+ abc_ids, timing, truncated = generate_tokens(
134
+ components.transformer,
135
+ token_prefixes(request, components.tokenizer),
136
+ resolve_sampling(block_state.abc_sampling, ABC_SAMPLING),
137
+ request.seed,
138
+ "abc",
139
+ components._execution_device,
140
+ cancelled=block_state.cancelled,
141
+ on_token=block_state.on_token,
142
+ graph_decoder=GraphAR if block_state.use_cuda_graph else None,
143
+ )
144
+ score = components.tokenizer.decode(abc_ids)
145
+ block_state.score = score
146
+ block_state.abc_ids = abc_ids
147
+ block_state.prefix = token_prefixes(request, components.tokenizer, abc_ids)
148
+ block_state.abc_truncated = truncated
149
+ block_state.abc_timing = timing
150
+ self.set_block_state(state, block_state)
151
+ return components, state
152
+
153
+
154
+ class YuE2SemanticStep(ModularPipelineBlocks):
155
+ model_name = "yue2"
156
+ _requirements = {"tiktoken": ">=0.12.0"}
157
+
158
+ @property
159
+ def description(self):
160
+ return "Generates the song's codec (semantic) tokens, with guidance from the `guider` component."
161
+
162
+ @property
163
+ def expected_components(self):
164
+ return [
165
+ ComponentSpec("transformer", ModelMixin),
166
+ ComponentSpec("tokenizer", PreTrainedTokenizer),
167
+ ComponentSpec(
168
+ "guider", YuE2SemanticGuider, config=FrozenDict({"scale": None}), default_creation_method="from_config"
169
+ ),
170
+ ]
171
+
172
+ @property
173
+ def inputs(self):
174
+ return [
175
+ InputParam("request", type_hint=SongRequest, required=True, description="Validated song request."),
176
+ InputParam("abc_ids", type_hint=list, required=True, description="Exact score token IDs."),
177
+ InputParam("prefix", type_hint=list, required=True, description="Semantic-stage prompt token IDs."),
178
+ InputParam(
179
+ "semantic_sampling",
180
+ type_hint=dict,
181
+ default=None,
182
+ description="Overrides for the semantic sampling defaults.",
183
+ ),
184
+ InputParam("use_cuda_graph", type_hint=bool, default=False, description="Decode tokens with CUDA graphs."),
185
+ ] + callback_inputs()
186
+
187
+ @property
188
+ def intermediate_outputs(self):
189
+ return [
190
+ OutputParam("semantic_tokens", type_hint=list, description="Codec token IDs, 25 per second of audio."),
191
+ OutputParam("semantic_truncated", type_hint=bool, description="Whether generation hit its token limit."),
192
+ OutputParam("semantic_timing", type_hint=dict, description="Semantic-stage timings."),
193
+ ]
194
+
195
+ @torch.inference_mode()
196
+ def __call__(self, components, state):
197
+ block_state = self.get_block_state(state)
198
+ check_cancelled(block_state.cancelled)
199
+ request = block_state.request
200
+ if token_prefixes(request, components.tokenizer, block_state.abc_ids) != block_state.prefix:
201
+ raise ValueError("The semantic prompt disagrees with the request and score IDs")
202
+ guider = components.guider
203
+ negative = None
204
+ if guider.scale_for(request.cot) != 1:
205
+ negative = negative_prefix(request, components.tokenizer, block_state.abc_ids)
206
+ tokens, timing, truncated = generate_tokens(
207
+ components.transformer,
208
+ block_state.prefix,
209
+ resolve_sampling(block_state.semantic_sampling, SEMANTIC_SAMPLING),
210
+ request.seed,
211
+ "semantic",
212
+ components._execution_device,
213
+ negative=negative,
214
+ combine_logits=lambda conditional, unconditional: guider.combine(conditional, unconditional, request.cot),
215
+ legacy_off=request.cot == "off",
216
+ cancelled=block_state.cancelled,
217
+ on_token=block_state.on_token,
218
+ graph_decoder=GraphAR if block_state.use_cuda_graph else None,
219
+ )
220
+ block_state.semantic_tokens = [int(t) - CODEC_OFFSET for t in tokens]
221
+ block_state.semantic_truncated = truncated
222
+ block_state.semantic_timing = timing
223
+ self.set_block_state(state, block_state)
224
+ return components, state
225
+
226
+
227
+ class YuE2PrepareChunksStep(ModularPipelineBlocks):
228
+ model_name = "yue2"
229
+
230
+ @property
231
+ def description(self):
232
+ return "Splits the song into acoustic chunks and draws their seeded noise."
233
+
234
+ @property
235
+ def inputs(self):
236
+ return [
237
+ InputParam("request", type_hint=SongRequest, required=True, description="Validated song request."),
238
+ InputParam("prefix", type_hint=list, required=True, description="Semantic-stage prompt token IDs."),
239
+ InputParam("semantic_tokens", type_hint=list, required=True, description="Codec token IDs."),
240
+ InputParam(
241
+ "acoustic_context",
242
+ type_hint=int,
243
+ default=CONTEXT,
244
+ description="Context limit that sets the acoustic chunk size.",
245
+ ),
246
+ ]
247
+
248
+ @property
249
+ def intermediate_outputs(self):
250
+ return [OutputParam("chunks", type_hint=list, description="Acoustic chunk tokens and CPU FP32 noise.")]
251
+
252
+ def __call__(self, components, state):
253
+ block_state = self.get_block_state(state)
254
+ block_state.chunks = song_chunks(
255
+ block_state.prefix,
256
+ block_state.semantic_tokens,
257
+ block_state.request.seed,
258
+ block_state.acoustic_context,
259
+ )
260
+ self.set_block_state(state, block_state)
261
+ return components, state
262
+
263
+
264
+ class YuE2ChunkConditionStep(ModularPipelineBlocks):
265
+ model_name = "yue2"
266
+
267
+ @property
268
+ def description(self):
269
+ return "Runs the chunk's tokens through the token stream once and keeps their keys and values."
270
+
271
+ @property
272
+ def expected_components(self):
273
+ return [ComponentSpec("transformer", ModelMixin)]
274
+
275
+ @property
276
+ def inputs(self):
277
+ return [
278
+ InputParam("chunks", type_hint=list, required=True, description="Prepared acoustic chunks."),
279
+ InputParam("cancelled", type_hint=object, default=None, description="Cancellation callable."),
280
+ ]
281
+
282
+ @torch.inference_mode()
283
+ def __call__(self, components, block_state, k):
284
+ check_cancelled(block_state.cancelled)
285
+ block_state.chunk = block_state.chunks[k]
286
+ block_state.kv_cache = YuE2PrefixKVCache()
287
+ ids = torch.tensor([block_state.chunk.ar_tokens], device=components._execution_device)
288
+ components.transformer(ids, kv_cache=block_state.kv_cache, logits_to_keep=1)
289
+ return components, block_state
290
+
291
+
292
+ class YuE2ChunkSynthesizeStep(ModularPipelineBlocks):
293
+ model_name = "yue2"
294
+
295
+ @property
296
+ def description(self):
297
+ return "Solves the chunk's flow-matching ODE with the midpoint method."
298
+
299
+ @property
300
+ def expected_components(self):
301
+ return [ComponentSpec("transformer", ModelMixin)]
302
+
303
+ @property
304
+ def inputs(self):
305
+ return [
306
+ InputParam("chunks", type_hint=list, required=True, description="Prepared acoustic chunks."),
307
+ InputParam("ode_steps", type_hint=int, default=32, description="Midpoint steps per chunk."),
308
+ InputParam("cancelled", type_hint=object, default=None, description="Cancellation callable."),
309
+ InputParam(
310
+ "on_progress", type_hint=object, default=None, description="Callback(stage, completed, total)."
311
+ ),
312
+ ]
313
+
314
+ @torch.inference_mode()
315
+ def __call__(self, components, block_state, k):
316
+ report = None
317
+ if block_state.on_progress is not None:
318
+
319
+ def report(done, total):
320
+ block_state.on_progress("synthesis", k * total + done, len(block_state.chunks) * total)
321
+
322
+ block_state.chunk_latents = solve_midpoint(
323
+ components.transformer,
324
+ block_state.kv_cache,
325
+ block_state.chunk.noise,
326
+ components._execution_device,
327
+ block_state.ode_steps,
328
+ block_state.cancelled,
329
+ report,
330
+ )
331
+ return components, block_state
332
+
333
+
334
+ class YuE2CollectChunkStep(ModularPipelineBlocks):
335
+ model_name = "yue2"
336
+
337
+ @property
338
+ def description(self):
339
+ return "Appends the chunk's latents."
340
+
341
+ def __call__(self, components, block_state, k):
342
+ block_state.latent_chunks.append(block_state.chunk_latents)
343
+ return components, block_state
344
+
345
+
346
+ class YuE2AcousticChunkLoop(LoopSequentialPipelineBlocks):
347
+ model_name = "yue2"
348
+ block_classes = [YuE2ChunkConditionStep, YuE2ChunkSynthesizeStep, YuE2CollectChunkStep]
349
+ block_names = ["condition", "synthesize", "collect"]
350
+
351
+ @property
352
+ def description(self):
353
+ return "Synthesizes acoustic latents chunk by chunk."
354
+
355
+ @property
356
+ def loop_inputs(self):
357
+ return [InputParam("chunks", type_hint=list, required=True, description="Prepared acoustic chunks.")]
358
+
359
+ @property
360
+ def loop_intermediate_outputs(self):
361
+ return [OutputParam("latents", type_hint=torch.Tensor, description="Acoustic latents [frames, 64], CPU FP32.")]
362
+
363
+ @torch.inference_mode()
364
+ def __call__(self, components, state):
365
+ block_state = self.get_block_state(state)
366
+ if not block_state.chunks:
367
+ raise ValueError("At least one acoustic chunk is required")
368
+ block_state.latent_chunks = []
369
+ for k in range(len(block_state.chunks)):
370
+ try:
371
+ components, block_state = self.loop_step(components, block_state, k=k)
372
+ finally:
373
+ block_state.kv_cache = None
374
+ block_state.latents = torch.cat(block_state.latent_chunks, dim=0)
375
+ self.set_block_state(state, block_state)
376
+ return components, state
377
+
378
+
379
+ class YuE2SynthesizeStep(SequentialPipelineBlocks):
380
+ model_name = "yue2"
381
+ block_classes = [YuE2PrepareChunksStep, YuE2AcousticChunkLoop]
382
+ block_names = ["prepare_chunks", "chunk_loop"]
383
+
384
+ @property
385
+ def description(self):
386
+ return "Turns codec tokens into acoustic latents."
387
+
388
+
389
+ class YuE2DecodeStep(ModularPipelineBlocks):
390
+ model_name = "yue2"
391
+
392
+ @property
393
+ def description(self):
394
+ return (
395
+ "Decodes latents to 48 kHz stereo audio in overlapping tiles. The VAE runs in FP32 with TF32 and cuDNN "
396
+ "autotuning disabled, as in the original release."
397
+ )
398
+
399
+ @property
400
+ def expected_components(self):
401
+ return [ComponentSpec("vae", ModelMixin)]
402
+
403
+ @property
404
+ def inputs(self):
405
+ return [
406
+ InputParam(
407
+ "latents",
408
+ type_hint=torch.Tensor,
409
+ required=True,
410
+ description="Acoustic latents, [frames, 64] or [1, 64, frames].",
411
+ ),
412
+ InputParam("vae_tile_frames", type_hint=int, default=1024, description="Latent frames decoded per tile."),
413
+ InputParam(
414
+ "vae_tile_overlap_frames",
415
+ type_hint=int,
416
+ default=16,
417
+ description="Context frames decoded on each side of a tile and cropped away.",
418
+ ),
419
+ InputParam("cancelled", type_hint=object, default=None, description="Cancellation callable."),
420
+ InputParam(
421
+ "on_progress", type_hint=object, default=None, description="Callback(stage, completed, total)."
422
+ ),
423
+ ]
424
+
425
+ @property
426
+ def intermediate_outputs(self):
427
+ return [
428
+ OutputParam("audios", type_hint=torch.Tensor, description="Stereo audio [1, 2, samples], CPU FP32."),
429
+ OutputParam("sample_rate", type_hint=int, description="Sample rate in Hz."),
430
+ ]
431
+
432
+ @torch.inference_mode()
433
+ def __call__(self, components, state):
434
+ block_state = self.get_block_state(state)
435
+ check_cancelled(block_state.cancelled)
436
+ vae = components.vae
437
+ device = components._execution_device
438
+ latents = torch.as_tensor(block_state.latents, dtype=torch.float32)
439
+ if latents.ndim == 2:
440
+ latents = latents.T.unsqueeze(0)
441
+ frames = latents.shape[-1]
442
+ tile, overlap = block_state.vae_tile_frames, block_state.vae_tile_overlap_frames
443
+ if tile < 1 or overlap < 0:
444
+ raise ValueError("vae_tile_frames must be positive and vae_tile_overlap_frames nonnegative")
445
+ pieces = []
446
+ starts = range(0, frames, tile)
447
+ with torch.backends.cudnn.flags(enabled=True, benchmark=False, deterministic=True, allow_tf32=False):
448
+ for index, start in enumerate(starts):
449
+ check_cancelled(block_state.cancelled)
450
+ end = min(frames, start + tile)
451
+ left, right = max(0, start - overlap), min(frames, end + overlap)
452
+ decoded = vae.decode(latents[..., left:right].to(device=device, dtype=torch.float32)).sample
453
+ crop = (start - left) * vae.hop_length
454
+ if end < frames:
455
+ decoded = decoded[..., crop : crop + (end - start) * vae.hop_length]
456
+ else:
457
+ decoded = decoded[..., crop:]
458
+ pieces.append(decoded.cpu())
459
+ if block_state.on_progress is not None:
460
+ block_state.on_progress("decode", index + 1, len(starts))
461
+ audio = torch.cat(pieces, dim=-1)
462
+ if not torch.isfinite(audio).all():
463
+ raise FloatingPointError("Decoded audio is non-finite")
464
+ block_state.audios = audio.clamp(-1, 1)
465
+ block_state.sample_rate = vae.config.sampling_rate
466
+ self.set_block_state(state, block_state)
467
+ return components, state
468
+
469
+
470
+ class YuE2Blocks(SequentialPipelineBlocks):
471
+ model_name = "yue2"
472
+ block_names = ["prepare", "plan", "semantic", "synthesize", "decode"]
473
+ block_classes = [YuE2PrepareInputsStep, YuE2PlanStep, YuE2SemanticStep, YuE2SynthesizeStep, YuE2DecodeStep]
474
+
475
+ def __init__(self, components_repo=None, components_revision=None, trust_components_code=False):
476
+ super().__init__()
477
+ self.components_repo = components_repo
478
+ self.components_revision = components_revision
479
+ self.component_types = {}
480
+ if components_repo is not None:
481
+ if not trust_components_code:
482
+ raise ValueError("Set trust_components_code=True to load code from the model repository")
483
+ for name, class_name in (
484
+ ("transformer", "YuE2TransformerModel"),
485
+ ("vae", "YuE2VAE"),
486
+ ("tokenizer", "YuE2Tokenizer"),
487
+ ):
488
+ self.component_types[name] = get_class_from_dynamic_module(
489
+ components_repo,
490
+ module_file=f"{name}.py",
491
+ class_name=class_name,
492
+ revision=components_revision,
493
+ trust_remote_code=True,
494
+ )
495
+
496
+ @property
497
+ def expected_components(self):
498
+ specs = super().expected_components
499
+ if self.components_repo is None:
500
+ return specs
501
+ return [
502
+ replace(
503
+ spec,
504
+ type_hint=self.component_types[spec.name],
505
+ pretrained_model_name_or_path=str(self.components_repo),
506
+ subfolder=spec.name,
507
+ revision=self.components_revision,
508
+ )
509
+ if spec.default_creation_method == "from_pretrained"
510
+ else spec
511
+ for spec in specs
512
+ ]
513
+
514
+ @property
515
+ def description(self):
516
+ return "YuE2 score planning, semantic generation, midpoint synthesis, and stereo decoding."
cuda_graph.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted for diffusers from multimodal-art-projection/YuE at commit ef1936f2ee39fe8de486a0f47a481c95f8d4da87.
2
+ # Licensed under Apache-2.0; see LICENSE.
3
+ """CUDA-graph token decoding for one request or its two guidance branches.
4
+
5
+ The graph replays the transformer's token stream for one new token per branch. The caller combines the branch logits,
6
+ samples once and passes that token to `step`. Each branch keeps its own RoPE positions and cache slots.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import torch
12
+
13
+
14
+ def apply_rotary_emb(hidden_states, cos, sin):
15
+ # Same arithmetic as `apply_rotary_emb` in the model repository's transformer.py.
16
+ half = hidden_states.shape[-1] // 2
17
+ x1, x2 = hidden_states[..., :half], hidden_states[..., half:]
18
+ cos, sin = cos.to(hidden_states.dtype), sin.to(hidden_states.dtype)
19
+ return torch.cat([x1 * cos - x2 * sin, x2 * cos + x1 * sin], dim=-1)
20
+
21
+
22
+ class _BranchPrefillCache:
23
+ """Writes one branch's prompt keys and values into the graph's sequence-major buffers."""
24
+
25
+ def __init__(self, keys, values, branch):
26
+ self.keys = [buffer[branch : branch + 1] for buffer in keys]
27
+ self.values = [buffer[branch : branch + 1] for buffer in values]
28
+ self.seen_tokens = 0
29
+
30
+ def get_seq_length(self):
31
+ return self.seen_tokens
32
+
33
+ def update(self, key, value, layer_idx):
34
+ end = self.seen_tokens + key.shape[1]
35
+ if end > self.keys[layer_idx].shape[1]:
36
+ raise ValueError("Prefix exceeds preallocated KV capacity")
37
+ self.keys[layer_idx][:, self.seen_tokens : end].copy_(key)
38
+ self.values[layer_idx][:, self.seen_tokens : end].copy_(value)
39
+ if layer_idx == len(self.keys) - 1:
40
+ self.seen_tokens = end
41
+ return self.keys[layer_idx][:, :end], self.values[layer_idx][:, :end]
42
+
43
+
44
+ class GraphAR:
45
+ """Fixed-capacity decoding. `prefill()` returns the first logits; up to `max_tokens - 1` `step(token)` calls follow.
46
+
47
+ Returned logits share output storage and stay valid until the next step.
48
+ """
49
+
50
+ def __init__(self, transformer, prefixes, max_tokens, device):
51
+ if transformer.training:
52
+ raise ValueError("GraphAR requires transformer.eval()")
53
+ if not 1 <= len(prefixes) <= 2:
54
+ raise ValueError("GraphAR supports one request or exactly two guidance branches")
55
+ if device.type != "cuda":
56
+ raise ValueError("CUDA graphs require a CUDA device")
57
+ try:
58
+ from diffusers.hooks.group_offloading import _get_group_onload_device
59
+
60
+ _get_group_onload_device(transformer)
61
+ except ValueError:
62
+ pass
63
+ else:
64
+ raise ValueError(
65
+ "CUDA graphs cannot capture a group-offloaded transformer: streamed weights change address between "
66
+ "steps. Pass use_cuda_graph=False, or keep the transformer resident on the GPU."
67
+ )
68
+ config = transformer.config
69
+ prefixes = [[int(token) for token in prefix] for prefix in prefixes]
70
+ for prefix in prefixes:
71
+ if not prefix or not all(0 <= token < config.vocab_size for token in prefix):
72
+ raise ValueError("Prefixes require valid token IDs")
73
+ if len(prefix) + max_tokens > config.max_position_embeddings:
74
+ raise ValueError("Prefix plus generation budget exceeds model context; no length was shortened")
75
+ self.transformer, self.device, self.dtype = transformer, device, transformer.dtype
76
+ self.prefixes, self.max_tokens, self.branches = prefixes, int(max_tokens), len(prefixes)
77
+ self.capacity = max(map(len, prefixes)) + self.max_tokens
78
+ self.graph = self.output = None
79
+ # PyTorch's variable-length FlashAttention takes per-branch key lengths on the GPU, so no mask is built.
80
+ flash = (
81
+ self.dtype in {torch.bfloat16, torch.float16}
82
+ and config.attention_head_dim % 8 == 0
83
+ and config.attention_head_dim <= 256
84
+ and "seqused_k" in str(torch.ops.aten._flash_attention_forward.default._schema)
85
+ )
86
+ if not flash:
87
+ raise ValueError(
88
+ "CUDA graphs need PyTorch's variable-length FlashAttention with a BF16/FP16 transformer; "
89
+ "pass use_cuda_graph=False."
90
+ )
91
+ self.ready, self.closed, self.steps = False, False, 0
92
+ # Sequence-major buffers make the packed FlashAttention view contiguous without copying the cache each step.
93
+ shape = (self.branches, self.capacity, config.num_key_value_heads, config.attention_head_dim)
94
+ self.keys = [torch.zeros(shape, device=device, dtype=self.dtype) for _ in range(config.num_layers)]
95
+ self.values = [torch.zeros(shape, device=device, dtype=self.dtype) for _ in range(config.num_layers)]
96
+ self.positions = torch.tensor([len(prefix) for prefix in prefixes], dtype=torch.long, device=device)
97
+ self.initial_positions = self.positions.clone()
98
+ self.cu_q = torch.arange(self.branches + 1, dtype=torch.int32, device=device)
99
+ self.cu_k = self.cu_q * self.capacity
100
+ self.tokens = torch.tensor([[prefix[-1]] for prefix in prefixes], dtype=torch.long, device=device)
101
+
102
+ @torch.inference_mode()
103
+ def _decode(self):
104
+ transformer = self.transformer
105
+ config = transformer.config
106
+ heads, kv_heads, head_dim = config.num_attention_heads, config.num_key_value_heads, config.attention_head_dim
107
+ cos, sin = transformer.rotary_emb(self.positions[:, None])
108
+ cos, sin = cos.unsqueeze(2), sin.unsqueeze(2)
109
+ hidden_states = transformer.embed_tokens(self.tokens)
110
+ used_lengths = (self.positions + 1).to(torch.int32)
111
+ slots = self.positions[:, None, None, None].expand(self.branches, 1, kv_heads, head_dim)
112
+ for block, keys, values in zip(transformer.transformer_blocks, self.keys, self.values):
113
+ attn = block.attn
114
+ normalized = block.norm1(hidden_states)
115
+ query = attn.norm_q(attn.to_q(normalized).view(self.branches, 1, heads, head_dim))
116
+ key = attn.norm_k(attn.to_k(normalized).view(self.branches, 1, kv_heads, head_dim))
117
+ value = attn.to_v(normalized).view(self.branches, 1, kv_heads, head_dim)
118
+ query = apply_rotary_emb(query, cos, sin)
119
+ key = apply_rotary_emb(key, cos, sin)
120
+ keys.scatter_(1, slots, key)
121
+ values.scatter_(1, slots, value)
122
+ # Every slot is allocated, but only each branch's filled prefix and current token are visible.
123
+ # The packed variable-length entry point respects seqused_k; the 4D fixed-batch one ignores it.
124
+ attn_output = torch.ops.aten._flash_attention_forward(
125
+ query[:, 0],
126
+ keys.view(-1, kv_heads, head_dim),
127
+ values.view(-1, kv_heads, head_dim),
128
+ self.cu_q,
129
+ self.cu_k,
130
+ 1,
131
+ self.capacity,
132
+ 0.0,
133
+ False,
134
+ False,
135
+ seqused_k=used_lengths,
136
+ )[0][:, None]
137
+ hidden_states = hidden_states + attn.to_out[0](attn_output.reshape(self.branches, 1, -1))
138
+ hidden_states = hidden_states + block.ff(block.norm2(hidden_states))
139
+ output = transformer.lm_head(transformer.norm_out(hidden_states))[:, 0]
140
+ self.positions.add_(1)
141
+ return output
142
+
143
+ @torch.inference_mode()
144
+ def _capture(self):
145
+ with torch.cuda.device(self.device):
146
+ current = torch.cuda.current_stream(self.device)
147
+ warmup = torch.cuda.Stream(device=self.device)
148
+ warmup.wait_stream(current)
149
+ with torch.cuda.stream(warmup):
150
+ for _ in range(3):
151
+ self.positions.copy_(self.initial_positions)
152
+ self._decode()
153
+ self.positions.copy_(self.initial_positions)
154
+ current.wait_stream(warmup)
155
+ torch.cuda.synchronize(self.device)
156
+ self.graph = torch.cuda.CUDAGraph()
157
+ with torch.cuda.graph(self.graph):
158
+ self.output = self._decode()
159
+ # Warmup and capture wrote one future slot; the first real step overwrites it before it becomes visible.
160
+ self.positions.copy_(self.initial_positions)
161
+
162
+ @torch.inference_mode()
163
+ def prefill(self):
164
+ if self.closed or self.ready:
165
+ raise RuntimeError("prefill must be called exactly once on an open GraphAR")
166
+ logits = []
167
+ for branch, prefix in enumerate(self.prefixes):
168
+ cache = _BranchPrefillCache(self.keys, self.values, branch)
169
+ ids = torch.tensor([prefix], dtype=torch.long, device=self.device)
170
+ logits.append(self.transformer(ids, kv_cache=cache, logits_to_keep=1).logits[:, -1])
171
+ if any(param.device != self.device for param in self.transformer.parameters()):
172
+ raise ValueError("CUDA graphs need the whole transformer resident on the GPU; pass use_cuda_graph=False.")
173
+ if self.max_tokens > 1:
174
+ self._capture()
175
+ self.ready = True
176
+ return torch.cat(logits, dim=0)
177
+
178
+ @torch.inference_mode()
179
+ def step(self, token):
180
+ if self.closed or not self.ready:
181
+ raise RuntimeError("Call prefill before step and do not use a closed GraphAR")
182
+ if self.steps >= self.max_tokens - 1:
183
+ raise ValueError("Requested generation budget is exhausted")
184
+ self.tokens.copy_(token.reshape(1, 1).expand(self.branches, 1))
185
+ self.graph.replay()
186
+ self.steps += 1
187
+ return self.output
188
+
189
+ def close(self):
190
+ self.graph = self.output = None
191
+ self.keys.clear()
192
+ self.values.clear()
193
+ self.tokens = self.positions = self.initial_positions = None
194
+ self.cu_q = self.cu_k = None
195
+ self.closed = True
guidance.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+
3
+ from diffusers.configuration_utils import ConfigMixin, register_to_config
4
+
5
+
6
+ class YuE2SemanticGuider(ConfigMixin):
7
+ config_name = "guider_config.json"
8
+
9
+ @register_to_config
10
+ def __init__(self, scale=None):
11
+ if scale is not None and (not math.isfinite(scale) or not 0 <= scale <= 20):
12
+ raise ValueError("Semantic guidance must be finite and in [0,20]")
13
+
14
+ def scale_for(self, cot):
15
+ return (1.01 if cot == "off" else 1.0) if self.config.scale is None else self.config.scale
16
+
17
+ def combine(self, conditional, unconditional, cot):
18
+ scale = self.scale_for(cot)
19
+ return conditional if scale == 1.0 else unconditional + scale * (conditional - unconditional)
modular_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "YuE2Blocks",
3
+ "_diffusers_version": "0.41.0.dev0",
4
+ "auto_map": {
5
+ "ModularPipelineBlocks": "blocks.YuE2Blocks"
6
+ },
7
+ "requirements": {
8
+ "tiktoken": ">=0.12.0"
9
+ }
10
+ }
modular_model_index.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "_class_name": "ModularPipeline",
3
+ "_blocks_class_name": "YuE2Blocks"
4
+ }
nar.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted for diffusers from multimodal-art-projection/YuE at commit ef1936f2ee39fe8de486a0f47a481c95f8d4da87.
2
+ # Licensed under Apache-2.0; see LICENSE.
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from numbers import Integral
8
+
9
+ import torch
10
+
11
+ from .protocol import CODEC_OFFSET, CODEC_SIZE, CONTEXT, MUSIC_END, chunk_ranges
12
+
13
+
14
+ @dataclass
15
+ class Chunk:
16
+ ar_tokens: list[int]
17
+ noise: torch.Tensor
18
+
19
+
20
+ class YuE2PrefixKVCache:
21
+ """Holds a chunk's token-prefix keys and values; later acoustic calls attend to the prefix without extending it."""
22
+
23
+ def __init__(self):
24
+ self.keys, self.values = [], []
25
+
26
+ def get_seq_length(self):
27
+ return self.keys[0].shape[1] if self.keys else 0
28
+
29
+ def update(self, key, value, layer_idx):
30
+ if layer_idx == len(self.keys):
31
+ self.keys.append(key)
32
+ self.values.append(value)
33
+ return key, value
34
+ return torch.cat((self.keys[layer_idx], key), dim=1), torch.cat((self.values[layer_idx], value), dim=1)
35
+
36
+
37
+ def _integers(values, name):
38
+ result = list(values)
39
+ if not result or any(isinstance(v, bool) or not isinstance(v, Integral) for v in result):
40
+ raise ValueError(f"{name} must be a nonempty sequence of integer token IDs")
41
+ return [int(v) for v in result]
42
+
43
+
44
+ def song_chunks(prefix, codec, seed, context=CONTEXT):
45
+ """Draw the whole song's CPU FP32 noise once, then split it at the release's chunk boundaries."""
46
+ prefix = _integers(prefix, "prefix")
47
+ codec = _integers(codec, "codec")
48
+ if min(prefix) < 0 or min(codec) < 0 or max(codec) >= CODEC_SIZE:
49
+ raise ValueError("Token IDs are outside their allowed vocabulary")
50
+ if isinstance(context, bool) or not isinstance(context, Integral) or not 1 <= context <= CONTEXT:
51
+ raise ValueError(f"context must be an integer in 1..{CONTEXT}")
52
+ ranges = chunk_ranges(len(codec), len(prefix), int(context))
53
+ generator = torch.Generator(device="cpu").manual_seed(int(seed))
54
+ noise = torch.randn((len(codec), 64), dtype=torch.float32, device="cpu", generator=generator)
55
+ return [
56
+ Chunk(prefix + [value + CODEC_OFFSET for value in codec[a:b]] + [MUSIC_END], noise[a:b]) for a, b in ranges
57
+ ]
58
+
59
+
60
+ @torch.inference_mode()
61
+ def solve_midpoint(
62
+ transformer,
63
+ kv_cache,
64
+ noise,
65
+ device,
66
+ steps=32,
67
+ cancelled: Callable[[], bool] | None = None,
68
+ on_progress: Callable[[int, int], None] | None = None,
69
+ ):
70
+ """Integrate the flow from t=1 (noise) to t=0 with the midpoint method; returns CPU FP32 [frames, 64] latents."""
71
+ if isinstance(steps, bool) or not isinstance(steps, Integral) or steps < 1:
72
+ raise ValueError("steps must be a positive integer")
73
+ if not torch.isfinite(noise).all():
74
+ raise ValueError("Acoustic noise contains non-finite values")
75
+
76
+ def velocity(state, t):
77
+ # The model takes time in logit space; logit(1) is clamped to 20.
78
+ raw = torch.logit(torch.tensor(t, dtype=torch.float64, device="cpu")).clamp(-20, 20).item()
79
+ return transformer(latents=state[None], timestep=raw, kv_cache=kv_cache).sample[0]
80
+
81
+ state = noise.to(device=device, dtype=transformer.dtype)
82
+ dt = 1.0 / steps
83
+ for step in range(steps):
84
+ if cancelled is not None and cancelled():
85
+ raise InterruptedError("Cancelled during acoustic flow matching")
86
+ t = 1.0 - step * dt
87
+ mid = state - velocity(state, t) * (dt / 2)
88
+ if cancelled is not None and cancelled():
89
+ raise InterruptedError("Cancelled during acoustic flow matching")
90
+ state = state - velocity(mid, t - dt / 2) * dt
91
+ if on_progress is not None:
92
+ on_progress(step + 1, int(steps))
93
+ result = state.float().cpu()
94
+ if not torch.isfinite(result).all():
95
+ raise FloatingPointError("Acoustic flow matching produced non-finite latents")
96
+ return result
protocol.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted for diffusers from multimodal-art-projection/YuE at commit ef1936f2ee39fe8de486a0f47a481c95f8d4da87.
2
+ # Licensed under Apache-2.0; see LICENSE.
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from dataclasses import asdict, dataclass
7
+
8
+ EOD = 151643
9
+ ABC_START, ABC_END = (151847, 151848)
10
+ MUSIC_START, MUSIC_END = (151851, 151852)
11
+ CODEC_OFFSET, CODEC_SIZE = (151853, 32768)
12
+ CONTEXT = 24576
13
+ INSTRUCTIONS = {
14
+ "off": "Generate music with codec tokens from the given conditions.",
15
+ "melody": "Generate a melody-only ABC transcription without chord symbols, then generate music with codec tokens from the given conditions.",
16
+ "full": "Generate a chord-annotated ABC transcription, then generate music with codec tokens from the given conditions.",
17
+ }
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class Sampling:
22
+ temperature: float = 1.0
23
+ top_p: float = 0.95
24
+ top_k: int = 100
25
+ repetition_penalty: float = 1.2
26
+ penalty_window: int = 50
27
+ min_tokens: int = 200
28
+ max_tokens: int = 9000
29
+
30
+ def __post_init__(self):
31
+ if any(type(x) is not int for x in (self.top_k, self.penalty_window, self.min_tokens, self.max_tokens)):
32
+ raise ValueError("Sampling counts must be integers")
33
+ if not all(math.isfinite(x) for x in (self.temperature, self.top_p, self.repetition_penalty)):
34
+ raise ValueError("Sampling numbers must be finite")
35
+ if not 0 <= self.temperature <= 5 or not 0 < self.top_p <= 1 or self.top_k < 1:
36
+ raise ValueError("Invalid sampling temperature/top_p/top_k")
37
+ if self.repetition_penalty <= 0 or not 1 <= self.penalty_window <= 100:
38
+ raise ValueError("Invalid repetition penalty/window")
39
+ if not 0 <= self.min_tokens <= self.max_tokens or self.max_tokens < 1:
40
+ raise ValueError("Require 0 <= min_tokens <= max_tokens")
41
+
42
+
43
+ ABC_SAMPLING = Sampling(0.7, 0.9, 30, 1.005, 100, 32, 4096)
44
+ SEMANTIC_SAMPLING = Sampling()
45
+
46
+
47
+ def resolve_sampling(overrides, default):
48
+ if overrides is None:
49
+ return default
50
+ if not isinstance(overrides, dict):
51
+ raise TypeError("Sampling overrides must be a dictionary")
52
+ return Sampling(**{**asdict(default), **overrides})
53
+
54
+
55
+ @dataclass(frozen=True)
56
+ class SongRequest:
57
+ style: str
58
+ lyrics: str
59
+ cot: str = "full"
60
+ seed: int = 831001
61
+ abc: str | None = None
62
+
63
+ def __post_init__(self):
64
+ if self.cot not in INSTRUCTIONS:
65
+ raise ValueError("cot must be off, melody or full")
66
+ if not isinstance(self.style, str) or not isinstance(self.lyrics, str):
67
+ raise TypeError("style and lyrics must be strings")
68
+ if type(self.seed) is not int or not 0 <= self.seed < 2**63:
69
+ raise ValueError("seed must be an integer in [0, 2**63)")
70
+ if self.abc is not None and (self.cot == "off" or not isinstance(self.abc, str) or not self.abc.strip()):
71
+ raise ValueError("External ABC requires nonempty text and cot=melody/full")
72
+
73
+ def text(self):
74
+ return f"{INSTRUCTIONS[self.cot]}\n[Tags]\n{self.style}\n[Lyrics]\n{self.lyrics}\n"
75
+
76
+
77
+ def _check_abc_ids(abc_ids):
78
+ abc_ids = list(abc_ids)
79
+ if any(type(token) is not int or not 0 <= token < EOD for token in abc_ids):
80
+ raise ValueError("ABC IDs must remain inside the ordinary text vocabulary")
81
+ return abc_ids
82
+
83
+
84
+ def token_prefixes(request, tokenizer, abc_ids=None):
85
+ base = [EOD] + tokenizer.encode(request.text())
86
+ if request.cot == "off":
87
+ return base + [ABC_START, ABC_END, MUSIC_START]
88
+ if abc_ids is None:
89
+ if request.abc is None:
90
+ return base + [ABC_START]
91
+ abc_ids = tokenizer.encode(request.abc)
92
+ return base + [ABC_START] + _check_abc_ids(abc_ids) + [ABC_END, MUSIC_START]
93
+
94
+
95
+ def negative_prefix(request, tokenizer, abc_ids=None):
96
+ base = [EOD] + tokenizer.encode(INSTRUCTIONS[request.cot])
97
+ if request.cot == "off":
98
+ return base + [MUSIC_START]
99
+ if abc_ids is None:
100
+ raise ValueError("Symbolic CFG must retain the exact positive-branch ABC IDs")
101
+ return base + [ABC_START] + _check_abc_ids(abc_ids) + [ABC_END, MUSIC_START]
102
+
103
+
104
+ def chunk_ranges(frames, prefix_tokens, context=CONTEXT):
105
+ size = min((context - prefix_tokens - 3) // 2, CONTEXT)
106
+ if frames < 1 or size < 1:
107
+ raise ValueError("Empty codec or prefix leaves no acoustic context")
108
+ return [(a, min(a + size, frames)) for a in range(0, frames, size)]
sampling.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Adapted for diffusers from multimodal-art-projection/YuE at commit ef1936f2ee39fe8de486a0f47a481c95f8d4da87.
2
+ # Licensed under Apache-2.0; see LICENSE.
3
+ from __future__ import annotations
4
+
5
+ import time
6
+
7
+ import torch
8
+
9
+ from .protocol import ABC_END, CODEC_OFFSET, CODEC_SIZE, CONTEXT, EOD, MUSIC_END
10
+
11
+
12
+ class YuE2StaticKVCache:
13
+ """Preallocated token cache for eager decoding; returns views of the filled prefix without copying history."""
14
+
15
+ def __init__(self, num_layers, batch_size, num_kv_heads, max_seq_len, head_dim, dtype, device):
16
+ self.num_layers = num_layers
17
+ self.max_seq_len = max_seq_len
18
+ self.seen_tokens = 0
19
+ shape = (batch_size, num_kv_heads, max_seq_len, head_dim)
20
+ self.keys = [torch.zeros(shape, dtype=dtype, device=device) for _ in range(num_layers)]
21
+ self.values = [torch.zeros(shape, dtype=dtype, device=device) for _ in range(num_layers)]
22
+
23
+ def get_seq_length(self):
24
+ return self.seen_tokens
25
+
26
+ def update(self, key, value, layer_idx):
27
+ start, end = self.seen_tokens, self.seen_tokens + key.shape[1]
28
+ if end > self.max_seq_len:
29
+ raise ValueError(f"KV cache capacity {self.max_seq_len} exceeded by {end}; generation was not shortened")
30
+ self.keys[layer_idx][:, :, start:end] = key.transpose(1, 2)
31
+ self.values[layer_idx][:, :, start:end] = value.transpose(1, 2)
32
+ if layer_idx == self.num_layers - 1:
33
+ self.seen_tokens = end
34
+ return self.keys[layer_idx][:, :, :end].transpose(1, 2), self.values[layer_idx][:, :, :end].transpose(1, 2)
35
+
36
+
37
+ def synchronize(device):
38
+ if device.type == "cuda":
39
+ torch.cuda.synchronize(device)
40
+ elif device.type == "mps":
41
+ torch.mps.synchronize()
42
+
43
+
44
+ def window_penalty(logits, recent_ids, penalty):
45
+ if penalty == 1.0 or len(recent_ids) == 0:
46
+ return logits
47
+ recent = torch.as_tensor(recent_ids, dtype=torch.long, device=logits.device).reshape(1, -1)
48
+ freq = torch.zeros_like(logits)
49
+ freq.scatter_add_(-1, recent, torch.ones_like(recent, dtype=logits.dtype))
50
+ alpha = penalty**freq
51
+ return torch.where(logits < 0, logits * alpha, logits / alpha)
52
+
53
+
54
+ def distribution(logits, sampling, history, step, phase, legacy_off=False):
55
+ # Planning-off requests keep the release's BF16 logits and top-3 floor; other modes sample from FP32 logits.
56
+ scores = logits.clone() if legacy_off else logits.float().clone()
57
+ end = ABC_END if phase == "abc" else MUSIC_END
58
+ allowed = torch.full_like(scores, float("-inf"))
59
+ if phase == "abc":
60
+ allowed[..., :EOD] = 0
61
+ else:
62
+ allowed[..., CODEC_OFFSET : CODEC_OFFSET + CODEC_SIZE] = 0
63
+ allowed[..., end] = 0
64
+ scores = scores + allowed
65
+ if step < sampling.min_tokens:
66
+ scores[..., end] = -torch.inf
67
+ scores = window_penalty(scores, history[-sampling.penalty_window :], sampling.repetition_penalty)
68
+ if sampling.temperature == 0:
69
+ return scores
70
+ if sampling.temperature != 1:
71
+ scores = scores / sampling.temperature
72
+ threshold = scores.topk(min(sampling.top_k, scores.shape[-1])).values[..., -1, None]
73
+ scores = scores.masked_fill(scores < threshold, -torch.inf)
74
+ if sampling.top_p < 1:
75
+ values, indices = scores.sort(descending=True)
76
+ probabilities = values.softmax(-1)
77
+ removed = probabilities.cumsum(-1) - probabilities > sampling.top_p
78
+ removed[..., : 3 if legacy_off else 1] = False
79
+ values = values.masked_fill(removed, -torch.inf)
80
+ scores = values.scatter(-1, indices, values)
81
+ return scores
82
+
83
+
84
+ @torch.inference_mode()
85
+ def generate_tokens(
86
+ transformer,
87
+ prefix,
88
+ sampling,
89
+ seed,
90
+ phase,
91
+ device,
92
+ negative=None,
93
+ combine_logits=None,
94
+ legacy_off=False,
95
+ cancelled=None,
96
+ on_token=None,
97
+ graph_decoder=None,
98
+ ):
99
+ """Sample one stage's tokens.
100
+
101
+ With `negative`, `combine_logits(conditional, unconditional)` applies guidance. `graph_decoder` (the `GraphAR`
102
+ class) decodes with CUDA graphs on CUDA devices; elsewhere decoding stays eager.
103
+ """
104
+ if len(prefix) + sampling.max_tokens > CONTEXT:
105
+ raise ValueError("Prefix + requested generation budget exceeds 24576; no implicit truncation")
106
+ if negative is not None and (combine_logits is None or len(negative) + sampling.max_tokens > CONTEXT):
107
+ raise ValueError("Guidance needs `combine_logits` and a negative prefix that fits the context")
108
+ if cancelled is not None and cancelled():
109
+ raise InterruptedError("Cancelled before prefill")
110
+ # Both stages reset the request seed, as the release does.
111
+ rng_device = device if device.type in {"cpu", "cuda"} else torch.device("cpu")
112
+ generator = torch.Generator(device=rng_device).manual_seed(seed)
113
+ config = transformer.config
114
+
115
+ def prefill(ids):
116
+ cache = YuE2StaticKVCache(
117
+ num_layers=config.num_layers,
118
+ batch_size=1,
119
+ num_kv_heads=config.num_key_value_heads,
120
+ max_seq_len=len(ids) + sampling.max_tokens,
121
+ head_dim=config.attention_head_dim,
122
+ dtype=transformer.dtype,
123
+ device=device,
124
+ )
125
+ logits = transformer(torch.tensor([ids], device=device), kv_cache=cache, logits_to_keep=1).logits
126
+ return logits[:, -1, :], cache
127
+
128
+ graph = None
129
+ positive_cache = negative_cache = None
130
+ synchronize(device)
131
+ start = time.perf_counter()
132
+ try:
133
+ if graph_decoder is not None and device.type == "cuda":
134
+ graph = graph_decoder(
135
+ transformer, [prefix] if negative is None else [prefix, negative], sampling.max_tokens, device
136
+ )
137
+ logits = graph.prefill()
138
+ conditional = logits[:1]
139
+ unconditional = logits[1:] if negative is not None else None
140
+ else:
141
+ conditional, positive_cache = prefill(prefix)
142
+ unconditional = None
143
+ if negative is not None:
144
+ unconditional, negative_cache = prefill(negative)
145
+ synchronize(device)
146
+ prefill_seconds = time.perf_counter() - start
147
+ history, first, eos = [], None, False
148
+ end = ABC_END if phase == "abc" else MUSIC_END
149
+ for step in range(sampling.max_tokens):
150
+ if cancelled is not None and cancelled():
151
+ raise InterruptedError(f"Cancelled during {phase}")
152
+ logits = conditional if negative is None else combine_logits(conditional, unconditional)
153
+ scores = distribution(logits, sampling, history, step, phase, legacy_off)
154
+ if sampling.temperature == 0:
155
+ next_id = scores.argmax(-1, keepdim=True)
156
+ else:
157
+ probabilities = scores.softmax(-1)
158
+ if device.type == "mps":
159
+ next_id = torch.multinomial(probabilities.cpu(), 1, generator=generator).to(device)
160
+ else:
161
+ next_id = torch.multinomial(probabilities, 1, generator=generator)
162
+ token = int(next_id.item())
163
+ if first is None:
164
+ first = time.perf_counter() - start
165
+ if on_token is not None:
166
+ on_token(phase, token)
167
+ if token == end:
168
+ eos = True
169
+ break
170
+ history.append(token)
171
+ if step + 1 < sampling.max_tokens:
172
+ if graph is not None:
173
+ branch_logits = graph.step(next_id)
174
+ conditional = branch_logits[:1]
175
+ unconditional = branch_logits[1:] if negative is not None else None
176
+ else:
177
+ conditional = transformer(next_id, kv_cache=positive_cache, logits_to_keep=1).logits[:, -1, :]
178
+ if negative_cache is not None:
179
+ unconditional = transformer(next_id, kv_cache=negative_cache, logits_to_keep=1).logits[
180
+ :, -1, :
181
+ ]
182
+ synchronize(device)
183
+ seconds = time.perf_counter() - start
184
+ count = len(history) + int(eos)
185
+ timing = {
186
+ "seconds": seconds,
187
+ "prefill_seconds": prefill_seconds,
188
+ "ttft_seconds": first,
189
+ "output_tokens": count,
190
+ "content_tokens": len(history),
191
+ "output_tps": count / seconds,
192
+ "prefix_tokens": len(prefix),
193
+ "cfg_branches": 1 if negative is None else 2,
194
+ "execution": "cuda_graph" if graph is not None else "eager",
195
+ }
196
+ return history, timing, not eos
197
+ finally:
198
+ if graph is not None:
199
+ graph.close()
200
+ positive_cache = negative_cache = None