Spaces:
Runtime error
Runtime error
Launch complete Breeze TTS 2 ZeroGPU studio
Browse files- .gitattributes +3 -31
- MODEL_LICENSE +427 -0
- README.md +52 -6
- UPSTREAM_CODE_LICENSE +175 -0
- app.py +807 -0
- breeze_infer/__init__.py +1 -0
- breeze_infer/api.py +256 -0
- breeze_infer/audio.py +22 -0
- breeze_infer/runtime.py +106 -0
- breeze_infer/templates.py +339 -0
- configs/fast.json +249 -0
- models/__init__.py +0 -0
- models/breeze.py +1888 -0
- models/breeze_backbone_factory.py +309 -0
- models/breeze_base_config.py +454 -0
- models/breeze_config.py +54 -0
- models/cudagraph/__init__.py +7 -0
- models/cudagraph/backbone_graph.py +397 -0
- models/cudagraph/backbone_prefill_graph.py +255 -0
- models/cudagraph/depth_decoder_graph.py +987 -0
- models/cudagraph/sampling.py +99 -0
- models/fast_streaming.py +943 -0
- models/generation_breeze.py +1352 -0
- models/logits_process.py +78 -0
- models/stream_runtime/__init__.py +15 -0
- models/stream_runtime/core/__init__.py +21 -0
- models/stream_runtime/core/compat.py +33 -0
- models/stream_runtime/inference/__init__.py +15 -0
- models/stream_runtime/stream/__init__.py +13 -0
- models/stream_runtime/stream/kv_cache.py +104 -0
- models/stream_runtime/stream/lane.py +435 -0
- models/stream_runtime/stream/runtime.py +746 -0
- models/stream_runtime/stream/state.py +165 -0
- models/stream_runtime/stream/workspace.py +67 -0
- models/t5gemma2_compat.py +813 -0
- models/text_encoder_graph.py +164 -0
- models/warmup_profile.py +278 -0
- packages.txt +2 -0
- requirements.txt +6 -0
.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 |
-
|
| 27 |
-
*.
|
| 28 |
-
*.
|
| 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:
|
| 5 |
-
colorTo:
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
<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
|