owensong commited on
Commit
ecfc07b
·
verified ·
1 Parent(s): 9f9c80c

Launch complete Breeze TTS 2 ZeroGPU studio

Browse files
.gitattributes CHANGED
@@ -1,35 +1,7 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
  *.pt filter=lfs diff=lfs merge=lfs -text
23
  *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
  *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
1
  *.bin filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  *.pt filter=lfs diff=lfs merge=lfs -text
3
  *.pth filter=lfs diff=lfs merge=lfs -text
 
4
  *.safetensors filter=lfs diff=lfs merge=lfs -text
5
+ *.wav filter=lfs diff=lfs merge=lfs -text
6
+ *.mp3 filter=lfs diff=lfs merge=lfs -text
7
+ *.flac filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
MODEL_LICENSE ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ BREEZEBLUE RESEARCH AND NON-COMMERCIAL LICENSE AGREEMENT
2
+
3
+ Version 1.0
4
+ Last Updated: August 25, 2026
5
+
6
+ This BreezeBlue Research and Non-Commercial License Agreement (the
7
+ "Agreement") is entered into between RESONIA, INC., doing business as
8
+ BreezeBlue ("BreezeBlue," "Licensor," "we," or "us"), and the individual or
9
+ entity accessing, using, modifying, or distributing any Model Materials
10
+ ("You," "Your," or "Licensee").
11
+
12
+ By accessing, downloading, using, modifying, or distributing any portion of
13
+ the Model Materials or a Derivative Model, You acknowledge that You have read,
14
+ understood, and agreed to this Agreement. If You act on behalf of an entity,
15
+ You represent that You have authority to bind that entity, and "You" includes
16
+ both You and that entity.
17
+
18
+ This Agreement permits research and non-commercial use of the Model Materials
19
+ free of charge. It does not grant any commercial rights and is not an open
20
+ source license. Commercial use requires a separate written license from
21
+ BreezeBlue.
22
+
23
+
24
+ 1. DEFINITIONS
25
+
26
+ 1.1 "Model" means the Breeze TTS 2 model.
27
+
28
+ 1.2 "Model Materials" means the Model weights, checkpoints, parameters,
29
+ adapters, model-specific tokenizer or codec weights, embeddings, and other
30
+ machine-learning artifacts that BreezeBlue expressly makes available under
31
+ this Agreement, together with any accompanying documentation expressly
32
+ designated as part of the Model Materials.
33
+
34
+ Model Materials do not include source code expressly licensed under the
35
+ Apache License, Version 2.0, or any third-party component made available under
36
+ its own license.
37
+
38
+ 1.3 "Derivative Model" means any modification, adaptation, fine-tune, LoRA,
39
+ merge, quantization, distillation, or other model that is based on or derived
40
+ from the Model Materials, including a model created using the weights,
41
+ parameters, activations, embeddings, or Outputs of the Model Materials to
42
+ replicate or materially transfer their speech or audio generation
43
+ capabilities. Outputs themselves are not Derivative Models.
44
+
45
+ 1.4 "Output" means audio, speech, text, metadata, or other content generated
46
+ by the Model Materials or a Derivative Model in response to an input.
47
+
48
+ 1.5 "Research Purpose" means academic or scientific research that is not
49
+ primarily intended for commercial advantage or monetary compensation,
50
+ including peer-reviewed research, reproducibility studies, benchmarking,
51
+ evaluation, security testing, and red-teaming.
52
+
53
+ 1.6 "Non-Commercial Purpose" means personal, educational, hobbyist, or other
54
+ use that is not primarily intended for commercial advantage or monetary
55
+ compensation, including limited evaluation and testing.
56
+
57
+ 1.7 "Commercial Purpose" means any use primarily intended for or directed
58
+ toward commercial advantage, business benefit, or monetary compensation,
59
+ whether direct or indirect. Commercial Purpose includes, without limitation:
60
+
61
+ (a) production use or use in a product, service, or business operation;
62
+
63
+ (b) hosting or making the Model Materials, a Derivative Model, or their
64
+ functionality available through an API, SaaS offering, application, plug-in,
65
+ website, or other service;
66
+
67
+ (c) creating or providing content, products, or services for a fee, for a
68
+ client, in connection with advertising, sponsorship, subscription revenue,
69
+ or any other monetized activity;
70
+
71
+ (d) using the Model Materials, a Derivative Model, or Outputs for an
72
+ organization's internal operations beyond limited evaluation; or
73
+
74
+ (e) distributing, licensing, selling, or otherwise commercializing the Model
75
+ Materials or a Derivative Model.
76
+
77
+ The status of a Licensee as a for-profit entity does not, by itself, make
78
+ limited internal research, benchmarking, evaluation, security testing, or
79
+ red-teaming a Commercial Purpose, provided that the use is not deployed in
80
+ production, made available to customers or other external end users, used to
81
+ generate revenue, or used to train or improve a non-BreezeBlue generative
82
+ model.
83
+
84
+ 1.8 "Affiliate" means an entity that directly or indirectly controls, is
85
+ controlled by, or is under common control with a party, where "control" means
86
+ ownership or control of more than fifty percent (50%) of the voting interests
87
+ of that entity.
88
+
89
+
90
+ 2. RESEARCH AND NON-COMMERCIAL LICENSE
91
+
92
+ Subject to Your continued compliance with this Agreement, BreezeBlue grants
93
+ You a limited, non-exclusive, worldwide, non-transferable, non-sublicensable,
94
+ royalty-free license under BreezeBlue's intellectual property rights in the
95
+ Model Materials to:
96
+
97
+ (a) access, use, reproduce, and modify the Model Materials;
98
+
99
+ (b) create Derivative Models; and
100
+
101
+ (c) distribute the Model Materials and Derivative Models as expressly
102
+ permitted by Section 4,
103
+
104
+ in each case solely for a Research Purpose or Non-Commercial Purpose.
105
+
106
+ No rights are granted except as expressly stated in this Agreement.
107
+
108
+
109
+ 3. COMMERCIAL USE
110
+
111
+ No Commercial Purpose is permitted under this Agreement. Any Commercial
112
+ Purpose involving the Model Materials, a Derivative Model, or an Output
113
+ requires a separate written commercial license from BreezeBlue.
114
+
115
+ For clarity, this Agreement does not include a creator, content monetization,
116
+ small-business, revenue-threshold, or other implied commercial-use exception.
117
+
118
+ This Agreement governs only the open-weight Model Materials and their
119
+ self-hosted use. Outputs generated through BreezeBlue's hosted products or
120
+ APIs are governed by the applicable Terms of Service, plan terms, and any
121
+ separate written agreement, rather than this Agreement.
122
+
123
+ For commercial licensing inquiries, contact:
124
+
125
+ Website: https://breezeblue.ai
126
+ Email: contact@breeze.blue
127
+
128
+
129
+ 4. DISTRIBUTION AND ATTRIBUTION
130
+
131
+ You may distribute the Model Materials or a Derivative Model solely for a
132
+ Research Purpose or Non-Commercial Purpose and only if You:
133
+
134
+ (a) provide every recipient with a complete copy of this Agreement and any
135
+ NOTICE file accompanying the Model Materials;
136
+
137
+ (b) retain all copyright, attribution, provenance, and proprietary notices;
138
+
139
+ (c) include the following notice in a NOTICE file distributed with the Model
140
+ Materials or Derivative Model:
141
+
142
+ "Breeze TTS 2 is licensed under the BreezeBlue Research and Non-Commercial
143
+ License Agreement. Copyright (c) 2026 RESONIA, INC. All Rights Reserved.";
144
+
145
+ (d) clearly identify any modifications You made and, for a Derivative Model,
146
+ state prominently in its model card, repository, and documentation:
147
+
148
+ "Derived from Breeze TTS 2 by BreezeBlue and licensed for research and
149
+ non-commercial use only.";
150
+
151
+ (e) license the distributed Model Materials or Derivative Model under this
152
+ Agreement without adding terms that conflict with or reduce the protections
153
+ of this Agreement; and
154
+
155
+ (f) ensure that each recipient independently accepts this Agreement before
156
+ accessing or using the Model Materials or Derivative Model.
157
+
158
+ You may not use "BreezeBlue," "Breeze TTS 2," or any confusingly similar name
159
+ as the primary name of a Derivative Model, product, or service, or in any way
160
+ that suggests endorsement, affiliation, official status, or successor status.
161
+ Reasonable descriptive attribution required by this Section is permitted.
162
+
163
+ Publication of non-commercial research results, benchmark results, and
164
+ illustrative samples in papers, reports, conference presentations, model
165
+ cards, repositories, or non-monetized demonstrations is permitted, provided
166
+ that You reasonably acknowledge Breeze TTS 2 and comply with this Agreement.
167
+
168
+
169
+ 5. USE RESTRICTIONS
170
+
171
+ You will not, and will not permit any third party to:
172
+
173
+ (a) use the Model Materials, a Derivative Model, or any Output for a
174
+ Commercial Purpose without a separate written commercial license;
175
+
176
+ (b) use the Model Materials, a Derivative Model, or any Output to create,
177
+ train, fine-tune, distill, evaluate for training selection, or otherwise
178
+ improve any non-BreezeBlue foundational or generative speech, audio,
179
+ language, or multimodal model, except for creating a Derivative Model solely
180
+ for a Research Purpose or Non-Commercial Purpose under this Agreement;
181
+
182
+ (c) use the Model Materials, a Derivative Model, or any Output to clone,
183
+ simulate, imitate, or impersonate a real person's voice, identity, likeness,
184
+ or persona without that person's explicit and legally sufficient consent;
185
+
186
+ (d) submit or use reference audio, speaker embeddings, voice prompts,
187
+ recordings, performances, or other materials unless You have all rights,
188
+ licenses, permissions, and consents necessary for that use;
189
+
190
+ (e) use the Model Materials, a Derivative Model, or any Output to defraud,
191
+ deceive, harass, stalk, threaten, defame, exploit, or cause harm to any person;
192
+
193
+ (f) generate or distribute sexual, exploitative, or otherwise harmful content
194
+ involving a minor;
195
+
196
+ (g) engage in deceptive political persuasion, election interference, voter
197
+ suppression, synthetic robocalls, or false impersonation of a candidate,
198
+ public official, or other person in a political context;
199
+
200
+ (h) identify, authenticate, track, surveil, or profile a person using voice or
201
+ other biometric characteristics without a lawful basis and all consent
202
+ required by applicable law;
203
+
204
+ (i) distribute synthetic audio in circumstances where a reasonable person
205
+ would be materially misled about its origin or authenticity, or fail to make
206
+ any disclosure required by applicable law;
207
+
208
+ (j) remove, obscure, alter, or circumvent any copyright notice, attribution,
209
+ watermark, provenance signal, safety mechanism, or other proprietary notice
210
+ included in the Model Materials or Outputs;
211
+
212
+ (k) attempt to extract or reconstruct non-public training data, source data,
213
+ trade secrets, or proprietary components from the Model Materials, except to
214
+ the extent such a restriction is prohibited by applicable law; or
215
+
216
+ (l) use, export, re-export, transfer, or make available the Model Materials or
217
+ a Derivative Model in violation of any applicable law, regulation, export
218
+ control, or trade sanction.
219
+
220
+ Nothing in Section 5(k) prohibits ordinary inspection, loading, conversion,
221
+ quantization, modification, or fine-tuning of the released Model Materials as
222
+ expressly permitted by this Agreement.
223
+
224
+
225
+ 6. LEGAL COMPLIANCE AND THIRD-PARTY RIGHTS
226
+
227
+ You are solely responsible for ensuring that Your use of the Model Materials,
228
+ Derivative Models, inputs, and Outputs complies with all applicable laws and
229
+ regulations and does not infringe or violate any third-party right, including
230
+ intellectual property, privacy, publicity, biometric privacy,
231
+ recording-consent, consumer-protection, election, and other applicable rights.
232
+
233
+ This Agreement grants no right to use, reproduce, clone, simulate, imitate,
234
+ or otherwise exploit any third-party voice, identity, likeness, persona,
235
+ performance, recording, copyrighted work, trademark, or other protected
236
+ material. You are solely responsible for obtaining all necessary rights,
237
+ licenses, permissions, notices, and consents.
238
+
239
+ BreezeBlue does not represent or warrant that any Model Material or Output is
240
+ cleared for Your intended use, unique, accurate, non-infringing, or suitable
241
+ for any particular purpose.
242
+
243
+
244
+ 7. INTELLECTUAL PROPERTY
245
+
246
+ 7.1 Model Materials. BreezeBlue and its licensors retain all right, title, and
247
+ interest in and to the Model Materials. Except for the limited license granted
248
+ in Section 2, no right or interest in the Model Materials is transferred to
249
+ You.
250
+
251
+ 7.2 Derivative Models. As between You and BreezeBlue, You own the original
252
+ modifications You contribute to a Derivative Model, to the extent permitted by
253
+ applicable law. That ownership is subject to BreezeBlue's ownership of the
254
+ underlying Model Materials and to every restriction in this Agreement. A
255
+ Derivative Model remains governed by this Agreement.
256
+
257
+ 7.3 Outputs. As between You and BreezeBlue, and to the extent permitted by
258
+ applicable law, You own Outputs You lawfully generate. Output ownership does
259
+ not remove or limit the non-commercial restrictions in this Agreement, grant
260
+ rights in third-party material, or authorize any use prohibited by this
261
+ Agreement.
262
+
263
+ 7.4 Trademarks. No trademark license is granted except the limited right to
264
+ make accurate descriptive references required for attribution under Section
265
+ 4. All goodwill arising from permitted use of BreezeBlue's marks belongs to
266
+ BreezeBlue.
267
+
268
+ 7.5 Feedback. If You voluntarily provide suggestions, comments, or other
269
+ feedback about the Model Materials, You grant BreezeBlue a perpetual,
270
+ irrevocable, worldwide, royalty-free, fully paid, transferable, sublicensable,
271
+ non-exclusive license to use and exploit that feedback without restriction or
272
+ compensation.
273
+
274
+ 7.6 Intellectual Property Claims. If You or Your Affiliate brings a claim or
275
+ legal proceeding alleging that the Model Materials, a Derivative Model, or an
276
+ Output infringes intellectual property rights owned or licensable by You or
277
+ Your Affiliate, the licenses granted to You under this Agreement terminate
278
+ automatically when the claim or proceeding is filed.
279
+
280
+
281
+ 8. REPRESENTATIONS
282
+
283
+ You represent and warrant that:
284
+
285
+ (a) You have legal capacity and authority to enter into this Agreement;
286
+
287
+ (b) You will use and distribute the Model Materials, Derivative Models, and
288
+ Outputs only as permitted by this Agreement;
289
+
290
+ (c) You have all necessary rights and consents for Your inputs and intended
291
+ uses; and
292
+
293
+ (d) You are not prohibited from receiving or using the Model Materials under
294
+ applicable export-control, sanctions, or other laws.
295
+
296
+
297
+ 9. DISCLAIMER OF WARRANTIES
298
+
299
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE MODEL MATERIALS,
300
+ DERIVATIVE MODELS, OUTPUTS, AND ALL RELATED MATERIALS ARE PROVIDED "AS IS" AND
301
+ "AS AVAILABLE," WITH ALL FAULTS AND WITHOUT WARRANTIES OF ANY KIND, WHETHER
302
+ EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE.
303
+
304
+ BREEZEBLUE DISCLAIMS ALL WARRANTIES, INCLUDING WARRANTIES OF TITLE,
305
+ NON-INFRINGEMENT, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
306
+ ACCURACY, RELIABILITY, SECURITY, AND ANY WARRANTY THAT THE MODEL MATERIALS OR
307
+ OUTPUTS WILL BE ERROR-FREE, UNINTERRUPTED, UNIQUE, OR SUITABLE FOR YOUR USE.
308
+
309
+ YOU ARE SOLELY RESPONSIBLE FOR DETERMINING THE APPROPRIATENESS AND LAWFULNESS
310
+ OF YOUR USE AND ASSUME ALL RISKS ARISING FROM THAT USE.
311
+
312
+
313
+ 10. LIMITATION OF LIABILITY
314
+
315
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, BREEZEBLUE AND ITS
316
+ AFFILIATES, LICENSORS, OFFICERS, DIRECTORS, EMPLOYEES, CONTRACTORS, AND AGENTS
317
+ WILL NOT BE LIABLE UNDER ANY THEORY OF LIABILITY FOR ANY INDIRECT, INCIDENTAL,
318
+ SPECIAL, CONSEQUENTIAL, EXEMPLARY, OR PUNITIVE DAMAGES, OR FOR ANY LOSS OF
319
+ PROFITS, REVENUE, BUSINESS, GOODWILL, DATA, OR USE, ARISING OUT OF OR RELATING
320
+ TO THIS AGREEMENT, THE MODEL MATERIALS, ANY DERIVATIVE MODEL, OR ANY OUTPUT,
321
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
322
+
323
+ TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, BREEZEBLUE'S TOTAL
324
+ AGGREGATE LIABILITY ARISING OUT OF OR RELATING TO THIS AGREEMENT WILL NOT
325
+ EXCEED ONE HUNDRED U.S. DOLLARS (US$100).
326
+
327
+
328
+ 11. INDEMNIFICATION
329
+
330
+ You agree to defend, indemnify, and hold harmless BreezeBlue and its
331
+ Affiliates, licensors, officers, directors, employees, contractors, and agents
332
+ from and against any third-party claim, demand, action, proceeding, damage,
333
+ loss, liability, judgment, settlement, cost, or expense, including reasonable
334
+ attorneys' fees, arising out of or relating to:
335
+
336
+ (a) Your access to, use, modification, or distribution of the Model Materials
337
+ or a Derivative Model;
338
+
339
+ (b) Your inputs or Outputs;
340
+
341
+ (c) Your violation of this Agreement or applicable law; or
342
+
343
+ (d) Your infringement, misappropriation, or violation of any third-party
344
+ right.
345
+
346
+
347
+ 12. TERM AND TERMINATION
348
+
349
+ This Agreement begins when You first access or use the Model Materials and
350
+ continues until terminated.
351
+
352
+ BreezeBlue may terminate this Agreement if You materially breach it. For a
353
+ material breach that is reasonably capable of cure, BreezeBlue will provide
354
+ written notice and at least thirty (30) days to cure before termination.
355
+ BreezeBlue may terminate immediately for a breach of Section 3, Section 5, or
356
+ applicable law, or where delay would reasonably create legal, security, fraud,
357
+ or safety risk.
358
+
359
+ Upon termination, You must immediately cease using and distributing the Model
360
+ Materials and Derivative Models, delete all copies under Your control, and,
361
+ upon reasonable request, confirm deletion in writing. Termination does not
362
+ require deletion of an Output lawfully created before termination, but no
363
+ Output may be used in violation of this Agreement or applicable law.
364
+
365
+ BreezeBlue may stop distributing the Model Materials prospectively. Such
366
+ discontinuation alone does not terminate rights already granted to a Licensee
367
+ that remains in compliance with this Agreement.
368
+
369
+ Sections 3, 5, 6, 7, 9, 10, 11, 12, and 13 survive termination to the extent
370
+ necessary to give them effect.
371
+
372
+
373
+ 13. GOVERNING LAW AND JURISDICTION
374
+
375
+ This Agreement is governed by and construed in accordance with the laws of the
376
+ State of Delaware, without regard to its conflict-of-laws principles. The
377
+ United Nations Convention on Contracts for the International Sale of Goods
378
+ does not apply.
379
+
380
+ To the extent permitted by applicable law, any dispute arising out of or
381
+ relating to this Agreement shall be brought exclusively in the state or
382
+ federal courts located in the State of Delaware, and each party consents to
383
+ the personal jurisdiction and venue of those courts.
384
+
385
+
386
+ 14. MISCELLANEOUS
387
+
388
+ 14.1 Entire Agreement. This Agreement and any NOTICE file accompanying the
389
+ Model Materials constitute the entire agreement concerning the Model
390
+ Materials and supersede prior or contemporaneous understandings concerning
391
+ the same subject matter.
392
+
393
+ 14.2 Separate Software Licenses. Source code and third-party components may be
394
+ made available under separate licenses. Those separate licenses govern only
395
+ the materials expressly identified as subject to them and do not grant any
396
+ right to use the Model Materials.
397
+
398
+ 14.3 Assignment. You may not assign or transfer this Agreement or any right
399
+ under it without BreezeBlue's prior written consent. BreezeBlue may assign
400
+ this Agreement in connection with a merger, acquisition, reorganization, sale
401
+ of assets, or operation of law.
402
+
403
+ 14.4 Severability. If any provision is held invalid or unenforceable, it will
404
+ be enforced to the maximum extent permitted, and the remaining provisions
405
+ will remain in effect.
406
+
407
+ 14.5 No Waiver. Failure to enforce a provision is not a waiver of the right to
408
+ enforce it later.
409
+
410
+ 14.6 No Endorsement. Nothing in this Agreement creates an agency,
411
+ partnership, joint venture, employment, fiduciary, sponsorship, or endorsement
412
+ relationship between the parties.
413
+
414
+ 14.7 Updates. BreezeBlue may publish new versions of this Agreement. A new
415
+ version applies only if You expressly accept it or access Model Materials
416
+ released under that new version. Your continued use of a previously obtained
417
+ version of the Model Materials remains governed by the version You accepted,
418
+ unless applicable law requires otherwise.
419
+
420
+
421
+ 15. CONTACT
422
+
423
+ Questions and commercial licensing requests may be sent to:
424
+
425
+ RESONIA, INC., doing business as BreezeBlue
426
+ Website: https://breezeblue.ai
427
+ Email: contact@breeze.blue
README.md CHANGED
@@ -1,13 +1,59 @@
1
  ---
2
  title: Breeze TTS 2 Studio
3
- emoji: 👁
4
- colorFrom: red
5
- colorTo: purple
6
  sdk: gradio
7
- sdk_version: 6.26.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
 
 
 
 
 
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Breeze TTS 2 Studio
3
+ emoji: "🌬️"
4
+ colorFrom: indigo
5
+ colorTo: blue
6
  sdk: gradio
 
 
7
  app_file: app.py
8
  pinned: false
9
+ license: other
10
+ license_name: breezeblue-research-and-non-commercial-license
11
+ license_link: https://huggingface.co/BreezeBlue/Breeze-TTS-2/blob/main/LICENSE
12
+ models:
13
+ - BreezeBlue/Breeze-TTS-2
14
+ short_description: Design, clone, and direct expressive English and Chinese.
15
+ python_version: 3.10.13
16
+ startup_duration_timeout: 1h
17
  ---
18
 
19
+ # Breeze TTS 2 Studio
20
+
21
+ An independent, full-capability research demo for
22
+ [`BreezeBlue/Breeze-TTS-2`](https://huggingface.co/BreezeBlue/Breeze-TTS-2).
23
+
24
+ ## What the Space demonstrates
25
+
26
+ - **Voice design:** create a new voice from a natural-language description without reference audio.
27
+ - **Voice cloning:** preserve a speaker from clean reference audio and its exact transcript.
28
+ - **Voice direction:** retain speaker identity while steering tone, emotion, pace, energy, and delivery.
29
+ - **Vocal events:** inline English events such as `(laugh)`, `(cough)`, `(clears throat)`, and `(sigh)`, plus Chinese events such as `[笑]`, `[咳嗽]`, `[清嗓子]`, and `[叹气]`.
30
+ - **Bilingual synthesis:** English and Chinese generation from one model.
31
+ - **Streaming:** audio chunks are played as the model produces them; a complete 24 kHz WAV is provided afterward.
32
+ - **Reproducibility:** deterministic seeds and pinned upstream model/code revisions.
33
+
34
+ ## ZeroGPU design
35
+
36
+ The model is loaded on CUDA at module scope as required by ZeroGPU, while the generation
37
+ function uses `@spaces.GPU(size="large")`. The app intentionally uses the upstream eager
38
+ inference path because `torch.compile` is unsupported on ZeroGPU and reliability matters more
39
+ than advertising fast-path numbers that shared hardware may not reproduce.
40
+
41
+ ## Source provenance
42
+
43
+ - Model revision: `c1c8ca18b70b30822735633991d9ebf4898e47d4`
44
+ - Inference-code revision: `ca632ce6c4d05f7985da4eab29b1a5d445b43f7b`
45
+ - Upstream inference repository: <https://github.com/breezeblue-ai/breeze-tts>
46
+
47
+ The `models/`, `breeze_infer/`, and `configs/` folders are vendored from that pinned inference
48
+ revision so that the Space does not depend on mutable code at startup.
49
+
50
+ ## License and safety
51
+
52
+ The upstream inference code is Apache-2.0. The model weights, derivative models, and
53
+ self-hosted outputs are governed by the **BreezeBlue Research and Non-Commercial License**.
54
+ Commercial use requires written authorization from RESONIA, INC.
55
+
56
+ Only use reference voices with appropriate rights and consent. Do not use generated speech
57
+ for impersonation, deception, fraud, harassment, or authentication bypass.
58
+
59
+ This Space is unofficial and is not affiliated with or endorsed by BreezeBlue.
UPSTREAM_CODE_LICENSE ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.
app.py ADDED
@@ -0,0 +1,807 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Breeze TTS 2 Studio — a polished ZeroGPU showcase."""
2
+
3
+ from __future__ import annotations
4
+
5
+ # ZeroGPU must patch PyTorch before any package imports torch.
6
+ import spaces
7
+
8
+ import html
9
+ import math
10
+ import random
11
+ import tempfile
12
+ import time
13
+ import uuid
14
+ from pathlib import Path
15
+
16
+ import gradio as gr
17
+ import numpy as np
18
+ import soundfile as sf
19
+ from huggingface_hub import snapshot_download
20
+
21
+ from breeze_infer.runtime import (
22
+ load_runtime,
23
+ set_all_seeds,
24
+ update_generation_config_for_breeze,
25
+ )
26
+ from breeze_infer.templates import get_template, prepare_inputs
27
+ from models.fast_streaming import FastBreezeStreamingRuntime, FastStreamingConfig
28
+
29
+
30
+ MODEL_ID = "BreezeBlue/Breeze-TTS-2"
31
+ MODEL_REVISION = "c1c8ca18b70b30822735633991d9ebf4898e47d4"
32
+ CODE_REVISION = "ca632ce6c4d05f7985da4eab29b1a5d445b43f7b"
33
+ MAX_TEXT_CHARS = 900
34
+ MAX_INSTRUCTION_CHARS = 560
35
+ MAX_REFERENCE_SECONDS = 60
36
+ MAX_NEW_TOKENS = 1500
37
+ MAX_SEQ_LEN = 2048
38
+
39
+
40
+ MODEL_PATH = Path(
41
+ snapshot_download(repo_id=MODEL_ID, revision=MODEL_REVISION)
42
+ )
43
+ TOKENIZER, MODEL, AUDIO_TOKENIZER = load_runtime(
44
+ MODEL_PATH,
45
+ device="cuda",
46
+ attn_implementation="eager",
47
+ )
48
+ update_generation_config_for_breeze(MODEL)
49
+ RUNTIME = FastBreezeStreamingRuntime(
50
+ MODEL,
51
+ AUDIO_TOKENIZER,
52
+ FastStreamingConfig(
53
+ max_new_tokens=MAX_NEW_TOKENS,
54
+ max_seq_len=MAX_SEQ_LEN,
55
+ collect_timing=True,
56
+ fast_all=False,
57
+ repetition_penalty=1.1,
58
+ ),
59
+ tokenizer=TOKENIZER,
60
+ )
61
+
62
+
63
+ MODE_HELP = {
64
+ "Voice design": (
65
+ "Describe a voice in plain language. No recording needed. Best for creating "
66
+ "a new character, narrator, host, or product voice."
67
+ ),
68
+ "Voice clone": (
69
+ "Upload a clean voice reference and its exact transcript. The model carries "
70
+ "the speaker's identity and natural delivery into new words."
71
+ ),
72
+ "Voice direction": (
73
+ "Start from a reference voice, then direct the performance—emotion, pace, "
74
+ "energy, intimacy, authority, or scene."
75
+ ),
76
+ }
77
+
78
+ DESIGN_EXAMPLES = [
79
+ [
80
+ "Voice design",
81
+ "English",
82
+ "(sigh) Welcome aboard. Your journey begins now.",
83
+ "A warm, thoughtful young woman with a clear voice and a calm, reflective delivery.",
84
+ 4.0,
85
+ ],
86
+ [
87
+ "Voice design",
88
+ "English",
89
+ "The storm is close now. Keep the lantern low, and stay where I can see you.",
90
+ "An older coastal storyteller with a weathered, resonant voice; intimate, measured, and quietly suspenseful.",
91
+ 4.0,
92
+ ],
93
+ [
94
+ "Voice design",
95
+ "中文",
96
+ "[笑] 歡迎來到今晚的故事時間,讓我們一起開始吧。",
97
+ "一位溫柔自信的年輕女性,聲音清晰,語氣親切,表達輕快而富有感染力。",
98
+ 4.0,
99
+ ],
100
+ [
101
+ "Voice design",
102
+ "中文",
103
+ "請稍等一下,我想我已經找到問題的答案了。",
104
+ "一位沉穩理性的中年研究員,音色溫暖,語速稍慢,思考時帶著自然的停頓。",
105
+ 4.0,
106
+ ],
107
+ ]
108
+
109
+ CSS = r"""
110
+ :root {
111
+ --ink: #172036;
112
+ --muted: #667085;
113
+ --line: #e8eaf0;
114
+ --paper: #ffffff;
115
+ --wash: #f6f7fb;
116
+ --accent: #5565e8;
117
+ --accent-dark: #3948c7;
118
+ --blue-soft: #eef0ff;
119
+ --mint: #dff7ed;
120
+ }
121
+
122
+ .gradio-container {
123
+ max-width: 1180px !important;
124
+ margin: 0 auto !important;
125
+ padding: 24px 22px 70px !important;
126
+ color: var(--ink) !important;
127
+ background:
128
+ radial-gradient(circle at 82% 4%, rgba(85,101,232,.10), transparent 23rem),
129
+ radial-gradient(circle at 9% 28%, rgba(72,187,156,.07), transparent 20rem),
130
+ #fbfbfd !important;
131
+ }
132
+
133
+ footer { display: none !important; }
134
+ .prose { max-width: none !important; }
135
+
136
+ #hero {
137
+ position: relative;
138
+ overflow: hidden;
139
+ padding: 44px 44px 38px;
140
+ border: 1px solid rgba(23,32,54,.08);
141
+ border-radius: 28px;
142
+ background: rgba(255,255,255,.86);
143
+ box-shadow: 0 22px 65px rgba(31,42,82,.08);
144
+ margin-bottom: 18px;
145
+ }
146
+ #hero:after {
147
+ content: "";
148
+ position: absolute;
149
+ width: 290px;
150
+ height: 290px;
151
+ right: -110px;
152
+ top: -135px;
153
+ border-radius: 50%;
154
+ border: 58px solid rgba(85,101,232,.08);
155
+ }
156
+ .hero-eyebrow {
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 9px;
160
+ color: #4c5bd4;
161
+ font-size: 12px;
162
+ font-weight: 750;
163
+ letter-spacing: .13em;
164
+ text-transform: uppercase;
165
+ }
166
+ .hero-eyebrow i {
167
+ width: 8px;
168
+ height: 8px;
169
+ display: inline-block;
170
+ border-radius: 50%;
171
+ background: #35b784;
172
+ box-shadow: 0 0 0 5px rgba(53,183,132,.12);
173
+ }
174
+ #hero h1 {
175
+ max-width: 760px;
176
+ margin: 18px 0 12px;
177
+ font-size: clamp(38px, 6vw, 67px);
178
+ line-height: .98;
179
+ letter-spacing: -.052em;
180
+ color: #131b2e;
181
+ }
182
+ #hero .lede {
183
+ max-width: 720px;
184
+ margin: 0;
185
+ font-size: 18px;
186
+ line-height: 1.58;
187
+ color: #5b6477;
188
+ }
189
+ .cap-row {
190
+ display: flex;
191
+ flex-wrap: wrap;
192
+ gap: 8px;
193
+ margin-top: 24px;
194
+ }
195
+ .cap-row span {
196
+ display: inline-flex;
197
+ align-items: center;
198
+ padding: 8px 12px;
199
+ border: 1px solid #e4e6ef;
200
+ border-radius: 999px;
201
+ color: #434d63;
202
+ background: #fff;
203
+ font-size: 12px;
204
+ font-weight: 650;
205
+ }
206
+
207
+ .studio-shell {
208
+ padding: 18px !important;
209
+ border: 1px solid rgba(23,32,54,.08) !important;
210
+ border-radius: 26px !important;
211
+ background: rgba(255,255,255,.92) !important;
212
+ box-shadow: 0 20px 52px rgba(31,42,82,.07) !important;
213
+ }
214
+ .panel {
215
+ padding: 20px !important;
216
+ border: 1px solid var(--line) !important;
217
+ border-radius: 20px !important;
218
+ background: var(--paper) !important;
219
+ }
220
+ .output-panel {
221
+ min-height: 100%;
222
+ background: #f8f9fc !important;
223
+ }
224
+ .section-kicker {
225
+ margin: 0 0 2px !important;
226
+ color: #7a8396 !important;
227
+ font-size: 11px !important;
228
+ font-weight: 750 !important;
229
+ letter-spacing: .12em !important;
230
+ text-transform: uppercase !important;
231
+ }
232
+ .section-title h2, .section-title h3 {
233
+ margin: 0 0 14px !important;
234
+ color: var(--ink) !important;
235
+ letter-spacing: -.025em !important;
236
+ }
237
+ .mode-note {
238
+ padding: 11px 13px;
239
+ border-left: 3px solid #7c88ef;
240
+ border-radius: 0 10px 10px 0;
241
+ background: #f5f6ff;
242
+ color: #505a70;
243
+ font-size: 13px;
244
+ line-height: 1.5;
245
+ }
246
+ .event-label {
247
+ margin: 3px 0 7px;
248
+ color: #71798a;
249
+ font-size: 12px;
250
+ }
251
+ .event-chip {
252
+ min-width: 0 !important;
253
+ border: 1px solid #e1e4ec !important;
254
+ border-radius: 999px !important;
255
+ background: #fff !important;
256
+ color: #465067 !important;
257
+ font-size: 12px !important;
258
+ box-shadow: none !important;
259
+ }
260
+ .event-chip:hover {
261
+ border-color: #aeb6f5 !important;
262
+ color: #3948c7 !important;
263
+ background: #f7f7ff !important;
264
+ }
265
+ #generate-button {
266
+ min-height: 50px;
267
+ border: none !important;
268
+ border-radius: 14px !important;
269
+ background: var(--accent) !important;
270
+ color: white !important;
271
+ font-weight: 720 !important;
272
+ box-shadow: 0 10px 24px rgba(85,101,232,.22) !important;
273
+ }
274
+ #generate-button:hover { background: var(--accent-dark) !important; }
275
+ .secondary-button {
276
+ min-height: 50px;
277
+ border-radius: 14px !important;
278
+ box-shadow: none !important;
279
+ }
280
+ #live-audio {
281
+ border: 1px solid #e0e3eb !important;
282
+ border-radius: 16px !important;
283
+ background: #fff !important;
284
+ }
285
+ .status-card {
286
+ min-height: 76px;
287
+ padding: 13px 15px;
288
+ border: 1px solid #e2e5ee;
289
+ border-radius: 14px;
290
+ background: white;
291
+ color: #596277;
292
+ font-size: 13px;
293
+ line-height: 1.55;
294
+ }
295
+ .status-card strong { color: #202a41; }
296
+ .model-facts {
297
+ display: grid;
298
+ grid-template-columns: repeat(2, minmax(0,1fr));
299
+ gap: 9px;
300
+ margin-top: 16px;
301
+ }
302
+ .model-facts div {
303
+ padding: 13px;
304
+ border: 1px solid #e5e7ee;
305
+ border-radius: 13px;
306
+ background: rgba(255,255,255,.76);
307
+ }
308
+ .model-facts b {
309
+ display: block;
310
+ margin-bottom: 3px;
311
+ font-size: 16px;
312
+ color: #202a41;
313
+ }
314
+ .model-facts span {
315
+ color: #737b8c;
316
+ font-size: 11px;
317
+ }
318
+ .recipe-grid {
319
+ display: grid;
320
+ grid-template-columns: repeat(3, minmax(0,1fr));
321
+ gap: 12px;
322
+ margin: 18px 0;
323
+ }
324
+ .recipe {
325
+ padding: 18px;
326
+ border: 1px solid #e3e6ed;
327
+ border-radius: 17px;
328
+ background: rgba(255,255,255,.84);
329
+ }
330
+ .recipe b { display: block; margin-bottom: 7px; color: #29334a; }
331
+ .recipe p { margin: 0; color: #6c7485; font-size: 13px; line-height: 1.5; }
332
+ .fine-print {
333
+ color: #70798a;
334
+ font-size: 12px;
335
+ line-height: 1.55;
336
+ }
337
+
338
+ @media (max-width: 760px) {
339
+ .gradio-container { padding: 12px 10px 48px !important; }
340
+ #hero { padding: 30px 24px; border-radius: 22px; }
341
+ #hero h1 { font-size: 44px; }
342
+ #hero .lede { font-size: 16px; }
343
+ .studio-shell { padding: 10px !important; }
344
+ .panel { padding: 14px !important; }
345
+ .recipe-grid { grid-template-columns: 1fr; }
346
+ }
347
+ """
348
+
349
+
350
+ def mode_ui(mode: str):
351
+ needs_reference = mode in {"Voice clone", "Voice direction"}
352
+ needs_instruction = mode in {"Voice design", "Voice direction"}
353
+ return (
354
+ f'<div class="mode-note">{html.escape(MODE_HELP[mode])}</div>',
355
+ gr.update(visible=needs_reference),
356
+ gr.update(visible=needs_instruction),
357
+ gr.update(
358
+ visible=needs_instruction,
359
+ value=4.0 if needs_instruction else 1.0,
360
+ interactive=needs_instruction,
361
+ ),
362
+ )
363
+
364
+
365
+ def insert_event(current_text: str, tag: str) -> str:
366
+ current_text = (current_text or "").lstrip()
367
+ return f"{tag} {current_text}".strip()
368
+
369
+
370
+ def reset_output():
371
+ return None, None, (
372
+ '<div class="status-card"><strong>Ready when you are.</strong><br>'
373
+ "Your generated audio and performance details will appear here.</div>"
374
+ )
375
+
376
+
377
+ def _clean_text(value: str | None, name: str, limit: int) -> str:
378
+ cleaned = (value or "").strip()
379
+ if not cleaned:
380
+ raise gr.Error(f"{name} is required.")
381
+ if len(cleaned) > limit:
382
+ raise gr.Error(f"{name} is too long ({len(cleaned)}/{limit} characters).")
383
+ return cleaned
384
+
385
+
386
+ def _validate_reference(path: str | None, transcript: str | None, consent: bool):
387
+ if not consent:
388
+ raise gr.Error(
389
+ "Please confirm that you have permission to use the reference voice."
390
+ )
391
+ if not path or not Path(path).is_file():
392
+ raise gr.Error("Upload or record a clean reference voice first.")
393
+ ref_text = _clean_text(transcript, "Exact reference transcript", MAX_TEXT_CHARS)
394
+ try:
395
+ info = sf.info(path)
396
+ except Exception as exc:
397
+ raise gr.Error("The reference audio could not be read. Try WAV, FLAC, or MP3.") from exc
398
+ if info.duration < 1.0:
399
+ raise gr.Error("Use at least one second of reference speech.")
400
+ if info.duration > MAX_REFERENCE_SECONDS:
401
+ raise gr.Error(
402
+ f"Keep reference audio under {MAX_REFERENCE_SECONDS} seconds for this demo."
403
+ )
404
+ return str(Path(path)), ref_text, float(info.duration)
405
+
406
+
407
+ def gpu_duration(
408
+ mode,
409
+ language,
410
+ text,
411
+ instruction,
412
+ reference_audio,
413
+ reference_text,
414
+ consent,
415
+ cfg_scale,
416
+ seed,
417
+ randomize_seed,
418
+ ):
419
+ del language, instruction, reference_audio, reference_text, consent, cfg_scale, seed, randomize_seed
420
+ estimate = 42 + len(text or "") // 7 + (12 if mode != "Voice design" else 0)
421
+ return max(45, min(120, estimate))
422
+
423
+
424
+ @spaces.GPU(duration=gpu_duration, size="large")
425
+ def generate(
426
+ mode: str,
427
+ language: str,
428
+ text: str,
429
+ instruction: str,
430
+ reference_audio: str | None,
431
+ reference_text: str,
432
+ consent: bool,
433
+ cfg_scale: float,
434
+ seed: float,
435
+ randomize_seed: bool,
436
+ ):
437
+ target_text = _clean_text(text, "Script", MAX_TEXT_CHARS)
438
+ if mode not in MODE_HELP:
439
+ raise gr.Error("Choose a generation mode.")
440
+
441
+ if randomize_seed:
442
+ final_seed = random.SystemRandom().randint(0, 2_147_483_647)
443
+ else:
444
+ final_seed = int(seed)
445
+ if final_seed < 0 or final_seed > 2_147_483_647:
446
+ raise gr.Error("Seed must be between 0 and 2,147,483,647.")
447
+
448
+ has_reference = mode in {"Voice clone", "Voice direction"}
449
+ ref_duration = None
450
+ ref_path = None
451
+ ref_transcript = None
452
+ if has_reference:
453
+ ref_path, ref_transcript, ref_duration = _validate_reference(
454
+ reference_audio, reference_text, consent
455
+ )
456
+
457
+ if mode == "Voice clone":
458
+ direction = "自然清晰地說話。" if language == "中文" else "Speak clearly and naturally."
459
+ guidance = 1.0
460
+ else:
461
+ direction = _clean_text(
462
+ instruction, "Voice description or direction", MAX_INSTRUCTION_CHARS
463
+ )
464
+ guidance = float(cfg_scale)
465
+ if not math.isfinite(guidance) or not 1.0 <= guidance <= 6.0:
466
+ raise gr.Error("Guidance strength must be between 1 and 6.")
467
+
468
+ request_id = f"space-{uuid.uuid4().hex}"
469
+ request = {
470
+ "id": request_id,
471
+ "text": target_text,
472
+ "instruction": direction,
473
+ "speaker": "S0",
474
+ }
475
+ template_name = "tts_instruction"
476
+ if has_reference:
477
+ request["ref_audio_path"] = ref_path
478
+ request["ref_text"] = ref_transcript
479
+ template_name = "ref_edit_tata"
480
+
481
+ set_all_seeds(final_seed)
482
+ inputs = prepare_inputs(
483
+ TOKENIZER,
484
+ AUDIO_TOKENIZER,
485
+ MODEL,
486
+ [request],
487
+ get_template(template_name),
488
+ guidance_scale=guidance,
489
+ guidance_scale_ref=None,
490
+ guidance_scale_ins=None,
491
+ )
492
+
493
+ output_path = Path(tempfile.gettempdir()) / f"breeze-tts-2-{uuid.uuid4().hex}.wav"
494
+ started = time.perf_counter()
495
+ first_audio_at = None
496
+ sample_count = 0
497
+ chunk_count = 0
498
+
499
+ with sf.SoundFile(
500
+ output_path,
501
+ mode="w",
502
+ samplerate=RUNTIME.sample_rate,
503
+ channels=1,
504
+ subtype="PCM_16",
505
+ ) as wav_file:
506
+ for chunk in RUNTIME.iter_audio_chunks(inputs, request_id=request_id):
507
+ audio = np.asarray(chunk.audio, dtype=np.float32).reshape(-1)
508
+ if audio.size == 0:
509
+ continue
510
+ audio = np.clip(audio, -1.0, 1.0)
511
+ wav_file.write(audio)
512
+ sample_count += audio.size
513
+ chunk_count += 1
514
+ if first_audio_at is None:
515
+ first_audio_at = time.perf_counter()
516
+ elapsed = time.perf_counter() - started
517
+ generated_seconds = sample_count / RUNTIME.sample_rate
518
+ live_status = (
519
+ '<div class="status-card"><strong>Streaming your voice…</strong><br>'
520
+ f"{generated_seconds:.1f}s rendered · {elapsed:.1f}s elapsed · seed {final_seed:,}</div>"
521
+ )
522
+ yield (chunk.sample_rate, audio), gr.skip(), live_status
523
+
524
+ if sample_count == 0:
525
+ raise gr.Error("The model completed without producing audio. Try a shorter script.")
526
+
527
+ elapsed = time.perf_counter() - started
528
+ audio_seconds = sample_count / RUNTIME.sample_rate
529
+ rtf = elapsed / audio_seconds if audio_seconds else 0.0
530
+ ttfa = (first_audio_at - started) * 1000 if first_audio_at else 0.0
531
+ reference_note = f" · reference {ref_duration:.1f}s" if ref_duration else ""
532
+ final_status = (
533
+ '<div class="status-card"><strong>Complete.</strong><br>'
534
+ f"{audio_seconds:.1f}s audio · {elapsed:.1f}s generation · {rtf:.2f} RTF · "
535
+ f"{ttfa:.0f}ms first chunk · {chunk_count} chunks · seed {final_seed:,}{reference_note}</div>"
536
+ )
537
+ yield gr.skip(), str(output_path), final_status
538
+
539
+
540
+ theme = gr.themes.Base(
541
+ primary_hue=gr.themes.colors.indigo,
542
+ neutral_hue=gr.themes.colors.slate,
543
+ font=["Inter", "ui-sans-serif", "system-ui", "sans-serif"],
544
+ font_mono=["IBM Plex Mono", "ui-monospace", "monospace"],
545
+ ).set(
546
+ body_background_fill="#fbfbfd",
547
+ block_background_fill="#ffffff",
548
+ block_border_width="0px",
549
+ input_background_fill="#ffffff",
550
+ input_border_color="#dfe3ec",
551
+ input_border_color_focus="#7c88ef",
552
+ button_primary_background_fill="#5565e8",
553
+ button_primary_background_fill_hover="#3948c7",
554
+ )
555
+
556
+
557
+ with gr.Blocks(title="Breeze TTS 2 Studio") as demo:
558
+ gr.HTML(
559
+ """
560
+ <section id="hero">
561
+ <div class="hero-eyebrow"><i></i> Unofficial research space · Breeze TTS 2</div>
562
+ <h1>Give a voice<br>to intent.</h1>
563
+ <p class="lede">Design a speaker from words, carry a real voice into a new script,
564
+ or direct a performance with natural language. Built to make every capability clear,
565
+ creative, and immediately usable.</p>
566
+ <div class="cap-row">
567
+ <span>Voice design</span><span>Voice cloning</span><span>Voice direction</span>
568
+ <span>English + 中文</span><span>Live chunk playback</span><span>24 kHz WAV</span>
569
+ </div>
570
+ </section>
571
+ """
572
+ )
573
+
574
+ with gr.Row(elem_classes="studio-shell", equal_height=False):
575
+ with gr.Column(scale=7, min_width=360, elem_classes="panel"):
576
+ gr.Markdown("CREATE", elem_classes="section-kicker")
577
+ gr.Markdown("## Shape the performance", elem_classes="section-title")
578
+
579
+ mode = gr.Radio(
580
+ ["Voice design", "Voice clone", "Voice direction"],
581
+ value="Voice design",
582
+ label="Mode",
583
+ )
584
+ mode_note = gr.HTML(
585
+ f'<div class="mode-note">{MODE_HELP["Voice design"]}</div>'
586
+ )
587
+ language = gr.Radio(["English", "中文"], value="English", label="Language")
588
+ text = gr.Textbox(
589
+ label="Script",
590
+ placeholder="Write the words you want to hear…",
591
+ lines=6,
592
+ max_lines=10,
593
+ max_length=MAX_TEXT_CHARS,
594
+ buttons=["copy"],
595
+ info="You can add vocal events directly in the script.",
596
+ )
597
+
598
+ gr.HTML('<div class="event-label">Vocal events — click to insert</div>')
599
+ with gr.Row():
600
+ event_buttons = []
601
+ for label, tag in [
602
+ ("Laugh", "(laugh)"),
603
+ ("Sigh", "(sigh)"),
604
+ ("Cough", "(cough)"),
605
+ ("Clear throat", "(clears throat)"),
606
+ ("笑", "[笑]"),
607
+ ("嘆氣", "[叹气]"),
608
+ ("咳嗽", "[咳嗽]"),
609
+ ("清嗓", "[清嗓子]"),
610
+ ]:
611
+ event_buttons.append(
612
+ (gr.Button(label, size="sm", elem_classes="event-chip"), tag)
613
+ )
614
+
615
+ with gr.Group(visible=True) as instruction_group:
616
+ instruction = gr.Textbox(
617
+ label="Voice description",
618
+ placeholder=(
619
+ "Describe age, timbre, accent, pace, energy, personality, "
620
+ "emotion, and scene…"
621
+ ),
622
+ lines=4,
623
+ max_lines=7,
624
+ max_length=MAX_INSTRUCTION_CHARS,
625
+ info="Write this in the same language as your script.",
626
+ )
627
+
628
+ with gr.Group(visible=False) as reference_group:
629
+ reference_audio = gr.Audio(
630
+ sources=["upload", "microphone"],
631
+ type="filepath",
632
+ label="Reference voice",
633
+ format="wav",
634
+ )
635
+ reference_text = gr.Textbox(
636
+ label="Exact reference transcript",
637
+ placeholder="Type exactly what is spoken in the recording…",
638
+ lines=3,
639
+ max_lines=6,
640
+ max_length=MAX_TEXT_CHARS,
641
+ info="Matching punctuation and wording helps preserve the voice.",
642
+ )
643
+ consent = gr.Checkbox(
644
+ label="I have permission to use this voice and will not use it deceptively.",
645
+ value=False,
646
+ )
647
+
648
+ with gr.Accordion("Generation settings", open=False):
649
+ with gr.Group(visible=True) as guidance_group:
650
+ cfg_scale = gr.Slider(
651
+ 1.0,
652
+ 6.0,
653
+ value=4.0,
654
+ step=0.25,
655
+ label="Direction strength",
656
+ info="Higher follows the description more strongly; 4 is the official recommendation.",
657
+ )
658
+ with gr.Row():
659
+ seed = gr.Number(
660
+ value=42,
661
+ precision=0,
662
+ label="Seed",
663
+ minimum=0,
664
+ maximum=2_147_483_647,
665
+ )
666
+ randomize_seed = gr.Checkbox(
667
+ value=True,
668
+ label="New variation each time",
669
+ )
670
+
671
+ with gr.Row():
672
+ generate_button = gr.Button(
673
+ "Generate voice",
674
+ variant="primary",
675
+ elem_id="generate-button",
676
+ scale=4,
677
+ )
678
+ clear_button = gr.Button(
679
+ "Clear output",
680
+ variant="secondary",
681
+ elem_classes="secondary-button",
682
+ scale=1,
683
+ )
684
+
685
+ with gr.Column(scale=5, min_width=320, elem_classes=["panel", "output-panel"]):
686
+ gr.Markdown("LISTEN", elem_classes="section-kicker")
687
+ gr.Markdown("## Your rendered voice", elem_classes="section-title")
688
+ live_audio = gr.Audio(
689
+ label="Live playback",
690
+ streaming=True,
691
+ autoplay=True,
692
+ interactive=False,
693
+ format="wav",
694
+ elem_id="live-audio",
695
+ )
696
+ download = gr.File(label="Download the complete WAV", interactive=False)
697
+ status = gr.HTML(
698
+ '<div class="status-card"><strong>Ready when you are.</strong><br>'
699
+ "Your generated audio and performance details will appear here.</div>"
700
+ )
701
+ gr.HTML(
702
+ """
703
+ <div class="model-facts">
704
+ <div><b>~3B</b><span>MODEL PARAMETERS</span></div>
705
+ <div><b>24 kHz</b><span>MONO OUTPUT</span></div>
706
+ <div><b>3 modes</b><span>DESIGN · CLONE · DIRECT</span></div>
707
+ <div><b>ZeroGPU</b><span>48 GB LARGE SLICE</span></div>
708
+ </div>
709
+ <p class="fine-print">This Space uses the reliable eager path. The published
710
+ &lt;40 ms TTFA and 0.32 RTF figures are official warmed-H100 fast-path results,
711
+ not a promise for shared ZeroGPU sessions.</p>
712
+ """
713
+ )
714
+
715
+ gr.HTML(
716
+ """
717
+ <div class="recipe-grid">
718
+ <div class="recipe"><b>Design with specificity</b><p>Combine vocal identity,
719
+ timbre, pace, energy, emotional state, and the scene. A coherent character works
720
+ better than a pile of adjectives.</p></div>
721
+ <div class="recipe"><b>Clone from clean speech</b><p>Use one speaker, little room
722
+ noise, and an exact transcript. Ten to thirty seconds is a practical starting point.</p></div>
723
+ <div class="recipe"><b>Direct the change</b><p>Describe the performance delta:
724
+ “keep the same identity, but speak more slowly with restrained urgency.”</p></div>
725
+ </div>
726
+ """
727
+ )
728
+
729
+ with gr.Accordion("Starter voices", open=False):
730
+ gr.Markdown(
731
+ "These examples demonstrate open-ended voice design in both supported languages."
732
+ )
733
+ gr.Examples(
734
+ examples=DESIGN_EXAMPLES,
735
+ inputs=[mode, language, text, instruction, cfg_scale],
736
+ label="Load a recipe",
737
+ )
738
+
739
+ with gr.Accordion("About this model, license, and responsible use", open=False):
740
+ gr.Markdown(
741
+ f"""
742
+ **Breeze TTS 2** is an open-weight bilingual speech model from BreezeBlue. This independent
743
+ Space exposes reference-free voice design, reference-guided cloning and direction, inline
744
+ vocal events, and streaming generation.
745
+
746
+ - Model: [{MODEL_ID}](https://huggingface.co/{MODEL_ID})
747
+ - Inference code: [breezeblue-ai/breeze-tts](https://github.com/breezeblue-ai/breeze-tts)
748
+ - Pinned model revision: `{MODEL_REVISION[:12]}`
749
+ - Pinned code revision: `{CODE_REVISION[:12]}`
750
+
751
+ The inference source is Apache-2.0. The model weights, derivative models, and self-hosted
752
+ outputs are licensed for **research and non-commercial use only**. Commercial use requires
753
+ written authorization from RESONIA, INC. Only upload voices you have the right and consent to
754
+ use. Do not use generated speech for impersonation, deception, fraud, harassment, or
755
+ authentication bypass.
756
+ """
757
+ )
758
+
759
+ mode.change(
760
+ mode_ui,
761
+ inputs=mode,
762
+ outputs=[mode_note, reference_group, instruction_group, guidance_group],
763
+ queue=False,
764
+ )
765
+ for button, tag in event_buttons:
766
+ button.click(
767
+ lambda current, event_tag=tag: insert_event(current, event_tag),
768
+ inputs=text,
769
+ outputs=text,
770
+ queue=False,
771
+ )
772
+
773
+ generation_event = generate_button.click(
774
+ generate,
775
+ inputs=[
776
+ mode,
777
+ language,
778
+ text,
779
+ instruction,
780
+ reference_audio,
781
+ reference_text,
782
+ consent,
783
+ cfg_scale,
784
+ seed,
785
+ randomize_seed,
786
+ ],
787
+ outputs=[live_audio, download, status],
788
+ api_name="generate",
789
+ api_description="Generate speech with Breeze TTS 2 using design, clone, or direction mode.",
790
+ show_progress="minimal",
791
+ concurrency_limit=1,
792
+ concurrency_id="breeze-tts-2",
793
+ stream_every=0.35,
794
+ )
795
+ clear_button.click(
796
+ reset_output,
797
+ outputs=[live_audio, download, status],
798
+ cancels=[generation_event],
799
+ queue=False,
800
+ )
801
+
802
+
803
+ if __name__ == "__main__":
804
+ demo.queue(max_size=40, default_concurrency_limit=1).launch(
805
+ theme=theme,
806
+ css=CSS,
807
+ )
breeze_infer/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Breeze TTS inference runtime package."""
breeze_infer/api.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Thin streaming API over the PyTorch Breeze inference runtime."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import tempfile
7
+ import threading
8
+ import uuid
9
+ from collections.abc import AsyncIterator, Iterator
10
+ from contextlib import asynccontextmanager
11
+ from dataclasses import dataclass, replace
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ from fastapi import FastAPI, File, Form, HTTPException, UploadFile
16
+ from fastapi.responses import JSONResponse, StreamingResponse
17
+
18
+ from breeze_infer.runtime import (
19
+ load_runtime,
20
+ resolve_device,
21
+ set_all_seeds,
22
+ update_generation_config_for_breeze,
23
+ )
24
+ from breeze_infer.templates import get_template, prepare_inputs
25
+ from models.fast_streaming import FastBreezeStreamingRuntime, FastStreamingConfig
26
+ from models.warmup_profile import load_warmup_profile
27
+
28
+ REPO_ROOT = Path(__file__).resolve().parents[1]
29
+ FAST_CONFIG = REPO_ROOT / "configs" / "fast.json"
30
+ DEFAULT_CFG_SCALE = 1.0
31
+ MAX_NEW_TOKENS = 1500
32
+ MAX_SEQ_LEN = 2048
33
+ REPETITION_PENALTY = 1.1
34
+ OPTIONAL_AUDIO_FILE = File(None)
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class ApiSettings:
39
+ model: Path
40
+ fast_all: bool | None
41
+ fast_text_encoder: bool
42
+ fast_backbone_prefill: bool
43
+ fast_backbone_decode: bool
44
+ fast_depth_decoder: bool
45
+ fast_codec: bool
46
+
47
+
48
+ _settings: ApiSettings | None = None
49
+ _request_lock = threading.Lock()
50
+
51
+
52
+ def _pcm16(audio: np.ndarray) -> bytes:
53
+ audio = np.asarray(audio, dtype=np.float32)
54
+ audio = np.clip(audio, -1.0, 1.0)
55
+ return (audio * 32767.0).astype("<i2", copy=False).tobytes()
56
+
57
+
58
+ async def _save_upload(upload: UploadFile) -> Path:
59
+ suffix = Path(upload.filename or "reference.wav").suffix or ".wav"
60
+ with tempfile.NamedTemporaryFile(
61
+ prefix="breeze_ref_", suffix=suffix, delete=False
62
+ ) as temporary:
63
+ path = Path(temporary.name)
64
+ try:
65
+ payload = await upload.read()
66
+ if not payload:
67
+ raise HTTPException(status_code=400, detail="Reference audio is empty.")
68
+ temporary.write(payload)
69
+ except Exception:
70
+ path.unlink(missing_ok=True)
71
+ raise
72
+ return path
73
+
74
+
75
+ def _load_app(app: FastAPI, settings: ApiSettings) -> None:
76
+ tokenizer, model, audio_tokenizer = load_runtime(
77
+ settings.model,
78
+ device=resolve_device(),
79
+ attn_implementation="eager",
80
+ )
81
+ update_generation_config_for_breeze(model)
82
+
83
+ config = FastStreamingConfig(
84
+ max_new_tokens=MAX_NEW_TOKENS,
85
+ max_seq_len=MAX_SEQ_LEN,
86
+ fast_all=settings.fast_all,
87
+ fast_text_encoder=settings.fast_text_encoder,
88
+ fast_backbone_prefill=settings.fast_backbone_prefill,
89
+ fast_backbone_decode=settings.fast_backbone_decode,
90
+ fast_depth_decoder=settings.fast_depth_decoder,
91
+ fast_codec=settings.fast_codec,
92
+ repetition_penalty=REPETITION_PENALTY,
93
+ )
94
+ runtime = FastBreezeStreamingRuntime(
95
+ model, audio_tokenizer, config, tokenizer=tokenizer
96
+ )
97
+ if runtime.fast_enabled:
98
+ profile = load_warmup_profile(FAST_CONFIG)
99
+ profile = replace(profile, codec_chunk_frames=runtime.codec_chunk_frames)
100
+ manifest = runtime.warmup_from_profile(profile)
101
+ print(f"fast warmup: {manifest['total_elapsed_ms']:.2f} ms", flush=True)
102
+
103
+ app.state.tokenizer = tokenizer
104
+ app.state.model = model
105
+ app.state.audio_tokenizer = audio_tokenizer
106
+ app.state.runtime = runtime
107
+
108
+
109
+ @asynccontextmanager
110
+ async def _lifespan(app: FastAPI) -> AsyncIterator[None]:
111
+ if _settings is None:
112
+ raise RuntimeError("API settings are not initialized")
113
+ _load_app(app, _settings)
114
+ yield
115
+
116
+
117
+ app = FastAPI(title="Breeze TTS API", lifespan=_lifespan)
118
+
119
+
120
+ @app.get("/health")
121
+ def health() -> JSONResponse:
122
+ if not hasattr(app.state, "runtime"):
123
+ return JSONResponse({"status": "loading"}, status_code=503)
124
+ return JSONResponse({"status": "ok", "sample_rate": app.state.runtime.sample_rate})
125
+
126
+
127
+ @app.post("/v1/audio/speech")
128
+ async def speech(
129
+ text: str = Form(...),
130
+ instruction: str = Form("Speak clearly and naturally."),
131
+ cfg_scale: float = Form(DEFAULT_CFG_SCALE),
132
+ ref_audio: UploadFile | None = OPTIONAL_AUDIO_FILE,
133
+ ref_text: str = Form(""),
134
+ seed: int = Form(42),
135
+ ) -> StreamingResponse:
136
+ if not _request_lock.acquire(blocking=False):
137
+ raise HTTPException(
138
+ status_code=409, detail="An inference request is already running."
139
+ )
140
+
141
+ reference_path: Path | None = None
142
+ try:
143
+ if not np.isfinite(cfg_scale) or cfg_scale <= 0:
144
+ raise HTTPException(
145
+ status_code=400, detail="cfg_scale must be greater than 0."
146
+ )
147
+ ref_text = ref_text.strip()
148
+ has_reference = ref_audio is not None and bool(ref_audio.filename)
149
+ if has_reference != bool(ref_text):
150
+ raise HTTPException(
151
+ status_code=400,
152
+ detail="ref_audio and ref_text must be provided together or both omitted.",
153
+ )
154
+ if has_reference:
155
+ assert ref_audio is not None
156
+ reference_path = await _save_upload(ref_audio)
157
+
158
+ request_id = f"api-{uuid.uuid4().hex}"
159
+ request = {
160
+ "id": request_id,
161
+ "text": text,
162
+ "instruction": instruction,
163
+ "speaker": "S0",
164
+ }
165
+ template_name = "tts_instruction"
166
+ if reference_path is not None:
167
+ request["ref_audio_path"] = str(reference_path)
168
+ request["ref_text"] = ref_text
169
+ template_name = "ref_edit_tata"
170
+
171
+ set_all_seeds(seed)
172
+ inputs = prepare_inputs(
173
+ app.state.tokenizer,
174
+ app.state.audio_tokenizer,
175
+ app.state.model,
176
+ [request],
177
+ get_template(template_name),
178
+ guidance_scale=cfg_scale,
179
+ guidance_scale_ref=None,
180
+ guidance_scale_ins=None,
181
+ )
182
+ except Exception:
183
+ if reference_path is not None:
184
+ reference_path.unlink(missing_ok=True)
185
+ _request_lock.release()
186
+ raise
187
+
188
+ def body() -> Iterator[bytes]:
189
+ try:
190
+ for chunk in app.state.runtime.iter_audio_chunks(
191
+ inputs, request_id=request_id
192
+ ):
193
+ pcm = _pcm16(chunk.audio)
194
+ if pcm:
195
+ yield pcm
196
+ finally:
197
+ if reference_path is not None:
198
+ reference_path.unlink(missing_ok=True)
199
+ _request_lock.release()
200
+
201
+ return StreamingResponse(
202
+ body(),
203
+ media_type="audio/pcm",
204
+ headers={
205
+ "X-Sample-Rate": str(app.state.runtime.sample_rate),
206
+ "X-Sample-Format": "s16le",
207
+ "Cache-Control": "no-store",
208
+ },
209
+ )
210
+
211
+
212
+ def main() -> None:
213
+ parser = argparse.ArgumentParser(
214
+ description="Serve Breeze TTS 2 streaming inference"
215
+ )
216
+ parser.add_argument("model", type=Path)
217
+ parser.add_argument("--host", default="127.0.0.1")
218
+ parser.add_argument("--port", type=int, default=7860)
219
+ parser.add_argument(
220
+ "--fast-all", action=argparse.BooleanOptionalAction, default=None
221
+ )
222
+ parser.add_argument(
223
+ "--fast-text-encoder", action=argparse.BooleanOptionalAction, default=False
224
+ )
225
+ parser.add_argument(
226
+ "--fast-backbone-prefill", action=argparse.BooleanOptionalAction, default=False
227
+ )
228
+ parser.add_argument(
229
+ "--fast-backbone-decode", action=argparse.BooleanOptionalAction, default=False
230
+ )
231
+ parser.add_argument(
232
+ "--fast-depth-decoder", action=argparse.BooleanOptionalAction, default=False
233
+ )
234
+ parser.add_argument(
235
+ "--fast-codec", action=argparse.BooleanOptionalAction, default=False
236
+ )
237
+ args = parser.parse_args()
238
+
239
+ global _settings
240
+ _settings = ApiSettings(
241
+ model=args.model,
242
+ fast_all=args.fast_all,
243
+ fast_text_encoder=args.fast_text_encoder,
244
+ fast_backbone_prefill=args.fast_backbone_prefill,
245
+ fast_backbone_decode=args.fast_backbone_decode,
246
+ fast_depth_decoder=args.fast_depth_decoder,
247
+ fast_codec=args.fast_codec,
248
+ )
249
+
250
+ import uvicorn
251
+
252
+ uvicorn.run(app, host=args.host, port=args.port, log_level="info")
253
+
254
+
255
+ if __name__ == "__main__":
256
+ main()
breeze_infer/audio.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Reference-audio loading for Breeze inference."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+ import soundfile as sf
10
+ import torch
11
+
12
+
13
+ def encode_prompt_audio(audio_tokenizer: Any, audio_path: str | Path) -> torch.Tensor:
14
+ wav, sample_rate = sf.read(audio_path, always_2d=True, dtype="float32")
15
+ wav = np.mean(wav, axis=1)
16
+ encoded = audio_tokenizer.encode(wav, sr=sample_rate)
17
+ codes = torch.as_tensor(encoded["audio_codes"][0], dtype=torch.int16)
18
+ if codes.ndim != 2:
19
+ raise ValueError(
20
+ f"Expected 2D audio codes for '{audio_path}', got shape {tuple(codes.shape)}"
21
+ )
22
+ return codes.cpu().contiguous()
breeze_infer/runtime.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import random
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+ import torch
10
+ from transformers import AutoTokenizer
11
+
12
+ from models.breeze import BreezeForConditionalGeneration
13
+
14
+
15
+ def get_dist_info() -> tuple[int, int, int]:
16
+ rank = int(os.environ.get("RANK", "0"))
17
+ world_size = int(os.environ.get("WORLD_SIZE", "1"))
18
+ local_rank = int(os.environ.get("LOCAL_RANK", str(rank)))
19
+ return rank, world_size, local_rank
20
+
21
+
22
+ def resolve_device(explicit_device: str | None = None) -> str:
23
+ if explicit_device:
24
+ return explicit_device
25
+
26
+ _, _, local_rank = get_dist_info()
27
+ if torch.cuda.is_available():
28
+ return f"cuda:{local_rank}"
29
+ return "cpu"
30
+
31
+
32
+ def set_all_seeds(seed: int) -> None:
33
+ random.seed(seed)
34
+ np.random.seed(seed)
35
+ torch.manual_seed(seed)
36
+ if torch.cuda.is_available():
37
+ torch.cuda.manual_seed_all(seed)
38
+
39
+
40
+ def update_generation_config_for_breeze(
41
+ model: torch.nn.Module,
42
+ generation_config: dict[str, Any] | None = None,
43
+ ) -> None:
44
+ generation_config = generation_config or {
45
+ "depth_decoder_do_sample": True,
46
+ "depth_decoder_temperature": 0.9,
47
+ "depth_decoder_top_p": 1.0,
48
+ "depth_decoder_top_k": 50,
49
+ "do_sample": True,
50
+ "top_p": 1.0,
51
+ "top_k": 50,
52
+ "max_new_tokens": 750,
53
+ "temperature": 0.9,
54
+ }
55
+
56
+ prefix = "depth_decoder_"
57
+ depth_decoder_attrs = {
58
+ attr[len(prefix) :]: value
59
+ for attr, value in generation_config.items()
60
+ if attr.startswith(prefix)
61
+ }
62
+ vars(model.depth_decoder.generation_config).update(
63
+ {"_from_model_config": False, **depth_decoder_attrs}
64
+ )
65
+ vars(model.generation_config).update(generation_config)
66
+
67
+
68
+ def load_runtime(
69
+ ckpt_dir: Path,
70
+ *,
71
+ device: str,
72
+ attn_implementation: str,
73
+ ) -> tuple[AutoTokenizer, BreezeForConditionalGeneration, Any]:
74
+
75
+ if device.startswith("cuda"):
76
+ try:
77
+ torch.cuda.set_device(device)
78
+ except Exception as exc:
79
+ rank, world_size, local_rank = get_dist_info()
80
+ raise RuntimeError(
81
+ "Failed to set CUDA device "
82
+ f"device={device} rank={rank} world_size={world_size} local_rank={local_rank} "
83
+ f"CUDA_VISIBLE_DEVICES={os.environ.get('CUDA_VISIBLE_DEVICES')} "
84
+ f"device_count={torch.cuda.device_count()}"
85
+ ) from exc
86
+ tokenizer = AutoTokenizer.from_pretrained(ckpt_dir)
87
+ model = BreezeForConditionalGeneration.from_pretrained(
88
+ ckpt_dir,
89
+ dtype=torch.bfloat16,
90
+ attn_implementation=attn_implementation,
91
+ )
92
+ model.to(device).eval()
93
+
94
+ from qwen_tts import Qwen3TTSTokenizer
95
+
96
+ bundled_audio_tokenizer = ckpt_dir / "audio_tokenizer"
97
+ if not bundled_audio_tokenizer.is_dir():
98
+ raise FileNotFoundError(
99
+ "Bundled audio tokenizer not found at "
100
+ f"{bundled_audio_tokenizer}. The Breeze model package must include "
101
+ "the audio_tokenizer directory."
102
+ )
103
+ audio_tokenizer = Qwen3TTSTokenizer.from_pretrained(
104
+ str(bundled_audio_tokenizer), device_map=device
105
+ )
106
+ return tokenizer, model, audio_tokenizer
breeze_infer/templates.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ import torch
8
+ import torch.nn.functional as F
9
+
10
+ from .audio import encode_prompt_audio
11
+
12
+ AUDIO_TAG = "<|AUDIO|>"
13
+ AUDIO_EOS = "<|audio_eos|>"
14
+ INSTRUCTION_BOS = "<ins_bos>"
15
+ INSTRUCTION_EOS = "<ins_eos>"
16
+
17
+
18
+ Segment = dict[str, Any]
19
+ Request = dict[str, Any]
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class TemplateSpec:
24
+ name: str
25
+ required_fields: tuple[str, ...]
26
+ build_segments: Callable[[Request], list[Segment]]
27
+ build_negative_segments: Callable[[Request], list[Segment]] | None = None
28
+ build_dual_branches: Callable[[Request], dict[str, list[Segment]]] | None = None
29
+
30
+
31
+ def _speaker_prefix(request: Request) -> str:
32
+ speaker = request.get("speaker", "S0")
33
+ if speaker in (None, ""):
34
+ return ""
35
+ if isinstance(speaker, str) and speaker.startswith("[") and speaker.endswith("]"):
36
+ return speaker
37
+ return f"[{speaker}]"
38
+
39
+
40
+ def _tts_plain_segments(request: Request) -> list[Segment]:
41
+ return [{"type": "text", "text": f"{_speaker_prefix(request)}{request['text']}"}]
42
+
43
+
44
+ def _tts_instruction_segments(request: Request) -> list[Segment]:
45
+ prefix = _speaker_prefix(request)
46
+ return [
47
+ {
48
+ "type": "text",
49
+ "text": f"{prefix}{INSTRUCTION_BOS}{request['instruction']}{INSTRUCTION_EOS}{request['text']}",
50
+ }
51
+ ]
52
+
53
+
54
+ def _tts_instruction_negative_segments(request: Request) -> list[Segment]:
55
+ return _tts_plain_segments(request)
56
+
57
+
58
+ def _ref_audio_segment(
59
+ request: Request,
60
+ *,
61
+ append_eos: bool = True,
62
+ drop_last_frame: bool = False,
63
+ ) -> Segment:
64
+ segment: Segment = {
65
+ "type": "audio",
66
+ "append_eos": append_eos,
67
+ "drop_last_frame": drop_last_frame,
68
+ }
69
+ if request.get("ref_audio_path"):
70
+ segment["audio_path"] = request["ref_audio_path"]
71
+ return segment
72
+
73
+
74
+ def _ref_clone_tata_segments(request: Request) -> list[Segment]:
75
+ prefix = _speaker_prefix(request)
76
+ return [
77
+ {"type": "text", "text": f"{prefix}{request['ref_text']}"},
78
+ _ref_audio_segment(request),
79
+ {"type": "text", "text": f"{prefix}{request['text']}"},
80
+ ]
81
+
82
+
83
+ def _ref_edit_tata_segments(request: Request) -> list[Segment]:
84
+ prefix = _speaker_prefix(request)
85
+ return [
86
+ {"type": "text", "text": f"{prefix}{request['ref_text']}"},
87
+ _ref_audio_segment(request),
88
+ {
89
+ "type": "text",
90
+ "text": f"{prefix}{INSTRUCTION_BOS}{request['instruction']}{INSTRUCTION_EOS}{request['text']}",
91
+ },
92
+ ]
93
+
94
+
95
+ def _ref_edit_tata_negative_segments(request: Request) -> list[Segment]:
96
+ return _ref_clone_tata_segments(request)
97
+
98
+
99
+ def _ref_edit_tata_dual_branches(request: Request) -> dict[str, list[Segment]]:
100
+ prefix = _speaker_prefix(request)
101
+ return {
102
+ "uncond": [{"type": "text", "text": f"{prefix}{request['text']}"}],
103
+ "ref": _ref_clone_tata_segments(request),
104
+ "ins": _tts_instruction_segments(request),
105
+ }
106
+
107
+
108
+ TEMPLATES: dict[str, TemplateSpec] = {
109
+ "tts_instruction": TemplateSpec(
110
+ name="tts_instruction",
111
+ required_fields=("text", "instruction"),
112
+ build_segments=_tts_instruction_segments,
113
+ build_negative_segments=_tts_instruction_negative_segments,
114
+ ),
115
+ "ref_edit_tata": TemplateSpec(
116
+ name="ref_edit_tata",
117
+ required_fields=("text", "instruction", "ref_audio_path", "ref_text"),
118
+ build_segments=_ref_edit_tata_segments,
119
+ build_negative_segments=_ref_edit_tata_negative_segments,
120
+ build_dual_branches=_ref_edit_tata_dual_branches,
121
+ ),
122
+ }
123
+
124
+
125
+ def get_template(name: str) -> TemplateSpec:
126
+ try:
127
+ return TEMPLATES[name]
128
+ except KeyError as exc:
129
+ raise KeyError(
130
+ f"Unknown template '{name}'. Available: {sorted(TEMPLATES)}"
131
+ ) from exc
132
+
133
+
134
+ def _encode_prompt_audio(audio_tokenizer: Any, audio_path: str) -> torch.Tensor:
135
+ return encode_prompt_audio(audio_tokenizer, audio_path)
136
+
137
+
138
+ def _resolve_segment_audio_codes(
139
+ audio_tokenizer: Any, segment: Segment
140
+ ) -> torch.Tensor:
141
+ audio_path = segment.get("audio_path")
142
+ if not audio_path:
143
+ raise ValueError("Audio segment must include audio_path")
144
+ return _encode_prompt_audio(audio_tokenizer, audio_path)
145
+
146
+
147
+ def _prepare_one(
148
+ tokenizer: Any,
149
+ audio_tokenizer: Any,
150
+ model_config: Any,
151
+ segments: list[Segment],
152
+ ) -> dict[str, torch.Tensor]:
153
+ rendered_segments: list[dict[str, str]] = []
154
+ audio_tokens_list: list[torch.Tensor] = []
155
+
156
+ for segment in segments:
157
+ segment_type = segment["type"]
158
+ if segment_type == "text":
159
+ encoded = tokenizer(segment["text"], add_special_tokens=True)
160
+ rendered = tokenizer.decode(encoded["input_ids"], skip_special_tokens=False)
161
+ rendered_segments.append({"type": "text", "value": rendered})
162
+ continue
163
+
164
+ if segment_type != "audio":
165
+ raise ValueError(f"Unknown segment type: {segment_type}")
166
+
167
+ codes = _resolve_segment_audio_codes(audio_tokenizer, segment)
168
+ if segment.get("drop_last_frame", False):
169
+ if codes.shape[0] <= 1:
170
+ raise ValueError(
171
+ "Cannot drop the last frame from an audio segment with <= 1 frame"
172
+ )
173
+ codes = codes[:-1].contiguous()
174
+ audio_tokens_list.append(codes)
175
+
176
+ placeholders = AUDIO_TAG * codes.shape[0]
177
+ if segment.get("append_eos", False):
178
+ placeholders += AUDIO_EOS
179
+ rendered_segments.append({"type": "audio", "value": placeholders})
180
+
181
+ final_text = "".join(segment["value"] for segment in rendered_segments)
182
+ encoded = tokenizer(final_text, add_special_tokens=False, return_tensors="pt")
183
+
184
+ text_ids_mask: list[bool] = []
185
+ text_ids_len: list[int] = []
186
+ for segment in rendered_segments:
187
+ segment_len = len(
188
+ tokenizer(segment["value"], add_special_tokens=False)["input_ids"]
189
+ )
190
+ if segment["type"] == "text":
191
+ text_ids_mask.extend([True] * segment_len)
192
+ text_ids_len.append(segment_len)
193
+ else:
194
+ text_ids_mask.extend([False] * segment_len)
195
+
196
+ num_codebooks = getattr(model_config, "num_codebooks", 16)
197
+ if audio_tokens_list:
198
+ audio_tokens = torch.cat(audio_tokens_list, dim=0).unsqueeze(0)
199
+ else:
200
+ audio_tokens = torch.zeros((1, 0, num_codebooks), dtype=torch.int16)
201
+
202
+ encoded["audio_tokens"] = audio_tokens
203
+ encoded["text_ids_mask"] = torch.tensor([text_ids_mask], dtype=torch.bool)
204
+ encoded["text_ids_len"] = torch.tensor(text_ids_len, dtype=torch.long)
205
+ return encoded
206
+
207
+
208
+ def _collate_inputs(
209
+ tokenizer: Any, inputs_list: list[dict[str, torch.Tensor]], device: str
210
+ ) -> dict[str, torch.Tensor | None]:
211
+ pad_token_id = tokenizer.pad_token_id
212
+ if pad_token_id is None:
213
+ pad_token_id = tokenizer.eos_token_id
214
+ if pad_token_id is None:
215
+ raise ValueError("Tokenizer has no pad_token_id or eos_token_id")
216
+
217
+ audio_tokens = torch.cat([item["audio_tokens"] for item in inputs_list], dim=1)
218
+ max_len = max(item["input_ids"].shape[1] for item in inputs_list)
219
+
220
+ input_ids_list = []
221
+ attention_mask_list = []
222
+ text_ids_mask_list = []
223
+ for item in inputs_list:
224
+ input_ids = item["input_ids"]
225
+ attention_mask = item["attention_mask"]
226
+ text_ids_mask = item["text_ids_mask"]
227
+ pad_len = max_len - input_ids.shape[1]
228
+ if pad_len > 0:
229
+ input_ids = F.pad(input_ids, (pad_len, 0), value=pad_token_id)
230
+ attention_mask = F.pad(attention_mask, (pad_len, 0), value=0)
231
+ text_ids_mask = F.pad(text_ids_mask, (pad_len, 0), value=False)
232
+ input_ids_list.append(input_ids)
233
+ attention_mask_list.append(attention_mask)
234
+ text_ids_mask_list.append(text_ids_mask)
235
+
236
+ input_values = audio_tokens.to(device) if audio_tokens.shape[1] > 0 else None
237
+ return {
238
+ "input_ids": torch.cat(input_ids_list, dim=0).to(device),
239
+ "attention_mask": torch.cat(attention_mask_list, dim=0).to(device),
240
+ "text_ids_mask": torch.cat(text_ids_mask_list, dim=0).to(device),
241
+ "text_ids_len": torch.cat(
242
+ [item["text_ids_len"] for item in inputs_list], dim=0
243
+ ).to(device),
244
+ "input_values": input_values,
245
+ }
246
+
247
+
248
+ def _prepare_segment_batches(
249
+ tokenizer: Any,
250
+ audio_tokenizer: Any,
251
+ model_config: Any,
252
+ device: str,
253
+ segment_batches: list[list[Segment]],
254
+ ) -> dict[str, torch.Tensor | None]:
255
+ inputs_list = [
256
+ _prepare_one(tokenizer, audio_tokenizer, model_config, segments)
257
+ for segments in segment_batches
258
+ ]
259
+ return _collate_inputs(tokenizer, inputs_list, device)
260
+
261
+
262
+ def prepare_inputs(
263
+ tokenizer: Any,
264
+ audio_tokenizer: Any,
265
+ model: Any,
266
+ requests: list[Request],
267
+ template: TemplateSpec,
268
+ *,
269
+ guidance_scale: float,
270
+ guidance_scale_ref: float | None,
271
+ guidance_scale_ins: float | None,
272
+ ) -> dict[str, torch.Tensor | None | float]:
273
+ for request in requests:
274
+ missing = []
275
+ for field in template.required_fields:
276
+ if not request.get(field):
277
+ missing.append(field)
278
+ if missing:
279
+ raise ValueError(
280
+ f"Request {request.get('id')} missing template fields: {missing}"
281
+ )
282
+
283
+ positive_segments = [template.build_segments(request) for request in requests]
284
+ inputs = _prepare_segment_batches(
285
+ tokenizer,
286
+ audio_tokenizer,
287
+ model.config,
288
+ model.device,
289
+ positive_segments,
290
+ )
291
+
292
+ use_dual_cfg = (
293
+ guidance_scale_ref is not None
294
+ and guidance_scale_ins is not None
295
+ and template.build_dual_branches is not None
296
+ )
297
+ if use_dual_cfg:
298
+ branch_batches = [template.build_dual_branches(request) for request in requests]
299
+ for branch_name, prefix in [
300
+ ("uncond", "cfg_uncond"),
301
+ ("ref", "cfg_ref"),
302
+ ("ins", "cfg_ins"),
303
+ ]:
304
+ branch_inputs = _prepare_segment_batches(
305
+ tokenizer,
306
+ audio_tokenizer,
307
+ model.config,
308
+ model.device,
309
+ [branches[branch_name] for branches in branch_batches],
310
+ )
311
+ inputs[f"{prefix}_prompt_ids"] = branch_inputs["input_ids"]
312
+ inputs[f"{prefix}_prompt_attention_mask"] = branch_inputs["attention_mask"]
313
+ inputs[f"{prefix}_text_ids_mask"] = branch_inputs["text_ids_mask"]
314
+ inputs[f"{prefix}_text_ids_len"] = branch_inputs["text_ids_len"]
315
+ inputs["cfg_scale_ref"] = guidance_scale_ref
316
+ inputs["cfg_scale_ins"] = guidance_scale_ins
317
+ return inputs
318
+
319
+ if guidance_scale != 1.0:
320
+ if template.build_negative_segments is None:
321
+ raise ValueError(
322
+ f"Template '{template.name}' does not define a negative prompt but cfg_scale={guidance_scale}"
323
+ )
324
+ negative_inputs = _prepare_segment_batches(
325
+ tokenizer,
326
+ audio_tokenizer,
327
+ model.config,
328
+ model.device,
329
+ [template.build_negative_segments(request) for request in requests],
330
+ )
331
+ inputs["cfg_negative_prompt_ids"] = negative_inputs["input_ids"]
332
+ inputs["cfg_negative_prompt_attention_mask"] = negative_inputs["attention_mask"]
333
+ inputs["cfg_negative_text_ids_mask"] = negative_inputs["text_ids_mask"]
334
+ inputs["cfg_negative_text_ids_len"] = negative_inputs["text_ids_len"]
335
+ if negative_inputs["input_values"] is not None:
336
+ inputs["cfg_negative_input_values"] = negative_inputs["input_values"]
337
+ inputs["cfg_scale"] = guidance_scale
338
+
339
+ return inputs
configs/fast.json ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": 1,
3
+ "name": "fast",
4
+ "service": {
5
+ "concurrency": 1,
6
+ "freeze_after_warmup": true,
7
+ "cfg_scales": [
8
+ 1.0,
9
+ 4.0
10
+ ]
11
+ },
12
+ "stages": {
13
+ "text_encoder": {
14
+ "graphs": [
15
+ {
16
+ "batch_size": 1,
17
+ "token_length": 32
18
+ },
19
+ {
20
+ "batch_size": 1,
21
+ "token_length": 64
22
+ },
23
+ {
24
+ "batch_size": 1,
25
+ "token_length": 96
26
+ },
27
+ {
28
+ "batch_size": 1,
29
+ "token_length": 128
30
+ },
31
+ {
32
+ "batch_size": 1,
33
+ "token_length": 160
34
+ },
35
+ {
36
+ "batch_size": 1,
37
+ "token_length": 192
38
+ },
39
+ {
40
+ "batch_size": 1,
41
+ "token_length": 224
42
+ },
43
+ {
44
+ "batch_size": 1,
45
+ "token_length": 256
46
+ },
47
+ {
48
+ "batch_size": 2,
49
+ "token_length": 32
50
+ },
51
+ {
52
+ "batch_size": 2,
53
+ "token_length": 64
54
+ },
55
+ {
56
+ "batch_size": 2,
57
+ "token_length": 96
58
+ },
59
+ {
60
+ "batch_size": 2,
61
+ "token_length": 128
62
+ },
63
+ {
64
+ "batch_size": 2,
65
+ "token_length": 160
66
+ },
67
+ {
68
+ "batch_size": 2,
69
+ "token_length": 192
70
+ },
71
+ {
72
+ "batch_size": 2,
73
+ "token_length": 224
74
+ },
75
+ {
76
+ "batch_size": 2,
77
+ "token_length": 256
78
+ },
79
+ {
80
+ "batch_size": 2,
81
+ "token_length": 288
82
+ },
83
+ {
84
+ "batch_size": 2,
85
+ "token_length": 320
86
+ },
87
+ {
88
+ "batch_size": 2,
89
+ "token_length": 352
90
+ },
91
+ {
92
+ "batch_size": 2,
93
+ "token_length": 384
94
+ },
95
+ {
96
+ "batch_size": 2,
97
+ "token_length": 416
98
+ },
99
+ {
100
+ "batch_size": 2,
101
+ "token_length": 448
102
+ },
103
+ {
104
+ "batch_size": 2,
105
+ "token_length": 480
106
+ },
107
+ {
108
+ "batch_size": 2,
109
+ "token_length": 512
110
+ }
111
+ ]
112
+ },
113
+ "backbone_prefill": {
114
+ "graphs": [
115
+ {
116
+ "branch_batch_size": 1,
117
+ "sequence_length": 32
118
+ },
119
+ {
120
+ "branch_batch_size": 1,
121
+ "sequence_length": 64
122
+ },
123
+ {
124
+ "branch_batch_size": 1,
125
+ "sequence_length": 96
126
+ },
127
+ {
128
+ "branch_batch_size": 1,
129
+ "sequence_length": 128
130
+ },
131
+ {
132
+ "branch_batch_size": 1,
133
+ "sequence_length": 160
134
+ },
135
+ {
136
+ "branch_batch_size": 1,
137
+ "sequence_length": 192
138
+ },
139
+ {
140
+ "branch_batch_size": 1,
141
+ "sequence_length": 224
142
+ },
143
+ {
144
+ "branch_batch_size": 1,
145
+ "sequence_length": 256
146
+ },
147
+ {
148
+ "branch_batch_size": 2,
149
+ "sequence_length": 32
150
+ },
151
+ {
152
+ "branch_batch_size": 2,
153
+ "sequence_length": 64
154
+ },
155
+ {
156
+ "branch_batch_size": 2,
157
+ "sequence_length": 96
158
+ },
159
+ {
160
+ "branch_batch_size": 2,
161
+ "sequence_length": 128
162
+ },
163
+ {
164
+ "branch_batch_size": 2,
165
+ "sequence_length": 160
166
+ },
167
+ {
168
+ "branch_batch_size": 2,
169
+ "sequence_length": 192
170
+ },
171
+ {
172
+ "branch_batch_size": 2,
173
+ "sequence_length": 224
174
+ },
175
+ {
176
+ "branch_batch_size": 2,
177
+ "sequence_length": 256
178
+ },
179
+ {
180
+ "branch_batch_size": 2,
181
+ "sequence_length": 288
182
+ },
183
+ {
184
+ "branch_batch_size": 2,
185
+ "sequence_length": 320
186
+ },
187
+ {
188
+ "branch_batch_size": 2,
189
+ "sequence_length": 352
190
+ },
191
+ {
192
+ "branch_batch_size": 2,
193
+ "sequence_length": 384
194
+ },
195
+ {
196
+ "branch_batch_size": 2,
197
+ "sequence_length": 416
198
+ },
199
+ {
200
+ "branch_batch_size": 2,
201
+ "sequence_length": 448
202
+ },
203
+ {
204
+ "branch_batch_size": 2,
205
+ "sequence_length": 480
206
+ },
207
+ {
208
+ "branch_batch_size": 2,
209
+ "sequence_length": 512
210
+ }
211
+ ]
212
+ },
213
+ "backbone_decode": {
214
+ "graphs": [
215
+ {
216
+ "branch_batch_size": 1
217
+ },
218
+ {
219
+ "branch_batch_size": 2
220
+ }
221
+ ]
222
+ },
223
+ "depth_decoder": {
224
+ "graphs": [
225
+ {
226
+ "batch_size": 1
227
+ },
228
+ {
229
+ "batch_size": 2
230
+ }
231
+ ]
232
+ },
233
+ "codec": {
234
+ "graphs": [
235
+ {
236
+ "num_lanes": 1,
237
+ "chunk_frames": 1
238
+ }
239
+ ]
240
+ }
241
+ },
242
+ "warmup_request": {
243
+ "template": "tts_instruction",
244
+ "text": "The cat sat on the mat.",
245
+ "instruction": "Speak naturally and clearly.",
246
+ "speaker": "S0",
247
+ "seed": 42
248
+ }
249
+ }
models/__init__.py ADDED
File without changes
models/breeze.py ADDED
@@ -0,0 +1,1888 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Breeze model implementation adapted from Hugging Face Transformers.
2
+ #
3
+ # coding=utf-8
4
+ # Copyright 2025 Sesame and The HuggingFace Inc. team. All rights reserved.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ from __future__ import annotations
19
+
20
+ from collections.abc import Callable
21
+
22
+ from dataclasses import dataclass
23
+
24
+ import torch
25
+ from torch import nn
26
+ from transformers.activations import ACT2FN
27
+ from transformers.cache_utils import Cache, DynamicCache
28
+ from transformers.generation import GenerationMixin
29
+ from transformers.integrations import use_kernel_forward_from_hub
30
+ from transformers.masking_utils import create_causal_mask
31
+ from transformers.modeling_layers import GradientCheckpointingLayer
32
+ from transformers.modeling_outputs import (
33
+ BaseModelOutputWithPast,
34
+ CausalLMOutputWithPast,
35
+ )
36
+ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
37
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
38
+ from transformers.models.auto import AutoModel
39
+ from transformers.processing_utils import Unpack
40
+ from transformers.utils import (
41
+ ModelOutput,
42
+ TransformersKwargs,
43
+ auto_docstring,
44
+ can_return_tuple,
45
+ logging,
46
+ )
47
+ from transformers.utils.deprecation import deprecate_kwarg
48
+
49
+ from .breeze_config import BreezeConfig, BreezeDepthDecoderConfig
50
+ from .generation_breeze import BreezeGenerationMixin
51
+
52
+ logger = logging.get_logger(__name__)
53
+
54
+
55
+ @dataclass
56
+ @auto_docstring(
57
+ custom_intro="""
58
+ Base class for the model autoregressive outputs.
59
+ """
60
+ )
61
+ class BreezeOutputWithPast(ModelOutput):
62
+ r"""
63
+ loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `labels` is provided):
64
+ Language modeling loss (for next-token prediction).
65
+ logits (`torch.FloatTensor` of shape `(batch_size, sequence_length, config.vocab_size)`):
66
+ Prediction scores of the language modeling head (scores for each vocabulary token before SoftMax).
67
+ past_key_values (`Cache`, *optional*, returned when `use_cache=True` is passed or when `config.use_cache=True`):
68
+ Tuple of `tuple(torch.FloatTensor)` of length `config.n_layers`, with each tuple having 2 tensors of shape
69
+ `(batch_size, num_heads, sequence_length, embed_size_per_head)`)
70
+
71
+ Contains pre-computed hidden-states (key and values in the self-attention blocks) that can be used (see
72
+ `past_key_values` input) to speed up sequential decoding.
73
+ depth_decoder_loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `labels` is provided):
74
+ Language modeling loss (for next-token prediction) of the depth decoder model.
75
+ depth_decoder_logits (`torch.FloatTensor` of shape `(batch_size, sequence_length, config.vocab_size)`):
76
+ Prediction scores of the depth decoder (scores for each vocabulary token before SoftMax).
77
+ depth_decoder_past_key_values (`tuple(tuple(torch.FloatTensor))`, *optional*, returned when `use_cache=True` is passed or when `config.use_cache=True`):
78
+ Tuple of `tuple(torch.FloatTensor)` of length `config.n_layers`, with each tuple having 2 tensors of shape
79
+ `(batch_size, num_heads, sequence_length, embed_size_per_head)`)
80
+ depth_decoder_hidden_states (`tuple(torch.FloatTensor)`, *optional*, returned when `output_hidden_states=True` is passed or when `config.output_hidden_states=True`):
81
+ Tuple of `torch.FloatTensor` (one for the output of the embeddings, if the model has an embedding layer, +
82
+ one for the output of each layer) of shape `(batch_size, sequence_length, hidden_size)`.
83
+
84
+ Hidden-states of the model at the output of each layer plus the optional initial embedding outputs.
85
+ depth_decoder_attentions (`tuple(torch.FloatTensor)`, *optional*, returned when `output_attentions=True` is passed or when `config.output_attentions=True`):
86
+ Tuple of `torch.FloatTensor` (one for each layer) of shape `(batch_size, num_heads, sequence_length,
87
+ sequence_length)`.
88
+ backbone_loss (`torch.FloatTensor` of shape `(1,)`, *optional*, returned when `labels` is provided):
89
+ Language modeling loss (for next-token prediction) of the backbone model.
90
+ """
91
+
92
+ loss: torch.FloatTensor | None = None
93
+ logits: torch.FloatTensor = None
94
+ past_key_values: tuple[tuple[torch.FloatTensor]] | None = None
95
+ hidden_states: tuple[torch.FloatTensor, ...] | None = None
96
+ attentions: tuple[torch.FloatTensor, ...] | None = None
97
+ depth_decoder_loss: torch.FloatTensor | None = None
98
+ depth_decoder_logits: torch.FloatTensor = None
99
+ depth_decoder_past_key_values: tuple[tuple[torch.FloatTensor]] | None = None
100
+ depth_decoder_hidden_states: tuple[torch.FloatTensor, ...] | None = None
101
+ depth_decoder_attentions: tuple[torch.FloatTensor, ...] | None = None
102
+ backbone_loss: torch.FloatTensor | None = None
103
+
104
+
105
+ @use_kernel_forward_from_hub("RMSNorm")
106
+ class BreezeRMSNorm(nn.Module):
107
+ def __init__(self, hidden_size, eps=1e-6):
108
+ """
109
+ BreezeRMSNorm is equivalent to T5LayerNorm
110
+ """
111
+ super().__init__()
112
+ self.weight = nn.Parameter(torch.ones(hidden_size))
113
+ self.variance_epsilon = eps
114
+
115
+ def forward(self, hidden_states):
116
+ input_dtype = hidden_states.dtype
117
+ hidden_states = hidden_states.to(torch.float32)
118
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
119
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
120
+ return self.weight * hidden_states.to(input_dtype)
121
+
122
+ def extra_repr(self):
123
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
124
+
125
+
126
+ class BreezeRotaryEmbedding(nn.Module):
127
+ inv_freq: torch.Tensor # fix linting for `register_buffer`
128
+
129
+ def __init__(self, config: BreezeConfig, device=None):
130
+ super().__init__()
131
+ # BC: "rope_type" was originally "type"
132
+ if hasattr(config, "rope_scaling") and isinstance(config.rope_scaling, dict):
133
+ self.rope_type = config.rope_scaling.get(
134
+ "rope_type", config.rope_scaling.get("type")
135
+ )
136
+ else:
137
+ self.rope_type = "default"
138
+ self.max_seq_len_cached = config.max_position_embeddings
139
+ self.original_max_seq_len = config.max_position_embeddings
140
+
141
+ self.config = config
142
+ self.rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
143
+
144
+ inv_freq, self.attention_scaling = self.rope_init_fn(self.config, device)
145
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
146
+ self.original_inv_freq = self.inv_freq
147
+
148
+ @torch.no_grad()
149
+ @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
150
+ def forward(self, x, position_ids):
151
+ inv_freq_expanded = (
152
+ self.inv_freq[None, :, None]
153
+ .float()
154
+ .expand(position_ids.shape[0], -1, 1)
155
+ .to(x.device)
156
+ )
157
+ position_ids_expanded = position_ids[:, None, :].float()
158
+
159
+ device_type = (
160
+ x.device.type
161
+ if isinstance(x.device.type, str) and x.device.type != "mps"
162
+ else "cpu"
163
+ )
164
+ with torch.autocast(device_type=device_type, enabled=False): # Force float32
165
+ freqs = (
166
+ inv_freq_expanded.float() @ position_ids_expanded.float()
167
+ ).transpose(1, 2)
168
+ emb = torch.cat((freqs, freqs), dim=-1)
169
+ cos = emb.cos() * self.attention_scaling
170
+ sin = emb.sin() * self.attention_scaling
171
+
172
+ return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
173
+
174
+
175
+ class BreezeMLP(nn.Module):
176
+ def __init__(self, config):
177
+ super().__init__()
178
+ self.config = config
179
+ self.hidden_size = config.hidden_size
180
+ self.intermediate_size = config.intermediate_size
181
+ self.gate_proj = nn.Linear(
182
+ self.hidden_size, self.intermediate_size, bias=config.mlp_bias
183
+ )
184
+ self.up_proj = nn.Linear(
185
+ self.hidden_size, self.intermediate_size, bias=config.mlp_bias
186
+ )
187
+ self.down_proj = nn.Linear(
188
+ self.intermediate_size, self.hidden_size, bias=config.mlp_bias
189
+ )
190
+ self.act_fn = ACT2FN[config.hidden_act]
191
+
192
+ def forward(self, x):
193
+ down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
194
+ return down_proj
195
+
196
+
197
+ def rotate_half(x):
198
+ """Rotates half the hidden dims of the input."""
199
+ x1 = x[..., : x.shape[-1] // 2]
200
+ x2 = x[..., x.shape[-1] // 2 :]
201
+ return torch.cat((-x2, x1), dim=-1)
202
+
203
+
204
+ def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
205
+ """Applies Rotary Position Embedding to the query and key tensors.
206
+
207
+ Args:
208
+ q (`torch.Tensor`): The query tensor.
209
+ k (`torch.Tensor`): The key tensor.
210
+ cos (`torch.Tensor`): The cosine part of the rotary embedding.
211
+ sin (`torch.Tensor`): The sine part of the rotary embedding.
212
+ position_ids (`torch.Tensor`, *optional*):
213
+ Deprecated and unused.
214
+ unsqueeze_dim (`int`, *optional*, defaults to 1):
215
+ The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
216
+ sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
217
+ that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
218
+ k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
219
+ cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
220
+ the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
221
+ Returns:
222
+ `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
223
+ """
224
+ cos = cos.unsqueeze(unsqueeze_dim)
225
+ sin = sin.unsqueeze(unsqueeze_dim)
226
+ q_embed = (q * cos) + (rotate_half(q) * sin)
227
+ k_embed = (k * cos) + (rotate_half(k) * sin)
228
+ return q_embed, k_embed
229
+
230
+
231
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
232
+ """
233
+ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
234
+ num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
235
+ """
236
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
237
+ if n_rep == 1:
238
+ return hidden_states
239
+ hidden_states = hidden_states[:, :, None, :, :].expand(
240
+ batch, num_key_value_heads, n_rep, slen, head_dim
241
+ )
242
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
243
+
244
+
245
+ def eager_attention_forward(
246
+ module: nn.Module,
247
+ query: torch.Tensor,
248
+ key: torch.Tensor,
249
+ value: torch.Tensor,
250
+ attention_mask: torch.Tensor | None,
251
+ scaling: float,
252
+ dropout: float = 0.0,
253
+ **kwargs: Unpack[TransformersKwargs],
254
+ ):
255
+ key_states = repeat_kv(key, module.num_key_value_groups)
256
+ value_states = repeat_kv(value, module.num_key_value_groups)
257
+
258
+ attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
259
+ if attention_mask is not None:
260
+ causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
261
+ attn_weights = attn_weights + causal_mask
262
+
263
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(
264
+ query.dtype
265
+ )
266
+ attn_weights = nn.functional.dropout(
267
+ attn_weights, p=dropout, training=module.training
268
+ )
269
+ attn_output = torch.matmul(attn_weights, value_states)
270
+ attn_output = attn_output.transpose(1, 2).contiguous()
271
+
272
+ return attn_output, attn_weights
273
+
274
+
275
+ class BreezeAttention(nn.Module):
276
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
277
+
278
+ def __init__(self, config: BreezeConfig, layer_idx: int):
279
+ super().__init__()
280
+ self.config = config
281
+ self.layer_idx = layer_idx
282
+ self.head_dim = getattr(
283
+ config, "head_dim", config.hidden_size // config.num_attention_heads
284
+ )
285
+ self.num_key_value_groups = (
286
+ config.num_attention_heads // config.num_key_value_heads
287
+ )
288
+ self.scaling = self.head_dim**-0.5
289
+ self.attention_dropout = config.attention_dropout
290
+ self.is_causal = True
291
+
292
+ self.q_proj = nn.Linear(
293
+ config.hidden_size,
294
+ config.num_attention_heads * self.head_dim,
295
+ bias=config.attention_bias,
296
+ )
297
+ self.k_proj = nn.Linear(
298
+ config.hidden_size,
299
+ config.num_key_value_heads * self.head_dim,
300
+ bias=config.attention_bias,
301
+ )
302
+ self.v_proj = nn.Linear(
303
+ config.hidden_size,
304
+ config.num_key_value_heads * self.head_dim,
305
+ bias=config.attention_bias,
306
+ )
307
+ self.o_proj = nn.Linear(
308
+ config.num_attention_heads * self.head_dim,
309
+ config.hidden_size,
310
+ bias=config.attention_bias,
311
+ )
312
+
313
+ @deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
314
+ def forward(
315
+ self,
316
+ hidden_states: torch.Tensor,
317
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
318
+ attention_mask: torch.Tensor | None,
319
+ past_key_values: Cache | None = None,
320
+ cache_position: torch.LongTensor | None = None,
321
+ **kwargs: Unpack[TransformersKwargs],
322
+ ) -> tuple[torch.Tensor, torch.Tensor]:
323
+ input_shape = hidden_states.shape[:-1]
324
+ hidden_shape = (*input_shape, -1, self.head_dim)
325
+
326
+ query_states = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
327
+ key_states = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
328
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
329
+
330
+ cos, sin = position_embeddings
331
+ query_states, key_states = apply_rotary_pos_emb(
332
+ query_states, key_states, cos, sin
333
+ )
334
+
335
+ if past_key_values is not None:
336
+ # sin and cos are specific to RoPE models; cache_position needed for the static cache
337
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
338
+ key_states, value_states = past_key_values.update(
339
+ key_states, value_states, self.layer_idx, cache_kwargs
340
+ )
341
+
342
+ attention_interface: Callable = eager_attention_forward
343
+ if self.config._attn_implementation != "eager":
344
+ attention_interface = ALL_ATTENTION_FUNCTIONS[
345
+ self.config._attn_implementation
346
+ ]
347
+
348
+ attn_output, attn_weights = attention_interface(
349
+ self,
350
+ query_states,
351
+ key_states,
352
+ value_states,
353
+ attention_mask,
354
+ dropout=0.0 if not self.training else self.attention_dropout,
355
+ scaling=self.scaling,
356
+ **kwargs,
357
+ )
358
+
359
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
360
+ attn_output = self.o_proj(attn_output)
361
+ return attn_output, attn_weights
362
+
363
+
364
+ class BreezeDecoderLayer(GradientCheckpointingLayer):
365
+ def __init__(self, config: BreezeConfig, layer_idx: int):
366
+ super().__init__()
367
+ self.hidden_size = config.hidden_size
368
+
369
+ self.self_attn = BreezeAttention(config=config, layer_idx=layer_idx)
370
+
371
+ self.mlp = BreezeMLP(config)
372
+ self.input_layernorm = BreezeRMSNorm(
373
+ config.hidden_size, eps=config.rms_norm_eps
374
+ )
375
+ self.post_attention_layernorm = BreezeRMSNorm(
376
+ config.hidden_size, eps=config.rms_norm_eps
377
+ )
378
+
379
+ @deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
380
+ def forward(
381
+ self,
382
+ hidden_states: torch.Tensor,
383
+ attention_mask: torch.Tensor | None = None,
384
+ position_ids: torch.LongTensor | None = None,
385
+ past_key_values: Cache | None = None,
386
+ use_cache: bool | None = False,
387
+ cache_position: torch.LongTensor | None = None,
388
+ position_embeddings: tuple[torch.Tensor, torch.Tensor] | None = None, # necessary, but kept here for BC
389
+ **kwargs: Unpack[TransformersKwargs],
390
+ ) -> torch.Tensor:
391
+ residual = hidden_states
392
+ hidden_states = self.input_layernorm(hidden_states)
393
+ # Self Attention
394
+ hidden_states, _ = self.self_attn(
395
+ hidden_states=hidden_states,
396
+ attention_mask=attention_mask,
397
+ position_ids=position_ids,
398
+ past_key_values=past_key_values,
399
+ use_cache=use_cache,
400
+ cache_position=cache_position,
401
+ position_embeddings=position_embeddings,
402
+ **kwargs,
403
+ )
404
+ hidden_states = residual + hidden_states
405
+
406
+ # Fully Connected
407
+ residual = hidden_states
408
+ hidden_states = self.post_attention_layernorm(hidden_states)
409
+ hidden_states = self.mlp(hidden_states)
410
+ hidden_states = residual + hidden_states
411
+ return hidden_states
412
+
413
+
414
+ @auto_docstring(
415
+ custom_intro="""
416
+ The bare Breeze Model outputting raw hidden-states without any specific head on top.
417
+ """
418
+ )
419
+ @auto_docstring
420
+ class BreezePreTrainedModel(PreTrainedModel):
421
+ config_class = BreezeConfig
422
+ config: BreezeConfig
423
+ base_model_prefix = "model"
424
+ supports_gradient_checkpointing = True
425
+ _no_split_modules = ["BreezeDecoderLayer"]
426
+ _skip_keys_device_placement = ["past_key_values"]
427
+ _supports_flash_attn = True
428
+ _supports_sdpa = True
429
+ # does not because of Mimi codec model
430
+ # _supports_flex_attn = True
431
+
432
+ _can_compile_fullgraph = True
433
+ _supports_attention_backend = True
434
+ _can_record_outputs = {
435
+ "hidden_states": BreezeDecoderLayer,
436
+ "attentions": BreezeAttention,
437
+ }
438
+
439
+ def _init_weights(self, module):
440
+ super()._init_weights(module)
441
+ if isinstance(module, BreezeCodebooksHead):
442
+ num_codebooks = module.num_codebooks
443
+ for i in range(num_codebooks - 1):
444
+ module.weight.data[i].normal_(
445
+ mean=0.0, std=self.config.initializer_range
446
+ )
447
+
448
+
449
+ @auto_docstring
450
+ class BreezeDepthDecoderModel(BreezePreTrainedModel):
451
+ config: BreezeDepthDecoderConfig
452
+
453
+ def __init__(self, config):
454
+ super().__init__(config)
455
+ self.padding_idx = config.pad_token_id
456
+ self.vocab_size = config.vocab_size
457
+ if hasattr(config, "audio_embed_size") and config.audio_embed_size:
458
+ self.audio_embed_size = config.audio_embed_size
459
+ else:
460
+ self.audio_embed_size = config.backbone_hidden_size
461
+ self.embed_tokens = nn.Embedding(
462
+ (config.num_codebooks * config.vocab_size), self.audio_embed_size
463
+ )
464
+ self.layers = nn.ModuleList(
465
+ [
466
+ BreezeDecoderLayer(config, layer_idx)
467
+ for layer_idx in range(config.num_hidden_layers)
468
+ ]
469
+ )
470
+ self.norm = BreezeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
471
+ self.rotary_emb = BreezeRotaryEmbedding(config=config)
472
+ self.gradient_checkpointing = False
473
+ self.inputs_embeds_projector = nn.Linear(
474
+ self.audio_embed_size, config.hidden_size, bias=False
475
+ )
476
+
477
+ if config.backbone_hidden_size != self.audio_embed_size:
478
+ self.backbone_hidden_state_projector = nn.Linear(
479
+ config.backbone_hidden_size, self.audio_embed_size, bias=False
480
+ )
481
+ else:
482
+ self.backbone_hidden_state_projector = None
483
+
484
+ # Initialize weights and apply final processing
485
+ self.post_init()
486
+
487
+ @auto_docstring
488
+ def forward(
489
+ self,
490
+ input_ids: torch.LongTensor = None,
491
+ backbone_last_hidden_state: torch.FloatTensor | None = None,
492
+ attention_mask: torch.Tensor | None = None,
493
+ position_ids: torch.LongTensor | None = None,
494
+ past_key_values: Cache | None = None,
495
+ inputs_embeds: torch.FloatTensor | None = None,
496
+ use_cache: bool | None = None,
497
+ cache_position: torch.LongTensor | None = None,
498
+ **kwargs: Unpack[TransformersKwargs],
499
+ ) -> tuple | BaseModelOutputWithPast:
500
+ r"""
501
+ backbone_last_hidden_state (`torch.FloatTensor` of shape `(batch_size, backbone_hidden_size)`, *optional*):
502
+ The last hidden state of the backbone model. Such input is required when the first codebook token (the one generated by the backbone model)
503
+ is provided in the `input_ids` argument.
504
+ """
505
+ if position_ids is not None and not torch.compiler.is_compiling():
506
+ logger.warning_once(
507
+ "Custom `position_ids` were provided but will be ignored. Breeze depth decoder automatically determines position_ids "
508
+ "from `cache_position` and as it requires them to be identical across the batch, the provided position_ids will be ignored."
509
+ )
510
+ position_ids = None
511
+ if (input_ids is None) ^ (inputs_embeds is not None):
512
+ raise ValueError(
513
+ "You must specify exactly one of input_ids or inputs_embeds."
514
+ )
515
+
516
+ if use_cache and past_key_values is None:
517
+ past_key_values = DynamicCache(config=self.config)
518
+
519
+ if cache_position is None:
520
+ past_seen_tokens = (
521
+ past_key_values.get_seq_length() if past_key_values is not None else 0
522
+ )
523
+ inputs_seq_length = (
524
+ inputs_embeds.shape[1]
525
+ if inputs_embeds is not None
526
+ else input_ids.shape[1]
527
+ )
528
+ device = (
529
+ inputs_embeds.device if inputs_embeds is not None else input_ids.device
530
+ )
531
+ cache_position = torch.arange(
532
+ past_seen_tokens, past_seen_tokens + inputs_seq_length, device=device
533
+ )
534
+
535
+ if inputs_embeds is None:
536
+ codebook_idxs = torch.clamp(cache_position - 1, min=0)
537
+ offset = codebook_idxs * self.vocab_size
538
+ inputs_embeds = self.embed_tokens(input_ids + offset)
539
+
540
+ input_ids_are_first_codebook = cache_position[0] == 0
541
+ if backbone_last_hidden_state is not None:
542
+ if self.backbone_hidden_state_projector is not None:
543
+ inputs_embeds[:, 0] = self.backbone_hidden_state_projector(
544
+ backbone_last_hidden_state
545
+ )
546
+ else:
547
+ inputs_embeds[:, 0] = backbone_last_hidden_state
548
+ else:
549
+ if not torch.compiler.is_compiling() and input_ids_are_first_codebook:
550
+ logger.warning(
551
+ "When the first codebook token is provided, `backbone_last_hidden_state` should also be provided for correct inference."
552
+ )
553
+
554
+ inputs_embeds = self.inputs_embeds_projector(inputs_embeds)
555
+
556
+ causal_mask = create_causal_mask(
557
+ config=self.config,
558
+ input_embeds=inputs_embeds,
559
+ attention_mask=attention_mask,
560
+ cache_position=cache_position,
561
+ past_key_values=past_key_values,
562
+ position_ids=position_ids,
563
+ )
564
+
565
+ hidden_states = inputs_embeds
566
+
567
+ # create position embeddings to be shared across the decoder layers
568
+ position_ids = cache_position.unsqueeze(0)
569
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
570
+
571
+ for decoder_layer in self.layers[: self.config.num_hidden_layers]:
572
+ hidden_states = decoder_layer(
573
+ hidden_states,
574
+ attention_mask=causal_mask,
575
+ position_ids=position_ids,
576
+ past_key_values=past_key_values,
577
+ use_cache=use_cache,
578
+ cache_position=cache_position,
579
+ position_embeddings=position_embeddings,
580
+ **kwargs,
581
+ )
582
+
583
+ hidden_states = self.norm(hidden_states)
584
+ return BaseModelOutputWithPast(
585
+ last_hidden_state=hidden_states,
586
+ past_key_values=past_key_values if use_cache else None,
587
+ )
588
+
589
+
590
+ class BreezeCodebooksHead(nn.Module):
591
+ def __init__(self, hidden_size, num_codebooks, vocab_size):
592
+ super().__init__()
593
+ self.num_codebooks = num_codebooks
594
+ self.weight = nn.Parameter(
595
+ torch.empty(self.num_codebooks - 1, hidden_size, vocab_size)
596
+ )
597
+
598
+ def forward(self, hidden_states, cache_position=None):
599
+ if cache_position is None:
600
+ seq_length = hidden_states.shape[1]
601
+ codebook_weight = self.weight[torch.arange(seq_length)]
602
+ else:
603
+ codebook_idxs = cache_position - 1
604
+ codebook_weight = self.weight[codebook_idxs]
605
+
606
+ hidden_states = [
607
+ nn.functional.linear(
608
+ hidden_states[:, codebook_idx, :], codebook_weight[codebook_idx].T
609
+ )
610
+ for codebook_idx in range(codebook_weight.shape[0])
611
+ ]
612
+ hidden_states = torch.stack(hidden_states, dim=1)
613
+
614
+ return hidden_states
615
+
616
+ # 重写 extra_repr 方法以显示参数细节
617
+ def extra_repr(self):
618
+ return f"weight_shape={list(self.weight.shape)}, num_codebooks={self.num_codebooks}"
619
+
620
+
621
+ @auto_docstring(
622
+ custom_intro="""
623
+ The BreezeDepthDecoder Model transformer, with a [`BreezeCodebooksHead`] on top,
624
+ which can be seen a position-specific language modeling head, allowing to use a different linear layer for each codebook
625
+ (e.g. position 0 is the first codebook and uses the first codebook head, etc.)
626
+ """
627
+ )
628
+ class BreezeDepthDecoderForCausalLM(BreezePreTrainedModel, GenerationMixin):
629
+ _tied_weights_keys = None
630
+ _tp_plan = None
631
+ _pp_plan = None
632
+
633
+ def __init__(self, config):
634
+ super().__init__(config)
635
+ self.model = BreezeDepthDecoderModel(config)
636
+ self.vocab_size = config.vocab_size
637
+ self.codebooks_head = BreezeCodebooksHead(
638
+ config.hidden_size, config.num_codebooks, config.vocab_size
639
+ )
640
+
641
+ # Compute per-codebook loss weights
642
+ # The depth decoder predicts num_codebooks-1 codebooks (31 for default config)
643
+ # We distribute weights by prepending a virtual codebook to make division even
644
+ codebook_loss_weights_list = getattr(config, "codebook_loss_weights", [1.0])
645
+ num_depth_codebooks = config.num_codebooks - 1 # 31 for default 32 codebooks
646
+ num_weights = len(codebook_loss_weights_list)
647
+ num_codebooks_with_virtual = num_depth_codebooks + 1
648
+ codebooks_per_weight = num_codebooks_with_virtual // num_weights
649
+ weights_expanded = []
650
+ for weight in codebook_loss_weights_list:
651
+ weights_expanded.extend([weight] * codebooks_per_weight)
652
+ weights_expanded = weights_expanded[1:]
653
+ self.codebook_loss_weights = weights_expanded
654
+
655
+ # Initialize weights and apply final processing
656
+ self.post_init()
657
+
658
+ @can_return_tuple
659
+ @auto_docstring
660
+ def forward(
661
+ self,
662
+ input_ids: torch.LongTensor = None,
663
+ backbone_last_hidden_state: torch.FloatTensor | None = None,
664
+ attention_mask: torch.Tensor | None = None,
665
+ position_ids: torch.LongTensor | None = None,
666
+ past_key_values: Cache | list[torch.FloatTensor] | None = None,
667
+ inputs_embeds: torch.FloatTensor | None = None,
668
+ labels: torch.LongTensor | None = None,
669
+ use_cache: bool | None = None,
670
+ cache_position: torch.LongTensor | None = None,
671
+ logits_to_keep: int | torch.Tensor = 0,
672
+ **kwargs: Unpack[TransformersKwargs],
673
+ ) -> tuple | CausalLMOutputWithPast:
674
+ r"""
675
+ backbone_last_hidden_state (`torch.FloatTensor` of shape `(batch_size, backbone_hidden_size)`, *optional*):
676
+ The last hidden state of the backbone model. Such input is required when the first codebook token (the one generated by the backbone model)
677
+ is provided in the `input_ids` argument.
678
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
679
+ Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
680
+ config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
681
+ (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
682
+ """
683
+ outputs = self.model(
684
+ input_ids=input_ids,
685
+ backbone_last_hidden_state=backbone_last_hidden_state,
686
+ attention_mask=attention_mask,
687
+ position_ids=position_ids,
688
+ past_key_values=past_key_values,
689
+ inputs_embeds=inputs_embeds,
690
+ use_cache=use_cache,
691
+ cache_position=cache_position,
692
+ **kwargs,
693
+ )
694
+
695
+ hidden_states = outputs[0]
696
+ # Only compute necessary logits, and do not upcast them to float if we are not computing the loss
697
+ if isinstance(logits_to_keep, int):
698
+ if logits_to_keep == 0:
699
+ # skip idx 0 logits since it's for the concatenated backbone last hidden state
700
+ slice_indices = slice(1, None)
701
+ else:
702
+ slice_indices = slice(-logits_to_keep, None)
703
+ else:
704
+ slice_indices = logits_to_keep
705
+
706
+ loss = None
707
+ logits = None
708
+
709
+ logits = self.codebooks_head(
710
+ hidden_states[:, slice_indices, :],
711
+ cache_position[slice_indices] if cache_position is not None else None,
712
+ )
713
+ logits = logits.contiguous()
714
+
715
+ if labels is not None:
716
+ shift_labels = labels[..., 1:].contiguous()
717
+ loss = self.loss_function(
718
+ logits=logits,
719
+ labels=None,
720
+ vocab_size=self.config.vocab_size,
721
+ shift_labels=shift_labels,
722
+ **kwargs,
723
+ )
724
+
725
+ return CausalLMOutputWithPast(
726
+ loss=loss,
727
+ logits=logits,
728
+ past_key_values=outputs.past_key_values,
729
+ hidden_states=outputs.hidden_states,
730
+ attentions=outputs.attentions,
731
+ )
732
+
733
+ def prepare_inputs_for_generation(
734
+ self,
735
+ input_ids: torch.LongTensor,
736
+ past_key_values: Cache | None = None,
737
+ attention_mask: torch.LongTensor | None = None,
738
+ inputs_embeds: torch.FloatTensor | None = None,
739
+ cache_position: torch.LongTensor | None = None,
740
+ **kwargs,
741
+ ):
742
+ model_inputs = super().prepare_inputs_for_generation(
743
+ input_ids,
744
+ past_key_values,
745
+ attention_mask,
746
+ inputs_embeds,
747
+ cache_position,
748
+ **kwargs,
749
+ )
750
+
751
+ is_first_generation_step = model_inputs["cache_position"][0] == 0
752
+ if not is_first_generation_step:
753
+ model_inputs.pop("backbone_last_hidden_state")
754
+
755
+ # breeze depth decoder does not use position_ids
756
+ model_inputs.pop("position_ids")
757
+
758
+ return model_inputs
759
+
760
+
761
+ class BreezeBackboneModelEmbeddings(nn.Module):
762
+ def __init__(self, config):
763
+ super().__init__()
764
+ self.hidden_size = config.hidden_size
765
+ if hasattr(config, "audio_embed_size") and config.audio_embed_size:
766
+ self.audio_embed_size = config.audio_embed_size
767
+ else:
768
+ self.audio_embed_size = self.hidden_size
769
+ self.embed_audio_tokens = nn.Embedding(
770
+ (config.num_codebooks * config.vocab_size), self.hidden_size
771
+ )
772
+ if self.audio_embed_size != self.hidden_size:
773
+ self.audio_embeds_projector = nn.Linear(
774
+ self.audio_embed_size, self.hidden_size, bias=False
775
+ )
776
+ else:
777
+ self.audio_embeds_projector = None
778
+ self.register_buffer(
779
+ "audio_tokens_offsets",
780
+ torch.arange(config.num_codebooks) * config.vocab_size,
781
+ persistent=False,
782
+ )
783
+
784
+ def forward(self, input_ids):
785
+ input_embeds = self.embed_audio_tokens(input_ids + self.audio_tokens_offsets)
786
+ if self.audio_embeds_projector:
787
+ input_embeds = self.audio_embeds_projector(input_embeds)
788
+ input_embeds = input_embeds.sum(dim=2)
789
+ return input_embeds
790
+
791
+
792
+ @auto_docstring
793
+ class BreezeBackboneModel(BreezePreTrainedModel):
794
+ def __init__(self, config):
795
+ super().__init__(config)
796
+ self.padding_idx = config.pad_token_id
797
+ self.vocab_size = config.vocab_size
798
+ self.embed_tokens = BreezeBackboneModelEmbeddings(config)
799
+ self.layers = nn.ModuleList(
800
+ [
801
+ BreezeDecoderLayer(config, layer_idx)
802
+ for layer_idx in range(config.num_hidden_layers)
803
+ ]
804
+ )
805
+ self.norm = BreezeRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
806
+ self.rotary_emb = BreezeRotaryEmbedding(config=config)
807
+ self.gradient_checkpointing = False
808
+
809
+ # Initialize weights and apply final processing
810
+ self.post_init()
811
+
812
+ @auto_docstring
813
+ def forward(
814
+ self,
815
+ input_ids: torch.LongTensor | None = None,
816
+ attention_mask: torch.Tensor | None = None,
817
+ position_ids: torch.LongTensor | None = None,
818
+ past_key_values: Cache | None = None,
819
+ inputs_embeds: torch.FloatTensor | None = None,
820
+ cache_position: torch.LongTensor | None = None,
821
+ use_cache: bool | None = None,
822
+ **kwargs: Unpack[TransformersKwargs],
823
+ ) -> BaseModelOutputWithPast:
824
+ r"""
825
+ input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length, num_codebooks) or (batch_size, sequence_length)`):
826
+ 1. (batch_size, sequence_length): corresponds to the input sequence prepared with the processor from the text prompt. Such input
827
+ requires `input_values` to be provided so that audio can be encoded in codebook tokens and then merged with the text tokens.
828
+
829
+ 2. (batch_size, sequence_length, num_codebooks): codebook tokens generated during the autoregressive decoding. Such input is not meant to be used by end users.
830
+
831
+ Indices can be obtained using [`AutoTokenizer`]. See [`PreTrainedTokenizer.encode`] and
832
+ [`PreTrainedTokenizer.__call__`] for details.
833
+
834
+ [What are input IDs?](../glossary#input-ids)
835
+ """
836
+ if (input_ids is None) ^ (inputs_embeds is not None):
837
+ raise ValueError(
838
+ "You must specify exactly one of input_ids or inputs_embeds"
839
+ )
840
+
841
+ if inputs_embeds is None:
842
+ inputs_embeds: torch.Tensor = self.embed_tokens(input_ids)
843
+
844
+ if use_cache and past_key_values is None:
845
+ past_key_values = DynamicCache(config=self.config)
846
+
847
+ if cache_position is None:
848
+ past_seen_tokens = (
849
+ past_key_values.get_seq_length() if past_key_values is not None else 0
850
+ )
851
+ cache_position: torch.Tensor = torch.arange(
852
+ past_seen_tokens,
853
+ past_seen_tokens + inputs_embeds.shape[1],
854
+ device=inputs_embeds.device,
855
+ )
856
+
857
+ if position_ids is None:
858
+ position_ids = cache_position.unsqueeze(0)
859
+
860
+ causal_mask = create_causal_mask(
861
+ config=self.config,
862
+ input_embeds=inputs_embeds,
863
+ attention_mask=attention_mask,
864
+ cache_position=cache_position,
865
+ past_key_values=past_key_values,
866
+ position_ids=position_ids,
867
+ )
868
+
869
+ hidden_states = inputs_embeds
870
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
871
+
872
+ for decoder_layer in self.layers[: self.config.num_hidden_layers]:
873
+ hidden_states = decoder_layer(
874
+ hidden_states,
875
+ attention_mask=causal_mask,
876
+ position_ids=position_ids,
877
+ past_key_values=past_key_values,
878
+ cache_position=cache_position,
879
+ position_embeddings=position_embeddings,
880
+ **kwargs,
881
+ )
882
+
883
+ hidden_states = self.norm(hidden_states)
884
+ return BaseModelOutputWithPast(
885
+ last_hidden_state=hidden_states,
886
+ past_key_values=past_key_values,
887
+ )
888
+
889
+
890
+ from transformers import AutoConfig, PretrainedConfig
891
+
892
+
893
+ @auto_docstring(
894
+ custom_intro="""
895
+ The Breeze model consists of two llama-like auto-regressive transformer models: a backbone model that predicts the first codebook token and a depth decoder that predicts the other codebook tokens.
896
+ """
897
+ )
898
+ class BreezeForConditionalGeneration(BreezePreTrainedModel, BreezeGenerationMixin):
899
+ _tied_weights_keys = [
900
+ "backbone_model.embed_tokens.embed_audio_tokens.weight",
901
+ "depth_decoder.model.embed_tokens.weight",
902
+ ]
903
+
904
+ def __init__(self, config):
905
+ super().__init__(config)
906
+ self.vocab_size = config.vocab_size
907
+ # Extra backbone EOS class at index vocab_size; audio codebook ids remain [0, vocab_size).
908
+ self.backbone_eos_token_id = config.vocab_size
909
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size + 1, bias=False)
910
+
911
+ # embed_text_tokens needs to include audio special tokens (audio_token_id, audio_eos_token_id)
912
+ # since they appear in input_ids before being replaced with audio embeddings
913
+ # text_vocab_size already includes these tokens after tokenizer extension
914
+ self.embed_text_tokens = nn.Embedding(
915
+ config.text_vocab_size, config.hidden_size
916
+ )
917
+
918
+ # Use factory to create backbone (supports breeze/qwen3/llama etc.)
919
+ from .breeze_backbone_factory import BreezeBackboneFactory
920
+
921
+ self.backbone_model = BreezeBackboneFactory.create_backbone(config)
922
+
923
+ self.depth_decoder = BreezeDepthDecoderForCausalLM._from_config(
924
+ config.depth_decoder_config
925
+ )
926
+
927
+ self.codec_model = AutoModel.from_config(config.codec_config)
928
+ # Codec model is always in eval mode (not trainable)
929
+ self.codec_model.eval()
930
+ for param in self.codec_model.parameters():
931
+ param.requires_grad = False
932
+
933
+ self.text_encoder_trainable = False
934
+ text_encoder_config = getattr(config, "text_encoder_config", None)
935
+ if text_encoder_config is None:
936
+ self.text_encoder = None
937
+ self.text_encoder_proj = None
938
+ elif isinstance(text_encoder_config, dict):
939
+ config.text_encoder_config = AutoConfig.for_model(**text_encoder_config)
940
+ elif isinstance(config.text_encoder_config, PretrainedConfig):
941
+ config.text_encoder_config = text_encoder_config
942
+ else:
943
+ raise ValueError(
944
+ "text_encoder_config must be None, dict or PretrainedConfig instance"
945
+ )
946
+ if getattr(config, "text_encoder_config", None) is not None:
947
+ self.text_encoder_layer_projs = None
948
+ self.text_encoder_dimfusion_layer_start_idx = getattr(
949
+ config, "text_encoder_dimfusion_layer_start_idx", 1
950
+ ) # 1 feature indicates from layer 1, feature 0 is embedding result
951
+ self.text_encoder_dimfusion_layer_end_idx = getattr(
952
+ config, "text_encoder_dimfusion_layer_end_idx", None
953
+ ) # None indicates up to last layer
954
+ self.text_encoder_dimfusion_fuse_first_layer = getattr(
955
+ config, "text_encoder_dimfusion_fuse_first_layer", False
956
+ )
957
+
958
+ self.text_encoder_feature_layer_idx = getattr(
959
+ config, "text_encoder_feature_layer_idx", -1
960
+ )
961
+ if isinstance(self.text_encoder_feature_layer_idx, int):
962
+ self.text_encoder_feature_layer_idx = (
963
+ self.text_encoder_feature_layer_idx,
964
+ )
965
+ if self.text_encoder_feature_layer_idx != (-1,):
966
+ print(
967
+ f" - [BreezeForConditionalGeneration] Text encoder feature layer idx: {self.text_encoder_feature_layer_idx}"
968
+ )
969
+
970
+ text_encoder_attn_implementation = getattr(
971
+ config.text_encoder_config,
972
+ "preferred_attn_implementation",
973
+ None,
974
+ )
975
+ if text_encoder_attn_implementation is None:
976
+ text_encoder_attn_implementation = "flash_attention_2"
977
+ config.text_encoder_config._attn_implementation = (
978
+ text_encoder_attn_implementation
979
+ )
980
+ # Skip random weight initialization since we'll load pretrained weights later
981
+ from transformers.modeling_utils import no_init_weights
982
+
983
+ with no_init_weights():
984
+ self.text_encoder = AutoModel.from_config(
985
+ config.text_encoder_config,
986
+ attn_implementation=text_encoder_attn_implementation,
987
+ dtype=torch.bfloat16,
988
+ )
989
+
990
+ text_encoder_proj_type = getattr(config, "text_encoder_proj_type", "linear")
991
+ if text_encoder_proj_type == "linear":
992
+ self.text_encoder_proj = nn.Linear(
993
+ config.text_encoder_config.hidden_size,
994
+ config.hidden_size,
995
+ bias=False,
996
+ )
997
+ elif text_encoder_proj_type == "mlp":
998
+ self.text_encoder_proj = nn.Sequential(
999
+ nn.Linear(
1000
+ config.text_encoder_config.hidden_size,
1001
+ config.hidden_size * 2,
1002
+ bias=False,
1003
+ ),
1004
+ nn.GELU(),
1005
+ nn.Linear(config.hidden_size * 2, config.hidden_size, bias=False),
1006
+ )
1007
+ elif text_encoder_proj_type == "breeze_text_encoder_adapter":
1008
+ from .t5_adapter import BreezeTextEncoderAdapter
1009
+
1010
+ self.text_encoder_proj = BreezeTextEncoderAdapter(config=config)
1011
+ elif text_encoder_proj_type == "breeze_dimfusion":
1012
+ proj_out_hidden_size = config.hidden_size
1013
+ if self.text_encoder_dimfusion_fuse_first_layer:
1014
+ proj_out_hidden_size //= 2
1015
+ self.text_encoder_proj = nn.Linear(
1016
+ config.text_encoder_config.hidden_size
1017
+ * len(self.text_encoder_feature_layer_idx),
1018
+ proj_out_hidden_size,
1019
+ bias=True, # bias=True, follow DimFusion design: https://github.com/huggingface/diffusers/blob/d0ae34d3136f7cb119a66fe0b9d526202343674c/src/diffusers/models/transformers/transformer_bria_fibo.py#L477
1020
+ )
1021
+ self.text_encoder_layer_projs = nn.ModuleList(
1022
+ [
1023
+ nn.Linear(
1024
+ config.text_encoder_config.hidden_size,
1025
+ config.hidden_size // 2,
1026
+ bias=False,
1027
+ )
1028
+ for _ in range(self.config.num_hidden_layers)
1029
+ ]
1030
+ )
1031
+
1032
+ dimfusion_kwargs = {
1033
+ "text_encoder_dimfusion_layer_start_idx": self.text_encoder_dimfusion_layer_start_idx,
1034
+ "text_encoder_dimfusion_layer_end_idx": self.text_encoder_dimfusion_layer_end_idx,
1035
+ "text_encoder_dimfusion_fuse_first_layer": self.text_encoder_dimfusion_fuse_first_layer,
1036
+ }
1037
+ print(
1038
+ " - [BreezeForConditionalGeneration] Text encoder DimFusion layer projections created."
1039
+ )
1040
+ for k, v in dimfusion_kwargs.items():
1041
+ print(f" - {k}: {v}")
1042
+ else:
1043
+ raise ValueError(
1044
+ f"Unsupported text_encoder_proj_type: {text_encoder_proj_type}"
1045
+ )
1046
+ print(
1047
+ " - [BreezeForConditionalGeneration] Text encoder projection type:",
1048
+ text_encoder_proj_type,
1049
+ )
1050
+
1051
+ requires_grad = getattr(config.text_encoder_config, "requires_grad", False)
1052
+ self.text_encoder_trainable = bool(requires_grad)
1053
+ if not self.text_encoder_trainable:
1054
+ nonzero_dropout_fields = self._get_nonzero_text_encoder_dropout_fields(
1055
+ config.text_encoder_config
1056
+ )
1057
+ if nonzero_dropout_fields:
1058
+ formatted_fields = ", ".join(
1059
+ f"{name}={value}" for name, value in nonzero_dropout_fields
1060
+ )
1061
+ raise ValueError(
1062
+ "Frozen text_encoder must be deterministic, but its config has non-zero dropout fields: "
1063
+ f"{formatted_fields}. Set train_text_encoder=true or use a text encoder config with dropout=0."
1064
+ )
1065
+ self.text_encoder.eval()
1066
+ for param in self.text_encoder.parameters():
1067
+ param.requires_grad = requires_grad
1068
+ import os
1069
+
1070
+ if os.environ.get("RANK", "0") == "0":
1071
+ logger.info(
1072
+ f"model.text_encoder initialized with requires_grad={requires_grad}"
1073
+ )
1074
+
1075
+ # Disable num_items_in_batch to avoid incorrect loss normalization
1076
+ # for depth decoder (which operates on different token counts than backbone)
1077
+ self.accepts_loss_kwargs = False
1078
+
1079
+ # Temporarily remove text_encoder before post_init to avoid slow re-initialization
1080
+ # (text_encoder weights will be loaded from pretrained checkpoint later)
1081
+ _text_encoder = self.text_encoder
1082
+ self.text_encoder = None
1083
+ self.post_init()
1084
+ self.text_encoder = _text_encoder
1085
+
1086
+ def get_input_embeddings(self):
1087
+ return self.backbone_model.embed_tokens
1088
+
1089
+ def set_input_embeddings(self, value):
1090
+ self.backbone_model.embed_tokens = value
1091
+
1092
+ def _tie_weights(self):
1093
+ if self.config.tie_codebooks_embeddings:
1094
+ self._tie_or_clone_weights(
1095
+ self.backbone_model.embed_tokens.embed_audio_tokens,
1096
+ self.depth_decoder.model.embed_tokens,
1097
+ )
1098
+
1099
+ @staticmethod
1100
+ def _get_nonzero_text_encoder_dropout_fields(text_encoder_config):
1101
+ dropout_field_names = (
1102
+ "dropout",
1103
+ "dropout_rate",
1104
+ "attention_dropout",
1105
+ "hidden_dropout",
1106
+ "hidden_dropout_prob",
1107
+ "activation_dropout",
1108
+ "classifier_dropout",
1109
+ "classifier_dropout_rate",
1110
+ "embd_pdrop",
1111
+ "resid_pdrop",
1112
+ )
1113
+ nonzero_fields = []
1114
+ for field_name in dropout_field_names:
1115
+ value = getattr(text_encoder_config, field_name, None)
1116
+ if value is None:
1117
+ continue
1118
+ try:
1119
+ numeric_value = float(value)
1120
+ except (TypeError, ValueError):
1121
+ continue
1122
+ if numeric_value != 0.0:
1123
+ nonzero_fields.append((field_name, value))
1124
+ return nonzero_fields
1125
+
1126
+ def train(self, mode: bool = True):
1127
+ """Override train() to keep non-trainable submodules in eval mode."""
1128
+ super().train(mode)
1129
+ # Always keep codec_model in eval mode
1130
+ self.codec_model.eval()
1131
+ if getattr(self, "text_encoder", None) is not None and not getattr(
1132
+ self, "text_encoder_trainable", False
1133
+ ):
1134
+ self.text_encoder.eval()
1135
+ return self
1136
+
1137
+ @classmethod
1138
+ def from_pretrained(cls, *args, **kwargs):
1139
+ if kwargs.get("output_loading_info", False):
1140
+ model, loading_info = super().from_pretrained(*args, **kwargs)
1141
+ else:
1142
+ model = super().from_pretrained(*args, **kwargs)
1143
+
1144
+ # copy depth decoder generation conf attr to the depth decoder generation config
1145
+ prefix = "depth_decoder_"
1146
+ prefix_len = len(prefix)
1147
+ depth_decoder_attrs = {
1148
+ attr[prefix_len:]: value
1149
+ for attr, value in vars(model.generation_config).items()
1150
+ if attr.startswith(prefix)
1151
+ }
1152
+
1153
+ vars(model.depth_decoder.generation_config).update(
1154
+ {"_from_model_config": False, **depth_decoder_attrs}
1155
+ )
1156
+
1157
+ # remove the depth decoder generation conf attr from the model generation config
1158
+ for attr in depth_decoder_attrs:
1159
+ delattr(model.generation_config, prefix + attr)
1160
+
1161
+ if "output_loading_info" in kwargs:
1162
+ return model, loading_info
1163
+ else:
1164
+ return model
1165
+
1166
+ def save_pretrained(self, *args, **kwargs):
1167
+ # copy the depth decoder generation config attributes to the model generation config
1168
+ prefix = "depth_decoder_"
1169
+ depth_decoder_attrs = self.depth_decoder.generation_config.to_diff_dict()
1170
+ depth_decoder_attrs.pop("transformers_version", None)
1171
+ for attr, value in depth_decoder_attrs.items():
1172
+ setattr(self.generation_config, prefix + attr, value)
1173
+
1174
+ super().save_pretrained(*args, **kwargs)
1175
+
1176
+ def gradient_checkpointing_enable(self, *args, **kwargs):
1177
+ gradient_checkpointing_kwargs = kwargs.pop("gradient_checkpointing_kwargs", {})
1178
+ sub_modules_to_disable_default = [
1179
+ # 'codec_model',
1180
+ # # 'backbone_model',
1181
+ # 'text_encoder',
1182
+ # 'text_encoder_proj',
1183
+ # 'depth_decoder'
1184
+ ]
1185
+
1186
+ if gradient_checkpointing_kwargs is None:
1187
+ gradient_checkpointing_kwargs = {}
1188
+ sub_modules_to_disable = gradient_checkpointing_kwargs.pop(
1189
+ "sub_modules_to_disable", sub_modules_to_disable_default
1190
+ )
1191
+
1192
+ # call super first to enable gradient checkpointing
1193
+ ret = super().gradient_checkpointing_enable(*args, **kwargs)
1194
+
1195
+ # override sub-modules to disable gradient checkpointing
1196
+ sub_modules_to_disable = gradient_checkpointing_kwargs.get(
1197
+ "sub_modules_to_disable", sub_modules_to_disable
1198
+ )
1199
+
1200
+ # 1. disable gradient checkpointing for specified sub-modules
1201
+ import os
1202
+
1203
+ rank = int(os.environ.get("RANK", "0"))
1204
+ print0 = lambda *args, **kwargs: print(*args, **kwargs) if rank == 0 else None
1205
+ for module_name in sub_modules_to_disable:
1206
+ module = getattr(self, module_name, None)
1207
+ module_layers = (
1208
+ [
1209
+ (name, mod)
1210
+ for name, mod in module.named_modules()
1211
+ if isinstance(mod, GradientCheckpointingLayer)
1212
+ ]
1213
+ if module is not None
1214
+ else []
1215
+ )
1216
+
1217
+ if not module_layers:
1218
+ print0(
1219
+ f" - [BreezeForConditionalGeneration] gradient_checkpointing_disable skipped for sub-module: {module_name}"
1220
+ )
1221
+ continue
1222
+
1223
+ print0(
1224
+ f" - [BreezeForConditionalGeneration] gradient_checkpointing_disable applied for sub-module: {module_name}"
1225
+ )
1226
+ for layer_name, layer in module_layers:
1227
+ layer.gradient_checkpointing = False
1228
+ print0(
1229
+ f" - Disabled gradient checkpointing for layer: {module_name}.{layer_name}"
1230
+ )
1231
+ return ret
1232
+
1233
+ def _batched_text_encoder_forward(self, segments, output_hidden_states=False):
1234
+ """Run text encoder on a list of variable-length token ID tensors using padded batching.
1235
+
1236
+ Args:
1237
+ segments: list of 1D tensors, each (seg_len,)
1238
+
1239
+ Returns:
1240
+ hidden_states: list of 2D tensors, each (seg_len, hidden) with padding removed
1241
+ layer_hidden_states: list of lists, per-segment layer hidden states (each (seg_len, hidden)),
1242
+ or None when not requested.
1243
+ """
1244
+ if not segments:
1245
+ return [], []
1246
+
1247
+ if (
1248
+ getattr(self, "_fast_text_encoder_cudagraph", False)
1249
+ and not output_hidden_states
1250
+ ):
1251
+ from .text_encoder_graph import TextEncoderGraphCache
1252
+
1253
+ cache = getattr(self, "_fast_text_encoder_graph_cache", None)
1254
+ if cache is None:
1255
+ cache = TextEncoderGraphCache(self.text_encoder, token_granularity=32)
1256
+ self._fast_text_encoder_graph_cache = cache
1257
+ return cache(segments)
1258
+
1259
+ device = segments[0].device
1260
+ lengths = [s.shape[0] for s in segments]
1261
+ feature_layer_idx = self.text_encoder_feature_layer_idx
1262
+ if isinstance(feature_layer_idx, int):
1263
+ feature_layer_idx = (feature_layer_idx,)
1264
+ elif isinstance(feature_layer_idx, list):
1265
+ feature_layer_idx = tuple(feature_layer_idx)
1266
+
1267
+ sorted_indices = sorted(range(len(segments)), key=lambda i: lengths[i])
1268
+ buckets = []
1269
+ current_bucket = []
1270
+ current_min_len = None
1271
+ for idx in sorted_indices:
1272
+ length = lengths[idx]
1273
+ if not current_bucket:
1274
+ current_bucket = [idx]
1275
+ current_min_len = length
1276
+ elif length / max(current_min_len, 1) <= 2:
1277
+ current_bucket.append(idx)
1278
+ else:
1279
+ buckets.append(current_bucket)
1280
+ current_bucket = [idx]
1281
+ current_min_len = length
1282
+ if current_bucket:
1283
+ buckets.append(current_bucket)
1284
+
1285
+ hidden_states = [None] * len(segments)
1286
+ layer_hidden_states = [None] * len(segments) if output_hidden_states else None
1287
+
1288
+ for bucket_indices in buckets:
1289
+ bucket_lengths = [lengths[i] for i in bucket_indices]
1290
+ max_len = max(bucket_lengths)
1291
+
1292
+ padded_ids = torch.zeros(
1293
+ len(bucket_indices), max_len, dtype=segments[0].dtype, device=device
1294
+ )
1295
+ attn_mask = torch.zeros(
1296
+ len(bucket_indices), max_len, dtype=torch.long, device=device
1297
+ )
1298
+ pos_ids = torch.zeros(
1299
+ len(bucket_indices), max_len, dtype=torch.long, device=device
1300
+ )
1301
+ for bucket_pos, idx in enumerate(bucket_indices):
1302
+ length = lengths[idx]
1303
+ padded_ids[bucket_pos, :length] = segments[idx]
1304
+ attn_mask[bucket_pos, :length] = 1
1305
+ pos_ids[bucket_pos, :length] = torch.arange(length, device=device)
1306
+
1307
+ output = self.text_encoder(
1308
+ input_ids=padded_ids,
1309
+ attention_mask=attn_mask,
1310
+ position_ids=pos_ids,
1311
+ output_hidden_states=output_hidden_states,
1312
+ )
1313
+
1314
+ if feature_layer_idx == (-1,):
1315
+ full_hs = output.last_hidden_state
1316
+ elif isinstance(feature_layer_idx, tuple):
1317
+ if output.hidden_states is None:
1318
+ raise ValueError(
1319
+ "output_hidden_states must be enabled when selecting non-final text encoder layers"
1320
+ )
1321
+ full_hs = torch.concat(
1322
+ [output.hidden_states[li] for li in feature_layer_idx], dim=-1
1323
+ )
1324
+ else:
1325
+ raise ValueError(
1326
+ f"Unsupported text_encoder_feature_layer_idx: {feature_layer_idx}"
1327
+ )
1328
+
1329
+ for bucket_pos, idx in enumerate(bucket_indices):
1330
+ hidden_states[idx] = full_hs[bucket_pos, : lengths[idx]]
1331
+ if output.hidden_states is not None:
1332
+ all_layer_hs = list(output.hidden_states)
1333
+ if layer_hidden_states is None:
1334
+ layer_hidden_states = [None] * len(segments)
1335
+ for bucket_pos, idx in enumerate(bucket_indices):
1336
+ layer_hidden_states[idx] = [
1337
+ lhs[bucket_pos, : lengths[idx]] for lhs in all_layer_hs
1338
+ ]
1339
+
1340
+ return hidden_states, layer_hidden_states
1341
+
1342
+ def convert_input_ids_to_embeds(
1343
+ self,
1344
+ input_ids: torch.Tensor | None = None,
1345
+ text_ids_mask: torch.Tensor | None = None,
1346
+ text_ids_len: torch.Tensor | None = None,
1347
+ attention_mask: torch.Tensor | None = None,
1348
+ ):
1349
+ assert self.text_encoder is not None, (
1350
+ "text_encoder is None, cannot convert input_ids to embeds"
1351
+ )
1352
+ assert text_ids_mask is not None, (
1353
+ "text_ids_mask is None, cannot convert input_ids to embeds"
1354
+ )
1355
+ assert text_ids_len is not None, (
1356
+ "text_ids_len is None, cannot convert input_ids to embeds"
1357
+ )
1358
+
1359
+ # Ensure text_ids_len matches mask-derived count (helps catch bad batching)
1360
+ total_text_tokens = int(text_ids_mask.sum().item())
1361
+ assert total_text_tokens == int(text_ids_len.sum().item()), (
1362
+ f"text_ids_mask sum {total_text_tokens} does not match text_ids_len sum {int(text_ids_len.sum().item())}"
1363
+ )
1364
+
1365
+ text_ids_len_list = [int(x) for x in text_ids_len.reshape(-1).tolist()]
1366
+ text_ids_len_idx = 0
1367
+
1368
+ # == Phase 1: Parse all samples and collect segments ==
1369
+ # Keep row-major order so the projected embeddings line up with
1370
+ # boolean assignment into inputs_embeds[text_ids_mask].
1371
+ all_segment_lengths = []
1372
+ all_seg_token_ids = []
1373
+ for batch_idx in range(input_ids.shape[0]):
1374
+ mask_row = text_ids_mask[batch_idx]
1375
+ sample_text_tokens = int(mask_row.sum().item())
1376
+ if sample_text_tokens == 0:
1377
+ continue
1378
+
1379
+ text_ids = input_ids[batch_idx][mask_row] # (total_text_len,)
1380
+ segment_lengths = []
1381
+ running = 0
1382
+ while running < sample_text_tokens:
1383
+ if text_ids_len_idx >= len(text_ids_len_list):
1384
+ raise ValueError(
1385
+ "text_ids_len exhausted before covering all text tokens in the batch"
1386
+ )
1387
+ seg_len = text_ids_len_list[text_ids_len_idx]
1388
+ text_ids_len_idx += 1
1389
+ if seg_len <= 0:
1390
+ continue
1391
+ segment_lengths.append(seg_len)
1392
+ running += seg_len
1393
+ if running != sample_text_tokens:
1394
+ raise ValueError(
1395
+ f"text_ids_len segments sum {running} does not match text_ids_mask sum {sample_text_tokens} for batch index {batch_idx}"
1396
+ )
1397
+
1398
+ seg_token_ids = text_ids.split(segment_lengths, dim=0) # list of 1D tensors
1399
+ all_segment_lengths.extend(segment_lengths)
1400
+ all_seg_token_ids.extend(seg_token_ids)
1401
+
1402
+ if text_ids_len_idx != len(text_ids_len_list):
1403
+ raise ValueError(
1404
+ f"Unused text_ids_len entries detected: consumed {text_ids_len_idx} of {len(text_ids_len_list)}"
1405
+ )
1406
+
1407
+ # == Phase 2: Text encoder forward ==
1408
+ # Batched independent encoding: each text segment is its own row in
1409
+ # the padded text-encoder batch. This prevents cross-sample and
1410
+ # cross-segment attention contamination under the flattening collator.
1411
+ if all_seg_token_ids:
1412
+ feature_layer_idx = self.text_encoder_feature_layer_idx
1413
+ if isinstance(feature_layer_idx, int):
1414
+ feature_layer_idx = (feature_layer_idx,)
1415
+ elif isinstance(feature_layer_idx, list):
1416
+ feature_layer_idx = tuple(feature_layer_idx)
1417
+ needs_layer_hidden_states = (
1418
+ self.text_encoder_layer_projs is not None or feature_layer_idx != (-1,)
1419
+ )
1420
+ seg_hidden_states, seg_layer_hidden_states = (
1421
+ self._batched_text_encoder_forward(
1422
+ all_seg_token_ids,
1423
+ output_hidden_states=needs_layer_hidden_states,
1424
+ )
1425
+ )
1426
+ seg_hidden_states = [hs.unsqueeze(0) for hs in seg_hidden_states]
1427
+ text_embeds, text_encoder_layer_hidden_states = self._project_segments(
1428
+ all_segment_lengths,
1429
+ seg_hidden_states,
1430
+ seg_layer_hidden_states,
1431
+ is_separate=True,
1432
+ )
1433
+ else:
1434
+ text_embeds = torch.empty(
1435
+ (0, self.config.hidden_size),
1436
+ device=input_ids.device,
1437
+ dtype=self.embed_text_tokens.weight.dtype,
1438
+ )
1439
+ text_encoder_layer_hidden_states = None
1440
+
1441
+ # == Phase 3: Assemble final outputs ==
1442
+ # Create inputs_embeds tensor
1443
+ inputs_embeds = torch.zeros(
1444
+ (input_ids.shape[0], input_ids.shape[1], self.config.hidden_size),
1445
+ dtype=text_embeds.dtype,
1446
+ device=text_embeds.device,
1447
+ )
1448
+ inputs_embeds[text_ids_mask] = text_embeds
1449
+
1450
+ return inputs_embeds, text_encoder_layer_hidden_states
1451
+
1452
+ def _project_segments(
1453
+ self, segment_lengths, seg_hidden_states, seg_layer_hidden_states, is_separate
1454
+ ):
1455
+ """Project text encoder hidden states per segment and handle layer projections.
1456
+
1457
+ Args:
1458
+ segment_lengths: list of int, length of each segment
1459
+ seg_hidden_states: list of (1, seg_len, hidden) tensors
1460
+ seg_layer_hidden_states: list of layer hidden state lists per segment.
1461
+ - If is_separate=True: each entry is a list of (seg_len, hidden) tensors (one per layer).
1462
+ - If is_separate=False: each entry is a list of (1, total_len, hidden) tensors (shared across segments).
1463
+ is_separate: whether segments were encoded separately
1464
+
1465
+ Returns:
1466
+ text_embeds: (total_text_len, hidden) projected embeddings
1467
+ text_encoder_layer_hidden_states: list of (total_text_len, hidden) or None
1468
+ """
1469
+ projected_segments = []
1470
+ for seg_idx, seg_hs in enumerate(seg_hidden_states):
1471
+ proj = self.text_encoder_proj
1472
+ if isinstance(proj, (nn.Linear, nn.Sequential)):
1473
+ projected_segments.append(proj(seg_hs).squeeze(0))
1474
+ else:
1475
+ seg_pos = torch.arange(seg_hs.shape[1], device=seg_hs.device).unsqueeze(
1476
+ 0
1477
+ )
1478
+ projected_segments.append(
1479
+ proj(inputs_embeds=seg_hs, position_ids=seg_pos).squeeze(0)
1480
+ )
1481
+ text_embeds = torch.cat(projected_segments, dim=0)
1482
+
1483
+ if self.text_encoder_layer_projs is not None:
1484
+ num_layers = len(self.text_encoder_layer_projs)
1485
+ projected_layer_hidden_states = []
1486
+
1487
+ for layer_idx in range(num_layers):
1488
+ layer_parts = []
1489
+ for seg_idx, seg_layer_hs_list in enumerate(seg_layer_hidden_states):
1490
+ selected = seg_layer_hs_list[
1491
+ self.text_encoder_dimfusion_layer_start_idx : self.text_encoder_dimfusion_layer_end_idx
1492
+ ]
1493
+ if len(selected) < num_layers:
1494
+ selected = selected + [selected[-1]] * (
1495
+ num_layers - len(selected)
1496
+ )
1497
+ elif len(selected) > num_layers:
1498
+ selected = selected[-num_layers:]
1499
+
1500
+ seg_layer_hs = selected[layer_idx]
1501
+ if is_separate:
1502
+ # Already per-segment: (seg_len, hidden) -> (1, seg_len, hidden)
1503
+ seg_layer_hs = seg_layer_hs.unsqueeze(0)
1504
+ else:
1505
+ # Joint encoding: (1, total_len, hidden) -> split -> (1, seg_len, hidden)
1506
+ seg_layer_hs = (
1507
+ seg_layer_hs.squeeze(0)
1508
+ .split(segment_lengths, dim=0)[seg_idx]
1509
+ .unsqueeze(0)
1510
+ )
1511
+
1512
+ layer_proj = self.text_encoder_layer_projs[layer_idx]
1513
+ layer_parts.append(layer_proj(seg_layer_hs).squeeze(0))
1514
+ projected_layer_hidden_states.append(torch.cat(layer_parts, dim=0))
1515
+
1516
+ text_encoder_layer_hidden_states = projected_layer_hidden_states
1517
+ else:
1518
+ text_encoder_layer_hidden_states = None
1519
+
1520
+ if self.text_encoder_dimfusion_fuse_first_layer:
1521
+ assert text_encoder_layer_hidden_states is not None, (
1522
+ "text_encoder_layer_hidden_states is None, cannot fuse first layer hidden states"
1523
+ )
1524
+ layer0_hidden_states = text_encoder_layer_hidden_states[0]
1525
+ assert (
1526
+ layer0_hidden_states.shape[-1] + text_embeds.shape[-1]
1527
+ ) == self.config.hidden_size, (
1528
+ f"Dimension mismatch when fusing layer 0 hidden states with text embeddings, but got {layer0_hidden_states.shape[-1]} + {text_embeds.shape[-1]} != {self.config.hidden_size}"
1529
+ )
1530
+ text_embeds = torch.cat([text_embeds, layer0_hidden_states], dim=-1)
1531
+
1532
+ return text_embeds, text_encoder_layer_hidden_states
1533
+
1534
+ def _merge_input_ids_with_input_values(
1535
+ self,
1536
+ input_ids: torch.Tensor | None = None,
1537
+ input_values: torch.Tensor | None = None,
1538
+ labels: torch.Tensor | None = None,
1539
+ text_ids_mask: torch.Tensor | None = None,
1540
+ text_ids_len: torch.Tensor | None = None,
1541
+ attention_mask: torch.Tensor | None = None,
1542
+ ) -> torch.Tensor | None:
1543
+ """
1544
+ Merges the input_ids and input_values to produce a single inputs_embeds tensor:
1545
+ 1 - Uses pre-encoded codebook tokens from input_values (no codec inference needed).
1546
+ 2 - Embeds codebook tokens and places them at the correct positions in the inputs_embeds tensor.
1547
+ 3 - If labels are provided, expands them to match codebook dimensions and position the target codebook tokens in the inputs_embeds tensor.
1548
+
1549
+ Args:
1550
+ input_ids (`torch.Tensor` of shape `(batch_size, sequence_length)`):
1551
+ The input ids to embed.
1552
+ input_values (`torch.Tensor` of shape `(batch_size, audio_sequence_length, num_codebooks)`):
1553
+ The pre-encoded audio tokens (RVQ codebook indices).
1554
+ labels (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*):
1555
+ Labels for computing the loss.
1556
+ """
1557
+ text_encoder_layer_hidden_states = None
1558
+ if self.text_encoder is not None:
1559
+ inputs_embeds, text_encoder_layer_hidden_states = (
1560
+ self.convert_input_ids_to_embeds(
1561
+ input_ids,
1562
+ text_ids_mask,
1563
+ text_ids_len,
1564
+ attention_mask=attention_mask,
1565
+ )
1566
+ )
1567
+ else:
1568
+ inputs_embeds = self.embed_text_tokens(input_ids)
1569
+
1570
+ if input_values is not None:
1571
+ # input_values are already pre-encoded tokens: (batch_size, audio_seq_len, num_codebooks)
1572
+ batched_audio_token_ids = input_values
1573
+
1574
+ # Get mask for audio token positions in input_ids
1575
+ audio_token_id = self.config.audio_token_id
1576
+ audio_token_mask = input_ids == audio_token_id
1577
+
1578
+ # Debug: check if audio tokens are found
1579
+ num_audio_tokens = audio_token_mask.sum().item()
1580
+ if num_audio_tokens == 0 and input_values is not None:
1581
+ # This should not happen - we have audio data but no audio token markers
1582
+ unique_ids = torch.unique(input_ids)
1583
+ raise RuntimeError(
1584
+ f"Audio token mismatch: Expected audio_token_id={audio_token_id} in input_ids, "
1585
+ f"but found 0 matches. input_values shape: {input_values.shape}. "
1586
+ f"Unique input_ids: {unique_ids.tolist()[:20]}... (showing first 20)"
1587
+ )
1588
+
1589
+ # Convert patches tokens to embeddings
1590
+ audio_embeds = self.backbone_model.embed_tokens(batched_audio_token_ids)
1591
+
1592
+ # Flatten audio_embeds for assignment to masked positions
1593
+ # audio_embeds shape: (batch_size, audio_seq_len, hidden_size)
1594
+ # Need to flatten to match the flattened audio_token_mask
1595
+ audio_embeds_flat = audio_embeds.reshape(-1, audio_embeds.shape[-1])
1596
+ # Ensure dtype matches
1597
+ audio_embeds_flat = audio_embeds_flat.to(inputs_embeds.dtype)
1598
+ inputs_embeds[audio_token_mask] = audio_embeds_flat
1599
+
1600
+ # same for the audio eos token.
1601
+ audio_eos_frame_ids = (
1602
+ torch.ones(
1603
+ (1, 1, self.config.num_codebooks),
1604
+ device=input_ids.device,
1605
+ dtype=torch.long,
1606
+ )
1607
+ * self.config.codebook_eos_token_id
1608
+ )
1609
+ audio_eos_embeds = self.backbone_model.embed_tokens(
1610
+ audio_eos_frame_ids
1611
+ ).squeeze(1)
1612
+ # Ensure dtype matches
1613
+ audio_eos_embeds = audio_eos_embeds.to(inputs_embeds.dtype)
1614
+
1615
+ audio_eos_token_mask = input_ids == self.config.audio_eos_token_id
1616
+ inputs_embeds[audio_eos_token_mask] = audio_eos_embeds.repeat(
1617
+ audio_eos_token_mask.sum(), 1
1618
+ )
1619
+
1620
+ # if the labels are provided, we need to expand the labels to (batch_size, seq_length, num_codebooks)
1621
+ if labels is not None:
1622
+ labels_expanded = labels.unsqueeze(-1).repeat(
1623
+ 1, 1, self.config.num_codebooks
1624
+ )
1625
+ # Flatten batched_audio_token_ids for assignment to masked positions
1626
+ batched_audio_token_ids_flat = batched_audio_token_ids.reshape(
1627
+ -1, batched_audio_token_ids.shape[-1]
1628
+ )
1629
+ labels_expanded[audio_token_mask] = batched_audio_token_ids_flat
1630
+ labels_expanded[audio_eos_token_mask] = -100
1631
+ eos_positions = audio_eos_token_mask.nonzero(as_tuple=True)
1632
+ labels_expanded[eos_positions[0], eos_positions[1], 0] = (
1633
+ self.backbone_eos_token_id
1634
+ )
1635
+ # mask depth decoder
1636
+ depth_decoder_ignore_frames_mask = labels == -101
1637
+ depth_decoder_ignore_frames_mask &= ~audio_eos_token_mask
1638
+ depth_decoder_ignore_frames_idxs = (
1639
+ depth_decoder_ignore_frames_mask.nonzero(as_tuple=True)
1640
+ )
1641
+ # set the first codebook patch to -100 to ignore
1642
+ labels_expanded[
1643
+ depth_decoder_ignore_frames_idxs[0],
1644
+ depth_decoder_ignore_frames_idxs[1],
1645
+ 1 : self.config.num_codebooks,
1646
+ ] = -100
1647
+ labels = labels_expanded
1648
+
1649
+ return {
1650
+ "inputs_embeds": inputs_embeds,
1651
+ "labels": labels,
1652
+ "text_encoder_layer_hidden_states": text_encoder_layer_hidden_states,
1653
+ "text_ids_mask": text_ids_mask,
1654
+ }
1655
+
1656
+ def _get_audio_token_from_batch(self, audio_batch):
1657
+ with torch.no_grad():
1658
+ print("Encoding audio batch of shape:", audio_batch.shape)
1659
+ codec_outputs = self.codec_model.encode(audio_batch.unsqueeze(0))
1660
+ codebook_ids = codec_outputs.audio_codes.transpose(1, -1)[0]
1661
+ return codebook_ids
1662
+
1663
+ def prepare_inputs_for_generation(
1664
+ self,
1665
+ input_ids: torch.LongTensor,
1666
+ past_key_values: Cache | None = None,
1667
+ attention_mask: torch.LongTensor | None = None,
1668
+ inputs_embeds: torch.FloatTensor | None = None,
1669
+ cache_position: torch.LongTensor | None = None,
1670
+ **kwargs,
1671
+ ):
1672
+ model_inputs = super().prepare_inputs_for_generation(
1673
+ input_ids=input_ids,
1674
+ past_key_values=past_key_values,
1675
+ attention_mask=attention_mask,
1676
+ inputs_embeds=inputs_embeds,
1677
+ cache_position=cache_position,
1678
+ **kwargs,
1679
+ )
1680
+
1681
+ # if input_ids is not None and input_ids.ndim == 2 and model_inputs.get("inputs_embeds") is None:
1682
+ # print("------- Merging input_ids with input_values for generation...")
1683
+ # merged_inputs = self._merge_input_ids_with_input_values(
1684
+ # input_ids=input_ids,
1685
+ # input_values=kwargs.get("input_values"),
1686
+ # labels=kwargs.get("labels"),
1687
+ # )
1688
+ # model_inputs.update(
1689
+ # {"inputs_embeds": merged_inputs["inputs_embeds"], "labels": merged_inputs["labels"], "input_ids": None}
1690
+ # )
1691
+
1692
+ # cache_position = model_inputs["cache_position"]
1693
+ # is_prefill = cache_position is not None and cache_position[0] == 0
1694
+ # if is_prefill:
1695
+ # pass
1696
+ # else:
1697
+ # # Remove text_encoder_layer_hidden_states to indicate no fusion needed during decoding
1698
+ # model_inputs.pop("text_encoder_layer_hidden_states", None)
1699
+
1700
+ return model_inputs
1701
+
1702
+ @can_return_tuple
1703
+ @auto_docstring
1704
+ def forward(
1705
+ self,
1706
+ input_ids: torch.LongTensor = None,
1707
+ input_values: torch.Tensor | None = None,
1708
+ attention_mask: torch.Tensor | None = None,
1709
+ position_ids: torch.LongTensor | None = None,
1710
+ past_key_values: Cache | list[torch.FloatTensor] | None = None,
1711
+ inputs_embeds: torch.FloatTensor | None = None,
1712
+ labels: torch.LongTensor | None = None,
1713
+ use_cache: bool | None = None,
1714
+ cache_position: torch.LongTensor | None = None,
1715
+ logits_to_keep: int | torch.Tensor = 0,
1716
+ text_ids_mask: torch.Tensor | None = None,
1717
+ text_ids_len: torch.Tensor | None = None,
1718
+ **kwargs: Unpack[TransformersKwargs],
1719
+ ) -> tuple | BreezeOutputWithPast:
1720
+ r"""
1721
+ input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length, num_codebooks) or (batch_size, sequence_length)`):
1722
+ 1. (batch_size, sequence_length): corresponds to the input sequence prepared with the processor from the text prompt. Such input
1723
+ requires `input_values` to be provided so that audio can be encoded in codebook tokens and then merged with the text tokens.
1724
+
1725
+ 2. (batch_size, sequence_length, num_codebooks): codebook tokens generated during the autoregressive decoding. Such input is not meant to be used by end users.
1726
+
1727
+ Indices can be obtained using [`AutoTokenizer`]. See [`PreTrainedTokenizer.encode`] and
1728
+ [`PreTrainedTokenizer.__call__`] for details.
1729
+
1730
+ [What are input IDs?](../glossary#input-ids)
1731
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
1732
+ Labels for computing the masked language modeling loss. Indices should be in `[config.audio_token_id, -100, -101]`.
1733
+ Requires targeted `input_values` to be provided as audio tokens will be inferred from it using the `codec_model`.
1734
+ - `config.audio_token_id` indicates an audio frames (considering sequence length elements as frames)
1735
+ - `-100` will be ignored in the loss computation
1736
+ - `-101` indicates the audio frame will be used only for the backbone model (using the first codebook token as labels)
1737
+
1738
+ Such labels can be prepared using `output_labels=True` when calling [`the repository input preparation utilities`].
1739
+ logits_to_keep (`int` or `torch.Tensor`, *optional*):
1740
+ Kept for compatibility. Does not support another value than:
1741
+ 1. `0`, which is equivalent to keeping all logits, used in the training regime
1742
+ 2. `1`, which is equivalent to keeping only the last logit, used in the generation regime
1743
+
1744
+ text_ids_mask (`torch.BoolTensor` of shape `(batch_size, sequence_length)`, *optional*):
1745
+ Marks text-token positions consumed by the text encoder.
1746
+ text_ids_len (`torch.LongTensor` of shape `(batch_size,)`, *optional*):
1747
+ Number of valid text tokens for each item in the batch.
1748
+ """
1749
+ text_encoder_layer_hidden_states = None
1750
+ if input_ids is not None and input_ids.ndim == 2:
1751
+ merged_inputs = self._merge_input_ids_with_input_values(
1752
+ input_ids,
1753
+ input_values,
1754
+ labels,
1755
+ text_ids_mask=text_ids_mask,
1756
+ text_ids_len=text_ids_len,
1757
+ attention_mask=attention_mask,
1758
+ )
1759
+ inputs_embeds = merged_inputs["inputs_embeds"]
1760
+ labels = merged_inputs["labels"]
1761
+ input_ids = None
1762
+ text_encoder_layer_hidden_states = merged_inputs.get(
1763
+ "text_encoder_layer_hidden_states", None
1764
+ )
1765
+
1766
+ backbone_outputs = self.backbone_model(
1767
+ input_ids=input_ids,
1768
+ attention_mask=attention_mask,
1769
+ position_ids=position_ids,
1770
+ past_key_values=past_key_values,
1771
+ inputs_embeds=inputs_embeds,
1772
+ use_cache=use_cache,
1773
+ cache_position=cache_position,
1774
+ text_encoder_layer_hidden_states=text_encoder_layer_hidden_states,
1775
+ text_ids_mask=text_ids_mask,
1776
+ **kwargs,
1777
+ )
1778
+
1779
+ backbone_hidden_states = backbone_outputs[0]
1780
+ backbone_hidden_states_for_depth_decoder = backbone_hidden_states
1781
+
1782
+ backbone_all_hidden_states = backbone_outputs.hidden_states
1783
+
1784
+ if backbone_all_hidden_states is None:
1785
+ backbone_all_hidden_states = (backbone_hidden_states_for_depth_decoder,)
1786
+ # Only compute necessary logits, and do not upcast them to float if we are not computing the loss
1787
+ slice_indices = (
1788
+ slice(-logits_to_keep, None)
1789
+ if isinstance(logits_to_keep, int)
1790
+ else logits_to_keep
1791
+ )
1792
+
1793
+ loss = None
1794
+ backbone_loss = None
1795
+ backbone_logits = None
1796
+ depth_decoder_loss = None
1797
+ depth_decoder_outputs = None
1798
+
1799
+ if labels is not None:
1800
+ # Filter out num_items_in_batch from kwargs to ensure reduction='mean' is used
1801
+ # This prevents incorrect loss normalization when backbone and depth decoder
1802
+ # operate on different numbers of tokens
1803
+ loss_kwargs = {k: v for k, v in kwargs.items() if k != "num_items_in_batch"}
1804
+
1805
+ # select first codebook as labels for the backbone model
1806
+ backbone_labels = labels[:, :, 0]
1807
+
1808
+ backbone_logits = self.lm_head(backbone_hidden_states[:, slice_indices, :])
1809
+ backbone_loss = self.loss_function(
1810
+ logits=backbone_logits,
1811
+ labels=backbone_labels,
1812
+ vocab_size=self.lm_head.out_features,
1813
+ **loss_kwargs,
1814
+ )
1815
+
1816
+ # Compute logits only when actually needed
1817
+ if backbone_logits is None and labels is None:
1818
+ backbone_logits = self.lm_head(backbone_hidden_states[:, slice_indices, :])
1819
+
1820
+ if labels is not None:
1821
+ # for the depth decoder, we need to select the frames to train on
1822
+ # those are frames where the label is not uniformly `ignore_index` along the codebook dimension
1823
+ train_mask = ~(labels[:, :, 1:] == -100).all(dim=-1)
1824
+ depth_decoder_input_ids = labels[train_mask][
1825
+ ..., : self.config.num_codebooks - 1
1826
+ ]
1827
+ # add place holder in position 0 that will be replaced by the backbone_last_hidden_state
1828
+ depth_decoder_input_ids = nn.functional.pad(
1829
+ depth_decoder_input_ids, (1, 0), value=0
1830
+ )
1831
+
1832
+ train_idxs = train_mask.nonzero(as_tuple=True)
1833
+ backbone_last_hidden_states = backbone_hidden_states_for_depth_decoder[
1834
+ train_idxs[0], train_idxs[1] - 1, :
1835
+ ]
1836
+ depth_decoder_labels = labels[train_mask]
1837
+
1838
+ depth_decoder_outputs = self.depth_decoder(
1839
+ input_ids=depth_decoder_input_ids,
1840
+ backbone_last_hidden_state=backbone_last_hidden_states,
1841
+ use_cache=use_cache,
1842
+ return_dict=True,
1843
+ labels=depth_decoder_labels,
1844
+ **loss_kwargs,
1845
+ )
1846
+
1847
+ depth_decoder_loss = depth_decoder_outputs.loss
1848
+ if self.training:
1849
+ loss = (
1850
+ backbone_loss
1851
+ + depth_decoder_loss * self.config.depth_header_loss_weight
1852
+ )
1853
+ else:
1854
+ loss = backbone_loss + depth_decoder_loss
1855
+ depth_decoder_hidden_states = None
1856
+ if depth_decoder_outputs is not None:
1857
+ depth_decoder_hidden_states = depth_decoder_outputs.hidden_states
1858
+
1859
+ return BreezeOutputWithPast(
1860
+ loss=loss,
1861
+ backbone_loss=backbone_loss,
1862
+ depth_decoder_loss=depth_decoder_loss,
1863
+ logits=backbone_logits,
1864
+ past_key_values=backbone_outputs.past_key_values,
1865
+ hidden_states=backbone_all_hidden_states,
1866
+ attentions=backbone_outputs.attentions,
1867
+ depth_decoder_logits=depth_decoder_outputs.logits
1868
+ if depth_decoder_outputs is not None
1869
+ else None,
1870
+ depth_decoder_past_key_values=depth_decoder_outputs.past_key_values
1871
+ if depth_decoder_outputs is not None
1872
+ else None,
1873
+ depth_decoder_hidden_states=depth_decoder_hidden_states,
1874
+ depth_decoder_attentions=depth_decoder_outputs.attentions
1875
+ if depth_decoder_outputs is not None
1876
+ else None,
1877
+ )
1878
+
1879
+
1880
+ __all__ = [
1881
+ "BreezeBackboneModel",
1882
+ "BreezeDepthDecoderForCausalLM",
1883
+ "BreezeDepthDecoderModel",
1884
+ "BreezeForConditionalGeneration",
1885
+ "BreezePreTrainedModel",
1886
+ ]
1887
+
1888
+ AutoModel.register(BreezeConfig, BreezeForConditionalGeneration, exist_ok=True)
models/breeze_backbone_factory.py ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Breeze Backbone Factory and Adapter
3
+
4
+ This module provides:
5
+ 1. BreezeBackboneFactory: Factory class to create different types of backbones
6
+ 2. BreezeBackboneAdapter: Adapter to use external pretrained LLMs as Breeze backbone
7
+
8
+ Usage:
9
+ from models.breeze_backbone_factory import BreezeBackboneFactory
10
+
11
+ # In BreezeForConditionalGeneration.__init__:
12
+ self.backbone_model = BreezeBackboneFactory.create_backbone(config)
13
+ """
14
+
15
+
16
+ import torch
17
+ from torch import nn
18
+ from transformers import AutoConfig
19
+ from transformers.cache_utils import Cache, DynamicCache
20
+ from transformers.masking_utils import create_causal_mask
21
+ from transformers.modeling_outputs import BaseModelOutputWithPast
22
+ from transformers.utils import logging
23
+
24
+ logger = logging.get_logger(__name__)
25
+
26
+
27
+ class BreezeBackboneFactory:
28
+ """Factory class to create Breeze backbone models"""
29
+
30
+ SUPPORTED_MODELS = {
31
+ "breeze": "Native Breeze backbone",
32
+ "qwen3": "Qwen3ForCausalLM",
33
+ "llama3": "Llama3ForCausalLM",
34
+ # Add more models as needed
35
+ }
36
+
37
+ @staticmethod
38
+ def create_backbone(config):
39
+ """
40
+ Create backbone model based on config.backbone_model_type
41
+
42
+ Args:
43
+ config: BreezeConfig instance with backbone_model_type field
44
+
45
+ Returns:
46
+ Backbone model with interface compatible with BreezeBackboneModel
47
+ """
48
+ backbone_type = getattr(config, "backbone_model_type", "breeze")
49
+
50
+ if backbone_type == "breeze":
51
+ # Import here to avoid circular dependency
52
+ from .breeze import BreezeBackboneModel
53
+
54
+ return BreezeBackboneModel(config)
55
+ else:
56
+ # Use adapter for external LLMs
57
+ if backbone_type not in BreezeBackboneFactory.SUPPORTED_MODELS:
58
+ logger.warning(
59
+ f"Backbone type '{backbone_type}' not in known types. "
60
+ f"Attempting to create adapter anyway..."
61
+ )
62
+ return BreezeBackboneAdapter.create_from_config(config)
63
+
64
+
65
+ class BreezeBackboneAdapter(nn.Module):
66
+ """
67
+ Adapter to use external pretrained LLM as Breeze Backbone
68
+
69
+ Key modifications from original LLM:
70
+ 1. Replace LLM's embedding with BreezeBackboneModelEmbeddings (for audio tokens)
71
+ 2. Keep LLM's transformer layers
72
+ 3. Keep LLM's norm layer
73
+ 4. Remove LLM's lm_head (Breeze has its own)
74
+
75
+ Interface: Compatible with BreezeBackboneModel's forward signature
76
+ """
77
+
78
+ def __init__(self, config, layers, norm, rotary_emb):
79
+ """
80
+ Args:
81
+ config: BreezeConfig (updated with LLM's architecture params)
82
+ layers: nn.ModuleList of transformer layers from LLM
83
+ norm: RMSNorm layer from LLM
84
+ rotary_emb: Rotary embedding layer from LLM
85
+ """
86
+ super().__init__()
87
+ self.config = config
88
+
89
+ # Ensure _attn_implementation is set on config
90
+ if not hasattr(config, "_attn_implementation"):
91
+ config._attn_implementation = "eager"
92
+
93
+ # Breeze's custom embedding for audio+text tokens
94
+ from .breeze import BreezeBackboneModelEmbeddings
95
+
96
+ self.embed_tokens = BreezeBackboneModelEmbeddings(config)
97
+
98
+ # Pretrained LLM's transformer components
99
+ self.layers = layers
100
+ self.norm = norm
101
+ self.rotary_emb = rotary_emb
102
+ self.gradient_checkpointing = False
103
+
104
+ @classmethod
105
+ def create_from_config(cls, config):
106
+ """
107
+ Create adapter from config (architecture only, no weight loading)
108
+
109
+ Weight loading is handled separately by breeze_model_init()
110
+
111
+ Args:
112
+ config: BreezeConfig with backbone_model_name_or_path set
113
+
114
+ Returns:
115
+ BreezeBackboneAdapter instance with initialized architecture
116
+ """
117
+ backbone_config = getattr(config, "backbone_config", None)
118
+ backbone_path = getattr(config, "backbone_model_name_or_path", None)
119
+ if backbone_config is None and backbone_path is None:
120
+ raise ValueError(
121
+ "config.backbone_config or config.backbone_model_name_or_path must be set"
122
+ )
123
+
124
+ backbone_type = config.backbone_model_type
125
+ if backbone_config is not None:
126
+ logger.info(
127
+ f"Creating backbone adapter for {backbone_type} from bundled config"
128
+ )
129
+ llm_config = AutoConfig.for_model(**backbone_config)
130
+ else:
131
+ logger.info(
132
+ f"Creating backbone adapter for {backbone_type} from {backbone_path}"
133
+ )
134
+ llm_config = AutoConfig.from_pretrained(backbone_path)
135
+
136
+ # Transfer attention implementation setting from BreezeConfig to LLM config
137
+ if (
138
+ hasattr(config, "_attn_implementation")
139
+ and config._attn_implementation is not None
140
+ ):
141
+ llm_config._attn_implementation = config._attn_implementation
142
+ logger.info(
143
+ f"Set attention implementation to: {config._attn_implementation}"
144
+ )
145
+ else:
146
+ # Default to eager if not specified
147
+ llm_config._attn_implementation = "eager"
148
+ logger.info("Attention implementation not specified, defaulting to: eager")
149
+
150
+ # Create layers based on backbone type
151
+ if backbone_type == "qwen3":
152
+ layers, norm, rotary_emb = cls._create_qwen3_layers(llm_config)
153
+ elif backbone_type == "llama3":
154
+ layers, norm, rotary_emb = cls._create_llama3_layers(llm_config)
155
+ else:
156
+ raise ValueError(
157
+ f"Unsupported backbone_model_type: {backbone_type}. "
158
+ f"Supported types: {list(BreezeBackboneFactory.SUPPORTED_MODELS.keys())}"
159
+ )
160
+
161
+ return cls(config, layers, norm, rotary_emb)
162
+
163
+ @staticmethod
164
+ def _create_qwen3_layers(llm_config):
165
+ """Create Qwen3 layer architecture"""
166
+ from transformers.models.qwen3.modeling_qwen3 import (
167
+ Qwen3DecoderLayer,
168
+ Qwen3RMSNorm,
169
+ Qwen3RotaryEmbedding,
170
+ )
171
+
172
+ layers = nn.ModuleList(
173
+ [
174
+ Qwen3DecoderLayer(llm_config, layer_idx)
175
+ for layer_idx in range(llm_config.num_hidden_layers)
176
+ ]
177
+ )
178
+ norm = Qwen3RMSNorm(llm_config.hidden_size, eps=llm_config.rms_norm_eps)
179
+ rotary_emb = Qwen3RotaryEmbedding(config=llm_config)
180
+
181
+ return layers, norm, rotary_emb
182
+
183
+ @staticmethod
184
+ def _create_llama3_layers(llm_config):
185
+ """Create Llama3 layer architecture"""
186
+ from transformers.models.llama.modeling_llama import (
187
+ LlamaDecoderLayer,
188
+ LlamaRMSNorm,
189
+ LlamaRotaryEmbedding,
190
+ )
191
+
192
+ layers = nn.ModuleList(
193
+ [
194
+ LlamaDecoderLayer(llm_config, layer_idx)
195
+ for layer_idx in range(llm_config.num_hidden_layers)
196
+ ]
197
+ )
198
+ norm = LlamaRMSNorm(llm_config.hidden_size, eps=llm_config.rms_norm_eps)
199
+ rotary_emb = LlamaRotaryEmbedding(config=llm_config)
200
+
201
+ return layers, norm, rotary_emb
202
+
203
+ def forward(
204
+ self,
205
+ input_ids: torch.LongTensor | None = None,
206
+ attention_mask: torch.Tensor | None = None,
207
+ position_ids: torch.LongTensor | None = None,
208
+ past_key_values: Cache | None = None,
209
+ inputs_embeds: torch.FloatTensor | None = None,
210
+ cache_position: torch.LongTensor | None = None,
211
+ text_encoder_layer_hidden_states: torch.FloatTensor | None = None,
212
+ text_ids_mask: torch.BoolTensor | None = None,
213
+ use_cache: bool | None = None,
214
+ **kwargs,
215
+ ) -> BaseModelOutputWithPast:
216
+ """
217
+ Forward pass - interface compatible with BreezeBackboneModel
218
+
219
+ Args:
220
+ input_ids: (batch_size, seq_len, num_codebooks) or (batch_size, seq_len)
221
+ inputs_embeds: Pre-computed embeddings
222
+ attention_mask: Attention mask
223
+ position_ids: Position IDs
224
+ past_key_values: Cached key/values for generation
225
+ cache_position: Cache position tensor
226
+ use_cache: Whether to use KV cache
227
+
228
+ Returns:
229
+ BaseModelOutputWithPast with last_hidden_state
230
+ """
231
+ # Embed tokens if not provided
232
+ if inputs_embeds is None:
233
+ inputs_embeds = self.embed_tokens(input_ids)
234
+
235
+ # Initialize cache
236
+ if use_cache and past_key_values is None:
237
+ past_key_values = DynamicCache(config=self.config)
238
+
239
+ # Compute cache_position if not provided
240
+ if cache_position is None:
241
+ past_seen_tokens = (
242
+ past_key_values.get_seq_length() if past_key_values is not None else 0
243
+ )
244
+ cache_position = torch.arange(
245
+ past_seen_tokens,
246
+ past_seen_tokens + inputs_embeds.shape[1],
247
+ device=inputs_embeds.device,
248
+ )
249
+
250
+ # Compute position_ids if not provided
251
+ if position_ids is None:
252
+ position_ids = cache_position.unsqueeze(0)
253
+
254
+ # Create causal mask
255
+ causal_mask = create_causal_mask(
256
+ config=self.config,
257
+ input_embeds=inputs_embeds,
258
+ attention_mask=attention_mask,
259
+ cache_position=cache_position,
260
+ past_key_values=past_key_values,
261
+ position_ids=position_ids,
262
+ )
263
+
264
+ # Compute position embeddings (for RoPE)
265
+ hidden_states = inputs_embeds
266
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
267
+
268
+ # Pass through transformer layers
269
+ for layer_idx, decoder_layer in enumerate(self.layers):
270
+ if text_encoder_layer_hidden_states is not None and layer_idx > 0:
271
+ # skip for first layer fusion, cause it may has aleady been done in convert_input_ids_to_embeds() function
272
+ text_layer_hidden_states = text_encoder_layer_hidden_states[layer_idx]
273
+ # hidden_states[text_ids_mask][..., -self.config.hidden_size//2:] = text_layer_hidden_states
274
+
275
+ hidden_states_half_first, hidden_states_half_second = (
276
+ hidden_states.split(self.config.hidden_size // 2, dim=-1)
277
+ )
278
+ if self.training:
279
+ hidden_states_half_second = hidden_states_half_second.clone()
280
+ hidden_states_half_second[text_ids_mask] = text_layer_hidden_states
281
+ # hidden_states_half_second = torch.where(
282
+ # text_ids_mask.unsqueeze(-1),
283
+ # text_layer_hidden_states,
284
+ # hidden_states_half_second,
285
+ # )
286
+ hidden_states = torch.cat(
287
+ [hidden_states_half_first, hidden_states_half_second], dim=-1
288
+ )
289
+
290
+ hidden_states = decoder_layer(
291
+ hidden_states,
292
+ attention_mask=causal_mask,
293
+ position_ids=position_ids,
294
+ past_key_values=past_key_values,
295
+ cache_position=cache_position,
296
+ position_embeddings=position_embeddings,
297
+ **kwargs,
298
+ )
299
+
300
+ # Final norm
301
+ hidden_states = self.norm(hidden_states)
302
+
303
+ return BaseModelOutputWithPast(
304
+ last_hidden_state=hidden_states,
305
+ past_key_values=past_key_values,
306
+ )
307
+
308
+
309
+ __all__ = ["BreezeBackboneAdapter", "BreezeBackboneFactory"]
models/breeze_base_config.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 Sesame and The HuggingFace Inc. team. All rights reserved.
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
+ from transformers.configuration_utils import PretrainedConfig
16
+ from transformers.modeling_rope_utils import rope_config_validation
17
+ from transformers.models.auto.configuration_auto import AutoConfig
18
+ from transformers.utils import logging
19
+
20
+ logger = logging.get_logger(__name__)
21
+
22
+
23
+ class BreezeDepthDecoderConfig(PretrainedConfig):
24
+ r"""
25
+ This is the configuration class to store the configuration of a [`BreezeDepthDecoderModel`]. It is used to instantiate an Breeze depth decoder
26
+ model according to the specified arguments, defining the model architecture. Instantiating a configuration with the defaults will yield
27
+ a similar configuration to that of the breeze-1b.
28
+
29
+ e.g. a Breeze checkpoint
30
+
31
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
32
+ documentation from [`PretrainedConfig`] for more information.
33
+
34
+
35
+ Args:
36
+ num_codebooks (`int`, *optional*, defaults to 32):
37
+ Number of codebooks used in the underlying codec model responsible for tokenizing the audio.
38
+ backbone_hidden_size (`int`, *optional*, defaults to 2048):
39
+ Dimension of the hidden representations of the backbone model used with this depth decoder.
40
+ vocab_size (`int`, *optional*, defaults to 2051):
41
+ Vocabulary size of the BreezeDepthDecoder model. Defines the number of different audio tokens that can be represented by each codebook.
42
+ hidden_size (`int`, *optional*, defaults to 1024):
43
+ Dimension of the hidden representations.
44
+ intermediate_size (`int`, *optional*, defaults to 8192):
45
+ Dimension of the MLP representations.
46
+ num_hidden_layers (`int`, *optional*, defaults to 4):
47
+ Number of hidden layers in the Transformer decoder.
48
+ num_attention_heads (`int`, *optional*, defaults to 8):
49
+ Number of attention heads for each attention layer in the Transformer decoder.
50
+ num_key_value_heads (`int`, *optional*, defaults to 2):
51
+ This is the number of key_value heads that should be used to implement Grouped Query Attention. If
52
+ `num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
53
+ `num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
54
+ converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
55
+ by meanpooling all the original heads within that group. For more details, check out [this
56
+ paper](https://huggingface.co/papers/2305.13245). If it is not specified, will default to
57
+ `num_attention_heads`.
58
+ hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
59
+ The non-linear activation function (function or string) in the decoder.
60
+ max_position_embeddings (`int`, *optional*, defaults to 33):
61
+ The maximum sequence length that this model might ever be used with.
62
+ initializer_range (`float`, *optional*, defaults to 0.02):
63
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
64
+ rms_norm_eps (`float`, *optional*, defaults to 1e-05):
65
+ The epsilon used by the rms normalization layers.
66
+ use_cache (`bool`, *optional*, defaults to `True`):
67
+ Whether or not the model should return the last key/values attentions (not used by all models). Only
68
+ relevant if `config.is_decoder=True`.
69
+ pad_token_id (`int`, *optional*, defaults to 2050):
70
+ Padding token id.
71
+ bos_token_id (`int`, *optional*):
72
+ Beginning of stream token id.
73
+ eos_token_id (`int`, *optional*):
74
+ End of stream token id.
75
+ rope_theta (`float`, *optional*, defaults to 500000):
76
+ The base period of the RoPE embeddings.
77
+ rope_scaling (`Dict`, *optional*):
78
+ Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
79
+ and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
80
+ accordingly.
81
+ Expected contents:
82
+ `rope_type` (`str`):
83
+ The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
84
+ 'llama3'], with 'default' being the original RoPE implementation.
85
+ `factor` (`float`, *optional*):
86
+ Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
87
+ most scaling types, a `factor` of x will enable the model to handle sequences of length x *
88
+ original maximum pre-trained length.
89
+ `original_max_position_embeddings` (`int`, *optional*):
90
+ Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
91
+ pretraining.
92
+ `attention_factor` (`float`, *optional*):
93
+ Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
94
+ computation. If unspecified, it defaults to value recommended by the implementation, using the
95
+ `factor` field to infer the suggested value.
96
+ `beta_fast` (`float`, *optional*):
97
+ Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
98
+ ramp function. If unspecified, it defaults to 32.
99
+ `beta_slow` (`float`, *optional*):
100
+ Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
101
+ ramp function. If unspecified, it defaults to 1.
102
+ `short_factor` (`list[float]`, *optional*):
103
+ Only used with 'longrope'. The scaling factor to be applied to short contexts (<
104
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
105
+ size divided by the number of attention heads divided by 2
106
+ `long_factor` (`list[float]`, *optional*):
107
+ Only used with 'longrope'. The scaling factor to be applied to long contexts (<
108
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
109
+ size divided by the number of attention heads divided by 2
110
+ `low_freq_factor` (`float`, *optional*):
111
+ Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
112
+ `high_freq_factor` (`float`, *optional*):
113
+ Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
114
+ attention_bias (`bool`, *optional*, defaults to `False`):
115
+ Whether to use a bias in the query, key, value and output projection layers during self-attention.
116
+ attention_dropout (`float`, *optional*, defaults to 0.0):
117
+ The dropout ratio for the attention probabilities.
118
+ mlp_bias (`bool`, *optional*, defaults to `False`):
119
+ Whether to use a bias in up_proj, down_proj and gate_proj layers in the MLP layers.
120
+ head_dim (`int`, *optional*):
121
+ The attention head dimension. If None, it will default to hidden_size // num_attention_heads
122
+
123
+ ```python
124
+ >>> from models.breeze import BreezeDepthDecoderModel
125
+ from models.breeze_config import BreezeDepthDecoderConfig
126
+
127
+ >>> # Initializing a BreezeDepthDecoder
128
+ >>> configuration = BreezeDepthDecoderConfig()
129
+ >>> model = BreezeDepthDecoderModel(configuration)
130
+
131
+ >>> # Accessing the model configuration
132
+ >>> configuration = model.config
133
+ ```"""
134
+
135
+ model_type = "breeze_depth_decoder_model"
136
+ base_config_key = "depth_decoder_config"
137
+ keys_to_ignore_at_inference = ["past_key_values"]
138
+
139
+ def __init__(
140
+ self,
141
+ num_codebooks=32,
142
+ backbone_hidden_size=2048,
143
+ vocab_size=2051,
144
+ hidden_size=1024,
145
+ intermediate_size=8192,
146
+ num_hidden_layers=4,
147
+ num_attention_heads=8,
148
+ num_key_value_heads=2,
149
+ hidden_act="silu",
150
+ max_position_embeddings=33,
151
+ initializer_range=0.02,
152
+ rms_norm_eps=1e-5,
153
+ use_cache=True,
154
+ pad_token_id=None,
155
+ bos_token_id=None,
156
+ eos_token_id=None,
157
+ rope_theta=500000,
158
+ rope_scaling=None,
159
+ attention_bias=False,
160
+ attention_dropout=0.0,
161
+ mlp_bias=False,
162
+ head_dim=None,
163
+ **kwargs,
164
+ ):
165
+ if kwargs.pop("tie_word_embeddings", False):
166
+ raise ValueError(
167
+ "`tie_word_embeddings=True` is not supported for BreezeDepthDecoderConfig"
168
+ )
169
+
170
+ super().__init__(
171
+ pad_token_id=pad_token_id,
172
+ bos_token_id=bos_token_id,
173
+ eos_token_id=eos_token_id,
174
+ tie_word_embeddings=False,
175
+ **kwargs,
176
+ )
177
+ self.num_codebooks = num_codebooks
178
+ self.vocab_size = vocab_size
179
+ self.backbone_hidden_size = backbone_hidden_size
180
+ self.max_position_embeddings = max_position_embeddings
181
+ self.hidden_size = hidden_size
182
+ self.intermediate_size = intermediate_size
183
+ self.num_hidden_layers = num_hidden_layers
184
+ self.num_attention_heads = num_attention_heads
185
+
186
+ # for backward compatibility
187
+ if num_key_value_heads is None:
188
+ num_key_value_heads = num_attention_heads
189
+
190
+ self.num_key_value_heads = num_key_value_heads
191
+ self.hidden_act = hidden_act
192
+ self.initializer_range = initializer_range
193
+ self.rms_norm_eps = rms_norm_eps
194
+ self.use_cache = use_cache
195
+ self.rope_theta = rope_theta
196
+ self.rope_scaling = rope_scaling
197
+ self.attention_bias = attention_bias
198
+ self.attention_dropout = attention_dropout
199
+ self.mlp_bias = mlp_bias
200
+ self.head_dim = (
201
+ head_dim
202
+ if head_dim is not None
203
+ else self.hidden_size // self.num_attention_heads
204
+ )
205
+ # Validate the correctness of rotary position embeddings parameters
206
+ # BC: if there is a 'type' field, copy it it to 'rope_type'.
207
+ if self.rope_scaling is not None and "type" in self.rope_scaling:
208
+ self.rope_scaling["rope_type"] = self.rope_scaling["type"]
209
+ rope_config_validation(self)
210
+
211
+
212
+ class BreezeConfig(PretrainedConfig):
213
+ r"""
214
+ This is the configuration class to store the configuration of a [`BreezeForConditionalGeneration`]. It is used to instantiate an Breeze
215
+ model according to the specified arguments, defining the model architecture. Instantiating a configuration
216
+ with the defaults will yield a similar configuration to that of the breeze-1b.
217
+
218
+ e.g. a Breeze checkpoint
219
+
220
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
221
+ documentation from [`PretrainedConfig`] for more information.
222
+
223
+ Args:
224
+ num_codebooks (`int`, *optional*, defaults to 32):
225
+ Number of codebooks used in the underlying codec model responsible for tokenizing the audio.
226
+ vocab_size (`int`, *optional*, defaults to 2051):
227
+ Vocabulary size of the Breeze model. Defines the number of different audio tokens that can be represented by each codebook.
228
+ text_vocab_size (`int`, *optional*, defaults to 128256):
229
+ Vocabulary size of the text input for the Breeze model. Defines the number of different text tokens that can be represented.
230
+ hidden_size (`int`, *optional*, defaults to 2048):
231
+ Dimension of the hidden representations of the backbone model.
232
+ intermediate_size (`int`, *optional*, defaults to 8192):
233
+ Dimension of the MLP representations of the backbone model.
234
+ num_hidden_layers (`int`, *optional*, defaults to 16):
235
+ Number of hidden layers in the backbone model Transformer decoder.
236
+ num_attention_heads (`int`, *optional*, defaults to 32):
237
+ Number of attention heads for each attention layer in the backbone model Transformer decoder.
238
+ num_key_value_heads (`int`, *optional*, defaults to 8):
239
+ This is the number of key_value heads that should be used to implement Grouped Query Attention. If
240
+ `num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
241
+ `num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
242
+ converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
243
+ by meanpooling all the original heads within that group. For more details, check out [this
244
+ paper](https://huggingface.co/papers/2305.13245).
245
+ hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
246
+ The non-linear activation function (function or string) in the backbone model Transformer decoder.
247
+ max_position_embeddings (`int`, *optional*, defaults to 2048):
248
+ The maximum sequence length that this model might ever be used with.
249
+ initializer_range (`float`, *optional*, defaults to 0.02):
250
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
251
+ rms_norm_eps (`float`, *optional*, defaults to 1e-05):
252
+ The epsilon used by the rms normalization layers.
253
+ use_cache (`bool`, *optional*, defaults to `True`):
254
+ Whether or not the model should return the last key/values attentions (not used by all models). Only
255
+ relevant if `config.is_decoder=True`.
256
+ pad_token_id (`int`, *optional*, defaults to 128002):
257
+ Padding token id.
258
+ codebook_pad_token_id (`int`, *optional*, defaults to 2050):
259
+ Padding token id for codebook tokens.
260
+ codebook_eos_token_id (`int`, *optional*, defaults to 0):
261
+ End of stream token id for codebook tokens.
262
+ bos_token_id (`int`, *optional*, defaults to 128000):
263
+ Beginning of stream token id.
264
+ eos_token_id (`int`, *optional*):
265
+ End of stream token id.
266
+ audio_token_id (`int`, *optional*, defaults to 128002):
267
+ Audio token id in the text input.
268
+ audio_eos_token_id (`int`, *optional*, defaults to 128003):
269
+ End of stream token id for audio in the text input.
270
+ rope_theta (`float`, *optional*, defaults to 500000):
271
+ The base period of the RoPE embeddings.
272
+ rope_scaling (`Dict`, *optional*, defaults to `{'factor': 32.0, 'high_freq_factor': 0.5, 'low_freq_factor': 0.125, 'original_max_position_embeddings': 1024, 'rope_type': 'llama3'}`):
273
+ Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
274
+ and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
275
+ accordingly.
276
+ Expected contents:
277
+ `rope_type` (`str`):
278
+ The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
279
+ 'llama3'], with 'default' being the original RoPE implementation.
280
+ `factor` (`float`, *optional*):
281
+ Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
282
+ most scaling types, a `factor` of x will enable the model to handle sequences of length x *
283
+ original maximum pre-trained length.
284
+ `original_max_position_embeddings` (`int`, *optional*):
285
+ Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
286
+ pretraining.
287
+ `attention_factor` (`float`, *optional*):
288
+ Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
289
+ computation. If unspecified, it defaults to value recommended by the implementation, using the
290
+ `factor` field to infer the suggested value.
291
+ `beta_fast` (`float`, *optional*):
292
+ Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
293
+ ramp function. If unspecified, it defaults to 32.
294
+ `beta_slow` (`float`, *optional*):
295
+ Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
296
+ ramp function. If unspecified, it defaults to 1.
297
+ `short_factor` (`list[float]`, *optional*):
298
+ Only used with 'longrope'. The scaling factor to be applied to short contexts (<
299
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
300
+ size divided by the number of attention heads divided by 2
301
+ `long_factor` (`list[float]`, *optional*):
302
+ Only used with 'longrope'. The scaling factor to be applied to long contexts (<
303
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
304
+ size divided by the number of attention heads divided by 2
305
+ `low_freq_factor` (`float`, *optional*):
306
+ Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
307
+ `high_freq_factor` (`float`, *optional*):
308
+ Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
309
+ attention_bias (`bool`, *optional*, defaults to `False`):
310
+ Whether to use a bias in the query, key, value and output projection layers during self-attention.
311
+ attention_dropout (`float`, *optional*, defaults to 0.0):
312
+ The dropout ratio for the attention probabilities.
313
+ mlp_bias (`bool`, *optional*, defaults to `False`):
314
+ Whether to use a bias in up_proj, down_proj and gate_proj layers in the MLP layers.
315
+ head_dim (`int`, *optional*):
316
+ The attention head dimension. If None, it will default to hidden_size // num_attention_heads
317
+ tie_codebooks_embeddings (`bool`, *optional*, defaults to `True`):
318
+ Whether to tie the codebook tokens embeddings of the backbone model to the codebook tokens embeddings of the depth decoder.
319
+ depth_decoder_config (`BreezeDepthDecoderConfig`, *optional*):
320
+ Configuration for the depth decoder.
321
+ codec_config (`PretrainedConfig`, *optional*):
322
+ Configuration for the codec.
323
+
324
+ ```python
325
+ >>> from models.breeze import BreezeForConditionalGeneration
326
+ from models.breeze_config import BreezeConfig
327
+
328
+ >>> # Initializing a BreezeConfig
329
+ >>> configuration = BreezeConfig()
330
+
331
+ >>> # Initializing a model
332
+ >>> model = BreezeForConditionalGeneration(configuration)
333
+
334
+ >>> # Accessing the model configuration
335
+ >>> configuration = model.config
336
+ ```"""
337
+
338
+ model_type = "breeze"
339
+ base_config_key = "breeze_config"
340
+ keys_to_ignore_at_inference = ["past_key_values"]
341
+ sub_configs = {
342
+ "codec_config": AutoConfig,
343
+ "depth_decoder_config": BreezeDepthDecoderConfig,
344
+ }
345
+
346
+ def __init__(
347
+ self,
348
+ num_codebooks=32,
349
+ vocab_size=2051,
350
+ text_vocab_size=128256,
351
+ hidden_size=2048,
352
+ intermediate_size=8192,
353
+ num_hidden_layers=16,
354
+ num_attention_heads=32,
355
+ num_key_value_heads=8,
356
+ hidden_act="silu",
357
+ max_position_embeddings=2048,
358
+ initializer_range=0.02,
359
+ rms_norm_eps=1e-5,
360
+ use_cache=True,
361
+ pad_token_id=128002,
362
+ codebook_pad_token_id=2050,
363
+ codebook_eos_token_id=0,
364
+ bos_token_id=128000,
365
+ eos_token_id=None,
366
+ audio_token_id=128002,
367
+ audio_eos_token_id=128003,
368
+ rope_theta=500000,
369
+ rope_scaling=None,
370
+ attention_bias=False,
371
+ attention_dropout=0.0,
372
+ mlp_bias=False,
373
+ head_dim=None,
374
+ tie_codebooks_embeddings=True,
375
+ depth_decoder_config=None,
376
+ codec_config=None,
377
+ **kwargs,
378
+ ):
379
+ if kwargs.pop("tie_word_embeddings", False):
380
+ raise ValueError(
381
+ "`tie_word_embeddings=True` is not supported for BreezeConfig"
382
+ )
383
+
384
+ super().__init__(
385
+ pad_token_id=pad_token_id,
386
+ bos_token_id=bos_token_id,
387
+ eos_token_id=eos_token_id,
388
+ tie_word_embeddings=False,
389
+ **kwargs,
390
+ )
391
+
392
+ if depth_decoder_config is None:
393
+ self.depth_decoder_config = BreezeDepthDecoderConfig()
394
+ logger.info(
395
+ "depth_decoder_config is None, using default depth decoder config."
396
+ )
397
+ elif isinstance(depth_decoder_config, dict):
398
+ self.depth_decoder_config = BreezeDepthDecoderConfig(**depth_decoder_config)
399
+ elif isinstance(depth_decoder_config, BreezeDepthDecoderConfig):
400
+ self.depth_decoder_config = depth_decoder_config
401
+
402
+ if codec_config is None:
403
+ self.codec_config = AutoConfig.for_model("mimi")
404
+ logger.info("codec_config is None, using default audio encoder config.")
405
+ elif isinstance(codec_config, dict):
406
+ self.codec_config = AutoConfig.for_model(**codec_config)
407
+ elif isinstance(codec_config, PretrainedConfig):
408
+ self.codec_config = codec_config
409
+
410
+ self.text_vocab_size = text_vocab_size
411
+ self.num_codebooks = num_codebooks
412
+ self.audio_token_id = audio_token_id
413
+ self.audio_eos_token_id = audio_eos_token_id
414
+ self.codebook_pad_token_id = codebook_pad_token_id
415
+ self.codebook_eos_token_id = codebook_eos_token_id
416
+ self.tie_codebooks_embeddings = tie_codebooks_embeddings
417
+
418
+ self.vocab_size = vocab_size
419
+ self.max_position_embeddings = max_position_embeddings
420
+ self.hidden_size = hidden_size
421
+ self.intermediate_size = intermediate_size
422
+ self.num_hidden_layers = num_hidden_layers
423
+ self.num_attention_heads = num_attention_heads
424
+
425
+ # for backward compatibility
426
+ if num_key_value_heads is None:
427
+ num_key_value_heads = num_attention_heads
428
+
429
+ self.num_key_value_heads = num_key_value_heads
430
+ self.hidden_act = hidden_act
431
+ self.initializer_range = initializer_range
432
+ self.rms_norm_eps = rms_norm_eps
433
+ self.use_cache = use_cache
434
+ self.rope_theta = rope_theta
435
+ self.rope_scaling = rope_scaling
436
+ self.attention_bias = attention_bias
437
+ self.attention_dropout = attention_dropout
438
+ self.mlp_bias = mlp_bias
439
+ self.head_dim = (
440
+ head_dim
441
+ if head_dim is not None
442
+ else self.hidden_size // self.num_attention_heads
443
+ )
444
+ # Validate the correctness of rotary position embeddings parameters
445
+ # BC: if there is a 'type' field, copy it it to 'rope_type'.
446
+ if self.rope_scaling is not None and "type" in self.rope_scaling:
447
+ self.rope_scaling["rope_type"] = self.rope_scaling["type"]
448
+ rope_config_validation(self)
449
+
450
+
451
+ __all__ = [
452
+ "BreezeConfig",
453
+ "BreezeDepthDecoderConfig",
454
+ ]
models/breeze_config.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from transformers import AutoConfig, AutoModel
4
+ from transformers.models.t5gemma.configuration_t5gemma import T5GemmaModuleConfig
5
+ from transformers.models.t5gemma.modeling_t5gemma import T5GemmaEncoder
6
+
7
+ from .breeze_base_config import BreezeConfig as BreezeConfig_transformers
8
+ from .breeze_base_config import BreezeDepthDecoderConfig
9
+ from .t5gemma2_compat import T5Gemma2TextConfig, T5Gemma2TextEncoder
10
+
11
+
12
+ class T5GemmaEncoderWrapper(T5GemmaEncoder):
13
+ config_class = T5GemmaModuleConfig
14
+
15
+
16
+ # register T5GemmaModuleConfig
17
+ AutoConfig.register("t5_gemma_module", T5GemmaModuleConfig)
18
+ AutoModel.register(T5GemmaModuleConfig, T5GemmaEncoderWrapper)
19
+ AutoConfig.register("t5gemma2_text", T5Gemma2TextConfig)
20
+ AutoModel.register(T5Gemma2TextConfig, T5Gemma2TextEncoder)
21
+
22
+
23
+ # Update BreezeConfig to handle text_encoder_config properly
24
+ class BreezeConfig(BreezeConfig_transformers):
25
+ model_type = "breeze"
26
+
27
+ sub_configs = {
28
+ "codec_config": AutoConfig,
29
+ "depth_decoder_config": BreezeDepthDecoderConfig,
30
+ }
31
+
32
+ def __init__(self, **kwargs):
33
+ text_encoder_config = kwargs.get("text_encoder_config", None)
34
+ super().__init__(**kwargs)
35
+
36
+ if text_encoder_config is None:
37
+ self.text_encoder_config = None
38
+ elif isinstance(text_encoder_config, str):
39
+ assert os.path.isdir(text_encoder_config), (
40
+ f"text_encoder_config as str must be a valid directory path: '{text_encoder_config}'"
41
+ )
42
+ self.text_encoder_config = AutoConfig.from_pretrained(text_encoder_config)
43
+ elif isinstance(text_encoder_config, dict):
44
+ self.text_encoder_config = AutoConfig.for_model(**text_encoder_config)
45
+ else:
46
+ raise ValueError(
47
+ f"text_encoder_config must be a str (path), dict, or AutoConfig instance. but got {type(text_encoder_config)}"
48
+ )
49
+
50
+
51
+ AutoConfig.register(
52
+ "breeze_depth_decoder_model", BreezeDepthDecoderConfig, exist_ok=True
53
+ )
54
+ AutoConfig.register("breeze", BreezeConfig, exist_ok=True)
models/cudagraph/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """CUDA Graph + StaticCache accelerated pieces for Breeze TTS."""
2
+
3
+ from .backbone_graph import BackboneGraph
4
+ from .depth_decoder_graph import DepthDecoderGraph
5
+ from .sampling import sample_logits
6
+
7
+ __all__ = ["BackboneGraph", "DepthDecoderGraph", "sample_logits"]
models/cudagraph/backbone_graph.py ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CUDA graph capture for the Breeze backbone's single-token decode step,
3
+ using transformers StaticCache.
4
+
5
+ The backbone (BreezeBackboneModel) predicts the first codebook token at each step.
6
+ For CFG, we run batch=2 (cond + uncond) and apply the guidance formula inside
7
+ the graph so it's captured as a single replay.
8
+
9
+ Strategy:
10
+ - Use transformers StaticCache for KV cache management
11
+ - Use the model's forward method (handles mask, RoPE, attention internally)
12
+ - Capture single-token decode + lm_head + CFG as a CUDA graph
13
+ - Update cache_position buffer between replays
14
+ - Batch size changes trigger automatic recapture via ensure_batch_size()
15
+ """
16
+
17
+ import torch
18
+ from transformers import StaticCache
19
+ from transformers.masking_utils import create_causal_mask
20
+
21
+
22
+ class BackboneGraph:
23
+ """
24
+ Captures the Breeze backbone's single-token decode step as a CUDA graph,
25
+ using the model's own forward with transformers StaticCache.
26
+
27
+ Supports batch=2 for CFG (cond=batch[0], uncond=batch[1]).
28
+
29
+ Batch size changes trigger automatic recapture:
30
+ graph.ensure_batch_size(4)
31
+
32
+ guidance_scale is runtime-mutable without recapture:
33
+ graph.guidance_scale.fill_(5.0)
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ backbone_model,
39
+ lm_head,
40
+ embed_tokens,
41
+ config,
42
+ device="cuda:0",
43
+ dtype=torch.bfloat16,
44
+ max_seq_len=512,
45
+ guidance_scale=3.0,
46
+ debug: bool = False,
47
+ batch_size: int = 1,
48
+ ):
49
+ self.device = device
50
+ self.dtype = dtype
51
+ self.debug = debug
52
+ self.no_graph = (
53
+ False # runtime flag: True = skip graph replay (for layer-diff hooks)
54
+ )
55
+ self.max_seq_len = max_seq_len
56
+ self.hidden_size = config.hidden_size
57
+ self.num_layers = config.num_hidden_layers
58
+ self.vocab_size = getattr(lm_head, "out_features", config.vocab_size)
59
+ self.num_codebooks = config.num_codebooks
60
+ self.config = config
61
+ self.batch_size = batch_size
62
+ self.half = self._real_batch_size(batch_size)
63
+
64
+ # Keep references to model components
65
+ self.model = backbone_model # BreezeBackboneModel
66
+ self.lm_head = lm_head # nn.Linear(hidden_size, vocab_size)
67
+ self.embed_tokens = embed_tokens # BreezeBackboneModelEmbeddings
68
+
69
+ # Cast lm_head to fp32 for numerical stability (hooks still fire through module forward)
70
+ self.lm_head = self.lm_head.float()
71
+
72
+ # Transformers StaticCache — batch=2 for CFG
73
+ self.static_cache = StaticCache(
74
+ config=config, max_cache_len=max_seq_len, batch_size=self.batch_size
75
+ )
76
+
77
+ # Cache position buffer — [1] for KV cache slot (shared across batch)
78
+ self.cache_position = torch.zeros(1, dtype=torch.long, device=device)
79
+ # Per-batch position_ids for RoPE — [batch_size, 1]
80
+ self.position_ids = torch.zeros(
81
+ self.batch_size, 1, dtype=torch.long, device=device
82
+ )
83
+ # Base position for each batch (set by set_generation_state from attention_mask)
84
+ self._base_position = torch.zeros(
85
+ self.batch_size, dtype=torch.long, device=device
86
+ )
87
+
88
+ # Guidance scale buffer — can be updated at runtime via fill_()
89
+ self.guidance_scale = torch.tensor(
90
+ [guidance_scale], dtype=torch.float32, device=device
91
+ )
92
+
93
+ self._alloc_buffers()
94
+
95
+ self.graph = None
96
+ self.captured = False
97
+ self.attn_mask = None
98
+ # Per-batch left-padding lengths for vectorized mask construction
99
+ self._pad_lens = torch.zeros(self.batch_size, dtype=torch.long, device=device)
100
+ # Pre-allocated index range [0, 1, ..., max_seq_len-1] for broadcasting
101
+ self._kv_indices = torch.arange(max_seq_len, dtype=torch.long, device=device)
102
+
103
+ @staticmethod
104
+ def _real_batch_size(batch_size: int) -> int:
105
+ if batch_size <= 1:
106
+ return batch_size
107
+ return batch_size // 2
108
+
109
+ # ------------------------------------------------------------------
110
+ # Buffer allocation (called from __init__ and _rebuild_for_batch)
111
+ # ------------------------------------------------------------------
112
+
113
+ def _alloc_buffers(self):
114
+ """Allocate / re-allocate I/O buffers for current batch_size."""
115
+ # Input: codebook IDs [batch, 1, num_codebooks]
116
+ self.input_ids_buf = torch.zeros(
117
+ self.batch_size, 1, self.num_codebooks, dtype=torch.long, device=self.device
118
+ )
119
+ # Hidden states output [batch, 1, hidden_size]
120
+ self.hidden_buf = torch.zeros(
121
+ self.batch_size, 1, self.hidden_size, dtype=self.dtype, device=self.device
122
+ )
123
+ # Raw logits [batch, vocab_size]
124
+ self.logits_buf = torch.zeros(
125
+ self.batch_size, self.vocab_size, dtype=torch.float32, device=self.device
126
+ )
127
+ # CFG-applied logits [half, vocab_size]
128
+ self.cfg_logits_buf = torch.zeros(
129
+ self.half, self.vocab_size, dtype=torch.float32, device=self.device
130
+ )
131
+
132
+ # ------------------------------------------------------------------
133
+ # Batch size management — auto-recapture on change
134
+ # ------------------------------------------------------------------
135
+
136
+ @torch.inference_mode()
137
+ def ensure_batch_size(self, batch_size: int):
138
+ """No-op if batch_size matches current captured graph; else rebuild and recapture."""
139
+ if batch_size == self.batch_size:
140
+ return
141
+ self._rebuild_for_batch(batch_size)
142
+
143
+ @torch.inference_mode()
144
+ def _rebuild_for_batch(self, batch_size: int):
145
+ self.batch_size = batch_size
146
+ self.half = self._real_batch_size(batch_size)
147
+ self.static_cache = StaticCache(
148
+ config=self.config, max_cache_len=self.max_seq_len, batch_size=batch_size
149
+ )
150
+ # Re-allocate buffers for new batch_size
151
+ self.cache_position = torch.zeros(1, dtype=torch.long, device=self.device)
152
+ self.position_ids = torch.zeros(
153
+ batch_size, 1, dtype=torch.long, device=self.device
154
+ )
155
+ self._base_position = torch.zeros(
156
+ batch_size, dtype=torch.long, device=self.device
157
+ )
158
+ self._alloc_buffers()
159
+ self.captured = False
160
+ self.graph = None
161
+ self.attn_mask = None
162
+ self._pad_lens = torch.zeros(batch_size, dtype=torch.long, device=self.device)
163
+ self._kv_indices = torch.arange(
164
+ self.max_seq_len, dtype=torch.long, device=self.device
165
+ )
166
+ if self.no_graph:
167
+ self.prepare_eager()
168
+ else:
169
+ self.capture()
170
+
171
+ @torch.inference_mode()
172
+ def prepare_eager(self):
173
+ """Initialize fixed eager buffers without compile or CUDA Graph capture."""
174
+ self.no_graph = True
175
+ self._init_cache_layers()
176
+ self._build_initial_attention_mask()
177
+ self.captured = False
178
+ return self
179
+
180
+ def _init_cache_layers(self):
181
+ """Force lazy initialization of StaticCache layers before graph capture."""
182
+ config = self.model.config
183
+ num_kv_heads = getattr(
184
+ config, "num_key_value_heads", config.num_attention_heads
185
+ )
186
+ head_dim = getattr(
187
+ config, "head_dim", config.hidden_size // config.num_attention_heads
188
+ )
189
+ dummy_k = torch.zeros(
190
+ self.batch_size,
191
+ num_kv_heads,
192
+ 1,
193
+ head_dim,
194
+ dtype=self.dtype,
195
+ device=self.device,
196
+ )
197
+ for layer in self.static_cache.layers:
198
+ if not layer.is_initialized:
199
+ layer.lazy_initialization(dummy_k)
200
+
201
+ def _build_initial_attention_mask(self):
202
+ """Build a default causal mask (no padding) for graph capture/warmup.
203
+
204
+ Uses create_causal_mask once at position 0 to determine the correct
205
+ shape and dtype, then allocates self.attn_mask as a static buffer.
206
+ """
207
+ dummy = torch.zeros(
208
+ self.batch_size, 1, self.hidden_size, dtype=self.dtype, device=self.device
209
+ )
210
+ pos = torch.tensor([0], device=self.device)
211
+ mask = create_causal_mask(
212
+ config=self.model.config,
213
+ input_embeds=dummy,
214
+ attention_mask=None,
215
+ cache_position=pos,
216
+ past_key_values=self.static_cache,
217
+ )
218
+ # mask shape: [batch, 1, 1, max_seq_len], dtype may be float or bool
219
+ if mask is None or mask.dtype == torch.bool:
220
+ # Some attn implementations return bool or None; allocate float mask
221
+ self.attn_mask = torch.zeros(
222
+ self.batch_size,
223
+ 1,
224
+ 1,
225
+ self.max_seq_len,
226
+ dtype=self.dtype,
227
+ device=self.device,
228
+ )
229
+ elif self.attn_mask is None:
230
+ self.attn_mask = mask.clone()
231
+ else:
232
+ self.attn_mask.copy_(mask)
233
+ print(
234
+ f"[BackboneGraph] Initial attention mask shape: {self.attn_mask.shape}, dtype: {self.attn_mask.dtype}"
235
+ )
236
+
237
+ self._mask_min_val = torch.finfo(self.attn_mask.dtype).min
238
+
239
+ def _set_attention_mask(self, position: int):
240
+ """Construct attention mask in-place from pad_lens and cache_position.
241
+
242
+ For each batch i, the valid KV range is [pad_lens[i], position].
243
+ Everything outside is masked with -inf. Fully vectorized, no loops.
244
+
245
+ attn_mask shape: [batch, 1, 1, max_seq_len]
246
+ """
247
+ # kv_indices: [max_seq_len], pad_lens: [batch, 1], position: scalar
248
+ kv = self._kv_indices # [S]
249
+ lo = self._pad_lens.unsqueeze(1) # [B, 1]
250
+ valid = (kv >= lo) & (kv <= position) # [B, S]
251
+ self.attn_mask.fill_(self._mask_min_val)
252
+ self.attn_mask[:, 0, 0, :].masked_fill_(valid, 0.0)
253
+
254
+ def _decode_step(self):
255
+ """Single-token decode: embed → backbone forward → lm_head → CFG."""
256
+ # 1. Embed codebook tokens (sum of num_codebooks embeddings)
257
+ inputs_embeds = self.embed_tokens(self.input_ids_buf) # [batch, 1, H]
258
+
259
+ # 2. Backbone forward with per-batch position_ids and shared cache_position
260
+ # position_ids: [batch_size, 1] — per-batch logical position for RoPE
261
+ # cache_position: [1] — shared KV cache slot for all batches
262
+ out = self.model(
263
+ inputs_embeds=inputs_embeds,
264
+ attention_mask=self.attn_mask,
265
+ past_key_values=self.static_cache,
266
+ position_ids=self.position_ids,
267
+ cache_position=self.cache_position,
268
+ use_cache=True,
269
+ )
270
+ self.hidden_buf.copy_(out.last_hidden_state)
271
+
272
+ # 3. lm_head → logits (lm_head is fp32, input cast to fp32)
273
+ logits = self.lm_head(self.hidden_buf[:, -1, :].float()) # [batch, vocab] fp32
274
+ self.logits_buf.copy_(logits)
275
+
276
+ # 4. CFG when paired cond/uncond rows are present; otherwise pass through.
277
+ if self.batch_size >= 2:
278
+ cond_logits = self.logits_buf[: self.half]
279
+ uncond_logits = self.logits_buf[self.half :]
280
+ cfg_result = uncond_logits + self.guidance_scale * (
281
+ cond_logits - uncond_logits
282
+ )
283
+ else:
284
+ cfg_result = self.logits_buf[: self.half]
285
+ self.cfg_logits_buf.copy_(cfg_result)
286
+
287
+ @torch.inference_mode()
288
+ def capture(self, prefill_len=100, num_warmup=3):
289
+ """Capture CUDA graph for single-token decode."""
290
+ print(f"Warming up backbone graph ({num_warmup} runs)...")
291
+
292
+ self._init_cache_layers()
293
+ self._build_initial_attention_mask()
294
+
295
+ # Initialize positions for warmup
296
+ self.cache_position[0] = prefill_len
297
+ self.position_ids.fill_(prefill_len)
298
+ self._pad_lens.zero_()
299
+ self._set_attention_mask(prefill_len)
300
+
301
+ for _ in range(num_warmup):
302
+ self._decode_step()
303
+ torch.cuda.synchronize(device=self.device)
304
+
305
+ print("Capturing CUDA graph for backbone decode...")
306
+ self.graph = torch.cuda.CUDAGraph()
307
+
308
+ s = torch.cuda.Stream(device=self.device)
309
+ s.wait_stream(torch.cuda.current_stream())
310
+ with torch.cuda.stream(s):
311
+ self._decode_step()
312
+ torch.cuda.synchronize(device=self.device)
313
+
314
+ with torch.cuda.graph(self.graph):
315
+ self._decode_step()
316
+
317
+ torch.cuda.current_stream().wait_stream(s)
318
+ torch.cuda.synchronize(device=self.device)
319
+ self.captured = True
320
+ print("Backbone CUDA graph captured!")
321
+
322
+ @torch.inference_mode()
323
+ def reset(self):
324
+ """Reset cache for new sequence."""
325
+ self.static_cache.reset()
326
+
327
+ @torch.inference_mode()
328
+ def finish_direct_prefill(self, seq_len: int) -> int:
329
+ self._prefill_len = int(seq_len)
330
+ return self._prefill_len
331
+
332
+ @torch.inference_mode()
333
+ def prefill_kv(self, past_key_values):
334
+ """
335
+ Copy HF DynamicCache from prefill into our StaticCache.
336
+ past_key_values: DynamicCache with num_layers layers of [batch, kv_heads, seq_len, head_dim]
337
+
338
+ For CFG: the DynamicCache should already have batch=2.
339
+ """
340
+ self.static_cache.reset()
341
+ seq_len = 0
342
+ for li in range(self.num_layers):
343
+ k, v = past_key_values[li]
344
+ seq_len = k.shape[2]
345
+ if seq_len > self.max_seq_len:
346
+ raise RuntimeError(
347
+ f"Input too long: prefill has {seq_len} tokens but max_seq_len={self.max_seq_len}."
348
+ )
349
+ cache_pos = torch.arange(seq_len, device=self.device)
350
+ self.static_cache.update(k, v, li, {"cache_position": cache_pos})
351
+ self._prefill_len = seq_len # remember for decode cache_position
352
+ return seq_len
353
+
354
+ @torch.inference_mode()
355
+ def set_generation_state(self, attention_mask=None):
356
+ """Initialize per-batch position_ids and pad_lens from attention_mask.
357
+
358
+ For left-padded inputs, pad_lens[i] = number of leading zeros in
359
+ attention_mask[i]. The mask is then constructed on-the-fly in
360
+ _set_attention_mask() using pad_lens — no 512-iter table rebuild.
361
+ """
362
+ if attention_mask is not None:
363
+ per_batch_pos = attention_mask.sum(dim=1).long() # [batch_size]
364
+ self._base_position.copy_(per_batch_pos)
365
+ self.position_ids.copy_(per_batch_pos.unsqueeze(-1))
366
+ self.cache_position[0] = per_batch_pos.max().item()
367
+
368
+ # Compute per-batch left-padding length:
369
+ # pad_lens[i] = seq_len - num_valid_tokens[i]
370
+ seq_len = attention_mask.shape[1]
371
+ self._pad_lens.copy_(seq_len - per_batch_pos)
372
+ else:
373
+ self._pad_lens.zero_()
374
+
375
+ @torch.inference_mode()
376
+ def run(self, input_ids, step_idx):
377
+ """
378
+ Run one decode step.
379
+ input_ids: [batch, 1, num_codebooks] long tensor
380
+ step_idx: decode step index (0-based), added to per-batch base position
381
+ Returns: (hidden_states [batch, 1, H], cfg_logits [half, vocab])
382
+ """
383
+ self.input_ids_buf.copy_(input_ids)
384
+ # Update per-batch position_ids: base_position + step_idx
385
+ self.position_ids.copy_((self._base_position + step_idx).unsqueeze(-1))
386
+ # KV cache slot: prefill_len + step_idx (append after prefill KV, never overwrite)
387
+ self.cache_position[0] = self._prefill_len + step_idx
388
+ self._set_attention_mask(self.cache_position[0].item())
389
+ if self.no_graph:
390
+ self._decode_step()
391
+ else:
392
+ self.graph.replay()
393
+ if self.debug:
394
+ # Fake forward: trigger lm_head hooks using buffer data from replay.
395
+ # Result is discarded — only hook side-effects matter.
396
+ self.lm_head(self.hidden_buf[:, -1, :].float())
397
+ return self.hidden_buf, self.cfg_logits_buf
models/cudagraph/backbone_prefill_graph.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Static-shape CUDA Graph buckets for batch-1 backbone prefill."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ from dataclasses import dataclass
7
+
8
+ import torch
9
+
10
+
11
+ @dataclass
12
+ class BackbonePrefillOutput:
13
+ hidden_states: torch.Tensor
14
+ logits: torch.Tensor
15
+ attention_mask: torch.Tensor
16
+ prefill_len: int
17
+
18
+
19
+ @dataclass
20
+ class _PrefillRecord:
21
+ graph: torch.cuda.CUDAGraph
22
+ stream: torch.cuda.Stream
23
+ inputs_embeds: torch.Tensor
24
+ attention_mask: torch.Tensor
25
+ position_ids: torch.Tensor
26
+ causal_mask: torch.Tensor
27
+ cache_position: torch.Tensor
28
+ hidden_states: torch.Tensor
29
+ logits: torch.Tensor
30
+
31
+
32
+ class BackbonePrefillGraphCache:
33
+ """Capture backbone prefill + lm_head while writing the decode StaticCache."""
34
+
35
+ def __init__(self, backbone_graph, *, token_granularity: int = 32) -> None:
36
+ if token_granularity <= 0:
37
+ raise ValueError("token_granularity must be > 0")
38
+ self.backbone_graph = backbone_graph
39
+ self.model = backbone_graph.model
40
+ self.lm_head = backbone_graph.lm_head
41
+ self.device = torch.device(backbone_graph.device)
42
+ self.dtype = backbone_graph.dtype
43
+ self.token_granularity = int(token_granularity)
44
+ self._records: dict[tuple[int, int], _PrefillRecord] = {}
45
+ self._graph_pool = torch.cuda.graph_pool_handle()
46
+ self._lock = threading.RLock()
47
+ self.captures = 0
48
+ self.replays = 0
49
+ self._frozen = False
50
+
51
+ def _bucket(self, length: int) -> int:
52
+ return (
53
+ (int(length) + self.token_granularity - 1) // self.token_granularity
54
+ ) * self.token_granularity
55
+
56
+ @staticmethod
57
+ def _copy_inputs(
58
+ inputs_embeds: torch.Tensor,
59
+ attention_mask: torch.Tensor,
60
+ static_embeds: torch.Tensor,
61
+ static_mask: torch.Tensor,
62
+ static_positions: torch.Tensor,
63
+ ) -> None:
64
+ seq_len = int(inputs_embeds.shape[1])
65
+ pad_len = int(static_embeds.shape[1]) - seq_len
66
+ static_embeds.zero_()
67
+ static_mask.zero_()
68
+ static_positions.fill_(1)
69
+ static_embeds[:, pad_len:].copy_(inputs_embeds)
70
+ static_mask[:, pad_len:].copy_(attention_mask)
71
+ positions = static_mask.long().cumsum(-1) - 1
72
+ positions.masked_fill_(static_mask == 0, 1)
73
+ static_positions.copy_(positions)
74
+
75
+ @staticmethod
76
+ def _update_causal_mask(
77
+ attention_mask: torch.Tensor, causal_mask: torch.Tensor
78
+ ) -> None:
79
+ batch_size, query_len = attention_mask.shape
80
+ key_len = causal_mask.shape[-1]
81
+ query_idx = torch.arange(query_len, device=attention_mask.device).view(
82
+ 1, query_len, 1
83
+ )
84
+ key_idx = torch.arange(key_len, device=attention_mask.device).view(
85
+ 1, 1, key_len
86
+ )
87
+ valid_keys = torch.zeros(
88
+ (batch_size, key_len), dtype=torch.bool, device=attention_mask.device
89
+ )
90
+ valid_keys[:, :query_len].copy_(attention_mask.to(torch.bool))
91
+ allowed = (key_idx <= query_idx) & valid_keys[:, None, :]
92
+ causal_mask.fill_(torch.finfo(causal_mask.dtype).min)
93
+ causal_mask[:, 0].masked_fill_(allowed, 0.0)
94
+
95
+ def _forward(
96
+ self,
97
+ inputs_embeds: torch.Tensor,
98
+ attention_mask: torch.Tensor,
99
+ position_ids: torch.Tensor,
100
+ cache_position: torch.Tensor,
101
+ ) -> tuple[torch.Tensor, torch.Tensor]:
102
+ output = self.model(
103
+ inputs_embeds=inputs_embeds,
104
+ attention_mask=attention_mask,
105
+ position_ids=position_ids,
106
+ past_key_values=self.backbone_graph.static_cache,
107
+ cache_position=cache_position,
108
+ use_cache=True,
109
+ )
110
+ hidden_states = output.last_hidden_state
111
+ logits = self.lm_head(hidden_states[:, -1, :].float()).float()
112
+ return hidden_states, logits
113
+
114
+ @torch.inference_mode()
115
+ def __call__(
116
+ self,
117
+ inputs_embeds: torch.Tensor,
118
+ attention_mask: torch.Tensor,
119
+ ) -> BackbonePrefillOutput:
120
+ batch_size, seq_len, hidden_size = inputs_embeds.shape
121
+ if batch_size != self.backbone_graph.batch_size:
122
+ raise ValueError(
123
+ f"prefill batch={batch_size} does not match decode graph "
124
+ f"batch={self.backbone_graph.batch_size}"
125
+ )
126
+ bucket_len = self._bucket(int(seq_len))
127
+ if bucket_len > self.backbone_graph.max_seq_len:
128
+ raise RuntimeError(
129
+ f"prefill bucket {bucket_len} exceeds max_seq_len "
130
+ f"{self.backbone_graph.max_seq_len}"
131
+ )
132
+ key = (int(batch_size), bucket_len)
133
+
134
+ with self._lock:
135
+ record = self._records.get(key)
136
+ if record is None:
137
+ if self._frozen:
138
+ raise RuntimeError(
139
+ f"backbone prefill CUDA graph {key} was not declared in the warmup profile"
140
+ )
141
+ static_embeds = torch.zeros(
142
+ (batch_size, bucket_len, hidden_size),
143
+ dtype=inputs_embeds.dtype,
144
+ device=inputs_embeds.device,
145
+ )
146
+ static_mask = torch.zeros(
147
+ (batch_size, bucket_len),
148
+ dtype=attention_mask.dtype,
149
+ device=attention_mask.device,
150
+ )
151
+ static_positions = torch.ones_like(static_mask, dtype=torch.long)
152
+ static_causal_mask = torch.empty(
153
+ (batch_size, 1, bucket_len, self.backbone_graph.max_seq_len),
154
+ dtype=inputs_embeds.dtype,
155
+ device=inputs_embeds.device,
156
+ )
157
+ cache_position = torch.arange(
158
+ bucket_len, device=inputs_embeds.device, dtype=torch.long
159
+ )
160
+ self._copy_inputs(
161
+ inputs_embeds,
162
+ attention_mask,
163
+ static_embeds,
164
+ static_mask,
165
+ static_positions,
166
+ )
167
+ self._update_causal_mask(static_mask, static_causal_mask)
168
+
169
+ capture_stream = torch.cuda.Stream(device=inputs_embeds.device)
170
+ capture_stream.wait_stream(
171
+ torch.cuda.current_stream(inputs_embeds.device)
172
+ )
173
+ with torch.cuda.stream(capture_stream):
174
+ for _ in range(3):
175
+ hidden_states, logits = self._forward(
176
+ static_embeds,
177
+ static_causal_mask,
178
+ static_positions,
179
+ cache_position,
180
+ )
181
+ capture_stream.synchronize()
182
+
183
+ graph = torch.cuda.CUDAGraph()
184
+ with torch.cuda.graph(
185
+ graph,
186
+ stream=capture_stream,
187
+ pool=self._graph_pool,
188
+ capture_error_mode="thread_local",
189
+ ):
190
+ hidden_states, logits = self._forward(
191
+ static_embeds,
192
+ static_causal_mask,
193
+ static_positions,
194
+ cache_position,
195
+ )
196
+ record = _PrefillRecord(
197
+ graph=graph,
198
+ stream=capture_stream,
199
+ inputs_embeds=static_embeds,
200
+ attention_mask=static_mask,
201
+ position_ids=static_positions,
202
+ causal_mask=static_causal_mask,
203
+ cache_position=cache_position,
204
+ hidden_states=hidden_states,
205
+ logits=logits,
206
+ )
207
+ self._records[key] = record
208
+ self.captures += 1
209
+
210
+ self._copy_inputs(
211
+ inputs_embeds,
212
+ attention_mask,
213
+ record.inputs_embeds,
214
+ record.attention_mask,
215
+ record.position_ids,
216
+ )
217
+ self._update_causal_mask(record.attention_mask, record.causal_mask)
218
+ current_stream = torch.cuda.current_stream(inputs_embeds.device)
219
+ record.stream.wait_stream(current_stream)
220
+ record.graph.replay()
221
+ current_stream.wait_stream(record.stream)
222
+ self.backbone_graph.finish_direct_prefill(bucket_len)
223
+ self.replays += 1
224
+ return BackbonePrefillOutput(
225
+ hidden_states=record.hidden_states,
226
+ logits=record.logits,
227
+ attention_mask=record.attention_mask,
228
+ prefill_len=bucket_len,
229
+ )
230
+
231
+ @property
232
+ def graph_keys(self) -> tuple[tuple[int, int], ...]:
233
+ return tuple(sorted(self._records))
234
+
235
+ @torch.inference_mode()
236
+ def warmup_graph(self, *, branch_batch_size: int, sequence_length: int) -> None:
237
+ if self._frozen:
238
+ raise RuntimeError("backbone prefill CUDA graph cache is already frozen")
239
+ inputs_embeds = torch.zeros(
240
+ branch_batch_size,
241
+ sequence_length,
242
+ int(self.backbone_graph.hidden_size),
243
+ dtype=self.dtype,
244
+ device=self.device,
245
+ )
246
+ attention_mask = torch.ones(
247
+ branch_batch_size,
248
+ sequence_length,
249
+ dtype=torch.long,
250
+ device=self.device,
251
+ )
252
+ self(inputs_embeds, attention_mask)
253
+
254
+ def freeze(self) -> None:
255
+ self._frozen = True
models/cudagraph/depth_decoder_graph.py ADDED
@@ -0,0 +1,987 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CUDA graph capture for the Breeze depth decoder's decode loop,
3
+ using transformers StaticCache.
4
+
5
+ The depth decoder generates num_codebooks-1 codebook tokens autoregressively
6
+ (typically 15 for production models with 16 RVQ codebooks):
7
+ - Step 0: prefill with 2 tokens (backbone_hidden placeholder + first_cb_token),
8
+ get logits for codebook[1]
9
+ - Steps 1..num_codebooks-2: decode 1 token at a time using previous codebook token's embedding
10
+
11
+ Strategy:
12
+ - Use transformers StaticCache for KV cache management
13
+ - Use the depth decoder's inner model forward
14
+ - Unroll the full loop for deterministic shapes
15
+ - Capture the entire loop as a single CUDA graph
16
+ - CFG is baked in: batch=2 (cond + uncond), guidance applied per step
17
+ - All tensors pre-allocated — no torch.tensor() calls inside the graph
18
+ - Sampling params (temperature, top_k, top_p, do_sample, guidance_scale) are
19
+ per-sample tensor buffers [half, 1] or [half]: update via set_*() methods
20
+ at runtime without recapture. Accepts both scalar (broadcast) and tensor
21
+ (per-sample) values.
22
+ - top_k uses a fixed MAX_K workspace; top_k_buf can be set to any value <= _max_k.
23
+ - Multi-batch bucket support: pre-capture graphs for multiple batch sizes
24
+ (powers of 2), select the smallest fitting bucket at runtime. No recapture
25
+ needed when batch size changes within the pre-captured range.
26
+ """
27
+
28
+ import logging
29
+ from dataclasses import dataclass
30
+
31
+ import torch
32
+ import torch._dynamo
33
+ import torch.nn.functional as F
34
+ from transformers import StaticCache
35
+ from transformers.masking_utils import create_causal_mask
36
+
37
+ from ..logits_process import mask_invalid_codec_token_logits
38
+
39
+ _log = logging.getLogger(__name__)
40
+
41
+
42
+ @dataclass
43
+ class _BucketState:
44
+ """Stores all per-bucket-size state needed for CUDA graph replay."""
45
+
46
+ batch_size: int
47
+ half: int
48
+ graph: torch.cuda.CUDAGraph
49
+ static_cache: StaticCache
50
+ # I/O buffers
51
+ backbone_hidden_buf: torch.Tensor
52
+ first_cb_token_buf: torch.Tensor
53
+ output_tokens: torch.Tensor
54
+ _tok_buf: torch.Tensor
55
+ prefill_input_ids: torch.Tensor
56
+ # Attention masks
57
+ prefill_attn: torch.Tensor
58
+ decode_attn: list
59
+ # Sampling param buffers
60
+ temperature_buf: torch.Tensor
61
+ top_k_buf: torch.Tensor
62
+ top_p_buf: torch.Tensor
63
+ do_sample_buf: torch.Tensor
64
+ guidance_scale: torch.Tensor
65
+ # Debug buffers (optional)
66
+ debug_logits: torch.Tensor | None = None
67
+ _debug_head_input: torch.Tensor | None = None
68
+ _debug_probs: torch.Tensor | None = None
69
+ _debug_probs_slot: torch.Tensor | None = None
70
+
71
+
72
+ class DepthDecoderGraph:
73
+ """
74
+ Captures the full depth decoder loop as a CUDA graph,
75
+ using the model's forward with transformers StaticCache.
76
+
77
+ For CFG: batch=2 (cond=batch[0], uncond=batch[1]).
78
+
79
+ Sampling params are per-sample tensor buffers — no recapture needed:
80
+ # Scalar (broadcast to all samples):
81
+ graph.set_temperature(0.5)
82
+ graph.set_guidance_scale(3.0)
83
+
84
+ # Per-sample (tensor):
85
+ graph.set_temperature(torch.tensor([0.5, 1.2]))
86
+ graph.set_guidance_scale(torch.tensor([3.0, 1.0]))
87
+
88
+ # Or pass directly in run():
89
+ graph.run(backbone_h, first_cb, temperature=torch.tensor([0.5, 1.2]))
90
+
91
+ Batch size changes trigger automatic recapture:
92
+ graph.ensure_batch_size(4)
93
+ """
94
+
95
+ def __init__(
96
+ self,
97
+ depth_decoder,
98
+ config,
99
+ device="cuda:0",
100
+ dtype=torch.bfloat16,
101
+ do_sample=True,
102
+ top_k=50,
103
+ top_p=1.0,
104
+ temperature=0.9,
105
+ guidance_scale=3.0,
106
+ num_codebooks=None,
107
+ codec_codebook_size=None,
108
+ fast: bool = False,
109
+ debug: bool = False,
110
+ batch_size=2,
111
+ bucket_sizes: list[int] | None = None,
112
+ ):
113
+ self.device = device
114
+ self.dtype = dtype
115
+ self.config = config
116
+ self.debug = debug
117
+ self.no_graph = (
118
+ False # runtime flag: True = skip graph replay (for layer-diff hooks)
119
+ )
120
+ self.num_layers = config.num_hidden_layers
121
+ self.hidden_size = config.hidden_size
122
+ self.backbone_hidden_size = config.backbone_hidden_size
123
+ self.vocab_size = config.vocab_size
124
+ self.codec_codebook_size = (
125
+ int(codec_codebook_size)
126
+ if codec_codebook_size is not None
127
+ else self.vocab_size
128
+ )
129
+ if not 0 < self.codec_codebook_size <= self.vocab_size:
130
+ raise ValueError(
131
+ f"codec_codebook_size must be in [1, {self.vocab_size}], "
132
+ f"got {self.codec_codebook_size}"
133
+ )
134
+ self.num_codebooks = (
135
+ num_codebooks if num_codebooks is not None else config.num_codebooks
136
+ )
137
+ self.num_decode_codebooks = self.num_codebooks - 1
138
+ self.max_seq = 2 + self.num_decode_codebooks
139
+ self.batch_size = batch_size
140
+ self.half = self._real_batch_size(self.batch_size)
141
+
142
+ # Multi-batch bucket support
143
+ self.bucket_sizes = sorted(bucket_sizes) if bucket_sizes else [batch_size]
144
+ self._bucket_graphs: dict[int, _BucketState] = {}
145
+
146
+ # Extract model components (references, not copies)
147
+ self.depth_model = depth_decoder.model # BreezeDepthDecoderModel
148
+ self.codebooks_head = depth_decoder.codebooks_head # BreezeCodebooksHead
149
+ self._orig_codebooks_head = (
150
+ depth_decoder.codebooks_head
151
+ ) # unwrapped ref for fake forward hooks
152
+ self.embed_tokens = self.depth_model.embed_tokens
153
+ self.inputs_embeds_projector = self.depth_model.inputs_embeds_projector
154
+ self.backbone_hidden_state_projector = getattr(
155
+ self.depth_model, "backbone_hidden_state_projector", None
156
+ )
157
+ self.audio_embed_size = self.depth_model.audio_embed_size
158
+
159
+ # Cast codebooks_head weight to fp32 for numerical stability
160
+ # (module forward still fires hooks, and F.linear works with fp32 input)
161
+ self.codebooks_head.weight.data = self.codebooks_head.weight.data.float()
162
+ self.fast = bool(fast)
163
+
164
+ # The fast path uses one maintained compile configuration before
165
+ # manual CUDA Graph capture. Compile modes are intentionally not public.
166
+ if self.fast:
167
+ compile_mode = "default"
168
+ _limit = self.num_layers * 4 + 16
169
+ torch._dynamo.config.cache_size_limit = max(
170
+ torch._dynamo.config.cache_size_limit, _limit
171
+ )
172
+ torch._dynamo.config.recompile_limit = max(
173
+ torch._dynamo.config.recompile_limit, _limit
174
+ )
175
+ already_compiled = hasattr(self.depth_model.layers[0], "_orig_mod")
176
+ if already_compiled:
177
+ _log.info("Depth decoder layers already compiled, skipping.")
178
+ else:
179
+ _log.info("Compiling depth decoder with the fast configuration.")
180
+ for index, layer in enumerate(self.depth_model.layers):
181
+ self.depth_model.layers[index] = torch.compile(
182
+ layer, mode=compile_mode, fullgraph=True
183
+ )
184
+ self.depth_model.norm = torch.compile(
185
+ self.depth_model.norm, mode=compile_mode, fullgraph=True
186
+ )
187
+ if not hasattr(self.codebooks_head, "_orig_mod"):
188
+ self.codebooks_head = torch.compile(
189
+ self.codebooks_head, mode=compile_mode, fullgraph=True
190
+ )
191
+
192
+ # Sampling param defaults — saved for _alloc_buffers / _rebuild_for_batch
193
+ self._default_temperature = temperature
194
+ self._default_top_k = top_k
195
+ self._default_top_p = top_p
196
+ self._default_do_sample = do_sample
197
+ self._default_guidance_scale = guidance_scale
198
+
199
+ # Fixed MAX_K workspace for graph-safe top_k.
200
+ # top_k_buf can be set to any value in [1, _max_k] at runtime without recapture.
201
+ # Default 1024 so users can freely adjust top_k up to 1024.
202
+ self._max_k = min(1024, self.vocab_size)
203
+ self._topk_ranks = torch.arange(
204
+ self._max_k, device=device
205
+ ) # pre-alloc, never recreated
206
+ # top_p: always keep the highest-prob token (graph-safe alternative to remove[0]=False)
207
+ self._keep_first = torch.zeros(self.vocab_size, dtype=torch.bool, device=device)
208
+ self._keep_first[0] = True
209
+
210
+ # Pre-allocate ALL cache_position tensors (no torch.tensor() inside graph)
211
+ self.prefill_cache_pos = torch.arange(2, device=device)
212
+ self.decode_cache_positions = [
213
+ torch.tensor([2 + i], device=device)
214
+ for i in range(self.num_decode_codebooks - 1)
215
+ ]
216
+ self.head_prefill_pos = torch.tensor([1], device=device)
217
+ self.codebook_offsets = [
218
+ torch.tensor([i * self.vocab_size], dtype=torch.long, device=device)
219
+ for i in range(self.num_decode_codebooks)
220
+ ]
221
+
222
+ # Transformers StaticCache
223
+ self.static_cache = StaticCache(
224
+ config=config, max_cache_len=self.max_seq, batch_size=self.batch_size
225
+ )
226
+
227
+ self._alloc_buffers()
228
+
229
+ self.graph = None
230
+ self.captured = False
231
+ self.prefill_attn = None
232
+ self.decode_attn = None
233
+
234
+ @staticmethod
235
+ def _real_batch_size(batch_size: int) -> int:
236
+ if batch_size <= 1:
237
+ return batch_size
238
+ return batch_size // 2
239
+
240
+ # ------------------------------------------------------------------
241
+ # Runtime setters — no recapture needed
242
+ # ------------------------------------------------------------------
243
+
244
+ def set_temperature(self, v):
245
+ """v: scalar float (broadcast) or [half] / [half,1] tensor (per-sample)."""
246
+ if isinstance(v, (int, float)):
247
+ self.temperature_buf.fill_(v)
248
+ else:
249
+ self.temperature_buf.copy_(v.view(self.half, 1))
250
+
251
+ def set_top_k(self, v):
252
+ """v: scalar int (broadcast) or [half] / [half,1] tensor (per-sample)."""
253
+ if isinstance(v, (int, float)):
254
+ assert v <= self._max_k, (
255
+ f"top_k={v} exceeds _max_k={self._max_k}; rebuild graph to increase MAX_K"
256
+ )
257
+ self.top_k_buf.fill_(v)
258
+ else:
259
+ assert v.max().item() <= self._max_k, (
260
+ f"top_k max={v.max().item()} exceeds _max_k={self._max_k}"
261
+ )
262
+ self.top_k_buf.copy_(v.view(self.half, 1))
263
+
264
+ def set_top_p(self, v):
265
+ """v: scalar float (broadcast) or [half] / [half,1] tensor (per-sample)."""
266
+ if isinstance(v, (int, float)):
267
+ self.top_p_buf.fill_(v)
268
+ else:
269
+ self.top_p_buf.copy_(v.view(self.half, 1))
270
+
271
+ def set_do_sample(self, v):
272
+ """v: scalar bool (broadcast) or [half] long tensor (per-sample, 0/1)."""
273
+ if isinstance(v, (bool, int, float)):
274
+ self.do_sample_buf.fill_(1 if v else 0)
275
+ else:
276
+ self.do_sample_buf.copy_(v.view(self.half))
277
+
278
+ def set_guidance_scale(self, v):
279
+ """v: scalar float (broadcast) or [half] / [half,1] tensor (per-sample)."""
280
+ if isinstance(v, (int, float)):
281
+ self.guidance_scale.fill_(v)
282
+ else:
283
+ self.guidance_scale.copy_(v.view(self.half, 1))
284
+
285
+ # ------------------------------------------------------------------
286
+ # Buffer allocation (called from __init__ and _rebuild_for_batch)
287
+ # ------------------------------------------------------------------
288
+
289
+ def _alloc_buffers(self):
290
+ """Allocate / re-allocate I/O buffers for current batch_size.
291
+
292
+ Also (re-)creates per-sample sampling param buffers whose first
293
+ dimension is ``self.half``. On first call the ``_default_*``
294
+ values are used; on subsequent calls (batch-size change) the
295
+ current first-sample value is preserved as the uniform default.
296
+ """
297
+ self.backbone_hidden_buf = torch.zeros(
298
+ self.batch_size,
299
+ self.backbone_hidden_size,
300
+ dtype=self.dtype,
301
+ device=self.device,
302
+ )
303
+ self.first_cb_token_buf = torch.zeros(
304
+ self.batch_size, dtype=torch.long, device=self.device
305
+ )
306
+ self.output_tokens = torch.zeros(
307
+ self.batch_size,
308
+ self.num_decode_codebooks,
309
+ dtype=torch.long,
310
+ device=self.device,
311
+ )
312
+ self._tok_buf = torch.zeros(
313
+ self.batch_size, dtype=torch.long, device=self.device
314
+ )
315
+ self.prefill_input_ids = torch.zeros(
316
+ self.batch_size, 2, dtype=torch.long, device=self.device
317
+ )
318
+
319
+ # --- Per-sample sampling param buffers [half, 1] or [half] ---
320
+ # Preserve current value when resizing, fall back to defaults on first init.
321
+ _temp = (
322
+ float(self.temperature_buf[0, 0])
323
+ if hasattr(self, "temperature_buf") and self.temperature_buf.numel()
324
+ else self._default_temperature
325
+ )
326
+ _top_k = (
327
+ int(self.top_k_buf[0, 0])
328
+ if hasattr(self, "top_k_buf") and self.top_k_buf.numel()
329
+ else self._default_top_k
330
+ )
331
+ _top_p = (
332
+ float(self.top_p_buf[0, 0])
333
+ if hasattr(self, "top_p_buf") and self.top_p_buf.numel()
334
+ else self._default_top_p
335
+ )
336
+ _do_sample = (
337
+ int(self.do_sample_buf[0])
338
+ if hasattr(self, "do_sample_buf") and self.do_sample_buf.numel()
339
+ else (1 if self._default_do_sample else 0)
340
+ )
341
+ _gs = (
342
+ float(self.guidance_scale[0, 0])
343
+ if hasattr(self, "guidance_scale") and self.guidance_scale.numel()
344
+ else self._default_guidance_scale
345
+ )
346
+
347
+ self.temperature_buf = torch.full(
348
+ (self.half, 1), _temp, dtype=torch.float32, device=self.device
349
+ )
350
+ self.top_k_buf = torch.full(
351
+ (self.half, 1), _top_k, dtype=torch.long, device=self.device
352
+ )
353
+ self.top_p_buf = torch.full(
354
+ (self.half, 1), _top_p, dtype=torch.float32, device=self.device
355
+ )
356
+ self.do_sample_buf = torch.full(
357
+ (self.half,), _do_sample, dtype=torch.long, device=self.device
358
+ )
359
+ self.guidance_scale = torch.full(
360
+ (self.half, 1), _gs, dtype=torch.float32, device=self.device
361
+ )
362
+
363
+ # Debug: per-codebook raw logits [num_decode_codebooks, batch_size, vocab_size]
364
+ if self.debug:
365
+ self.debug_logits = torch.zeros(
366
+ self.num_decode_codebooks,
367
+ self.batch_size,
368
+ self.vocab_size,
369
+ dtype=torch.float32,
370
+ device=self.device,
371
+ )
372
+ # Debug: hidden states before each codebooks_head call (for fake forward hooks)
373
+ self._debug_head_input = torch.zeros(
374
+ self.num_decode_codebooks,
375
+ self.batch_size,
376
+ 1,
377
+ self.hidden_size,
378
+ dtype=torch.float32,
379
+ device=self.device,
380
+ )
381
+ # Debug: probs before torch.multinomial (for fake multinomial hooks)
382
+ self._debug_probs = torch.zeros(
383
+ self.num_decode_codebooks,
384
+ self.half,
385
+ self.vocab_size,
386
+ dtype=torch.float32,
387
+ device=self.device,
388
+ )
389
+ # Single-slot buffer written by _cfg_sample, copied per-cb in _full_loop
390
+ self._debug_probs_slot = torch.zeros(
391
+ self.half,
392
+ self.vocab_size,
393
+ dtype=torch.float32,
394
+ device=self.device,
395
+ )
396
+ else:
397
+ self.debug_logits = None
398
+ self._debug_head_input = None
399
+ self._debug_probs = None
400
+ self._debug_probs_slot = None
401
+
402
+ # ------------------------------------------------------------------
403
+ # Batch size management — auto-recapture on change
404
+ # ------------------------------------------------------------------
405
+
406
+ @torch.inference_mode()
407
+ def ensure_batch_size(self, batch_size: int):
408
+ """No-op if batch_size matches current captured graph; else rebuild and recapture."""
409
+ if batch_size == self.batch_size:
410
+ return
411
+ self._rebuild_for_batch(batch_size)
412
+
413
+ @torch.inference_mode()
414
+ def _rebuild_for_batch(self, batch_size: int):
415
+ self.batch_size = batch_size
416
+ self.half = self._real_batch_size(self.batch_size)
417
+ self.static_cache = StaticCache(
418
+ config=self.config, max_cache_len=self.max_seq, batch_size=batch_size
419
+ )
420
+ self._alloc_buffers()
421
+ self.captured = False
422
+ self.graph = None
423
+ self.prefill_attn = None
424
+ self.decode_attn = None
425
+ if self.no_graph:
426
+ self.prepare_eager()
427
+ else:
428
+ self.capture()
429
+
430
+ @torch.inference_mode()
431
+ def prepare_eager(self):
432
+ """Initialize the depth loop for native eager execution only."""
433
+ self.no_graph = True
434
+ self._init_cache_layers()
435
+ self._build_attention_masks()
436
+ self.captured = False
437
+ return self
438
+
439
+ # ------------------------------------------------------------------
440
+ # Internal helpers
441
+ # ------------------------------------------------------------------
442
+
443
+ def _init_cache_layers(self):
444
+ """Force lazy initialization of StaticCache layers before graph capture."""
445
+ config = self.depth_model.config
446
+ num_kv_heads = getattr(
447
+ config, "num_key_value_heads", config.num_attention_heads
448
+ )
449
+ head_dim = getattr(
450
+ config, "head_dim", config.hidden_size // config.num_attention_heads
451
+ )
452
+ dummy_k = torch.zeros(
453
+ self.batch_size,
454
+ num_kv_heads,
455
+ 1,
456
+ head_dim,
457
+ dtype=self.dtype,
458
+ device=self.device,
459
+ )
460
+ for layer in self.static_cache.layers:
461
+ if not layer.is_initialized:
462
+ layer.lazy_initialization(dummy_k)
463
+
464
+ def _make_attn_mask(self, input_embeds, cache_position):
465
+ return create_causal_mask(
466
+ config=self.depth_model.config,
467
+ input_embeds=input_embeds,
468
+ attention_mask=None,
469
+ cache_position=cache_position,
470
+ past_key_values=self.static_cache,
471
+ )
472
+
473
+ def _build_attention_masks(self):
474
+ dummy_prefill = torch.zeros(
475
+ self.batch_size, 2, self.hidden_size, dtype=self.dtype, device=self.device
476
+ )
477
+ dummy_decode = torch.zeros(
478
+ self.batch_size, 1, self.hidden_size, dtype=self.dtype, device=self.device
479
+ )
480
+ self.prefill_attn = self._make_attn_mask(dummy_prefill, self.prefill_cache_pos)
481
+ self.decode_attn = []
482
+ for pos in self.decode_cache_positions:
483
+ self.decode_attn.append(self._make_attn_mask(dummy_decode, pos))
484
+
485
+ def _cfg_sample(self, logits):
486
+ """Graph-safe CFG + sampling matching HF transformers order.
487
+
488
+ HF order: temperature -> top_k -> top_p -> softmax -> multinomial.
489
+ All filtering on raw logits (not log-space) for parity with
490
+ TemperatureLogitsWarper / TopKLogitsWarper / TopPLogitsWarper.
491
+
492
+ Writes sampled tokens into self._tok_buf [batch_size].
493
+ For batch_size=2*N: processes N samples, writes to both halves (cond+uncond).
494
+ """
495
+ # logits: [batch_size, 1, vocab]
496
+ if self.batch_size >= 2:
497
+ cond_logits = logits[: self.half, 0, :]
498
+ uncond_logits = logits[self.half :, 0, :]
499
+ cfg = uncond_logits + self.guidance_scale * (cond_logits - uncond_logits)
500
+ else:
501
+ cfg = logits[: self.half, 0, :]
502
+
503
+ mask_invalid_codec_token_logits(
504
+ cfg,
505
+ codebook_size=self.codec_codebook_size,
506
+ token_vocab_size=self.vocab_size,
507
+ )
508
+
509
+ # temperature scaling (on raw logits, same as TemperatureLogitsWarper)
510
+ scaled = cfg / self.temperature_buf # [half, vocab]
511
+
512
+ # top_k on raw logits (graph-safe: fixed _max_k workspace)
513
+ effective_k = torch.where(
514
+ self.top_k_buf > 0,
515
+ self.top_k_buf,
516
+ torch.full_like(self.top_k_buf, self._max_k),
517
+ )
518
+ topk_vals, topk_idx = torch.topk(scaled, self._max_k) # [half, _max_k]
519
+ topk_mask = self._topk_ranks < effective_k # [_max_k] broadcasts
520
+ topk_vals = torch.where(
521
+ topk_mask, topk_vals, torch.full_like(topk_vals, float("-inf"))
522
+ )
523
+ scaled = torch.full_like(scaled, float("-inf")).scatter_(1, topk_idx, topk_vals)
524
+
525
+ # top_p on raw logits (HF-style: softmax -> cumsum -> mask -> apply to raw logits)
526
+ sorted_logits, sorted_idx = torch.sort(scaled, descending=True) # [half, vocab]
527
+ cumprobs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
528
+ remove = cumprobs > self.top_p_buf
529
+ remove = F.pad(
530
+ remove[..., :-1], (1, 0), value=False
531
+ ) # shift right, first always kept
532
+ sorted_logits = torch.where(
533
+ remove, torch.full_like(sorted_logits, float("-inf")), sorted_logits
534
+ )
535
+ scaled = torch.full_like(scaled, float("-inf")).scatter_(
536
+ 1, sorted_idx, sorted_logits
537
+ )
538
+
539
+ # softmax -> multinomial (same as HF _sample)
540
+ probs = F.softmax(scaled, dim=-1)
541
+
542
+ # Debug: save probs before multinomial for fake forward hooks
543
+ if self._debug_probs_slot is not None:
544
+ self._debug_probs_slot.copy_(probs)
545
+
546
+ # do_sample: compute both paths, select via torch.where (no Python branch in graph)
547
+ greedy_toks = torch.argmax(cfg, dim=-1) # [half]
548
+ sampled_toks = torch.multinomial(probs, 1).squeeze(-1) # [half]
549
+ toks = torch.where(
550
+ self.do_sample_buf.bool(), sampled_toks, greedy_toks
551
+ ) # [half] per-sample
552
+
553
+ # Write to the active slots; duplicate for paired cond/uncond mode.
554
+ self._tok_buf[: self.half] = toks
555
+ if self.batch_size >= 2:
556
+ self._tok_buf[self.half :] = toks
557
+
558
+ def _full_loop(self):
559
+ """The full depth decoder loop on static buffers. Graph-safe."""
560
+ # Build [B, 2] input_ids: pos0=0 (placeholder), pos1=first_cb_token
561
+ self.prefill_input_ids[:, 0] = 0
562
+ self.prefill_input_ids[:, 1] = self.first_cb_token_buf
563
+
564
+ # Embed full 2-token sequence, then overwrite pos0 with backbone hidden
565
+ # (matches parity baseline: embed placeholder, then replace with backbone_h)
566
+ prefill_embeds = self.embed_tokens(
567
+ self.prefill_input_ids
568
+ ) # [B, 2, audio_embed_size]
569
+ # Project backbone hidden to audio_embed_size if dimensions differ
570
+ backbone_h = self.backbone_hidden_buf # [B, backbone_hidden_size]
571
+ if self.backbone_hidden_state_projector is not None:
572
+ backbone_h = self.backbone_hidden_state_projector(
573
+ backbone_h
574
+ ) # [B, audio_embed_size]
575
+ prefill_embeds[:, 0] = backbone_h # overwrite pos0
576
+ prefill_embeds = self.inputs_embeds_projector(prefill_embeds) # [B, 2, hidden]
577
+
578
+ hidden_states = prefill_embeds
579
+ position_ids = self.prefill_cache_pos.unsqueeze(0) # [1, 2]
580
+ position_embeddings = self.depth_model.rotary_emb(hidden_states, position_ids)
581
+
582
+ for decoder_layer in self.depth_model.layers:
583
+ hidden_states = decoder_layer(
584
+ hidden_states,
585
+ attention_mask=self.prefill_attn,
586
+ position_ids=position_ids,
587
+ past_key_values=self.static_cache,
588
+ use_cache=True,
589
+ cache_position=self.prefill_cache_pos,
590
+ position_embeddings=position_embeddings,
591
+ )
592
+
593
+ hidden_states = self.depth_model.norm(hidden_states)
594
+
595
+ # First codebook logits from position 1 (fp32: weight already cast, input cast here)
596
+ if self._debug_head_input is not None:
597
+ self._debug_head_input[0].copy_(hidden_states[:, 1:, :].float())
598
+ first_logits = self.codebooks_head(
599
+ hidden_states[:, 1:, :].float(),
600
+ cache_position=self.head_prefill_pos,
601
+ ) # [B, 1, vocab] fp32
602
+
603
+ if self.debug_logits is not None:
604
+ self.debug_logits[0].copy_(first_logits[:, 0, :])
605
+
606
+ self._cfg_sample(first_logits)
607
+ if self._debug_probs is not None:
608
+ self._debug_probs[0].copy_(self._debug_probs_slot)
609
+ self._tok_buf.clamp_(0, self.vocab_size - 1)
610
+ self.output_tokens[:, 0] = self._tok_buf
611
+
612
+ # Remaining codebooks: decode one at a time
613
+ for cb_idx in range(1, self.num_decode_codebooks):
614
+ offset_tok = self._tok_buf + self.codebook_offsets[cb_idx]
615
+ emb = self.embed_tokens(
616
+ offset_tok.unsqueeze(1).clamp_(
617
+ 0, self.num_codebooks * self.vocab_size - 1
618
+ )
619
+ ) # [B, 1, backbone_H]
620
+ emb = self.inputs_embeds_projector(emb) # [B, 1, hidden]
621
+
622
+ cache_pos = self.decode_cache_positions[cb_idx - 1]
623
+ pos_ids = cache_pos.unsqueeze(0)
624
+ pos_emb = self.depth_model.rotary_emb(emb, pos_ids)
625
+
626
+ hidden_states = emb
627
+ for decoder_layer in self.depth_model.layers:
628
+ hidden_states = decoder_layer(
629
+ hidden_states,
630
+ attention_mask=self.decode_attn[cb_idx - 1],
631
+ position_ids=pos_ids,
632
+ past_key_values=self.static_cache,
633
+ use_cache=True,
634
+ cache_position=cache_pos,
635
+ position_embeddings=pos_emb,
636
+ )
637
+
638
+ hidden_states = self.depth_model.norm(hidden_states)
639
+
640
+ if self._debug_head_input is not None:
641
+ self._debug_head_input[cb_idx].copy_(hidden_states.float())
642
+ logits = self.codebooks_head(
643
+ hidden_states.float(),
644
+ cache_position=cache_pos,
645
+ ) # [B, 1, vocab] fp32
646
+
647
+ if self.debug_logits is not None:
648
+ self.debug_logits[cb_idx].copy_(logits[:, 0, :])
649
+
650
+ self._cfg_sample(logits)
651
+ if self._debug_probs is not None:
652
+ self._debug_probs[cb_idx].copy_(self._debug_probs_slot)
653
+ self._tok_buf.clamp_(0, self.vocab_size - 1)
654
+ self.output_tokens[:, cb_idx] = self._tok_buf
655
+
656
+ # ------------------------------------------------------------------
657
+ # Snapshot / restore helpers for multi-bucket capture
658
+ # ------------------------------------------------------------------
659
+
660
+ def _snapshot_state(self) -> _BucketState:
661
+ """Snapshot current self.* buffers into a _BucketState."""
662
+ return _BucketState(
663
+ batch_size=self.batch_size,
664
+ half=self.half,
665
+ graph=self.graph,
666
+ static_cache=self.static_cache,
667
+ backbone_hidden_buf=self.backbone_hidden_buf,
668
+ first_cb_token_buf=self.first_cb_token_buf,
669
+ output_tokens=self.output_tokens,
670
+ _tok_buf=self._tok_buf,
671
+ prefill_input_ids=self.prefill_input_ids,
672
+ prefill_attn=self.prefill_attn,
673
+ decode_attn=self.decode_attn,
674
+ temperature_buf=self.temperature_buf,
675
+ top_k_buf=self.top_k_buf,
676
+ top_p_buf=self.top_p_buf,
677
+ do_sample_buf=self.do_sample_buf,
678
+ guidance_scale=self.guidance_scale,
679
+ debug_logits=self.debug_logits,
680
+ _debug_head_input=self._debug_head_input,
681
+ _debug_probs=self._debug_probs,
682
+ _debug_probs_slot=self._debug_probs_slot,
683
+ )
684
+
685
+ def _swap_to_bucket(self, state: _BucketState):
686
+ """Point self.* at the given bucket's buffers (for graph replay)."""
687
+ self.batch_size = state.batch_size
688
+ self.half = state.half
689
+ self.graph = state.graph
690
+ self.static_cache = state.static_cache
691
+ self.backbone_hidden_buf = state.backbone_hidden_buf
692
+ self.first_cb_token_buf = state.first_cb_token_buf
693
+ self.output_tokens = state.output_tokens
694
+ self._tok_buf = state._tok_buf
695
+ self.prefill_input_ids = state.prefill_input_ids
696
+ self.prefill_attn = state.prefill_attn
697
+ self.decode_attn = state.decode_attn
698
+ self.temperature_buf = state.temperature_buf
699
+ self.top_k_buf = state.top_k_buf
700
+ self.top_p_buf = state.top_p_buf
701
+ self.do_sample_buf = state.do_sample_buf
702
+ self.guidance_scale = state.guidance_scale
703
+ self.debug_logits = state.debug_logits
704
+ self._debug_head_input = state._debug_head_input
705
+ self._debug_probs = state._debug_probs
706
+ self._debug_probs_slot = state._debug_probs_slot
707
+
708
+ # ------------------------------------------------------------------
709
+ # Bucket selection
710
+ # ------------------------------------------------------------------
711
+
712
+ def _select_bucket(self, actual_batch: int) -> int | None:
713
+ """Return smallest bucket_size >= actual_batch, or None if none fits."""
714
+ for bsz in self.bucket_sizes:
715
+ if bsz >= actual_batch:
716
+ return bsz
717
+ return None
718
+
719
+ @staticmethod
720
+ def _copy_cfg_padded(dst, src, actual_batch: int, bucket_half: int):
721
+ """Copy [cond..., uncond...] rows into a padded CFG bucket.
722
+
723
+ ``_cfg_sample`` slices the static bucket as
724
+ ``[:bucket_half]`` for conditional rows and ``[bucket_half:]`` for
725
+ unconditional rows. When an actual batch is padded to a larger bucket,
726
+ real uncond rows must start at ``bucket_half`` rather than immediately
727
+ after the real cond rows.
728
+ """
729
+ actual_half = actual_batch // 2
730
+ bucket_batch = bucket_half * 2
731
+
732
+ dst[:actual_half].copy_(src[:actual_half])
733
+ dst[bucket_half : bucket_half + actual_half].copy_(
734
+ src[actual_half:actual_batch]
735
+ )
736
+
737
+ if actual_half < bucket_half:
738
+ dst[actual_half:bucket_half].zero_()
739
+ dst[bucket_half + actual_half : bucket_batch].zero_()
740
+
741
+ # ------------------------------------------------------------------
742
+ # Padded param setter (for bucket padding)
743
+ # ------------------------------------------------------------------
744
+
745
+ def _set_param_padded(self, buf, value, actual_n, bucket_n, is_1d=False):
746
+ """Set a sampling param buffer with padding for bucket size mismatch.
747
+
748
+ Args:
749
+ buf: target buffer, shape [bucket_n, 1] or [bucket_n]
750
+ value: scalar or tensor
751
+ actual_n: number of actual samples
752
+ bucket_n: bucket's half size
753
+ is_1d: True if buf is 1D (e.g. do_sample_buf)
754
+ """
755
+ if isinstance(value, (int, float, bool)):
756
+ buf.fill_(int(value) if is_1d else value)
757
+ else:
758
+ t = value.view(actual_n) if is_1d else value.view(actual_n, -1)
759
+ if is_1d:
760
+ buf[:actual_n].copy_(t)
761
+ if actual_n < bucket_n:
762
+ buf[actual_n:].fill_(t[0].item())
763
+ else:
764
+ buf[:actual_n].copy_(t[:, : buf.shape[-1]])
765
+ if actual_n < bucket_n:
766
+ buf[actual_n:].fill_(t[0, 0].item())
767
+
768
+ # ------------------------------------------------------------------
769
+ # Capture: single bucket + multi-bucket entry point
770
+ # ------------------------------------------------------------------
771
+
772
+ @torch.inference_mode()
773
+ def _capture_single_bucket(self, bsz: int, num_warmup: int):
774
+ """Capture a CUDA graph for a single batch size."""
775
+ # Set up state for this bucket size
776
+ self.batch_size = bsz
777
+ self.half = self._real_batch_size(bsz)
778
+ self.static_cache = StaticCache(
779
+ config=self.config, max_cache_len=self.max_seq, batch_size=bsz
780
+ )
781
+ self._alloc_buffers()
782
+ self._init_cache_layers()
783
+ self._build_attention_masks()
784
+
785
+ # Warmup
786
+ for _ in range(num_warmup):
787
+ self.static_cache.reset()
788
+ self._full_loop()
789
+ torch.cuda.synchronize(device=self.device)
790
+
791
+ # Capture
792
+ s = torch.cuda.Stream(device=self.device)
793
+ s.wait_stream(torch.cuda.current_stream())
794
+ with torch.cuda.stream(s):
795
+ self.graph = torch.cuda.CUDAGraph()
796
+ self.static_cache.reset()
797
+ self._full_loop()
798
+ torch.cuda.synchronize(device=self.device)
799
+
800
+ self.static_cache.reset()
801
+ with torch.cuda.graph(self.graph):
802
+ self._full_loop()
803
+
804
+ torch.cuda.current_stream().wait_stream(s)
805
+ torch.cuda.synchronize(device=self.device)
806
+
807
+ # Save state for this bucket
808
+ self._bucket_graphs[bsz] = self._snapshot_state()
809
+ print(f" Depth decoder CUDA graph captured for bucket_size={bsz}")
810
+
811
+ @torch.inference_mode()
812
+ def capture(self, num_warmup=3):
813
+ """Warmup and capture CUDA graphs for all bucket sizes."""
814
+ if self.fast:
815
+ num_warmup = max(num_warmup, 8)
816
+ print(
817
+ f"Capturing depth decoder graphs for buckets={self.bucket_sizes} "
818
+ f"({num_warmup} warmup runs, fast={self.fast})..."
819
+ )
820
+
821
+ for bsz in self.bucket_sizes:
822
+ self._capture_single_bucket(bsz, num_warmup)
823
+
824
+ # Leave self.* pointing at the smallest bucket (default)
825
+ self._swap_to_bucket(self._bucket_graphs[self.bucket_sizes[0]])
826
+ self.captured = True
827
+ print(
828
+ f"Depth decoder CUDA graphs captured for all {len(self.bucket_sizes)} buckets!"
829
+ )
830
+ return self
831
+
832
+ # ------------------------------------------------------------------
833
+ # Eager fallback (for batch sizes exceeding all buckets)
834
+ # ------------------------------------------------------------------
835
+
836
+ @torch.inference_mode()
837
+ def _run_eager(
838
+ self,
839
+ backbone_hidden,
840
+ first_cb_token,
841
+ temperature=None,
842
+ top_k=None,
843
+ top_p=None,
844
+ do_sample=None,
845
+ guidance_scale=None,
846
+ ):
847
+ """Fallback: ensure_batch_size + _full_loop (no CUDA graph)."""
848
+ actual_batch = backbone_hidden.shape[0]
849
+ self.ensure_batch_size(actual_batch)
850
+
851
+ self.backbone_hidden_buf.copy_(backbone_hidden)
852
+ self.first_cb_token_buf.copy_(first_cb_token)
853
+
854
+ if temperature is not None:
855
+ self.set_temperature(temperature)
856
+ if top_k is not None:
857
+ self.set_top_k(top_k)
858
+ if top_p is not None:
859
+ self.set_top_p(top_p)
860
+ if do_sample is not None:
861
+ self.set_do_sample(do_sample)
862
+ if guidance_scale is not None:
863
+ self.set_guidance_scale(guidance_scale)
864
+
865
+ self.static_cache.reset()
866
+ self._full_loop()
867
+ return self.output_tokens[: self.half].clone()
868
+
869
+ # ------------------------------------------------------------------
870
+ # Run: bucket selection + graph replay
871
+ # ------------------------------------------------------------------
872
+
873
+ @torch.inference_mode()
874
+ def run(
875
+ self,
876
+ backbone_hidden,
877
+ first_cb_token,
878
+ temperature=None,
879
+ top_k=None,
880
+ top_p=None,
881
+ do_sample=None,
882
+ guidance_scale=None,
883
+ ):
884
+ """
885
+ Run the captured graph with automatic bucket selection.
886
+
887
+ backbone_hidden: [batch_size, backbone_hidden_size]
888
+ first_cb_token: [batch_size] long tensor
889
+
890
+ Optional per-sample sampling params (applied before graph replay):
891
+ temperature: float or [half] / [half,1] tensor
892
+ top_k: int or [half] / [half,1] tensor
893
+ top_p: float or [half] / [half,1] tensor
894
+ do_sample: bool or [half] long tensor (0/1)
895
+ guidance_scale: float or [half] / [half,1] tensor
896
+
897
+ Returns: [actual_half, num_decode_codebooks] long tensor of codebook tokens
898
+ """
899
+ actual_batch = backbone_hidden.shape[0]
900
+ actual_half = self._real_batch_size(actual_batch)
901
+
902
+ # Safety guard: single CFG expects paired cond+uncond rows. no-CFG uses
903
+ # batch=1 and is valid.
904
+ if actual_batch != 1 and actual_batch % 2 != 0:
905
+ _log.warning(
906
+ "DepthDecoderGraph.run: invalid CFG actual_batch=%d, "
907
+ "returning zeros to avoid CUDA graph mismatch",
908
+ actual_batch,
909
+ )
910
+ return torch.zeros(
911
+ 1,
912
+ self.num_decode_codebooks,
913
+ dtype=torch.long,
914
+ device=self.device,
915
+ )
916
+
917
+ # Eager fallback: no_graph flag or no fitting bucket
918
+ bucket_bsz = self._select_bucket(actual_batch)
919
+ if self.no_graph or bucket_bsz is None:
920
+ return self._run_eager(
921
+ backbone_hidden,
922
+ first_cb_token,
923
+ temperature,
924
+ top_k,
925
+ top_p,
926
+ do_sample,
927
+ guidance_scale,
928
+ )
929
+
930
+ # Swap to the selected bucket's state
931
+ state = self._bucket_graphs[bucket_bsz]
932
+ self._swap_to_bucket(state)
933
+ bucket_half = self.half
934
+
935
+ if actual_batch == 1:
936
+ self.backbone_hidden_buf[:1].copy_(backbone_hidden[:1])
937
+ self.first_cb_token_buf[:1].copy_(first_cb_token[:1])
938
+ else:
939
+ # Preserve the CFG layout expected by _cfg_sample:
940
+ # [cond real][cond pad][uncond real][uncond pad].
941
+ self._copy_cfg_padded(
942
+ self.backbone_hidden_buf, backbone_hidden, actual_batch, bucket_half
943
+ )
944
+ self._copy_cfg_padded(
945
+ self.first_cb_token_buf, first_cb_token, actual_batch, bucket_half
946
+ )
947
+
948
+ # Set sampling params with padding
949
+ if temperature is not None:
950
+ self._set_param_padded(
951
+ self.temperature_buf, temperature, actual_half, bucket_half
952
+ )
953
+ if top_k is not None:
954
+ self._set_param_padded(self.top_k_buf, top_k, actual_half, bucket_half)
955
+ if top_p is not None:
956
+ self._set_param_padded(self.top_p_buf, top_p, actual_half, bucket_half)
957
+ if do_sample is not None:
958
+ self._set_param_padded(
959
+ self.do_sample_buf, do_sample, actual_half, bucket_half, is_1d=True
960
+ )
961
+ if guidance_scale is not None:
962
+ self._set_param_padded(
963
+ self.guidance_scale, guidance_scale, actual_half, bucket_half
964
+ )
965
+
966
+ # Replay graph
967
+ self.static_cache.reset()
968
+ state.graph.replay()
969
+
970
+ if self.debug and self._debug_head_input is not None:
971
+ head = self._orig_codebooks_head
972
+ for cb_idx in range(self.num_decode_codebooks):
973
+ if cb_idx == 0:
974
+ head(
975
+ self._debug_head_input[0], cache_position=self.head_prefill_pos
976
+ )
977
+ else:
978
+ head(
979
+ self._debug_head_input[cb_idx],
980
+ cache_position=self.decode_cache_positions[cb_idx - 1],
981
+ )
982
+ torch.multinomial(
983
+ self._debug_probs[cb_idx], 1, _hook_only_for_debug=True
984
+ )
985
+
986
+ # Slice output to actual_half (discard padding)
987
+ return self.output_tokens[:actual_half].clone()
models/cudagraph/sampling.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared sampling helpers for backbone and depth decoder generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import random
7
+ from collections.abc import Iterable
8
+
9
+ import numpy as np
10
+ import torch
11
+ import torch.nn.functional as F
12
+
13
+
14
+ def apply_repetition_penalty(
15
+ logits: torch.Tensor,
16
+ token_history: torch.Tensor,
17
+ repetition_penalty: float,
18
+ ) -> torch.Tensor:
19
+ """Apply repetition penalty to logits in-place and return them.
20
+
21
+ Args:
22
+ logits: Tensor shaped [1, 1, vocab] or [1, vocab].
23
+ token_history: 1-D tensor of previously generated token ids.
24
+ repetition_penalty: HF-style repetition penalty (>1.0).
25
+ """
26
+ if repetition_penalty == 1.0 or token_history.numel() == 0:
27
+ return logits
28
+ unique_toks = token_history.unique()
29
+ tok_logits = logits[..., unique_toks]
30
+ logits[..., unique_toks] = torch.where(
31
+ tok_logits > 0, tok_logits / repetition_penalty, tok_logits * repetition_penalty
32
+ )
33
+ return logits
34
+
35
+
36
+ def sample_logits(
37
+ logits: torch.Tensor,
38
+ *,
39
+ temperature: float,
40
+ top_k: int,
41
+ top_p: float,
42
+ do_sample: bool,
43
+ token_history: torch.Tensor | None = None,
44
+ repetition_penalty: float = 1.0,
45
+ suppress_mask: torch.Tensor | None = None,
46
+ suppress_tokens: Iterable[int] | None = None,
47
+ ) -> torch.Tensor:
48
+ """Sample a token from logits.
49
+
50
+ HF-compatible order: suppress -> temperature -> top_k -> top_p -> softmax -> sample.
51
+ Matches transformers logits_processor (TemperatureLogitsWarper, TopKLogitsWarper,
52
+ TopPLogitsWarper) followed by softmax + multinomial.
53
+ """
54
+ logits = logits.clone().float()
55
+ if token_history is not None:
56
+ apply_repetition_penalty(logits, token_history, repetition_penalty)
57
+ if suppress_mask is not None:
58
+ logits[..., suppress_mask] = float("-inf")
59
+ if suppress_tokens:
60
+ logits[..., list(suppress_tokens)] = float("-inf")
61
+ if not do_sample:
62
+ return torch.argmax(logits, dim=-1)
63
+ # temperature scaling (on raw logits, same as TemperatureLogitsWarper)
64
+ logits = logits / temperature
65
+ # top_k filtering (on raw logits, same as TopKLogitsWarper)
66
+ if top_k > 0:
67
+ k = min(top_k, logits.size(-1))
68
+ topk_vals, _ = torch.topk(logits, k)
69
+ threshold = topk_vals[..., -1:]
70
+ logits = logits.masked_fill(logits < threshold, float("-inf"))
71
+ # top_p filtering (on raw logits, same as TopPLogitsWarper)
72
+ if top_p < 1.0:
73
+ sorted_logits, sorted_indices = torch.sort(logits, descending=True)
74
+ cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
75
+ sorted_indices_to_remove = cumulative_probs > top_p
76
+ # HF-style shift: keep the token that pushes cumsum over top_p
77
+ sorted_indices_to_remove = F.pad(
78
+ sorted_indices_to_remove[..., :-1], (1, 0), value=False
79
+ )
80
+ sorted_logits[sorted_indices_to_remove] = float("-inf")
81
+ logits = torch.full_like(logits, float("-inf")).scatter_(
82
+ -1, sorted_indices, sorted_logits
83
+ )
84
+ # softmax -> multinomial (same as HF _sample)
85
+ probs = F.softmax(logits, dim=-1)
86
+ return torch.multinomial(probs, 1).squeeze(-1)
87
+
88
+
89
+ def set_deterministic(seed=42):
90
+ """Enable full deterministic mode and seed all RNGs for exact reproducibility."""
91
+ random.seed(seed)
92
+ os.environ["PYTHONHASHSEED"] = str(seed)
93
+ np.random.seed(seed)
94
+ torch.manual_seed(seed)
95
+ torch.cuda.manual_seed_all(seed)
96
+ os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
97
+ torch.use_deterministic_algorithms(True, warn_only=True)
98
+ torch.backends.cudnn.deterministic = True
99
+ torch.backends.cudnn.benchmark = False
models/fast_streaming.py ADDED
@@ -0,0 +1,943 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import time
5
+ import uuid
6
+ from collections.abc import Iterator
7
+ from dataclasses import dataclass, field
8
+ from pathlib import Path
9
+ from typing import Any, Literal
10
+
11
+ import numpy as np
12
+ import torch
13
+
14
+ from .cudagraph.backbone_graph import BackboneGraph
15
+ from .cudagraph.backbone_prefill_graph import BackbonePrefillGraphCache
16
+ from .cudagraph.depth_decoder_graph import DepthDecoderGraph
17
+ from .cudagraph.sampling import sample_logits
18
+ from .warmup_profile import FastStreamingWarmupProfile
19
+
20
+ FastCfgMode = Literal["no_cfg", "single_cfg"]
21
+
22
+ _DUAL_CFG_KEYS = (
23
+ "cfg_scale_ref",
24
+ "cfg_scale_ins",
25
+ "cfg_uncond_prompt_ids",
26
+ "cfg_ref_prompt_ids",
27
+ "cfg_ins_prompt_ids",
28
+ )
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class FastStreamingConfig:
33
+ max_new_tokens: int = 750
34
+ max_seq_len: int = 1024
35
+ collect_timing: bool = False
36
+ fast_all: bool | None = None
37
+ fast_text_encoder: bool = False
38
+ fast_backbone_prefill: bool = False
39
+ fast_backbone_decode: bool = False
40
+ fast_depth_decoder: bool = False
41
+ fast_codec: bool = False
42
+ temperature: float | None = None
43
+ top_k: int | None = None
44
+ top_p: float | None = None
45
+ do_sample: bool | None = None
46
+ repetition_penalty: float = 1.1
47
+
48
+ def stage_fast(self, stage: str) -> bool:
49
+ """Resolve the master switch before the per-stage setting."""
50
+ if self.fast_all is not None:
51
+ return self.fast_all
52
+ field_name = f"fast_{stage}"
53
+ if field_name not in self.__dataclass_fields__ or field_name == "fast_all":
54
+ raise ValueError(f"unknown fast path stage: {stage!r}")
55
+ return bool(getattr(self, field_name))
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class FastStreamingChunk:
60
+ audio: np.ndarray
61
+ sample_rate: int
62
+ codec_frames: int
63
+ is_final: bool
64
+ timing: dict[str, float | int | bool] = field(default_factory=dict)
65
+
66
+
67
+ @dataclass(frozen=True)
68
+ class FastCfgSelection:
69
+ mode: FastCfgMode
70
+ guidance_scale: float
71
+ use_negative_as_main: bool
72
+
73
+
74
+ @dataclass
75
+ class _BranchBatch:
76
+ inputs_embeds: torch.Tensor
77
+ attention_mask: torch.Tensor
78
+ branch_batch_size: int
79
+ cfg: FastCfgSelection
80
+
81
+
82
+ def reject_dual_cfg(inputs: dict[str, Any]) -> None:
83
+ present = [key for key in _DUAL_CFG_KEYS if inputs.get(key) is not None]
84
+ if present:
85
+ raise ValueError(
86
+ "fast streaming supports only no_cfg and single_cfg; "
87
+ f"dual CFG fields are not supported: {present}"
88
+ )
89
+
90
+
91
+ def select_fast_cfg(inputs: dict[str, Any]) -> FastCfgSelection:
92
+ reject_dual_cfg(inputs)
93
+ cfg_scale = float(inputs.get("cfg_scale", 1.0))
94
+ has_negative = inputs.get("cfg_negative_prompt_ids") is not None
95
+ if cfg_scale == 0.0 and has_negative:
96
+ return FastCfgSelection(
97
+ mode="no_cfg", guidance_scale=1.0, use_negative_as_main=True
98
+ )
99
+ if cfg_scale != 1.0:
100
+ if not has_negative:
101
+ raise ValueError(
102
+ "single_cfg requires cfg_negative_prompt_ids when cfg_scale != 1.0"
103
+ )
104
+ return FastCfgSelection(
105
+ mode="single_cfg", guidance_scale=cfg_scale, use_negative_as_main=False
106
+ )
107
+ return FastCfgSelection(
108
+ mode="no_cfg", guidance_scale=1.0, use_negative_as_main=False
109
+ )
110
+
111
+
112
+ def is_backbone_eos_token(token: torch.Tensor | int, config: Any) -> bool:
113
+ token_id = int(token.item() if isinstance(token, torch.Tensor) else token)
114
+ return token_id == int(config.vocab_size)
115
+
116
+
117
+ def is_terminal_pad_frame(frame: torch.Tensor, config: Any) -> bool:
118
+ return bool((frame == int(config.codebook_pad_token_id)).all().item())
119
+
120
+
121
+ def should_decode_codec_frame(frame: torch.Tensor, config: Any) -> bool:
122
+ return not is_terminal_pad_frame(frame, config)
123
+
124
+
125
+ def _get_device(model: torch.nn.Module) -> torch.device:
126
+ try:
127
+ return next(model.parameters()).device
128
+ except StopIteration:
129
+ return torch.device("cpu")
130
+
131
+
132
+ def _get_dtype(model: torch.nn.Module) -> torch.dtype:
133
+ try:
134
+ return next(model.parameters()).dtype
135
+ except StopIteration:
136
+ return torch.bfloat16
137
+
138
+
139
+ def _left_pad_tensor(
140
+ tensor: torch.Tensor, target_len: int, value: float = 0
141
+ ) -> torch.Tensor:
142
+ pad_len = target_len - tensor.shape[1]
143
+ if pad_len <= 0:
144
+ return tensor
145
+ pad_shape = (tensor.shape[0], pad_len, *tensor.shape[2:])
146
+ pad = torch.full(pad_shape, value, dtype=tensor.dtype, device=tensor.device)
147
+ return torch.cat([pad, tensor], dim=1)
148
+
149
+
150
+ def _extract_audio_np(audio: torch.Tensor) -> np.ndarray:
151
+ while audio.dim() > 1:
152
+ audio = audio[0]
153
+ return audio.detach().float().cpu().numpy()
154
+
155
+
156
+ class FastBreezeStreamingRuntime:
157
+ def __init__(
158
+ self,
159
+ model: torch.nn.Module,
160
+ audio_tokenizer: Any,
161
+ config: FastStreamingConfig | None = None,
162
+ *,
163
+ tokenizer: Any | None = None,
164
+ ) -> None:
165
+ self.model = model
166
+ self.audio_tokenizer = audio_tokenizer
167
+ self.tokenizer = tokenizer
168
+ self.config = config or FastStreamingConfig()
169
+ self._fast_text_encoder = self.config.stage_fast("text_encoder")
170
+ self._fast_backbone_prefill = self.config.stage_fast("backbone_prefill")
171
+ self._fast_backbone_decode = self.config.stage_fast("backbone_decode")
172
+ self._fast_depth_decoder = self.config.stage_fast("depth_decoder")
173
+ self._fast_codec = self.config.stage_fast("codec")
174
+ self.model._fast_text_encoder_cudagraph = self._fast_text_encoder
175
+ self._codec_chunk_frames = 1 if self._fast_codec else 2
176
+ self.device = _get_device(model)
177
+ self.dtype = _get_dtype(model)
178
+ self._backbone_graph: BackboneGraph | None = None
179
+ self._backbone_graphs: dict[int, BackboneGraph] = {}
180
+ self._backbone_prefill_graph: BackbonePrefillGraphCache | None = None
181
+ self._backbone_prefill_graphs: dict[int, BackbonePrefillGraphCache] = {}
182
+ self._depth_decoder_graph: DepthDecoderGraph | None = None
183
+ self._codec_runtime = None
184
+ self._warmup_profile: FastStreamingWarmupProfile | None = None
185
+ self._warmup_manifest: dict[str, Any] | None = None
186
+ self._frozen_branch_batch_sizes: frozenset[int] | None = None
187
+ self._codec_codebook_size = int(self.model.config.codec_config.codebook_size)
188
+ self._reserved_codec_token_ids = tuple(
189
+ range(self._codec_codebook_size, int(self.model.config.vocab_size))
190
+ )
191
+
192
+ if self.device.type != "cuda":
193
+ raise RuntimeError("fast streaming requires a CUDA device")
194
+ if self.config.repetition_penalty <= 0:
195
+ raise ValueError("repetition_penalty must be > 0")
196
+
197
+ @property
198
+ def sample_rate(self) -> int:
199
+ return int(self.model.config.codec_config.sampling_rate)
200
+
201
+ def _sampling_params(self, generation_config: Any) -> dict[str, Any]:
202
+ def _value(name: str, override: Any, default: Any) -> Any:
203
+ if override is not None:
204
+ return override
205
+ value = getattr(generation_config, name, default)
206
+ return default if value is None else value
207
+
208
+ return {
209
+ "temperature": float(_value("temperature", self.config.temperature, 1.0)),
210
+ "top_k": int(_value("top_k", self.config.top_k, 0)),
211
+ "top_p": float(_value("top_p", self.config.top_p, 1.0)),
212
+ "do_sample": bool(_value("do_sample", self.config.do_sample, True)),
213
+ }
214
+
215
+ def _ensure_graphs(
216
+ self,
217
+ branch_batch_size: int,
218
+ guidance_scale: float,
219
+ *,
220
+ depth_bucket_sizes: list[int] | None = None,
221
+ ) -> None:
222
+ if (
223
+ self._frozen_branch_batch_sizes is not None
224
+ and branch_batch_size not in self._frozen_branch_batch_sizes
225
+ ):
226
+ raise RuntimeError(
227
+ "request branch batch size "
228
+ f"{branch_batch_size} is not declared by the frozen warmup config; "
229
+ f"expected one of {sorted(self._frozen_branch_batch_sizes)}"
230
+ )
231
+
232
+ backbone_graph = self._backbone_graphs.get(branch_batch_size)
233
+ if backbone_graph is None:
234
+ backbone_graph = BackboneGraph(
235
+ backbone_model=self.model.backbone_model,
236
+ lm_head=self.model.lm_head,
237
+ embed_tokens=self.model.backbone_model.embed_tokens,
238
+ config=self.model.config,
239
+ device=str(self.device),
240
+ dtype=self.dtype,
241
+ max_seq_len=self.config.max_seq_len,
242
+ guidance_scale=guidance_scale,
243
+ batch_size=branch_batch_size,
244
+ )
245
+ if self._fast_backbone_decode:
246
+ backbone_graph.capture()
247
+ else:
248
+ backbone_graph.prepare_eager()
249
+ self._backbone_graphs[branch_batch_size] = backbone_graph
250
+ else:
251
+ backbone_graph.guidance_scale.fill_(guidance_scale)
252
+ self._backbone_graph = backbone_graph
253
+ self._backbone_prefill_graph = self._backbone_prefill_graphs.get(
254
+ branch_batch_size
255
+ )
256
+
257
+ if self._depth_decoder_graph is None:
258
+ depth_gen = self.model.depth_decoder.generation_config
259
+ depth_params = self._sampling_params(depth_gen)
260
+ depth_bucket_sizes = depth_bucket_sizes or [1, 2]
261
+ self._depth_decoder_graph = DepthDecoderGraph(
262
+ depth_decoder=self.model.depth_decoder,
263
+ config=self.model.config.depth_decoder_config,
264
+ device=str(self.device),
265
+ dtype=self.dtype,
266
+ guidance_scale=guidance_scale,
267
+ num_codebooks=self.model.config.num_codebooks,
268
+ codec_codebook_size=self._codec_codebook_size,
269
+ fast=self._fast_depth_decoder,
270
+ batch_size=depth_bucket_sizes[0],
271
+ bucket_sizes=depth_bucket_sizes,
272
+ **depth_params,
273
+ )
274
+ if self._fast_depth_decoder:
275
+ self._depth_decoder_graph.capture()
276
+ else:
277
+ self._depth_decoder_graph.prepare_eager()
278
+ else:
279
+ self._depth_decoder_graph.set_guidance_scale(guidance_scale)
280
+
281
+ def _codec(self):
282
+ if self._codec_runtime is not None:
283
+ return self._codec_runtime
284
+ from .stream_runtime import MultiRequestStreamRuntime, QwenStreamRuntimeConfig
285
+
286
+ if getattr(self.audio_tokenizer, "model", None) is None:
287
+ raise RuntimeError(
288
+ "audio_tokenizer.model is required for fast streaming codec decode"
289
+ )
290
+ try:
291
+ codec_dtype = next(self.audio_tokenizer.model.parameters()).dtype
292
+ except StopIteration:
293
+ codec_dtype = torch.float32
294
+ runtime_config = QwenStreamRuntimeConfig(
295
+ chunk_frames=self._codec_chunk_frames,
296
+ num_lanes=1,
297
+ max_active_reqs=1,
298
+ fast=self._fast_codec,
299
+ device=self.device,
300
+ dtype=codec_dtype,
301
+ )
302
+ self._codec_runtime = MultiRequestStreamRuntime(
303
+ self.audio_tokenizer, runtime_config
304
+ )
305
+ return self._codec_runtime
306
+
307
+ def _merge_branch(
308
+ self,
309
+ *,
310
+ input_ids: torch.Tensor,
311
+ attention_mask: torch.Tensor,
312
+ text_ids_mask: torch.Tensor,
313
+ text_ids_len: torch.Tensor,
314
+ input_values: torch.Tensor | None,
315
+ ) -> tuple[torch.Tensor, torch.Tensor]:
316
+ merged = self.model._merge_input_ids_with_input_values(
317
+ input_ids=input_ids,
318
+ attention_mask=attention_mask,
319
+ text_ids_mask=text_ids_mask,
320
+ text_ids_len=text_ids_len,
321
+ input_values=input_values,
322
+ )
323
+ return merged["inputs_embeds"], attention_mask
324
+
325
+ def _merge_cfg_branches(
326
+ self, inputs: dict[str, Any]
327
+ ) -> tuple[torch.Tensor, torch.Tensor] | None:
328
+ """Merge cond/uncond together so their text segments use one batched graph."""
329
+ cond_values = inputs.get("input_values")
330
+ uncond_values = inputs.get("cfg_negative_input_values")
331
+ if (cond_values is None) != (uncond_values is None):
332
+ return None
333
+ if cond_values is not None and tuple(cond_values.shape[1:]) != tuple(
334
+ uncond_values.shape[1:]
335
+ ):
336
+ return None
337
+
338
+ cond_ids = inputs["input_ids"]
339
+ uncond_ids = inputs["cfg_negative_prompt_ids"]
340
+ if cond_ids.shape[0] != 1 or uncond_ids.shape[0] != 1:
341
+ return None
342
+ max_len = max(cond_ids.shape[1], uncond_ids.shape[1])
343
+ input_ids = torch.cat(
344
+ [
345
+ _left_pad_tensor(cond_ids, max_len, 0),
346
+ _left_pad_tensor(uncond_ids, max_len, 0),
347
+ ],
348
+ dim=0,
349
+ )
350
+ attention_mask = torch.cat(
351
+ [
352
+ _left_pad_tensor(inputs["attention_mask"], max_len, 0),
353
+ _left_pad_tensor(
354
+ inputs["cfg_negative_prompt_attention_mask"], max_len, 0
355
+ ),
356
+ ],
357
+ dim=0,
358
+ )
359
+ text_ids_mask = torch.cat(
360
+ [
361
+ _left_pad_tensor(inputs["text_ids_mask"], max_len, False),
362
+ _left_pad_tensor(inputs["cfg_negative_text_ids_mask"], max_len, False),
363
+ ],
364
+ dim=0,
365
+ )
366
+ text_ids_len = torch.cat(
367
+ [inputs["text_ids_len"], inputs["cfg_negative_text_ids_len"]], dim=0
368
+ )
369
+ input_values = (
370
+ None
371
+ if cond_values is None
372
+ else torch.cat([cond_values, uncond_values], dim=0)
373
+ )
374
+ merged = self.model._merge_input_ids_with_input_values(
375
+ input_ids=input_ids,
376
+ attention_mask=attention_mask,
377
+ text_ids_mask=text_ids_mask,
378
+ text_ids_len=text_ids_len,
379
+ input_values=input_values,
380
+ )
381
+ return merged["inputs_embeds"].contiguous(), attention_mask.contiguous()
382
+
383
+ def _build_branch_batch(self, inputs: dict[str, Any]) -> _BranchBatch:
384
+ cfg = select_fast_cfg(inputs)
385
+ if cfg.use_negative_as_main:
386
+ embeds, mask = self._merge_branch(
387
+ input_ids=inputs["cfg_negative_prompt_ids"],
388
+ attention_mask=inputs["cfg_negative_prompt_attention_mask"],
389
+ text_ids_mask=inputs["cfg_negative_text_ids_mask"],
390
+ text_ids_len=inputs["cfg_negative_text_ids_len"],
391
+ input_values=inputs.get("cfg_negative_input_values"),
392
+ )
393
+ return _BranchBatch(embeds.contiguous(), mask.contiguous(), 1, cfg)
394
+
395
+ if cfg.mode == "no_cfg":
396
+ cond_embeds, cond_mask = self._merge_branch(
397
+ input_ids=inputs["input_ids"],
398
+ attention_mask=inputs["attention_mask"],
399
+ text_ids_mask=inputs["text_ids_mask"],
400
+ text_ids_len=inputs["text_ids_len"],
401
+ input_values=inputs.get("input_values"),
402
+ )
403
+ return _BranchBatch(
404
+ cond_embeds.contiguous(), cond_mask.contiguous(), 1, cfg
405
+ )
406
+
407
+ joint = self._merge_cfg_branches(inputs) if self._fast_text_encoder else None
408
+ if joint is not None:
409
+ inputs_embeds, attention_mask = joint
410
+ return _BranchBatch(inputs_embeds, attention_mask, 2, cfg)
411
+
412
+ cond_embeds, cond_mask = self._merge_branch(
413
+ input_ids=inputs["input_ids"],
414
+ attention_mask=inputs["attention_mask"],
415
+ text_ids_mask=inputs["text_ids_mask"],
416
+ text_ids_len=inputs["text_ids_len"],
417
+ input_values=inputs.get("input_values"),
418
+ )
419
+ uncond_embeds, uncond_mask = self._merge_branch(
420
+ input_ids=inputs["cfg_negative_prompt_ids"],
421
+ attention_mask=inputs["cfg_negative_prompt_attention_mask"],
422
+ text_ids_mask=inputs["cfg_negative_text_ids_mask"],
423
+ text_ids_len=inputs["cfg_negative_text_ids_len"],
424
+ input_values=inputs.get("cfg_negative_input_values"),
425
+ )
426
+ max_len = max(cond_embeds.shape[1], uncond_embeds.shape[1])
427
+ inputs_embeds = torch.cat(
428
+ [
429
+ _left_pad_tensor(cond_embeds, max_len, 0),
430
+ _left_pad_tensor(uncond_embeds, max_len, 0),
431
+ ],
432
+ dim=0,
433
+ ).contiguous()
434
+ attention_mask = torch.cat(
435
+ [
436
+ _left_pad_tensor(cond_mask, max_len, 0),
437
+ _left_pad_tensor(uncond_mask, max_len, 0),
438
+ ],
439
+ dim=0,
440
+ ).contiguous()
441
+ return _BranchBatch(inputs_embeds, attention_mask, 2, cfg)
442
+
443
+ def _decode_codec_frames(
444
+ self,
445
+ *,
446
+ frames: list[torch.Tensor],
447
+ request_id: str,
448
+ reset: bool,
449
+ is_final: bool,
450
+ timing: dict[str, float | int | bool],
451
+ ) -> FastStreamingChunk:
452
+ frame_tensor = torch.stack(frames).to(self.device, dtype=torch.long)
453
+ codes = frame_tensor.transpose(0, 1).unsqueeze(0).contiguous()
454
+ codec_started = time.perf_counter()
455
+ audio = self._codec().decode_request_chunk(request_id, codes, reset=reset)
456
+ codec_launch_ms = (time.perf_counter() - codec_started) * 1000.0
457
+ d2h_started = time.perf_counter()
458
+ audio_np = _extract_audio_np(audio)
459
+ d2h_ms = (time.perf_counter() - d2h_started) * 1000.0
460
+ timing = {
461
+ **timing,
462
+ "codec_launch_ms": codec_launch_ms,
463
+ "audio_d2h_ms": d2h_ms,
464
+ }
465
+ return FastStreamingChunk(
466
+ audio=audio_np,
467
+ sample_rate=self.sample_rate,
468
+ codec_frames=len(frames),
469
+ is_final=is_final,
470
+ timing=timing,
471
+ )
472
+
473
+ @property
474
+ def fast_enabled(self) -> bool:
475
+ return any(
476
+ (
477
+ self._fast_text_encoder,
478
+ self._fast_backbone_prefill,
479
+ self._fast_backbone_decode,
480
+ self._fast_depth_decoder,
481
+ self._fast_codec,
482
+ )
483
+ )
484
+
485
+ @property
486
+ def codec_chunk_frames(self) -> int:
487
+ return self._codec_chunk_frames
488
+
489
+ @torch.inference_mode()
490
+ def warmup_from_profile(
491
+ self,
492
+ profile: FastStreamingWarmupProfile,
493
+ *,
494
+ manifest_path: str | Path | None = None,
495
+ ) -> dict[str, Any]:
496
+ """Deterministically create every graph declared by a serving profile."""
497
+ if self._warmup_manifest is not None:
498
+ raise RuntimeError("runtime has already completed profile warmup")
499
+ if self.tokenizer is None:
500
+ raise ValueError(
501
+ "profile warmup requires FastBreezeStreamingRuntime(tokenizer=...)"
502
+ )
503
+ if profile.codec_chunk_frames != self._codec_chunk_frames:
504
+ raise ValueError(
505
+ f"profile codec chunk_frames={profile.codec_chunk_frames} does not match "
506
+ f"runtime chunk_frames={self._codec_chunk_frames}"
507
+ )
508
+ if profile.codec_num_lanes != 1:
509
+ raise ValueError("fast streaming runtime supports exactly one codec lane")
510
+ if any(
511
+ graph.sequence_length > self.config.max_seq_len
512
+ for graph in profile.backbone_prefill_graphs
513
+ ):
514
+ raise ValueError(
515
+ "profile prefill sequence_length exceeds runtime max_seq_len"
516
+ )
517
+
518
+ total_started = time.perf_counter()
519
+ stages: dict[str, Any] = {}
520
+
521
+ graph_started = time.perf_counter()
522
+ cfg_scale_by_batch = {
523
+ 1 if cfg_scale == 1.0 else 2: cfg_scale for cfg_scale in profile.cfg_scales
524
+ }
525
+ for branch_batch_size in profile.backbone_decode_branch_batch_sizes:
526
+ self._ensure_graphs(
527
+ branch_batch_size,
528
+ cfg_scale_by_batch[branch_batch_size],
529
+ depth_bucket_sizes=list(profile.depth_decoder_batch_sizes),
530
+ )
531
+ assert self._depth_decoder_graph is not None
532
+ torch.cuda.synchronize(self.device)
533
+ graph_elapsed_ms = (time.perf_counter() - graph_started) * 1000.0
534
+ stages["backbone_decode"] = {
535
+ "fast": self._fast_backbone_decode,
536
+ "graphs": [
537
+ {"branch_batch_size": batch_size}
538
+ for batch_size in sorted(self._backbone_graphs)
539
+ ],
540
+ "elapsed_ms_with_depth_decoder": graph_elapsed_ms,
541
+ }
542
+ stages["depth_decoder"] = {
543
+ "fast": self._fast_depth_decoder,
544
+ "graphs": [
545
+ {"batch_size": int(batch_size)}
546
+ for batch_size in sorted(self._depth_decoder_graph._bucket_graphs)
547
+ ],
548
+ "elapsed_ms_with_backbone_decode": graph_elapsed_ms,
549
+ }
550
+
551
+ sampling_started = time.perf_counter()
552
+ backbone_params = self._sampling_params(self.model.generation_config)
553
+ sampling_logits = torch.zeros(
554
+ 1,
555
+ int(self.model.config.vocab_size) + 1,
556
+ dtype=torch.float32,
557
+ device=self.device,
558
+ )
559
+ for _ in range(2):
560
+ sample_logits(
561
+ sampling_logits,
562
+ suppress_tokens=self._reserved_codec_token_ids,
563
+ **backbone_params,
564
+ )
565
+ torch.cuda.synchronize(self.device)
566
+ stages["backbone_decode"]["sampling_warmup_ms"] = (
567
+ time.perf_counter() - sampling_started
568
+ ) * 1000.0
569
+
570
+ text_started = time.perf_counter()
571
+ text_graph_keys: tuple[tuple[int, int], ...] = ()
572
+ if self._fast_text_encoder:
573
+ from .text_encoder_graph import TextEncoderGraphCache
574
+
575
+ text_cache = getattr(self.model, "_fast_text_encoder_graph_cache", None)
576
+ if text_cache is None:
577
+ text_cache = TextEncoderGraphCache(
578
+ self.model.text_encoder, token_granularity=32
579
+ )
580
+ self.model._fast_text_encoder_graph_cache = text_cache
581
+ for graph in profile.text_encoder_graphs:
582
+ text_cache.warmup_graph(
583
+ batch_size=graph.batch_size,
584
+ token_length=graph.token_length,
585
+ device=self.device,
586
+ )
587
+ text_graph_keys = text_cache.graph_keys
588
+ if profile.freeze_after_warmup:
589
+ text_cache.freeze()
590
+ torch.cuda.synchronize(self.device)
591
+ stages["text_encoder"] = {
592
+ "fast": self._fast_text_encoder,
593
+ "graphs": [
594
+ {"batch_size": batch_size, "token_length": token_length}
595
+ for batch_size, token_length in text_graph_keys
596
+ ],
597
+ "elapsed_ms": (time.perf_counter() - text_started) * 1000.0,
598
+ }
599
+
600
+ prefill_started = time.perf_counter()
601
+ prefill_graph_keys: list[tuple[int, int]] = []
602
+ if self._fast_backbone_prefill:
603
+ for branch_batch_size in profile.backbone_decode_branch_batch_sizes:
604
+ backbone_graph = self._backbone_graphs[branch_batch_size]
605
+ prefill_cache = BackbonePrefillGraphCache(
606
+ backbone_graph, token_granularity=32
607
+ )
608
+ for graph in profile.backbone_prefill_graphs:
609
+ if graph.branch_batch_size == branch_batch_size:
610
+ prefill_cache.warmup_graph(
611
+ branch_batch_size=graph.branch_batch_size,
612
+ sequence_length=graph.sequence_length,
613
+ )
614
+ if profile.freeze_after_warmup:
615
+ prefill_cache.freeze()
616
+ self._backbone_prefill_graphs[branch_batch_size] = prefill_cache
617
+ prefill_graph_keys.extend(prefill_cache.graph_keys)
618
+ if self._backbone_graph is not None:
619
+ self._backbone_prefill_graph = self._backbone_prefill_graphs.get(
620
+ self._backbone_graph.batch_size
621
+ )
622
+ torch.cuda.synchronize(self.device)
623
+ stages["backbone_prefill"] = {
624
+ "fast": self._fast_backbone_prefill,
625
+ "graphs": [
626
+ {"branch_batch_size": batch_size, "sequence_length": sequence_length}
627
+ for batch_size, sequence_length in prefill_graph_keys
628
+ ],
629
+ "elapsed_ms": (time.perf_counter() - prefill_started) * 1000.0,
630
+ }
631
+
632
+ codec_started = time.perf_counter()
633
+ codec = self._codec()
634
+ codec_request_id = "profile-codec-warmup"
635
+ codec.open_request(codec_request_id, reset=True, is_first_decode=True)
636
+ codec_codes = torch.zeros(
637
+ 1,
638
+ int(self.model.config.num_codebooks),
639
+ self._codec_chunk_frames,
640
+ dtype=torch.long,
641
+ device=self.device,
642
+ )
643
+ codec_audio = codec.decode_request_chunk(
644
+ codec_request_id,
645
+ codec_codes,
646
+ reset=True,
647
+ )
648
+ _extract_audio_np(codec_audio)
649
+ codec.close_request(codec_request_id)
650
+ torch.cuda.synchronize(self.device)
651
+ stages["codec"] = {
652
+ "fast": self._fast_codec,
653
+ "graphs": [
654
+ {
655
+ "num_lanes": len(codec.lanes),
656
+ "chunk_frames": self._codec_chunk_frames,
657
+ "captured": all(lane.cuda_graph is not None for lane in codec.lanes)
658
+ if self._fast_codec
659
+ else False,
660
+ }
661
+ ],
662
+ "elapsed_ms": (time.perf_counter() - codec_started) * 1000.0,
663
+ }
664
+
665
+ if profile.freeze_after_warmup:
666
+ self._frozen_branch_batch_sizes = frozenset(
667
+ profile.backbone_decode_branch_batch_sizes
668
+ )
669
+ self._warmup_profile = profile
670
+
671
+ from breeze_infer.templates import get_template, prepare_inputs
672
+
673
+ request_spec = profile.warmup_request
674
+ synthetic_results: list[dict[str, Any]] = []
675
+ for cfg_scale in profile.cfg_scales:
676
+ synthetic_started = time.perf_counter()
677
+ request_id = f"profile-first-frame-cfg{cfg_scale:g}"
678
+ torch.manual_seed(request_spec.seed)
679
+ torch.cuda.manual_seed_all(request_spec.seed)
680
+ synthetic_inputs = prepare_inputs(
681
+ self.tokenizer,
682
+ self.audio_tokenizer,
683
+ self.model,
684
+ [
685
+ {
686
+ "id": request_id,
687
+ "text": request_spec.text,
688
+ "instruction": request_spec.instruction,
689
+ "speaker": request_spec.speaker,
690
+ }
691
+ ],
692
+ get_template(request_spec.template),
693
+ guidance_scale=cfg_scale,
694
+ guidance_scale_ref=None,
695
+ guidance_scale_ins=None,
696
+ )
697
+ synthetic_iterator = self.iter_audio_chunks(
698
+ synthetic_inputs, request_id=request_id
699
+ )
700
+ try:
701
+ try:
702
+ synthetic_chunk = next(synthetic_iterator)
703
+ except StopIteration as exc:
704
+ raise RuntimeError(
705
+ "warmup request completed without producing an audio frame"
706
+ ) from exc
707
+ finally:
708
+ synthetic_iterator.close()
709
+ torch.cuda.synchronize(self.device)
710
+ synthetic_results.append(
711
+ {
712
+ "template": request_spec.template,
713
+ "cfg_scale": cfg_scale,
714
+ "cfg_mode": "no_cfg" if cfg_scale == 1.0 else "single_cfg",
715
+ "audio_samples": int(synthetic_chunk.audio.size),
716
+ "ttfa_internal_ms": synthetic_chunk.timing.get("ttfa_internal_ms"),
717
+ "elapsed_ms": (time.perf_counter() - synthetic_started) * 1000.0,
718
+ }
719
+ )
720
+ stages["synthetic_first_frames"] = synthetic_results
721
+
722
+ manifest = {
723
+ "schema_version": 1,
724
+ "status": "ready",
725
+ "profile_source": profile.source,
726
+ "profile": profile.to_dict(),
727
+ "stages": stages,
728
+ "frozen": profile.freeze_after_warmup,
729
+ "total_elapsed_ms": (time.perf_counter() - total_started) * 1000.0,
730
+ }
731
+ self._warmup_manifest = manifest
732
+ if manifest_path is not None:
733
+ path = Path(manifest_path)
734
+ path.parent.mkdir(parents=True, exist_ok=True)
735
+ path.write_text(
736
+ json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
737
+ encoding="utf-8",
738
+ )
739
+ return manifest
740
+
741
+ @torch.inference_mode()
742
+ def iter_audio_chunks(
743
+ self,
744
+ inputs: dict[str, Any],
745
+ *,
746
+ request_id: str | None = None,
747
+ ) -> Iterator[FastStreamingChunk]:
748
+ cfg = select_fast_cfg(inputs)
749
+ branch_batch_size = 2 if cfg.mode == "single_cfg" else 1
750
+ self._ensure_graphs(branch_batch_size, cfg.guidance_scale)
751
+ assert self._backbone_graph is not None
752
+ assert self._depth_decoder_graph is not None
753
+
754
+ codec = self._codec()
755
+ branch = self._build_branch_batch(inputs)
756
+ request_id = request_id or f"local-{uuid.uuid4().hex}"
757
+ codec.open_request(request_id, reset=True, is_first_decode=True)
758
+
759
+ backbone_params = self._sampling_params(self.model.generation_config)
760
+ depth_params = self._sampling_params(self.model.depth_decoder.generation_config)
761
+ chunk_buffer: list[torch.Tensor] = []
762
+ chunk_index = 0
763
+ total_frames = 0
764
+ backbone_token_history = torch.empty(
765
+ self.config.max_new_tokens,
766
+ dtype=torch.long,
767
+ device=self.device,
768
+ )
769
+ first_decode = True
770
+ t_start = time.perf_counter()
771
+ t_chunk = t_start
772
+ prefill_start_event = None
773
+ prefill_end_event = None
774
+ if self.config.collect_timing:
775
+ prefill_start_event = torch.cuda.Event(enable_timing=True)
776
+ prefill_end_event = torch.cuda.Event(enable_timing=True)
777
+ prefill_start_event.record()
778
+
779
+ try:
780
+ attention_mask = branch.attention_mask
781
+ if self._fast_backbone_prefill:
782
+ if self._backbone_prefill_graph is None:
783
+ self._backbone_prefill_graph = BackbonePrefillGraphCache(
784
+ self._backbone_graph, token_granularity=32
785
+ )
786
+ prefill_output = self._backbone_prefill_graph(
787
+ branch.inputs_embeds, attention_mask
788
+ )
789
+ hidden = prefill_output.hidden_states
790
+ logits = prefill_output.logits
791
+ prefill_len = prefill_output.prefill_len
792
+ generation_attention_mask = prefill_output.attention_mask
793
+ else:
794
+ position_ids = attention_mask.long().cumsum(-1) - 1
795
+ position_ids.masked_fill_(attention_mask == 0, 1)
796
+ prefill_cache = None
797
+ cache_position = None
798
+ backbone_out = self.model.backbone_model(
799
+ inputs_embeds=branch.inputs_embeds,
800
+ attention_mask=attention_mask,
801
+ position_ids=position_ids,
802
+ past_key_values=prefill_cache,
803
+ cache_position=cache_position,
804
+ use_cache=True,
805
+ )
806
+ hidden = backbone_out.last_hidden_state
807
+ logits = self.model.lm_head(hidden[:, -1, :].float()).float()
808
+ prefill_len = self._backbone_graph.prefill_kv(
809
+ backbone_out.past_key_values
810
+ )
811
+ generation_attention_mask = attention_mask
812
+
813
+ if branch.branch_batch_size == 2:
814
+ cond_logits = logits[:1]
815
+ uncond_logits = logits[1:]
816
+ logits = uncond_logits + branch.cfg.guidance_scale * (
817
+ cond_logits - uncond_logits
818
+ )
819
+ else:
820
+ logits = logits[:1]
821
+ token = sample_logits(
822
+ logits,
823
+ suppress_tokens=self._reserved_codec_token_ids,
824
+ **backbone_params,
825
+ ).view(1)
826
+ self._backbone_graph.set_generation_state(generation_attention_mask)
827
+ if prefill_end_event is not None:
828
+ prefill_end_event.record()
829
+
830
+ for step_idx in range(self.config.max_new_tokens):
831
+ if is_backbone_eos_token(token, self.model.config):
832
+ break
833
+
834
+ if prefill_len + step_idx >= self.config.max_seq_len - 1:
835
+ break
836
+
837
+ if branch.branch_batch_size == 2:
838
+ token_batch = token.repeat(2)
839
+ depth_hidden = hidden[:, -1, :]
840
+ else:
841
+ token_batch = token
842
+ depth_hidden = hidden[:1, -1, :]
843
+
844
+ depth_tokens = self._depth_decoder_graph.run(
845
+ depth_hidden,
846
+ token_batch,
847
+ guidance_scale=branch.cfg.guidance_scale,
848
+ **depth_params,
849
+ )
850
+ frame = torch.cat([token.view(1), depth_tokens[0]], dim=0)
851
+ if should_decode_codec_frame(frame, self.model.config):
852
+ chunk_buffer.append(frame.detach())
853
+
854
+ # A complete codec frame can be decoded immediately. Emit it
855
+ # before computing the next backbone token so that one full
856
+ # backbone decode step is no longer on the TTFA critical path.
857
+ reached_limit = step_idx == self.config.max_new_tokens - 1
858
+ chunk_ready = len(chunk_buffer) >= self._codec_chunk_frames
859
+ if chunk_ready or reached_limit:
860
+ if chunk_buffer:
861
+ decode_started = time.perf_counter()
862
+ frames = chunk_buffer
863
+ chunk_buffer = []
864
+ total_frames += len(frames)
865
+ timing: dict[str, float | int | bool] = {
866
+ "chunk_index": chunk_index,
867
+ "codec_frames": len(frames),
868
+ "decode_launch_ms": (decode_started - t_chunk) * 1000.0,
869
+ "total_frames": total_frames,
870
+ "is_final": reached_limit,
871
+ }
872
+ chunk = self._decode_codec_frames(
873
+ frames=frames,
874
+ request_id=request_id,
875
+ reset=first_decode,
876
+ is_final=reached_limit,
877
+ timing=timing,
878
+ )
879
+ if chunk_index == 0:
880
+ first_timing = {
881
+ **chunk.timing,
882
+ "ttfa_internal_ms": (time.perf_counter() - t_start)
883
+ * 1000.0,
884
+ }
885
+ if (
886
+ prefill_start_event is not None
887
+ and prefill_end_event is not None
888
+ ):
889
+ first_timing["prefill_gpu_ms"] = (
890
+ prefill_start_event.elapsed_time(prefill_end_event)
891
+ )
892
+ chunk = FastStreamingChunk(
893
+ audio=chunk.audio,
894
+ sample_rate=chunk.sample_rate,
895
+ codec_frames=chunk.codec_frames,
896
+ is_final=chunk.is_final,
897
+ timing=first_timing,
898
+ )
899
+ yield chunk
900
+ first_decode = False
901
+ chunk_index += 1
902
+ t_chunk = time.perf_counter()
903
+ if reached_limit:
904
+ break
905
+
906
+ frame_for_backbone = frame.view(1, 1, -1)
907
+ if branch.branch_batch_size == 2:
908
+ frame_for_backbone = frame_for_backbone.repeat(2, 1, 1)
909
+
910
+ hidden, logits = self._backbone_graph.run(
911
+ frame_for_backbone, step_idx=step_idx
912
+ )
913
+ logits = logits.float()
914
+ backbone_token_history[step_idx] = token[0]
915
+ token = sample_logits(
916
+ logits,
917
+ token_history=backbone_token_history[: step_idx + 1],
918
+ repetition_penalty=self.config.repetition_penalty,
919
+ suppress_tokens=self._reserved_codec_token_ids,
920
+ **backbone_params,
921
+ ).view(1)
922
+
923
+ if is_backbone_eos_token(token, self.model.config):
924
+ break
925
+
926
+ if chunk_buffer:
927
+ frames = chunk_buffer
928
+ total_frames += len(frames)
929
+ yield self._decode_codec_frames(
930
+ frames=frames,
931
+ request_id=request_id,
932
+ reset=first_decode,
933
+ is_final=True,
934
+ timing={
935
+ "chunk_index": chunk_index,
936
+ "codec_frames": len(frames),
937
+ "decode_launch_ms": (time.perf_counter() - t_chunk) * 1000.0,
938
+ "total_frames": total_frames,
939
+ "is_final": True,
940
+ },
941
+ )
942
+ finally:
943
+ codec.close_request(request_id)
models/generation_breeze.py ADDED
@@ -0,0 +1,1352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 The HuggingFace Inc. team. All rights reserved.
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
+ import os
16
+ from dataclasses import dataclass
17
+ from typing import TYPE_CHECKING, Any, Optional
18
+
19
+ import numpy as np
20
+ import torch
21
+ from torch import nn
22
+ from transformers.generation import (
23
+ GenerateDecoderOnlyOutput,
24
+ GenerationConfig,
25
+ GenerationMixin,
26
+ GenerationMode,
27
+ )
28
+ from transformers.generation.logits_process import LogitsProcessorList
29
+ from transformers.generation.stopping_criteria import (
30
+ MaxLengthCriteria,
31
+ StoppingCriteriaList,
32
+ )
33
+ from transformers.generation.utils import GenerateNonBeamOutput
34
+ from transformers.utils import logging
35
+
36
+ from .logits_process import mask_invalid_codec_token_logits
37
+
38
+ if TYPE_CHECKING:
39
+ from transformers.generation.streamers import BaseStreamer
40
+
41
+
42
+ logger = logging.get_logger(__name__)
43
+
44
+
45
+ def _extract_decoded_audio_tensor(decoded_audio: Any) -> torch.Tensor:
46
+ if hasattr(decoded_audio, "audio_values"):
47
+ decoded_audio = decoded_audio.audio_values
48
+
49
+ while isinstance(decoded_audio, (list, tuple)):
50
+ decoded_audio = decoded_audio[0]
51
+
52
+ if isinstance(decoded_audio, np.ndarray):
53
+ decoded_audio = torch.as_tensor(decoded_audio)
54
+
55
+ if not isinstance(decoded_audio, torch.Tensor):
56
+ raise TypeError(f"Unsupported decoded audio type: {type(decoded_audio)!r}")
57
+
58
+ while decoded_audio.dim() > 1:
59
+ decoded_audio = decoded_audio[0]
60
+
61
+ return decoded_audio
62
+
63
+
64
+ @dataclass
65
+ class BreezeGenerateOutput(GenerateDecoderOnlyOutput):
66
+ """
67
+ Outputs of BreezeForConditionalGeneration.generate.
68
+
69
+ Args:
70
+ sequences (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
71
+ The generated sequences. The second dimension (sequence_length) is either equal to `max_length` or shorter
72
+ if all batches finished early due to the `eos_token_id`.
73
+ scores (`tuple(torch.FloatTensor)` *optional*, returned when `output_scores=True`):
74
+ Processed prediction scores of the language modeling head (scores for each vocabulary token before SoftMax)
75
+ at each generation step. Tuple of `torch.FloatTensor` with up to `max_new_tokens` elements (one element for
76
+ each generated token), with each tensor of shape `(batch_size, config.vocab_size)`.
77
+ logits (`tuple(torch.FloatTensor)` *optional*, returned when `output_logits=True`):
78
+ Unprocessed prediction scores of the language modeling head (scores for each vocabulary token before SoftMax)
79
+ at each generation step. Tuple of `torch.FloatTensor` with up to `max_new_tokens` elements (one element for
80
+ each generated token), with each tensor of shape `(batch_size, config.vocab_size)`.
81
+ attentions (`tuple(tuple(torch.FloatTensor))`, *optional*, returned when `output_attentions=True`):
82
+ Tuple (one element for each generated token) of tuples (one element for each layer of the decoder) of
83
+ `torch.FloatTensor` of shape `(batch_size, num_heads, generated_length, sequence_length)`.
84
+ hidden_states (`tuple(tuple(torch.FloatTensor))`, *optional*, returned when `output_hidden_states=True`):
85
+ Tuple (one element for each generated token) of tuples (one element for each layer of the decoder) of
86
+ `torch.FloatTensor` of shape `(batch_size, generated_length, hidden_size)`.
87
+ past_key_values (`Cache`, *optional*, returned when `use_cache=True`):
88
+ Returns the model cache, used to speed up decoding. Different models have a different cache format, check
89
+ audio (`list(torch.FloatTensor)` of length `batch_size`):
90
+ The generated audio.
91
+ """
92
+
93
+ audio: list[torch.Tensor] | None = None
94
+
95
+
96
+ class BreezeGenerationMixin(GenerationMixin):
97
+ # CFG kwargs that should bypass validation (Breeze requires custom CFG implementation
98
+ # because transformers' UnbatchedClassifierFreeGuidanceLogitsProcessor doesn't pass
99
+ # text_ids_mask and text_ids_len which Breeze model requires)
100
+ # Note: we use 'cfg_*' names to avoid triggering transformers' built-in CFG handling
101
+ _cfg_kwargs = {
102
+ "cfg_scale",
103
+ "cfg_negative_prompt_ids",
104
+ "cfg_negative_prompt_attention_mask",
105
+ "cfg_negative_text_ids_mask",
106
+ "cfg_negative_text_ids_len",
107
+ "cfg_negative_input_values",
108
+ # Dual CFG kwargs
109
+ "cfg_scale_ref",
110
+ "cfg_scale_ins",
111
+ "cfg_uncond_prompt_ids",
112
+ "cfg_uncond_prompt_attention_mask",
113
+ "cfg_uncond_text_ids_mask",
114
+ "cfg_uncond_text_ids_len",
115
+ "cfg_ref_prompt_ids",
116
+ "cfg_ref_prompt_attention_mask",
117
+ "cfg_ref_text_ids_mask",
118
+ "cfg_ref_text_ids_len",
119
+ "cfg_ins_prompt_ids",
120
+ "cfg_ins_prompt_attention_mask",
121
+ "cfg_ins_text_ids_mask",
122
+ "cfg_ins_text_ids_len",
123
+ }
124
+
125
+ def _mask_reserved_codec_logits(self, scores: torch.Tensor) -> torch.Tensor:
126
+ return mask_invalid_codec_token_logits(
127
+ scores,
128
+ codebook_size=int(self.config.codec_config.codebook_size),
129
+ token_vocab_size=int(self.config.vocab_size),
130
+ )
131
+
132
+ def _reserved_codec_token_ids(self) -> list[int]:
133
+ return list(
134
+ range(
135
+ int(self.config.codec_config.codebook_size),
136
+ int(self.config.vocab_size),
137
+ )
138
+ )
139
+
140
+ def _validate_model_kwargs(self, model_kwargs: dict[str, Any]):
141
+ """Override to allow CFG-related kwargs."""
142
+ cfg_kwargs = {
143
+ k: model_kwargs.pop(k)
144
+ for k in list(model_kwargs.keys())
145
+ if k in self._cfg_kwargs
146
+ }
147
+ super()._validate_model_kwargs(model_kwargs)
148
+ model_kwargs.update(cfg_kwargs)
149
+
150
+ def _depth_decoder_generate_with_cfg(
151
+ self,
152
+ depth_decoder_input_ids: torch.LongTensor,
153
+ cond_backbone_hidden_state: torch.FloatTensor,
154
+ uncond_backbone_hidden_state: torch.FloatTensor,
155
+ cfg_scale: float,
156
+ ) -> torch.LongTensor:
157
+ """
158
+ Custom depth decoder generation with CFG.
159
+
160
+ At each step, we run the depth decoder twice (cond and uncond) and apply CFG to the logits.
161
+ """
162
+ depth_decoder = self.depth_decoder
163
+ generation_config = depth_decoder.generation_config
164
+ num_codebooks = self.config.num_codebooks
165
+
166
+ # Get generation params from depth decoder config
167
+ do_sample = generation_config.do_sample
168
+ temperature = generation_config.temperature
169
+ top_p = generation_config.top_p
170
+ top_k = generation_config.top_k
171
+
172
+ batch_size = depth_decoder_input_ids.shape[0]
173
+ device = depth_decoder_input_ids.device
174
+
175
+ # Initialize sequences with input_ids (which contains placeholder + token0)
176
+ sequences = depth_decoder_input_ids # [batch_size, 2]
177
+
178
+ # Generate tokens 1 to num_codebooks-1 (31 tokens for 32 codebooks)
179
+ for step in range(num_codebooks - 1):
180
+ # Cond forward pass
181
+ cond_outputs = depth_decoder(
182
+ input_ids=sequences,
183
+ backbone_last_hidden_state=cond_backbone_hidden_state,
184
+ use_cache=False,
185
+ return_dict=True,
186
+ )
187
+ cond_logits = cond_outputs.logits[:, -1, :].float()
188
+
189
+ # Uncond forward pass
190
+ uncond_outputs = depth_decoder(
191
+ input_ids=sequences,
192
+ backbone_last_hidden_state=uncond_backbone_hidden_state,
193
+ use_cache=False,
194
+ return_dict=True,
195
+ )
196
+ uncond_logits = uncond_outputs.logits[:, -1, :].float()
197
+
198
+ # Apply CFG
199
+ next_token_logits = uncond_logits + cfg_scale * (
200
+ cond_logits - uncond_logits
201
+ )
202
+ self._mask_reserved_codec_logits(next_token_logits)
203
+
204
+ # Apply temperature
205
+ if temperature is not None and temperature != 1.0:
206
+ next_token_logits = next_token_logits / temperature
207
+
208
+ # Sample or argmax
209
+ if do_sample:
210
+ probs = nn.functional.softmax(next_token_logits, dim=-1)
211
+
212
+ # Apply top_k
213
+ if top_k is not None and top_k > 0:
214
+ top_k_probs, top_k_indices = torch.topk(
215
+ probs, min(top_k, probs.size(-1))
216
+ )
217
+ probs = torch.zeros_like(probs).scatter_(
218
+ -1, top_k_indices, top_k_probs
219
+ )
220
+ probs = probs / probs.sum(dim=-1, keepdim=True)
221
+
222
+ # Apply top_p
223
+ if top_p is not None and top_p < 1.0:
224
+ sorted_probs, sorted_indices = torch.sort(probs, descending=True)
225
+ cumulative_probs = torch.cumsum(sorted_probs, dim=-1)
226
+ sorted_indices_to_remove = cumulative_probs > top_p
227
+ sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[
228
+ ..., :-1
229
+ ].clone()
230
+ sorted_indices_to_remove[..., 0] = 0
231
+ indices_to_remove = sorted_indices_to_remove.scatter(
232
+ -1, sorted_indices, sorted_indices_to_remove
233
+ )
234
+ probs = probs.masked_fill(indices_to_remove, 0.0)
235
+ probs = probs / probs.sum(dim=-1, keepdim=True)
236
+
237
+ next_tokens = torch.multinomial(probs, num_samples=1)
238
+ else:
239
+ next_tokens = torch.argmax(next_token_logits, dim=-1, keepdim=True)
240
+
241
+ # Append to sequences
242
+ sequences = torch.cat([sequences, next_tokens], dim=-1)
243
+
244
+ return sequences
245
+
246
+ def _depth_decoder_generate_with_dual_cfg(
247
+ self,
248
+ depth_decoder_input_ids: torch.LongTensor,
249
+ uncond_backbone_hidden_state: torch.FloatTensor,
250
+ ref_backbone_hidden_state: torch.FloatTensor,
251
+ ins_backbone_hidden_state: torch.FloatTensor,
252
+ cfg_scale_ref: float,
253
+ cfg_scale_ins: float,
254
+ ) -> torch.LongTensor:
255
+ """
256
+ Custom depth decoder generation with dual CFG.
257
+
258
+ At each step, we run the depth decoder three times (uncond, ref, ins) and apply dual CFG to the logits:
259
+ logits = uncond + cfg_ref * (ref - uncond) + cfg_ins * (ins - uncond)
260
+ """
261
+ depth_decoder = self.depth_decoder
262
+ generation_config = depth_decoder.generation_config
263
+ num_codebooks = self.config.num_codebooks
264
+
265
+ do_sample = generation_config.do_sample
266
+ temperature = generation_config.temperature
267
+ top_p = generation_config.top_p
268
+ top_k = generation_config.top_k
269
+
270
+ batch_size = depth_decoder_input_ids.shape[0]
271
+ device = depth_decoder_input_ids.device
272
+
273
+ sequences = depth_decoder_input_ids # [batch_size, 2]
274
+
275
+ for step in range(num_codebooks - 1):
276
+ # Uncond forward pass
277
+ uncond_outputs = depth_decoder(
278
+ input_ids=sequences,
279
+ backbone_last_hidden_state=uncond_backbone_hidden_state,
280
+ use_cache=False,
281
+ return_dict=True,
282
+ )
283
+ uncond_logits = uncond_outputs.logits[:, -1, :].float()
284
+
285
+ # Ref-only forward pass
286
+ ref_outputs = depth_decoder(
287
+ input_ids=sequences,
288
+ backbone_last_hidden_state=ref_backbone_hidden_state,
289
+ use_cache=False,
290
+ return_dict=True,
291
+ )
292
+ ref_logits = ref_outputs.logits[:, -1, :].float()
293
+
294
+ # Ins-only forward pass
295
+ ins_outputs = depth_decoder(
296
+ input_ids=sequences,
297
+ backbone_last_hidden_state=ins_backbone_hidden_state,
298
+ use_cache=False,
299
+ return_dict=True,
300
+ )
301
+ ins_logits = ins_outputs.logits[:, -1, :].float()
302
+
303
+ # Apply dual CFG
304
+ next_token_logits = (
305
+ uncond_logits
306
+ + cfg_scale_ref * (ref_logits - uncond_logits)
307
+ + cfg_scale_ins * (ins_logits - uncond_logits)
308
+ )
309
+ self._mask_reserved_codec_logits(next_token_logits)
310
+
311
+ # Apply temperature
312
+ if temperature is not None and temperature != 1.0:
313
+ next_token_logits = next_token_logits / temperature
314
+
315
+ # Sample or argmax
316
+ if do_sample:
317
+ probs = nn.functional.softmax(next_token_logits, dim=-1)
318
+
319
+ if top_k is not None and top_k > 0:
320
+ top_k_probs, top_k_indices = torch.topk(
321
+ probs, min(top_k, probs.size(-1))
322
+ )
323
+ probs = torch.zeros_like(probs).scatter_(
324
+ -1, top_k_indices, top_k_probs
325
+ )
326
+ probs = probs / probs.sum(dim=-1, keepdim=True)
327
+
328
+ if top_p is not None and top_p < 1.0:
329
+ sorted_probs, sorted_indices = torch.sort(probs, descending=True)
330
+ cumulative_probs = torch.cumsum(sorted_probs, dim=-1)
331
+ sorted_indices_to_remove = cumulative_probs > top_p
332
+ sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[
333
+ ..., :-1
334
+ ].clone()
335
+ sorted_indices_to_remove[..., 0] = 0
336
+ indices_to_remove = sorted_indices_to_remove.scatter(
337
+ -1, sorted_indices, sorted_indices_to_remove
338
+ )
339
+ probs = probs.masked_fill(indices_to_remove, 0.0)
340
+ probs = probs / probs.sum(dim=-1, keepdim=True)
341
+
342
+ next_tokens = torch.multinomial(probs, num_samples=1)
343
+ else:
344
+ next_tokens = torch.argmax(next_token_logits, dim=-1, keepdim=True)
345
+
346
+ sequences = torch.cat([sequences, next_tokens], dim=-1)
347
+
348
+ return sequences
349
+
350
+ def _get_stopping_criteria(
351
+ self,
352
+ *args,
353
+ **kwargs,
354
+ ) -> StoppingCriteriaList:
355
+ criteria = super()._get_stopping_criteria(*args, **kwargs)
356
+
357
+ kept_criteria = StoppingCriteriaList()
358
+ for criterion in criteria:
359
+ if not isinstance(criterion, MaxLengthCriteria):
360
+ # logger.warning(
361
+ # f"Breeze does not support {criterion.__class__.__name__} stopping criteria, it will be ignored."
362
+ # )
363
+ pass
364
+ else:
365
+ kept_criteria.append(criterion)
366
+ return kept_criteria
367
+
368
+ def _prepare_generation_config(
369
+ self,
370
+ generation_config: GenerationConfig | None,
371
+ use_model_defaults: bool | None = None,
372
+ **kwargs: Any,
373
+ ) -> tuple[GenerationConfig, dict]:
374
+ """
375
+ This method overrides [~generation.utils.GenerationMixin._prepare_generation_config].
376
+ It ensures that the depth decoder generation config is initialized and that passed args as depth_decoder_* are properly handled.
377
+ """
378
+ # Extract CFG kwargs before passing to super() to preserve them
379
+ cfg_kwargs = {
380
+ k: kwargs.pop(k) for k in list(kwargs.keys()) if k in self._cfg_kwargs
381
+ }
382
+
383
+ # extract depth decoder kwargs and remove them from the main kwargs
384
+ depth_decoder_kwargs = {
385
+ k[len("depth_decoder_") :]: v
386
+ for k, v in kwargs.items()
387
+ if k.startswith("depth_decoder_")
388
+ }
389
+
390
+ # remove the depth decoder keys from the original kwargs
391
+ kwargs = {k: v for k, v in kwargs.items() if not k.startswith("depth_decoder_")}
392
+
393
+ # initialize the generation config
394
+ generation_config, model_kwargs = super()._prepare_generation_config(
395
+ generation_config, use_model_defaults, **kwargs
396
+ )
397
+
398
+ # Restore CFG kwargs to model_kwargs
399
+ model_kwargs.update(cfg_kwargs)
400
+
401
+ self.depth_decoder.generation_config.update(**depth_decoder_kwargs)
402
+
403
+ # ensure the depth decoder generation config is valid
404
+ depth_decoder_min_new_tokens = self.depth_decoder.generation_config.min_new_tokens or (self.config.num_codebooks - 1)
405
+ depth_decoder_max_new_tokens = self.depth_decoder.generation_config.max_new_tokens or (self.config.num_codebooks - 1)
406
+
407
+ if {depth_decoder_min_new_tokens, depth_decoder_max_new_tokens} != {
408
+ self.config.num_codebooks - 1
409
+ }:
410
+ raise ValueError(
411
+ f"depth_decoder_generation_config's min_new_tokens ({depth_decoder_min_new_tokens}) and max_new_tokens ({depth_decoder_max_new_tokens}) must be equal to self.config.num_codebooks - 1 ({self.config.num_codebooks - 1})"
412
+ )
413
+ elif self.depth_decoder.generation_config.return_dict_in_generate:
414
+ logger.warning(
415
+ "depth_decoder_generation_config.return_dict_in_generate is set to True, but this will be ignored as the depth decoder model does not return a dictionary in generate"
416
+ )
417
+ self.depth_decoder.generation_config.return_dict_in_generate = False
418
+
419
+ self.depth_decoder.generation_config.min_new_tokens = (
420
+ depth_decoder_min_new_tokens
421
+ )
422
+ self.depth_decoder.generation_config.max_new_tokens = (
423
+ depth_decoder_max_new_tokens
424
+ )
425
+
426
+ # Monkey patch the get_generation_mode method to support Breeze model
427
+ original_get_generation_mode = generation_config.get_generation_mode
428
+
429
+ def patched_get_generation_mode(assistant_model=None):
430
+ generation_mode = original_get_generation_mode(assistant_model)
431
+ if generation_mode not in [
432
+ GenerationMode.GREEDY_SEARCH,
433
+ GenerationMode.SAMPLE,
434
+ ]:
435
+ raise ValueError(
436
+ f"Generation mode {generation_mode} is not supported for Breeze model. Please set generation parameters to use greedy or sampling generation."
437
+ )
438
+
439
+ return generation_mode
440
+
441
+ generation_config.get_generation_mode = patched_get_generation_mode
442
+
443
+ return generation_config, model_kwargs
444
+
445
+ def _sample(
446
+ self,
447
+ input_ids: torch.LongTensor,
448
+ logits_processor: LogitsProcessorList,
449
+ stopping_criteria: StoppingCriteriaList,
450
+ generation_config: GenerationConfig,
451
+ synced_gpus: bool = False,
452
+ streamer: Optional["BaseStreamer"] = None,
453
+ **model_kwargs,
454
+ ) -> GenerateNonBeamOutput | torch.LongTensor:
455
+ """
456
+ This method overrides [~generation.utils.GenerationMixin._sample].
457
+ To ease maintenance, modifications are marked with the comment "Breeze specific".
458
+
459
+ Indeed, Breeze model requires a custom generation sampling step:
460
+ 1. Infer the backbone model to sample the first codebook token
461
+ 2. Call generate on the depth decoder with the first codebook token as input_ids to sample the next codebook tokens
462
+ 3. Use these generated codebook tokens as input_ids to sample the next first codebook token using the backbone model
463
+ 4. Repeat until stopping criteria is met
464
+
465
+ Breeze supports two stopping criteria:
466
+ - stop when the generated sequence is at max_length
467
+ - stop when all the generated codebook tokens are the codebook_eos_token_id
468
+ """
469
+ # init values
470
+ # *************** Breeze specific ***************
471
+ pad_token_id = self.config.codebook_pad_token_id
472
+ has_eos_stopping_criteria = generation_config._eos_token_tensor is not None
473
+ # ============================================
474
+ output_attentions = generation_config.output_attentions
475
+ output_hidden_states = generation_config.output_hidden_states
476
+ output_scores = generation_config.output_scores
477
+ output_logits = generation_config.output_logits
478
+ return_dict_in_generate = generation_config.return_dict_in_generate
479
+ do_sample = generation_config.do_sample
480
+
481
+ # *************** CFG specific ***************
482
+ # Breeze requires custom CFG because transformers' UnbatchedClassifierFreeGuidanceLogitsProcessor
483
+ # doesn't pass text_ids_mask/text_ids_len which Breeze model requires
484
+ # We use 'cfg_scale' instead of 'guidance_scale' to avoid triggering transformers' CFG
485
+ # cfg_scale=1.0: pure cond (normal generation with description)
486
+ # cfg_scale=0.0: pure uncond (generation without description)
487
+ # cfg_scale>1.0: enhance description influence
488
+ cfg_scale = model_kwargs.pop("cfg_scale", 1.0)
489
+ negative_prompt_ids = model_kwargs.pop("cfg_negative_prompt_ids", None)
490
+ negative_prompt_attention_mask = model_kwargs.pop(
491
+ "cfg_negative_prompt_attention_mask", None
492
+ )
493
+ negative_text_ids_mask = model_kwargs.pop("cfg_negative_text_ids_mask", None)
494
+ negative_text_ids_len = model_kwargs.pop("cfg_negative_text_ids_len", None)
495
+ negative_input_values = model_kwargs.pop("cfg_negative_input_values", None)
496
+ use_cfg = cfg_scale != 1.0 and negative_prompt_ids is not None
497
+
498
+ # cfg_scale=0 means pure unconditional: swap in the negative prompt as the main input
499
+ # so we only run a single forward pass instead of a wasteful dual pass
500
+ if cfg_scale == 0.0 and negative_prompt_ids is not None:
501
+ input_ids = negative_prompt_ids
502
+ model_kwargs["attention_mask"] = negative_prompt_attention_mask
503
+ if negative_text_ids_mask is not None:
504
+ model_kwargs["text_ids_mask"] = negative_text_ids_mask
505
+ else:
506
+ model_kwargs["text_ids_mask"] = negative_prompt_attention_mask.bool()
507
+ if negative_text_ids_len is not None:
508
+ model_kwargs["text_ids_len"] = negative_text_ids_len
509
+ else:
510
+ model_kwargs["text_ids_len"] = negative_prompt_attention_mask.sum(
511
+ dim=1
512
+ ).long()
513
+ use_cfg = False
514
+ # ============================================
515
+
516
+ # *************** Dual CFG specific ***************
517
+ # Dual CFG: separate scales for reference audio and instruction/performance
518
+ # Formula: logits = uncond + cfg_ref * (ref - uncond) + cfg_ins * (ins - uncond)
519
+ cfg_scale_ref = model_kwargs.pop("cfg_scale_ref", None)
520
+ cfg_scale_ins = model_kwargs.pop("cfg_scale_ins", None)
521
+ uncond_prompt_ids = model_kwargs.pop("cfg_uncond_prompt_ids", None)
522
+ uncond_prompt_attention_mask = model_kwargs.pop(
523
+ "cfg_uncond_prompt_attention_mask", None
524
+ )
525
+ uncond_text_ids_mask = model_kwargs.pop("cfg_uncond_text_ids_mask", None)
526
+ uncond_text_ids_len = model_kwargs.pop("cfg_uncond_text_ids_len", None)
527
+ ref_prompt_ids = model_kwargs.pop("cfg_ref_prompt_ids", None)
528
+ ref_prompt_attention_mask = model_kwargs.pop(
529
+ "cfg_ref_prompt_attention_mask", None
530
+ )
531
+ ref_text_ids_mask = model_kwargs.pop("cfg_ref_text_ids_mask", None)
532
+ ref_text_ids_len = model_kwargs.pop("cfg_ref_text_ids_len", None)
533
+ ins_prompt_ids = model_kwargs.pop("cfg_ins_prompt_ids", None)
534
+ ins_prompt_attention_mask = model_kwargs.pop(
535
+ "cfg_ins_prompt_attention_mask", None
536
+ )
537
+ ins_text_ids_mask = model_kwargs.pop("cfg_ins_text_ids_mask", None)
538
+ ins_text_ids_len = model_kwargs.pop("cfg_ins_text_ids_len", None)
539
+ use_dual_cfg = (
540
+ cfg_scale_ref is not None
541
+ and cfg_scale_ins is not None
542
+ and uncond_prompt_ids is not None
543
+ and ref_prompt_ids is not None
544
+ and ins_prompt_ids is not None
545
+ )
546
+ if use_dual_cfg:
547
+ use_cfg = False # disable single CFG when dual CFG is active
548
+ # ============================================
549
+
550
+ # init attention / hidden states / scores tuples
551
+ scores = () if (return_dict_in_generate and output_scores) else None
552
+ raw_logits = () if (return_dict_in_generate and output_logits) else None
553
+ decoder_attentions = (
554
+ () if (return_dict_in_generate and output_attentions) else None
555
+ )
556
+ decoder_hidden_states = (
557
+ () if (return_dict_in_generate and output_hidden_states) else None
558
+ )
559
+
560
+ # keep track of which sequences are already finished
561
+ batch_size, cur_len = input_ids.shape[:2]
562
+ this_peer_finished = False
563
+ unfinished_sequences = torch.ones(
564
+ batch_size, dtype=torch.long, device=input_ids.device
565
+ )
566
+ model_kwargs = self._get_initial_cache_position(
567
+ cur_len, input_ids.device, model_kwargs
568
+ )
569
+
570
+ # *************** CFG specific ***************
571
+ if use_cfg:
572
+ # Start with a copy of model_kwargs to get use_cache and other necessary params
573
+ # but exclude attention_mask, text_ids_mask, text_ids_len, cache_position which need to be recomputed
574
+ negative_model_kwargs = {
575
+ k: v
576
+ for k, v in model_kwargs.items()
577
+ if k
578
+ not in [
579
+ "attention_mask",
580
+ "text_ids_mask",
581
+ "text_ids_len",
582
+ "cache_position",
583
+ "past_key_values",
584
+ ]
585
+ }
586
+ negative_model_kwargs["attention_mask"] = negative_prompt_attention_mask
587
+ # Override input_values for negative prompt if provided (e.g. prompt audio for ref audio mode)
588
+ if negative_input_values is not None:
589
+ negative_model_kwargs["input_values"] = negative_input_values
590
+ # Use pre-computed text_ids_mask and text_ids_len if provided, otherwise fall back to computing from attention_mask
591
+ # Pre-computed values ensure correct position_ids generation in text encoder (one segment per sample)
592
+ if negative_text_ids_mask is not None:
593
+ negative_model_kwargs["text_ids_mask"] = negative_text_ids_mask
594
+ else:
595
+ negative_model_kwargs["text_ids_mask"] = (
596
+ negative_prompt_attention_mask.bool()
597
+ )
598
+ if negative_text_ids_len is not None:
599
+ negative_model_kwargs["text_ids_len"] = negative_text_ids_len
600
+ else:
601
+ negative_model_kwargs["text_ids_len"] = (
602
+ negative_prompt_attention_mask.sum(dim=1).long()
603
+ )
604
+ negative_model_kwargs = self._get_initial_cache_position(
605
+ negative_prompt_ids.shape[1],
606
+ negative_prompt_ids.device,
607
+ negative_model_kwargs,
608
+ )
609
+ # ============================================
610
+
611
+ # *************** Dual CFG init ***************
612
+ if use_dual_cfg:
613
+ _exclude_keys = {
614
+ "attention_mask",
615
+ "text_ids_mask",
616
+ "text_ids_len",
617
+ "cache_position",
618
+ "past_key_values",
619
+ }
620
+
621
+ def _make_branch_kwargs(
622
+ prompt_ids, attn_mask, text_mask, text_len, include_input_values
623
+ ):
624
+ kw = {k: v for k, v in model_kwargs.items() if k not in _exclude_keys}
625
+ kw["attention_mask"] = attn_mask
626
+ kw["text_ids_mask"] = text_mask
627
+ kw["text_ids_len"] = text_len
628
+ if not include_input_values:
629
+ kw.pop("input_values", None)
630
+ return self._get_initial_cache_position(
631
+ prompt_ids.shape[1], prompt_ids.device, kw
632
+ )
633
+
634
+ # uncond: text only (no ref audio, no instruction)
635
+ uncond_model_kwargs = _make_branch_kwargs(
636
+ uncond_prompt_ids,
637
+ uncond_prompt_attention_mask,
638
+ uncond_text_ids_mask,
639
+ uncond_text_ids_len,
640
+ include_input_values=False,
641
+ )
642
+ # ref_cond: ref audio + text (no instruction) — inherits input_values from model_kwargs
643
+ ref_model_kwargs = _make_branch_kwargs(
644
+ ref_prompt_ids,
645
+ ref_prompt_attention_mask,
646
+ ref_text_ids_mask,
647
+ ref_text_ids_len,
648
+ include_input_values=True,
649
+ )
650
+ # ins_cond: instruction + text (no ref audio)
651
+ ins_model_kwargs = _make_branch_kwargs(
652
+ ins_prompt_ids,
653
+ ins_prompt_attention_mask,
654
+ ins_text_ids_mask,
655
+ ins_text_ids_len,
656
+ include_input_values=False,
657
+ )
658
+ # ============================================
659
+
660
+ # *************** Breeze specific ***************
661
+ if input_ids.ndim == 2 and model_kwargs.get("inputs_embeds") is None:
662
+ # in the case where the passed input_ids correspond to text tokens, i.e. don't have a third dimension for codebook ids,
663
+ # we need to remove the input length to the MaxLengthCriteria stopping criteria has such input are not returned
664
+ for criterion in stopping_criteria:
665
+ if isinstance(criterion, MaxLengthCriteria):
666
+ criterion.max_length -= cur_len
667
+ # ============================================
668
+
669
+ model_forward = self.__call__
670
+ compile_forward = self._valid_auto_compile_criteria(
671
+ model_kwargs, generation_config
672
+ )
673
+ if compile_forward:
674
+ os.environ["TOKENIZERS_PARALLELISM"] = "0"
675
+ model_forward = self.get_compiled_call(generation_config.compile_config)
676
+
677
+ is_prefill = True
678
+ while self._has_unfinished_sequences(
679
+ this_peer_finished,
680
+ synced_gpus,
681
+ device=input_ids.device,
682
+ ):
683
+ # *************** Dual CFG specific ***************
684
+ # In dual CFG mode, skip the main forward pass entirely — only run the 3 branches.
685
+ # We reuse ref_outputs as `outputs` for model_kwargs update and hidden_states
686
+ # (backbone_last_hidden_state from `outputs` is unused since dual CFG computes its own).
687
+ if use_dual_cfg:
688
+ if is_prefill:
689
+
690
+ def _run_branch(prompt_ids, branch_kwargs):
691
+ mi = self.prepare_inputs_for_generation(
692
+ prompt_ids, **branch_kwargs
693
+ )
694
+ mi.update({"output_hidden_states": True})
695
+ out = self(**mi, return_dict=True)
696
+ return out, self._update_model_kwargs_for_generation(
697
+ out, branch_kwargs
698
+ )
699
+
700
+ uncond_outputs, uncond_model_kwargs = _run_branch(
701
+ uncond_prompt_ids, uncond_model_kwargs
702
+ )
703
+ ref_outputs, ref_model_kwargs = _run_branch(
704
+ ref_prompt_ids, ref_model_kwargs
705
+ )
706
+ ins_outputs, ins_model_kwargs = _run_branch(
707
+ ins_prompt_ids, ins_model_kwargs
708
+ )
709
+ is_prefill = False
710
+ else:
711
+
712
+ def _run_branch_decode(prompt_ids, branch_kwargs):
713
+ mi = self.prepare_inputs_for_generation(
714
+ prompt_ids, **branch_kwargs
715
+ )
716
+ mi.update({"output_hidden_states": True})
717
+ out = model_forward(**mi, return_dict=True)
718
+ return out, self._update_model_kwargs_for_generation(
719
+ out, branch_kwargs
720
+ )
721
+
722
+ uncond_outputs, uncond_model_kwargs = _run_branch_decode(
723
+ uncond_prompt_ids, uncond_model_kwargs
724
+ )
725
+ ref_outputs, ref_model_kwargs = _run_branch_decode(
726
+ ref_prompt_ids, ref_model_kwargs
727
+ )
728
+ ins_outputs, ins_model_kwargs = _run_branch_decode(
729
+ ins_prompt_ids, ins_model_kwargs
730
+ )
731
+
732
+ # Use ref_outputs as the dummy `outputs` for the rest of the loop
733
+ outputs = ref_outputs
734
+ else:
735
+ # ============================================
736
+ # prepare model inputs
737
+ model_inputs = self.prepare_inputs_for_generation(
738
+ input_ids, **model_kwargs
739
+ )
740
+
741
+ # prepare variable output controls (note: some models won't accept all output controls)
742
+ model_inputs.update(
743
+ {"output_attentions": output_attentions}
744
+ if output_attentions
745
+ else {}
746
+ )
747
+ # *************** Breeze specific ***************
748
+ model_inputs.update({"output_hidden_states": True})
749
+ # ============================================
750
+
751
+ if is_prefill:
752
+ outputs = self(**model_inputs, return_dict=True)
753
+ # *************** CFG specific ***************
754
+ if use_cfg:
755
+ negative_model_inputs = self.prepare_inputs_for_generation(
756
+ negative_prompt_ids, **negative_model_kwargs
757
+ )
758
+ negative_model_inputs.update({"output_hidden_states": True})
759
+ negative_outputs = self(
760
+ **negative_model_inputs, return_dict=True
761
+ )
762
+ negative_model_kwargs = (
763
+ self._update_model_kwargs_for_generation(
764
+ negative_outputs, negative_model_kwargs
765
+ )
766
+ )
767
+ # ============================================
768
+ is_prefill = False
769
+ else:
770
+ outputs = model_forward(**model_inputs, return_dict=True)
771
+ # *************** CFG specific ***************
772
+ if use_cfg:
773
+ negative_model_inputs = self.prepare_inputs_for_generation(
774
+ negative_prompt_ids, **negative_model_kwargs
775
+ )
776
+ negative_model_inputs.update({"output_hidden_states": True})
777
+ negative_outputs = model_forward(
778
+ **negative_model_inputs, return_dict=True
779
+ )
780
+ negative_model_kwargs = (
781
+ self._update_model_kwargs_for_generation(
782
+ negative_outputs, negative_model_kwargs
783
+ )
784
+ )
785
+ # ============================================
786
+
787
+ # synced_gpus: don't waste resources running the code we don't need; kwargs must be updated before skipping
788
+ model_kwargs = self._update_model_kwargs_for_generation(
789
+ outputs,
790
+ model_kwargs,
791
+ )
792
+ if synced_gpus and this_peer_finished:
793
+ continue
794
+
795
+ # Clone is needed to avoid keeping a hanging ref to outputs.logits which may be very large for first iteration
796
+ # (the clone itself is always small)
797
+ next_token_logits = outputs.logits[:, -1, :].clone().float()
798
+ next_token_logits = next_token_logits.to(input_ids.device)
799
+
800
+ # *************** CFG specific ***************
801
+ if use_cfg:
802
+ negative_logits = negative_outputs.logits[:, -1, :].clone().float()
803
+ negative_logits = negative_logits.to(input_ids.device)
804
+ # CFG formula: logits = uncond_logits + cfg_scale * (cond_logits - uncond_logits)
805
+ next_token_logits = negative_logits + cfg_scale * (
806
+ next_token_logits - negative_logits
807
+ )
808
+ # ============================================
809
+ # *************** Dual CFG specific ***************
810
+ if use_dual_cfg:
811
+ uncond_logits = (
812
+ uncond_outputs.logits[:, -1, :].clone().float().to(input_ids.device)
813
+ )
814
+ ref_logits = (
815
+ ref_outputs.logits[:, -1, :].clone().float().to(input_ids.device)
816
+ )
817
+ ins_logits = (
818
+ ins_outputs.logits[:, -1, :].clone().float().to(input_ids.device)
819
+ )
820
+ # Dual CFG: logits = uncond + cfg_ref * (ref - uncond) + cfg_ins * (ins - uncond)
821
+ # Note: next_token_logits from backbone is not used — we don't run a full-cond forward
822
+ next_token_logits = (
823
+ uncond_logits
824
+ + cfg_scale_ref * (ref_logits - uncond_logits)
825
+ + cfg_scale_ins * (ins_logits - uncond_logits)
826
+ )
827
+ # ============================================
828
+
829
+ # pre-process distribution
830
+ next_token_scores = logits_processor(input_ids, next_token_logits)
831
+ self._mask_reserved_codec_logits(next_token_scores)
832
+
833
+ # Store scores, attentions and hidden_states when required
834
+ if return_dict_in_generate:
835
+ if output_scores:
836
+ scores += (next_token_scores,)
837
+ if output_logits:
838
+ raw_logits += (next_token_logits,)
839
+ if output_attentions:
840
+ decoder_attentions += (outputs.attentions,)
841
+
842
+ if output_hidden_states:
843
+ decoder_hidden_states += (outputs.hidden_states,)
844
+
845
+ # token selection
846
+ if do_sample:
847
+ probs = nn.functional.softmax(next_token_scores, dim=-1)
848
+ # TODO (joao): this OP throws "skipping cudagraphs due to ['incompatible ops']", find solution
849
+ # next_tokens = torch.multinomial(probs, num_samples=1).squeeze(1)
850
+
851
+ def multinomial(probs, num_samples=1):
852
+ ori_shape = probs.shape[:-1]
853
+ probs = probs.reshape(-1, probs.shape[-1])
854
+ samples = torch.multinomial(probs, num_samples=num_samples)
855
+ samples = samples.reshape([*ori_shape, -1])
856
+ return samples
857
+
858
+ next_tokens = multinomial(probs, num_samples=1).squeeze(-1)
859
+ else:
860
+ next_tokens = torch.argmax(next_token_scores, dim=-1)
861
+
862
+ # *************** Breeze specific ***************
863
+ # Detect backbone EOS: backbone predicts token at index vocab_size
864
+ backbone_eos_token_id = self.config.vocab_size
865
+ backbone_eos_mask = next_tokens == backbone_eos_token_id # (batch_size,)
866
+
867
+ # infer the depth decoder only for non-EOS rows
868
+ backbone_last_hidden_state = outputs.hidden_states[-1][:, -1, :]
869
+
870
+ # *************** CFG specific for depth decoder ***************
871
+ if use_cfg:
872
+ uncond_backbone_last_hidden_state = negative_outputs.hidden_states[-1][
873
+ :, -1, :
874
+ ]
875
+ # ============================================
876
+ # *************** Dual CFG specific for depth decoder ***************
877
+ if use_dual_cfg:
878
+ uncond_backbone_last_hidden_state = uncond_outputs.hidden_states[-1][
879
+ :, -1, :
880
+ ]
881
+ ref_backbone_last_hidden_state = ref_outputs.hidden_states[-1][:, -1, :]
882
+ ins_backbone_last_hidden_state = ins_outputs.hidden_states[-1][:, -1, :]
883
+ # ============================================
884
+
885
+ if backbone_last_hidden_state.ndim == 3:
886
+ # patched generate
887
+ (b, patch_size, hidden_size) = backbone_last_hidden_state.shape
888
+ backbone_last_hidden_state = backbone_last_hidden_state.reshape(
889
+ b * patch_size, hidden_size
890
+ )
891
+ if use_cfg:
892
+ uncond_backbone_last_hidden_state = (
893
+ uncond_backbone_last_hidden_state.reshape(
894
+ b * patch_size, hidden_size
895
+ )
896
+ )
897
+ if use_dual_cfg:
898
+ uncond_backbone_last_hidden_state = (
899
+ uncond_backbone_last_hidden_state.reshape(
900
+ b * patch_size, hidden_size
901
+ )
902
+ )
903
+ ref_backbone_last_hidden_state = (
904
+ ref_backbone_last_hidden_state.reshape(
905
+ b * patch_size, hidden_size
906
+ )
907
+ )
908
+ ins_backbone_last_hidden_state = (
909
+ ins_backbone_last_hidden_state.reshape(
910
+ b * patch_size, hidden_size
911
+ )
912
+ )
913
+ else:
914
+ patch_size = 1
915
+
916
+ # Preallocate full codebook output with pad tokens.
917
+ # Newer checkpoints return the complete audio frame from the depth decoder,
918
+ # while older ones return only the continuation after the backbone's first codebook.
919
+ num_codebooks = self.config.num_codebooks
920
+ total_rows = batch_size * patch_size if patch_size > 1 else batch_size
921
+ codebook_ids = torch.full(
922
+ (total_rows, num_codebooks),
923
+ pad_token_id,
924
+ device=input_ids.device,
925
+ dtype=input_ids.dtype,
926
+ )
927
+
928
+ # Determine which rows need depth decoder (non-EOS and unfinished)
929
+ if patch_size > 1:
930
+ non_eos_mask = ~backbone_eos_mask.reshape(total_rows)
931
+ else:
932
+ non_eos_mask = ~backbone_eos_mask
933
+
934
+ # Clamp EOS rows to 0 so they are valid codebook ids for depth decoder input
935
+ first_codebook_ids_clamped = next_tokens.clone()
936
+ first_codebook_ids_clamped[backbone_eos_mask] = 0
937
+
938
+ if non_eos_mask.any():
939
+ if patch_size > 1:
940
+ first_codebook_ids_flat = first_codebook_ids_clamped.reshape(
941
+ total_rows, 1
942
+ )
943
+ else:
944
+ first_codebook_ids_flat = first_codebook_ids_clamped[..., None]
945
+
946
+ depth_decoder_input_ids = nn.functional.pad(
947
+ first_codebook_ids_flat[non_eos_mask], (1, 0), value=0
948
+ )
949
+ bhs = backbone_last_hidden_state[non_eos_mask]
950
+
951
+ # *************** Dual CFG specific for depth decoder ***************
952
+ if use_dual_cfg:
953
+ active_codebook_ids = self._depth_decoder_generate_with_dual_cfg(
954
+ depth_decoder_input_ids=depth_decoder_input_ids,
955
+ uncond_backbone_hidden_state=uncond_backbone_last_hidden_state[
956
+ non_eos_mask
957
+ ].clone(),
958
+ ref_backbone_hidden_state=ref_backbone_last_hidden_state[
959
+ non_eos_mask
960
+ ].clone(),
961
+ ins_backbone_hidden_state=ins_backbone_last_hidden_state[
962
+ non_eos_mask
963
+ ].clone(),
964
+ cfg_scale_ref=cfg_scale_ref,
965
+ cfg_scale_ins=cfg_scale_ins,
966
+ )
967
+ elif use_cfg:
968
+ active_codebook_ids = self._depth_decoder_generate_with_cfg(
969
+ depth_decoder_input_ids=depth_decoder_input_ids,
970
+ cond_backbone_hidden_state=bhs.clone(),
971
+ uncond_backbone_hidden_state=uncond_backbone_last_hidden_state[
972
+ non_eos_mask
973
+ ].clone(),
974
+ cfg_scale=cfg_scale,
975
+ )
976
+ else:
977
+ depth_decoder_outputs = self.depth_decoder.generate(
978
+ input_ids=depth_decoder_input_ids,
979
+ backbone_last_hidden_state=bhs.clone(),
980
+ suppress_tokens=self._reserved_codec_token_ids(),
981
+ )
982
+ active_codebook_ids = (
983
+ depth_decoder_outputs
984
+ if isinstance(depth_decoder_outputs, torch.Tensor)
985
+ else depth_decoder_outputs.sequences
986
+ )
987
+ # ============================================
988
+ # Remove the placeholder in position 0. Depending on checkpoint vintage,
989
+ # the depth decoder may now return either the full frame or only the
990
+ # continuation after the backbone's first codebook.
991
+ active_codebook_ids = active_codebook_ids[:, 1:]
992
+ if active_codebook_ids.shape[-1] == num_codebooks:
993
+ normalized_codebook_ids = active_codebook_ids
994
+ elif active_codebook_ids.shape[-1] == num_codebooks - 1:
995
+ normalized_codebook_ids = torch.cat(
996
+ [first_codebook_ids_flat[non_eos_mask], active_codebook_ids],
997
+ dim=-1,
998
+ )
999
+ else:
1000
+ raise ValueError(
1001
+ "Unexpected depth decoder output width "
1002
+ f"{active_codebook_ids.shape[-1]} after placeholder removal; "
1003
+ f"expected {num_codebooks} or {num_codebooks - 1}."
1004
+ )
1005
+ codebook_ids[non_eos_mask] = normalized_codebook_ids
1006
+
1007
+ next_tokens = codebook_ids # (total_rows, num_codebooks)
1008
+
1009
+ # add sequence dimension
1010
+ next_tokens_3d = next_tokens.reshape(
1011
+ batch_size, 1, patch_size * next_tokens.shape[-1]
1012
+ )
1013
+
1014
+ # finished sentences should have their next token be a padding token
1015
+ next_tokens_3d = next_tokens_3d * unfinished_sequences.view(
1016
+ batch_size, 1, 1
1017
+ ) + pad_token_id * (1 - unfinished_sequences.view(batch_size, 1, 1))
1018
+
1019
+ # update generated ids, model inputs, and length for next step
1020
+ if input_ids.ndim == 2:
1021
+ input_ids = next_tokens_3d
1022
+ else:
1023
+ input_ids = torch.cat([input_ids, next_tokens_3d], dim=1)
1024
+ # ============================================
1025
+
1026
+ if streamer is not None:
1027
+ streamer.put(next_tokens.cpu())
1028
+
1029
+ # *************** Breeze specific ***************
1030
+ # Stop sequences where backbone predicted EOS
1031
+ unfinished_sequences = unfinished_sequences & ~backbone_eos_mask
1032
+ # ============================================
1033
+ unfinished_sequences = unfinished_sequences & ~stopping_criteria(
1034
+ input_ids, scores
1035
+ )
1036
+ this_peer_finished = unfinished_sequences.max() == 0
1037
+ cur_len += 1
1038
+
1039
+ # This is needed to properly delete outputs.logits which may be very large for first iteration
1040
+ # Otherwise a reference to outputs is kept which keeps the logits alive in the next iteration
1041
+ del outputs
1042
+
1043
+ # *************** Breeze specific ***************
1044
+ if not use_cfg and not use_dual_cfg:
1045
+ depth_decoder_outputs = None
1046
+ # ============================================
1047
+
1048
+ # *************** CFG specific ***************
1049
+ if use_cfg:
1050
+ # negative_prompt_ids starts as 2D (batch, seq_len), convert to 3D after first iteration
1051
+ if negative_prompt_ids.ndim == 2:
1052
+ negative_prompt_ids = next_tokens_3d
1053
+ else:
1054
+ negative_prompt_ids = torch.cat(
1055
+ [negative_prompt_ids, next_tokens_3d], dim=1
1056
+ )
1057
+ del negative_outputs
1058
+ # ============================================
1059
+
1060
+ # *************** Dual CFG specific ***************
1061
+ if use_dual_cfg:
1062
+
1063
+ def _update_branch_ids(prompt_ids):
1064
+ if prompt_ids.ndim == 2:
1065
+ return next_tokens_3d
1066
+ return torch.cat([prompt_ids, next_tokens_3d], dim=1)
1067
+
1068
+ uncond_prompt_ids = _update_branch_ids(uncond_prompt_ids)
1069
+ ref_prompt_ids = _update_branch_ids(ref_prompt_ids)
1070
+ ins_prompt_ids = _update_branch_ids(ins_prompt_ids)
1071
+ del uncond_outputs, ref_outputs, ins_outputs
1072
+ # ============================================
1073
+
1074
+ if streamer is not None:
1075
+ streamer.end()
1076
+
1077
+ if return_dict_in_generate:
1078
+ return GenerateDecoderOnlyOutput(
1079
+ sequences=input_ids,
1080
+ scores=scores,
1081
+ logits=raw_logits,
1082
+ attentions=decoder_attentions,
1083
+ hidden_states=decoder_hidden_states,
1084
+ past_key_values=model_kwargs.get("past_key_values"),
1085
+ )
1086
+ else:
1087
+ return input_ids
1088
+
1089
+ def generate(
1090
+ self,
1091
+ input_ids: torch.Tensor | None = None,
1092
+ input_values: torch.Tensor | None = None,
1093
+ input_values_cutoffs: torch.Tensor | None = None,
1094
+ generation_config: GenerationConfig | None = None,
1095
+ logits_processor: LogitsProcessorList | None = None,
1096
+ stopping_criteria: StoppingCriteriaList | None = None,
1097
+ synced_gpus: bool | None = None,
1098
+ streamer: Optional["BaseStreamer"] = None,
1099
+ output_audio: bool | None = False,
1100
+ audio_tokenizer=None,
1101
+ **kwargs,
1102
+ ) -> GenerateNonBeamOutput | torch.LongTensor:
1103
+ r"""
1104
+ This method overrides [`~generation.utils.GenerationMixin.generate`] to match the specifics of the Breeze model.
1105
+ Indeed, Breeze model requires a custom generation sampling step:
1106
+ 1. Infer the backbone model to sample the first codebook token
1107
+ 2. Call generate on the depth decoder with the first codebook token as `input_ids` to sample the next codebook tokens
1108
+ 3. Use these generated codebook tokens as `input_ids` to sample the next first codebook token using the backbone model
1109
+ 4. Repeat until stopping criteria is met
1110
+
1111
+ <Tip warning={true}>
1112
+
1113
+ Most generation-controlling parameters are set in `generation_config` which, if not passed, will be set to the
1114
+ model's default generation configuration. You can override any `generation_config` by passing the corresponding
1115
+ parameters to generate(), e.g. `.generate(inputs, do_sample=True)`.
1116
+ </Tip>
1117
+
1118
+ Parameters:
1119
+ inputs_ids (`torch.Tensor` of shape (batch_size, seq_length), *optional*):
1120
+ The sequence used as a prompt for the backbone model.
1121
+ input_values (`torch.Tensor` of shape (batch_size, channels, max_concatenated_audio_length), *optional*):
1122
+ The batched audio input values, where each batch entry contains the concatenation of all audio segments for that entry.
1123
+ These values will be encoded into codebook tokens using the codec model and merged with the text input ids provided in `input_ids`.
1124
+ input_values_cutoffs (`torch.Tensor` of shape (batch_size, max_num_audio), *optional*):
1125
+ Specify the end positions of audio segments within each batch entry, relative to the concatenated audio input.
1126
+ If a batch entry has fewer segments than the maximum, it is padded with -1. For example, in a batch of 2 sequences
1127
+ where the first contains 2 audio segments of length l1, and the second contains 1 audio segment of length l2,
1128
+ the input_values_cutoffs would be: [[l1, 2 * l1], [l2, -1]].
1129
+ generation_config ([`~generation.GenerationConfig`], *optional*):
1130
+ The generation configuration to be used as base parametrization for the generation call. `**kwargs`
1131
+ passed to generate matching the attributes of `generation_config` will override them. If
1132
+ `generation_config` is not provided, the default will be used, which has the following loading
1133
+ priority: 1) from the `generation_config.json` model file, if it exists; 2) from the model
1134
+ configuration. Please note that unspecified parameters will inherit [`~generation.GenerationConfig`]'s
1135
+ default values, whose documentation should be checked to parameterize generation.
1136
+ logits_processor (`LogitsProcessorList`, *optional*):
1137
+ Custom logits processors that complement the default logits processors built from arguments and
1138
+ generation config. If a logit processor is passed that is already created with the arguments or a
1139
+ generation config an error is thrown. This feature is intended for advanced users.
1140
+ stopping_criteria (`StoppingCriteriaList`, *optional*):
1141
+ Custom stopping criteria that complements the default stopping criteria built from arguments and a
1142
+ generation config. If a stopping criteria is passed that is already created with the arguments or a
1143
+ generation config an error is thrown. If your stopping criteria depends on the `scores` input, make
1144
+ sure you pass `return_dict_in_generate=True, output_scores=True` to `generate`. This feature is
1145
+ intended for advanced users.
1146
+ synced_gpus (`bool`, *optional*):
1147
+ Whether to continue running the while loop until max_length. Unless overridden, this flag will be set
1148
+ to `True` if using `FullyShardedDataParallel` or DeepSpeed ZeRO Stage 3 with multiple GPUs to avoid
1149
+ deadlocking if one GPU finishes generating before other GPUs. Otherwise, defaults to `False`.
1150
+ streamer (`BaseStreamer`, *optional*):
1151
+ Streamer object that will be used to stream the generated sequences. Generated tokens are passed
1152
+ through `streamer.put(token_ids)` and the streamer is responsible for any further processing.
1153
+ output_audio (`bool`, *optional*):
1154
+ Whether to return the generated audio.
1155
+ kwargs (`dict[str, Any]`, *optional*):
1156
+ Ad hoc parametrization of `generation_config` and/or additional model-specific kwargs that will be
1157
+ forwarded to the `forward` function of the model. Depth decoder specific kwargs should be prefixed with *depth_decoder_*.
1158
+
1159
+ Return:
1160
+ [`BreezeGenerateOutput`] or `torch.LongTensor` or `list[torch.FloatTensor]`: A [`BreezeGenerateOutput`]
1161
+ (if `return_dict_in_generate=True` or when `config.return_dict_in_generate=True`) or a `torch.LongTensor` when `output_audio=False`
1162
+ or a `list[torch.FloatTensor]` otherwise.
1163
+
1164
+ Example:
1165
+
1166
+ ```python
1167
+ >>> from models.breeze import BreezeForConditionalGeneration
1168
+ from transformers import AutoTokenizer
1169
+ >>> from datasets import load_dataset, Audio
1170
+
1171
+ >>> model_id = "/path/to/breeze-model"
1172
+ >>> torch_device = "cuda" if torch.cuda.is_available() else "cpu"
1173
+
1174
+ >>> processor = AutoProcessor.from_pretrained(model_id)
1175
+
1176
+ >>> ds = load_dataset("hf-internal-testing/dailytalk-dummy", split="train")
1177
+ >>> # ensure the audio is 24kHz
1178
+ >>> ds = ds.cast_column("audio", Audio(sampling_rate=24000))
1179
+
1180
+ >>> conversation = []
1181
+ >>> # prepare a conversation with text and corresponding audio
1182
+ >>> for text, audio, speaker_id in zip(ds[:4]["text"], ds[:4]["audio"], ds[:4]["speaker_id"]):
1183
+ ... conversation.append(
1184
+ ... {
1185
+ ... "role": f"{speaker_id}",
1186
+ ... "content": [{"type": "text", "text": text}, {"type": "audio", "path": audio["array"]}],
1187
+ ... }
1188
+ ... )
1189
+
1190
+ >>> # text prompt
1191
+ >>> conversation.append({"role": f"{ds[4]['speaker_id']}", "content": [{"type": "text", "text": ds[4]["text"]}]})
1192
+
1193
+ >>> inputs = processor.apply_chat_template(
1194
+ ... conversation,
1195
+ ... tokenize=True,
1196
+ ... return_dict=True,
1197
+ ... ).to(torch_device)
1198
+
1199
+ >>> model = BreezeForConditionalGeneration.from_pretrained(model_id, device_map=torch_device)
1200
+ >>> audio = model.generate(**inputs, output_audio=True)
1201
+ >>> processor.save_audio(audio, "output.wav")
1202
+ ```
1203
+ """
1204
+ generate_output = super().generate(
1205
+ input_ids=input_ids,
1206
+ input_values=input_values,
1207
+ input_values_cutoffs=input_values_cutoffs,
1208
+ generation_config=generation_config,
1209
+ logits_processor=logits_processor,
1210
+ stopping_criteria=stopping_criteria,
1211
+ synced_gpus=synced_gpus,
1212
+ streamer=streamer,
1213
+ **kwargs,
1214
+ )
1215
+
1216
+ generate_returned_dict = not isinstance(generate_output, torch.Tensor)
1217
+ audio = None
1218
+ if output_audio:
1219
+ generated_audio_codes = (
1220
+ generate_output.sequences if generate_returned_dict else generate_output
1221
+ )
1222
+
1223
+ # reshape for patched audio tokens, if patch size == 1, this does nothing
1224
+ patch_size = generated_audio_codes.shape[-1] // self.config.num_codebooks
1225
+ generated_audio_codes = generated_audio_codes.reshape(
1226
+ generated_audio_codes.shape[0],
1227
+ generated_audio_codes.shape[1] * patch_size,
1228
+ generated_audio_codes.shape[2] // patch_size,
1229
+ )
1230
+
1231
+ # infer the codec model
1232
+ audio = []
1233
+ with torch.no_grad():
1234
+ # =======================================
1235
+ # TODO: @eustlb, this should be batched !!!
1236
+ # but requires making sure batched inference of the codec model works as intended
1237
+ for sample_index, audio_codes_batch in enumerate(generated_audio_codes):
1238
+ # Truncate at first pad frame (EOS rows are stored as all-pad)
1239
+ is_pad_frame = (
1240
+ audio_codes_batch == self.config.codebook_pad_token_id
1241
+ ).all(dim=-1)
1242
+ eos_idxs = is_pad_frame.nonzero()
1243
+ if eos_idxs.numel() != 0:
1244
+ cutoff_idx = eos_idxs.min()
1245
+ else:
1246
+ cutoff_idx = audio_codes_batch.shape[0]
1247
+
1248
+ audio_codes_batch = audio_codes_batch[:cutoff_idx]
1249
+
1250
+ if cutoff_idx == 0:
1251
+ # write log to file
1252
+ with open(
1253
+ "/tmp/breeze_generation_warnings.log", "a"
1254
+ ) as log_file:
1255
+ log_file.write(
1256
+ "[no-code] No codebook tokens were generated, generating silent audio.\n"
1257
+ )
1258
+
1259
+ # generate silent audio if no codebook tokens were generated
1260
+ num_codebooks = getattr(
1261
+ self.codec_model,
1262
+ "num_codebooks",
1263
+ getattr(
1264
+ self.config,
1265
+ "num_codebooks",
1266
+ audio_codes_batch.shape[-1],
1267
+ ),
1268
+ )
1269
+ dummy_codes = torch.ones(
1270
+ 1,
1271
+ num_codebooks,
1272
+ device=audio_codes_batch.device,
1273
+ dtype=audio_codes_batch.dtype,
1274
+ )
1275
+ logger.warning(
1276
+ "No codebook tokens were generated, generating silent audio."
1277
+ )
1278
+ if audio_tokenizer is not None:
1279
+ codec_decode_output = audio_tokenizer.decode(
1280
+ {"audio_codes": dummy_codes}
1281
+ )
1282
+ decode_audio = _extract_decoded_audio_tensor(
1283
+ codec_decode_output
1284
+ )
1285
+ else:
1286
+ codec_decode_output = self.codec_model.decode(
1287
+ dummy_codes.transpose(0, 1).unsqueeze(0)
1288
+ )
1289
+ decode_audio = _extract_decoded_audio_tensor(
1290
+ codec_decode_output
1291
+ )
1292
+ else:
1293
+ codebook_size = int(self.config.codec_config.codebook_size)
1294
+ invalid_codes = (audio_codes_batch < 0) | (
1295
+ audio_codes_batch >= codebook_size
1296
+ )
1297
+ if invalid_codes.any():
1298
+ frame_index, codebook_index = invalid_codes.nonzero()[
1299
+ 0
1300
+ ].tolist()
1301
+ token_id = int(
1302
+ audio_codes_batch[frame_index, codebook_index]
1303
+ )
1304
+ raise ValueError(
1305
+ "Generated codec token is outside the decoder codebook range: "
1306
+ f"sample={sample_index}, frame={frame_index}, "
1307
+ f"codebook={codebook_index}, token={token_id}, "
1308
+ f"valid=[0, {codebook_size - 1}]"
1309
+ )
1310
+
1311
+ if (
1312
+ audio_tokenizer is None
1313
+ ): # 兼容qwen3, 因为qwen3 tokenizer没有cardinality属性
1314
+ if (
1315
+ audio_codes_batch.max().item()
1316
+ >= self.codec_model.quantizer.cardinality
1317
+ ):
1318
+ audio_codes_batch = torch.clamp(
1319
+ audio_codes_batch,
1320
+ 0,
1321
+ self.codec_model.quantizer.cardinality - 1,
1322
+ )
1323
+
1324
+ if audio_tokenizer is not None:
1325
+ codec_decode_output = audio_tokenizer.decode(
1326
+ {"audio_codes": [audio_codes_batch]}
1327
+ )
1328
+ decode_audio = _extract_decoded_audio_tensor(
1329
+ codec_decode_output
1330
+ )
1331
+ else:
1332
+ codec_decode_output = self.codec_model.decode(
1333
+ audio_codes_batch.transpose(0, 1).unsqueeze(0)
1334
+ )
1335
+ decode_audio = _extract_decoded_audio_tensor(
1336
+ codec_decode_output
1337
+ )
1338
+
1339
+ codes_min_max = (
1340
+ audio_codes_batch.min().item(),
1341
+ audio_codes_batch.max().item(),
1342
+ )
1343
+ # logger.info(f"Decoded audio codes with min/max values: {codes_min_max}")
1344
+ audio.append(decode_audio)
1345
+ # =======================================
1346
+
1347
+ if generate_returned_dict:
1348
+ return BreezeGenerateOutput(audio=audio, **generate_output)
1349
+ elif output_audio:
1350
+ return audio
1351
+ else:
1352
+ return generate_output
models/logits_process.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import torch
4
+ from transformers.generation.logits_process import LogitsProcessor
5
+
6
+
7
+ def mask_invalid_codec_token_logits(
8
+ scores: torch.Tensor,
9
+ *,
10
+ codebook_size: int,
11
+ token_vocab_size: int,
12
+ ) -> torch.Tensor:
13
+ """Mask reserved model tokens that are not valid codec codebook entries."""
14
+ if codebook_size <= 0 or token_vocab_size < codebook_size:
15
+ raise ValueError(
16
+ f"Invalid codec token range: codebook_size={codebook_size}, "
17
+ f"token_vocab_size={token_vocab_size}"
18
+ )
19
+ if token_vocab_size > scores.shape[-1]:
20
+ raise ValueError(
21
+ f"token_vocab_size={token_vocab_size} exceeds logits size {scores.shape[-1]}"
22
+ )
23
+ if codebook_size < token_vocab_size:
24
+ scores[..., codebook_size:token_vocab_size] = float("-inf")
25
+ return scores
26
+
27
+
28
+ class GeneratedTokenRepetitionPenaltyLogitsProcessor(LogitsProcessor):
29
+ """Apply HF-style repetition penalty only to generated, in-vocab tokens."""
30
+
31
+ def __init__(self, penalty: float) -> None:
32
+ if penalty <= 0:
33
+ raise ValueError("penalty must be > 0")
34
+ self.penalty = float(penalty)
35
+
36
+ def __call__(
37
+ self, input_ids: torch.LongTensor, scores: torch.FloatTensor
38
+ ) -> torch.FloatTensor:
39
+ # Breeze starts generation with 2D text prompt IDs, then replaces them
40
+ # with 3D generated codec frames after the first decoding step.
41
+ if input_ids.ndim == 2:
42
+ return scores
43
+ if input_ids.ndim != 3:
44
+ raise ValueError(
45
+ f"Expected 2D prompt IDs or 3D codec frames, got {input_ids.ndim}D"
46
+ )
47
+
48
+ generated_ids = input_ids[..., 0]
49
+ if self.penalty == 1.0 or generated_ids.numel() == 0:
50
+ return scores
51
+
52
+ vocab_size = scores.shape[-1]
53
+ valid = (generated_ids >= 0) & (generated_ids < vocab_size)
54
+ if not torch.any(valid):
55
+ return scores
56
+
57
+ # Invalid IDs use token 0 as a temporary gather/scatter target. Restore
58
+ # token 0 explicitly afterward so those placeholders cannot affect it.
59
+ safe_ids = generated_ids.masked_fill(~valid, 0)
60
+ selected_scores = torch.gather(scores, 1, safe_ids)
61
+ penalized_scores = torch.where(
62
+ selected_scores < 0,
63
+ selected_scores * self.penalty,
64
+ selected_scores / self.penalty,
65
+ )
66
+
67
+ processed = scores.clone()
68
+ processed.scatter_(1, safe_ids, penalized_scores)
69
+
70
+ original_zero = scores[:, 0]
71
+ penalized_zero = torch.where(
72
+ original_zero < 0,
73
+ original_zero * self.penalty,
74
+ original_zero / self.penalty,
75
+ )
76
+ generated_zero = (valid & (generated_ids == 0)).any(dim=1)
77
+ processed[:, 0] = torch.where(generated_zero, penalized_zero, original_zero)
78
+ return processed
models/stream_runtime/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .inference import (
2
+ BACKEND_NAME,
3
+ ExecutionLane,
4
+ MultiRequestStreamRuntime,
5
+ QwenStreamRuntimeConfig,
6
+ load_tokenizer,
7
+ )
8
+
9
+ __all__ = [
10
+ "BACKEND_NAME",
11
+ "ExecutionLane",
12
+ "MultiRequestStreamRuntime",
13
+ "QwenStreamRuntimeConfig",
14
+ "load_tokenizer",
15
+ ]
models/stream_runtime/core/__init__.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .compat import (
2
+ BACKEND_NAME,
3
+ Qwen3TTSTokenizer,
4
+ Qwen3TTSTokenizerV2CausalConvNet,
5
+ Qwen3TTSTokenizerV2CausalTransConvNet,
6
+ Qwen3TTSTokenizerV2ConvNeXtBlock,
7
+ Qwen3TTSTokenizerV2Decoder,
8
+ Qwen3TTSTokenizerV2DecoderDecoderBlock,
9
+ Qwen3TTSTokenizerV2DecoderDecoderResidualUnit,
10
+ )
11
+
12
+ __all__ = [
13
+ "BACKEND_NAME",
14
+ "Qwen3TTSTokenizer",
15
+ "Qwen3TTSTokenizerV2CausalConvNet",
16
+ "Qwen3TTSTokenizerV2CausalTransConvNet",
17
+ "Qwen3TTSTokenizerV2ConvNeXtBlock",
18
+ "Qwen3TTSTokenizerV2Decoder",
19
+ "Qwen3TTSTokenizerV2DecoderDecoderBlock",
20
+ "Qwen3TTSTokenizerV2DecoderDecoderResidualUnit",
21
+ ]
models/stream_runtime/core/compat.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import os
4
+
5
+ BACKEND_NAME = os.environ.get("QWEN_TTS_STREAM_BACKEND", "official").strip().lower()
6
+
7
+ if BACKEND_NAME == "official":
8
+ from qwen_tts.core.tokenizer_12hz.modeling_qwen3_tts_tokenizer_v2 import (
9
+ Qwen3TTSTokenizerV2CausalConvNet,
10
+ Qwen3TTSTokenizerV2CausalTransConvNet,
11
+ Qwen3TTSTokenizerV2ConvNeXtBlock,
12
+ Qwen3TTSTokenizerV2Decoder,
13
+ Qwen3TTSTokenizerV2DecoderDecoderBlock,
14
+ Qwen3TTSTokenizerV2DecoderDecoderResidualUnit,
15
+ )
16
+ from qwen_tts.inference.qwen3_tts_tokenizer import Qwen3TTSTokenizer
17
+ else:
18
+ raise ImportError(
19
+ "Breeze streaming runtime only supports the official qwen_tts backend. "
20
+ "Set QWEN_TTS_STREAM_BACKEND=official or leave it unset."
21
+ )
22
+
23
+
24
+ __all__ = [
25
+ "BACKEND_NAME",
26
+ "Qwen3TTSTokenizer",
27
+ "Qwen3TTSTokenizerV2CausalConvNet",
28
+ "Qwen3TTSTokenizerV2CausalTransConvNet",
29
+ "Qwen3TTSTokenizerV2ConvNeXtBlock",
30
+ "Qwen3TTSTokenizerV2Decoder",
31
+ "Qwen3TTSTokenizerV2DecoderDecoderBlock",
32
+ "Qwen3TTSTokenizerV2DecoderDecoderResidualUnit",
33
+ ]
models/stream_runtime/inference/__init__.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ..core.compat import BACKEND_NAME
2
+ from ..stream.lane import ExecutionLane
3
+ from ..stream.runtime import (
4
+ MultiRequestStreamRuntime,
5
+ QwenStreamRuntimeConfig,
6
+ load_tokenizer,
7
+ )
8
+
9
+ __all__ = [
10
+ "BACKEND_NAME",
11
+ "ExecutionLane",
12
+ "MultiRequestStreamRuntime",
13
+ "QwenStreamRuntimeConfig",
14
+ "load_tokenizer",
15
+ ]
models/stream_runtime/stream/__init__.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .lane import ExecutionLane
2
+ from .runtime import MultiRequestStreamRuntime, QwenStreamRuntimeConfig, load_tokenizer
3
+ from .state import RequestStatePool
4
+ from .workspace import WorkspacePool
5
+
6
+ __all__ = [
7
+ "ExecutionLane",
8
+ "MultiRequestStreamRuntime",
9
+ "QwenStreamRuntimeConfig",
10
+ "RequestStatePool",
11
+ "WorkspacePool",
12
+ "load_tokenizer",
13
+ ]
models/stream_runtime/stream/kv_cache.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ import torch
6
+ from torch import nn
7
+
8
+
9
+ class _LayerKV(nn.Module):
10
+ def __init__(self, k: torch.Tensor, v: torch.Tensor):
11
+ super().__init__()
12
+ self.register_buffer("k", k, persistent=False)
13
+ self.register_buffer("v", v, persistent=False)
14
+
15
+
16
+ class StaticShiftKVCache(nn.Module):
17
+ def __init__(
18
+ self,
19
+ num_layers: int,
20
+ window: int,
21
+ batch: int,
22
+ h_kv: int,
23
+ head_dim: int,
24
+ device,
25
+ dtype,
26
+ ):
27
+ super().__init__()
28
+ self.window = int(window)
29
+ self.layers = nn.ModuleList(
30
+ [
31
+ _LayerKV(
32
+ k=torch.zeros(
33
+ (batch, h_kv, self.window, head_dim), device=device, dtype=dtype
34
+ ),
35
+ v=torch.zeros(
36
+ (batch, h_kv, self.window, head_dim), device=device, dtype=dtype
37
+ ),
38
+ )
39
+ for _ in range(num_layers)
40
+ ]
41
+ )
42
+ self.is_sliding = [True] * num_layers
43
+
44
+ def get_seq_length(self) -> int:
45
+ return int(self.window)
46
+
47
+ def get_mask_sizes(self, cache_position: torch.Tensor, layer_idx: int):
48
+ q_len = int(cache_position.numel()) if cache_position is not None else 1
49
+ if cache_position is None or cache_position.numel() == 0:
50
+ return self.window, 0
51
+ cur_pos = int(cache_position.reshape(-1)[-1].item())
52
+ kv_length = min(cur_pos + 1, self.window)
53
+ kv_offset = max(cur_pos + 1 - kv_length, 0)
54
+ return kv_length, kv_offset
55
+
56
+ def update(
57
+ self,
58
+ key_states: torch.Tensor,
59
+ value_states: torch.Tensor,
60
+ layer_idx: int,
61
+ cache_kwargs: Any,
62
+ ):
63
+ layer = self.layers[layer_idx]
64
+ t_new = key_states.shape[-2]
65
+ if not torch._dynamo.is_compiling() and t_new > self.window:
66
+ raise ValueError(
67
+ f"StaticShiftKVCache assumes T_new <= window={self.window}, got {t_new}."
68
+ )
69
+ shift = min(int(t_new), self.window)
70
+ if shift < self.window:
71
+ layer.k[..., :-shift, :].copy_(layer.k[..., shift:, :])
72
+ layer.v[..., :-shift, :].copy_(layer.v[..., shift:, :])
73
+ layer.k[..., -shift:, :].copy_(key_states[..., -shift:, :])
74
+ layer.v[..., -shift:, :].copy_(value_states[..., -shift:, :])
75
+ else:
76
+ layer.k.copy_(key_states[..., -self.window :, :])
77
+ layer.v.copy_(value_states[..., -self.window :, :])
78
+ return layer.k, layer.v
79
+
80
+
81
+ def make_fixed_attention_mask(
82
+ batch: int,
83
+ window: int,
84
+ cache_position: torch.Tensor,
85
+ device: torch.device,
86
+ dtype: torch.dtype,
87
+ ) -> torch.Tensor:
88
+ positions = cache_position.reshape(-1)
89
+ end_pos = positions[-1]
90
+ # Column j corresponds to the absolute key position:
91
+ # abs_key(j) = end_pos - window + 1 + j
92
+ # We keep the column index static so torch.compile sees a fixed-size buffer.
93
+ cols = torch.arange(window, device=device, dtype=positions.dtype).view(1, window)
94
+ min_valid_col = torch.clamp((window - 1) - end_pos, min=0)
95
+ max_valid_col = positions.view(-1, 1) - end_pos + (window - 1)
96
+ valid = (cols >= min_valid_col) & (cols <= max_valid_col)
97
+ zeros = torch.zeros((positions.numel(), window), device=device, dtype=dtype)
98
+ neg_inf = torch.full(
99
+ (positions.numel(), window), torch.finfo(dtype).min, device=device, dtype=dtype
100
+ )
101
+ mask = torch.where(valid, zeros, neg_inf)
102
+ return mask.view(1, 1, positions.numel(), window).expand(
103
+ batch, 1, positions.numel(), window
104
+ )
models/stream_runtime/stream/lane.py ADDED
@@ -0,0 +1,435 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ import torch
6
+ from torch import nn
7
+
8
+ from ..core.compat import (
9
+ Qwen3TTSTokenizer,
10
+ Qwen3TTSTokenizerV2CausalConvNet,
11
+ Qwen3TTSTokenizerV2CausalTransConvNet,
12
+ Qwen3TTSTokenizerV2ConvNeXtBlock,
13
+ Qwen3TTSTokenizerV2Decoder,
14
+ Qwen3TTSTokenizerV2DecoderDecoderBlock,
15
+ Qwen3TTSTokenizerV2DecoderDecoderResidualUnit,
16
+ )
17
+ from .kv_cache import StaticShiftKVCache, make_fixed_attention_mask
18
+ from .state import ConvStateBlock, RequestStateSlot, TransConvStateBlock
19
+ from .workspace import ConvWorkspaceBlock, TransConvWorkspaceBlock, WorkspaceSlot
20
+
21
+
22
+ def _causal_conv_left_cache_len(conv: Qwen3TTSTokenizerV2CausalConvNet) -> int:
23
+ return int(conv.padding)
24
+
25
+
26
+ def _tconv_left_cache_len(conv: Qwen3TTSTokenizerV2CausalTransConvNet) -> int:
27
+ kernel = int(conv.conv.kernel_size[0])
28
+ stride = int(conv.conv.stride[0])
29
+ return int((kernel - 1) // stride)
30
+
31
+
32
+ def _cg_mark() -> None:
33
+ if hasattr(torch, "compiler") and hasattr(
34
+ torch.compiler, "cudagraph_mark_step_begin"
35
+ ):
36
+ torch.compiler.cudagraph_mark_step_begin()
37
+
38
+
39
+ def _ensure_canonical_ncl(x: torch.Tensor) -> torch.Tensor:
40
+ """Materialize canonical contiguous NCL strides before compiled convolutions."""
41
+ x = x.contiguous()
42
+ if x.ndim != 3 or (x.stride(-1) == 1 and x.stride(-2) == x.shape[-1]):
43
+ return x
44
+ y = torch.empty_like(x, memory_format=torch.contiguous_format)
45
+ y.copy_(x)
46
+ return y
47
+
48
+
49
+ @dataclass
50
+ class LaneBinding:
51
+ request_slot: RequestStateSlot
52
+ workspace_slot: WorkspaceSlot
53
+
54
+
55
+ class StaticCachedQwenCausalConv1dV2(nn.Module):
56
+ def __init__(
57
+ self, name: str, conv: Qwen3TTSTokenizerV2CausalConvNet, left_cache_len: int
58
+ ):
59
+ super().__init__()
60
+ self.name = name
61
+ self.conv = conv
62
+ self.left_cache_len = int(left_cache_len)
63
+
64
+ def forward(
65
+ self,
66
+ x: torch.Tensor,
67
+ state: dict[str, ConvStateBlock],
68
+ workspace: dict[str, ConvWorkspaceBlock],
69
+ ) -> torch.Tensor:
70
+ x = _ensure_canonical_ncl(x)
71
+ if self.left_cache_len <= 0:
72
+ return self.conv(x)
73
+ s = state[self.name]
74
+ w = workspace[self.name]
75
+ if not torch._dynamo.is_compiling():
76
+ if s.cache_buf.shape[-1] != self.left_cache_len:
77
+ raise ValueError(f"{self.name} cache length mismatch.")
78
+ if w.x_buf.shape[-1] != self.left_cache_len + x.shape[-1]:
79
+ raise ValueError(f"{self.name} x_buf length mismatch.")
80
+ w.x_buf[..., : self.left_cache_len].copy_(s.cache_buf)
81
+ w.x_buf[..., self.left_cache_len :].copy_(x)
82
+ conv_input = _ensure_canonical_ncl(w.x_buf)
83
+ y = self.conv(conv_input)
84
+ s.cache_buf.copy_(conv_input[..., -self.left_cache_len :])
85
+ return y[..., -x.shape[-1] :]
86
+
87
+
88
+ class StaticCachedQwenTransposedConv1dV2(nn.Module):
89
+ def __init__(
90
+ self,
91
+ name: str,
92
+ conv: Qwen3TTSTokenizerV2CausalTransConvNet,
93
+ left_cache_len: int,
94
+ ):
95
+ super().__init__()
96
+ self.name = name
97
+ self.conv = conv
98
+ self.left_cache_len = int(left_cache_len)
99
+
100
+ def forward(
101
+ self,
102
+ x: torch.Tensor,
103
+ state: dict[str, TransConvStateBlock],
104
+ workspace: dict[str, TransConvWorkspaceBlock],
105
+ ) -> torch.Tensor:
106
+ x = _ensure_canonical_ncl(x)
107
+ if self.left_cache_len <= 0:
108
+ return self.conv(x)
109
+ s = state[self.name]
110
+ w = workspace[self.name]
111
+ if not torch._dynamo.is_compiling():
112
+ if s.cache_buf.shape[-1] != self.left_cache_len:
113
+ raise ValueError(f"{self.name} cache length mismatch.")
114
+ if w.x_buf.shape[-1] != self.left_cache_len + x.shape[-1]:
115
+ raise ValueError(f"{self.name} x_buf length mismatch.")
116
+ w.x_buf[..., : self.left_cache_len].copy_(s.cache_buf)
117
+ w.x_buf[..., self.left_cache_len :].copy_(x)
118
+ conv_input = _ensure_canonical_ncl(w.x_buf)
119
+ y = self.conv(conv_input)
120
+ s.cache_buf.copy_(conv_input[..., -self.left_cache_len :])
121
+ stride = int(self.conv.conv.stride[0])
122
+ prefix = self.left_cache_len * stride
123
+ new_len = x.shape[-1] * stride
124
+ return y[..., prefix : prefix + new_len]
125
+
126
+
127
+ class StaticCachedConvNeXtBlockV2(nn.Module):
128
+ def __init__(self, name: str, block: Qwen3TTSTokenizerV2ConvNeXtBlock):
129
+ super().__init__()
130
+ self.block = block
131
+ self.dwconv = StaticCachedQwenCausalConv1dV2(
132
+ name=name,
133
+ conv=block.dwconv,
134
+ left_cache_len=_causal_conv_left_cache_len(block.dwconv),
135
+ )
136
+
137
+ def forward(
138
+ self, hidden_states: torch.Tensor, binding: LaneBinding
139
+ ) -> torch.Tensor:
140
+ residual = hidden_states
141
+ hidden_states = self.dwconv(
142
+ hidden_states, binding.request_slot.conv1d, binding.workspace_slot.conv1d
143
+ )
144
+ hidden_states = hidden_states.permute(0, 2, 1).contiguous()
145
+ hidden_states = self.block.norm(hidden_states)
146
+ hidden_states = self.block.pwconv1(hidden_states)
147
+ hidden_states = self.block.act(hidden_states)
148
+ hidden_states = self.block.pwconv2(hidden_states)
149
+ hidden_states = self.block.gamma * hidden_states
150
+ hidden_states = hidden_states.permute(0, 2, 1).contiguous()
151
+ return residual + hidden_states
152
+
153
+
154
+ class StaticCachedDecoderResidualUnitV2(nn.Module):
155
+ def __init__(self, name: str, unit: Qwen3TTSTokenizerV2DecoderDecoderResidualUnit):
156
+ super().__init__()
157
+ self.unit = unit
158
+ self.conv1 = StaticCachedQwenCausalConv1dV2(
159
+ name=name,
160
+ conv=unit.conv1,
161
+ left_cache_len=_causal_conv_left_cache_len(unit.conv1),
162
+ )
163
+
164
+ def forward(self, hidden_state: torch.Tensor, binding: LaneBinding) -> torch.Tensor:
165
+ residual = hidden_state.contiguous()
166
+ hidden_state = self.unit.act1(hidden_state).contiguous()
167
+ hidden_state = self.conv1(
168
+ hidden_state, binding.request_slot.conv1d, binding.workspace_slot.conv1d
169
+ )
170
+ hidden_state = self.unit.act2(hidden_state).contiguous()
171
+ hidden_state = self.unit.conv2(_ensure_canonical_ncl(hidden_state))
172
+ return hidden_state + residual
173
+
174
+
175
+ class StaticCachedDecoderBlockV2(nn.Module):
176
+ def __init__(self, name: str, block: Qwen3TTSTokenizerV2DecoderDecoderBlock):
177
+ super().__init__()
178
+ self.block = block
179
+ self.tconv = StaticCachedQwenTransposedConv1dV2(
180
+ name=f"{name}_tconv",
181
+ conv=block.block[1],
182
+ left_cache_len=_tconv_left_cache_len(block.block[1]),
183
+ )
184
+ self.residual_units = nn.ModuleList(
185
+ [
186
+ StaticCachedDecoderResidualUnitV2(f"{name}_residual_{idx}_conv1", unit)
187
+ for idx, unit in enumerate(block.block[2:])
188
+ ]
189
+ )
190
+
191
+ def forward(self, hidden: torch.Tensor, binding: LaneBinding) -> torch.Tensor:
192
+ hidden = self.block.block[0](hidden).contiguous()
193
+ hidden = self.tconv(
194
+ hidden, binding.request_slot.tconv1d, binding.workspace_slot.tconv1d
195
+ )
196
+ for unit in self.residual_units:
197
+ hidden = unit(hidden, binding)
198
+ return hidden
199
+
200
+
201
+ def _copy_conv_state(
202
+ dst: dict[str, ConvStateBlock], src: dict[str, ConvStateBlock]
203
+ ) -> None:
204
+ for name, block in dst.items():
205
+ block.cache_buf.copy_(src[name].cache_buf)
206
+
207
+
208
+ def _copy_tconv_state(
209
+ dst: dict[str, TransConvStateBlock], src: dict[str, TransConvStateBlock]
210
+ ) -> None:
211
+ for name, block in dst.items():
212
+ block.cache_buf.copy_(src[name].cache_buf)
213
+
214
+
215
+ def _copy_kv_state(dst: StaticShiftKVCache, src: StaticShiftKVCache) -> None:
216
+ for dst_layer, src_layer in zip(dst.layers, src.layers):
217
+ dst_layer.k.copy_(src_layer.k)
218
+ dst_layer.v.copy_(src_layer.v)
219
+
220
+
221
+ def _reset_request_state_tensors(slot: RequestStateSlot) -> None:
222
+ for block in slot.conv1d.values():
223
+ block.cache_buf.zero_()
224
+ for block in slot.tconv1d.values():
225
+ block.cache_buf.zero_()
226
+ for layer in slot.kv.layers:
227
+ layer.k.zero_()
228
+ layer.v.zero_()
229
+
230
+
231
+ class _LaneCore(nn.Module):
232
+ def __init__(
233
+ self,
234
+ decoder: Qwen3TTSTokenizerV2Decoder,
235
+ chunk_frames: int,
236
+ binding: LaneBinding,
237
+ ):
238
+ super().__init__()
239
+ self.__dict__["decoder_ref"] = decoder
240
+ self.config = decoder.config
241
+ self.chunk_frames = int(chunk_frames)
242
+ self.binding = binding
243
+ self.pre_conv = StaticCachedQwenCausalConv1dV2(
244
+ "pre_conv",
245
+ decoder.pre_conv,
246
+ left_cache_len=_causal_conv_left_cache_len(decoder.pre_conv),
247
+ )
248
+ self.upsample = nn.ModuleList()
249
+ for idx, blocks in enumerate(decoder.upsample):
250
+ self.upsample.append(
251
+ nn.ModuleList(
252
+ [
253
+ StaticCachedQwenTransposedConv1dV2(
254
+ f"upsample_{idx}_tconv",
255
+ blocks[0],
256
+ left_cache_len=_tconv_left_cache_len(blocks[0]),
257
+ ),
258
+ StaticCachedConvNeXtBlockV2(
259
+ f"upsample_{idx}_dwconv", blocks[1]
260
+ ),
261
+ ]
262
+ )
263
+ )
264
+ self.decoder_pre_conv = StaticCachedQwenCausalConv1dV2(
265
+ "decoder_pre_conv",
266
+ decoder.decoder[0],
267
+ left_cache_len=_causal_conv_left_cache_len(decoder.decoder[0]),
268
+ )
269
+ self.decoder_blocks = nn.ModuleList(
270
+ [
271
+ StaticCachedDecoderBlockV2(f"decoder_block_{idx}", block)
272
+ for idx, block in enumerate(decoder.decoder[1:-2])
273
+ ]
274
+ )
275
+ self.decoder_snake = decoder.decoder[-2]
276
+ self.final_conv = StaticCachedQwenCausalConv1dV2(
277
+ "final_conv",
278
+ decoder.decoder[-1],
279
+ left_cache_len=_causal_conv_left_cache_len(decoder.decoder[-1]),
280
+ )
281
+ self.pre_transformer = decoder.pre_transformer
282
+ self.quantizer = decoder.quantizer
283
+
284
+ def forward(
285
+ self, codes: torch.Tensor, cache_position: torch.Tensor
286
+ ) -> torch.Tensor:
287
+ binding = self.binding
288
+ hidden = self.quantizer.decode(codes)
289
+ hidden = (
290
+ self.pre_conv(
291
+ hidden, binding.request_slot.conv1d, binding.workspace_slot.conv1d
292
+ )
293
+ .transpose(1, 2)
294
+ .contiguous()
295
+ )
296
+ attn_mask = make_fixed_attention_mask(
297
+ batch=hidden.shape[0],
298
+ window=self.config.sliding_window,
299
+ cache_position=cache_position,
300
+ device=hidden.device,
301
+ dtype=hidden.dtype,
302
+ )
303
+ hidden = self.pre_transformer(
304
+ inputs_embeds=hidden,
305
+ use_cache=True,
306
+ past_key_values=binding.request_slot.kv,
307
+ cache_position=cache_position,
308
+ attention_mask=attn_mask,
309
+ ).last_hidden_state
310
+ hidden = hidden.permute(0, 2, 1).contiguous()
311
+ for stage in self.upsample:
312
+ hidden = stage[0](
313
+ hidden, binding.request_slot.tconv1d, binding.workspace_slot.tconv1d
314
+ )
315
+ hidden = stage[1](hidden, binding)
316
+ wav = self.decoder_pre_conv(
317
+ hidden, binding.request_slot.conv1d, binding.workspace_slot.conv1d
318
+ )
319
+ for block in self.decoder_blocks:
320
+ wav = block(wav, binding)
321
+ wav = self.decoder_snake(wav).contiguous()
322
+ wav = self.final_conv(
323
+ wav, binding.request_slot.conv1d, binding.workspace_slot.conv1d
324
+ )
325
+ return wav.clamp(min=-1, max=1)
326
+
327
+
328
+ class ExecutionLane:
329
+ def __init__(
330
+ self,
331
+ lane_id: int,
332
+ tokenizer: Qwen3TTSTokenizer,
333
+ chunk_frames: int,
334
+ hot_state: RequestStateSlot,
335
+ workspace: WorkspaceSlot,
336
+ fast: bool = False,
337
+ ):
338
+ if tokenizer.model is None:
339
+ raise ValueError("Tokenizer model is not loaded.")
340
+ self.chunk_frames = int(chunk_frames)
341
+ self.lane_id = int(lane_id)
342
+ self.binding = LaneBinding(request_slot=hot_state, workspace_slot=workspace)
343
+ self.core = _LaneCore(
344
+ tokenizer.model.decoder, chunk_frames=chunk_frames, binding=self.binding
345
+ )
346
+ num_quantizers = int(tokenizer.model.decoder.config.num_quantizers)
347
+ state_device = next(iter(hot_state.conv1d.values())).cache_buf.device
348
+ self.codes_in_buf = torch.zeros(
349
+ (1, num_quantizers, self.chunk_frames),
350
+ device=state_device,
351
+ dtype=torch.long,
352
+ )
353
+ self.cache_position_buf = torch.zeros(
354
+ (self.chunk_frames,), device=state_device, dtype=torch.long
355
+ )
356
+ self._base_cache_position = torch.arange(
357
+ self.chunk_frames, device=state_device, dtype=torch.long
358
+ )
359
+ self.step_model = self.core
360
+ self.fast = bool(fast)
361
+ self.cuda_graph: torch.cuda.CUDAGraph | None = None
362
+ self.cuda_graph_out: torch.Tensor | None = None
363
+
364
+ def load_request_state(self, state: RequestStateSlot) -> None:
365
+ self.binding.request_slot.next_step = int(state.next_step)
366
+ self.binding.request_slot.req_id = state.req_id
367
+ self.binding.request_slot.tail_flushed = bool(state.tail_flushed)
368
+ _copy_conv_state(self.binding.request_slot.conv1d, state.conv1d)
369
+ _copy_tconv_state(self.binding.request_slot.tconv1d, state.tconv1d)
370
+ _copy_kv_state(self.binding.request_slot.kv, state.kv)
371
+
372
+ def store_request_state(self, state: RequestStateSlot) -> None:
373
+ state.req_id = self.binding.request_slot.req_id
374
+ state.next_step = int(self.binding.request_slot.next_step)
375
+ state.tail_flushed = bool(self.binding.request_slot.tail_flushed)
376
+ _copy_conv_state(state.conv1d, self.binding.request_slot.conv1d)
377
+ _copy_tconv_state(state.tconv1d, self.binding.request_slot.tconv1d)
378
+ _copy_kv_state(state.kv, self.binding.request_slot.kv)
379
+
380
+ def reset_hot_state(self) -> None:
381
+ self.binding.request_slot.req_id = None
382
+ self.binding.request_slot.next_step = 0
383
+ self.binding.request_slot.tail_flushed = False
384
+ _reset_request_state_tensors(self.binding.request_slot)
385
+ self.codes_in_buf.zero_()
386
+ self.cache_position_buf.zero_()
387
+ for block in self.binding.workspace_slot.conv1d.values():
388
+ block.x_buf.zero_()
389
+ for block in self.binding.workspace_slot.tconv1d.values():
390
+ block.x_buf.zero_()
391
+
392
+ def warmup_cuda_graph(self, warmup_rounds: int = 2) -> None:
393
+ """Capture the stateful fixed-shape codec step after Snake compilation."""
394
+ if not self.fast or self.cuda_graph is not None:
395
+ return
396
+ device = self.codes_in_buf.device
397
+ if device.type != "cuda":
398
+ raise RuntimeError("codec CUDA Graph fast path requires CUDA")
399
+ capture_stream = torch.cuda.Stream(device=device)
400
+ capture_stream.wait_stream(torch.cuda.current_stream(device))
401
+ with torch.cuda.stream(capture_stream):
402
+ for _ in range(max(1, warmup_rounds)):
403
+ self.cuda_graph_out = self.step_model(
404
+ self.codes_in_buf, self.cache_position_buf
405
+ )
406
+ capture_stream.synchronize()
407
+ self.cuda_graph = torch.cuda.CUDAGraph()
408
+ with torch.cuda.graph(
409
+ self.cuda_graph,
410
+ stream=capture_stream,
411
+ capture_error_mode="thread_local",
412
+ ):
413
+ self.cuda_graph_out = self.step_model(
414
+ self.codes_in_buf, self.cache_position_buf
415
+ )
416
+ torch.cuda.current_stream(device).wait_stream(capture_stream)
417
+
418
+ def run_step(self, codes_chunk: torch.Tensor, step_idx: int) -> torch.Tensor:
419
+ t_new = int(codes_chunk.shape[-1])
420
+ if t_new != self.chunk_frames:
421
+ raise ValueError(
422
+ f"ExecutionLane expects fixed chunk_frames={self.chunk_frames}, got step len={t_new}."
423
+ )
424
+ self.codes_in_buf.copy_(codes_chunk)
425
+ self.cache_position_buf.copy_(self._base_cache_position)
426
+ self.cache_position_buf.add_(int(step_idx))
427
+ _cg_mark()
428
+ if self.cuda_graph is None:
429
+ out = self.step_model(self.codes_in_buf, self.cache_position_buf)
430
+ else:
431
+ self.cuda_graph.replay()
432
+ assert self.cuda_graph_out is not None
433
+ out = self.cuda_graph_out
434
+ self.binding.request_slot.next_step = int(step_idx) + t_new
435
+ return out
models/stream_runtime/stream/runtime.py ADDED
@@ -0,0 +1,746 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import time
5
+ from collections import OrderedDict
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ import torch
10
+
11
+ from ..core.compat import Qwen3TTSTokenizer
12
+ from .kv_cache import StaticShiftKVCache
13
+ from .lane import ExecutionLane, _causal_conv_left_cache_len, _tconv_left_cache_len
14
+ from .state import (
15
+ ConvStateBlock,
16
+ RequestStatePool,
17
+ RequestStateSlot,
18
+ TransConvStateBlock,
19
+ reset_request_state,
20
+ )
21
+ from .workspace import (
22
+ ConvWorkspaceBlock,
23
+ TransConvWorkspaceBlock,
24
+ WorkspacePool,
25
+ WorkspaceSlot,
26
+ reset_workspace,
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ @dataclass
33
+ class QwenStreamRuntimeConfig:
34
+ chunk_frames: int = 1
35
+ # Strategy for a residual tail when len(codes) % chunk_frames != 0 and chunk_frames > 1.
36
+ # - "eager": decode the tail one frame at a time on a dedicated eager lane.
37
+ # This avoids pad contamination at the tail boundary and is the default.
38
+ # - "pad": keep the existing fixed-shape path by right-padding the tail to chunk_frames,
39
+ # decoding it on the main lane, then trimming the wav back to the expected length.
40
+ non_integer_chunk_strategy: str = "eager"
41
+ num_lanes: int = 1
42
+ max_active_reqs: int | None = None
43
+ fast: bool = False
44
+ lifecycle_assert_mode: str = "warn"
45
+ tombstone_capacity: int = 1024
46
+ device: torch.device | None = None
47
+ dtype: torch.dtype = torch.float32
48
+
49
+
50
+ @dataclass
51
+ class TombstoneEntry:
52
+ reason: str
53
+
54
+
55
+ def _tensor_bytes(tensor: torch.Tensor) -> int:
56
+ return int(tensor.numel() * tensor.element_size())
57
+
58
+
59
+ def _format_bytes(num_bytes: int) -> str:
60
+ units = ["B", "KiB", "MiB", "GiB", "TiB"]
61
+ value = float(num_bytes)
62
+ for unit in units:
63
+ if value < 1024.0 or unit == units[-1]:
64
+ return f"{value:.2f} {unit}"
65
+ value /= 1024.0
66
+ return f"{num_bytes} B"
67
+
68
+
69
+ def _request_state_bytes(slot: RequestStateSlot) -> int:
70
+ total = 0
71
+ for block in slot.conv1d.values():
72
+ total += _tensor_bytes(block.cache_buf)
73
+ for block in slot.tconv1d.values():
74
+ total += _tensor_bytes(block.cache_buf)
75
+ for layer in slot.kv.layers:
76
+ total += _tensor_bytes(layer.k)
77
+ total += _tensor_bytes(layer.v)
78
+ return total
79
+
80
+
81
+ def _workspace_bytes(slot: WorkspaceSlot) -> int:
82
+ total = 0
83
+ for block in slot.conv1d.values():
84
+ total += _tensor_bytes(block.x_buf)
85
+ for block in slot.tconv1d.values():
86
+ total += _tensor_bytes(block.x_buf)
87
+ return total
88
+
89
+
90
+ def _step_lengths(decoder, chunk_frames: int) -> dict[str, int]:
91
+ lengths: dict[str, int] = {}
92
+ cur = int(chunk_frames)
93
+ lengths["pre_conv"] = cur
94
+ for idx, blocks in enumerate(decoder.upsample):
95
+ lengths[f"upsample_{idx}_tconv"] = cur
96
+ stride = int(blocks[0].conv.stride[0])
97
+ cur = cur * stride
98
+ lengths[f"upsample_{idx}_dwconv"] = cur
99
+ lengths["decoder_pre_conv"] = cur
100
+ for idx, block in enumerate(decoder.decoder[1:-2]):
101
+ lengths[f"decoder_block_{idx}_tconv"] = cur
102
+ stride = int(block.block[1].conv.stride[0])
103
+ cur = cur * stride
104
+ for ridx, _unit in enumerate(block.block[2:]):
105
+ lengths[f"decoder_block_{idx}_residual_{ridx}_conv1"] = cur
106
+ lengths["final_conv"] = cur
107
+ return lengths
108
+
109
+
110
+ def build_request_state_slot(
111
+ tokenizer: Qwen3TTSTokenizer,
112
+ chunk_frames: int,
113
+ device: torch.device,
114
+ dtype: torch.dtype,
115
+ ) -> RequestStateSlot:
116
+ decoder = tokenizer.model.decoder # type: ignore[union-attr]
117
+ config = decoder.config
118
+ conv1d: dict[str, ConvStateBlock] = {}
119
+ tconv1d: dict[str, TransConvStateBlock] = {}
120
+
121
+ def add_conv(name: str, channels: int, left: int):
122
+ conv1d[name] = ConvStateBlock(
123
+ cache_buf=torch.zeros((1, channels, left), device=device, dtype=dtype),
124
+ left_cache_len=left,
125
+ )
126
+
127
+ def add_tconv(name: str, channels: int, left: int):
128
+ tconv1d[name] = TransConvStateBlock(
129
+ cache_buf=torch.zeros((1, channels, left), device=device, dtype=dtype),
130
+ left_cache_len=left,
131
+ )
132
+
133
+ add_conv(
134
+ "pre_conv", config.codebook_dim, _causal_conv_left_cache_len(decoder.pre_conv)
135
+ )
136
+ for idx, blocks in enumerate(decoder.upsample):
137
+ add_tconv(
138
+ f"upsample_{idx}_tconv",
139
+ blocks[0].conv.in_channels,
140
+ _tconv_left_cache_len(blocks[0]),
141
+ )
142
+ add_conv(
143
+ f"upsample_{idx}_dwconv",
144
+ blocks[1].dwconv.conv.in_channels,
145
+ _causal_conv_left_cache_len(blocks[1].dwconv),
146
+ )
147
+ add_conv(
148
+ "decoder_pre_conv",
149
+ config.latent_dim,
150
+ _causal_conv_left_cache_len(decoder.decoder[0]),
151
+ )
152
+ for idx, block in enumerate(decoder.decoder[1:-2]):
153
+ add_tconv(
154
+ f"decoder_block_{idx}_tconv",
155
+ block.block[1].conv.in_channels,
156
+ _tconv_left_cache_len(block.block[1]),
157
+ )
158
+ for ridx, unit in enumerate(block.block[2:]):
159
+ add_conv(
160
+ f"decoder_block_{idx}_residual_{ridx}_conv1",
161
+ unit.conv1.conv.in_channels,
162
+ _causal_conv_left_cache_len(unit.conv1),
163
+ )
164
+ add_conv(
165
+ "final_conv",
166
+ decoder.decoder[-1].conv.in_channels,
167
+ _causal_conv_left_cache_len(decoder.decoder[-1]),
168
+ )
169
+ kv = StaticShiftKVCache(
170
+ num_layers=config.num_hidden_layers,
171
+ window=config.sliding_window,
172
+ batch=1,
173
+ h_kv=config.num_key_value_heads,
174
+ head_dim=getattr(
175
+ config, "head_dim", config.hidden_size // config.num_attention_heads
176
+ ),
177
+ device=device,
178
+ dtype=dtype,
179
+ )
180
+ return RequestStateSlot(
181
+ req_id=None,
182
+ next_step=0,
183
+ tail_flushed=False,
184
+ conv1d=conv1d,
185
+ tconv1d=tconv1d,
186
+ kv=kv,
187
+ )
188
+
189
+
190
+ def build_workspace_slot(
191
+ tokenizer: Qwen3TTSTokenizer,
192
+ chunk_frames: int,
193
+ device: torch.device,
194
+ dtype: torch.dtype,
195
+ ) -> WorkspaceSlot:
196
+ decoder = tokenizer.model.decoder # type: ignore[union-attr]
197
+ step_lengths = _step_lengths(decoder, chunk_frames)
198
+ conv1d: dict[str, ConvWorkspaceBlock] = {}
199
+ tconv1d: dict[str, TransConvWorkspaceBlock] = {}
200
+
201
+ def add_conv(name: str, channels: int, left: int):
202
+ conv1d[name] = ConvWorkspaceBlock(
203
+ x_buf=torch.zeros(
204
+ (1, channels, left + step_lengths[name]), device=device, dtype=dtype
205
+ )
206
+ )
207
+
208
+ def add_tconv(name: str, channels: int, left: int):
209
+ tconv1d[name] = TransConvWorkspaceBlock(
210
+ x_buf=torch.zeros(
211
+ (1, channels, left + step_lengths[name]), device=device, dtype=dtype
212
+ )
213
+ )
214
+
215
+ add_conv(
216
+ "pre_conv",
217
+ decoder.config.codebook_dim,
218
+ _causal_conv_left_cache_len(decoder.pre_conv),
219
+ )
220
+ for idx, blocks in enumerate(decoder.upsample):
221
+ add_tconv(
222
+ f"upsample_{idx}_tconv",
223
+ blocks[0].conv.in_channels,
224
+ _tconv_left_cache_len(blocks[0]),
225
+ )
226
+ add_conv(
227
+ f"upsample_{idx}_dwconv",
228
+ blocks[1].dwconv.conv.in_channels,
229
+ _causal_conv_left_cache_len(blocks[1].dwconv),
230
+ )
231
+ add_conv(
232
+ "decoder_pre_conv",
233
+ decoder.config.latent_dim,
234
+ _causal_conv_left_cache_len(decoder.decoder[0]),
235
+ )
236
+ for idx, block in enumerate(decoder.decoder[1:-2]):
237
+ add_tconv(
238
+ f"decoder_block_{idx}_tconv",
239
+ block.block[1].conv.in_channels,
240
+ _tconv_left_cache_len(block.block[1]),
241
+ )
242
+ for ridx, unit in enumerate(block.block[2:]):
243
+ add_conv(
244
+ f"decoder_block_{idx}_residual_{ridx}_conv1",
245
+ unit.conv1.conv.in_channels,
246
+ _causal_conv_left_cache_len(unit.conv1),
247
+ )
248
+ add_conv(
249
+ "final_conv",
250
+ decoder.decoder[-1].conv.in_channels,
251
+ _causal_conv_left_cache_len(decoder.decoder[-1]),
252
+ )
253
+ return WorkspaceSlot(conv1d=conv1d, tconv1d=tconv1d)
254
+
255
+
256
+ class MultiRequestStreamRuntime:
257
+ def __init__(self, tokenizer: Qwen3TTSTokenizer, config: QwenStreamRuntimeConfig):
258
+ if tokenizer.model is None:
259
+ raise ValueError("Tokenizer model is not loaded.")
260
+ if config.device is None:
261
+ try:
262
+ config.device = next(tokenizer.model.parameters()).device
263
+ except StopIteration:
264
+ config.device = torch.device("cpu")
265
+ self.tokenizer = tokenizer
266
+ self.config = config
267
+ if self.config.lifecycle_assert_mode not in {"off", "warn", "raise"}:
268
+ raise ValueError(
269
+ f"lifecycle_assert_mode must be one of off/warn/raise, got {self.config.lifecycle_assert_mode}"
270
+ )
271
+ if self.config.tombstone_capacity < 0:
272
+ raise ValueError(
273
+ f"tombstone_capacity must be >= 0, got {self.config.tombstone_capacity}"
274
+ )
275
+ if self.config.non_integer_chunk_strategy not in {"eager", "pad"}:
276
+ raise ValueError(
277
+ "non_integer_chunk_strategy must be one of {'eager', 'pad'}, "
278
+ f"got {self.config.non_integer_chunk_strategy!r}"
279
+ )
280
+ if self.config.fast and (
281
+ self.config.num_lanes != 1 or self.config.max_active_reqs not in {None, 1}
282
+ ):
283
+ raise ValueError("fast codec requires num_lanes=1 and max_active_reqs<=1")
284
+ if (
285
+ self.config.fast
286
+ and self.config.device is not None
287
+ and self.config.device.type == "cuda"
288
+ ):
289
+ torch.backends.cudnn.benchmark = True
290
+ self._compile_snakes()
291
+ self._req_tombstones: OrderedDict[str, TombstoneEntry] = OrderedDict()
292
+ self._enforce_compile_attention_backend()
293
+ self.request_pool = RequestStatePool(
294
+ create_slot=lambda: build_request_state_slot(
295
+ self.tokenizer,
296
+ self.config.chunk_frames,
297
+ self.config.device,
298
+ self.config.dtype,
299
+ ),
300
+ reset_slot=reset_request_state,
301
+ max_active_reqs=self.config.max_active_reqs,
302
+ on_evict=self._on_request_evicted,
303
+ )
304
+ self.workspace_pool = WorkspacePool(
305
+ create_slot=lambda: build_workspace_slot(
306
+ self.tokenizer,
307
+ self.config.chunk_frames,
308
+ self.config.device,
309
+ self.config.dtype,
310
+ ),
311
+ reset_slot=reset_workspace,
312
+ size=self.config.num_lanes,
313
+ )
314
+ self.lanes: list[ExecutionLane] = []
315
+ self.tail_eager_lanes: list[ExecutionLane] = []
316
+ self.req_to_lane: dict[str, int] = {}
317
+ self._single_lane_req_id: str | None = None
318
+ for lane_id in range(self.config.num_lanes):
319
+ workspace_idx, workspace_slot = self.workspace_pool.acquire()
320
+ if workspace_idx != lane_id:
321
+ raise RuntimeError(
322
+ "Workspace pool returned unexpected lane slot order."
323
+ )
324
+ hot_state = build_request_state_slot(
325
+ self.tokenizer,
326
+ self.config.chunk_frames,
327
+ self.config.device,
328
+ self.config.dtype,
329
+ )
330
+ self.lanes.append(
331
+ ExecutionLane(
332
+ lane_id=lane_id,
333
+ tokenizer=self.tokenizer,
334
+ chunk_frames=self.config.chunk_frames,
335
+ hot_state=hot_state,
336
+ workspace=workspace_slot,
337
+ fast=self.config.fast,
338
+ )
339
+ )
340
+ if (
341
+ self.config.chunk_frames > 1
342
+ and self.config.non_integer_chunk_strategy == "eager"
343
+ ):
344
+ eager_tail_hot_state = build_request_state_slot(
345
+ self.tokenizer,
346
+ chunk_frames=1,
347
+ device=self.config.device,
348
+ dtype=self.config.dtype,
349
+ )
350
+ eager_tail_workspace = build_workspace_slot(
351
+ self.tokenizer,
352
+ chunk_frames=1,
353
+ device=self.config.device,
354
+ dtype=self.config.dtype,
355
+ )
356
+ # Residual tails are rare and only happen at request shutdown. Keep this fallback
357
+ # eager so the default path avoids padding a future code into the tail boundary.
358
+ self.tail_eager_lanes.append(
359
+ ExecutionLane(
360
+ lane_id=lane_id,
361
+ tokenizer=self.tokenizer,
362
+ chunk_frames=1,
363
+ hot_state=eager_tail_hot_state,
364
+ workspace=eager_tail_workspace,
365
+ fast=False,
366
+ )
367
+ )
368
+ self.samples_per_code = self._validate_and_get_samples_per_code()
369
+ self._log_runtime_config()
370
+ self._maybe_warmup_fast_codec()
371
+
372
+ def _validate_and_get_samples_per_code(self) -> int:
373
+ theoretical_chunk_out = _step_lengths(
374
+ self.tokenizer.model.decoder, self.config.chunk_frames
375
+ )["final_conv"] # type: ignore[union-attr]
376
+ if theoretical_chunk_out % self.config.chunk_frames != 0:
377
+ logger.error(
378
+ "breeze_codec invalid chunk->wav ratio: theoretical_chunk_out=%d chunk_frames=%d is not divisible",
379
+ theoretical_chunk_out,
380
+ self.config.chunk_frames,
381
+ )
382
+ theoretical_spc = theoretical_chunk_out // self.config.chunk_frames
383
+ num_quantizers = int(self.tokenizer.model.decoder.config.num_quantizers) # type: ignore[union-attr]
384
+ dummy_codes = torch.zeros(
385
+ (1, num_quantizers, self.config.chunk_frames),
386
+ device=self.config.device,
387
+ dtype=torch.long,
388
+ )
389
+ with torch.inference_mode():
390
+ observed = self.tokenizer.model.decoder(dummy_codes) # type: ignore[union-attr]
391
+ observed_chunk_out = int(observed.shape[-1])
392
+ if observed_chunk_out % self.config.chunk_frames != 0:
393
+ logger.error(
394
+ "breeze_codec observed non-integer chunk->wav ratio: observed_chunk_out=%d chunk_frames=%d",
395
+ observed_chunk_out,
396
+ self.config.chunk_frames,
397
+ )
398
+ observed_spc = observed_chunk_out // self.config.chunk_frames
399
+ if observed_spc != theoretical_spc:
400
+ logger.error(
401
+ "breeze_codec chunk->wav ratio mismatch: theoretical=%d observed=%d",
402
+ theoretical_spc,
403
+ observed_spc,
404
+ )
405
+ else:
406
+ logger.info(
407
+ "breeze_codec chunk->wav ratio verified: chunk_frames=%d chunk_output=%d samples_per_code=%d",
408
+ self.config.chunk_frames,
409
+ observed_chunk_out,
410
+ observed_spc,
411
+ )
412
+ return int(observed_spc)
413
+
414
+ def _compile_snakes(self) -> None:
415
+ if not self.config.fast:
416
+ return
417
+ torch._dynamo.config.recompile_limit = max(
418
+ int(torch._dynamo.config.recompile_limit), 64
419
+ )
420
+ torch._dynamo.config.accumulated_recompile_limit = max(
421
+ int(torch._dynamo.config.accumulated_recompile_limit), 256
422
+ )
423
+ compiled = 0
424
+ for module in self.tokenizer.model.decoder.modules():
425
+ if module.__class__.__name__ != "SnakeBeta":
426
+ continue
427
+ module.forward = torch.compile(
428
+ module.forward,
429
+ mode="max-autotune-no-cudagraphs",
430
+ fullgraph=True,
431
+ dynamic=True,
432
+ )
433
+ compiled += 1
434
+ logger.info("breeze_codec compiled %d SnakeBeta modules", compiled)
435
+
436
+ def _enforce_compile_attention_backend(self) -> None:
437
+ if not self.config.fast:
438
+ return
439
+ decoder = self.tokenizer.model.decoder # type: ignore[union-attr]
440
+ pre_transformer = getattr(decoder, "pre_transformer", None)
441
+ if pre_transformer is None or not hasattr(pre_transformer, "config"):
442
+ return
443
+ current_impl = getattr(pre_transformer.config, "_attn_implementation", None)
444
+ if current_impl != "eager":
445
+ logger.warning(
446
+ "breeze_codec forcing pre_transformer attention backend to eager for fast codec. previous=%s",
447
+ current_impl,
448
+ )
449
+ pre_transformer.config._attn_implementation = "eager"
450
+ else:
451
+ logger.info(
452
+ "breeze_codec pre_transformer attention backend already eager for fast codec."
453
+ )
454
+
455
+ def _log_runtime_config(self) -> None:
456
+ sample_request_state = build_request_state_slot(
457
+ self.tokenizer,
458
+ self.config.chunk_frames,
459
+ self.config.device,
460
+ self.config.dtype,
461
+ )
462
+ sample_workspace = build_workspace_slot(
463
+ self.tokenizer,
464
+ self.config.chunk_frames,
465
+ self.config.device,
466
+ self.config.dtype,
467
+ )
468
+ current_impl = getattr(
469
+ self.tokenizer.model.decoder.pre_transformer.config,
470
+ "_attn_implementation",
471
+ "unknown",
472
+ ) # type: ignore[union-attr]
473
+ logger.info(
474
+ "breeze_codec runtime init: chunk_frames=%d num_lanes=%d device=%s dtype=%s fast=%s attn_impl=%s non_integer_chunk_strategy=%s lifecycle_assert_mode=%s tombstone_capacity=%d max_active_reqs=%s",
475
+ self.config.chunk_frames,
476
+ self.config.num_lanes,
477
+ self.config.device,
478
+ self.config.dtype,
479
+ self.config.fast,
480
+ current_impl,
481
+ self.config.non_integer_chunk_strategy,
482
+ self.config.lifecycle_assert_mode,
483
+ self.config.tombstone_capacity,
484
+ self.config.max_active_reqs,
485
+ )
486
+ logger.info(
487
+ "breeze_codec memory: per_request_state=%s per_workspace=%s total_request_capacity=%s total_workspace_capacity=%s samples_per_code=%s",
488
+ _format_bytes(_request_state_bytes(sample_request_state)),
489
+ _format_bytes(_workspace_bytes(sample_workspace)),
490
+ _format_bytes(
491
+ _request_state_bytes(sample_request_state)
492
+ * (self.config.max_active_reqs or 1)
493
+ ),
494
+ _format_bytes(_workspace_bytes(sample_workspace) * self.config.num_lanes),
495
+ self.samples_per_code,
496
+ )
497
+
498
+ def _sync_device(self) -> None:
499
+ if self.config.device is not None and self.config.device.type == "cuda":
500
+ torch.cuda.synchronize(self.config.device)
501
+
502
+ def _handle_lifecycle_assert(self, message: str) -> None:
503
+ mode = self.config.lifecycle_assert_mode
504
+ if mode == "off":
505
+ return
506
+ if mode == "warn":
507
+ logger.warning(message)
508
+ return
509
+ raise RuntimeError(message)
510
+
511
+ def _record_tombstone(self, req_id: str, reason: str) -> None:
512
+ if self.config.tombstone_capacity == 0:
513
+ return
514
+ self._req_tombstones.pop(req_id, None)
515
+ self._req_tombstones[req_id] = TombstoneEntry(reason=reason)
516
+ while len(self._req_tombstones) > self.config.tombstone_capacity:
517
+ self._req_tombstones.popitem(last=False)
518
+
519
+ def _clear_tombstone(self, req_id: str) -> None:
520
+ self._req_tombstones.pop(req_id, None)
521
+
522
+ def _on_request_evicted(self, req_id: str) -> None:
523
+ self.req_to_lane.pop(req_id, None)
524
+ if self._single_lane_req_id == req_id:
525
+ self._single_lane_req_id = None
526
+ self._record_tombstone(req_id, "evicted_lru")
527
+
528
+ def _maybe_warmup_fast_codec(self) -> None:
529
+ if not self.config.fast:
530
+ return
531
+ self._sync_device()
532
+ t0 = time.perf_counter()
533
+ with torch.inference_mode():
534
+ for lane in self.lanes:
535
+ lane.reset_hot_state()
536
+ lane.warmup_cuda_graph(warmup_rounds=2)
537
+ lane.reset_hot_state()
538
+ self._sync_device()
539
+ logger.info(
540
+ "breeze_codec fast codec warmup done: elapsed_ms=%.3f",
541
+ (time.perf_counter() - t0) * 1000.0,
542
+ )
543
+
544
+ def create_request(self, req_id: str, reset: bool = True) -> RequestStateSlot:
545
+ slot = self.request_pool.get(req_id)
546
+ if reset:
547
+ reset_request_state(slot)
548
+ slot.req_id = req_id
549
+ elif slot.req_id is None:
550
+ slot.req_id = req_id
551
+ return slot
552
+
553
+ def reset_request(self, req_id: str) -> RequestStateSlot:
554
+ return self.create_request(req_id, reset=True)
555
+
556
+ def release_request(self, req_id: str) -> None:
557
+ was_active = req_id in self.request_pool.active_req_ids()
558
+ self.req_to_lane.pop(req_id, None)
559
+ if self._single_lane_req_id == req_id:
560
+ self._single_lane_req_id = None
561
+ self.request_pool.pop(req_id)
562
+ if was_active:
563
+ self._record_tombstone(req_id, "closed")
564
+
565
+ # Public request-centric API
566
+ def open_request(
567
+ self, req_id: str, reset: bool = True, is_first_decode: bool | None = None
568
+ ) -> RequestStateSlot:
569
+ is_active = req_id in self.request_pool.active_req_ids()
570
+ tombstone = self._req_tombstones.get(req_id)
571
+ if is_first_decode is True and is_active:
572
+ self._handle_lifecycle_assert(
573
+ f"open_request received is_first_decode=True for an already active request: req_id={req_id}"
574
+ )
575
+ if is_first_decode is False and not is_active:
576
+ reason = tombstone.reason if tombstone is not None else "missing"
577
+ self._handle_lifecycle_assert(
578
+ f"open_request received is_first_decode=False for a non-active request: req_id={req_id} tombstone_reason={reason}"
579
+ )
580
+ self._clear_tombstone(req_id)
581
+ return self.create_request(req_id=req_id, reset=reset)
582
+
583
+ def close_request(self, req_id: str) -> None:
584
+ self.release_request(req_id)
585
+
586
+ def _select_lane(self, req_id: str) -> ExecutionLane:
587
+ lane_idx = self.req_to_lane.get(req_id)
588
+ if lane_idx is None:
589
+ lane_idx = len(self.req_to_lane) % self.config.num_lanes
590
+ self.req_to_lane[req_id] = lane_idx
591
+ return self.lanes[lane_idx]
592
+
593
+ def _select_tail_eager_lane(self, req_id: str) -> ExecutionLane:
594
+ lane_idx = self.req_to_lane.get(req_id)
595
+ if lane_idx is None:
596
+ raise RuntimeError(
597
+ f"tail eager lane requested before main lane selection: req_id={req_id}"
598
+ )
599
+ return self.tail_eager_lanes[lane_idx]
600
+
601
+ def _normalize_codes(self, codes_chunk: torch.Tensor) -> torch.Tensor:
602
+ if codes_chunk.ndim != 3:
603
+ raise ValueError(
604
+ f"Expected codes chunk shape [B, Q, T], got {tuple(codes_chunk.shape)}"
605
+ )
606
+ if codes_chunk.shape[0] != 1:
607
+ raise ValueError(
608
+ f"Only batch_size=1 is supported, got {codes_chunk.shape[0]}"
609
+ )
610
+ if codes_chunk.device != self.config.device:
611
+ codes_chunk = codes_chunk.to(self.config.device)
612
+ if codes_chunk.dtype != torch.long:
613
+ codes_chunk = codes_chunk.to(torch.long)
614
+ return codes_chunk.contiguous()
615
+
616
+ def decode_request_chunk(
617
+ self, req_id: str, codes_chunk: torch.Tensor, reset: bool = False
618
+ ) -> torch.Tensor:
619
+ state = self.create_request(req_id, reset=reset)
620
+ codes_chunk = self._normalize_codes(codes_chunk)
621
+ if codes_chunk.shape[-1] == 0:
622
+ return torch.zeros(
623
+ (1, 1, 0), device=self.config.device, dtype=self.config.dtype
624
+ )
625
+ if state.tail_flushed:
626
+ logger.error(
627
+ "decode() called after residual tail flush; request must be reset or closed before reuse. req_id=%s",
628
+ req_id,
629
+ )
630
+ return torch.zeros(
631
+ (1, 1, 0), device=self.config.device, dtype=self.config.dtype
632
+ )
633
+ lane = self._select_lane(req_id)
634
+ self.request_pool.pin(req_id)
635
+ try:
636
+ sticky_lane = self.config.fast
637
+ if reset or not sticky_lane or self._single_lane_req_id != req_id:
638
+ lane.load_request_state(state)
639
+ if sticky_lane:
640
+ self._single_lane_req_id = req_id
641
+ outs = []
642
+ state_stored = False
643
+ with torch.inference_mode():
644
+ full_len = (
645
+ codes_chunk.shape[-1] // self.config.chunk_frames
646
+ ) * self.config.chunk_frames
647
+ for offset in range(0, full_len, self.config.chunk_frames):
648
+ step_codes = codes_chunk[
649
+ ..., offset : offset + self.config.chunk_frames
650
+ ]
651
+ outs.append(lane.run_step(step_codes, state.next_step).clone())
652
+ state.next_step += int(step_codes.shape[-1])
653
+ lane.binding.request_slot.next_step = state.next_step
654
+ tail = codes_chunk.shape[-1] - full_len
655
+ if tail > 0:
656
+ tail_codes = codes_chunk[..., full_len:]
657
+ if self.config.non_integer_chunk_strategy == "eager":
658
+ # Persist the fixed-chunk lane state first, then finish the residual tail
659
+ # on a dedicated eager chunk=1 lane. This keeps the default strategy free
660
+ # of future-code padding at the tail boundary.
661
+ lane.store_request_state(state)
662
+ state_stored = True
663
+ tail_lane = self._select_tail_eager_lane(req_id)
664
+ tail_lane.load_request_state(state)
665
+ for offset in range(tail):
666
+ step_codes = tail_codes[..., offset : offset + 1]
667
+ outs.append(
668
+ tail_lane.run_step(step_codes, state.next_step).clone()
669
+ )
670
+ state.next_step += 1
671
+ tail_lane.binding.request_slot.next_step = state.next_step
672
+ state.tail_flushed = True
673
+ tail_lane.binding.request_slot.tail_flushed = True
674
+ tail_lane.store_request_state(state)
675
+ else:
676
+ pad = torch.zeros(
677
+ (
678
+ tail_codes.shape[0],
679
+ tail_codes.shape[1],
680
+ self.config.chunk_frames - tail,
681
+ ),
682
+ device=tail_codes.device,
683
+ dtype=tail_codes.dtype,
684
+ )
685
+ padded = torch.cat([tail_codes, pad], dim=-1)
686
+ padded_wav = lane.run_step(padded, state.next_step).clone()
687
+ trim_len = int(tail * self.samples_per_code)
688
+ outs.append(padded_wav[..., :trim_len])
689
+ state.next_step += tail
690
+ state.tail_flushed = True
691
+ lane.binding.request_slot.next_step = state.next_step
692
+ lane.binding.request_slot.tail_flushed = True
693
+ logger.warning(
694
+ "Residual tail decode detected; request must be reset or closed after this call. req_id=%s tail_frames=%d strategy=%s",
695
+ req_id,
696
+ tail,
697
+ self.config.non_integer_chunk_strategy,
698
+ )
699
+ if not state_stored and not sticky_lane:
700
+ lane.store_request_state(state)
701
+ if not outs:
702
+ return torch.zeros(
703
+ (1, 1, 0), device=self.config.device, dtype=self.config.dtype
704
+ )
705
+ return torch.cat(outs, dim=-1)
706
+ finally:
707
+ self.request_pool.unpin(req_id)
708
+
709
+ def decode_request_sequence(
710
+ self, req_id: str, codes: torch.Tensor, reset: bool = True
711
+ ) -> torch.Tensor:
712
+ return self.decode_request_chunk(req_id=req_id, codes_chunk=codes, reset=reset)
713
+
714
+ def get_request_step(self, req_id: str) -> int:
715
+ slot = self.request_pool.get(req_id)
716
+ return int(slot.next_step)
717
+
718
+ def decode(
719
+ self, req_id: str, codes_chunk: torch.Tensor, reset: bool = False
720
+ ) -> torch.Tensor:
721
+ return self.decode_request_chunk(
722
+ req_id=req_id, codes_chunk=codes_chunk, reset=reset
723
+ )
724
+
725
+ def decode_sequence(
726
+ self, req_id: str, codes: torch.Tensor, reset: bool = True
727
+ ) -> torch.Tensor:
728
+ return self.decode_request_sequence(req_id=req_id, codes=codes, reset=reset)
729
+
730
+ def get_step(self, req_id: str) -> int:
731
+ return self.get_request_step(req_id)
732
+
733
+
734
+ def load_tokenizer(model_path: str | Path) -> Qwen3TTSTokenizer:
735
+ model_path = Path(model_path)
736
+ if not model_path.exists():
737
+ raise FileNotFoundError(f"model path not found: {model_path}")
738
+ tok = Qwen3TTSTokenizer.from_pretrained(
739
+ str(model_path),
740
+ torch_dtype=torch.float32,
741
+ load_feature_extractor=False,
742
+ )
743
+ if tok.model is None:
744
+ raise RuntimeError("Tokenizer model failed to load.")
745
+ tok.model.eval()
746
+ return tok
models/stream_runtime/stream/state.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from collections import OrderedDict
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from typing import Generic, TypeVar
8
+
9
+ import torch
10
+
11
+ from .kv_cache import StaticShiftKVCache
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ @dataclass
19
+ class ConvStateBlock:
20
+ cache_buf: torch.Tensor
21
+ left_cache_len: int
22
+
23
+
24
+ @dataclass
25
+ class TransConvStateBlock:
26
+ cache_buf: torch.Tensor
27
+ left_cache_len: int
28
+
29
+
30
+ @dataclass
31
+ class RequestStateSlot:
32
+ req_id: str | None
33
+ next_step: int
34
+ tail_flushed: bool
35
+ conv1d: dict[str, ConvStateBlock]
36
+ tconv1d: dict[str, TransConvStateBlock]
37
+ kv: StaticShiftKVCache
38
+
39
+
40
+ def reset_request_state(slot: RequestStateSlot) -> None:
41
+ slot.req_id = None
42
+ slot.next_step = 0
43
+ slot.tail_flushed = False
44
+ for block in slot.conv1d.values():
45
+ block.cache_buf.zero_()
46
+ for block in slot.tconv1d.values():
47
+ block.cache_buf.zero_()
48
+ for layer in slot.kv.layers:
49
+ layer.k.zero_()
50
+ layer.v.zero_()
51
+
52
+
53
+ class RequestStatePool(Generic[T]):
54
+ def __init__(
55
+ self,
56
+ create_slot: Callable[[], T],
57
+ reset_slot: Callable[[T], None],
58
+ max_active_reqs: int | None = None,
59
+ on_evict: Callable[[str], None] | None = None,
60
+ ):
61
+ if max_active_reqs is not None and max_active_reqs <= 0:
62
+ raise ValueError(
63
+ f"max_active_reqs must be > 0 or None, got {max_active_reqs}"
64
+ )
65
+ self._create_slot = create_slot
66
+ self._reset_slot = reset_slot
67
+ self.max_active_reqs = max_active_reqs
68
+ self._on_evict = on_evict
69
+ self._req_to_slot: OrderedDict[str, int] = OrderedDict()
70
+ self._slot_to_req: list[str | None] = []
71
+ self._slots: list[T] = []
72
+ self._pinned: set[str] = set()
73
+ self._dynamic: dict[str, T] = {}
74
+ if self.max_active_reqs is not None:
75
+ self._slots = [self._create_slot() for _ in range(self.max_active_reqs)]
76
+ self._slot_to_req = [None for _ in range(self.max_active_reqs)]
77
+
78
+ def __len__(self) -> int:
79
+ return (
80
+ len(self._req_to_slot)
81
+ if self.max_active_reqs is not None
82
+ else len(self._dynamic)
83
+ )
84
+
85
+ def active_req_ids(self) -> list[str]:
86
+ if self.max_active_reqs is None:
87
+ return list(self._dynamic.keys())
88
+ return list(self._req_to_slot.keys())
89
+
90
+ def num_free_slots(self) -> int | None:
91
+ if self.max_active_reqs is None:
92
+ return None
93
+ return sum(1 for req_id in self._slot_to_req if req_id is None)
94
+
95
+ def pin(self, req_id: str) -> None:
96
+ self._pinned.add(req_id)
97
+
98
+ def unpin(self, req_id: str) -> None:
99
+ self._pinned.discard(req_id)
100
+
101
+ def _find_free_slot(self) -> int | None:
102
+ for idx, req_id in enumerate(self._slot_to_req):
103
+ if req_id is None:
104
+ return idx
105
+ return None
106
+
107
+ def _evict_lru(self) -> int:
108
+ victim_req_id: str | None = None
109
+ for req_id in self._req_to_slot.keys():
110
+ if req_id not in self._pinned:
111
+ victim_req_id = req_id
112
+ break
113
+ if victim_req_id is None:
114
+ logger.error(
115
+ "RequestStatePool found no evictable request. max_active_reqs=%s active_req_ids=%s pinned=%s",
116
+ self.max_active_reqs,
117
+ list(self._req_to_slot.keys()),
118
+ sorted(self._pinned),
119
+ )
120
+ raise RuntimeError("No evictable request state slot available.")
121
+ slot_idx = self._req_to_slot.pop(victim_req_id)
122
+ self._slot_to_req[slot_idx] = None
123
+ self._reset_slot(self._slots[slot_idx])
124
+ if self._on_evict is not None:
125
+ self._on_evict(victim_req_id)
126
+ logger.info(
127
+ "Evicted request state due to LRU capacity limit: req_id=%s slot_idx=%d",
128
+ victim_req_id,
129
+ slot_idx,
130
+ )
131
+ return slot_idx
132
+
133
+ def get(self, req_id: str) -> T:
134
+ if self.max_active_reqs is None:
135
+ value = self._dynamic.get(req_id)
136
+ if value is None:
137
+ value = self._create_slot()
138
+ self._dynamic[req_id] = value
139
+ return value
140
+ slot_idx = self._req_to_slot.get(req_id)
141
+ if slot_idx is None:
142
+ slot_idx = self._find_free_slot()
143
+ if slot_idx is None:
144
+ slot_idx = self._evict_lru()
145
+ self._slot_to_req[slot_idx] = req_id
146
+ self._req_to_slot[req_id] = slot_idx
147
+ self._reset_slot(self._slots[slot_idx])
148
+ self._req_to_slot.move_to_end(req_id)
149
+ return self._slots[slot_idx]
150
+
151
+ def pop(self, req_id: str) -> None:
152
+ self._pinned.discard(req_id)
153
+ if self.max_active_reqs is None:
154
+ self._dynamic.pop(req_id, None)
155
+ return
156
+ slot_idx = self._req_to_slot.pop(req_id, None)
157
+ if slot_idx is None:
158
+ return
159
+ self._reset_slot(self._slots[slot_idx])
160
+ self._slot_to_req[slot_idx] = None
161
+
162
+ def get_slot_index(self, req_id: str) -> int | None:
163
+ if self.max_active_reqs is None:
164
+ return None
165
+ return self._req_to_slot.get(req_id)
models/stream_runtime/stream/workspace.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from typing import Generic, TypeVar
6
+
7
+ import torch
8
+
9
+ W = TypeVar("W")
10
+
11
+
12
+ @dataclass
13
+ class ConvWorkspaceBlock:
14
+ x_buf: torch.Tensor
15
+
16
+
17
+ @dataclass
18
+ class TransConvWorkspaceBlock:
19
+ x_buf: torch.Tensor
20
+
21
+
22
+ @dataclass
23
+ class WorkspaceSlot:
24
+ conv1d: dict[str, ConvWorkspaceBlock]
25
+ tconv1d: dict[str, TransConvWorkspaceBlock]
26
+
27
+
28
+ def reset_workspace(slot: WorkspaceSlot) -> None:
29
+ for block in slot.conv1d.values():
30
+ block.x_buf.zero_()
31
+ for block in slot.tconv1d.values():
32
+ block.x_buf.zero_()
33
+
34
+
35
+ class WorkspacePool(Generic[W]):
36
+ def __init__(
37
+ self, create_slot: Callable[[], W], reset_slot: Callable[[W], None], size: int
38
+ ):
39
+ if size <= 0:
40
+ raise ValueError(f"workspace pool size must be > 0, got {size}")
41
+ self._reset_slot = reset_slot
42
+ self._slots: list[W] = [create_slot() for _ in range(size)]
43
+ self._free: list[int] = list(range(size))
44
+
45
+ def __len__(self) -> int:
46
+ return len(self._slots)
47
+
48
+ def num_free_slots(self) -> int:
49
+ return len(self._free)
50
+
51
+ def acquire(self) -> tuple[int, W]:
52
+ if not self._free:
53
+ raise RuntimeError("No free workspace slot available.")
54
+ slot_idx = self._free.pop(0)
55
+ slot = self._slots[slot_idx]
56
+ self._reset_slot(slot)
57
+ return slot_idx, slot
58
+
59
+ def release(self, slot_idx: int) -> None:
60
+ if slot_idx in self._free:
61
+ return
62
+ self._reset_slot(self._slots[slot_idx])
63
+ self._free.append(slot_idx)
64
+ self._free.sort()
65
+
66
+ def get_slot(self, slot_idx: int) -> W:
67
+ return self._slots[slot_idx]
models/t5gemma2_compat.py ADDED
@@ -0,0 +1,813 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Local compatibility shim for loading T5Gemma 2 text encoders under
2
+ # transformers 4.57.x, which does not ship the t5gemma2 architecture.
3
+
4
+
5
+ import torch
6
+ from torch import nn
7
+ from transformers import PretrainedConfig
8
+ from transformers.activations import ACT2FN
9
+ from transformers.modeling_layers import GradientCheckpointingLayer
10
+ from transformers.modeling_outputs import BaseModelOutput
11
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
12
+
13
+
14
+ class T5Gemma2TextConfig(PretrainedConfig):
15
+ model_type = "t5gemma2_text"
16
+ keys_to_ignore_at_inference = ["past_key_values"]
17
+
18
+ def __init__(
19
+ self,
20
+ vocab_size: int = 262208,
21
+ hidden_size: int = 2304,
22
+ intermediate_size: int = 9216,
23
+ num_hidden_layers: int = 26,
24
+ num_attention_heads: int = 8,
25
+ num_key_value_heads: int = 4,
26
+ head_dim: int = 256,
27
+ hidden_activation: str = "gelu_pytorch_tanh",
28
+ max_position_embeddings: int = 131072,
29
+ initializer_range: float = 0.02,
30
+ rms_norm_eps: float = 1e-6,
31
+ use_cache: bool = True,
32
+ rope_parameters: dict | None = None,
33
+ attention_bias: bool = False,
34
+ attention_dropout: float = 0.0,
35
+ dropout_rate: float = 0.0,
36
+ query_pre_attn_scalar: int = 256,
37
+ sliding_window: int | None = 4096,
38
+ layer_types: list[str] | None = None,
39
+ final_logit_softcapping: float | None = None,
40
+ attn_logit_softcapping: float | None = None,
41
+ eoi_token_index: int = 256000,
42
+ pad_token_id: int | None = 0,
43
+ eos_token_id: int | None = 1,
44
+ bos_token_id: int | None = 2,
45
+ tie_word_embeddings: bool = True,
46
+ **kwargs,
47
+ ):
48
+ sliding_window_pattern = kwargs.pop(
49
+ "_sliding_window_pattern", kwargs.pop("sliding_window_pattern", 6)
50
+ )
51
+ rope_scaling = kwargs.pop("rope_scaling", None)
52
+ super().__init__(
53
+ pad_token_id=pad_token_id,
54
+ eos_token_id=eos_token_id,
55
+ bos_token_id=bos_token_id,
56
+ tie_word_embeddings=tie_word_embeddings,
57
+ **kwargs,
58
+ )
59
+
60
+ self.vocab_size = vocab_size
61
+ self.hidden_size = hidden_size
62
+ self.intermediate_size = intermediate_size
63
+ self.num_hidden_layers = num_hidden_layers
64
+ self.num_attention_heads = num_attention_heads
65
+ self.num_key_value_heads = num_key_value_heads
66
+ self.head_dim = head_dim
67
+ self.hidden_activation = hidden_activation
68
+ self.max_position_embeddings = max_position_embeddings
69
+ self.initializer_range = initializer_range
70
+ self.rms_norm_eps = rms_norm_eps
71
+ self.use_cache = use_cache
72
+ self.attention_bias = attention_bias
73
+ self.attention_dropout = attention_dropout
74
+ self.dropout_rate = dropout_rate
75
+ self.query_pre_attn_scalar = query_pre_attn_scalar
76
+ self.sliding_window = sliding_window
77
+ self.final_logit_softcapping = final_logit_softcapping
78
+ self.attn_logit_softcapping = attn_logit_softcapping
79
+ self.eoi_token_index = eoi_token_index
80
+
81
+ if layer_types is None:
82
+ layer_types = [
83
+ "sliding_attention"
84
+ if (i + 1) % sliding_window_pattern
85
+ else "full_attention"
86
+ for i in range(num_hidden_layers)
87
+ ]
88
+ self.layer_types = layer_types
89
+ self.rope_parameters = self._normalize_rope_parameters(
90
+ rope_parameters, rope_scaling
91
+ )
92
+
93
+ @staticmethod
94
+ def _normalize_rope_parameters(
95
+ rope_parameters: dict | None, rope_scaling: dict | None
96
+ ) -> dict:
97
+ params = {
98
+ "sliding_attention": {"rope_type": "default", "rope_theta": 10000.0},
99
+ "full_attention": {"rope_type": "default", "rope_theta": 1000000.0},
100
+ }
101
+ if rope_parameters:
102
+ for key, value in rope_parameters.items():
103
+ params[key] = (
104
+ dict(value) if value is not None else {"rope_type": "default"}
105
+ )
106
+ if rope_scaling:
107
+ params["full_attention"].update(rope_scaling)
108
+ params["sliding_attention"].setdefault("rope_type", "default")
109
+ params["sliding_attention"].setdefault("rope_theta", 10000.0)
110
+ params["full_attention"].setdefault("rope_type", "default")
111
+ params["full_attention"].setdefault("rope_theta", 1000000.0)
112
+ return params
113
+
114
+
115
+ class T5Gemma2RMSNorm(nn.Module):
116
+ def __init__(self, dim: int, eps: float = 1e-6):
117
+ super().__init__()
118
+ self.eps = eps
119
+ self.weight = nn.Parameter(torch.zeros(dim))
120
+
121
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
122
+ output = x.float() * torch.rsqrt(
123
+ x.float().pow(2).mean(-1, keepdim=True) + self.eps
124
+ )
125
+ output = output * (1.0 + self.weight.float())
126
+ return output.type_as(x)
127
+
128
+
129
+ class T5Gemma2MLP(nn.Module):
130
+ def __init__(self, config: T5Gemma2TextConfig):
131
+ super().__init__()
132
+ self.gate_proj = nn.Linear(
133
+ config.hidden_size, config.intermediate_size, bias=False
134
+ )
135
+ self.up_proj = nn.Linear(
136
+ config.hidden_size, config.intermediate_size, bias=False
137
+ )
138
+ self.down_proj = nn.Linear(
139
+ config.intermediate_size, config.hidden_size, bias=False
140
+ )
141
+ self.act_fn = ACT2FN[config.hidden_activation]
142
+ self.dropout = nn.Dropout(config.dropout_rate)
143
+
144
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
145
+ hidden_states = self.act_fn(self.gate_proj(x)) * self.up_proj(x)
146
+ hidden_states = self.dropout(hidden_states)
147
+ return self.down_proj(hidden_states)
148
+
149
+
150
+ class T5Gemma2RotaryEmbedding(nn.Module):
151
+ def __init__(self, config: T5Gemma2TextConfig, device=None):
152
+ super().__init__()
153
+ self.config = config
154
+ self.layer_types = sorted(set(config.layer_types))
155
+ for layer_type in self.layer_types:
156
+ inv_freq, attention_scaling = self._compute_inv_freq(
157
+ config, layer_type, device=device
158
+ )
159
+ self.register_buffer(f"{layer_type}_inv_freq", inv_freq, persistent=False)
160
+ setattr(self, f"{layer_type}_attention_scaling", attention_scaling)
161
+
162
+ @staticmethod
163
+ def _compute_inv_freq(
164
+ config: T5Gemma2TextConfig, layer_type: str, device=None
165
+ ) -> tuple[torch.Tensor, float]:
166
+ rope_params = config.rope_parameters[layer_type]
167
+ rope_type = rope_params.get("rope_type", "default")
168
+ base = rope_params["rope_theta"]
169
+ dim = (
170
+ getattr(config, "head_dim", None)
171
+ or config.hidden_size // config.num_attention_heads
172
+ )
173
+ inv_freq = 1.0 / (
174
+ base ** (torch.arange(0, dim, 2, device=device, dtype=torch.float32) / dim)
175
+ )
176
+ if rope_type == "linear":
177
+ inv_freq = inv_freq / float(rope_params["factor"])
178
+ elif rope_type != "default":
179
+ raise ValueError(
180
+ f"Unsupported T5Gemma2 rope_type in local compat shim: {rope_type}"
181
+ )
182
+ return inv_freq, 1.0
183
+
184
+ def forward(self, x: torch.Tensor, position_ids: torch.LongTensor, layer_type: str):
185
+ inv_freq = getattr(self, f"{layer_type}_inv_freq")
186
+ attention_scaling = getattr(self, f"{layer_type}_attention_scaling")
187
+ inv_freq_expanded = (
188
+ inv_freq[None, :, None]
189
+ .float()
190
+ .expand(position_ids.shape[0], -1, 1)
191
+ .to(x.device)
192
+ )
193
+ position_ids_expanded = position_ids[:, None, :].float()
194
+
195
+ device_type = (
196
+ x.device.type
197
+ if isinstance(x.device.type, str) and x.device.type != "mps"
198
+ else "cpu"
199
+ )
200
+ with torch.autocast(device_type=device_type, enabled=False):
201
+ freqs = (
202
+ inv_freq_expanded.float() @ position_ids_expanded.float()
203
+ ).transpose(1, 2)
204
+ emb = torch.cat((freqs, freqs), dim=-1)
205
+ cos = emb.cos() * attention_scaling
206
+ sin = emb.sin() * attention_scaling
207
+
208
+ return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
209
+
210
+
211
+ def rotate_half(x: torch.Tensor) -> torch.Tensor:
212
+ x1 = x[..., : x.shape[-1] // 2]
213
+ x2 = x[..., x.shape[-1] // 2 :]
214
+ return torch.cat((-x2, x1), dim=-1)
215
+
216
+
217
+ def apply_rotary_pos_emb(
218
+ q: torch.Tensor,
219
+ k: torch.Tensor,
220
+ cos: torch.Tensor,
221
+ sin: torch.Tensor,
222
+ unsqueeze_dim=1,
223
+ ):
224
+ cos = cos.unsqueeze(unsqueeze_dim)
225
+ sin = sin.unsqueeze(unsqueeze_dim)
226
+ q_embed = (q * cos) + (rotate_half(q) * sin)
227
+ k_embed = (k * cos) + (rotate_half(k) * sin)
228
+ return q_embed, k_embed
229
+
230
+
231
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
232
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
233
+ if n_rep == 1:
234
+ return hidden_states
235
+ hidden_states = hidden_states[:, :, None, :, :].expand(
236
+ batch, num_key_value_heads, n_rep, slen, head_dim
237
+ )
238
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
239
+
240
+
241
+ def eager_attention_forward(
242
+ module: nn.Module,
243
+ query: torch.Tensor,
244
+ key: torch.Tensor,
245
+ value: torch.Tensor,
246
+ attention_mask: torch.Tensor | None,
247
+ dropout: float = 0.0,
248
+ scaling: float | None = None,
249
+ softcap: float | None = None,
250
+ **kwargs,
251
+ ):
252
+ if scaling is None:
253
+ scaling = module.head_dim**-0.5
254
+ key_states = repeat_kv(key, module.num_key_value_groups)
255
+ value_states = repeat_kv(value, module.num_key_value_groups)
256
+ attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
257
+ if softcap is not None:
258
+ attn_weights = torch.tanh(attn_weights / softcap) * softcap
259
+ if attention_mask is not None:
260
+ attn_weights = attn_weights + attention_mask[:, :, :, : key_states.shape[-2]]
261
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(
262
+ query.dtype
263
+ )
264
+ attn_weights = nn.functional.dropout(
265
+ attn_weights, p=dropout, training=module.training
266
+ )
267
+ attn_output = torch.matmul(attn_weights, value_states)
268
+ attn_output = attn_output.transpose(1, 2).contiguous()
269
+ return attn_output, attn_weights
270
+
271
+
272
+ def _padding_mask_to_additive_mask(
273
+ attention_mask: torch.Tensor | None,
274
+ query: torch.Tensor,
275
+ layer_type: str,
276
+ sliding_window: int | None,
277
+ ) -> torch.Tensor | None:
278
+ batch_size, _, seq_len, _ = query.shape
279
+ device = query.device
280
+ dtype = query.dtype
281
+
282
+ has_padding = attention_mask is not None and not bool(
283
+ attention_mask.to(torch.bool).all()
284
+ )
285
+ if layer_type == "full_attention" and not has_padding:
286
+ return None
287
+
288
+ if attention_mask is None:
289
+ valid_kv = torch.ones(batch_size, seq_len, device=device, dtype=torch.bool)
290
+ else:
291
+ valid_kv = attention_mask.to(device=device, dtype=torch.bool)
292
+ allowed = valid_kv[:, None, None, :].expand(batch_size, 1, seq_len, seq_len)
293
+
294
+ if layer_type == "sliding_attention":
295
+ if sliding_window is None:
296
+ raise ValueError(
297
+ "T5Gemma2 sliding_attention requires config.sliding_window"
298
+ )
299
+ q_idx = torch.arange(seq_len, device=device)[:, None]
300
+ kv_idx = torch.arange(seq_len, device=device)[None, :]
301
+ left_window_size = (sliding_window + 1) // 2
302
+ right_window_size = sliding_window // 2 + 1
303
+ dist = q_idx - kv_idx
304
+ local = ((dist >= 0) & (dist < left_window_size)) | (
305
+ (dist < 0) & (-dist < right_window_size)
306
+ )
307
+ allowed = allowed & local[None, None, :, :]
308
+
309
+ additive_mask = torch.zeros(
310
+ (batch_size, 1, seq_len, seq_len), device=device, dtype=dtype
311
+ )
312
+ additive_mask.masked_fill_(~allowed, torch.finfo(dtype).min)
313
+ return additive_mask
314
+
315
+
316
+ def t5gemma2_flash_attention_forward(
317
+ module: nn.Module,
318
+ query: torch.Tensor,
319
+ key: torch.Tensor,
320
+ value: torch.Tensor,
321
+ attention_mask: torch.Tensor | None,
322
+ dropout: float = 0.0,
323
+ scaling: float | None = None,
324
+ sliding_window: int | None = None,
325
+ softcap: float | None = None,
326
+ **kwargs,
327
+ ):
328
+ if query.device.type != "cuda" or query.dtype not in (
329
+ torch.float16,
330
+ torch.bfloat16,
331
+ ):
332
+ additive_mask = _padding_mask_to_additive_mask(
333
+ attention_mask,
334
+ query,
335
+ module.layer_type,
336
+ module.config.sliding_window,
337
+ )
338
+ return eager_attention_forward(
339
+ module,
340
+ query,
341
+ key,
342
+ value,
343
+ additive_mask,
344
+ dropout=dropout,
345
+ scaling=scaling,
346
+ softcap=softcap,
347
+ **kwargs,
348
+ )
349
+
350
+ try:
351
+ from flash_attn import flash_attn_func, flash_attn_varlen_func
352
+ from flash_attn.bert_padding import pad_input, unpad_input
353
+ except ImportError as exc:
354
+ raise ImportError(
355
+ "flash_attn is required for T5Gemma2 flash_attention_2"
356
+ ) from exc
357
+
358
+ query = query.transpose(1, 2).contiguous()
359
+ key = key.transpose(1, 2).contiguous()
360
+ value = value.transpose(1, 2).contiguous()
361
+ batch_size, seq_len = query.shape[:2]
362
+ dropout_p = dropout if module.training else 0.0
363
+ softcap_value = 0.0 if softcap is None else softcap
364
+
365
+ window_size = (-1, -1)
366
+ if module.layer_type == "sliding_attention":
367
+ if sliding_window is None:
368
+ raise ValueError(
369
+ "T5Gemma2 sliding_attention requires config.sliding_window"
370
+ )
371
+ left_window_size = (sliding_window + 1) // 2
372
+ right_window_size = sliding_window // 2 + 1
373
+ window_size = (left_window_size - 1, right_window_size - 1)
374
+
375
+ has_padding = attention_mask is not None and not bool(
376
+ attention_mask.to(torch.bool).all()
377
+ )
378
+ if has_padding:
379
+ attention_mask = attention_mask.to(device=query.device, dtype=torch.bool)
380
+ query_unpad, indices_q, cu_seqlens_q, max_seqlen_q, _ = unpad_input(
381
+ query, attention_mask
382
+ )
383
+ key_unpad, _, cu_seqlens_k, max_seqlen_k, _ = unpad_input(key, attention_mask)
384
+ value_unpad, _, _, _, _ = unpad_input(value, attention_mask)
385
+ attn_output_unpad = flash_attn_varlen_func(
386
+ query_unpad,
387
+ key_unpad,
388
+ value_unpad,
389
+ cu_seqlens_q,
390
+ cu_seqlens_k,
391
+ max_seqlen_q,
392
+ max_seqlen_k,
393
+ dropout_p=dropout_p,
394
+ softmax_scale=scaling,
395
+ causal=False,
396
+ window_size=window_size,
397
+ softcap=softcap_value,
398
+ )
399
+ attn_output = pad_input(attn_output_unpad, indices_q, batch_size, seq_len)
400
+ else:
401
+ attn_output = flash_attn_func(
402
+ query,
403
+ key,
404
+ value,
405
+ dropout_p=dropout_p,
406
+ softmax_scale=scaling,
407
+ causal=False,
408
+ window_size=window_size,
409
+ softcap=softcap_value,
410
+ )
411
+
412
+ return attn_output, None
413
+
414
+
415
+ class T5Gemma2SelfAttention(nn.Module):
416
+ def __init__(self, config: T5Gemma2TextConfig, layer_idx: int):
417
+ super().__init__()
418
+ self.config = config
419
+ self.layer_idx = layer_idx
420
+ self.layer_type = config.layer_types[layer_idx]
421
+ self.head_dim = getattr(
422
+ config, "head_dim", config.hidden_size // config.num_attention_heads
423
+ )
424
+ self.num_key_value_groups = (
425
+ config.num_attention_heads // config.num_key_value_heads
426
+ )
427
+ self.scaling = config.query_pre_attn_scalar**-0.5
428
+ self.attention_dropout = config.attention_dropout
429
+ self.is_causal = False
430
+ self.sliding_window = (
431
+ config.sliding_window if self.layer_type == "sliding_attention" else None
432
+ )
433
+
434
+ self.q_proj = nn.Linear(
435
+ config.hidden_size,
436
+ config.num_attention_heads * self.head_dim,
437
+ bias=config.attention_bias,
438
+ )
439
+ self.k_proj = nn.Linear(
440
+ config.hidden_size,
441
+ config.num_key_value_heads * self.head_dim,
442
+ bias=config.attention_bias,
443
+ )
444
+ self.v_proj = nn.Linear(
445
+ config.hidden_size,
446
+ config.num_key_value_heads * self.head_dim,
447
+ bias=config.attention_bias,
448
+ )
449
+ self.o_proj = nn.Linear(
450
+ config.num_attention_heads * self.head_dim,
451
+ config.hidden_size,
452
+ bias=config.attention_bias,
453
+ )
454
+ self.q_norm = T5Gemma2RMSNorm(dim=self.head_dim, eps=config.rms_norm_eps)
455
+ self.k_norm = T5Gemma2RMSNorm(dim=self.head_dim, eps=config.rms_norm_eps)
456
+
457
+ def forward(
458
+ self,
459
+ hidden_states: torch.Tensor,
460
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
461
+ attention_mask: torch.Tensor | None = None,
462
+ **kwargs,
463
+ ):
464
+ input_shape = hidden_states.shape[:-1]
465
+ hidden_shape = (*input_shape, -1, self.head_dim)
466
+
467
+ query_states = self.q_proj(hidden_states).view(hidden_shape).transpose(1, 2)
468
+ key_states = self.k_proj(hidden_states).view(hidden_shape).transpose(1, 2)
469
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
470
+
471
+ query_states = self.q_norm(query_states)
472
+ key_states = self.k_norm(key_states)
473
+
474
+ cos, sin = position_embeddings
475
+ query_states, key_states = apply_rotary_pos_emb(
476
+ query_states, key_states, cos, sin
477
+ )
478
+
479
+ attn_impl = getattr(self.config, "_attn_implementation", None) or getattr(
480
+ self.config,
481
+ "preferred_attn_implementation",
482
+ "flash_attention_2",
483
+ )
484
+ if attn_impl == "flash_attention_2":
485
+ attention_interface = t5gemma2_flash_attention_forward
486
+ else:
487
+ attention_interface = (
488
+ eager_attention_forward
489
+ if attn_impl == "eager"
490
+ else ALL_ATTENTION_FUNCTIONS[attn_impl]
491
+ )
492
+ attn_output, attn_weights = attention_interface(
493
+ self,
494
+ query_states,
495
+ key_states,
496
+ value_states,
497
+ attention_mask,
498
+ dropout=self.attention_dropout if self.training else 0.0,
499
+ scaling=self.scaling,
500
+ sliding_window=self.sliding_window,
501
+ softcap=self.config.attn_logit_softcapping,
502
+ **kwargs,
503
+ )
504
+
505
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
506
+ attn_output = self.o_proj(attn_output)
507
+ return attn_output, attn_weights
508
+
509
+
510
+ class T5Gemma2EncoderLayer(GradientCheckpointingLayer):
511
+ def __init__(self, config: T5Gemma2TextConfig, layer_idx: int):
512
+ super().__init__()
513
+ self.attention_type = config.layer_types[layer_idx]
514
+ self.self_attn = T5Gemma2SelfAttention(config=config, layer_idx=layer_idx)
515
+ self.pre_self_attn_layernorm = T5Gemma2RMSNorm(
516
+ config.hidden_size, eps=config.rms_norm_eps
517
+ )
518
+ self.post_self_attn_layernorm = T5Gemma2RMSNorm(
519
+ config.hidden_size, eps=config.rms_norm_eps
520
+ )
521
+ self.mlp = T5Gemma2MLP(config)
522
+ self.pre_feedforward_layernorm = T5Gemma2RMSNorm(
523
+ config.hidden_size, eps=config.rms_norm_eps
524
+ )
525
+ self.post_feedforward_layernorm = T5Gemma2RMSNorm(
526
+ config.hidden_size, eps=config.rms_norm_eps
527
+ )
528
+ self.dropout = nn.Dropout(config.dropout_rate)
529
+
530
+ def forward(
531
+ self,
532
+ hidden_states: torch.Tensor,
533
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
534
+ attention_mask: torch.Tensor | None = None,
535
+ **kwargs,
536
+ ) -> torch.Tensor:
537
+ residual = hidden_states
538
+ hidden_states = self.pre_self_attn_layernorm(hidden_states)
539
+ hidden_states, _ = self.self_attn(
540
+ hidden_states=hidden_states,
541
+ position_embeddings=position_embeddings,
542
+ attention_mask=attention_mask,
543
+ **kwargs,
544
+ )
545
+ hidden_states = self.post_self_attn_layernorm(hidden_states)
546
+ hidden_states = residual + self.dropout(hidden_states)
547
+
548
+ residual = hidden_states
549
+ hidden_states = self.pre_feedforward_layernorm(hidden_states)
550
+ hidden_states = self.mlp(hidden_states)
551
+ hidden_states = self.post_feedforward_layernorm(hidden_states)
552
+ hidden_states = residual + self.dropout(hidden_states)
553
+ return hidden_states
554
+
555
+
556
+ class T5Gemma2TextScaledWordEmbedding(nn.Embedding):
557
+ def __init__(
558
+ self,
559
+ num_embeddings: int,
560
+ embedding_dim: int,
561
+ padding_idx: int | None,
562
+ embed_scale: float = 1.0,
563
+ eoi_token_index: int = 256000,
564
+ device=None,
565
+ dtype=None,
566
+ ):
567
+ super().__init__(
568
+ num_embeddings, embedding_dim, padding_idx, device=device, dtype=dtype
569
+ )
570
+ self.scalar_embed_scale = embed_scale
571
+ self.register_buffer("embed_scale", torch.tensor(embed_scale), persistent=False)
572
+ self.eoi_token_index = eoi_token_index
573
+ self.eoi_embedding = nn.Parameter(
574
+ torch.zeros(self.embedding_dim, device=device, dtype=dtype)
575
+ )
576
+
577
+ def forward(self, input_ids: torch.Tensor):
578
+ input_embeddings = super().forward(input_ids) * self.embed_scale.to(
579
+ self.weight.dtype
580
+ )
581
+ eoi_mask = input_ids == self.eoi_token_index
582
+ return torch.where(
583
+ eoi_mask.unsqueeze(-1),
584
+ self.eoi_embedding.to(input_embeddings.dtype),
585
+ input_embeddings,
586
+ )
587
+
588
+
589
+ class T5Gemma2TextEncoder(PreTrainedModel):
590
+ config_class = T5Gemma2TextConfig
591
+ base_model_prefix = "model"
592
+ supports_gradient_checkpointing = True
593
+ _no_split_modules = ["T5Gemma2EncoderLayer"]
594
+ _supports_flash_attn = True
595
+ _supports_sdpa = True
596
+ _supports_attention_backend = True
597
+
598
+ def __init__(self, config: T5Gemma2TextConfig):
599
+ super().__init__(config)
600
+ self.padding_idx = config.pad_token_id
601
+ self.vocab_size = config.vocab_size
602
+ self.embed_tokens = T5Gemma2TextScaledWordEmbedding(
603
+ config.vocab_size,
604
+ config.hidden_size,
605
+ self.padding_idx,
606
+ embed_scale=config.hidden_size**0.5,
607
+ eoi_token_index=config.eoi_token_index,
608
+ )
609
+ self.norm = T5Gemma2RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
610
+ self.layers = nn.ModuleList(
611
+ [
612
+ T5Gemma2EncoderLayer(config, layer_idx)
613
+ for layer_idx in range(config.num_hidden_layers)
614
+ ]
615
+ )
616
+ self.dropout = nn.Dropout(config.dropout_rate)
617
+ self.rotary_emb = T5Gemma2RotaryEmbedding(config)
618
+ self.gradient_checkpointing = False
619
+ self.post_init()
620
+
621
+ def get_input_embeddings(self):
622
+ return self.embed_tokens
623
+
624
+ def set_input_embeddings(self, new_embeddings):
625
+ self.embed_tokens = new_embeddings
626
+ self.vocab_size = new_embeddings.num_embeddings
627
+
628
+ def _get_resized_embeddings(
629
+ self,
630
+ old_embeddings: nn.Embedding,
631
+ new_num_tokens: int | None = None,
632
+ pad_to_multiple_of: int | None = None,
633
+ mean_resizing: bool = True,
634
+ ) -> nn.Embedding:
635
+ if new_num_tokens is None:
636
+ return old_embeddings
637
+ if pad_to_multiple_of is not None:
638
+ new_num_tokens = (
639
+ (new_num_tokens + pad_to_multiple_of - 1) // pad_to_multiple_of
640
+ ) * pad_to_multiple_of
641
+
642
+ old_num_tokens, old_embedding_dim = old_embeddings.weight.shape
643
+ if old_num_tokens == new_num_tokens:
644
+ return old_embeddings
645
+ padding_idx = old_embeddings.padding_idx
646
+ if padding_idx is not None and padding_idx >= new_num_tokens:
647
+ padding_idx = None
648
+ new_embeddings = T5Gemma2TextScaledWordEmbedding(
649
+ new_num_tokens,
650
+ old_embedding_dim,
651
+ padding_idx,
652
+ embed_scale=getattr(
653
+ old_embeddings, "scalar_embed_scale", self.config.hidden_size**0.5
654
+ ),
655
+ eoi_token_index=getattr(
656
+ old_embeddings, "eoi_token_index", self.config.eoi_token_index
657
+ ),
658
+ device=old_embeddings.weight.device,
659
+ dtype=old_embeddings.weight.dtype,
660
+ )
661
+ self._init_weights(new_embeddings)
662
+ num_tokens_to_copy = min(old_num_tokens, new_num_tokens)
663
+ new_embeddings.weight.data[:num_tokens_to_copy] = old_embeddings.weight.data[
664
+ :num_tokens_to_copy
665
+ ]
666
+ if hasattr(old_embeddings, "eoi_embedding"):
667
+ new_embeddings.eoi_embedding.data.copy_(old_embeddings.eoi_embedding.data)
668
+ return new_embeddings
669
+
670
+ def _init_weights(self, module):
671
+ std = self.config.initializer_range
672
+ if isinstance(module, nn.Linear):
673
+ module.weight.data.normal_(mean=0.0, std=std)
674
+ if module.bias is not None:
675
+ module.bias.data.zero_()
676
+ elif isinstance(module, T5Gemma2TextScaledWordEmbedding):
677
+ module.weight.data.normal_(mean=0.0, std=std)
678
+ if module.padding_idx is not None:
679
+ module.weight.data[module.padding_idx].zero_()
680
+ module.eoi_embedding.data.zero_()
681
+ module.embed_scale.fill_(module.scalar_embed_scale)
682
+ elif isinstance(module, T5Gemma2RMSNorm):
683
+ module.weight.data.zero_()
684
+
685
+ @staticmethod
686
+ def _build_additive_attention_mask(
687
+ attention_mask: torch.Tensor | None,
688
+ inputs_embeds: torch.Tensor,
689
+ layer_type: str,
690
+ sliding_window: int | None,
691
+ ) -> torch.Tensor | None:
692
+ batch_size, seq_len = inputs_embeds.shape[:2]
693
+ device = inputs_embeds.device
694
+
695
+ has_padding = attention_mask is not None
696
+ if layer_type == "full_attention" and not has_padding:
697
+ return None
698
+
699
+ if attention_mask is None:
700
+ valid_kv = torch.ones(batch_size, seq_len, device=device, dtype=torch.bool)
701
+ else:
702
+ valid_kv = attention_mask.to(device=device, dtype=torch.bool)
703
+ allowed = valid_kv[:, None, None, :].expand(batch_size, 1, seq_len, seq_len)
704
+
705
+ if layer_type == "sliding_attention":
706
+ if sliding_window is None:
707
+ raise ValueError(
708
+ "T5Gemma2 sliding_attention requires config.sliding_window"
709
+ )
710
+ q_idx = torch.arange(seq_len, device=device)[:, None]
711
+ kv_idx = torch.arange(seq_len, device=device)[None, :]
712
+ left_window_size = (sliding_window + 1) // 2
713
+ right_window_size = sliding_window // 2 + 1
714
+ dist = q_idx - kv_idx
715
+ local = ((dist >= 0) & (dist < left_window_size)) | (
716
+ (dist < 0) & (-dist < right_window_size)
717
+ )
718
+ allowed = allowed & local[None, None, :, :]
719
+
720
+ min_value = torch.finfo(inputs_embeds.dtype).min
721
+ additive_mask = torch.zeros(
722
+ (batch_size, 1, seq_len, seq_len), device=device, dtype=inputs_embeds.dtype
723
+ )
724
+ additive_mask.masked_fill_(~allowed, min_value)
725
+ return additive_mask
726
+
727
+ def forward(
728
+ self,
729
+ input_ids: torch.LongTensor | None = None,
730
+ attention_mask: torch.Tensor | None = None,
731
+ position_ids: torch.LongTensor | None = None,
732
+ inputs_embeds: torch.FloatTensor | None = None,
733
+ output_hidden_states: bool | None = None,
734
+ return_dict: bool | None = None,
735
+ token_type_ids: torch.Tensor | None = None,
736
+ **kwargs,
737
+ ) -> BaseModelOutput:
738
+ if (input_ids is None) == (inputs_embeds is None):
739
+ raise ValueError(
740
+ "You must specify exactly one of input_ids or inputs_embeds"
741
+ )
742
+ kwargs.pop("past_key_values", None)
743
+ kwargs.pop("use_cache", None)
744
+
745
+ output_hidden_states = (
746
+ output_hidden_states
747
+ if output_hidden_states is not None
748
+ else self.config.output_hidden_states
749
+ )
750
+ return_dict = (
751
+ return_dict if return_dict is not None else self.config.use_return_dict
752
+ )
753
+
754
+ if inputs_embeds is None:
755
+ inputs_embeds = self.embed_tokens(input_ids)
756
+ if position_ids is None:
757
+ position_ids = torch.arange(
758
+ 0, inputs_embeds.shape[1], device=inputs_embeds.device
759
+ ).unsqueeze(0)
760
+
761
+ hidden_states = self.dropout(inputs_embeds)
762
+ all_hidden_states = () if output_hidden_states else None
763
+
764
+ position_embeddings = {
765
+ layer_type: self.rotary_emb(hidden_states, position_ids, layer_type)
766
+ for layer_type in set(self.config.layer_types)
767
+ }
768
+ attn_impl = getattr(self.config, "_attn_implementation", None) or getattr(
769
+ self.config,
770
+ "preferred_attn_implementation",
771
+ "flash_attention_2",
772
+ )
773
+ if attn_impl == "flash_attention_2":
774
+ padding_mask = None
775
+ if getattr(self, "_force_static_attention_mask", False) or attention_mask is not None and not bool(
776
+ attention_mask.to(torch.bool).all()
777
+ ):
778
+ padding_mask = attention_mask
779
+ attn_masks = {
780
+ layer_type: padding_mask for layer_type in set(self.config.layer_types)
781
+ }
782
+ else:
783
+ attn_masks = {
784
+ layer_type: self._build_additive_attention_mask(
785
+ attention_mask,
786
+ inputs_embeds,
787
+ layer_type,
788
+ self.config.sliding_window,
789
+ )
790
+ for layer_type in set(self.config.layer_types)
791
+ }
792
+
793
+ for layer_module in self.layers[: self.config.num_hidden_layers]:
794
+ if output_hidden_states:
795
+ all_hidden_states += (hidden_states,)
796
+ hidden_states = layer_module(
797
+ hidden_states,
798
+ position_embeddings[layer_module.attention_type],
799
+ attn_masks[layer_module.attention_type],
800
+ output_attentions=False,
801
+ **kwargs,
802
+ )
803
+
804
+ hidden_states = self.norm(hidden_states)
805
+ hidden_states = self.dropout(hidden_states)
806
+ if output_hidden_states:
807
+ all_hidden_states += (hidden_states,)
808
+
809
+ if not return_dict:
810
+ return tuple(v for v in [hidden_states, all_hidden_states] if v is not None)
811
+ return BaseModelOutput(
812
+ last_hidden_state=hidden_states, hidden_states=all_hidden_states
813
+ )
models/text_encoder_graph.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Batch-1 static CUDA Graph cache for the HF text encoder."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ from collections.abc import Sequence
7
+ from dataclasses import dataclass
8
+ from typing import Any
9
+
10
+ import torch
11
+
12
+
13
+ @dataclass
14
+ class _GraphRecord:
15
+ graph: torch.cuda.CUDAGraph
16
+ stream: torch.cuda.Stream
17
+ input_ids: torch.Tensor
18
+ attention_mask: torch.Tensor
19
+ position_ids: torch.Tensor
20
+ output: torch.Tensor
21
+
22
+
23
+ class TextEncoderGraphCache:
24
+ """Capture fixed padded text-encoder buckets and replay them without CPU dispatch."""
25
+
26
+ def __init__(
27
+ self, text_encoder: torch.nn.Module, *, token_granularity: int = 32
28
+ ) -> None:
29
+ if token_granularity <= 0:
30
+ raise ValueError("token_granularity must be > 0")
31
+ self.text_encoder = text_encoder
32
+ self.text_encoder._force_static_attention_mask = True
33
+ self.text_encoder.config._attn_implementation = "sdpa"
34
+ self.token_granularity = int(token_granularity)
35
+ self._records: dict[tuple[int, int], _GraphRecord] = {}
36
+ self._lock = threading.RLock()
37
+ self._graph_pool = torch.cuda.graph_pool_handle()
38
+ self.captures = 0
39
+ self.replays = 0
40
+ self._frozen = False
41
+
42
+ def _bucket(self, length: int) -> int:
43
+ return (
44
+ (int(length) + self.token_granularity - 1) // self.token_granularity
45
+ ) * self.token_granularity
46
+
47
+ @staticmethod
48
+ def _copy_inputs(
49
+ segments: Sequence[torch.Tensor],
50
+ input_ids: torch.Tensor,
51
+ attention_mask: torch.Tensor,
52
+ position_ids: torch.Tensor,
53
+ ) -> None:
54
+ input_ids.zero_()
55
+ attention_mask.zero_()
56
+ position_ids.zero_()
57
+ for row, segment in enumerate(segments):
58
+ length = int(segment.shape[0])
59
+ input_ids[row, :length].copy_(segment)
60
+ attention_mask[row, :length].fill_(1)
61
+ position_ids[row, :length].copy_(
62
+ torch.arange(length, device=segment.device, dtype=torch.long)
63
+ )
64
+
65
+ @torch.inference_mode()
66
+ def __call__(
67
+ self, segments: Sequence[torch.Tensor]
68
+ ) -> tuple[list[torch.Tensor], list[Any]]:
69
+ if not segments:
70
+ return [], []
71
+ lengths = [int(segment.shape[0]) for segment in segments]
72
+ if any(length <= 0 for length in lengths):
73
+ raise ValueError("text encoder graph does not support empty segments")
74
+ batch_size = len(segments)
75
+ max_length = self._bucket(max(lengths))
76
+ key = (batch_size, max_length)
77
+
78
+ with self._lock:
79
+ record = self._records.get(key)
80
+ if record is None:
81
+ if self._frozen:
82
+ raise RuntimeError(
83
+ f"text encoder CUDA graph {key} was not declared in the warmup profile"
84
+ )
85
+ device = segments[0].device
86
+ static_ids = torch.zeros(
87
+ (batch_size, max_length), dtype=segments[0].dtype, device=device
88
+ )
89
+ static_mask = torch.zeros(
90
+ (batch_size, max_length), dtype=torch.long, device=device
91
+ )
92
+ static_positions = torch.zeros_like(static_mask)
93
+ self._copy_inputs(segments, static_ids, static_mask, static_positions)
94
+
95
+ capture_stream = torch.cuda.Stream(device=device)
96
+ capture_stream.wait_stream(torch.cuda.current_stream(device))
97
+ with torch.cuda.stream(capture_stream):
98
+ for _ in range(3):
99
+ static_output = self.text_encoder(
100
+ input_ids=static_ids,
101
+ attention_mask=static_mask,
102
+ position_ids=static_positions,
103
+ output_hidden_states=False,
104
+ ).last_hidden_state
105
+ capture_stream.synchronize()
106
+
107
+ graph = torch.cuda.CUDAGraph()
108
+ with torch.cuda.graph(
109
+ graph,
110
+ stream=capture_stream,
111
+ pool=self._graph_pool,
112
+ capture_error_mode="thread_local",
113
+ ):
114
+ static_output = self.text_encoder(
115
+ input_ids=static_ids,
116
+ attention_mask=static_mask,
117
+ position_ids=static_positions,
118
+ output_hidden_states=False,
119
+ ).last_hidden_state
120
+ record = _GraphRecord(
121
+ graph=graph,
122
+ stream=capture_stream,
123
+ input_ids=static_ids,
124
+ attention_mask=static_mask,
125
+ position_ids=static_positions,
126
+ output=static_output,
127
+ )
128
+ self._records[key] = record
129
+ self.captures += 1
130
+
131
+ self._copy_inputs(
132
+ segments,
133
+ record.input_ids,
134
+ record.attention_mask,
135
+ record.position_ids,
136
+ )
137
+ current_stream = torch.cuda.current_stream(segments[0].device)
138
+ record.stream.wait_stream(current_stream)
139
+ record.graph.replay()
140
+ current_stream.wait_stream(record.stream)
141
+ self.replays += 1
142
+ hidden_states = [
143
+ record.output[row, :length] for row, length in enumerate(lengths)
144
+ ]
145
+ return hidden_states, []
146
+
147
+ @property
148
+ def graph_keys(self) -> tuple[tuple[int, int], ...]:
149
+ return tuple(sorted(self._records))
150
+
151
+ @torch.inference_mode()
152
+ def warmup_graph(
153
+ self, *, batch_size: int, token_length: int, device: torch.device
154
+ ) -> None:
155
+ if self._frozen:
156
+ raise RuntimeError("text encoder CUDA graph cache is already frozen")
157
+ segments = [
158
+ torch.zeros(token_length, dtype=torch.long, device=device)
159
+ for _ in range(batch_size)
160
+ ]
161
+ self(segments)
162
+
163
+ def freeze(self) -> None:
164
+ self._frozen = True
models/warmup_profile.py ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class TextEncoderWarmupGraph:
11
+ batch_size: int
12
+ token_length: int
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class BackbonePrefillWarmupGraph:
17
+ branch_batch_size: int
18
+ sequence_length: int
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class SyntheticWarmupRequest:
23
+ template: str
24
+ text: str
25
+ instruction: str
26
+ speaker: str
27
+ seed: int
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class FastStreamingWarmupProfile:
32
+ schema_version: int
33
+ name: str
34
+ cfg_scales: tuple[float, ...]
35
+ text_encoder_graphs: tuple[TextEncoderWarmupGraph, ...]
36
+ backbone_prefill_graphs: tuple[BackbonePrefillWarmupGraph, ...]
37
+ backbone_decode_branch_batch_sizes: tuple[int, ...]
38
+ depth_decoder_batch_sizes: tuple[int, ...]
39
+ codec_chunk_frames: int
40
+ codec_num_lanes: int
41
+ warmup_request: SyntheticWarmupRequest
42
+ freeze_after_warmup: bool
43
+ source: str | None = None
44
+
45
+ @property
46
+ def cfg_modes(self) -> tuple[str, ...]:
47
+ return tuple(
48
+ "single_cfg" if cfg_scale != 1.0 else "no_cfg"
49
+ for cfg_scale in self.cfg_scales
50
+ )
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ return {
54
+ "schema_version": self.schema_version,
55
+ "name": self.name,
56
+ "service": {
57
+ "concurrency": 1,
58
+ "cfg_scales": list(self.cfg_scales),
59
+ "cfg_modes": list(self.cfg_modes),
60
+ "freeze_after_warmup": self.freeze_after_warmup,
61
+ },
62
+ "stages": {
63
+ "text_encoder": {
64
+ "graphs": [
65
+ {
66
+ "batch_size": graph.batch_size,
67
+ "token_length": graph.token_length,
68
+ }
69
+ for graph in self.text_encoder_graphs
70
+ ]
71
+ },
72
+ "backbone_prefill": {
73
+ "graphs": [
74
+ {
75
+ "branch_batch_size": graph.branch_batch_size,
76
+ "sequence_length": graph.sequence_length,
77
+ }
78
+ for graph in self.backbone_prefill_graphs
79
+ ]
80
+ },
81
+ "backbone_decode": {
82
+ "graphs": [
83
+ {"branch_batch_size": batch_size}
84
+ for batch_size in self.backbone_decode_branch_batch_sizes
85
+ ]
86
+ },
87
+ "depth_decoder": {
88
+ "graphs": [
89
+ {"batch_size": batch_size}
90
+ for batch_size in self.depth_decoder_batch_sizes
91
+ ]
92
+ },
93
+ "codec": {
94
+ "graphs": [
95
+ {
96
+ "num_lanes": self.codec_num_lanes,
97
+ "chunk_frames": self.codec_chunk_frames,
98
+ }
99
+ ]
100
+ },
101
+ },
102
+ "warmup_request": {
103
+ "template": self.warmup_request.template,
104
+ "text": self.warmup_request.text,
105
+ "instruction": self.warmup_request.instruction,
106
+ "speaker": self.warmup_request.speaker,
107
+ "seed": self.warmup_request.seed,
108
+ },
109
+ }
110
+
111
+
112
+ def _positive_int(value: Any, path: str) -> int:
113
+ value = int(value)
114
+ if value <= 0:
115
+ raise ValueError(f"{path} must be > 0")
116
+ return value
117
+
118
+
119
+ def _graphs(stages: dict[str, Any], stage: str) -> list[dict[str, Any]]:
120
+ value = stages.get(stage)
121
+ if not isinstance(value, dict):
122
+ raise ValueError(f"stages.{stage} must be an object")
123
+ graphs = value.get("graphs")
124
+ if not isinstance(graphs, list) or not graphs:
125
+ raise ValueError(f"stages.{stage}.graphs must be a non-empty list")
126
+ if not all(isinstance(graph, dict) for graph in graphs):
127
+ raise ValueError(f"stages.{stage}.graphs entries must be objects")
128
+ return graphs
129
+
130
+
131
+ def parse_warmup_profile(
132
+ payload: dict[str, Any], *, source: str | None = None
133
+ ) -> FastStreamingWarmupProfile:
134
+ if int(payload.get("schema_version", 0)) != 1:
135
+ raise ValueError("warmup profile schema_version must be 1")
136
+ name = str(payload.get("name", "")).strip()
137
+ if not name:
138
+ raise ValueError("warmup profile name must not be empty")
139
+
140
+ service = payload.get("service")
141
+ if not isinstance(service, dict):
142
+ raise ValueError("service must be an object")
143
+ concurrency = _positive_int(service.get("concurrency", 0), "service.concurrency")
144
+ if concurrency != 1:
145
+ raise ValueError("fast streaming warmup supports only service.concurrency=1")
146
+ raw_cfg_scales = service.get("cfg_scales")
147
+ if not isinstance(raw_cfg_scales, list) or not raw_cfg_scales:
148
+ raise ValueError("service.cfg_scales must be a non-empty list")
149
+ cfg_scales = tuple(sorted({float(value) for value in raw_cfg_scales}))
150
+ if any(cfg_scale < 1.0 for cfg_scale in cfg_scales):
151
+ raise ValueError("warmup profiles require service.cfg_scales values >= 1")
152
+ expected_branch_batches = tuple(
153
+ sorted({1 if cfg_scale == 1.0 else 2 for cfg_scale in cfg_scales})
154
+ )
155
+ freeze_after_warmup = bool(service.get("freeze_after_warmup", True))
156
+
157
+ stages = payload.get("stages")
158
+ if not isinstance(stages, dict):
159
+ raise ValueError("stages must be an object")
160
+
161
+ text_graphs = tuple(
162
+ TextEncoderWarmupGraph(
163
+ batch_size=_positive_int(
164
+ graph.get("batch_size"), "text_encoder.batch_size"
165
+ ),
166
+ token_length=_positive_int(
167
+ graph.get("token_length"), "text_encoder.token_length"
168
+ ),
169
+ )
170
+ for graph in _graphs(stages, "text_encoder")
171
+ )
172
+ if any(graph.token_length % 32 for graph in text_graphs):
173
+ raise ValueError("text_encoder token_length values must be multiples of 32")
174
+ prefill_graphs = tuple(
175
+ BackbonePrefillWarmupGraph(
176
+ branch_batch_size=_positive_int(
177
+ graph.get("branch_batch_size"), "backbone_prefill.branch_batch_size"
178
+ ),
179
+ sequence_length=_positive_int(
180
+ graph.get("sequence_length"), "backbone_prefill.sequence_length"
181
+ ),
182
+ )
183
+ for graph in _graphs(stages, "backbone_prefill")
184
+ )
185
+ if any(graph.sequence_length % 32 for graph in prefill_graphs):
186
+ raise ValueError(
187
+ "backbone_prefill sequence_length values must be multiples of 32"
188
+ )
189
+
190
+ decode_batches = tuple(
191
+ sorted(
192
+ {
193
+ _positive_int(
194
+ graph.get("branch_batch_size"),
195
+ "backbone_decode.branch_batch_size",
196
+ )
197
+ for graph in _graphs(stages, "backbone_decode")
198
+ }
199
+ )
200
+ )
201
+ if decode_batches != expected_branch_batches:
202
+ raise ValueError(
203
+ "backbone_decode branch batches must match cfg_scales; "
204
+ f"expected {expected_branch_batches}, got {decode_batches}"
205
+ )
206
+ if {graph.branch_batch_size for graph in prefill_graphs} != set(decode_batches):
207
+ raise ValueError(
208
+ "backbone_prefill must declare graphs for every backbone_decode branch batch"
209
+ )
210
+
211
+ depth_batches = tuple(
212
+ _positive_int(graph.get("batch_size"), "depth_decoder.batch_size")
213
+ for graph in _graphs(stages, "depth_decoder")
214
+ )
215
+ if tuple(sorted(set(depth_batches))) != expected_branch_batches:
216
+ raise ValueError(
217
+ "depth_decoder batch sizes must match cfg_scales; "
218
+ f"expected {expected_branch_batches}"
219
+ )
220
+
221
+ codec_graphs = _graphs(stages, "codec")
222
+ if len(codec_graphs) != 1:
223
+ raise ValueError("codec must declare exactly one graph")
224
+ codec_num_lanes = _positive_int(codec_graphs[0].get("num_lanes"), "codec.num_lanes")
225
+ codec_chunk_frames = _positive_int(
226
+ codec_graphs[0].get("chunk_frames"), "codec.chunk_frames"
227
+ )
228
+ if codec_num_lanes != 1:
229
+ raise ValueError("single-concurrency service requires codec.num_lanes=1")
230
+
231
+ warmup_request_payload = payload.get("warmup_request")
232
+ if not isinstance(warmup_request_payload, dict):
233
+ raise ValueError("warmup_request must be an object")
234
+ warmup_request = SyntheticWarmupRequest(
235
+ template=str(warmup_request_payload.get("template", "")).strip(),
236
+ text=str(warmup_request_payload.get("text", "")).strip(),
237
+ instruction=str(warmup_request_payload.get("instruction", "")).strip(),
238
+ speaker=str(warmup_request_payload.get("speaker", "S0")).strip(),
239
+ seed=int(warmup_request_payload.get("seed", 42)),
240
+ )
241
+ if not warmup_request.template or not warmup_request.text:
242
+ raise ValueError(
243
+ "warmup_request.template and warmup_request.text must not be empty"
244
+ )
245
+
246
+ return FastStreamingWarmupProfile(
247
+ schema_version=1,
248
+ name=name,
249
+ cfg_scales=cfg_scales,
250
+ text_encoder_graphs=tuple(
251
+ sorted(
252
+ set(text_graphs),
253
+ key=lambda graph: (graph.batch_size, graph.token_length),
254
+ )
255
+ ),
256
+ backbone_prefill_graphs=tuple(
257
+ sorted(
258
+ set(prefill_graphs),
259
+ key=lambda graph: (graph.branch_batch_size, graph.sequence_length),
260
+ )
261
+ ),
262
+ backbone_decode_branch_batch_sizes=decode_batches,
263
+ depth_decoder_batch_sizes=depth_batches,
264
+ codec_chunk_frames=codec_chunk_frames,
265
+ codec_num_lanes=codec_num_lanes,
266
+ warmup_request=warmup_request,
267
+ freeze_after_warmup=freeze_after_warmup,
268
+ source=source,
269
+ )
270
+
271
+
272
+ def load_warmup_profile(path: str | Path) -> FastStreamingWarmupProfile:
273
+ path = Path(path).resolve()
274
+ with path.open("r", encoding="utf-8") as handle:
275
+ payload = json.load(handle)
276
+ if not isinstance(payload, dict):
277
+ raise ValueError("warmup profile root must be an object")
278
+ return parse_warmup_profile(payload, source=str(path))
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ ffmpeg
2
+ libsndfile1
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ torch==2.9.1
2
+ torchaudio==2.9.1
3
+ qwen-tts==0.1.1
4
+ transformers==4.57.3
5
+ numpy>=2.0,<3
6
+ soundfile>=0.13