Add files using upload-large-folder tool
Browse files- README.md +232 -0
- code/PYTHON.md +89 -0
- code/config/accelerate_config.yaml +18 -0
- code/config/default.ini +85 -0
- code/config/diffrhythm-1b.json +13 -0
- code/dataset/dataset.py +171 -0
- code/dataset/train.scp +4 -0
- code/g2p/g2p_generation.py +133 -0
- code/g2p/sources/bpmf_2_pinyin.txt +41 -0
- code/g2p/sources/pinyin_2_bpmf.txt +429 -0
- code/infer/infer.py +228 -0
- code/infer/infer_utils.py +459 -0
- code/model/__init__.py +6 -0
- code/model/cfm.py +318 -0
- code/model/dit.py +224 -0
- code/model/modules.py +660 -0
- code/model/trainer.py +334 -0
- code/model/utils.py +200 -0
- code/pyproject.toml +24 -0
- code/tt_diffrhythm/__init__.py +18 -0
- code/tt_diffrhythm/common.py +113 -0
- code/tt_diffrhythm/diffrhythm_dit.py +401 -0
- code/tt_diffrhythm/diffrhythm_host.py +224 -0
- code/tt_diffrhythm/local.py +142 -0
- code/tt_diffrhythm/oobleck_vae.py +296 -0
- code/tt_diffrhythm/remote.py +264 -0
- code/tt_diffrhythm/server.py +334 -0
- code/tt_diffrhythm/shapes.py +5 -0
- code/tt_diffrhythm/warm_cache.py +69 -0
- code/tt_diffrhythm/wire.py +18 -0
- code/tt_diffrhythm/worker.py +127 -0
- image/blobs/sha256/0cc125e439ef6882a01bae57bece67f14ec2ad123aec1fb12e86bc50175a4b44 +28 -0
- image/blobs/sha256/340cf5e2c047e0fc940f2973810428f58250242238231682812dec6f6d54f3ac +0 -0
- image/blobs/sha256/44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a +1 -0
- image/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 +0 -0
- image/blobs/sha256/6acee8cd93aa0889cc3a8b09f1b2564234ee82aa7c31e87ab61591552d15e894 +0 -0
- image/blobs/sha256/81aae6dd123609902a2f56719e50ef0bcf3aa00aff71fc66b02648b3f4aea2f2 +121 -0
- image/blobs/sha256/8f09ce5e10995b3fa6362dae9945d9402a5ccabce3d5bf20df0a53c75d9f94ab +0 -0
- image/blobs/sha256/8f94a918ad7d7da27e2b025adda011abaa34d892741d6b28221015c75a4ac5ba +26 -0
- image/blobs/sha256/9b3433e39d3d40a2f64d1f1e8cb155387651684c5f5c9473bd733c72056dee58 +1 -0
- image/blobs/sha256/c2a425aad5e9144add8f2a619c6e3d452d30ddbb5d4f4d3e45caab4f7bb3d233 +0 -0
- image/blobs/sha256/c6bd350dd2ed57d0ea34248e5b27d985a6786330d2d3ec29f5563e03ca9f00da +0 -0
- image/blobs/sha256/e600ba821186d2d540ea813a478dda1e629c539eef283f062f31309cb8c3af47 +0 -0
- image/blobs/sha256/eae56eb3c9610901618ab9c30e2ba8cdcf6981cdd3af7ed5755aba2cc46db6e1 +1 -0
- image/blobs/sha256/ee5adbd6eba4dfd9ba7e59692c46379b3d60a88f8445ec6a045d4aeb336f5bb9 +0 -0
- image/index.json +1 -0
- image/manifest.json +1 -0
- image/oci-layout +1 -0
- requirements.lock +148 -0
- tt_kernel_manifest.json +130 -0
README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
tags:
|
| 3 |
+
- blackhole
|
| 4 |
+
- p100a
|
| 5 |
+
- tenstorrent
|
| 6 |
+
- ttnn
|
| 7 |
+
- tt-metal
|
| 8 |
+
- tt-model-catalog
|
| 9 |
+
- tt-nn
|
| 10 |
+
- text-to-audio
|
| 11 |
+
- music-generation
|
| 12 |
+
- diffrhythm
|
| 13 |
+
- tt-dit-server
|
| 14 |
+
- tt-model-cache
|
| 15 |
+
- tt-model-container
|
| 16 |
+
pipeline_tag: text-to-audio
|
| 17 |
+
license: apache-2.0
|
| 18 |
+
license_link: https://huggingface.co/ASLP-lab/DiffRhythm-1_2-full
|
| 19 |
+
base_model:
|
| 20 |
+
- ASLP-lab/DiffRhythm-1_2-full
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
# diffrhythm-1.2-p100a
|
| 24 |
+
|
| 25 |
+
DiffRhythm 1.2 Full, ported to one Tenstorrent Blackhole p100a.
|
| 26 |
+
Weights: [ASLP-lab/DiffRhythm-1_2-full](https://huggingface.co/ASLP-lab/DiffRhythm-1_2-full) · Upstream code: [ASLP-lab/DiffRhythm](https://github.com/ASLP-lab/DiffRhythm) · Port: [SunandBean/tt-diffrhythm](https://github.com/SunandBean/tt-diffrhythm)
|
| 27 |
+
|
| 28 |
+
Runs on **p100a** (mesh 1×1). The **CFM transformer and the stable-audio VAE decoder run on the card**; the style encoder, the sampler and the post-processing stay on the CPU.
|
| 29 |
+
|
| 30 |
+
**What this port adds** — the CFM transformer and the stable-audio-tools Oobleck decoder on the card, sharing the worker, transport and host helpers with the sibling [tt-acestep](https://github.com/SunandBean/tt-acestep) port.
|
| 31 |
+
**What it builds on** — DiffRhythm's own pipeline (Apache-2.0), which runs unchanged on the CPU; only those two stages move.
|
| 32 |
+
|
| 33 |
+
**A 96-second song in 10.0 s** end to end — the CFM transformer 7.7 s across 62 calls, the audio VAE decode 2.0 s. The same song takes 9.7–10.1 s on an RTX 5070 Ti.
|
| 34 |
+
|
| 35 |
+
## Listen
|
| 36 |
+
|
| 37 |
+
Exactly what the container returned on run 1 of the measurement below — `POST /predict` with
|
| 38 |
+
`{"prompt": "warm lo-fi hip hop, mellow rhodes piano, soft vinyl crackle, relaxed drums",
|
| 39 |
+
"instrumental": true, "duration": 96, "seed": 42}`, generated in **10.05 s** on the card. The file is
|
| 40 |
+
the response's WAV bytes, unmodified: 96.0 s stereo 44.1 kHz 16-bit, sha256 `7e89a573e6d94e4f`.
|
| 41 |
+
|
| 42 |
+
<audio controls src="https://huggingface.co/sunandbean/diffrhythm-1.2-p100a/resolve/main/media/diffrhythm_96s_instrumental.wav"></audio>
|
| 43 |
+
|
| 44 |
+
[`media/diffrhythm_96s_instrumental.wav`](media/diffrhythm_96s_instrumental.wav) (16 MB)
|
| 45 |
+
|
| 46 |
+
## How it is wired
|
| 47 |
+
|
| 48 |
+
Two stages move to the card and nothing else changes: `remote.py` replaces `cfm.transformer` with a `RemoteDiffRhythmDiT` and decodes through a `RemoteVae`, so the upstream `CFM.sample` loop runs unmodified.
|
| 49 |
+
|
| 50 |
+
Both shims reach the card through exactly one method, `worker.call(msg)`, and there are two things that answer it.
|
| 51 |
+
|
| 52 |
+
**Two processes** (`TTWorker`), when DiffRhythm and tt-metal cannot share an interpreter — which is the deployment this port came from, where DiffRhythm resolved `torch 2.10.0+cu128` and `numpy>=2` against ttnn's `torch 2.11.0+cpu` and `numpy 1.x`:
|
| 53 |
+
|
| 54 |
+
```
|
| 55 |
+
DiffRhythm venv (CPU) TT image (p100a)
|
| 56 |
+
───────────────────── ────────────────
|
| 57 |
+
style encoder ──socket──► worker.py
|
| 58 |
+
CFM sampler loop DiffRhythmDiT (on the card)
|
| 59 |
+
post-processing OobleckTT (on the card)
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
Tensors cross as raw little-endian bytes with a dtype and a shape (`wire.py`), because the two sides cannot share a pickle of a torch tensor.
|
| 63 |
+
|
| 64 |
+
**One process** (`LocalWorker`), when one environment satisfies both — which is what the container below is. `LocalWorker` answers the same `call(msg)` in-process, so the shims, their window arithmetic and their drop-in signatures are reused unchanged rather than reimplemented.
|
| 65 |
+
|
| 66 |
+
Messages still carry `wire`-encoded tensors in that case even though both ends are one process. That is deliberate: it is what lets the shims run against either worker, and the encoding is a float32 copy of a few MB against card work measured in seconds.
|
| 67 |
+
|
| 68 |
+
## Quickstart
|
| 69 |
+
|
| 70 |
+
Prerequisite: a tt-metal / ttnn environment for the worker, and DiffRhythm installed in its own environment for the host side.
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
hf download sunandbean/diffrhythm-1.2-p100a --include "code/*" --local-dir diffrhythm-1.2-p100a && cd diffrhythm-1.2-p100a
|
| 74 |
+
pip install -e code/
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
Host side, inside the DiffRhythm environment:
|
| 78 |
+
|
| 79 |
+
```python
|
| 80 |
+
from tt_diffrhythm import TTWorker, RemoteDiffRhythmDiT, RemoteVae
|
| 81 |
+
|
| 82 |
+
worker = TTWorker(cfm_model_pt, model="diffrhythm", vae=vae_model_pt).start()
|
| 83 |
+
worker.wait_ready()
|
| 84 |
+
cfm.transformer = RemoteDiffRhythmDiT(worker, cfm.transformer)
|
| 85 |
+
... # upstream CFM.sample runs unchanged
|
| 86 |
+
audio = RemoteVae(worker, upsample=2048)(latents)
|
| 87 |
+
worker.close()
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
Worker side, inside the tt-metal image:
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
+
python -m tt_diffrhythm.worker --socket /ipc/worker.sock \
|
| 94 |
+
--checkpoint /checkpoint/cfm_model.pt --vae /vae/vae_model.pt
|
| 95 |
+
python -m tt_diffrhythm.warm_cache # compile every sequence bucket ahead of time
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
| | |
|
| 99 |
+
|---|---|
|
| 100 |
+
| **Duration** | 96–285 seconds |
|
| 101 |
+
| **Lyrics** | Plain lyrics with estimated line timing, or a timestamped LRC |
|
| 102 |
+
| **Guidance** | CFG: `pred + 4 (pred − null)` — every step runs two DiT calls |
|
| 103 |
+
| **Shapes** | Song lengths fall into sequence buckets (`diffrhythm_host.seq_buckets`), both CFG branches, plus the VAE windows. `warm_cache` compiles all of them. |
|
| 104 |
+
|
| 105 |
+
## Performance
|
| 106 |
+
|
| 107 |
+
Measured on an idle p100a. A 96-second song is 2067 latent frames, padded to a sequence of 2560.
|
| 108 |
+
|
| 109 |
+
| | |
|
| 110 |
+
|---|---:|
|
| 111 |
+
| **DiT, whole 96-second song** | **8.9 s** (7.7 s at HiFi2) |
|
| 112 |
+
| One DiT call at sequence 2560 | 121 ms |
|
| 113 |
+
| Per call during the device check | 0.143–0.186 s |
|
| 114 |
+
|
| 115 |
+
Where a 121 ms call goes (`dit_profile.py`, 156.8 ms profiled):
|
| 116 |
+
|
| 117 |
+
| Op | Time | Calls |
|
| 118 |
+
|---|---:|---:|
|
| 119 |
+
| matmul | 66.6 ms | 66 |
|
| 120 |
+
| SDPA | 21.9 ms | 16 |
|
| 121 |
+
| add | 12.0 ms | 47 |
|
| 122 |
+
| RMS norm | 7.7 ms | 32 |
|
| 123 |
+
| slice | 7.3 ms | 62 |
|
| 124 |
+
| rotary embedding | 5.2 ms | 32 |
|
| 125 |
+
|
| 126 |
+
The audio VAE decoder, against a CPU decode of the same latents:
|
| 127 |
+
|
| 128 |
+
| Latent frames | CPU | p100a (first / warm) |
|
| 129 |
+
|---:|---:|---:|
|
| 130 |
+
| 512 | 10.67 s | 33.94 / **0.33** s |
|
| 131 |
+
| 1024 | — | 0.81 s |
|
| 132 |
+
|
| 133 |
+
The first decode at a shape pays the kernel compilation; afterwards it is 30× faster than the CPU.
|
| 134 |
+
|
| 135 |
+
### From the published container
|
| 136 |
+
|
| 137 |
+
The same image anyone can `tt-model pull`, reached over its own HTTP server on an idle card
|
| 138 |
+
(2026-10-09). Three 96-second instrumentals, seed 42, from a cold start:
|
| 139 |
+
|
| 140 |
+
| | run 1 | run 2 | run 3 |
|
| 141 |
+
|---|---:|---:|---:|
|
| 142 |
+
| **Generation** | **10.05 s** | 10.06 s | 10.04 s |
|
| 143 |
+
| CFM transformer, 62 calls | 7.68 s | 7.68 s | 7.69 s |
|
| 144 |
+
| VAE decode | 2.03 s | 2.05 s | 2.01 s |
|
| 145 |
+
|
| 146 |
+
All three returned the same 96.0 s stereo 44.1 kHz WAV. Load to ready was 50 s, of which the VAE
|
| 147 |
+
warm-up is 3.1 s; device DRAM after load is 2.9 GB of 29.9 — the card is nowhere near full, and
|
| 148 |
+
the port holds that headroom deliberately so the sequence buckets can grow to 285 seconds.
|
| 149 |
+
|
| 150 |
+
The whole request takes about 30 s of wall time, because the response carries the song: 17 MB of
|
| 151 |
+
WAV, base64-encoded. Generation is the 10 s above; the rest is serialising and shipping it.
|
| 152 |
+
|
| 153 |
+
### Against an RTX 5070 Ti
|
| 154 |
+
|
| 155 |
+
DiffRhythm's own GPU path with the deployment's stage-by-stage placement (one model in VRAM at a
|
| 156 |
+
time, which is what keeps it inside 16 GB), same prompt and duration, a fresh seed per run:
|
| 157 |
+
|
| 158 |
+
| | p100a (container) | RTX 5070 Ti |
|
| 159 |
+
|---|---:|---:|
|
| 160 |
+
| 96-second instrumental | 10.04–10.06 s | **9.73–10.05 s** |
|
| 161 |
+
| First run | 10.05 s | 12.35 s |
|
| 162 |
+
| Peak device memory | 2.9 GB of 29.9 | 2.5 GB of 15.5 |
|
| 163 |
+
|
| 164 |
+
The same, within noise. The card reaches it with the CFM transformer and the audio VAE decoder in
|
| 165 |
+
TTNN and everything else on the CPU; the GPU reaches it by moving three models in and out of VRAM.
|
| 166 |
+
|
| 167 |
+
### Accuracy
|
| 168 |
+
|
| 169 |
+
**The torch reference in this port against upstream DiffRhythm** (float32, per CFM call): PCC **0.9999997–0.9999999**. For scale, upstream float32 against the GPU fp16 run is 0.999995–0.999999 — so the reimplementation is closer to upstream than upstream is to its own GPU run.
|
| 170 |
+
|
| 171 |
+
**The card against this port's reference**: minimum PCC **0.99994** across the whole 96-second song.
|
| 172 |
+
|
| 173 |
+
**Final latents against the GPU fp16 run**, which is what actually matters after CFG amplification:
|
| 174 |
+
|
| 175 |
+
| | Final latent PCC | Relative RMS |
|
| 176 |
+
|---|---:|---:|
|
| 177 |
+
| CPU float32 | 0.99997 | 0.008 |
|
| 178 |
+
| **p100a (HiFi4, the default)** | **0.9954** | 0.096 |
|
| 179 |
+
| p100a (HiFi2) | 0.9931 | 0.118 |
|
| 180 |
+
| CPU bf16 | 0.99129 | 0.132 |
|
| 181 |
+
|
| 182 |
+
**The card beats a CPU bf16 run.** Decoded audio against the GPU: PCC 0.9721 and 2.06 dB spectral distance, against 0.9691 and 3.07 dB for CPU bf16 (CPU float32 is 0.9996 / 0.51 dB). The audio VAE decoder itself is PCC 0.99963 with 0.93 dB spectral distance against the CPU.
|
| 183 |
+
|
| 184 |
+
CFG is why the margin matters: `pred + 4 (pred − null)` multiplies the difference between the two branches by four, so a small per-call error is amplified before it reaches the audio. `DRPrecision` therefore keeps the activations, the residual and the output in fp32 and defaults to HiFi4 matmuls — HiFi2 is about 14% faster and measurably worse.
|
| 185 |
+
|
| 186 |
+
## Caveats
|
| 187 |
+
|
| 188 |
+
- **Single card only.** Mesh 1×1.
|
| 189 |
+
- **Seeds do not reproduce across backends.** A song made on the p100a cannot be reproduced bit-for-bit on a GPU with the same seed.
|
| 190 |
+
- **Two environments, or one.** Where DiffRhythm's environment and the tt-metal runtime cannot be merged, the host side runs inside DiffRhythm's own environment and the worker inside a tt-metal image. Where they can — which the container below is — `LocalWorker` runs both in one process. Which one you get is a property of the environment, not of the port.
|
| 191 |
+
- **CFG doubles the work.** Every sampler step is two DiT calls, one conditional and one null.
|
| 192 |
+
- The first song of each shape pays the kernel compilation, and so does the first VAE decode at a shape (33.9 s against 0.33 s warm). Run `warm_cache` after changing the code or the image.
|
| 193 |
+
- A render holds the card for the length of the song. On a shared card that blocks everything else.
|
| 194 |
+
|
| 195 |
+
## Licensing
|
| 196 |
+
|
| 197 |
+
- Weights: [ASLP-lab/DiffRhythm-1_2-full](https://huggingface.co/ASLP-lab/DiffRhythm-1_2-full), Apache-2.0. Not redistributed here.
|
| 198 |
+
- Port code (`code/tt_diffrhythm/`): Apache-2.0, from [SunandBean/tt-diffrhythm](https://github.com/SunandBean/tt-diffrhythm).
|
| 199 |
+
- `code/{model,infer,g2p,config,dataset}/` are **DiffRhythm itself** (Apache-2.0), at [`28ad63c`](https://github.com/ASLP-lab/DiffRhythm/commit/28ad63c0f096fe2ee258bcabbcf081d5d9366afd), unmodified. They ship here because the container runs them: upstream is a research checkout with no packaging, so it is vendored as source rather than resolved by pip. `code/models/` is [tt-metal](https://github.com/tenstorrent/tt-metal)'s `models/common` and `models/tt_dit` (Apache-2.0), for the same reason.
|
| 200 |
+
- `code/tt_diffrhythm/common.py` and the worker, transport and audio VAE modules are shared with the sibling [tt-acestep](https://github.com/SunandBean/tt-acestep) port (Apache-2.0); `oobleck_vae.py` implements both DiffRhythm's stable-audio-tools decoder and ACE-Step's diffusers Oobleck decoder.
|
| 201 |
+
|
| 202 |
+
## Run it as a container
|
| 203 |
+
|
| 204 |
+
Everything below is generated by `tt-model package` from the manifest this image was built from.
|
| 205 |
+
|
| 206 |
+
```bash
|
| 207 |
+
tt-model pull sunandbean/diffrhythm-1.2-p100a --with-weights
|
| 208 |
+
tt-model serve sunandbean/diffrhythm-1.2-p100a
|
| 209 |
+
```
|
| 210 |
+
|
| 211 |
+
`pull --with-weights` downloads the Docker image and the [`ASLP-lab/DiffRhythm-1_2-full`](https://huggingface.co/ASLP-lab/DiffRhythm-1_2-full) weights (into your HF cache; they are not in the image). `serve` starts the model's own HTTP server on port 20000 (or the next free port, if that one is busy); the first start compiles kernels for your device, which takes several minutes, and the server is ready when it logs `Application startup complete`.
|
| 212 |
+
|
| 213 |
+
### Request shape
|
| 214 |
+
|
| 215 |
+
`POST /predict` takes `{"prompt": str, "lyrics": str, "instrumental": bool,
|
| 216 |
+
"duration": int, "seed": int}` and returns a base64 WAV with `timing_s` and the `lrc` the
|
| 217 |
+
song was actually sung to.
|
| 218 |
+
|
| 219 |
+
`prompt` is the style description. Vocals need `lyrics`: real LRC is used as given, and
|
| 220 |
+
plain lines get approximate starts derived from their word counts -- a convenience, not
|
| 221 |
+
learned alignment. `duration` is 96-285 seconds; use the ACE-Step port for longer songs.
|
| 222 |
+
The schedule is fixed at **32 steps**; any other `inference_steps` is a 422.
|
| 223 |
+
|
| 224 |
+
### Provenance
|
| 225 |
+
|
| 226 |
+
The exact sources the image was built from — the model code under `code/` is byte-identical to what is inside the image (`pyproject.toml`, `PYTHON.md` and `examples/` are packaging files this repo adds, and are not in the image):
|
| 227 |
+
|
| 228 |
+
| component | built from |
|
| 229 |
+
| --- | --- |
|
| 230 |
+
| tt-metal | [`58648d44443223041148125843ca7c7d4780463e`](https://github.com/tenstorrent/tt-metal/commit/58648d44443223041148125843ca7c7d4780463e) |
|
| 231 |
+
| `code/` digest | `6fe96af2d7e7a1b8` (sha256, first 16 hex digits) |
|
| 232 |
+
| built | 2026-10-09T14:54:37+00:00 by tt-model 0.1.0 |
|
code/PYTHON.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# tt_diffrhythm — Python reference
|
| 2 |
+
|
| 3 |
+
DiffRhythm 1.2 Full on one Tenstorrent Blackhole p100a. The CFM transformer and the stable-audio VAE
|
| 4 |
+
decoder run on the card; everything else stays on the CPU in DiffRhythm's own environment.
|
| 5 |
+
|
| 6 |
+
## Two processes
|
| 7 |
+
|
| 8 |
+
DiffRhythm's environment and the tt-metal runtime need different torch and numpy builds, so they run
|
| 9 |
+
as two processes talking over a Unix socket in a directory bind-mounted into both. Tensors cross as
|
| 10 |
+
raw little-endian bytes with a dtype and a shape (`wire.py`).
|
| 11 |
+
|
| 12 |
+
## Host side (inside the DiffRhythm environment)
|
| 13 |
+
|
| 14 |
+
### `TTWorker(checkpoint, image=None, log=None, reply_timeout_s=120.0, model="diffrhythm", vae=None)`
|
| 15 |
+
|
| 16 |
+
`checkpoint` is `cfm_model.pt` and `vae` is `vae_model.pt`; Hugging Face cache links are resolved.
|
| 17 |
+
`image` is the tt-metal / ttnn image the port was built against; it has no portable default, so set
|
| 18 |
+
it here or through `MUSIC_TT_IMAGE`. `.start()` starts the worker container on the card,
|
| 19 |
+
`.wait_ready(timeout_s)` blocks until the weights are on the device, `.close()` stops the container and
|
| 20 |
+
waits for it to exit, so the card is free only after that.
|
| 21 |
+
|
| 22 |
+
### `RemoteDiffRhythmDiT(worker, transformer)`
|
| 23 |
+
|
| 24 |
+
A drop-in for `cfm.transformer` in `CFM.sample` (batch 1): same call, same output. Assign it and the
|
| 25 |
+
upstream sampler runs unchanged.
|
| 26 |
+
|
| 27 |
+
### `RemoteVae(worker, upsample=2048)`
|
| 28 |
+
|
| 29 |
+
`[B, 64, T]` latents to `[B, 2, T * upsample]` float32 audio on the CPU. It picks the largest VAE
|
| 30 |
+
window from `shapes.VAE_WINDOWS` that fits the song.
|
| 31 |
+
|
| 32 |
+
## Worker side (inside the tt-metal image)
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
python -m tt_diffrhythm.worker --socket /ipc/worker.sock \
|
| 36 |
+
--checkpoint /checkpoint/cfm_model.pt --vae /vae/vae_model.pt
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
Protocol (plain dicts, tensors via `wire`):
|
| 40 |
+
|
| 41 |
+
| | |
|
| 42 |
+
|---|---|
|
| 43 |
+
| `-> {"op": "ready", ...}` | once the weights are on the device |
|
| 44 |
+
| `<- {"op": "prepare", "text_embed": {drop_text: [T, 512]}, "cond": [T, 64]}` | `-> {"op": "prepared", ...}` |
|
| 45 |
+
| `<- {"op": "forward", "x": [T, 64], "drop_text": bool, "style": [512], "c": [512]}` | `-> {"op": "velocity", "v": [T, 64]}` |
|
| 46 |
+
|
| 47 |
+
## Direct use on the card
|
| 48 |
+
|
| 49 |
+
```python
|
| 50 |
+
from tt_diffrhythm import DiffRhythmDiT, DRPrecision, OobleckTT, open_device, close_device
|
| 51 |
+
from tt_diffrhythm.diffrhythm_host import Checkpoint
|
| 52 |
+
|
| 53 |
+
dev = open_device()
|
| 54 |
+
dit = DiffRhythmDiT(dev, Checkpoint("cfm_model.pt")) # DRPrecision() by default
|
| 55 |
+
vae = OobleckTT(dev, "vae_model.pt")
|
| 56 |
+
...
|
| 57 |
+
close_device(dev)
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
### `DRPrecision`
|
| 61 |
+
|
| 62 |
+
Extends the shared `Precision` with the choices CFG forces. `pred + 4 (pred − null)` multiplies the
|
| 63 |
+
difference between the two branches by four, so the activations, the residual and the output stay in
|
| 64 |
+
fp32 and matmuls default to **HiFi4**. HiFi2 is about 14% faster and measurably worse
|
| 65 |
+
(final latent PCC 0.9931 against 0.9954).
|
| 66 |
+
|
| 67 |
+
## Kernel cache
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
python -m tt_diffrhythm.warm_cache
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
Compiles every sequence bucket of a 96–285 s song, both CFG branches, and the VAE windows in
|
| 74 |
+
`shapes.VAE_WINDOWS`. The first decode at a VAE shape costs 33.9 s against 0.33 s warm, so this is
|
| 75 |
+
worth running after any change to the code or the image.
|
| 76 |
+
|
| 77 |
+
## Modules
|
| 78 |
+
|
| 79 |
+
| Module | Role |
|
| 80 |
+
|---|---|
|
| 81 |
+
| `remote.py` | host side: `TTWorker`, `RemoteDiffRhythmDiT`, `RemoteVae`, the worker container |
|
| 82 |
+
| `worker.py` | worker side: loads the CFM transformer and the VAE and answers one host pipeline |
|
| 83 |
+
| `wire.py` | the tensor transport between the two torch builds |
|
| 84 |
+
| `shapes.py` | shapes both sides need, with no torch or ttnn import |
|
| 85 |
+
| `diffrhythm_dit.py` | the CFM transformer on the card, and `DRPrecision` |
|
| 86 |
+
| `diffrhythm_host.py` | checkpoint access, the torch reference and the sequence buckets |
|
| 87 |
+
| `oobleck_vae.py` | the audio VAE decoder on the card |
|
| 88 |
+
| `common.py` | device and host helpers shared with the tt-acestep port |
|
| 89 |
+
| `warm_cache.py` | ahead-of-time kernel compilation for every bucket |
|
code/config/accelerate_config.yaml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
compute_environment: LOCAL_MACHINE
|
| 2 |
+
debug: false
|
| 3 |
+
distributed_type: MULTI_GPU
|
| 4 |
+
downcast_bf16: 'no'
|
| 5 |
+
enable_cpu_affinity: false
|
| 6 |
+
machine_rank: 0
|
| 7 |
+
main_training_function: main
|
| 8 |
+
mixed_precision: fp16
|
| 9 |
+
num_machines: 1
|
| 10 |
+
rdzv_backend: static
|
| 11 |
+
same_network: true
|
| 12 |
+
num_processes: 1
|
| 13 |
+
tpu_env: []
|
| 14 |
+
tpu_use_cluster: false
|
| 15 |
+
tpu_use_sudo: false
|
| 16 |
+
use_cpu: false
|
| 17 |
+
main_process_port: 12342
|
| 18 |
+
|
code/config/default.ini
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
[DEFAULTS]
|
| 3 |
+
|
| 4 |
+
#name of the run
|
| 5 |
+
exp_name = diffrhythm-test
|
| 6 |
+
|
| 7 |
+
# the batch size
|
| 8 |
+
batch_size = 8
|
| 9 |
+
|
| 10 |
+
# the chunk size
|
| 11 |
+
max_frames = 3000
|
| 12 |
+
min_frames = 10
|
| 13 |
+
|
| 14 |
+
# number of CPU workers for the DataLoader
|
| 15 |
+
num_workers = 4
|
| 16 |
+
|
| 17 |
+
# the random seed
|
| 18 |
+
seed = 42
|
| 19 |
+
|
| 20 |
+
# Batches for gradient accumulation
|
| 21 |
+
accum_batches = 1
|
| 22 |
+
|
| 23 |
+
# Number of steps between checkpoints
|
| 24 |
+
checkpoint_every = 10000
|
| 25 |
+
|
| 26 |
+
# trainer checkpoint file to restart training from
|
| 27 |
+
ckpt_path = ''
|
| 28 |
+
|
| 29 |
+
# model checkpoint file to start a new training run from
|
| 30 |
+
pretrained_ckpt_path = ''
|
| 31 |
+
|
| 32 |
+
# Checkpoint path for the pretransform model if needed
|
| 33 |
+
pretransform_ckpt_path = ''
|
| 34 |
+
|
| 35 |
+
# configuration model specifying model hyperparameters
|
| 36 |
+
model_config = ''
|
| 37 |
+
|
| 38 |
+
# configuration for datasets
|
| 39 |
+
dataset_config = ''
|
| 40 |
+
|
| 41 |
+
# directory to save the checkpoints in
|
| 42 |
+
save_dir = ''
|
| 43 |
+
|
| 44 |
+
# grad norm
|
| 45 |
+
max_grad_norm = 1.0
|
| 46 |
+
|
| 47 |
+
# grad accu
|
| 48 |
+
grad_accumulation_steps = 1
|
| 49 |
+
|
| 50 |
+
# lr 1e-5
|
| 51 |
+
learning_rate = 7.5e-5
|
| 52 |
+
|
| 53 |
+
# epoch
|
| 54 |
+
epochs = 110
|
| 55 |
+
|
| 56 |
+
# warmup steps
|
| 57 |
+
num_warmup_updates = 20
|
| 58 |
+
|
| 59 |
+
# save checkpoint per steps
|
| 60 |
+
save_per_updates = 5000
|
| 61 |
+
|
| 62 |
+
# save last checkpoint per steps
|
| 63 |
+
last_per_steps = 5000
|
| 64 |
+
|
| 65 |
+
file_path = "dataset/train.scp"
|
| 66 |
+
|
| 67 |
+
audio_drop_prob = 0.3
|
| 68 |
+
|
| 69 |
+
cond_drop_prob = 0.0
|
| 70 |
+
|
| 71 |
+
style_drop_prob = 0.1
|
| 72 |
+
|
| 73 |
+
lrc_drop_prob = 0.1
|
| 74 |
+
|
| 75 |
+
use_style_prompt = 1
|
| 76 |
+
|
| 77 |
+
reset_lr = 0
|
| 78 |
+
|
| 79 |
+
resumable_with_seed = 666
|
| 80 |
+
|
| 81 |
+
downsample_rate = 2048
|
| 82 |
+
|
| 83 |
+
grad_ckpt = 0
|
| 84 |
+
|
| 85 |
+
sampling_rate = 44100
|
code/config/diffrhythm-1b.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "diffrhythm",
|
| 3 |
+
"model": {
|
| 4 |
+
"dim": 2048,
|
| 5 |
+
"depth": 16,
|
| 6 |
+
"heads": 32,
|
| 7 |
+
"ff_mult": 4,
|
| 8 |
+
"text_dim": 512,
|
| 9 |
+
"conv_layers": 4,
|
| 10 |
+
"mel_dim": 64,
|
| 11 |
+
"text_num_embeds": 363
|
| 12 |
+
}
|
| 13 |
+
}
|
code/dataset/dataset.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 2 |
+
# 2025 Huakang Chen (huakang@mail.nwpu.edu.cn)
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
import random
|
| 18 |
+
|
| 19 |
+
class DiffusionDataset(torch.utils.data.Dataset):
|
| 20 |
+
def __init__(self, file_path, max_frames=2048, min_frames=512, sampling_rate=44100, downsample_rate=2048, precision='fp16'):
|
| 21 |
+
|
| 22 |
+
self.max_frames = max_frames
|
| 23 |
+
self.min_frames = min_frames
|
| 24 |
+
self.sampling_rate = sampling_rate
|
| 25 |
+
self.downsample_rate = 2048
|
| 26 |
+
self.max_secs = max_frames / (sampling_rate / downsample_rate)
|
| 27 |
+
|
| 28 |
+
self.file_path = file_path
|
| 29 |
+
with open(file_path, 'r') as f:
|
| 30 |
+
self.file_lst = [line.strip() for line in f.readlines()]
|
| 31 |
+
|
| 32 |
+
self.pad_token_id = 0
|
| 33 |
+
self.comma_token_id = 1
|
| 34 |
+
self.period_token_id = 2
|
| 35 |
+
self.start_token_id = 355
|
| 36 |
+
|
| 37 |
+
if precision == 'fp16':
|
| 38 |
+
self.feature_dtype = torch.float16
|
| 39 |
+
elif precision == 'bf16':
|
| 40 |
+
self.feature_dtype = torch.bfloat16
|
| 41 |
+
elif precision == 'fp32':
|
| 42 |
+
self.feature_dtype = torch.float32
|
| 43 |
+
|
| 44 |
+
random.seed(42)
|
| 45 |
+
random.shuffle(self.file_lst)
|
| 46 |
+
|
| 47 |
+
def load_item(self, item, field):
|
| 48 |
+
try:
|
| 49 |
+
item, reader_idx = item[field]
|
| 50 |
+
item = self.lance_connections[reader_idx].get_datas_by_rowids([item._rowid])[0]
|
| 51 |
+
except Exception as e:
|
| 52 |
+
return None
|
| 53 |
+
return item
|
| 54 |
+
|
| 55 |
+
def get_triple(self, item):
|
| 56 |
+
utt, lrc_path, latent_path, style_path = item.split("|")
|
| 57 |
+
|
| 58 |
+
time_lrc = torch.load(lrc_path, map_location='cpu')
|
| 59 |
+
input_times = time_lrc['time']
|
| 60 |
+
input_lrcs = time_lrc['lrc']
|
| 61 |
+
lrc_with_time = list(zip(input_times, input_lrcs))
|
| 62 |
+
|
| 63 |
+
latent = torch.load(latent_path, map_location='cpu') # [b, d, t]
|
| 64 |
+
latent = latent.squeeze(0)
|
| 65 |
+
|
| 66 |
+
prompt = torch.load(style_path, map_location='cpu') # [b, d]
|
| 67 |
+
prompt = prompt.squeeze(0)
|
| 68 |
+
|
| 69 |
+
max_start_frame = max(0, latent.shape[-1] - self.max_frames)
|
| 70 |
+
start_frame = random.randint(0, max_start_frame)
|
| 71 |
+
start_time = start_frame * self.downsample_rate / self.sampling_rate
|
| 72 |
+
normalized_start_time = start_frame / latent.shape[-1]
|
| 73 |
+
latent = latent[:, start_frame:]
|
| 74 |
+
|
| 75 |
+
lrc_with_time = [(time_start - start_time, line) for (time_start, line) in lrc_with_time if (time_start - start_time) >= 0] # empty for pure music
|
| 76 |
+
lrc_with_time = [(time_start, line) for (time_start, line) in lrc_with_time if time_start < self.max_secs] # drop time longer than max_secs
|
| 77 |
+
|
| 78 |
+
if len(lrc_with_time) >= 1:
|
| 79 |
+
latent_end_time = lrc_with_time[-1][0]
|
| 80 |
+
else:
|
| 81 |
+
raise
|
| 82 |
+
|
| 83 |
+
if self.max_frames == 2048:
|
| 84 |
+
lrc_with_time = lrc_with_time[:-1] if len(lrc_with_time) >= 1 else lrc_with_time # drop last, can be empty
|
| 85 |
+
|
| 86 |
+
lrc = torch.zeros((self.max_frames,), dtype=torch.long)
|
| 87 |
+
|
| 88 |
+
tokens_count = 0
|
| 89 |
+
last_end_pos = 0
|
| 90 |
+
for time_start, line in lrc_with_time:
|
| 91 |
+
tokens = [token if token != self.period_token_id else self.comma_token_id for token in line] + [self.period_token_id]
|
| 92 |
+
tokens = torch.tensor(tokens, dtype=torch.long)
|
| 93 |
+
num_tokens = tokens.shape[0]
|
| 94 |
+
|
| 95 |
+
gt_frame_start = int(time_start * self.sampling_rate / self.downsample_rate)
|
| 96 |
+
|
| 97 |
+
frame_shift = 0
|
| 98 |
+
|
| 99 |
+
frame_start = max(gt_frame_start - frame_shift, last_end_pos)
|
| 100 |
+
frame_len = min(num_tokens, self.max_frames - frame_start)
|
| 101 |
+
|
| 102 |
+
lrc[frame_start:frame_start + frame_len] = tokens[:frame_len]
|
| 103 |
+
|
| 104 |
+
tokens_count += num_tokens
|
| 105 |
+
last_end_pos = frame_start + frame_len
|
| 106 |
+
|
| 107 |
+
latent = latent[:, :int(latent_end_time * self.sampling_rate / self.downsample_rate)]
|
| 108 |
+
|
| 109 |
+
latent = latent.to(self.feature_dtype)
|
| 110 |
+
prompt = prompt.to(self.feature_dtype)
|
| 111 |
+
|
| 112 |
+
return prompt, lrc, latent, normalized_start_time
|
| 113 |
+
|
| 114 |
+
def __getitem__(self, index):
|
| 115 |
+
idx = index
|
| 116 |
+
while True:
|
| 117 |
+
try:
|
| 118 |
+
prompt, lrc, latent, start_time = self.get_triple(self.file_lst[idx])
|
| 119 |
+
if latent.shape[-1] < self.min_frames: # Too short
|
| 120 |
+
raise
|
| 121 |
+
item = {'prompt': prompt, "lrc": lrc, "latent": latent, "start_time": start_time}
|
| 122 |
+
return item
|
| 123 |
+
except Exception as e:
|
| 124 |
+
idx = random.randint(0, self.__len__() - 1)
|
| 125 |
+
continue
|
| 126 |
+
|
| 127 |
+
def __len__(self):
|
| 128 |
+
return len(self.file_lst)
|
| 129 |
+
|
| 130 |
+
def custom_collate_fn(self, batch):
|
| 131 |
+
latent_list = [item['latent'] for item in batch]
|
| 132 |
+
prompt_list = [item['prompt'] for item in batch]
|
| 133 |
+
lrc_list = [item['lrc'] for item in batch]
|
| 134 |
+
start_time_list = [item['start_time'] for item in batch]
|
| 135 |
+
|
| 136 |
+
latent_lengths = torch.LongTensor([latent.shape[-1] for latent in latent_list])
|
| 137 |
+
prompt_lengths = torch.LongTensor([prompt.shape[-1] for prompt in prompt_list])
|
| 138 |
+
lrc_lengths = torch.LongTensor([lrc.shape[-1] for lrc in lrc_list])
|
| 139 |
+
|
| 140 |
+
max_prompt_length = prompt_lengths.amax()
|
| 141 |
+
|
| 142 |
+
padded_prompt_list = []
|
| 143 |
+
for prompt in prompt_list:
|
| 144 |
+
padded_prompt = torch.nn.functional.pad(prompt, (0, max_prompt_length - prompt.shape[-1]))
|
| 145 |
+
padded_prompt_list.append(padded_prompt)
|
| 146 |
+
|
| 147 |
+
padded_latent_list = []
|
| 148 |
+
for latent in latent_list:
|
| 149 |
+
padded_latent = torch.nn.functional.pad(latent, (0, self.max_frames - latent.shape[-1]))
|
| 150 |
+
padded_latent_list.append(padded_latent)
|
| 151 |
+
|
| 152 |
+
padded_start_time_list = []
|
| 153 |
+
for start_time in start_time_list:
|
| 154 |
+
padded_start_time = start_time
|
| 155 |
+
padded_start_time_list.append(padded_start_time)
|
| 156 |
+
|
| 157 |
+
prompt_tensor = torch.stack(padded_prompt_list)
|
| 158 |
+
lrc_tensor = torch.stack(lrc_list)
|
| 159 |
+
latent_tensor = torch.stack(padded_latent_list)
|
| 160 |
+
start_time_tensor = torch.tensor(padded_start_time_list)
|
| 161 |
+
|
| 162 |
+
return {'prompt': prompt_tensor, 'lrc': lrc_tensor, 'latent': latent_tensor, \
|
| 163 |
+
"prompt_lengths": prompt_lengths, "lrc_lengths": lrc_lengths, "latent_lengths": latent_lengths, \
|
| 164 |
+
"start_time": start_time_tensor}
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
if __name__ == "__main__":
|
| 168 |
+
dd = DiffusionDataset("train.scp", 2048, 512)
|
| 169 |
+
x = dd[0]
|
| 170 |
+
import pdb; pdb.set_trace()
|
| 171 |
+
print(x)
|
code/dataset/train.scp
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2626046476|dataset/lrc/2626046476.pt|dataset/latent/2626046476.pt|dataset/style/2626046476.pt
|
| 2 |
+
549162241|dataset/lrc/549162241.pt|dataset/latent/549162241.pt|dataset/style/549162241.pt
|
| 3 |
+
547512549|dataset/lrc/547512549.pt|dataset/latent/547512549.pt|dataset/style/547512549.pt
|
| 4 |
+
28528706|dataset/lrc/28528706.pt|dataset/latent/28528706.pt|dataset/style/28528706.pt
|
code/g2p/g2p_generation.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2024 Amphion.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
from g2p.g2p import PhonemeBpeTokenizer
|
| 10 |
+
from g2p.utils.g2p import phonemizer_g2p
|
| 11 |
+
import tqdm
|
| 12 |
+
from typing import List
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
import re
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def ph_g2p(text, language):
|
| 19 |
+
|
| 20 |
+
return phonemizer_g2p(text=text, language=language)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def g2p(text, sentence, language):
|
| 24 |
+
|
| 25 |
+
return text_tokenizer.tokenize(text=text, sentence=sentence, language=language)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def is_chinese(char):
|
| 29 |
+
if char >= "\u4e00" and char <= "\u9fa5":
|
| 30 |
+
return True
|
| 31 |
+
else:
|
| 32 |
+
return False
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def is_alphabet(char):
|
| 36 |
+
if (char >= "\u0041" and char <= "\u005a") or (
|
| 37 |
+
char >= "\u0061" and char <= "\u007a"
|
| 38 |
+
):
|
| 39 |
+
return True
|
| 40 |
+
else:
|
| 41 |
+
return False
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def is_other(char):
|
| 45 |
+
if not (is_chinese(char) or is_alphabet(char)):
|
| 46 |
+
return True
|
| 47 |
+
else:
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_segment(text: str) -> List[str]:
|
| 52 |
+
# sentence --> [ch_part, en_part, ch_part, ...]
|
| 53 |
+
segments = []
|
| 54 |
+
types = []
|
| 55 |
+
flag = 0
|
| 56 |
+
temp_seg = ""
|
| 57 |
+
temp_lang = ""
|
| 58 |
+
|
| 59 |
+
# Determine the type of each character. type: blank, chinese, alphabet, number, unk and point.
|
| 60 |
+
for i, ch in enumerate(text):
|
| 61 |
+
if is_chinese(ch):
|
| 62 |
+
types.append("zh")
|
| 63 |
+
elif is_alphabet(ch):
|
| 64 |
+
types.append("en")
|
| 65 |
+
else:
|
| 66 |
+
types.append("other")
|
| 67 |
+
|
| 68 |
+
assert len(types) == len(text)
|
| 69 |
+
|
| 70 |
+
for i in range(len(types)):
|
| 71 |
+
# find the first char of the seg
|
| 72 |
+
if flag == 0:
|
| 73 |
+
temp_seg += text[i]
|
| 74 |
+
temp_lang = types[i]
|
| 75 |
+
flag = 1
|
| 76 |
+
else:
|
| 77 |
+
if temp_lang == "other":
|
| 78 |
+
if types[i] == temp_lang:
|
| 79 |
+
temp_seg += text[i]
|
| 80 |
+
else:
|
| 81 |
+
temp_seg += text[i]
|
| 82 |
+
temp_lang = types[i]
|
| 83 |
+
else:
|
| 84 |
+
if types[i] == temp_lang:
|
| 85 |
+
temp_seg += text[i]
|
| 86 |
+
elif types[i] == "other":
|
| 87 |
+
temp_seg += text[i]
|
| 88 |
+
else:
|
| 89 |
+
segments.append((temp_seg, temp_lang))
|
| 90 |
+
temp_seg = text[i]
|
| 91 |
+
temp_lang = types[i]
|
| 92 |
+
flag = 1
|
| 93 |
+
|
| 94 |
+
segments.append((temp_seg, temp_lang))
|
| 95 |
+
return segments
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def chn_eng_g2p(text: str):
|
| 99 |
+
# now only en and ch
|
| 100 |
+
segments = get_segment(text)
|
| 101 |
+
all_phoneme = ""
|
| 102 |
+
all_tokens = []
|
| 103 |
+
|
| 104 |
+
for index in range(len(segments)):
|
| 105 |
+
seg = segments[index]
|
| 106 |
+
phoneme, token = g2p(seg[0], text, seg[1])
|
| 107 |
+
all_phoneme += phoneme + "|"
|
| 108 |
+
all_tokens += token
|
| 109 |
+
|
| 110 |
+
if seg[1] == "en" and index == len(segments) - 1 and all_phoneme[-2] == "_":
|
| 111 |
+
all_phoneme = all_phoneme[:-2]
|
| 112 |
+
all_tokens = all_tokens[:-1]
|
| 113 |
+
return all_phoneme, all_tokens
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
text_tokenizer = PhonemeBpeTokenizer()
|
| 117 |
+
with open("./g2p/g2p/vocab.json", "r", encoding='utf-8') as f:
|
| 118 |
+
json_data = f.read()
|
| 119 |
+
data = json.loads(json_data)
|
| 120 |
+
vocab = data["vocab"]
|
| 121 |
+
|
| 122 |
+
if __name__ == '__main__':
|
| 123 |
+
phone, token = chn_eng_g2p("你好,hello world")
|
| 124 |
+
phone, token = chn_eng_g2p("你好,hello world, Bonjour, 테스트 해 보겠습니다, 五月雨緑")
|
| 125 |
+
print(phone)
|
| 126 |
+
print(token)
|
| 127 |
+
|
| 128 |
+
#phone, token = text_tokenizer.tokenize("你好,hello world, Bonjour, 테스트 해 보겠습니다, 五月雨緑", "", "auto")
|
| 129 |
+
phone, token = text_tokenizer.tokenize("緑", "", "auto")
|
| 130 |
+
#phone, token = text_tokenizer.tokenize("आइए इसका परीक्षण करें", "", "auto")
|
| 131 |
+
#phone, token = text_tokenizer.tokenize("आइए इसका परीक्षण करें", "", "other")
|
| 132 |
+
print(phone)
|
| 133 |
+
print(token)
|
code/g2p/sources/bpmf_2_pinyin.txt
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
b ㄅ
|
| 2 |
+
p ㄆ
|
| 3 |
+
m ㄇ
|
| 4 |
+
f ㄈ
|
| 5 |
+
d ㄉ
|
| 6 |
+
t ㄊ
|
| 7 |
+
n ㄋ
|
| 8 |
+
l ㄌ
|
| 9 |
+
g ㄍ
|
| 10 |
+
k ㄎ
|
| 11 |
+
h ㄏ
|
| 12 |
+
j ㄐ
|
| 13 |
+
q ㄑ
|
| 14 |
+
x ㄒ
|
| 15 |
+
zh ㄓ
|
| 16 |
+
ch ㄔ
|
| 17 |
+
sh ㄕ
|
| 18 |
+
r ㄖ
|
| 19 |
+
z ㄗ
|
| 20 |
+
c ㄘ
|
| 21 |
+
s ㄙ
|
| 22 |
+
i ㄧ
|
| 23 |
+
u ㄨ
|
| 24 |
+
v ㄩ
|
| 25 |
+
a ㄚ
|
| 26 |
+
o ㄛ
|
| 27 |
+
e ㄜ
|
| 28 |
+
e ㄝ
|
| 29 |
+
ai ㄞ
|
| 30 |
+
ei ㄟ
|
| 31 |
+
ao ㄠ
|
| 32 |
+
ou ㄡ
|
| 33 |
+
an ㄢ
|
| 34 |
+
en ㄣ
|
| 35 |
+
ang ㄤ
|
| 36 |
+
eng ㄥ
|
| 37 |
+
er ㄦ
|
| 38 |
+
2 ˊ
|
| 39 |
+
3 ˇ
|
| 40 |
+
4 ˋ
|
| 41 |
+
0 ˙
|
code/g2p/sources/pinyin_2_bpmf.txt
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
a ㄚ
|
| 2 |
+
ai ㄞ
|
| 3 |
+
an ㄢ
|
| 4 |
+
ang ㄤ
|
| 5 |
+
ao ㄠ
|
| 6 |
+
ba ㄅㄚ
|
| 7 |
+
bai ㄅㄞ
|
| 8 |
+
ban ㄅㄢ
|
| 9 |
+
bang ㄅㄤ
|
| 10 |
+
bao ㄅㄠ
|
| 11 |
+
bei ㄅㄟ
|
| 12 |
+
ben ㄅㄣ
|
| 13 |
+
beng ㄅㄥ
|
| 14 |
+
bi ㄅㄧ
|
| 15 |
+
bian ㄅㄧㄢ
|
| 16 |
+
biang ㄅㄧㄤ
|
| 17 |
+
biao ㄅㄧㄠ
|
| 18 |
+
bie ㄅㄧㄝ
|
| 19 |
+
bin ㄅㄧㄣ
|
| 20 |
+
bing ㄅㄧㄥ
|
| 21 |
+
bo ㄅㄛ
|
| 22 |
+
bu ㄅㄨ
|
| 23 |
+
ca ㄘㄚ
|
| 24 |
+
cai ㄘㄞ
|
| 25 |
+
can ㄘㄢ
|
| 26 |
+
cang ㄘㄤ
|
| 27 |
+
cao ㄘㄠ
|
| 28 |
+
ce ㄘㄜ
|
| 29 |
+
cen ㄘㄣ
|
| 30 |
+
ceng ㄘㄥ
|
| 31 |
+
cha ㄔㄚ
|
| 32 |
+
chai ㄔㄞ
|
| 33 |
+
chan ㄔㄢ
|
| 34 |
+
chang ㄔㄤ
|
| 35 |
+
chao ㄔㄠ
|
| 36 |
+
che ㄔㄜ
|
| 37 |
+
chen ㄔㄣ
|
| 38 |
+
cheng ㄔㄥ
|
| 39 |
+
chi ㄔ
|
| 40 |
+
chong ㄔㄨㄥ
|
| 41 |
+
chou ㄔㄡ
|
| 42 |
+
chu ㄔㄨ
|
| 43 |
+
chua ㄔㄨㄚ
|
| 44 |
+
chuai ㄔㄨㄞ
|
| 45 |
+
chuan ㄔㄨㄢ
|
| 46 |
+
chuang ㄔㄨㄤ
|
| 47 |
+
chui ㄔㄨㄟ
|
| 48 |
+
chun ㄔㄨㄣ
|
| 49 |
+
chuo ㄔㄨㄛ
|
| 50 |
+
ci ㄘ
|
| 51 |
+
cong ㄘㄨㄥ
|
| 52 |
+
cou ㄘㄡ
|
| 53 |
+
cu ㄘㄨ
|
| 54 |
+
cuan ㄘㄨㄢ
|
| 55 |
+
cui ㄘㄨㄟ
|
| 56 |
+
cun ㄘㄨㄣ
|
| 57 |
+
cuo ㄘㄨㄛ
|
| 58 |
+
da ㄉㄚ
|
| 59 |
+
dai ㄉㄞ
|
| 60 |
+
dan ㄉㄢ
|
| 61 |
+
dang ㄉㄤ
|
| 62 |
+
dao ㄉㄠ
|
| 63 |
+
de ㄉㄜ
|
| 64 |
+
dei ㄉㄟ
|
| 65 |
+
den ㄉㄣ
|
| 66 |
+
deng ㄉㄥ
|
| 67 |
+
di ㄉㄧ
|
| 68 |
+
dia ㄉㄧㄚ
|
| 69 |
+
dian ㄉㄧㄢ
|
| 70 |
+
diao ㄉㄧㄠ
|
| 71 |
+
die ㄉㄧㄝ
|
| 72 |
+
din ㄉㄧㄣ
|
| 73 |
+
ding ㄉㄧㄥ
|
| 74 |
+
diu ㄉㄧㄡ
|
| 75 |
+
dong ㄉㄨㄥ
|
| 76 |
+
dou ㄉㄡ
|
| 77 |
+
du ㄉㄨ
|
| 78 |
+
duan ㄉㄨㄢ
|
| 79 |
+
dui ㄉㄨㄟ
|
| 80 |
+
dun ㄉㄨㄣ
|
| 81 |
+
duo ㄉㄨㄛ
|
| 82 |
+
e ㄜ
|
| 83 |
+
ei ㄟ
|
| 84 |
+
en ㄣ
|
| 85 |
+
eng ㄥ
|
| 86 |
+
er ㄦ
|
| 87 |
+
fa ㄈㄚ
|
| 88 |
+
fan ㄈㄢ
|
| 89 |
+
fang ㄈㄤ
|
| 90 |
+
fei ㄈㄟ
|
| 91 |
+
fen ㄈㄣ
|
| 92 |
+
feng ㄈㄥ
|
| 93 |
+
fo ㄈㄛ
|
| 94 |
+
fou ㄈㄡ
|
| 95 |
+
fu ㄈㄨ
|
| 96 |
+
ga ㄍㄚ
|
| 97 |
+
gai ㄍㄞ
|
| 98 |
+
gan ㄍㄢ
|
| 99 |
+
gang ㄍㄤ
|
| 100 |
+
gao ㄍㄠ
|
| 101 |
+
ge ㄍㄜ
|
| 102 |
+
gei ㄍㄟ
|
| 103 |
+
gen ㄍㄣ
|
| 104 |
+
geng ㄍㄥ
|
| 105 |
+
gong ㄍㄨㄥ
|
| 106 |
+
gou ㄍㄡ
|
| 107 |
+
gu ㄍㄨ
|
| 108 |
+
gua ㄍㄨㄚ
|
| 109 |
+
guai ㄍㄨㄞ
|
| 110 |
+
guan ㄍㄨㄢ
|
| 111 |
+
guang ㄍㄨㄤ
|
| 112 |
+
gui ㄍㄨㄟ
|
| 113 |
+
gun ㄍㄨㄣ
|
| 114 |
+
guo ㄍㄨㄛ
|
| 115 |
+
ha ㄏㄚ
|
| 116 |
+
hai ㄏㄞ
|
| 117 |
+
han ㄏㄢ
|
| 118 |
+
hang ㄏㄤ
|
| 119 |
+
hao ㄏㄠ
|
| 120 |
+
he ㄏㄜ
|
| 121 |
+
hei ㄏㄟ
|
| 122 |
+
hen ㄏㄣ
|
| 123 |
+
heng ㄏㄥ
|
| 124 |
+
hm ㄏㄇ
|
| 125 |
+
hong ㄏㄨㄥ
|
| 126 |
+
hou ㄏㄡ
|
| 127 |
+
hu ㄏㄨ
|
| 128 |
+
hua ㄏㄨㄚ
|
| 129 |
+
huai ㄏㄨㄞ
|
| 130 |
+
huan ㄏㄨㄢ
|
| 131 |
+
huang ㄏㄨㄤ
|
| 132 |
+
hui ㄏㄨㄟ
|
| 133 |
+
hun ㄏㄨㄣ
|
| 134 |
+
huo ㄏㄨㄛ
|
| 135 |
+
ji ㄐㄧ
|
| 136 |
+
jia ㄐㄧㄚ
|
| 137 |
+
jian ㄐㄧㄢ
|
| 138 |
+
jiang ㄐㄧㄤ
|
| 139 |
+
jiao ㄐㄧㄠ
|
| 140 |
+
jie ㄐㄧㄝ
|
| 141 |
+
jin ㄐㄧㄣ
|
| 142 |
+
jing ㄐㄧㄥ
|
| 143 |
+
jiong ㄐㄩㄥ
|
| 144 |
+
jiu ㄐㄧㄡ
|
| 145 |
+
ju ㄐㄩ
|
| 146 |
+
jv ㄐㄩ
|
| 147 |
+
juan ㄐㄩㄢ
|
| 148 |
+
jvan ㄐㄩㄢ
|
| 149 |
+
jue ㄐㄩㄝ
|
| 150 |
+
jve ㄐㄩㄝ
|
| 151 |
+
jun ㄐㄩㄣ
|
| 152 |
+
ka ㄎㄚ
|
| 153 |
+
kai ㄎㄞ
|
| 154 |
+
kan ㄎㄢ
|
| 155 |
+
kang ㄎㄤ
|
| 156 |
+
kao ㄎㄠ
|
| 157 |
+
ke ㄎㄜ
|
| 158 |
+
kei ㄎㄟ
|
| 159 |
+
ken ㄎㄣ
|
| 160 |
+
keng ㄎㄥ
|
| 161 |
+
kong ㄎㄨㄥ
|
| 162 |
+
kou ㄎㄡ
|
| 163 |
+
ku ㄎㄨ
|
| 164 |
+
kua ㄎㄨㄚ
|
| 165 |
+
kuai ㄎㄨㄞ
|
| 166 |
+
kuan ㄎㄨㄢ
|
| 167 |
+
kuang ㄎㄨㄤ
|
| 168 |
+
kui ㄎㄨㄟ
|
| 169 |
+
kun ㄎㄨㄣ
|
| 170 |
+
kuo ㄎㄨㄛ
|
| 171 |
+
la ㄌㄚ
|
| 172 |
+
lai ㄌㄞ
|
| 173 |
+
lan ㄌㄢ
|
| 174 |
+
lang ㄌㄤ
|
| 175 |
+
lao ㄌㄠ
|
| 176 |
+
le ㄌㄜ
|
| 177 |
+
lei ㄌㄟ
|
| 178 |
+
leng ㄌㄥ
|
| 179 |
+
li ㄌㄧ
|
| 180 |
+
lia ㄌㄧㄚ
|
| 181 |
+
lian ㄌㄧㄢ
|
| 182 |
+
liang ㄌㄧㄤ
|
| 183 |
+
liao ㄌㄧㄠ
|
| 184 |
+
lie ㄌㄧㄝ
|
| 185 |
+
lin ㄌㄧㄣ
|
| 186 |
+
ling ㄌㄧㄥ
|
| 187 |
+
liu ㄌㄧㄡ
|
| 188 |
+
lo ㄌㄛ
|
| 189 |
+
long ㄌㄨㄥ
|
| 190 |
+
lou ㄌㄡ
|
| 191 |
+
lu ㄌㄨ
|
| 192 |
+
luan ㄌㄨㄢ
|
| 193 |
+
lue ㄌㄩㄝ
|
| 194 |
+
lun ㄌㄨㄣ
|
| 195 |
+
luo ㄌㄨㄛ
|
| 196 |
+
lv ㄌㄩ
|
| 197 |
+
lve ㄌㄩㄝ
|
| 198 |
+
m ㄇㄨ
|
| 199 |
+
ma ㄇㄚ
|
| 200 |
+
mai ㄇㄞ
|
| 201 |
+
man ㄇㄢ
|
| 202 |
+
mang ㄇㄤ
|
| 203 |
+
mao ㄇㄠ
|
| 204 |
+
me ㄇㄜ
|
| 205 |
+
mei ㄇㄟ
|
| 206 |
+
men ㄇㄣ
|
| 207 |
+
meng ㄇㄥ
|
| 208 |
+
mi ㄇㄧ
|
| 209 |
+
mian ㄇㄧㄢ
|
| 210 |
+
miao ㄇㄧㄠ
|
| 211 |
+
mie ㄇㄧㄝ
|
| 212 |
+
min ㄇㄧㄣ
|
| 213 |
+
ming ㄇㄧㄥ
|
| 214 |
+
miu ㄇㄧㄡ
|
| 215 |
+
mo ㄇㄛ
|
| 216 |
+
mou ㄇㄡ
|
| 217 |
+
mu ㄇㄨ
|
| 218 |
+
n ㄣ
|
| 219 |
+
na ㄋㄚ
|
| 220 |
+
nai ㄋㄞ
|
| 221 |
+
nan ㄋㄢ
|
| 222 |
+
nang ㄋㄤ
|
| 223 |
+
nao ㄋㄠ
|
| 224 |
+
ne ㄋㄜ
|
| 225 |
+
nei ㄋㄟ
|
| 226 |
+
nen ㄋㄣ
|
| 227 |
+
neng ㄋㄥ
|
| 228 |
+
ng ㄣ
|
| 229 |
+
ni ㄋㄧ
|
| 230 |
+
nian ㄋㄧㄢ
|
| 231 |
+
niang ㄋㄧㄤ
|
| 232 |
+
niao ㄋㄧㄠ
|
| 233 |
+
nie ㄋㄧㄝ
|
| 234 |
+
nin ㄋㄧㄣ
|
| 235 |
+
ning ㄋㄧㄥ
|
| 236 |
+
niu ㄋㄧㄡ
|
| 237 |
+
nong ㄋㄨㄥ
|
| 238 |
+
nou ㄋㄡ
|
| 239 |
+
nu ㄋㄨ
|
| 240 |
+
nuan ㄋㄨㄢ
|
| 241 |
+
nue ㄋㄩㄝ
|
| 242 |
+
nun ㄋㄨㄣ
|
| 243 |
+
nuo ㄋㄨㄛ
|
| 244 |
+
nv ㄋㄩ
|
| 245 |
+
nve ㄋㄩㄝ
|
| 246 |
+
o ㄛ
|
| 247 |
+
ou ㄡ
|
| 248 |
+
pa ㄆㄚ
|
| 249 |
+
pai ㄆㄞ
|
| 250 |
+
pan ㄆㄢ
|
| 251 |
+
pang ㄆㄤ
|
| 252 |
+
pao ㄆㄠ
|
| 253 |
+
pei ㄆㄟ
|
| 254 |
+
pen ㄆㄣ
|
| 255 |
+
peng ㄆㄥ
|
| 256 |
+
pi ㄆㄧ
|
| 257 |
+
pian ㄆㄧㄢ
|
| 258 |
+
piao ㄆㄧㄠ
|
| 259 |
+
pie ㄆㄧㄝ
|
| 260 |
+
pin ㄆㄧㄣ
|
| 261 |
+
ping ㄆㄧㄥ
|
| 262 |
+
po ㄆㄛ
|
| 263 |
+
pou ㄆㄡ
|
| 264 |
+
pu ㄆㄨ
|
| 265 |
+
qi ㄑㄧ
|
| 266 |
+
qia ㄑㄧㄚ
|
| 267 |
+
qian ㄑㄧㄢ
|
| 268 |
+
qiang ㄑㄧㄤ
|
| 269 |
+
qiao ㄑㄧㄠ
|
| 270 |
+
qie ㄑㄧㄝ
|
| 271 |
+
qin ㄑㄧㄣ
|
| 272 |
+
qing ㄑㄧㄥ
|
| 273 |
+
qiong ㄑㄩㄥ
|
| 274 |
+
qiu ㄑㄧㄡ
|
| 275 |
+
qu ㄑㄩ
|
| 276 |
+
quan ㄑㄩㄢ
|
| 277 |
+
qvan ㄑㄩㄢ
|
| 278 |
+
que ㄑㄩㄝ
|
| 279 |
+
qun ㄑㄩㄣ
|
| 280 |
+
ran ㄖㄢ
|
| 281 |
+
rang ㄖㄤ
|
| 282 |
+
rao ㄖㄠ
|
| 283 |
+
re ㄖㄜ
|
| 284 |
+
ren ㄖㄣ
|
| 285 |
+
reng ㄖㄥ
|
| 286 |
+
ri ㄖ
|
| 287 |
+
rong ㄖㄨㄥ
|
| 288 |
+
rou ㄖㄡ
|
| 289 |
+
ru ㄖㄨ
|
| 290 |
+
rua ㄖㄨㄚ
|
| 291 |
+
ruan ㄖㄨㄢ
|
| 292 |
+
rui ㄖㄨㄟ
|
| 293 |
+
run ㄖㄨㄣ
|
| 294 |
+
ruo ㄖㄨㄛ
|
| 295 |
+
sa ㄙㄚ
|
| 296 |
+
sai ㄙㄞ
|
| 297 |
+
san ㄙㄢ
|
| 298 |
+
sang ㄙㄤ
|
| 299 |
+
sao ㄙㄠ
|
| 300 |
+
se ㄙㄜ
|
| 301 |
+
sen ㄙㄣ
|
| 302 |
+
seng ㄙㄥ
|
| 303 |
+
sha ㄕㄚ
|
| 304 |
+
shai ㄕㄞ
|
| 305 |
+
shan ㄕㄢ
|
| 306 |
+
shang ㄕㄤ
|
| 307 |
+
shao ㄕㄠ
|
| 308 |
+
she ㄕㄜ
|
| 309 |
+
shei ㄕㄟ
|
| 310 |
+
shen ㄕㄣ
|
| 311 |
+
sheng ㄕㄥ
|
| 312 |
+
shi ㄕ
|
| 313 |
+
shou ㄕㄡ
|
| 314 |
+
shu ㄕㄨ
|
| 315 |
+
shua ㄕㄨㄚ
|
| 316 |
+
shuai ㄕㄨㄞ
|
| 317 |
+
shuan ㄕㄨㄢ
|
| 318 |
+
shuang ㄕㄨㄤ
|
| 319 |
+
shui ㄕㄨㄟ
|
| 320 |
+
shun ㄕㄨㄣ
|
| 321 |
+
shuo ㄕㄨㄛ
|
| 322 |
+
si ㄙ
|
| 323 |
+
song ㄙㄨㄥ
|
| 324 |
+
sou ㄙㄡ
|
| 325 |
+
su ㄙㄨ
|
| 326 |
+
suan ㄙㄨㄢ
|
| 327 |
+
sui ㄙㄨㄟ
|
| 328 |
+
sun ㄙㄨㄣ
|
| 329 |
+
suo ㄙㄨㄛ
|
| 330 |
+
ta ㄊㄚ
|
| 331 |
+
tai ㄊㄞ
|
| 332 |
+
tan ㄊㄢ
|
| 333 |
+
tang ㄊㄤ
|
| 334 |
+
tao ㄊㄠ
|
| 335 |
+
te ㄊㄜ
|
| 336 |
+
tei ㄊㄟ
|
| 337 |
+
teng ㄊㄥ
|
| 338 |
+
ti ㄊㄧ
|
| 339 |
+
tian ㄊㄧㄢ
|
| 340 |
+
tiao ㄊㄧㄠ
|
| 341 |
+
tie ㄊㄧㄝ
|
| 342 |
+
ting ㄊㄧㄥ
|
| 343 |
+
tong ㄊㄨㄥ
|
| 344 |
+
tou ㄊㄡ
|
| 345 |
+
tsuo ㄘㄨㄛ
|
| 346 |
+
tu ㄊㄨ
|
| 347 |
+
tuan ㄊㄨㄢ
|
| 348 |
+
tui ㄊㄨㄟ
|
| 349 |
+
tun ㄊㄨㄣ
|
| 350 |
+
tuo ㄊㄨㄛ
|
| 351 |
+
tzan ㄗㄢ
|
| 352 |
+
wa ㄨㄚ
|
| 353 |
+
wai ㄨㄞ
|
| 354 |
+
wan ㄨㄢ
|
| 355 |
+
wang ㄨㄤ
|
| 356 |
+
wei ㄨㄟ
|
| 357 |
+
wen ㄨㄣ
|
| 358 |
+
weng ㄨㄥ
|
| 359 |
+
wo ㄨㄛ
|
| 360 |
+
wong ㄨㄥ
|
| 361 |
+
wu ㄨ
|
| 362 |
+
xi ㄒㄧ
|
| 363 |
+
xia ㄒㄧㄚ
|
| 364 |
+
xian ㄒㄧㄢ
|
| 365 |
+
xiang ㄒㄧㄤ
|
| 366 |
+
xiao ㄒㄧㄠ
|
| 367 |
+
xie ㄒㄧㄝ
|
| 368 |
+
xin ㄒㄧㄣ
|
| 369 |
+
xing ㄒㄧㄥ
|
| 370 |
+
xiong ㄒㄩㄥ
|
| 371 |
+
xiu ㄒㄧㄡ
|
| 372 |
+
xu ㄒㄩ
|
| 373 |
+
xuan ㄒㄩㄢ
|
| 374 |
+
xue ㄒㄩㄝ
|
| 375 |
+
xun ㄒㄩㄣ
|
| 376 |
+
ya ㄧㄚ
|
| 377 |
+
yai ㄧㄞ
|
| 378 |
+
yan ㄧㄢ
|
| 379 |
+
yang ㄧㄤ
|
| 380 |
+
yao ㄧㄠ
|
| 381 |
+
ye ㄧㄝ
|
| 382 |
+
yi ㄧ
|
| 383 |
+
yin ㄧㄣ
|
| 384 |
+
ying ㄧㄥ
|
| 385 |
+
yo ㄧㄛ
|
| 386 |
+
yong ㄩㄥ
|
| 387 |
+
you ㄧㄡ
|
| 388 |
+
yu ㄩ
|
| 389 |
+
yuan ㄩㄢ
|
| 390 |
+
yue ㄩㄝ
|
| 391 |
+
yve ㄩㄝ
|
| 392 |
+
yun ㄩㄣ
|
| 393 |
+
za ㄗㄚ
|
| 394 |
+
zai ㄗㄞ
|
| 395 |
+
zan ㄗㄢ
|
| 396 |
+
zang ㄗㄤ
|
| 397 |
+
zao ㄗㄠ
|
| 398 |
+
ze ㄗㄜ
|
| 399 |
+
zei ㄗㄟ
|
| 400 |
+
zen ㄗㄣ
|
| 401 |
+
zeng ㄗㄥ
|
| 402 |
+
zha ㄓㄚ
|
| 403 |
+
zhai ㄓㄞ
|
| 404 |
+
zhan ㄓㄢ
|
| 405 |
+
zhang ㄓㄤ
|
| 406 |
+
zhao ㄓㄠ
|
| 407 |
+
zhe ㄓㄜ
|
| 408 |
+
zhei ㄓㄟ
|
| 409 |
+
zhen ㄓㄣ
|
| 410 |
+
zheng ㄓㄥ
|
| 411 |
+
zhi ㄓ
|
| 412 |
+
zhong ㄓㄨㄥ
|
| 413 |
+
zhou ㄓㄡ
|
| 414 |
+
zhu ㄓㄨ
|
| 415 |
+
zhua ㄓㄨㄚ
|
| 416 |
+
zhuai ㄓㄨㄞ
|
| 417 |
+
zhuan ㄓㄨㄢ
|
| 418 |
+
zhuang ㄓㄨㄤ
|
| 419 |
+
zhui ㄓㄨㄟ
|
| 420 |
+
zhun ㄓㄨㄣ
|
| 421 |
+
zhuo ㄓㄨㄛ
|
| 422 |
+
zi ㄗ
|
| 423 |
+
zong ㄗㄨㄥ
|
| 424 |
+
zou ㄗㄡ
|
| 425 |
+
zu ㄗㄨ
|
| 426 |
+
zuan ㄗㄨㄢ
|
| 427 |
+
zui ㄗㄨㄟ
|
| 428 |
+
zun ㄗㄨㄣ
|
| 429 |
+
zuo ㄗㄨㄛ
|
code/infer/infer.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 2 |
+
# 2025 Huakang Chen (huakang@mail.nwpu.edu.cn)
|
| 3 |
+
# 2025 Guobin Ma (guobin.ma@gmail.com)
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import os
|
| 19 |
+
import time
|
| 20 |
+
import random
|
| 21 |
+
|
| 22 |
+
import torch
|
| 23 |
+
import torchaudio
|
| 24 |
+
from einops import rearrange
|
| 25 |
+
|
| 26 |
+
print("Current working directory:", os.getcwd())
|
| 27 |
+
|
| 28 |
+
from infer_utils import (
|
| 29 |
+
decode_audio,
|
| 30 |
+
get_lrc_token,
|
| 31 |
+
get_negative_style_prompt,
|
| 32 |
+
get_reference_latent,
|
| 33 |
+
get_style_prompt,
|
| 34 |
+
prepare_model,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def inference(
|
| 39 |
+
cfm_model,
|
| 40 |
+
vae_model,
|
| 41 |
+
cond,
|
| 42 |
+
text,
|
| 43 |
+
duration,
|
| 44 |
+
style_prompt,
|
| 45 |
+
negative_style_prompt,
|
| 46 |
+
start_time,
|
| 47 |
+
pred_frames,
|
| 48 |
+
batch_infer_num,
|
| 49 |
+
song_duration,
|
| 50 |
+
chunked=False,
|
| 51 |
+
):
|
| 52 |
+
with torch.inference_mode():
|
| 53 |
+
latents, _ = cfm_model.sample(
|
| 54 |
+
cond=cond,
|
| 55 |
+
text=text,
|
| 56 |
+
duration=duration,
|
| 57 |
+
style_prompt=style_prompt,
|
| 58 |
+
max_duration=duration,
|
| 59 |
+
song_duration=song_duration,
|
| 60 |
+
negative_style_prompt=negative_style_prompt,
|
| 61 |
+
steps=32,
|
| 62 |
+
cfg_strength=4.0,
|
| 63 |
+
start_time=start_time,
|
| 64 |
+
latent_pred_segments=pred_frames,
|
| 65 |
+
batch_infer_num=batch_infer_num
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
outputs = []
|
| 69 |
+
for latent in latents:
|
| 70 |
+
latent = latent.to(torch.float32)
|
| 71 |
+
latent = latent.transpose(1, 2) # [b d t]
|
| 72 |
+
|
| 73 |
+
output = decode_audio(latent, vae_model, chunked=chunked)
|
| 74 |
+
|
| 75 |
+
# Rearrange audio batch to a single sequence
|
| 76 |
+
output = rearrange(output, "b d n -> d (b n)")
|
| 77 |
+
# Peak normalize, clip, convert to int16, and save to file
|
| 78 |
+
output = (
|
| 79 |
+
output.to(torch.float32)
|
| 80 |
+
.div(torch.max(torch.abs(output)))
|
| 81 |
+
.clamp(-1, 1)
|
| 82 |
+
.mul(32767)
|
| 83 |
+
.to(torch.int16)
|
| 84 |
+
.cpu()
|
| 85 |
+
)
|
| 86 |
+
outputs.append(output)
|
| 87 |
+
|
| 88 |
+
return outputs
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
parser = argparse.ArgumentParser()
|
| 93 |
+
parser.add_argument(
|
| 94 |
+
"--lrc-path",
|
| 95 |
+
type=str,
|
| 96 |
+
help="lyrics of target song",
|
| 97 |
+
) # lyrics of target song
|
| 98 |
+
parser.add_argument(
|
| 99 |
+
"--ref-prompt",
|
| 100 |
+
type=str,
|
| 101 |
+
help="reference prompt as style prompt for target song",
|
| 102 |
+
required=False,
|
| 103 |
+
) # reference prompt as style prompt for target song
|
| 104 |
+
parser.add_argument(
|
| 105 |
+
"--ref-audio-path",
|
| 106 |
+
type=str,
|
| 107 |
+
help="reference audio as style prompt for target song",
|
| 108 |
+
required=False,
|
| 109 |
+
) # reference audio as style prompt for target song
|
| 110 |
+
parser.add_argument(
|
| 111 |
+
"--chunked",
|
| 112 |
+
action="store_true",
|
| 113 |
+
help="whether to use chunked decoding",
|
| 114 |
+
) # whether to use chunked decoding
|
| 115 |
+
parser.add_argument(
|
| 116 |
+
"--audio-length",
|
| 117 |
+
type=int,
|
| 118 |
+
default=95,
|
| 119 |
+
# choices=[95, 285],
|
| 120 |
+
help="length of generated song, upported values are exactly 95 or any value between 96 and 285 (inclusive).",
|
| 121 |
+
) # length of target song
|
| 122 |
+
# parser.add_argument(
|
| 123 |
+
# "--repo-id", type=str, default="ASLP-lab/DiffRhythm-base", help="target model"
|
| 124 |
+
# )
|
| 125 |
+
parser.add_argument(
|
| 126 |
+
"--output-dir",
|
| 127 |
+
type=str,
|
| 128 |
+
default="infer/example/output",
|
| 129 |
+
help="output directory fo generated song",
|
| 130 |
+
) # output directory of target song
|
| 131 |
+
parser.add_argument(
|
| 132 |
+
"--edit",
|
| 133 |
+
action="store_true",
|
| 134 |
+
help="whether to open edit mode",
|
| 135 |
+
) # edit flag
|
| 136 |
+
parser.add_argument(
|
| 137 |
+
"--ref-song",
|
| 138 |
+
type=str,
|
| 139 |
+
required=False,
|
| 140 |
+
help="reference prompt as latent prompt for editing",
|
| 141 |
+
) # reference prompt as latent prompt for editing
|
| 142 |
+
parser.add_argument(
|
| 143 |
+
"--edit-segments",
|
| 144 |
+
type=str,
|
| 145 |
+
required=False,
|
| 146 |
+
help="Time segments to edit (in seconds). Format: `[[start1,end1],...]`. "
|
| 147 |
+
"Use `-1` for audio start/end (e.g., `[[-1,25], [50.0,-1]]`)."
|
| 148 |
+
) # edit segments of target song
|
| 149 |
+
parser.add_argument(
|
| 150 |
+
"--batch-infer-num",
|
| 151 |
+
type=int,
|
| 152 |
+
default=1,
|
| 153 |
+
required=False,
|
| 154 |
+
help="number of songs per batch",
|
| 155 |
+
) # number of songs per batch
|
| 156 |
+
args = parser.parse_args()
|
| 157 |
+
|
| 158 |
+
assert (
|
| 159 |
+
args.ref_prompt or args.ref_audio_path
|
| 160 |
+
), "either ref_prompt or ref_audio_path should be provided"
|
| 161 |
+
assert not (
|
| 162 |
+
args.ref_prompt and args.ref_audio_path
|
| 163 |
+
), "only one of them should be provided"
|
| 164 |
+
if args.edit:
|
| 165 |
+
assert (
|
| 166 |
+
args.ref_song and args.edit_segments
|
| 167 |
+
), "reference song and edit segments should be provided for editing"
|
| 168 |
+
|
| 169 |
+
device = "cpu"
|
| 170 |
+
if torch.cuda.is_available():
|
| 171 |
+
device = "cuda"
|
| 172 |
+
elif torch.mps.is_available():
|
| 173 |
+
device = "mps"
|
| 174 |
+
|
| 175 |
+
audio_length = args.audio_length
|
| 176 |
+
if audio_length == 95:
|
| 177 |
+
max_frames = 2048
|
| 178 |
+
elif 95 < audio_length <= 285:
|
| 179 |
+
max_frames = 6144
|
| 180 |
+
else:
|
| 181 |
+
raise ValueError(
|
| 182 |
+
f"Invalid audio_length: {audio_length}. "
|
| 183 |
+
"Supported values are exactly 95 or any value between 96 and 285 (inclusive)."
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
cfm, tokenizer, muq, vae = prepare_model(max_frames, device)
|
| 187 |
+
|
| 188 |
+
if args.lrc_path:
|
| 189 |
+
with open(args.lrc_path, "r", encoding='utf-8') as f:
|
| 190 |
+
lrc = f.read()
|
| 191 |
+
else:
|
| 192 |
+
lrc = ""
|
| 193 |
+
lrc_prompt, start_time, end_frame, song_duration = get_lrc_token(max_frames, lrc, tokenizer, audio_length, device)
|
| 194 |
+
|
| 195 |
+
if args.ref_audio_path:
|
| 196 |
+
style_prompt = get_style_prompt(muq, args.ref_audio_path)
|
| 197 |
+
else:
|
| 198 |
+
style_prompt = get_style_prompt(muq, prompt=args.ref_prompt)
|
| 199 |
+
|
| 200 |
+
negative_style_prompt = get_negative_style_prompt(device)
|
| 201 |
+
|
| 202 |
+
latent_prompt, pred_frames = get_reference_latent(device, max_frames, args.edit, args.edit_segments, args.ref_song, vae)
|
| 203 |
+
|
| 204 |
+
s_t = time.time()
|
| 205 |
+
generated_songs = inference(
|
| 206 |
+
cfm_model=cfm,
|
| 207 |
+
vae_model=vae,
|
| 208 |
+
cond=latent_prompt,
|
| 209 |
+
text=lrc_prompt,
|
| 210 |
+
duration=end_frame,
|
| 211 |
+
style_prompt=style_prompt,
|
| 212 |
+
negative_style_prompt=negative_style_prompt,
|
| 213 |
+
start_time=start_time,
|
| 214 |
+
pred_frames=pred_frames,
|
| 215 |
+
chunked=args.chunked,
|
| 216 |
+
batch_infer_num=args.batch_infer_num,
|
| 217 |
+
song_duration=song_duration
|
| 218 |
+
)
|
| 219 |
+
e_t = time.time() - s_t
|
| 220 |
+
print(f"inference cost {e_t:.2f} seconds")
|
| 221 |
+
|
| 222 |
+
generated_song = random.sample(generated_songs, 1)[0]
|
| 223 |
+
|
| 224 |
+
output_dir = args.output_dir
|
| 225 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 226 |
+
|
| 227 |
+
output_path = os.path.join(output_dir, "output.wav")
|
| 228 |
+
torchaudio.save(output_path, generated_song, sample_rate=44100)
|
code/infer/infer_utils.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 2 |
+
# 2025 Huakang Chen (huakang@mail.nwpu.edu.cn)
|
| 3 |
+
# 2025 Guobin Ma (guobin.ma@gmail.com)
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
import librosa
|
| 19 |
+
import torchaudio
|
| 20 |
+
import random
|
| 21 |
+
import json
|
| 22 |
+
from muq import MuQMuLan
|
| 23 |
+
from mutagen.mp3 import MP3
|
| 24 |
+
import os
|
| 25 |
+
import numpy as np
|
| 26 |
+
from huggingface_hub import hf_hub_download
|
| 27 |
+
|
| 28 |
+
from sys import path
|
| 29 |
+
path.append(os.getcwd())
|
| 30 |
+
|
| 31 |
+
from model import DiT, CFM
|
| 32 |
+
|
| 33 |
+
def vae_sample(mean, scale):
|
| 34 |
+
stdev = torch.nn.functional.softplus(scale) + 1e-4
|
| 35 |
+
var = stdev * stdev
|
| 36 |
+
logvar = torch.log(var)
|
| 37 |
+
latents = torch.randn_like(mean) * stdev + mean
|
| 38 |
+
|
| 39 |
+
kl = (mean * mean + var - logvar - 1).sum(1).mean()
|
| 40 |
+
|
| 41 |
+
return latents, kl
|
| 42 |
+
|
| 43 |
+
def normalize_audio(y, target_dbfs=0):
|
| 44 |
+
max_amplitude = torch.max(torch.abs(y))
|
| 45 |
+
|
| 46 |
+
target_amplitude = 10.0**(target_dbfs / 20.0)
|
| 47 |
+
scale_factor = target_amplitude / max_amplitude
|
| 48 |
+
|
| 49 |
+
normalized_audio = y * scale_factor
|
| 50 |
+
|
| 51 |
+
return normalized_audio
|
| 52 |
+
|
| 53 |
+
def set_audio_channels(audio, target_channels):
|
| 54 |
+
if target_channels == 1:
|
| 55 |
+
# Convert to mono
|
| 56 |
+
audio = audio.mean(1, keepdim=True)
|
| 57 |
+
elif target_channels == 2:
|
| 58 |
+
# Convert to stereo
|
| 59 |
+
if audio.shape[1] == 1:
|
| 60 |
+
audio = audio.repeat(1, 2, 1)
|
| 61 |
+
elif audio.shape[1] > 2:
|
| 62 |
+
audio = audio[:, :2, :]
|
| 63 |
+
return audio
|
| 64 |
+
|
| 65 |
+
class PadCrop(torch.nn.Module):
|
| 66 |
+
def __init__(self, n_samples, randomize=True):
|
| 67 |
+
super().__init__()
|
| 68 |
+
self.n_samples = n_samples
|
| 69 |
+
self.randomize = randomize
|
| 70 |
+
|
| 71 |
+
def __call__(self, signal):
|
| 72 |
+
n, s = signal.shape
|
| 73 |
+
start = 0 if (not self.randomize) else torch.randint(0, max(0, s - self.n_samples) + 1, []).item()
|
| 74 |
+
end = start + self.n_samples
|
| 75 |
+
output = signal.new_zeros([n, self.n_samples])
|
| 76 |
+
output[:, :min(s, self.n_samples)] = signal[:, start:end]
|
| 77 |
+
return output
|
| 78 |
+
|
| 79 |
+
def prepare_audio(audio, in_sr, target_sr, target_length, target_channels, device):
|
| 80 |
+
|
| 81 |
+
audio = audio.to(device)
|
| 82 |
+
|
| 83 |
+
if in_sr != target_sr:
|
| 84 |
+
resample_tf = torchaudio.functional.Resample(in_sr, target_sr).to(device)
|
| 85 |
+
audio = resample_tf(audio)
|
| 86 |
+
if target_length is None:
|
| 87 |
+
target_length = audio.shape[-1]
|
| 88 |
+
audio = PadCrop(target_length, randomize=False)(audio)
|
| 89 |
+
|
| 90 |
+
# Add batch dimension
|
| 91 |
+
if audio.dim() == 1:
|
| 92 |
+
audio = audio.unsqueeze(0).unsqueeze(0)
|
| 93 |
+
elif audio.dim() == 2:
|
| 94 |
+
audio = audio.unsqueeze(0)
|
| 95 |
+
|
| 96 |
+
audio = set_audio_channels(audio, target_channels)
|
| 97 |
+
|
| 98 |
+
return audio
|
| 99 |
+
|
| 100 |
+
def decode_audio(latents, vae_model, chunked=False, overlap=32, chunk_size=128):
|
| 101 |
+
downsampling_ratio = 2048
|
| 102 |
+
io_channels = 2
|
| 103 |
+
if not chunked:
|
| 104 |
+
return vae_model.decode_export(latents)
|
| 105 |
+
else:
|
| 106 |
+
# chunked decoding
|
| 107 |
+
hop_size = chunk_size - overlap
|
| 108 |
+
total_size = latents.shape[2]
|
| 109 |
+
batch_size = latents.shape[0]
|
| 110 |
+
chunks = []
|
| 111 |
+
i = 0
|
| 112 |
+
for i in range(0, total_size - chunk_size + 1, hop_size):
|
| 113 |
+
chunk = latents[:, :, i : i + chunk_size]
|
| 114 |
+
chunks.append(chunk)
|
| 115 |
+
if i + chunk_size != total_size:
|
| 116 |
+
# Final chunk
|
| 117 |
+
chunk = latents[:, :, -chunk_size:]
|
| 118 |
+
chunks.append(chunk)
|
| 119 |
+
chunks = torch.stack(chunks)
|
| 120 |
+
num_chunks = chunks.shape[0]
|
| 121 |
+
# samples_per_latent is just the downsampling ratio
|
| 122 |
+
samples_per_latent = downsampling_ratio
|
| 123 |
+
# Create an empty waveform, we will populate it with chunks as decode them
|
| 124 |
+
y_size = total_size * samples_per_latent
|
| 125 |
+
y_final = torch.zeros((batch_size, io_channels, y_size)).to(latents.device)
|
| 126 |
+
for i in range(num_chunks):
|
| 127 |
+
x_chunk = chunks[i, :]
|
| 128 |
+
# decode the chunk
|
| 129 |
+
y_chunk = vae_model.decode_export(x_chunk)
|
| 130 |
+
# figure out where to put the audio along the time domain
|
| 131 |
+
if i == num_chunks - 1:
|
| 132 |
+
# final chunk always goes at the end
|
| 133 |
+
t_end = y_size
|
| 134 |
+
t_start = t_end - y_chunk.shape[2]
|
| 135 |
+
else:
|
| 136 |
+
t_start = i * hop_size * samples_per_latent
|
| 137 |
+
t_end = t_start + chunk_size * samples_per_latent
|
| 138 |
+
# remove the edges of the overlaps
|
| 139 |
+
ol = (overlap // 2) * samples_per_latent
|
| 140 |
+
chunk_start = 0
|
| 141 |
+
chunk_end = y_chunk.shape[2]
|
| 142 |
+
if i > 0:
|
| 143 |
+
# no overlap for the start of the first chunk
|
| 144 |
+
t_start += ol
|
| 145 |
+
chunk_start += ol
|
| 146 |
+
if i < num_chunks - 1:
|
| 147 |
+
# no overlap for the end of the last chunk
|
| 148 |
+
t_end -= ol
|
| 149 |
+
chunk_end -= ol
|
| 150 |
+
# paste the chunked audio into our y_final output audio
|
| 151 |
+
y_final[:, :, t_start:t_end] = y_chunk[:, :, chunk_start:chunk_end]
|
| 152 |
+
return y_final
|
| 153 |
+
|
| 154 |
+
def encode_audio(audio, vae_model, chunked=False, overlap=32, chunk_size=128):
|
| 155 |
+
downsampling_ratio = 2048
|
| 156 |
+
latent_dim = 128
|
| 157 |
+
if not chunked:
|
| 158 |
+
# default behavior. Encode the entire audio in parallel
|
| 159 |
+
return vae_model.encode_export(audio)
|
| 160 |
+
else:
|
| 161 |
+
# CHUNKED ENCODING
|
| 162 |
+
# samples_per_latent is just the downsampling ratio (which is also the upsampling ratio)
|
| 163 |
+
samples_per_latent = downsampling_ratio
|
| 164 |
+
total_size = audio.shape[2] # in samples
|
| 165 |
+
batch_size = audio.shape[0]
|
| 166 |
+
chunk_size *= samples_per_latent # converting metric in latents to samples
|
| 167 |
+
overlap *= samples_per_latent # converting metric in latents to samples
|
| 168 |
+
hop_size = chunk_size - overlap
|
| 169 |
+
chunks = []
|
| 170 |
+
for i in range(0, total_size - chunk_size + 1, hop_size):
|
| 171 |
+
chunk = audio[:,:,i:i+chunk_size]
|
| 172 |
+
chunks.append(chunk)
|
| 173 |
+
if i+chunk_size != total_size:
|
| 174 |
+
# Final chunk
|
| 175 |
+
chunk = audio[:,:,-chunk_size:]
|
| 176 |
+
chunks.append(chunk)
|
| 177 |
+
chunks = torch.stack(chunks)
|
| 178 |
+
num_chunks = chunks.shape[0]
|
| 179 |
+
# Note: y_size might be a different value from the latent length used in diffusion training
|
| 180 |
+
# because we can encode audio of varying lengths
|
| 181 |
+
# However, the audio should've been padded to a multiple of samples_per_latent by now.
|
| 182 |
+
y_size = total_size // samples_per_latent
|
| 183 |
+
# Create an empty latent, we will populate it with chunks as we encode them
|
| 184 |
+
y_final = torch.zeros((batch_size,latent_dim,y_size)).to(audio.device)
|
| 185 |
+
for i in range(num_chunks):
|
| 186 |
+
x_chunk = chunks[i,:]
|
| 187 |
+
# encode the chunk
|
| 188 |
+
y_chunk = vae_model.encode_export(x_chunk)
|
| 189 |
+
# figure out where to put the audio along the time domain
|
| 190 |
+
if i == num_chunks-1:
|
| 191 |
+
# final chunk always goes at the end
|
| 192 |
+
t_end = y_size
|
| 193 |
+
t_start = t_end - y_chunk.shape[2]
|
| 194 |
+
else:
|
| 195 |
+
t_start = i * hop_size // samples_per_latent
|
| 196 |
+
t_end = t_start + chunk_size // samples_per_latent
|
| 197 |
+
# remove the edges of the overlaps
|
| 198 |
+
ol = overlap//samples_per_latent//2
|
| 199 |
+
chunk_start = 0
|
| 200 |
+
chunk_end = y_chunk.shape[2]
|
| 201 |
+
if i > 0:
|
| 202 |
+
# no overlap for the start of the first chunk
|
| 203 |
+
t_start += ol
|
| 204 |
+
chunk_start += ol
|
| 205 |
+
if i < num_chunks-1:
|
| 206 |
+
# no overlap for the end of the last chunk
|
| 207 |
+
t_end -= ol
|
| 208 |
+
chunk_end -= ol
|
| 209 |
+
# paste the chunked audio into our y_final output audio
|
| 210 |
+
y_final[:,:,t_start:t_end] = y_chunk[:,:,chunk_start:chunk_end]
|
| 211 |
+
return y_final
|
| 212 |
+
|
| 213 |
+
def prepare_model(max_frames, device, repo_id="ASLP-lab/DiffRhythm-1_2"):
|
| 214 |
+
# prepare cfm model
|
| 215 |
+
if max_frames == 2048:
|
| 216 |
+
repo_id = "ASLP-lab/DiffRhythm-1_2"
|
| 217 |
+
else:
|
| 218 |
+
repo_id = "ASLP-lab/DiffRhythm-1_2-full"
|
| 219 |
+
|
| 220 |
+
dit_ckpt_path = hf_hub_download(
|
| 221 |
+
repo_id=repo_id, filename="cfm_model.pt", cache_dir="./pretrained"
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
dit_config_path = "./config/diffrhythm-1b.json"
|
| 225 |
+
with open(dit_config_path) as f:
|
| 226 |
+
model_config = json.load(f)
|
| 227 |
+
dit_model_cls = DiT
|
| 228 |
+
cfm = CFM(
|
| 229 |
+
transformer=dit_model_cls(**model_config["model"], max_frames=max_frames),
|
| 230 |
+
num_channels=model_config["model"]["mel_dim"],
|
| 231 |
+
max_frames=max_frames
|
| 232 |
+
)
|
| 233 |
+
cfm = cfm.to(device)
|
| 234 |
+
cfm = load_checkpoint(cfm, dit_ckpt_path, device=device, use_ema=False)
|
| 235 |
+
|
| 236 |
+
# prepare tokenizer
|
| 237 |
+
tokenizer = CNENTokenizer()
|
| 238 |
+
|
| 239 |
+
# prepare muq
|
| 240 |
+
muq = MuQMuLan.from_pretrained("OpenMuQ/MuQ-MuLan-large", cache_dir="./pretrained")
|
| 241 |
+
muq = muq.to(device).eval()
|
| 242 |
+
|
| 243 |
+
# prepare vae
|
| 244 |
+
vae_ckpt_path = hf_hub_download(
|
| 245 |
+
repo_id="ASLP-lab/DiffRhythm-vae",
|
| 246 |
+
filename="vae_model.pt",
|
| 247 |
+
cache_dir="./pretrained",
|
| 248 |
+
)
|
| 249 |
+
vae = torch.jit.load(vae_ckpt_path, map_location="cpu").to(device)
|
| 250 |
+
|
| 251 |
+
return cfm, tokenizer, muq, vae
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
# for song edit, will be added in the future
|
| 255 |
+
def get_reference_latent(device, max_frames, edit, pred_segments, ref_song, vae_model):
|
| 256 |
+
sampling_rate = 44100
|
| 257 |
+
downsample_rate = 2048
|
| 258 |
+
io_channels = 2
|
| 259 |
+
if edit:
|
| 260 |
+
input_audio, in_sr = torchaudio.load(ref_song)
|
| 261 |
+
input_audio = prepare_audio(input_audio, in_sr=in_sr, target_sr=sampling_rate, target_length=None, target_channels=io_channels, device=device)
|
| 262 |
+
input_audio = normalize_audio(input_audio, -6)
|
| 263 |
+
|
| 264 |
+
with torch.no_grad():
|
| 265 |
+
latent = encode_audio(input_audio, vae_model, chunked=True) # [b d t]
|
| 266 |
+
mean, scale = latent.chunk(2, dim=1)
|
| 267 |
+
prompt, _ = vae_sample(mean, scale)
|
| 268 |
+
prompt = prompt.transpose(1, 2) # [b t d]
|
| 269 |
+
|
| 270 |
+
pred_segments = json.loads(pred_segments)
|
| 271 |
+
|
| 272 |
+
pred_frames = []
|
| 273 |
+
for st, et in pred_segments:
|
| 274 |
+
sf = 0 if st == -1 else int(st * sampling_rate / downsample_rate)
|
| 275 |
+
ef = max_frames if et == -1 else int(et * sampling_rate / downsample_rate)
|
| 276 |
+
pred_frames.append((sf, ef))
|
| 277 |
+
|
| 278 |
+
return prompt, pred_frames
|
| 279 |
+
else:
|
| 280 |
+
prompt = torch.zeros(1, max_frames, 64).to(device)
|
| 281 |
+
pred_frames = [(0, max_frames)]
|
| 282 |
+
return prompt, pred_frames
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def get_negative_style_prompt(device):
|
| 286 |
+
file_path = "infer/example/vocal.npy"
|
| 287 |
+
vocal_stlye = np.load(file_path)
|
| 288 |
+
|
| 289 |
+
vocal_stlye = torch.from_numpy(vocal_stlye).to(device) # [1, 512]
|
| 290 |
+
vocal_stlye = vocal_stlye.half()
|
| 291 |
+
|
| 292 |
+
return vocal_stlye
|
| 293 |
+
|
| 294 |
+
|
| 295 |
+
@torch.no_grad()
|
| 296 |
+
def get_style_prompt(model, wav_path=None, prompt=None):
|
| 297 |
+
mulan = model
|
| 298 |
+
|
| 299 |
+
if prompt is not None:
|
| 300 |
+
return mulan(texts=prompt).half()
|
| 301 |
+
|
| 302 |
+
ext = os.path.splitext(wav_path)[-1].lower()
|
| 303 |
+
if ext == ".mp3":
|
| 304 |
+
meta = MP3(wav_path)
|
| 305 |
+
audio_len = meta.info.length
|
| 306 |
+
elif ext in [".wav", ".flac"]:
|
| 307 |
+
audio_len = librosa.get_duration(path=wav_path)
|
| 308 |
+
else:
|
| 309 |
+
raise ValueError("Unsupported file format: {}".format(ext))
|
| 310 |
+
|
| 311 |
+
if audio_len < 10:
|
| 312 |
+
print(
|
| 313 |
+
f"Warning: The audio file {wav_path} is too short ({audio_len:.2f} seconds). Expected at least 10 seconds."
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
assert audio_len >= 10
|
| 317 |
+
|
| 318 |
+
mid_time = audio_len // 2
|
| 319 |
+
start_time = mid_time - 5
|
| 320 |
+
wav, _ = librosa.load(wav_path, sr=24000, offset=start_time, duration=10)
|
| 321 |
+
|
| 322 |
+
wav = torch.tensor(wav).unsqueeze(0).to(model.device)
|
| 323 |
+
|
| 324 |
+
with torch.no_grad():
|
| 325 |
+
audio_emb = mulan(wavs=wav) # [1, 512]
|
| 326 |
+
|
| 327 |
+
audio_emb = audio_emb
|
| 328 |
+
audio_emb = audio_emb.half()
|
| 329 |
+
|
| 330 |
+
return audio_emb
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
def parse_lyrics(lyrics: str):
|
| 334 |
+
lyrics_with_time = []
|
| 335 |
+
lyrics = lyrics.strip()
|
| 336 |
+
for line in lyrics.split("\n"):
|
| 337 |
+
try:
|
| 338 |
+
time, lyric = line[1:9], line[10:]
|
| 339 |
+
lyric = lyric.strip()
|
| 340 |
+
mins, secs = time.split(":")
|
| 341 |
+
secs = int(mins) * 60 + float(secs)
|
| 342 |
+
lyrics_with_time.append((secs, lyric))
|
| 343 |
+
except:
|
| 344 |
+
continue
|
| 345 |
+
return lyrics_with_time
|
| 346 |
+
|
| 347 |
+
|
| 348 |
+
class CNENTokenizer:
|
| 349 |
+
def __init__(self):
|
| 350 |
+
with open("./g2p/g2p/vocab.json", "r", encoding='utf-8') as file:
|
| 351 |
+
self.phone2id: dict = json.load(file)["vocab"]
|
| 352 |
+
self.id2phone = {v: k for (k, v) in self.phone2id.items()}
|
| 353 |
+
from g2p.g2p_generation import chn_eng_g2p
|
| 354 |
+
|
| 355 |
+
self.tokenizer = chn_eng_g2p
|
| 356 |
+
|
| 357 |
+
def encode(self, text):
|
| 358 |
+
phone, token = self.tokenizer(text)
|
| 359 |
+
token = [x + 1 for x in token]
|
| 360 |
+
return token
|
| 361 |
+
|
| 362 |
+
def decode(self, token):
|
| 363 |
+
return "|".join([self.id2phone[x - 1] for x in token])
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def get_lrc_token(max_frames, text, tokenizer, max_secs, device):
|
| 367 |
+
|
| 368 |
+
lyrics_shift = 0
|
| 369 |
+
sampling_rate = 44100
|
| 370 |
+
downsample_rate = 2048
|
| 371 |
+
# max_secs = max_frames / (sampling_rate / downsample_rate)
|
| 372 |
+
|
| 373 |
+
comma_token_id = 1
|
| 374 |
+
period_token_id = 2
|
| 375 |
+
|
| 376 |
+
lrc_with_time = parse_lyrics(text)
|
| 377 |
+
|
| 378 |
+
modified_lrc_with_time = []
|
| 379 |
+
for i in range(len(lrc_with_time)):
|
| 380 |
+
time, line = lrc_with_time[i]
|
| 381 |
+
line_token = tokenizer.encode(line)
|
| 382 |
+
modified_lrc_with_time.append((time, line_token))
|
| 383 |
+
lrc_with_time = modified_lrc_with_time
|
| 384 |
+
|
| 385 |
+
lrc_with_time = [
|
| 386 |
+
(time_start, line)
|
| 387 |
+
for (time_start, line) in lrc_with_time
|
| 388 |
+
if time_start < max_secs
|
| 389 |
+
]
|
| 390 |
+
if max_frames == 2048:
|
| 391 |
+
lrc_with_time = lrc_with_time[:-1] if len(lrc_with_time) >= 1 else lrc_with_time
|
| 392 |
+
|
| 393 |
+
end_frame = max_frames if max_frames == 2048 else int(max_secs * (sampling_rate / downsample_rate))
|
| 394 |
+
end_frame = min(end_frame, max_frames)
|
| 395 |
+
|
| 396 |
+
normalized_start_time = 0.0
|
| 397 |
+
|
| 398 |
+
normalized_duration = end_frame / max_frames
|
| 399 |
+
|
| 400 |
+
lrc = torch.zeros((end_frame,), dtype=torch.long)
|
| 401 |
+
|
| 402 |
+
tokens_count = 0
|
| 403 |
+
last_end_pos = 0
|
| 404 |
+
for time_start, line in lrc_with_time:
|
| 405 |
+
tokens = [
|
| 406 |
+
token if token != period_token_id else comma_token_id for token in line
|
| 407 |
+
] + [period_token_id]
|
| 408 |
+
tokens = torch.tensor(tokens, dtype=torch.long)
|
| 409 |
+
num_tokens = tokens.shape[0]
|
| 410 |
+
|
| 411 |
+
gt_frame_start = int(time_start * sampling_rate / downsample_rate)
|
| 412 |
+
|
| 413 |
+
frame_shift = random.randint(int(-lyrics_shift), int(lyrics_shift))
|
| 414 |
+
|
| 415 |
+
frame_start = max(gt_frame_start - frame_shift, last_end_pos)
|
| 416 |
+
frame_len = min(num_tokens, end_frame - frame_start)
|
| 417 |
+
|
| 418 |
+
lrc[frame_start : frame_start + frame_len] = tokens[:frame_len]
|
| 419 |
+
|
| 420 |
+
tokens_count += num_tokens
|
| 421 |
+
last_end_pos = frame_start + frame_len
|
| 422 |
+
|
| 423 |
+
lrc_emb = lrc.unsqueeze(0).to(device)
|
| 424 |
+
|
| 425 |
+
normalized_start_time = torch.tensor(normalized_start_time).unsqueeze(0).to(device)
|
| 426 |
+
normalized_start_time = normalized_start_time.half()
|
| 427 |
+
|
| 428 |
+
normalized_duration = torch.tensor(normalized_duration).unsqueeze(0).to(device)
|
| 429 |
+
normalized_duration = normalized_duration.half()
|
| 430 |
+
|
| 431 |
+
return lrc_emb, normalized_start_time, end_frame, normalized_duration
|
| 432 |
+
|
| 433 |
+
|
| 434 |
+
def load_checkpoint(model, ckpt_path, device, use_ema=True):
|
| 435 |
+
model = model.half()
|
| 436 |
+
|
| 437 |
+
ckpt_type = ckpt_path.split(".")[-1]
|
| 438 |
+
if ckpt_type == "safetensors":
|
| 439 |
+
from safetensors.torch import load_file
|
| 440 |
+
|
| 441 |
+
checkpoint = load_file(ckpt_path)
|
| 442 |
+
else:
|
| 443 |
+
checkpoint = torch.load(ckpt_path, weights_only=True)
|
| 444 |
+
|
| 445 |
+
if use_ema:
|
| 446 |
+
if ckpt_type == "safetensors":
|
| 447 |
+
checkpoint = {"ema_model_state_dict": checkpoint}
|
| 448 |
+
checkpoint["model_state_dict"] = {
|
| 449 |
+
k.replace("ema_model.", ""): v
|
| 450 |
+
for k, v in checkpoint["ema_model_state_dict"].items()
|
| 451 |
+
if k not in ["initted", "step"]
|
| 452 |
+
}
|
| 453 |
+
model.load_state_dict(checkpoint["model_state_dict"], strict=False)
|
| 454 |
+
else:
|
| 455 |
+
if ckpt_type == "safetensors":
|
| 456 |
+
checkpoint = {"model_state_dict": checkpoint}
|
| 457 |
+
model.load_state_dict(checkpoint["model_state_dict"], strict=False)
|
| 458 |
+
|
| 459 |
+
return model.to(device)
|
code/model/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from model.cfm import CFM
|
| 2 |
+
from model.dit import DiT
|
| 3 |
+
from model.trainer import Trainer
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
__all__ = ["CFM"]
|
code/model/cfm.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 2 |
+
# 2025 Ziqian Ning (ningziqian@mail.nwpu.edu.cn)
|
| 3 |
+
# 2025 Huakang Chen (huakang@mail.nwpu.edu.cn)
|
| 4 |
+
# 2025 Guobin Ma (guobin.ma@mail.nwpu.edu.cn)
|
| 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 |
+
""" This implementation is adapted from github repo:
|
| 19 |
+
https://github.com/SWivid/F5-TTS.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from __future__ import annotations
|
| 23 |
+
from typing import Callable
|
| 24 |
+
from random import random
|
| 25 |
+
|
| 26 |
+
import torch
|
| 27 |
+
from torch import nn
|
| 28 |
+
import torch
|
| 29 |
+
import torch.nn.functional as F
|
| 30 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 31 |
+
|
| 32 |
+
from torchdiffeq import odeint
|
| 33 |
+
|
| 34 |
+
from model.utils import (
|
| 35 |
+
exists,
|
| 36 |
+
list_str_to_idx,
|
| 37 |
+
list_str_to_tensor,
|
| 38 |
+
lens_to_mask,
|
| 39 |
+
mask_from_frac_lengths,
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def custom_mask_from_start_end_indices(
|
| 43 |
+
seq_len: int["b"], # noqa: F821
|
| 44 |
+
latent_pred_segments,
|
| 45 |
+
device,
|
| 46 |
+
max_seq_len
|
| 47 |
+
):
|
| 48 |
+
max_seq_len = max_seq_len
|
| 49 |
+
seq = torch.arange(max_seq_len, device=device).long()
|
| 50 |
+
|
| 51 |
+
res_mask = torch.zeros(max_seq_len, device=device, dtype=torch.bool)
|
| 52 |
+
|
| 53 |
+
for start, end in latent_pred_segments:
|
| 54 |
+
start = start.unsqueeze(0)
|
| 55 |
+
end = end.unsqueeze(0)
|
| 56 |
+
start_mask = seq[None, :] >= start[:, None]
|
| 57 |
+
end_mask = seq[None, :] < end[:, None]
|
| 58 |
+
res_mask = res_mask | (start_mask & end_mask)
|
| 59 |
+
|
| 60 |
+
return res_mask
|
| 61 |
+
|
| 62 |
+
class CFM(nn.Module):
|
| 63 |
+
def __init__(
|
| 64 |
+
self,
|
| 65 |
+
transformer: nn.Module,
|
| 66 |
+
sigma=0.0,
|
| 67 |
+
odeint_kwargs: dict = dict(
|
| 68 |
+
method="euler"
|
| 69 |
+
),
|
| 70 |
+
odeint_options: dict = dict(
|
| 71 |
+
min_step=0.05
|
| 72 |
+
),
|
| 73 |
+
audio_drop_prob=0.3,
|
| 74 |
+
cond_drop_prob=0.2,
|
| 75 |
+
style_drop_prob=0.1,
|
| 76 |
+
lrc_drop_prob=0.1,
|
| 77 |
+
num_channels=None,
|
| 78 |
+
frac_lengths_mask: tuple[float, float] = (0.7, 1.0),
|
| 79 |
+
vocab_char_map: dict[str:int] | None = None,
|
| 80 |
+
max_frames=2048
|
| 81 |
+
):
|
| 82 |
+
super().__init__()
|
| 83 |
+
|
| 84 |
+
self.frac_lengths_mask = frac_lengths_mask
|
| 85 |
+
|
| 86 |
+
self.num_channels = num_channels
|
| 87 |
+
|
| 88 |
+
# classifier-free guidance
|
| 89 |
+
self.audio_drop_prob = audio_drop_prob
|
| 90 |
+
self.cond_drop_prob = cond_drop_prob
|
| 91 |
+
self.style_drop_prob = style_drop_prob
|
| 92 |
+
self.lrc_drop_prob = lrc_drop_prob
|
| 93 |
+
|
| 94 |
+
# transformer
|
| 95 |
+
self.transformer = transformer
|
| 96 |
+
dim = transformer.dim
|
| 97 |
+
self.dim = dim
|
| 98 |
+
|
| 99 |
+
# conditional flow related
|
| 100 |
+
self.sigma = sigma
|
| 101 |
+
|
| 102 |
+
# sampling related
|
| 103 |
+
self.odeint_kwargs = odeint_kwargs
|
| 104 |
+
|
| 105 |
+
self.odeint_options = odeint_options
|
| 106 |
+
|
| 107 |
+
# vocab map for tokenization
|
| 108 |
+
self.vocab_char_map = vocab_char_map
|
| 109 |
+
|
| 110 |
+
self.max_frames = max_frames
|
| 111 |
+
|
| 112 |
+
@property
|
| 113 |
+
def device(self):
|
| 114 |
+
return next(self.parameters()).device
|
| 115 |
+
|
| 116 |
+
@torch.no_grad()
|
| 117 |
+
def sample(
|
| 118 |
+
self,
|
| 119 |
+
cond: float["b n d"] | float["b nw"], # noqa: F722
|
| 120 |
+
text: int["b nt"] | list[str], # noqa: F722
|
| 121 |
+
duration: int | int["b"], # noqa: F821
|
| 122 |
+
*,
|
| 123 |
+
style_prompt = None,
|
| 124 |
+
style_prompt_lens = None,
|
| 125 |
+
negative_style_prompt = None,
|
| 126 |
+
lens: int["b"] | None = None, # noqa: F821
|
| 127 |
+
steps=32,
|
| 128 |
+
cfg_strength=4.0,
|
| 129 |
+
sway_sampling_coef=None,
|
| 130 |
+
seed: int | None = None,
|
| 131 |
+
max_duration=6144,
|
| 132 |
+
vocoder: Callable[[float["b d n"]], float["b nw"]] | None = None, # noqa: F722
|
| 133 |
+
no_ref_audio=False,
|
| 134 |
+
duplicate_test=False,
|
| 135 |
+
t_inter=0.1,
|
| 136 |
+
edit_mask=None,
|
| 137 |
+
start_time=None,
|
| 138 |
+
latent_pred_segments=None,
|
| 139 |
+
song_duration=None,
|
| 140 |
+
batch_infer_num=1
|
| 141 |
+
):
|
| 142 |
+
self.eval()
|
| 143 |
+
|
| 144 |
+
if next(self.parameters()).dtype == torch.float16:
|
| 145 |
+
cond = cond.half()
|
| 146 |
+
|
| 147 |
+
# raw wave
|
| 148 |
+
if cond.shape[1] > duration:
|
| 149 |
+
cond = cond[:, :duration, :]
|
| 150 |
+
|
| 151 |
+
if cond.ndim == 2:
|
| 152 |
+
cond = self.mel_spec(cond)
|
| 153 |
+
cond = cond.permute(0, 2, 1)
|
| 154 |
+
assert cond.shape[-1] == self.num_channels
|
| 155 |
+
|
| 156 |
+
batch, cond_seq_len, device = *cond.shape[:2], cond.device
|
| 157 |
+
if not exists(lens):
|
| 158 |
+
lens = torch.full((batch,), cond_seq_len, device=device, dtype=torch.long)
|
| 159 |
+
|
| 160 |
+
# text
|
| 161 |
+
if isinstance(text, list):
|
| 162 |
+
if exists(self.vocab_char_map):
|
| 163 |
+
text = list_str_to_idx(text, self.vocab_char_map).to(device)
|
| 164 |
+
else:
|
| 165 |
+
text = list_str_to_tensor(text).to(device)
|
| 166 |
+
assert text.shape[0] == batch
|
| 167 |
+
|
| 168 |
+
# duration
|
| 169 |
+
cond_mask = lens_to_mask(lens)
|
| 170 |
+
if edit_mask is not None:
|
| 171 |
+
cond_mask = cond_mask & edit_mask
|
| 172 |
+
|
| 173 |
+
latent_pred_segments = torch.tensor(latent_pred_segments).to(cond.device)
|
| 174 |
+
fixed_span_mask = custom_mask_from_start_end_indices(cond_seq_len, latent_pred_segments, device=cond.device, max_seq_len=duration)
|
| 175 |
+
fixed_span_mask = fixed_span_mask.unsqueeze(-1)
|
| 176 |
+
step_cond = torch.where(fixed_span_mask, torch.zeros_like(cond), cond)
|
| 177 |
+
|
| 178 |
+
if isinstance(duration, int):
|
| 179 |
+
duration = torch.full((batch_infer_num,), duration, device=device, dtype=torch.long)
|
| 180 |
+
|
| 181 |
+
duration = duration.clamp(max=max_duration)
|
| 182 |
+
max_duration = duration.amax()
|
| 183 |
+
|
| 184 |
+
# duplicate test corner for inner time step oberservation
|
| 185 |
+
if duplicate_test:
|
| 186 |
+
test_cond = F.pad(cond, (0, 0, cond_seq_len, max_duration - 2 * cond_seq_len), value=0.0)
|
| 187 |
+
|
| 188 |
+
if batch > 1:
|
| 189 |
+
mask = lens_to_mask(duration)
|
| 190 |
+
else: # save memory and speed up, as single inference need no mask currently
|
| 191 |
+
mask = None
|
| 192 |
+
|
| 193 |
+
# test for no ref audio
|
| 194 |
+
if no_ref_audio:
|
| 195 |
+
cond = torch.zeros_like(cond)
|
| 196 |
+
|
| 197 |
+
cond = cond.repeat(batch_infer_num, 1, 1)
|
| 198 |
+
step_cond = step_cond.repeat(batch_infer_num, 1, 1)
|
| 199 |
+
text = text.repeat(batch_infer_num, 1)
|
| 200 |
+
style_prompt = style_prompt.repeat(batch_infer_num, 1)
|
| 201 |
+
negative_style_prompt = negative_style_prompt.repeat(batch_infer_num, 1)
|
| 202 |
+
start_time = start_time.repeat(batch_infer_num)
|
| 203 |
+
fixed_span_mask = fixed_span_mask.repeat(batch_infer_num, 1, 1)
|
| 204 |
+
song_duration = song_duration.repeat(batch_infer_num)
|
| 205 |
+
|
| 206 |
+
def fn(t, x):
|
| 207 |
+
# predict flow
|
| 208 |
+
pred = self.transformer(
|
| 209 |
+
x=x, cond=step_cond, text=text, time=t, drop_audio_cond=False, drop_text=False, drop_prompt=False,
|
| 210 |
+
style_prompt=style_prompt, start_time=start_time, duration=song_duration
|
| 211 |
+
)
|
| 212 |
+
if cfg_strength < 1e-5:
|
| 213 |
+
return pred
|
| 214 |
+
|
| 215 |
+
null_pred = self.transformer(
|
| 216 |
+
x=x, cond=step_cond, text=text, time=t, drop_audio_cond=True, drop_text=True, drop_prompt=False,
|
| 217 |
+
style_prompt=negative_style_prompt, start_time=start_time, duration=song_duration
|
| 218 |
+
)
|
| 219 |
+
return pred + (pred - null_pred) * cfg_strength
|
| 220 |
+
|
| 221 |
+
# noise input
|
| 222 |
+
# to make sure batch inference result is same with different batch size, and for sure single inference
|
| 223 |
+
# still some difference maybe due to convolutional layers
|
| 224 |
+
y0 = []
|
| 225 |
+
for dur in duration:
|
| 226 |
+
if exists(seed):
|
| 227 |
+
torch.manual_seed(seed)
|
| 228 |
+
y0.append(torch.randn(dur, self.num_channels, device=self.device, dtype=step_cond.dtype))
|
| 229 |
+
y0 = pad_sequence(y0, padding_value=0, batch_first=True)
|
| 230 |
+
|
| 231 |
+
t_start = 0
|
| 232 |
+
|
| 233 |
+
# duplicate test corner for inner time step oberservation
|
| 234 |
+
if duplicate_test:
|
| 235 |
+
t_start = t_inter
|
| 236 |
+
y0 = (1 - t_start) * y0 + t_start * test_cond
|
| 237 |
+
steps = int(steps * (1 - t_start))
|
| 238 |
+
|
| 239 |
+
t = torch.linspace(t_start, 1, steps, device=self.device, dtype=step_cond.dtype)
|
| 240 |
+
if sway_sampling_coef is not None:
|
| 241 |
+
t = t + sway_sampling_coef * (torch.cos(torch.pi / 2 * t) - 1 + t)
|
| 242 |
+
|
| 243 |
+
trajectory = odeint(fn, y0, t, **self.odeint_kwargs)
|
| 244 |
+
|
| 245 |
+
sampled = trajectory[-1]
|
| 246 |
+
out = sampled
|
| 247 |
+
out = torch.where(fixed_span_mask, out, cond)
|
| 248 |
+
|
| 249 |
+
if exists(vocoder):
|
| 250 |
+
out = out.permute(0, 2, 1)
|
| 251 |
+
out = vocoder(out)
|
| 252 |
+
|
| 253 |
+
out = torch.chunk(out, batch_infer_num, dim=0)
|
| 254 |
+
return out, trajectory
|
| 255 |
+
|
| 256 |
+
def forward(
|
| 257 |
+
self,
|
| 258 |
+
inp: float["b n d"] | float["b nw"], # mel or raw wave # noqa: F722
|
| 259 |
+
text: int["b nt"] | list[str], # noqa: F722
|
| 260 |
+
style_prompt = None,
|
| 261 |
+
style_prompt_lens = None,
|
| 262 |
+
lens: int["b"] | None = None, # noqa: F821
|
| 263 |
+
noise_scheduler: str | None = None,
|
| 264 |
+
grad_ckpt = False,
|
| 265 |
+
start_time = None,
|
| 266 |
+
):
|
| 267 |
+
|
| 268 |
+
batch, seq_len, dtype, device, _σ1 = *inp.shape[:2], inp.dtype, self.device, self.sigma
|
| 269 |
+
|
| 270 |
+
# lens and mask
|
| 271 |
+
if not exists(lens):
|
| 272 |
+
lens = torch.full((batch,), seq_len, device=device)
|
| 273 |
+
|
| 274 |
+
mask = lens_to_mask(lens, length=seq_len) # useless here, as collate_fn will pad to max length in batch
|
| 275 |
+
|
| 276 |
+
# get a random span to mask out for training conditionally
|
| 277 |
+
frac_lengths = torch.zeros((batch,), device=self.device).float().uniform_(*self.frac_lengths_mask)
|
| 278 |
+
rand_span_mask = mask_from_frac_lengths(lens, frac_lengths, self.max_frames)
|
| 279 |
+
|
| 280 |
+
if exists(mask):
|
| 281 |
+
rand_span_mask = mask
|
| 282 |
+
|
| 283 |
+
# mel is x1
|
| 284 |
+
x1 = inp
|
| 285 |
+
|
| 286 |
+
# x0 is gaussian noise
|
| 287 |
+
x0 = torch.randn_like(x1)
|
| 288 |
+
|
| 289 |
+
# time step
|
| 290 |
+
time = torch.normal(mean=0, std=1, size=(batch,), device=self.device)
|
| 291 |
+
time = torch.nn.functional.sigmoid(time)
|
| 292 |
+
# TODO. noise_scheduler
|
| 293 |
+
|
| 294 |
+
# sample xt (��_t(x) in the paper)
|
| 295 |
+
t = time.unsqueeze(-1).unsqueeze(-1)
|
| 296 |
+
φ = (1 - t) * x0 + t * x1
|
| 297 |
+
flow = x1 - x0
|
| 298 |
+
|
| 299 |
+
# only predict what is within the random mask span for infilling
|
| 300 |
+
cond = torch.where(rand_span_mask[..., None], torch.zeros_like(x1), x1)
|
| 301 |
+
|
| 302 |
+
# transformer and cfg training with a drop rate
|
| 303 |
+
drop_audio_cond = random() < self.audio_drop_prob # p_drop in voicebox paper
|
| 304 |
+
drop_text = random() < self.lrc_drop_prob
|
| 305 |
+
drop_prompt = random() < self.style_drop_prob
|
| 306 |
+
|
| 307 |
+
# if want rigourously mask out padding, record in collate_fn in dataset.py, and pass in here
|
| 308 |
+
# adding mask will use more memory, thus also need to adjust batchsampler with scaled down threshold for long sequences
|
| 309 |
+
pred = self.transformer(
|
| 310 |
+
x=φ, cond=cond, text=text, time=time, drop_audio_cond=drop_audio_cond, drop_text=drop_text, drop_prompt=drop_prompt,
|
| 311 |
+
style_prompt=style_prompt, start_time=start_time
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
# flow matching loss
|
| 315 |
+
loss = F.mse_loss(pred, flow, reduction="none")
|
| 316 |
+
loss = loss[rand_span_mask]
|
| 317 |
+
|
| 318 |
+
return loss.mean(), cond, pred
|
code/model/dit.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 2 |
+
# 2025 Ziqian Ning (ningziqian@mail.nwpu.edu.cn)
|
| 3 |
+
# 2025 Huakang Chen (huakang@mail.nwpu.edu.cn)
|
| 4 |
+
# 2025 Yuepeng Jiang (Jiangyp@mail.nwpu.edu.cn)
|
| 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 |
+
""" This implementation is adapted from github repo:
|
| 19 |
+
https://github.com/SWivid/F5-TTS.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from __future__ import annotations
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
from torch import nn
|
| 26 |
+
import torch
|
| 27 |
+
|
| 28 |
+
from transformers.models.llama.modeling_llama import LlamaDecoderLayer, LlamaRotaryEmbedding
|
| 29 |
+
from transformers.models.llama import LlamaConfig
|
| 30 |
+
|
| 31 |
+
from model.modules import (
|
| 32 |
+
TimestepEmbedding,
|
| 33 |
+
ConvNeXtV2Block,
|
| 34 |
+
ConvPositionEmbedding,
|
| 35 |
+
AdaLayerNormZero_Final,
|
| 36 |
+
precompute_freqs_cis,
|
| 37 |
+
get_pos_embed_indices,
|
| 38 |
+
_prepare_decoder_attention_mask,
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
# Text embedding
|
| 42 |
+
class TextEmbedding(nn.Module):
|
| 43 |
+
def __init__(self, text_num_embeds, text_dim, max_pos, conv_layers=0, conv_mult=2):
|
| 44 |
+
super().__init__()
|
| 45 |
+
self.text_embed = nn.Embedding(text_num_embeds + 1, text_dim) # use 0 as filler token
|
| 46 |
+
|
| 47 |
+
if conv_layers > 0:
|
| 48 |
+
self.extra_modeling = True
|
| 49 |
+
self.precompute_max_pos = max_pos # ~44s of 24khz audio
|
| 50 |
+
self.register_buffer("freqs_cis", precompute_freqs_cis(text_dim, self.precompute_max_pos), persistent=False)
|
| 51 |
+
self.text_blocks = nn.Sequential(
|
| 52 |
+
*[ConvNeXtV2Block(text_dim, text_dim * conv_mult) for _ in range(conv_layers)]
|
| 53 |
+
)
|
| 54 |
+
else:
|
| 55 |
+
self.extra_modeling = False
|
| 56 |
+
|
| 57 |
+
def forward(self, text: int["b nt"], seq_len, drop_text=False): # noqa: F722
|
| 58 |
+
batch, text_len = text.shape[0], text.shape[1]
|
| 59 |
+
|
| 60 |
+
if drop_text: # cfg for text
|
| 61 |
+
text = torch.zeros_like(text)
|
| 62 |
+
|
| 63 |
+
text = self.text_embed(text) # b n -> b n d
|
| 64 |
+
|
| 65 |
+
# possible extra modeling
|
| 66 |
+
if self.extra_modeling:
|
| 67 |
+
# sinus pos emb
|
| 68 |
+
batch_start = torch.zeros((batch,), dtype=torch.long)
|
| 69 |
+
pos_idx = get_pos_embed_indices(batch_start, seq_len, max_pos=self.precompute_max_pos)
|
| 70 |
+
text_pos_embed = self.freqs_cis[pos_idx]
|
| 71 |
+
text = text + text_pos_embed
|
| 72 |
+
|
| 73 |
+
# convnextv2 blocks
|
| 74 |
+
text = self.text_blocks(text)
|
| 75 |
+
|
| 76 |
+
return text
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# noised input audio and context mixing embedding
|
| 80 |
+
class InputEmbedding(nn.Module):
|
| 81 |
+
def __init__(self, mel_dim, text_dim, out_dim, cond_dim):
|
| 82 |
+
super().__init__()
|
| 83 |
+
self.proj = nn.Linear(mel_dim * 2 + text_dim + cond_dim * 2, out_dim)
|
| 84 |
+
self.conv_pos_embed = ConvPositionEmbedding(dim=out_dim)
|
| 85 |
+
|
| 86 |
+
def forward(self, x: float["b n d"], cond: float["b n d"], text_embed: float["b n d"], style_emb, time_emb, drop_audio_cond=False): # noqa: F722
|
| 87 |
+
if drop_audio_cond: # cfg for cond audio
|
| 88 |
+
cond = torch.zeros_like(cond)
|
| 89 |
+
style_emb = style_emb.unsqueeze(1).repeat(1, x.shape[1], 1)
|
| 90 |
+
time_emb = time_emb.unsqueeze(1).repeat(1, x.shape[1], 1)
|
| 91 |
+
x = self.proj(torch.cat((x, cond, text_embed, style_emb, time_emb), dim=-1))
|
| 92 |
+
x = self.conv_pos_embed(x) + x
|
| 93 |
+
return x
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# Transformer backbone using Llama blocks
|
| 97 |
+
class DiT(nn.Module):
|
| 98 |
+
def __init__(
|
| 99 |
+
self,
|
| 100 |
+
*,
|
| 101 |
+
dim,
|
| 102 |
+
depth=8,
|
| 103 |
+
heads=8,
|
| 104 |
+
dim_head=64,
|
| 105 |
+
dropout=0.1,
|
| 106 |
+
ff_mult=4,
|
| 107 |
+
mel_dim=100,
|
| 108 |
+
text_num_embeds=256,
|
| 109 |
+
text_dim=None,
|
| 110 |
+
conv_layers=0,
|
| 111 |
+
long_skip_connection=False,
|
| 112 |
+
max_frames=2048
|
| 113 |
+
):
|
| 114 |
+
super().__init__()
|
| 115 |
+
|
| 116 |
+
self.max_frames = max_frames
|
| 117 |
+
|
| 118 |
+
cond_dim = 512
|
| 119 |
+
self.time_embed = TimestepEmbedding(cond_dim)
|
| 120 |
+
self.start_time_embed = TimestepEmbedding(cond_dim)
|
| 121 |
+
self.duration_time_embed = TimestepEmbedding(cond_dim) if self.max_frames == 6144 else None
|
| 122 |
+
if text_dim is None:
|
| 123 |
+
text_dim = mel_dim
|
| 124 |
+
self.text_embed = TextEmbedding(text_num_embeds, text_dim, conv_layers=conv_layers, max_pos=self.max_frames)
|
| 125 |
+
self.input_embed = InputEmbedding(mel_dim, text_dim, dim, cond_dim=cond_dim)
|
| 126 |
+
|
| 127 |
+
self.dim = dim
|
| 128 |
+
self.depth = depth
|
| 129 |
+
|
| 130 |
+
llama_config = LlamaConfig(hidden_size=dim, intermediate_size=dim * ff_mult, hidden_act='silu', max_position_embeddings=self.max_frames)
|
| 131 |
+
llama_config._attn_implementation = 'sdpa'
|
| 132 |
+
self.transformer_blocks = nn.ModuleList(
|
| 133 |
+
[LlamaDecoderLayer(llama_config, layer_idx=i) for i in range(depth)]
|
| 134 |
+
)
|
| 135 |
+
self.rotary_emb = LlamaRotaryEmbedding(config=llama_config)
|
| 136 |
+
self.long_skip_connection = nn.Linear(dim * 2, dim, bias=False) if long_skip_connection else None
|
| 137 |
+
|
| 138 |
+
self.text_fusion_linears = nn.ModuleList(
|
| 139 |
+
[
|
| 140 |
+
nn.Sequential(
|
| 141 |
+
nn.Linear(cond_dim, dim),
|
| 142 |
+
nn.SiLU()
|
| 143 |
+
) for i in range(depth // 2)
|
| 144 |
+
]
|
| 145 |
+
)
|
| 146 |
+
for layer in self.text_fusion_linears:
|
| 147 |
+
for p in layer.parameters():
|
| 148 |
+
p.detach().zero_()
|
| 149 |
+
|
| 150 |
+
self.norm_out = AdaLayerNormZero_Final(dim, cond_dim) # final modulation
|
| 151 |
+
self.proj_out = nn.Linear(dim, mel_dim)
|
| 152 |
+
|
| 153 |
+
def forward_timestep_invariant(self, text, seq_len, drop_text, start_time):
|
| 154 |
+
s_t = self.start_time_embed(start_time)
|
| 155 |
+
text_embed = self.text_embed(text, seq_len, drop_text=drop_text)
|
| 156 |
+
text_residuals = []
|
| 157 |
+
for layer in self.text_fusion_linears:
|
| 158 |
+
text_residual = layer(text_embed)
|
| 159 |
+
text_residuals.append(text_residual)
|
| 160 |
+
return s_t, text_embed, text_residuals
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def forward(
|
| 164 |
+
self,
|
| 165 |
+
x: float["b n d"], # nosied input audio # noqa: F722
|
| 166 |
+
cond: float["b n d"], # masked cond audio # noqa: F722
|
| 167 |
+
text: int["b nt"], # text # noqa: F722
|
| 168 |
+
time: float["b"] | float[""], # time step # noqa: F821 F722
|
| 169 |
+
drop_audio_cond, # cfg for cond audio
|
| 170 |
+
drop_text, # cfg for text
|
| 171 |
+
drop_prompt=False,
|
| 172 |
+
style_prompt=None, # [b d t]
|
| 173 |
+
start_time=None,
|
| 174 |
+
duration=None
|
| 175 |
+
):
|
| 176 |
+
|
| 177 |
+
batch, seq_len = x.shape[0], x.shape[1]
|
| 178 |
+
if time.ndim == 0:
|
| 179 |
+
time = time.repeat(batch)
|
| 180 |
+
|
| 181 |
+
# t: conditioning time, c: context (text + masked cond audio), x: noised input audio
|
| 182 |
+
t = self.time_embed(time)
|
| 183 |
+
s_t = self.start_time_embed(start_time)
|
| 184 |
+
d_t = self.duration_time_embed(duration) if self.max_frames == 6144 else torch.zeros_like(s_t)
|
| 185 |
+
c = t + s_t + d_t
|
| 186 |
+
text_embed = self.text_embed(text, seq_len, drop_text=drop_text)
|
| 187 |
+
|
| 188 |
+
if drop_prompt:
|
| 189 |
+
style_prompt = torch.zeros_like(style_prompt)
|
| 190 |
+
|
| 191 |
+
style_embed = style_prompt # [b, 512]
|
| 192 |
+
|
| 193 |
+
x = self.input_embed(x, cond, text_embed, style_embed, c, drop_audio_cond=drop_audio_cond)
|
| 194 |
+
|
| 195 |
+
if self.long_skip_connection is not None:
|
| 196 |
+
residual = x
|
| 197 |
+
|
| 198 |
+
pos_ids = torch.arange(x.shape[1], device=x.device)
|
| 199 |
+
pos_ids = pos_ids.unsqueeze(0).repeat(x.shape[0], 1)
|
| 200 |
+
rotary_embed = self.rotary_emb(x, pos_ids)
|
| 201 |
+
|
| 202 |
+
attention_mask = torch.ones(
|
| 203 |
+
(batch, seq_len),
|
| 204 |
+
dtype=torch.bool,
|
| 205 |
+
device=x.device,
|
| 206 |
+
)
|
| 207 |
+
attention_mask = _prepare_decoder_attention_mask(
|
| 208 |
+
attention_mask,
|
| 209 |
+
(batch, seq_len),
|
| 210 |
+
x,
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
for i, block in enumerate(self.transformer_blocks):
|
| 214 |
+
x, *_ = block(x, attention_mask=attention_mask, position_embeddings=rotary_embed)
|
| 215 |
+
if i < self.depth // 2:
|
| 216 |
+
x = x + self.text_fusion_linears[i](text_embed)
|
| 217 |
+
|
| 218 |
+
if self.long_skip_connection is not None:
|
| 219 |
+
x = self.long_skip_connection(torch.cat((x, residual), dim=-1))
|
| 220 |
+
|
| 221 |
+
x = self.norm_out(x, c)
|
| 222 |
+
output = self.proj_out(x)
|
| 223 |
+
|
| 224 |
+
return output
|
code/model/modules.py
ADDED
|
@@ -0,0 +1,660 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 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 |
+
""" This implementation is adapted from github repo:
|
| 16 |
+
https://github.com/SWivid/F5-TTS.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
from typing import Optional
|
| 21 |
+
import math
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
from torch import nn
|
| 25 |
+
import torch
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
import torchaudio
|
| 28 |
+
|
| 29 |
+
from x_transformers.x_transformers import apply_rotary_pos_emb
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class FiLMLayer(nn.Module):
|
| 34 |
+
"""
|
| 35 |
+
Feature-wise Linear Modulation (FiLM) layer
|
| 36 |
+
Reference: https://arxiv.org/abs/1709.07871
|
| 37 |
+
"""
|
| 38 |
+
def __init__(self, in_channels, cond_channels):
|
| 39 |
+
|
| 40 |
+
super(FiLMLayer, self).__init__()
|
| 41 |
+
self.in_channels = in_channels
|
| 42 |
+
self.film = nn.Conv1d(cond_channels, in_channels * 2, 1)
|
| 43 |
+
|
| 44 |
+
def forward(self, x, c):
|
| 45 |
+
gamma, beta = torch.chunk(self.film(c.unsqueeze(2)), chunks=2, dim=1)
|
| 46 |
+
gamma = gamma.transpose(1, 2)
|
| 47 |
+
beta = beta.transpose(1, 2)
|
| 48 |
+
return gamma * x + beta
|
| 49 |
+
|
| 50 |
+
# raw wav to mel spec
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class MelSpec(nn.Module):
|
| 54 |
+
def __init__(
|
| 55 |
+
self,
|
| 56 |
+
filter_length=1024,
|
| 57 |
+
hop_length=256,
|
| 58 |
+
win_length=1024,
|
| 59 |
+
n_mel_channels=100,
|
| 60 |
+
target_sample_rate=24_000,
|
| 61 |
+
normalize=False,
|
| 62 |
+
power=1,
|
| 63 |
+
norm=None,
|
| 64 |
+
center=True,
|
| 65 |
+
):
|
| 66 |
+
super().__init__()
|
| 67 |
+
self.n_mel_channels = n_mel_channels
|
| 68 |
+
|
| 69 |
+
self.mel_stft = torchaudio.transforms.MelSpectrogram(
|
| 70 |
+
sample_rate=target_sample_rate,
|
| 71 |
+
n_fft=filter_length,
|
| 72 |
+
win_length=win_length,
|
| 73 |
+
hop_length=hop_length,
|
| 74 |
+
n_mels=n_mel_channels,
|
| 75 |
+
power=power,
|
| 76 |
+
center=center,
|
| 77 |
+
normalized=normalize,
|
| 78 |
+
norm=norm,
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
self.register_buffer("dummy", torch.tensor(0), persistent=False)
|
| 82 |
+
|
| 83 |
+
def forward(self, inp):
|
| 84 |
+
if len(inp.shape) == 3:
|
| 85 |
+
inp = inp.squeeze(1) # 'b 1 nw -> b nw'
|
| 86 |
+
|
| 87 |
+
assert len(inp.shape) == 2
|
| 88 |
+
|
| 89 |
+
if self.dummy.device != inp.device:
|
| 90 |
+
self.to(inp.device)
|
| 91 |
+
|
| 92 |
+
mel = self.mel_stft(inp)
|
| 93 |
+
mel = mel.clamp(min=1e-5).log()
|
| 94 |
+
return mel
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
# sinusoidal position embedding
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class SinusPositionEmbedding(nn.Module):
|
| 101 |
+
def __init__(self, dim):
|
| 102 |
+
super().__init__()
|
| 103 |
+
self.dim = dim
|
| 104 |
+
|
| 105 |
+
def forward(self, x, scale=1000):
|
| 106 |
+
device = x.device
|
| 107 |
+
half_dim = self.dim // 2
|
| 108 |
+
emb = math.log(10000) / (half_dim - 1)
|
| 109 |
+
emb = torch.exp(torch.arange(half_dim, device=device).float() * -emb)
|
| 110 |
+
emb = scale * x.unsqueeze(1) * emb.unsqueeze(0)
|
| 111 |
+
emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
|
| 112 |
+
return emb
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# convolutional position embedding
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class ConvPositionEmbedding(nn.Module):
|
| 119 |
+
def __init__(self, dim, kernel_size=31, groups=16):
|
| 120 |
+
super().__init__()
|
| 121 |
+
assert kernel_size % 2 != 0
|
| 122 |
+
self.conv1d = nn.Sequential(
|
| 123 |
+
nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2),
|
| 124 |
+
nn.Mish(),
|
| 125 |
+
nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2),
|
| 126 |
+
nn.Mish(),
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
def forward(self, x: float["b n d"], mask: bool["b n"] | None = None): # noqa: F722
|
| 130 |
+
if mask is not None:
|
| 131 |
+
mask = mask[..., None]
|
| 132 |
+
x = x.masked_fill(~mask, 0.0)
|
| 133 |
+
|
| 134 |
+
x = x.permute(0, 2, 1)
|
| 135 |
+
x = self.conv1d(x)
|
| 136 |
+
out = x.permute(0, 2, 1)
|
| 137 |
+
|
| 138 |
+
if mask is not None:
|
| 139 |
+
out = out.masked_fill(~mask, 0.0)
|
| 140 |
+
|
| 141 |
+
return out
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
# rotary positional embedding related
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0, theta_rescale_factor=1.0):
|
| 148 |
+
# proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning
|
| 149 |
+
# has some connection to NTK literature
|
| 150 |
+
# https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/
|
| 151 |
+
# https://github.com/lucidrains/rotary-embedding-torch/blob/main/rotary_embedding_torch/rotary_embedding_torch.py
|
| 152 |
+
theta *= theta_rescale_factor ** (dim / (dim - 2))
|
| 153 |
+
freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
|
| 154 |
+
t = torch.arange(end, device=freqs.device) # type: ignore
|
| 155 |
+
freqs = torch.outer(t, freqs).float() # type: ignore
|
| 156 |
+
freqs_cos = torch.cos(freqs) # real part
|
| 157 |
+
freqs_sin = torch.sin(freqs) # imaginary part
|
| 158 |
+
return torch.cat([freqs_cos, freqs_sin], dim=-1)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def get_pos_embed_indices(start, length, max_pos, scale=1.0):
|
| 162 |
+
# length = length if isinstance(length, int) else length.max()
|
| 163 |
+
scale = scale * torch.ones_like(start, dtype=torch.float32) # in case scale is a scalar
|
| 164 |
+
pos = (
|
| 165 |
+
start.unsqueeze(1)
|
| 166 |
+
+ (torch.arange(length, device=start.device, dtype=torch.float32).unsqueeze(0) * scale.unsqueeze(1)).long()
|
| 167 |
+
)
|
| 168 |
+
# avoid extra long error.
|
| 169 |
+
pos = torch.where(pos < max_pos, pos, max_pos - 1)
|
| 170 |
+
return pos
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# Global Response Normalization layer (Instance Normalization ?)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
class GRN(nn.Module):
|
| 177 |
+
def __init__(self, dim):
|
| 178 |
+
super().__init__()
|
| 179 |
+
self.gamma = nn.Parameter(torch.zeros(1, 1, dim))
|
| 180 |
+
self.beta = nn.Parameter(torch.zeros(1, 1, dim))
|
| 181 |
+
|
| 182 |
+
def forward(self, x):
|
| 183 |
+
Gx = torch.norm(x, p=2, dim=1, keepdim=True)
|
| 184 |
+
Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6)
|
| 185 |
+
return self.gamma * (x * Nx) + self.beta + x
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
# ConvNeXt-V2 Block https://github.com/facebookresearch/ConvNeXt-V2/blob/main/models/convnextv2.py
|
| 189 |
+
# ref: https://github.com/bfs18/e2_tts/blob/main/rfwave/modules.py#L108
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
class ConvNeXtV2Block(nn.Module):
|
| 193 |
+
def __init__(
|
| 194 |
+
self,
|
| 195 |
+
dim: int,
|
| 196 |
+
intermediate_dim: int,
|
| 197 |
+
dilation: int = 1,
|
| 198 |
+
):
|
| 199 |
+
super().__init__()
|
| 200 |
+
padding = (dilation * (7 - 1)) // 2
|
| 201 |
+
self.dwconv = nn.Conv1d(
|
| 202 |
+
dim, dim, kernel_size=7, padding=padding, groups=dim, dilation=dilation
|
| 203 |
+
) # depthwise conv
|
| 204 |
+
self.norm = nn.LayerNorm(dim, eps=1e-6)
|
| 205 |
+
self.pwconv1 = nn.Linear(dim, intermediate_dim) # pointwise/1x1 convs, implemented with linear layers
|
| 206 |
+
self.act = nn.GELU()
|
| 207 |
+
self.grn = GRN(intermediate_dim)
|
| 208 |
+
self.pwconv2 = nn.Linear(intermediate_dim, dim)
|
| 209 |
+
|
| 210 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 211 |
+
residual = x
|
| 212 |
+
x = x.transpose(1, 2) # b n d -> b d n
|
| 213 |
+
x = self.dwconv(x)
|
| 214 |
+
x = x.transpose(1, 2) # b d n -> b n d
|
| 215 |
+
x = self.norm(x)
|
| 216 |
+
x = self.pwconv1(x)
|
| 217 |
+
x = self.act(x)
|
| 218 |
+
x = self.grn(x)
|
| 219 |
+
x = self.pwconv2(x)
|
| 220 |
+
return residual + x
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
# AdaLayerNormZero
|
| 224 |
+
# return with modulated x for attn input, and params for later mlp modulation
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
class AdaLayerNormZero(nn.Module):
|
| 228 |
+
def __init__(self, dim):
|
| 229 |
+
super().__init__()
|
| 230 |
+
|
| 231 |
+
self.silu = nn.SiLU()
|
| 232 |
+
self.linear = nn.Linear(dim, dim * 6)
|
| 233 |
+
|
| 234 |
+
self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 235 |
+
|
| 236 |
+
def forward(self, x, emb=None):
|
| 237 |
+
emb = self.linear(self.silu(emb))
|
| 238 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = torch.chunk(emb, 6, dim=1)
|
| 239 |
+
|
| 240 |
+
x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
|
| 241 |
+
return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
# AdaLayerNormZero for final layer
|
| 245 |
+
# return only with modulated x for attn input, cuz no more mlp modulation
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class AdaLayerNormZero_Final(nn.Module):
|
| 249 |
+
def __init__(self, dim, cond_dim):
|
| 250 |
+
super().__init__()
|
| 251 |
+
|
| 252 |
+
self.silu = nn.SiLU()
|
| 253 |
+
self.linear = nn.Linear(cond_dim, dim * 2)
|
| 254 |
+
|
| 255 |
+
self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 256 |
+
|
| 257 |
+
def forward(self, x, emb):
|
| 258 |
+
emb = self.linear(self.silu(emb))
|
| 259 |
+
scale, shift = torch.chunk(emb, 2, dim=1)
|
| 260 |
+
|
| 261 |
+
x = self.norm(x) * (1 + scale)[:, None, :] + shift[:, None, :]
|
| 262 |
+
return x
|
| 263 |
+
|
| 264 |
+
|
| 265 |
+
# FeedForward
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
class FeedForward(nn.Module):
|
| 269 |
+
def __init__(self, dim, dim_out=None, mult=4, dropout=0.0, approximate: str = "none"):
|
| 270 |
+
super().__init__()
|
| 271 |
+
inner_dim = int(dim * mult)
|
| 272 |
+
dim_out = dim_out if dim_out is not None else dim
|
| 273 |
+
|
| 274 |
+
activation = nn.GELU(approximate=approximate)
|
| 275 |
+
#activation = nn.SiLU()
|
| 276 |
+
project_in = nn.Sequential(nn.Linear(dim, inner_dim), activation)
|
| 277 |
+
self.ff = nn.Sequential(project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out))
|
| 278 |
+
|
| 279 |
+
def forward(self, x):
|
| 280 |
+
return self.ff(x)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
# Attention with possible joint part
|
| 284 |
+
# modified from diffusers/src/diffusers/models/attention_processor.py
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
class Attention(nn.Module):
|
| 288 |
+
def __init__(
|
| 289 |
+
self,
|
| 290 |
+
processor: JointAttnProcessor | AttnProcessor,
|
| 291 |
+
dim: int,
|
| 292 |
+
heads: int = 8,
|
| 293 |
+
dim_head: int = 64,
|
| 294 |
+
dropout: float = 0.0,
|
| 295 |
+
context_dim: Optional[int] = None, # if not None -> joint attention
|
| 296 |
+
context_pre_only=None,
|
| 297 |
+
):
|
| 298 |
+
super().__init__()
|
| 299 |
+
|
| 300 |
+
if not hasattr(F, "scaled_dot_product_attention"):
|
| 301 |
+
raise ImportError("Attention equires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
|
| 302 |
+
|
| 303 |
+
self.processor = processor
|
| 304 |
+
|
| 305 |
+
self.dim = dim
|
| 306 |
+
self.heads = heads
|
| 307 |
+
self.inner_dim = dim_head * heads
|
| 308 |
+
self.dropout = dropout
|
| 309 |
+
|
| 310 |
+
self.context_dim = context_dim
|
| 311 |
+
self.context_pre_only = context_pre_only
|
| 312 |
+
|
| 313 |
+
self.to_q = nn.Linear(dim, self.inner_dim)
|
| 314 |
+
self.to_k = nn.Linear(dim, self.inner_dim)
|
| 315 |
+
self.to_v = nn.Linear(dim, self.inner_dim)
|
| 316 |
+
|
| 317 |
+
if self.context_dim is not None:
|
| 318 |
+
self.to_k_c = nn.Linear(context_dim, self.inner_dim)
|
| 319 |
+
self.to_v_c = nn.Linear(context_dim, self.inner_dim)
|
| 320 |
+
if self.context_pre_only is not None:
|
| 321 |
+
self.to_q_c = nn.Linear(context_dim, self.inner_dim)
|
| 322 |
+
|
| 323 |
+
self.to_out = nn.ModuleList([])
|
| 324 |
+
self.to_out.append(nn.Linear(self.inner_dim, dim))
|
| 325 |
+
self.to_out.append(nn.Dropout(dropout))
|
| 326 |
+
|
| 327 |
+
if self.context_pre_only is not None and not self.context_pre_only:
|
| 328 |
+
self.to_out_c = nn.Linear(self.inner_dim, dim)
|
| 329 |
+
|
| 330 |
+
def forward(
|
| 331 |
+
self,
|
| 332 |
+
x: float["b n d"], # noised input x # noqa: F722
|
| 333 |
+
c: float["b n d"] = None, # context c # noqa: F722
|
| 334 |
+
mask: bool["b n"] | None = None, # noqa: F722
|
| 335 |
+
rope=None, # rotary position embedding for x
|
| 336 |
+
c_rope=None, # rotary position embedding for c
|
| 337 |
+
) -> torch.Tensor:
|
| 338 |
+
if c is not None:
|
| 339 |
+
return self.processor(self, x, c=c, mask=mask, rope=rope, c_rope=c_rope)
|
| 340 |
+
else:
|
| 341 |
+
return self.processor(self, x, mask=mask, rope=rope)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
# Attention processor
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
class AttnProcessor:
|
| 348 |
+
def __init__(self):
|
| 349 |
+
pass
|
| 350 |
+
|
| 351 |
+
def __call__(
|
| 352 |
+
self,
|
| 353 |
+
attn: Attention,
|
| 354 |
+
x: float["b n d"], # noised input x # noqa: F722
|
| 355 |
+
mask: bool["b n"] | None = None, # noqa: F722
|
| 356 |
+
rope=None, # rotary position embedding
|
| 357 |
+
) -> torch.FloatTensor:
|
| 358 |
+
batch_size = x.shape[0]
|
| 359 |
+
|
| 360 |
+
# `sample` projections.
|
| 361 |
+
query = attn.to_q(x)
|
| 362 |
+
key = attn.to_k(x)
|
| 363 |
+
value = attn.to_v(x)
|
| 364 |
+
|
| 365 |
+
# apply rotary position embedding
|
| 366 |
+
if rope is not None:
|
| 367 |
+
freqs, xpos_scale = rope
|
| 368 |
+
q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
|
| 369 |
+
|
| 370 |
+
query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
|
| 371 |
+
key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
|
| 372 |
+
|
| 373 |
+
# attention
|
| 374 |
+
inner_dim = key.shape[-1]
|
| 375 |
+
head_dim = inner_dim // attn.heads
|
| 376 |
+
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
| 377 |
+
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
| 378 |
+
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
| 379 |
+
|
| 380 |
+
# mask. e.g. inference got a batch with different target durations, mask out the padding
|
| 381 |
+
if mask is not None:
|
| 382 |
+
attn_mask = mask
|
| 383 |
+
attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n'
|
| 384 |
+
attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2])
|
| 385 |
+
else:
|
| 386 |
+
attn_mask = None
|
| 387 |
+
|
| 388 |
+
x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
|
| 389 |
+
x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
|
| 390 |
+
x = x.to(query.dtype)
|
| 391 |
+
|
| 392 |
+
# linear proj
|
| 393 |
+
x = attn.to_out[0](x)
|
| 394 |
+
# dropout
|
| 395 |
+
x = attn.to_out[1](x)
|
| 396 |
+
|
| 397 |
+
if mask is not None:
|
| 398 |
+
mask = mask.unsqueeze(-1)
|
| 399 |
+
x = x.masked_fill(~mask, 0.0)
|
| 400 |
+
|
| 401 |
+
return x
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
# Joint Attention processor for MM-DiT
|
| 405 |
+
# modified from diffusers/src/diffusers/models/attention_processor.py
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
class JointAttnProcessor:
|
| 409 |
+
def __init__(self):
|
| 410 |
+
pass
|
| 411 |
+
|
| 412 |
+
def __call__(
|
| 413 |
+
self,
|
| 414 |
+
attn: Attention,
|
| 415 |
+
x: float["b n d"], # noised input x # noqa: F722
|
| 416 |
+
c: float["b nt d"] = None, # context c, here text # noqa: F722
|
| 417 |
+
mask: bool["b n"] | None = None, # noqa: F722
|
| 418 |
+
rope=None, # rotary position embedding for x
|
| 419 |
+
c_rope=None, # rotary position embedding for c
|
| 420 |
+
) -> torch.FloatTensor:
|
| 421 |
+
residual = x
|
| 422 |
+
|
| 423 |
+
batch_size = c.shape[0]
|
| 424 |
+
|
| 425 |
+
# `sample` projections.
|
| 426 |
+
query = attn.to_q(x)
|
| 427 |
+
key = attn.to_k(x)
|
| 428 |
+
value = attn.to_v(x)
|
| 429 |
+
|
| 430 |
+
# `context` projections.
|
| 431 |
+
c_query = attn.to_q_c(c)
|
| 432 |
+
c_key = attn.to_k_c(c)
|
| 433 |
+
c_value = attn.to_v_c(c)
|
| 434 |
+
|
| 435 |
+
# apply rope for context and noised input independently
|
| 436 |
+
if rope is not None:
|
| 437 |
+
freqs, xpos_scale = rope
|
| 438 |
+
q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
|
| 439 |
+
query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
|
| 440 |
+
key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
|
| 441 |
+
if c_rope is not None:
|
| 442 |
+
freqs, xpos_scale = c_rope
|
| 443 |
+
q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
|
| 444 |
+
c_query = apply_rotary_pos_emb(c_query, freqs, q_xpos_scale)
|
| 445 |
+
c_key = apply_rotary_pos_emb(c_key, freqs, k_xpos_scale)
|
| 446 |
+
|
| 447 |
+
# attention
|
| 448 |
+
query = torch.cat([query, c_query], dim=1)
|
| 449 |
+
key = torch.cat([key, c_key], dim=1)
|
| 450 |
+
value = torch.cat([value, c_value], dim=1)
|
| 451 |
+
|
| 452 |
+
inner_dim = key.shape[-1]
|
| 453 |
+
head_dim = inner_dim // attn.heads
|
| 454 |
+
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
| 455 |
+
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
| 456 |
+
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
| 457 |
+
|
| 458 |
+
# mask. e.g. inference got a batch with different target durations, mask out the padding
|
| 459 |
+
if mask is not None:
|
| 460 |
+
attn_mask = F.pad(mask, (0, c.shape[1]), value=True) # no mask for c (text)
|
| 461 |
+
attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n'
|
| 462 |
+
attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2])
|
| 463 |
+
else:
|
| 464 |
+
attn_mask = None
|
| 465 |
+
|
| 466 |
+
x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
|
| 467 |
+
x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
|
| 468 |
+
x = x.to(query.dtype)
|
| 469 |
+
|
| 470 |
+
# Split the attention outputs.
|
| 471 |
+
x, c = (
|
| 472 |
+
x[:, : residual.shape[1]],
|
| 473 |
+
x[:, residual.shape[1] :],
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
# linear proj
|
| 477 |
+
x = attn.to_out[0](x)
|
| 478 |
+
# dropout
|
| 479 |
+
x = attn.to_out[1](x)
|
| 480 |
+
if not attn.context_pre_only:
|
| 481 |
+
c = attn.to_out_c(c)
|
| 482 |
+
|
| 483 |
+
if mask is not None:
|
| 484 |
+
mask = mask.unsqueeze(-1)
|
| 485 |
+
x = x.masked_fill(~mask, 0.0)
|
| 486 |
+
# c = c.masked_fill(~mask, 0.) # no mask for c (text)
|
| 487 |
+
|
| 488 |
+
return x, c
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
# DiT Block
|
| 492 |
+
|
| 493 |
+
|
| 494 |
+
class DiTBlock(nn.Module):
|
| 495 |
+
def __init__(self, dim, heads, dim_head, ff_mult=4, dropout=0.1, use_style_prompt=False):
|
| 496 |
+
super().__init__()
|
| 497 |
+
|
| 498 |
+
self.attn_norm = AdaLayerNormZero(dim)
|
| 499 |
+
self.attn = Attention(
|
| 500 |
+
processor=AttnProcessor(),
|
| 501 |
+
dim=dim,
|
| 502 |
+
heads=heads,
|
| 503 |
+
dim_head=dim_head,
|
| 504 |
+
dropout=dropout,
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 508 |
+
self.ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
|
| 509 |
+
|
| 510 |
+
self.use_style_prompt = use_style_prompt
|
| 511 |
+
if use_style_prompt:
|
| 512 |
+
#self.film = FiLMLayer(dim, dim)
|
| 513 |
+
self.prompt_norm = AdaLayerNormZero_Final(dim)
|
| 514 |
+
|
| 515 |
+
def forward(self, x, t, c=None, mask=None, rope=None): # x: noised input, t: time embedding
|
| 516 |
+
if c is not None and self.use_style_prompt:
|
| 517 |
+
#x = self.film(x, c)
|
| 518 |
+
x = self.prompt_norm(x, c)
|
| 519 |
+
|
| 520 |
+
# pre-norm & modulation for attention input
|
| 521 |
+
norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, emb=t)
|
| 522 |
+
|
| 523 |
+
# attention
|
| 524 |
+
attn_output = self.attn(x=norm, mask=mask, rope=rope)
|
| 525 |
+
|
| 526 |
+
# process attention output for input x
|
| 527 |
+
x = x + gate_msa.unsqueeze(1) * attn_output
|
| 528 |
+
|
| 529 |
+
norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
|
| 530 |
+
ff_output = self.ff(norm)
|
| 531 |
+
x = x + gate_mlp.unsqueeze(1) * ff_output
|
| 532 |
+
|
| 533 |
+
return x
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
# MMDiT Block https://arxiv.org/abs/2403.03206
|
| 537 |
+
|
| 538 |
+
|
| 539 |
+
class MMDiTBlock(nn.Module):
|
| 540 |
+
r"""
|
| 541 |
+
modified from diffusers/src/diffusers/models/attention.py
|
| 542 |
+
|
| 543 |
+
notes.
|
| 544 |
+
_c: context related. text, cond, etc. (left part in sd3 fig2.b)
|
| 545 |
+
_x: noised input related. (right part)
|
| 546 |
+
context_pre_only: last layer only do prenorm + modulation cuz no more ffn
|
| 547 |
+
"""
|
| 548 |
+
|
| 549 |
+
def __init__(self, dim, heads, dim_head, ff_mult=4, dropout=0.1, context_pre_only=False):
|
| 550 |
+
super().__init__()
|
| 551 |
+
|
| 552 |
+
self.context_pre_only = context_pre_only
|
| 553 |
+
|
| 554 |
+
self.attn_norm_c = AdaLayerNormZero_Final(dim) if context_pre_only else AdaLayerNormZero(dim)
|
| 555 |
+
self.attn_norm_x = AdaLayerNormZero(dim)
|
| 556 |
+
self.attn = Attention(
|
| 557 |
+
processor=JointAttnProcessor(),
|
| 558 |
+
dim=dim,
|
| 559 |
+
heads=heads,
|
| 560 |
+
dim_head=dim_head,
|
| 561 |
+
dropout=dropout,
|
| 562 |
+
context_dim=dim,
|
| 563 |
+
context_pre_only=context_pre_only,
|
| 564 |
+
)
|
| 565 |
+
|
| 566 |
+
if not context_pre_only:
|
| 567 |
+
self.ff_norm_c = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 568 |
+
self.ff_c = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
|
| 569 |
+
else:
|
| 570 |
+
self.ff_norm_c = None
|
| 571 |
+
self.ff_c = None
|
| 572 |
+
self.ff_norm_x = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
|
| 573 |
+
self.ff_x = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
|
| 574 |
+
|
| 575 |
+
def forward(self, x, c, t, mask=None, rope=None, c_rope=None): # x: noised input, c: context, t: time embedding
|
| 576 |
+
# pre-norm & modulation for attention input
|
| 577 |
+
if self.context_pre_only:
|
| 578 |
+
norm_c = self.attn_norm_c(c, t)
|
| 579 |
+
else:
|
| 580 |
+
norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, emb=t)
|
| 581 |
+
norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, emb=t)
|
| 582 |
+
|
| 583 |
+
# attention
|
| 584 |
+
x_attn_output, c_attn_output = self.attn(x=norm_x, c=norm_c, mask=mask, rope=rope, c_rope=c_rope)
|
| 585 |
+
|
| 586 |
+
# process attention output for context c
|
| 587 |
+
if self.context_pre_only:
|
| 588 |
+
c = None
|
| 589 |
+
else: # if not last layer
|
| 590 |
+
c = c + c_gate_msa.unsqueeze(1) * c_attn_output
|
| 591 |
+
|
| 592 |
+
norm_c = self.ff_norm_c(c) * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None]
|
| 593 |
+
c_ff_output = self.ff_c(norm_c)
|
| 594 |
+
c = c + c_gate_mlp.unsqueeze(1) * c_ff_output
|
| 595 |
+
|
| 596 |
+
# process attention output for input x
|
| 597 |
+
x = x + x_gate_msa.unsqueeze(1) * x_attn_output
|
| 598 |
+
|
| 599 |
+
norm_x = self.ff_norm_x(x) * (1 + x_scale_mlp[:, None]) + x_shift_mlp[:, None]
|
| 600 |
+
x_ff_output = self.ff_x(norm_x)
|
| 601 |
+
x = x + x_gate_mlp.unsqueeze(1) * x_ff_output
|
| 602 |
+
|
| 603 |
+
return c, x
|
| 604 |
+
|
| 605 |
+
|
| 606 |
+
# time step conditioning embedding
|
| 607 |
+
|
| 608 |
+
|
| 609 |
+
class TimestepEmbedding(nn.Module):
|
| 610 |
+
def __init__(self, dim, freq_embed_dim=256):
|
| 611 |
+
super().__init__()
|
| 612 |
+
self.time_embed = SinusPositionEmbedding(freq_embed_dim)
|
| 613 |
+
self.time_mlp = nn.Sequential(nn.Linear(freq_embed_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
|
| 614 |
+
|
| 615 |
+
def forward(self, timestep: float["b"]): # noqa: F821
|
| 616 |
+
time_hidden = self.time_embed(timestep)
|
| 617 |
+
time_hidden = time_hidden.to(timestep.dtype)
|
| 618 |
+
time = self.time_mlp(time_hidden) # b d
|
| 619 |
+
return time
|
| 620 |
+
|
| 621 |
+
|
| 622 |
+
# attention mask realated
|
| 623 |
+
|
| 624 |
+
|
| 625 |
+
def _prepare_decoder_attention_mask(attention_mask, input_shape, inputs_embeds):
|
| 626 |
+
# create noncausal mask
|
| 627 |
+
# [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
|
| 628 |
+
combined_attention_mask = None
|
| 629 |
+
|
| 630 |
+
def _expand_mask(
|
| 631 |
+
mask: torch.Tensor, dtype: torch.dtype, tgt_len: int = None
|
| 632 |
+
):
|
| 633 |
+
"""
|
| 634 |
+
Expands attention_mask from `[bsz, seq_len]` to `[bsz, 1, tgt_seq_len, src_seq_len]`.
|
| 635 |
+
"""
|
| 636 |
+
bsz, src_len = mask.size()
|
| 637 |
+
tgt_len = tgt_len if tgt_len is not None else src_len
|
| 638 |
+
|
| 639 |
+
expanded_mask = (
|
| 640 |
+
mask[:, None, None, :].expand(bsz, 1, tgt_len, src_len).to(dtype)
|
| 641 |
+
)
|
| 642 |
+
|
| 643 |
+
inverted_mask = 1.0 - expanded_mask
|
| 644 |
+
|
| 645 |
+
return inverted_mask.masked_fill(
|
| 646 |
+
inverted_mask.to(torch.bool), torch.finfo(dtype).min
|
| 647 |
+
)
|
| 648 |
+
|
| 649 |
+
if attention_mask is not None:
|
| 650 |
+
# [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
|
| 651 |
+
expanded_attn_mask = _expand_mask(
|
| 652 |
+
attention_mask, inputs_embeds.dtype, tgt_len=input_shape[-1]
|
| 653 |
+
).to(inputs_embeds.device)
|
| 654 |
+
combined_attention_mask = (
|
| 655 |
+
expanded_attn_mask
|
| 656 |
+
if combined_attention_mask is None
|
| 657 |
+
else expanded_attn_mask + combined_attention_mask
|
| 658 |
+
)
|
| 659 |
+
|
| 660 |
+
return combined_attention_mask
|
code/model/trainer.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 2 |
+
# 2025 Ziqian Ning (ningziqian@mail.nwpu.edu.cn)
|
| 3 |
+
# 2025 Huakang Chen (huakang@mail.nwpu.edu.cn)
|
| 4 |
+
#
|
| 5 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 6 |
+
# you may not use this file except in compliance with the License.
|
| 7 |
+
# You may obtain a copy of the License at
|
| 8 |
+
|
| 9 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 10 |
+
|
| 11 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 12 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 13 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 14 |
+
# See the License for the specific language governing permissions and
|
| 15 |
+
# limitations under the License.
|
| 16 |
+
|
| 17 |
+
""" This implementation is adapted from github repo:
|
| 18 |
+
https://github.com/SWivid/F5-TTS.
|
| 19 |
+
"""
|
| 20 |
+
|
| 21 |
+
from __future__ import annotations
|
| 22 |
+
|
| 23 |
+
import os
|
| 24 |
+
import gc
|
| 25 |
+
from tqdm import tqdm
|
| 26 |
+
import wandb
|
| 27 |
+
|
| 28 |
+
import torch
|
| 29 |
+
from torch.optim import AdamW
|
| 30 |
+
from torch.optim.lr_scheduler import LinearLR, SequentialLR, ConstantLR
|
| 31 |
+
|
| 32 |
+
from accelerate import Accelerator
|
| 33 |
+
from accelerate.utils import DistributedDataParallelKwargs
|
| 34 |
+
from dataset.dataset import DiffusionDataset
|
| 35 |
+
|
| 36 |
+
from torch.utils.data import DataLoader
|
| 37 |
+
|
| 38 |
+
from ema_pytorch import EMA
|
| 39 |
+
|
| 40 |
+
from model import CFM
|
| 41 |
+
from model.utils import exists, default
|
| 42 |
+
|
| 43 |
+
class Trainer:
|
| 44 |
+
def __init__(
|
| 45 |
+
self,
|
| 46 |
+
model: CFM,
|
| 47 |
+
args,
|
| 48 |
+
epochs,
|
| 49 |
+
learning_rate,
|
| 50 |
+
num_warmup_updates=20000,
|
| 51 |
+
save_per_updates=1000,
|
| 52 |
+
checkpoint_path=None,
|
| 53 |
+
batch_size=32,
|
| 54 |
+
batch_size_type: str = "sample",
|
| 55 |
+
max_samples=32,
|
| 56 |
+
grad_accumulation_steps=1,
|
| 57 |
+
max_grad_norm=1.0,
|
| 58 |
+
noise_scheduler: str | None = None,
|
| 59 |
+
duration_predictor: torch.nn.Module | None = None,
|
| 60 |
+
wandb_project="test_e2-tts",
|
| 61 |
+
wandb_run_name="test_run",
|
| 62 |
+
wandb_resume_id: str = None,
|
| 63 |
+
last_per_steps=None,
|
| 64 |
+
accelerate_kwargs: dict = dict(),
|
| 65 |
+
ema_kwargs: dict = dict(),
|
| 66 |
+
bnb_optimizer: bool = False,
|
| 67 |
+
reset_lr: bool = False,
|
| 68 |
+
use_style_prompt: bool = False,
|
| 69 |
+
grad_ckpt: bool = False
|
| 70 |
+
):
|
| 71 |
+
self.args = args
|
| 72 |
+
|
| 73 |
+
ddp_kwargs = DistributedDataParallelKwargs(find_unused_parameters=False, )
|
| 74 |
+
|
| 75 |
+
logger = "wandb" if wandb.api.api_key else None
|
| 76 |
+
|
| 77 |
+
self.accelerator = Accelerator(
|
| 78 |
+
log_with=logger,
|
| 79 |
+
kwargs_handlers=[ddp_kwargs],
|
| 80 |
+
gradient_accumulation_steps=grad_accumulation_steps,
|
| 81 |
+
**accelerate_kwargs,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
if logger == "wandb":
|
| 85 |
+
if exists(wandb_resume_id):
|
| 86 |
+
init_kwargs = {"wandb": {"resume": "allow", "name": wandb_run_name, "id": wandb_resume_id}}
|
| 87 |
+
else:
|
| 88 |
+
init_kwargs = {"wandb": {"resume": "allow", "name": wandb_run_name}}
|
| 89 |
+
self.accelerator.init_trackers(
|
| 90 |
+
project_name=wandb_project,
|
| 91 |
+
init_kwargs=init_kwargs,
|
| 92 |
+
config={
|
| 93 |
+
"epochs": epochs,
|
| 94 |
+
"learning_rate": learning_rate,
|
| 95 |
+
"num_warmup_updates": num_warmup_updates,
|
| 96 |
+
"batch_size": batch_size,
|
| 97 |
+
"batch_size_type": batch_size_type,
|
| 98 |
+
"max_samples": max_samples,
|
| 99 |
+
"grad_accumulation_steps": grad_accumulation_steps,
|
| 100 |
+
"max_grad_norm": max_grad_norm,
|
| 101 |
+
"gpus": self.accelerator.num_processes,
|
| 102 |
+
"noise_scheduler": noise_scheduler,
|
| 103 |
+
},
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
self.precision = self.accelerator.state.mixed_precision
|
| 107 |
+
self.precision = self.precision.replace("no", "fp32")
|
| 108 |
+
|
| 109 |
+
self.model = model
|
| 110 |
+
|
| 111 |
+
if self.is_main:
|
| 112 |
+
self.ema_model = EMA(model, include_online_model=False, **ema_kwargs)
|
| 113 |
+
|
| 114 |
+
self.ema_model.to(self.accelerator.device)
|
| 115 |
+
if self.accelerator.state.distributed_type in ["DEEPSPEED", "FSDP"]:
|
| 116 |
+
self.ema_model.half()
|
| 117 |
+
|
| 118 |
+
self.epochs = epochs
|
| 119 |
+
self.num_warmup_updates = num_warmup_updates
|
| 120 |
+
self.save_per_updates = save_per_updates
|
| 121 |
+
self.last_per_steps = default(last_per_steps, save_per_updates * grad_accumulation_steps)
|
| 122 |
+
self.checkpoint_path = default(checkpoint_path, "ckpts/test_e2-tts")
|
| 123 |
+
|
| 124 |
+
self.max_samples = max_samples
|
| 125 |
+
self.grad_accumulation_steps = grad_accumulation_steps
|
| 126 |
+
self.max_grad_norm = max_grad_norm
|
| 127 |
+
|
| 128 |
+
self.noise_scheduler = noise_scheduler
|
| 129 |
+
|
| 130 |
+
self.duration_predictor = duration_predictor
|
| 131 |
+
|
| 132 |
+
self.reset_lr = reset_lr
|
| 133 |
+
|
| 134 |
+
self.use_style_prompt = use_style_prompt
|
| 135 |
+
|
| 136 |
+
self.grad_ckpt = grad_ckpt
|
| 137 |
+
|
| 138 |
+
if bnb_optimizer:
|
| 139 |
+
import bitsandbytes as bnb
|
| 140 |
+
|
| 141 |
+
self.optimizer = bnb.optim.AdamW8bit(model.parameters(), lr=learning_rate)
|
| 142 |
+
else:
|
| 143 |
+
self.optimizer = AdamW(model.parameters(), lr=learning_rate)
|
| 144 |
+
|
| 145 |
+
if self.accelerator.state.distributed_type == "DEEPSPEED":
|
| 146 |
+
self.accelerator.state.deepspeed_plugin.deepspeed_config['train_micro_batch_size_per_gpu'] = batch_size
|
| 147 |
+
|
| 148 |
+
self.get_dataloader()
|
| 149 |
+
self.get_scheduler()
|
| 150 |
+
|
| 151 |
+
self.model, self.optimizer, self.scheduler, self.train_dataloader = self.accelerator.prepare(self.model, self.optimizer, self.scheduler, self.train_dataloader)
|
| 152 |
+
|
| 153 |
+
def get_scheduler(self):
|
| 154 |
+
warmup_steps = (
|
| 155 |
+
self.num_warmup_updates * self.accelerator.num_processes
|
| 156 |
+
) # consider a fixed warmup steps while using accelerate multi-gpu ddp
|
| 157 |
+
total_steps = len(self.train_dataloader) * self.epochs / self.grad_accumulation_steps
|
| 158 |
+
decay_steps = total_steps - warmup_steps
|
| 159 |
+
warmup_scheduler = LinearLR(self.optimizer, start_factor=1e-8, end_factor=1.0, total_iters=warmup_steps)
|
| 160 |
+
decay_scheduler = LinearLR(self.optimizer, start_factor=1.0, end_factor=1e-8, total_iters=decay_steps)
|
| 161 |
+
self.scheduler = SequentialLR(
|
| 162 |
+
self.optimizer, schedulers=[warmup_scheduler, decay_scheduler], milestones=[warmup_steps]
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
def get_constant_scheduler(self):
|
| 166 |
+
total_steps = len(self.train_dataloader) * self.epochs / self.grad_accumulation_steps
|
| 167 |
+
self.scheduler = ConstantLR(self.optimizer, factor=1, total_iters=total_steps)
|
| 168 |
+
|
| 169 |
+
def get_dataloader(self):
|
| 170 |
+
print(self.args)
|
| 171 |
+
dd = DiffusionDataset(self.args.file_path, self.args.max_frames, self.args.min_frames, self.args.sampling_rate, self.args.downsample_rate, self.precision)
|
| 172 |
+
self.train_dataloader = DataLoader(
|
| 173 |
+
dataset=dd,
|
| 174 |
+
batch_size=self.args.batch_size,
|
| 175 |
+
shuffle=True,
|
| 176 |
+
num_workers=4,
|
| 177 |
+
pin_memory=True,
|
| 178 |
+
collate_fn=dd.custom_collate_fn,
|
| 179 |
+
persistent_workers=True
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
@property
|
| 184 |
+
def is_main(self):
|
| 185 |
+
return self.accelerator.is_main_process
|
| 186 |
+
|
| 187 |
+
def save_checkpoint(self, step, last=False):
|
| 188 |
+
self.accelerator.wait_for_everyone()
|
| 189 |
+
if self.is_main:
|
| 190 |
+
checkpoint = dict(
|
| 191 |
+
model_state_dict=self.accelerator.unwrap_model(self.model).state_dict(),
|
| 192 |
+
optimizer_state_dict=self.accelerator.unwrap_model(self.optimizer).state_dict(),
|
| 193 |
+
ema_model_state_dict=self.ema_model.state_dict(),
|
| 194 |
+
scheduler_state_dict=self.scheduler.state_dict(),
|
| 195 |
+
step=step,
|
| 196 |
+
)
|
| 197 |
+
if not os.path.exists(self.checkpoint_path):
|
| 198 |
+
os.makedirs(self.checkpoint_path)
|
| 199 |
+
if last:
|
| 200 |
+
self.accelerator.save(checkpoint, f"{self.checkpoint_path}/model_last.pt")
|
| 201 |
+
print(f"Saved last checkpoint at step {step}")
|
| 202 |
+
else:
|
| 203 |
+
self.accelerator.save(checkpoint, f"{self.checkpoint_path}/model_{step}.pt")
|
| 204 |
+
|
| 205 |
+
def load_checkpoint(self):
|
| 206 |
+
if (
|
| 207 |
+
not exists(self.checkpoint_path)
|
| 208 |
+
or not os.path.exists(self.checkpoint_path)
|
| 209 |
+
or not os.listdir(self.checkpoint_path)
|
| 210 |
+
):
|
| 211 |
+
return 0
|
| 212 |
+
|
| 213 |
+
self.accelerator.wait_for_everyone()
|
| 214 |
+
if "model_last.pt" in os.listdir(self.checkpoint_path):
|
| 215 |
+
latest_checkpoint = "model_last.pt"
|
| 216 |
+
else:
|
| 217 |
+
latest_checkpoint = sorted(
|
| 218 |
+
[f for f in os.listdir(self.checkpoint_path) if f.endswith(".pt")],
|
| 219 |
+
key=lambda x: int("".join(filter(str.isdigit, x))),
|
| 220 |
+
)[-1]
|
| 221 |
+
|
| 222 |
+
checkpoint = torch.load(f"{self.checkpoint_path}/{latest_checkpoint}", map_location="cpu")
|
| 223 |
+
|
| 224 |
+
if self.is_main:
|
| 225 |
+
ema_dict = self.ema_model.state_dict()
|
| 226 |
+
ema_checkpoint_dict = checkpoint["ema_model_state_dict"]
|
| 227 |
+
|
| 228 |
+
filtered_ema_dict = {
|
| 229 |
+
k: v for k, v in ema_checkpoint_dict.items()
|
| 230 |
+
if k in ema_dict and ema_dict[k].shape == v.shape
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
self.ema_model.load_state_dict(filtered_ema_dict, strict=False)
|
| 234 |
+
|
| 235 |
+
model_dict = self.accelerator.unwrap_model(self.model).state_dict()
|
| 236 |
+
checkpoint_model_dict = checkpoint["model_state_dict"]
|
| 237 |
+
|
| 238 |
+
filtered_model_dict = {
|
| 239 |
+
k: v for k, v in checkpoint_model_dict.items()
|
| 240 |
+
if k in model_dict and model_dict[k].shape == v.shape
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
self.accelerator.unwrap_model(self.model).load_state_dict(filtered_model_dict, strict=False)
|
| 244 |
+
|
| 245 |
+
if "step" in checkpoint:
|
| 246 |
+
if self.scheduler and not self.reset_lr:
|
| 247 |
+
self.scheduler.load_state_dict(checkpoint["scheduler_state_dict"])
|
| 248 |
+
step = checkpoint["step"]
|
| 249 |
+
else:
|
| 250 |
+
step = 0
|
| 251 |
+
|
| 252 |
+
del checkpoint
|
| 253 |
+
gc.collect()
|
| 254 |
+
print("Checkpoint loaded at step", step)
|
| 255 |
+
return step
|
| 256 |
+
|
| 257 |
+
def train(self, resumable_with_seed: int = None):
|
| 258 |
+
train_dataloader = self.train_dataloader
|
| 259 |
+
|
| 260 |
+
start_step = self.load_checkpoint()
|
| 261 |
+
global_step = start_step
|
| 262 |
+
|
| 263 |
+
if resumable_with_seed > 0:
|
| 264 |
+
orig_epoch_step = len(train_dataloader)
|
| 265 |
+
skipped_epoch = int(start_step // orig_epoch_step)
|
| 266 |
+
skipped_batch = start_step % orig_epoch_step
|
| 267 |
+
skipped_dataloader = self.accelerator.skip_first_batches(train_dataloader, num_batches=skipped_batch)
|
| 268 |
+
else:
|
| 269 |
+
skipped_epoch = 0
|
| 270 |
+
|
| 271 |
+
for epoch in range(skipped_epoch, self.epochs):
|
| 272 |
+
self.model.train()
|
| 273 |
+
if resumable_with_seed > 0 and epoch == skipped_epoch:
|
| 274 |
+
progress_bar = tqdm(
|
| 275 |
+
skipped_dataloader,
|
| 276 |
+
desc=f"Epoch {epoch+1}/{self.epochs}",
|
| 277 |
+
unit="step",
|
| 278 |
+
disable=not self.accelerator.is_local_main_process,
|
| 279 |
+
initial=skipped_batch,
|
| 280 |
+
total=orig_epoch_step,
|
| 281 |
+
smoothing=0.15
|
| 282 |
+
)
|
| 283 |
+
else:
|
| 284 |
+
progress_bar = tqdm(
|
| 285 |
+
train_dataloader,
|
| 286 |
+
desc=f"Epoch {epoch+1}/{self.epochs}",
|
| 287 |
+
unit="step",
|
| 288 |
+
disable=not self.accelerator.is_local_main_process,
|
| 289 |
+
smoothing=0.15
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
for batch in progress_bar:
|
| 293 |
+
with self.accelerator.accumulate(self.model):
|
| 294 |
+
text_inputs = batch["lrc"]
|
| 295 |
+
mel_spec = batch["latent"].permute(0, 2, 1)
|
| 296 |
+
mel_lengths = batch["latent_lengths"]
|
| 297 |
+
style_prompt = batch["prompt"]
|
| 298 |
+
style_prompt_lens = batch["prompt_lengths"]
|
| 299 |
+
start_time = batch["start_time"]
|
| 300 |
+
|
| 301 |
+
loss, cond, pred = self.model(
|
| 302 |
+
mel_spec, text=text_inputs, lens=mel_lengths, noise_scheduler=self.noise_scheduler,
|
| 303 |
+
style_prompt=style_prompt,
|
| 304 |
+
style_prompt_lens=style_prompt_lens,
|
| 305 |
+
grad_ckpt=self.grad_ckpt, start_time=start_time
|
| 306 |
+
)
|
| 307 |
+
self.accelerator.backward(loss)
|
| 308 |
+
|
| 309 |
+
if self.max_grad_norm > 0 and self.accelerator.sync_gradients:
|
| 310 |
+
self.accelerator.clip_grad_norm_(self.model.parameters(), self.max_grad_norm)
|
| 311 |
+
|
| 312 |
+
self.optimizer.step()
|
| 313 |
+
self.scheduler.step()
|
| 314 |
+
self.optimizer.zero_grad()
|
| 315 |
+
|
| 316 |
+
if self.is_main:
|
| 317 |
+
self.ema_model.update()
|
| 318 |
+
|
| 319 |
+
global_step += 1
|
| 320 |
+
|
| 321 |
+
if self.accelerator.is_local_main_process:
|
| 322 |
+
self.accelerator.log({"loss": loss.item(), "lr": self.scheduler.get_last_lr()[0]}, step=global_step)
|
| 323 |
+
|
| 324 |
+
progress_bar.set_postfix(step=str(global_step), loss=loss.item())
|
| 325 |
+
|
| 326 |
+
if global_step % (self.save_per_updates * self.grad_accumulation_steps) == 0:
|
| 327 |
+
self.save_checkpoint(global_step)
|
| 328 |
+
|
| 329 |
+
if global_step % self.last_per_steps == 0:
|
| 330 |
+
self.save_checkpoint(global_step, last=True)
|
| 331 |
+
|
| 332 |
+
self.save_checkpoint(global_step, last=True)
|
| 333 |
+
|
| 334 |
+
self.accelerator.end_training()
|
code/model/utils.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2025 ASLP-LAB
|
| 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 |
+
""" This implementation is adapted from github repo:
|
| 16 |
+
https://github.com/SWivid/F5-TTS.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import os
|
| 22 |
+
import random
|
| 23 |
+
from collections import defaultdict
|
| 24 |
+
from importlib.resources import files
|
| 25 |
+
|
| 26 |
+
import torch
|
| 27 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# seed everything
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def seed_everything(seed=0):
|
| 34 |
+
random.seed(seed)
|
| 35 |
+
os.environ["PYTHONHASHSEED"] = str(seed)
|
| 36 |
+
torch.manual_seed(seed)
|
| 37 |
+
torch.cuda.manual_seed(seed)
|
| 38 |
+
torch.cuda.manual_seed_all(seed)
|
| 39 |
+
torch.backends.cudnn.deterministic = True
|
| 40 |
+
torch.backends.cudnn.benchmark = False
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# helpers
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def exists(v):
|
| 47 |
+
return v is not None
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def default(v, d):
|
| 51 |
+
return v if exists(v) else d
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# tensor helpers
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def lens_to_mask(t: int["b"], length: int | None = None) -> bool["b n"]: # noqa: F722 F821
|
| 58 |
+
if not exists(length):
|
| 59 |
+
length = t.amax()
|
| 60 |
+
|
| 61 |
+
seq = torch.arange(length, device=t.device)
|
| 62 |
+
return seq[None, :] < t[:, None]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def mask_from_start_end_indices(seq_len: int["b"], start: int["b"], end: int["b"], max_frames): # noqa: F722 F821
|
| 66 |
+
max_seq_len = max_frames
|
| 67 |
+
seq = torch.arange(max_seq_len, device=start.device).long()
|
| 68 |
+
start_mask = seq[None, :] >= start[:, None]
|
| 69 |
+
end_mask = seq[None, :] < end[:, None]
|
| 70 |
+
return start_mask & end_mask
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def mask_from_frac_lengths(seq_len: int["b"], frac_lengths: float["b"], max_frames): # noqa: F722 F821
|
| 74 |
+
lengths = (frac_lengths * seq_len).long()
|
| 75 |
+
max_start = seq_len - lengths
|
| 76 |
+
|
| 77 |
+
rand = torch.rand_like(frac_lengths)
|
| 78 |
+
start = (max_start * rand).long().clamp(min=0)
|
| 79 |
+
end = start + lengths
|
| 80 |
+
|
| 81 |
+
return mask_from_start_end_indices(seq_len, start, end, max_frames)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def maybe_masked_mean(t: float["b n d"], mask: bool["b n"] = None) -> float["b d"]: # noqa: F722
|
| 85 |
+
if not exists(mask):
|
| 86 |
+
return t.mean(dim=1)
|
| 87 |
+
|
| 88 |
+
t = torch.where(mask[:, :, None], t, torch.tensor(0.0, device=t.device))
|
| 89 |
+
num = t.sum(dim=1)
|
| 90 |
+
den = mask.float().sum(dim=1)
|
| 91 |
+
|
| 92 |
+
return num / den.clamp(min=1.0)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# simple utf-8 tokenizer, since paper went character based
|
| 96 |
+
def list_str_to_tensor(text: list[str], padding_value=-1) -> int["b nt"]: # noqa: F722
|
| 97 |
+
list_tensors = [torch.tensor([*bytes(t, "UTF-8")]) for t in text] # ByT5 style
|
| 98 |
+
text = pad_sequence(list_tensors, padding_value=padding_value, batch_first=True)
|
| 99 |
+
return text
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# char tokenizer, based on custom dataset's extracted .txt file
|
| 103 |
+
def list_str_to_idx(
|
| 104 |
+
text: list[str] | list[list[str]],
|
| 105 |
+
vocab_char_map: dict[str, int], # {char: idx}
|
| 106 |
+
padding_value=-1,
|
| 107 |
+
) -> int["b nt"]: # noqa: F722
|
| 108 |
+
list_idx_tensors = [torch.tensor([vocab_char_map.get(c, 0) for c in t]) for t in text] # pinyin or char style
|
| 109 |
+
text = pad_sequence(list_idx_tensors, padding_value=padding_value, batch_first=True)
|
| 110 |
+
return text
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
# Get tokenizer
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def get_tokenizer(dataset_name, tokenizer: str = "pinyin"):
|
| 117 |
+
"""
|
| 118 |
+
tokenizer - "pinyin" do g2p for only chinese characters, need .txt vocab_file
|
| 119 |
+
- "char" for char-wise tokenizer, need .txt vocab_file
|
| 120 |
+
- "byte" for utf-8 tokenizer
|
| 121 |
+
- "custom" if you're directly passing in a path to the vocab.txt you want to use
|
| 122 |
+
vocab_size - if use "pinyin", all available pinyin types, common alphabets (also those with accent) and symbols
|
| 123 |
+
- if use "char", derived from unfiltered character & symbol counts of custom dataset
|
| 124 |
+
- if use "byte", set to 256 (unicode byte range)
|
| 125 |
+
"""
|
| 126 |
+
if tokenizer in ["pinyin", "char"]:
|
| 127 |
+
tokenizer_path = os.path.join(files("diffrhythm").joinpath("../../data"), f"{dataset_name}_{tokenizer}/vocab.txt")
|
| 128 |
+
with open(tokenizer_path, "r", encoding="utf-8") as f:
|
| 129 |
+
vocab_char_map = {}
|
| 130 |
+
for i, char in enumerate(f):
|
| 131 |
+
vocab_char_map[char[:-1]] = i
|
| 132 |
+
vocab_size = len(vocab_char_map)
|
| 133 |
+
assert vocab_char_map[" "] == 0, "make sure space is of idx 0 in vocab.txt, cuz 0 is used for unknown char"
|
| 134 |
+
|
| 135 |
+
elif tokenizer == "byte":
|
| 136 |
+
vocab_char_map = None
|
| 137 |
+
vocab_size = 256
|
| 138 |
+
|
| 139 |
+
elif tokenizer == "custom":
|
| 140 |
+
with open(dataset_name, "r", encoding="utf-8") as f:
|
| 141 |
+
vocab_char_map = {}
|
| 142 |
+
for i, char in enumerate(f):
|
| 143 |
+
vocab_char_map[char[:-1]] = i
|
| 144 |
+
vocab_size = len(vocab_char_map)
|
| 145 |
+
|
| 146 |
+
return vocab_char_map, vocab_size
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# convert char to pinyin
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def convert_char_to_pinyin(text_list, polyphone=True):
|
| 153 |
+
final_text_list = []
|
| 154 |
+
god_knows_why_en_testset_contains_zh_quote = str.maketrans(
|
| 155 |
+
{"“": '"', "”": '"', "‘": "'", "’": "'"}
|
| 156 |
+
) # in case librispeech (orig no-pc) test-clean
|
| 157 |
+
custom_trans = str.maketrans({";": ","}) # add custom trans here, to address oov
|
| 158 |
+
for text in text_list:
|
| 159 |
+
char_list = []
|
| 160 |
+
text = text.translate(god_knows_why_en_testset_contains_zh_quote)
|
| 161 |
+
text = text.translate(custom_trans)
|
| 162 |
+
for seg in jieba.cut(text):
|
| 163 |
+
seg_byte_len = len(bytes(seg, "UTF-8"))
|
| 164 |
+
if seg_byte_len == len(seg): # if pure alphabets and symbols
|
| 165 |
+
if char_list and seg_byte_len > 1 and char_list[-1] not in " :'\"":
|
| 166 |
+
char_list.append(" ")
|
| 167 |
+
char_list.extend(seg)
|
| 168 |
+
elif polyphone and seg_byte_len == 3 * len(seg): # if pure chinese characters
|
| 169 |
+
seg = lazy_pinyin(seg, style=Style.TONE3, tone_sandhi=True)
|
| 170 |
+
for c in seg:
|
| 171 |
+
if c not in "。,、;:?!《》【】—…":
|
| 172 |
+
char_list.append(" ")
|
| 173 |
+
char_list.append(c)
|
| 174 |
+
else: # if mixed chinese characters, alphabets and symbols
|
| 175 |
+
for c in seg:
|
| 176 |
+
if ord(c) < 256:
|
| 177 |
+
char_list.extend(c)
|
| 178 |
+
else:
|
| 179 |
+
if c not in "。,、;:?!《》【】—…":
|
| 180 |
+
char_list.append(" ")
|
| 181 |
+
char_list.extend(lazy_pinyin(c, style=Style.TONE3, tone_sandhi=True))
|
| 182 |
+
else: # if is zh punc
|
| 183 |
+
char_list.append(c)
|
| 184 |
+
final_text_list.append(char_list)
|
| 185 |
+
|
| 186 |
+
return final_text_list
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# filter func for dirty data with many repetitions
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def repetition_found(text, length=2, tolerance=10):
|
| 193 |
+
pattern_count = defaultdict(int)
|
| 194 |
+
for i in range(len(text) - length + 1):
|
| 195 |
+
pattern = text[i : i + length]
|
| 196 |
+
pattern_count[pattern] += 1
|
| 197 |
+
for pattern, count in pattern_count.items():
|
| 198 |
+
if count > tolerance:
|
| 199 |
+
return True
|
| 200 |
+
return False
|
code/pyproject.toml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=68"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "tt-diffrhythm"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "DiffRhythm 1.2 Full on one Tenstorrent Blackhole p100a"
|
| 9 |
+
readme = "PYTHON.md"
|
| 10 |
+
requires-python = ">=3.10"
|
| 11 |
+
license = { text = "Apache-2.0" }
|
| 12 |
+
# ttnn / tt-metal is not on PyPI: it comes from the tt-metal environment this runs in.
|
| 13 |
+
dependencies = [
|
| 14 |
+
"torch",
|
| 15 |
+
"numpy",
|
| 16 |
+
"safetensors",
|
| 17 |
+
"huggingface_hub",
|
| 18 |
+
]
|
| 19 |
+
|
| 20 |
+
[project.optional-dependencies]
|
| 21 |
+
test = ["pytest"]
|
| 22 |
+
|
| 23 |
+
[tool.setuptools.packages.find]
|
| 24 |
+
include = ["tt_diffrhythm*"]
|
code/tt_diffrhythm/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""DiffRhythm 1.2 Full on one Tenstorrent Blackhole p100a.
|
| 3 |
+
|
| 4 |
+
The CFM transformer and the stable-audio VAE decoder run on the card in TTNN. The style
|
| 5 |
+
encoder, the sampler and the post-processing stay on the CPU inside DiffRhythm's own
|
| 6 |
+
environment. `LocalWorker` runs both in one process when one environment satisfies DiffRhythm
|
| 7 |
+
and ttnn at once; `TTWorker` keeps them apart, talking to the card through a worker process over
|
| 8 |
+
a Unix socket, when it does not.
|
| 9 |
+
"""
|
| 10 |
+
from .common import close_device, dram_stats, open_device
|
| 11 |
+
from .diffrhythm_dit import DiffRhythmDiT, DRPrecision
|
| 12 |
+
from .local import LocalWorker
|
| 13 |
+
from .oobleck_vae import OobleckTT
|
| 14 |
+
from .remote import RemoteDiffRhythmDiT, RemoteVae, TTWorker
|
| 15 |
+
|
| 16 |
+
__all__ = ["DiffRhythmDiT", "DRPrecision", "OobleckTT", "TTWorker", "LocalWorker", "RemoteDiffRhythmDiT",
|
| 17 |
+
"RemoteVae", "open_device", "close_device", "dram_stats"]
|
| 18 |
+
__version__ = "0.1.0"
|
code/tt_diffrhythm/common.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Device and host helpers shared with the ACE-Step port.
|
| 3 |
+
|
| 4 |
+
Extracted verbatim from `tt-acestep` (Apache-2.0), where the same code opens the card, reports
|
| 5 |
+
DRAM, moves tensors, and prepares checkpoint tensors for ttnn: the matmul layout, the
|
| 6 |
+
adjacent-pair head permutation ttnn's rotary kernel expects, and the tiled rotation matrix.
|
| 7 |
+
|
| 8 |
+
The two ports also share `worker.py`, `remote.py`, `wire.py` and `oobleck_vae.py`; this module
|
| 9 |
+
exists so that this repository stands alone.
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from dataclasses import dataclass
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import ttnn
|
| 17 |
+
|
| 18 |
+
MEM = ttnn.DRAM_MEMORY_CONFIG
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def open_device(l1_small_size: int = 98304): # convs (VAE) keep their config tensors in L1_SMALL
|
| 22 |
+
return ttnn.open_mesh_device(mesh_shape=ttnn.MeshShape(1, 1),
|
| 23 |
+
dispatch_core_config=ttnn.DispatchCoreConfig(ttnn.DispatchCoreType.WORKER),
|
| 24 |
+
l1_small_size=l1_small_size)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def close_device(dev):
|
| 28 |
+
ttnn.close_mesh_device(dev)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def dram_stats(dev) -> dict:
|
| 32 |
+
try:
|
| 33 |
+
s = ttnn.get_memory_view(dev, ttnn.BufferType.DRAM)
|
| 34 |
+
return {"allocated_bytes": s.total_bytes_allocated_per_bank * s.num_banks,
|
| 35 |
+
"free_bytes": s.total_bytes_free_per_bank * s.num_banks}
|
| 36 |
+
except Exception: # pragma: no cover - diagnostics only
|
| 37 |
+
return {}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass
|
| 41 |
+
class Precision:
|
| 42 |
+
weight_dtype: ttnn.DataType = ttnn.bfloat16
|
| 43 |
+
mm_fidelity: ttnn.MathFidelity = ttnn.MathFidelity.HiFi2
|
| 44 |
+
sdpa_fidelity: ttnn.MathFidelity = ttnn.MathFidelity.HiFi4
|
| 45 |
+
sdpa_fp32_acc: bool = True
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _dev(dev, t: torch.Tensor, dtype=ttnn.bfloat16):
|
| 49 |
+
return ttnn.from_torch(t.contiguous(), dtype=dtype, layout=ttnn.TILE_LAYOUT, device=dev, memory_config=MEM)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _row(dev, v: torch.Tensor):
|
| 53 |
+
"""[D] -> [1, 1, 1, D] bf16 row (norm gamma, shift, gate, bias)."""
|
| 54 |
+
return _dev(dev, v.to(torch.bfloat16).reshape(1, 1, 1, -1))
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
TILE = 32
|
| 58 |
+
SEQ_MULTIPLE = 128 # sequence lengths are padded to multiples of this at least (SDPA chunk sizes divide it)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
NEG = -1e9 # additive mask value for excluded keys (finite, representable in bf16)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def linear_to_mm(w: torch.Tensor) -> torch.Tensor:
|
| 65 |
+
"""nn.Linear weight [out, in] -> matmul weight [in, out]."""
|
| 66 |
+
return w.t().contiguous()
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def interleave_pairs_permutation(head_dim: int) -> torch.Tensor:
|
| 70 |
+
"""new[j] = old[p[j]]: rotate-half pairs (i, i + D/2) -> adjacent pairs (2i, 2i + 1).
|
| 71 |
+
|
| 72 |
+
Applying it to the rows of q_proj/k_proj (per head) and to q_norm/k_norm leaves every q.k product unchanged and
|
| 73 |
+
lets tt's rotary_embedding_llama (adjacent pairs) replace upstream's rotate_half RoPE."""
|
| 74 |
+
half = head_dim // 2
|
| 75 |
+
p = torch.empty(head_dim, dtype=torch.long)
|
| 76 |
+
p[0::2] = torch.arange(half)
|
| 77 |
+
p[1::2] = torch.arange(half) + half
|
| 78 |
+
return p
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def permute_heads_rows(w_out_in: torch.Tensor, n_heads: int, head_dim: int, perm: torch.Tensor) -> torch.Tensor:
|
| 82 |
+
out, inp = w_out_in.shape
|
| 83 |
+
assert out == n_heads * head_dim
|
| 84 |
+
return w_out_in.view(n_heads, head_dim, inp)[:, perm, :].reshape(out, inp).contiguous()
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def rot_transformation_mat(tile: int = TILE) -> torch.Tensor:
|
| 88 |
+
"""[1, 1, 32, 32] T with (x @ T)[2k] = -x[2k+1], (x @ T)[2k+1] = x[2k] (adjacent-pair rotation)."""
|
| 89 |
+
m = torch.zeros(1, 1, tile, tile)
|
| 90 |
+
m[..., torch.arange(0, tile, 2), torch.arange(1, tile, 2)] = 1.0
|
| 91 |
+
m[..., torch.arange(1, tile, 2), torch.arange(0, tile, 2)] = -1.0
|
| 92 |
+
return m
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def rms(x: torch.Tensor, w: torch.Tensor, eps: float) -> torch.Tensor:
|
| 96 |
+
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + eps) * w
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def round_up(n: int, m: int = SEQ_MULTIPLE) -> int:
|
| 100 |
+
return (n + m - 1) // m * m
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def swiglu_interleave(gate_out_in: torch.Tensor, up_out_in: torch.Tensor, tile: int = TILE) -> torch.Tensor:
|
| 104 |
+
"""[K, 2N] weight for minimal_matmul(fuse_swiglu=True): column tile 2p = gate tile p, 2p+1 = up tile p."""
|
| 105 |
+
g, u = linear_to_mm(gate_out_in), linear_to_mm(up_out_in)
|
| 106 |
+
K, N = g.shape
|
| 107 |
+
assert N % tile == 0, N
|
| 108 |
+
return torch.stack([g.view(K, N // tile, tile), u.view(K, N // tile, tile)], dim=2).reshape(K, 2 * N).contiguous()
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def apply_rope_adjacent(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
|
| 112 |
+
rot = torch.stack([-x[..., 1::2], x[..., 0::2]], dim=-1).flatten(-2)
|
| 113 |
+
return x * cos + rot * sin
|
code/tt_diffrhythm/diffrhythm_dit.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""DiffRhythm 1.2 DiT on ONE Blackhole (P100a), TTNN, batch 1.
|
| 3 |
+
|
| 4 |
+
Mirrors diffrhythm_host.RefDecoder op for op, with the block kernels of acestep_dit.py (fused QKV minimal_matmul,
|
| 5 |
+
adjacent-pair rotary_embedding_llama, fused SwiGLU, fp32-accumulated SDPA).
|
| 6 |
+
|
| 7 |
+
Per song (prepare): for each CFG branch, the timestep-invariant part of the input projection
|
| 8 |
+
[text_embed @ Wtext + cond @ Wcond] and the 8 text-fusion residuals silu(text_embed @ Wf + bf) are computed on the
|
| 9 |
+
device from the host's text embedding and kept there; plus the RoPE tables and the masks.
|
| 10 |
+
Per call (forward): x @ Wx + that base + the call's row (style/time), zero past the song, two grouped k31 convs with
|
| 11 |
+
Mish (zeroing the padding before each), 16 Llama layers (32 x 64 heads, padded keys excluded), the fusion residuals
|
| 12 |
+
after the first 8, then LayerNorm * (1 + scale) + shift and proj_out.
|
| 13 |
+
|
| 14 |
+
Speed (dit_profile.py, sdpa_check.py, conv_speed.py, mm_sweep.py): the attention excludes the padding with
|
| 15 |
+
block-diagonal windows [0, frames, S] built on the device instead of reading an [S, S] mask for every head (30 % faster
|
| 16 |
+
at 6144 frames); each position conv is one matmul over the 31 shifted rows (unfold) instead of ttnn's grouped conv,
|
| 17 |
+
which expands the 16 groups densely anyway; the matmuls use blockings measured for these shapes.
|
| 18 |
+
"""
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
from dataclasses import dataclass
|
| 22 |
+
from typing import Dict, List, Optional
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import ttnn
|
| 26 |
+
|
| 27 |
+
from . import diffrhythm_host as host
|
| 28 |
+
from .common import MEM, Precision, _dev, _row, close_device, dram_stats, open_device # noqa: F401
|
| 29 |
+
from .common import rot_transformation_mat
|
| 30 |
+
|
| 31 |
+
try:
|
| 32 |
+
from models.tt_dit.utils.matmul import get_matmul_config
|
| 33 |
+
except ImportError: # pragma: no cover - only on hosts without tt-metal models/
|
| 34 |
+
get_matmul_config = None
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclass
|
| 38 |
+
class DRPrecision(Precision):
|
| 39 |
+
"""Precision on top of acestep_dit.Precision. CFG (pred + 4 (pred - null)) amplifies the error of the small
|
| 40 |
+
difference between the two branches, and 31 steps accumulate it, so a DiT call that matches the GPU at PCC 0.9999
|
| 41 |
+
still drifts over a song. Closed-loop check (device_check.py --closed-loop, 96 s song, final latents vs the GPU's;
|
| 42 |
+
CPU float32 0.99997, CPU all-bfloat16 0.991): all bfloat16 0.970; the defaults below 0.994-0.995 for +1.2 s per
|
| 43 |
+
song. Splitting the weights (bf16 hi + lo) or keeping the branch tensors in float32 gained nothing more
|
| 44 |
+
(experiments/tt-diffrhythm/results/precision_sweep_d96.json)."""
|
| 45 |
+
|
| 46 |
+
mm_fidelity: ttnn.MathFidelity = ttnn.MathFidelity.HiFi4
|
| 47 |
+
x_fp32: bool = True # upload the noisy latents in float32 (the input projection reads them)
|
| 48 |
+
residual_fp32: bool = True # keep the residual stream in float32 (branch outputs are added into it)
|
| 49 |
+
out_fp32: bool = True # the final projection writes float32
|
| 50 |
+
rows_fp32: bool = True # the per-call rows (style/time input row, output scale and shift) in float32: their
|
| 51 |
+
# rounding is the same for every frame and step, so it adds up instead of averaging out
|
| 52 |
+
consts_fp32: bool = True # the same for the constant per-channel rows: RMSNorm weights and proj_out's bias
|
| 53 |
+
windowed_attention: bool = True # padded keys excluded by on-device windows (False: an [S, S] additive mask)
|
| 54 |
+
unfold_conv: bool = True # position convs as one matmul over the unfolded rows (False: ttnn.conv1d, groups=16)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# minimal_matmul blocks (M, K, N, (subblock h, w)) per (K, N) on the P100a's 11x10 grid (mm_sweep.py: 5-15 % faster
|
| 58 |
+
# than tt-metal's 8x8x8 default); a list of (largest M, blocks) where the best depends on the length
|
| 59 |
+
MM_BLOCKS = {
|
| 60 |
+
(2048, 6144): [(None, (4, 8, 4, (4, 1)))], # qkv
|
| 61 |
+
(2048, 2048): [(None, (4, 4, 4, (4, 1)))], # o
|
| 62 |
+
(2048, 16384): [(3072, (8, 8, 8, (2, 2))), (None, (4, 8, 8, (4, 1)))], # gate/up, fused SwiGLU
|
| 63 |
+
(8192, 2048): [(None, (4, 8, 4, (4, 1)))], # down
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def conv_unfold_weight(w: torch.Tensor, groups: int) -> torch.Tensor:
|
| 68 |
+
"""Grouped conv1d weight [C, C / groups, k] -> matmul weight [k * C, C] for the unfolded input (row t holds the
|
| 69 |
+
input rows t - k // 2 ... t + k // 2 side by side): block-diagonal per tap, row j * C + (g * cg + i), column
|
| 70 |
+
g * cg + o."""
|
| 71 |
+
C, cg, k = w.shape
|
| 72 |
+
out = torch.zeros(k, C, C)
|
| 73 |
+
wg = w.view(groups, cg, cg, k) # g, o, i, tap
|
| 74 |
+
for g in range(groups):
|
| 75 |
+
out[:, g * cg:(g + 1) * cg, g * cg:(g + 1) * cg] = wg[g].permute(2, 1, 0) # tap, i, o
|
| 76 |
+
return out.reshape(k * C, C)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class Layer:
|
| 80 |
+
def __init__(self, dev, w: host.LayerWeights, prec: Precision):
|
| 81 |
+
wd = prec.weight_dtype
|
| 82 |
+
self.wqkv = _dev(dev, w.wqkv.to(torch.bfloat16), wd)
|
| 83 |
+
self.wo = _dev(dev, w.wo.to(torch.bfloat16), wd)
|
| 84 |
+
self.w_gateup = _dev(dev, w.w_gateup.to(torch.bfloat16), wd)
|
| 85 |
+
self.w_down = _dev(dev, w.w_down.to(torch.bfloat16), wd)
|
| 86 |
+
if getattr(prec, "consts_fp32", False):
|
| 87 |
+
self.ln1, self.ln2 = (_dev(dev, v.float().reshape(1, 1, 1, -1), ttnn.float32) for v in (w.ln1, w.ln2))
|
| 88 |
+
else:
|
| 89 |
+
self.ln1, self.ln2 = _row(dev, w.ln1), _row(dev, w.ln2)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@dataclass
|
| 93 |
+
class Song:
|
| 94 |
+
"""Device state of one generation: per-branch base and fusion residuals, tables and masks."""
|
| 95 |
+
|
| 96 |
+
geo: host.Geometry
|
| 97 |
+
branches: Dict[bool, dict] # drop_text -> {"base": tensor, "fusion": [8 tensors]}
|
| 98 |
+
cos: ttnn.Tensor
|
| 99 |
+
sin: ttnn.Tensor
|
| 100 |
+
key_mask: Optional[ttnn.Tensor] # [S, S] additive mask, or None with windowed attention
|
| 101 |
+
windows: Optional[ttnn.Tensor] # cu_window_seqlens [0, frames, S] (or [0, S]) for windowed attention
|
| 102 |
+
row_mask: ttnn.Tensor
|
| 103 |
+
|
| 104 |
+
def tensors(self) -> List[ttnn.Tensor]:
|
| 105 |
+
out = [t for t in (self.cos, self.sin, self.key_mask, self.windows, self.row_mask) if t is not None]
|
| 106 |
+
for b in self.branches.values():
|
| 107 |
+
out += [b["base"], *b["fusion"]]
|
| 108 |
+
return out
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class DiffRhythmDiT:
|
| 112 |
+
def __init__(self, dev, ckpt: host.Checkpoint, prec: Optional[Precision] = None, keep_ref: bool = False):
|
| 113 |
+
self.dev, self.cfg = dev, ckpt.cfg
|
| 114 |
+
self.prec = prec or DRPrecision()
|
| 115 |
+
self.res_dtype = ttnn.float32 if getattr(self.prec, "residual_fp32", False) else ttnn.bfloat16
|
| 116 |
+
self.ref = host.RefDecoder(ckpt) # host weights: layouts and the per-call rows
|
| 117 |
+
self.grid = dev.compute_with_storage_grid_size()
|
| 118 |
+
r = self.ref
|
| 119 |
+
self.layers = [Layer(dev, w, self.prec) for w in r.layers]
|
| 120 |
+
bf = lambda t: _dev(dev, t.to(torch.bfloat16))
|
| 121 |
+
self.wx, self.wcond, self.wtext = bf(r.iw.wx), bf(r.iw.wcond), bf(r.iw.wtext)
|
| 122 |
+
self.fusion = [(bf(w), _row(dev, b)) for w, b in r.fusion]
|
| 123 |
+
self.unfold = getattr(self.prec, "unfold_conv", False)
|
| 124 |
+
if self.unfold:
|
| 125 |
+
self.conv = [(bf(conv_unfold_weight(w, self.cfg.conv_groups)), _row(dev, b)) for w, b in r.conv]
|
| 126 |
+
else:
|
| 127 |
+
self.conv = [(ttnn.from_torch(w.unsqueeze(2).contiguous(), dtype=ttnn.float32),
|
| 128 |
+
ttnn.from_torch(b.reshape(1, 1, 1, -1), dtype=ttnn.float32)) for w, b in r.conv]
|
| 129 |
+
self.w_out, self.b_out = bf(r.w_out), _row(dev, r.b_out)
|
| 130 |
+
self.b_out32 = (_dev(dev, r.b_out.float().reshape(1, 1, 1, -1), ttnn.float32)
|
| 131 |
+
if getattr(self.prec, "consts_fp32", False) else None)
|
| 132 |
+
self.trans_mat = _dev(dev, rot_transformation_mat())
|
| 133 |
+
arch = dev.arch()
|
| 134 |
+
ck = lambda fid, fp32: ttnn.init_device_compute_kernel_config(
|
| 135 |
+
arch, math_fidelity=fid, math_approx_mode=False, fp32_dest_acc_en=fp32, packer_l1_acc=False)
|
| 136 |
+
self.ck_mm = ck(self.prec.mm_fidelity, True)
|
| 137 |
+
self.ck_norm = ck(ttnn.MathFidelity.HiFi4, True)
|
| 138 |
+
self.ck_sdpa = ck(self.prec.sdpa_fidelity, self.prec.sdpa_fp32_acc)
|
| 139 |
+
self.ck_rope = ck(ttnn.MathFidelity.HiFi4, True)
|
| 140 |
+
self.ck_conv = ck(ttnn.MathFidelity.HiFi4, True)
|
| 141 |
+
self._mm_cfg_cache: Dict[tuple, object] = {}
|
| 142 |
+
self.windowed = getattr(self.prec, "windowed_attention", False)
|
| 143 |
+
if not self.unfold:
|
| 144 |
+
# ttnn's conv moves its weights onto the device (in its own layout) on the first call. Done now, before
|
| 145 |
+
# any song, so they do not land behind a song's tensors (the VAE's later allocations, and so its compiled
|
| 146 |
+
# kernels, would then depend on the song length). The prepared weights serve every sequence bucket
|
| 147 |
+
# (bit-identical to weights prepared at each length).
|
| 148 |
+
S = host.seq_buckets()[0]
|
| 149 |
+
z = _dev(dev, torch.zeros(1, 1, S, self.cfg.dim, dtype=torch.bfloat16))
|
| 150 |
+
for i in range(2):
|
| 151 |
+
ttnn.deallocate(self._conv(z, i, S))
|
| 152 |
+
ttnn.deallocate(z)
|
| 153 |
+
if not keep_ref: # the per-call rows need only the small input/output weights on the host
|
| 154 |
+
r.layers = None
|
| 155 |
+
|
| 156 |
+
# ------------------------------------------------------------------ song
|
| 157 |
+
def prepare(self, text_embed: Dict[bool, torch.Tensor], cond: torch.Tensor) -> Song:
|
| 158 |
+
"""text_embed {drop_text: [S, 512]} from upstream's TextEmbedding, cond [S, 64] -> device state."""
|
| 159 |
+
c = self.cfg
|
| 160 |
+
geo = host.Geometry(frames=cond.shape[0])
|
| 161 |
+
S = geo.seq_pad
|
| 162 |
+
branches = {}
|
| 163 |
+
cond_d = _dev(self.dev, host.pad_rows(cond.float(), S).to(torch.bfloat16).reshape(1, 1, S, -1))
|
| 164 |
+
for null, te in text_embed.items():
|
| 165 |
+
t = _dev(self.dev, host.pad_rows(te.float(), S).to(torch.bfloat16).reshape(1, 1, S, -1))
|
| 166 |
+
bd = ttnn.bfloat16
|
| 167 |
+
base = ttnn.linear(t, self.wtext, compute_kernel_config=self.ck_mm, memory_config=MEM, dtype=bd)
|
| 168 |
+
if not null: # the CFG branch drops the audio condition (zeros)
|
| 169 |
+
bc = ttnn.linear(cond_d, self.wcond, compute_kernel_config=self.ck_mm, memory_config=MEM, dtype=bd)
|
| 170 |
+
base = self._add_free(base, bc)
|
| 171 |
+
fusion = []
|
| 172 |
+
for w, b in self.fusion:
|
| 173 |
+
f = ttnn.linear(t, w, bias=b, compute_kernel_config=self.ck_mm, memory_config=MEM, dtype=bd)
|
| 174 |
+
fusion.append(ttnn.silu(f, memory_config=MEM))
|
| 175 |
+
ttnn.deallocate(f)
|
| 176 |
+
ttnn.deallocate(t)
|
| 177 |
+
branches[null] = {"base": base, "fusion": fusion}
|
| 178 |
+
ttnn.deallocate(cond_d)
|
| 179 |
+
cos, sin = host.rope_tables(S, c)
|
| 180 |
+
f = lambda m: _dev(self.dev, m.to(torch.bfloat16)[None, None])
|
| 181 |
+
rows = host.row_mask(geo).expand(S, c.dim)
|
| 182 |
+
if self.windowed:
|
| 183 |
+
bounds = [0, geo.frames, S] if geo.frames < S else [0, S]
|
| 184 |
+
windows = ttnn.from_torch(torch.tensor(bounds, dtype=torch.int32), dtype=ttnn.int32,
|
| 185 |
+
layout=ttnn.ROW_MAJOR_LAYOUT, device=self.dev)
|
| 186 |
+
key_mask = None
|
| 187 |
+
else:
|
| 188 |
+
windows, key_mask = None, f(host.key_mask(geo))
|
| 189 |
+
return Song(geo, branches, f(cos), f(sin), key_mask, windows, f(rows))
|
| 190 |
+
|
| 191 |
+
def release(self, song: Song):
|
| 192 |
+
for t in song.tensors():
|
| 193 |
+
ttnn.deallocate(t)
|
| 194 |
+
|
| 195 |
+
# ------------------------------------------------------------------ ops
|
| 196 |
+
def _mm_config(self, M, K, N):
|
| 197 |
+
for max_m, (mb, kb, nb, (sh, sw)) in MM_BLOCKS.get((K, N), []):
|
| 198 |
+
if max_m is None or M <= max_m:
|
| 199 |
+
return ttnn.MinimalMatmulConfig(M_block_size=mb, K_block_size=kb, N_block_size=nb, subblock_h=sh,
|
| 200 |
+
subblock_w=sw, compute_with_storage_grid_size=self.grid)
|
| 201 |
+
return get_matmul_config(M, K, N, self.grid)
|
| 202 |
+
|
| 203 |
+
def _mm(self, x, w, M, K, N, fuse_swiglu=False, dtype=ttnn.bfloat16):
|
| 204 |
+
key = (M, K, N)
|
| 205 |
+
if key not in self._mm_cfg_cache:
|
| 206 |
+
self._mm_cfg_cache[key] = self._mm_config(M, K, N)
|
| 207 |
+
out = ttnn.experimental.minimal_matmul(x, w, config=self._mm_cfg_cache[key], compute_kernel_config=self.ck_mm,
|
| 208 |
+
dtype=dtype, memory_config=MEM, fuse_swiglu=fuse_swiglu)
|
| 209 |
+
if len(out.shape) != 4:
|
| 210 |
+
out = ttnn.reshape(out, [1, 1, M, out.shape[-1]])
|
| 211 |
+
return out
|
| 212 |
+
|
| 213 |
+
@staticmethod
|
| 214 |
+
def _add_free(a, b, dtype=None):
|
| 215 |
+
out = ttnn.add(a, b, memory_config=MEM, dtype=dtype) if dtype is not None else ttnn.add(a, b, memory_config=MEM)
|
| 216 |
+
ttnn.deallocate(a)
|
| 217 |
+
ttnn.deallocate(b)
|
| 218 |
+
return out
|
| 219 |
+
|
| 220 |
+
def _residual(self, h, o):
|
| 221 |
+
"""h + o in the residual stream's dtype; frees both."""
|
| 222 |
+
if self.res_dtype == ttnn.bfloat16:
|
| 223 |
+
return self._add_free(h, o)
|
| 224 |
+
if o.dtype != ttnn.float32:
|
| 225 |
+
o32 = ttnn.typecast(o, ttnn.float32)
|
| 226 |
+
ttnn.deallocate(o)
|
| 227 |
+
o = o32
|
| 228 |
+
return self._add_free(h, o)
|
| 229 |
+
|
| 230 |
+
def _bf16(self, x):
|
| 231 |
+
"""x as bfloat16 for a matmul; frees x when it had to be converted."""
|
| 232 |
+
if x.dtype == ttnn.bfloat16:
|
| 233 |
+
return x
|
| 234 |
+
out = ttnn.typecast(x, ttnn.bfloat16)
|
| 235 |
+
ttnn.deallocate(x)
|
| 236 |
+
return out
|
| 237 |
+
|
| 238 |
+
def _rms(self, x, w):
|
| 239 |
+
n = ttnn.rms_norm(x, epsilon=self.cfg.eps, weight=w, compute_kernel_config=self.ck_norm, memory_config=MEM)
|
| 240 |
+
return self._bf16(n)
|
| 241 |
+
|
| 242 |
+
def _mish(self, x):
|
| 243 |
+
if hasattr(ttnn, "mish"):
|
| 244 |
+
return ttnn.mish(x, memory_config=MEM)
|
| 245 |
+
sp = ttnn.softplus(x, memory_config=MEM)
|
| 246 |
+
th = ttnn.tanh(sp, memory_config=MEM)
|
| 247 |
+
ttnn.deallocate(sp)
|
| 248 |
+
out = ttnn.multiply(x, th, memory_config=MEM)
|
| 249 |
+
ttnn.deallocate(th)
|
| 250 |
+
return out
|
| 251 |
+
|
| 252 |
+
def _conv(self, x, i, S):
|
| 253 |
+
if self.unfold:
|
| 254 |
+
return self._conv_unfold(x, i, S)
|
| 255 |
+
c = self.cfg
|
| 256 |
+
w, b = self.conv[i]
|
| 257 |
+
cfg = ttnn.Conv2dConfig(weights_dtype=ttnn.bfloat16, config_tensors_in_dram=True)
|
| 258 |
+
out, out_len, (w, b) = ttnn.conv1d(
|
| 259 |
+
input_tensor=x, weight_tensor=w, bias_tensor=b, device=self.dev, in_channels=c.dim, out_channels=c.dim,
|
| 260 |
+
batch_size=1, input_length=S, kernel_size=c.conv_kernel, stride=1, padding=c.conv_kernel // 2, dilation=1,
|
| 261 |
+
groups=c.conv_groups, dtype=ttnn.bfloat16, compute_config=self.ck_conv, conv_config=cfg,
|
| 262 |
+
return_output_dim=True, return_weights_and_bias=True)
|
| 263 |
+
self.conv[i] = (w, b) # prepared on the device by the first call
|
| 264 |
+
if out.layout != ttnn.TILE_LAYOUT:
|
| 265 |
+
out = ttnn.to_layout(out, ttnn.TILE_LAYOUT)
|
| 266 |
+
return ttnn.reshape(out, [1, 1, out_len, c.dim])
|
| 267 |
+
|
| 268 |
+
def _conv_unfold(self, x, i, S):
|
| 269 |
+
"""[1, 1, S, C] -> the same conv as one matmul: rows padded by k // 2 zeros at both ends, the k shifted
|
| 270 |
+
copies side by side ([S, k * C]), times the block-diagonal weight."""
|
| 271 |
+
c = self.cfg
|
| 272 |
+
k, pad = c.conv_kernel, c.conv_kernel // 2
|
| 273 |
+
w, b = self.conv[i]
|
| 274 |
+
xr = ttnn.reshape(ttnn.to_layout(x, ttnn.ROW_MAJOR_LAYOUT), [S, c.dim])
|
| 275 |
+
xp = ttnn.pad(xr, [(pad, pad), (0, 0)], 0.0)
|
| 276 |
+
ttnn.deallocate(xr)
|
| 277 |
+
parts = [ttnn.slice(xp, [j, 0], [j + S, c.dim]) for j in range(k)]
|
| 278 |
+
ttnn.deallocate(xp)
|
| 279 |
+
xu = ttnn.concat(parts, dim=1, memory_config=MEM)
|
| 280 |
+
for t in parts:
|
| 281 |
+
ttnn.deallocate(t)
|
| 282 |
+
xt = ttnn.to_layout(ttnn.reshape(xu, [1, 1, S, k * c.dim]), ttnn.TILE_LAYOUT)
|
| 283 |
+
ttnn.deallocate(xu)
|
| 284 |
+
y = self._mm(xt, w, S, k * c.dim, c.dim)
|
| 285 |
+
ttnn.deallocate(xt)
|
| 286 |
+
out = ttnn.add(y, b, memory_config=MEM)
|
| 287 |
+
ttnn.deallocate(y)
|
| 288 |
+
return out
|
| 289 |
+
|
| 290 |
+
def _conv_pos(self, h, song: Song, S):
|
| 291 |
+
"""Two k31 convs with Mish; the rows past the song are zeroed before each (upstream's sequence ends there)."""
|
| 292 |
+
y = h
|
| 293 |
+
for i in range(2):
|
| 294 |
+
m = ttnn.multiply(y, song.row_mask, memory_config=MEM)
|
| 295 |
+
if y is not h:
|
| 296 |
+
ttnn.deallocate(y)
|
| 297 |
+
conv = self._conv(m, i, S)
|
| 298 |
+
ttnn.deallocate(m)
|
| 299 |
+
y = self._mish(conv)
|
| 300 |
+
ttnn.deallocate(conv)
|
| 301 |
+
return y
|
| 302 |
+
|
| 303 |
+
def _sdpa(self, q, k, v, song: Song, S):
|
| 304 |
+
# sdpa_check.py: 256 x 256 chunks are fastest from 3072 frames on; at 2560 windowed attention prefers 128-row Q
|
| 305 |
+
# chunks (S is a multiple of SEQ_BUCKET = 256, so both divide it)
|
| 306 |
+
q_chunk = 128 if self.windowed and S <= 2560 else 256
|
| 307 |
+
cfg = ttnn.SDPAProgramConfig(compute_with_storage_grid_size=self.grid, q_chunk_size=q_chunk, k_chunk_size=256,
|
| 308 |
+
exp_approx_mode=False)
|
| 309 |
+
exclude = {"cu_window_seqlens": song.windows} if self.windowed else {"attn_mask": song.key_mask}
|
| 310 |
+
return ttnn.transformer.scaled_dot_product_attention(
|
| 311 |
+
q, k, v, is_causal=False, scale=self.cfg.head_dim ** -0.5, program_config=cfg,
|
| 312 |
+
compute_kernel_config=self.ck_sdpa, memory_config=MEM, **exclude)
|
| 313 |
+
|
| 314 |
+
def _attention(self, h, L: Layer, S, song: Song):
|
| 315 |
+
c = self.cfg
|
| 316 |
+
qkv = self._mm(h, L.wqkv, S, c.dim, 3 * c.dim)
|
| 317 |
+
q, k, v = ttnn.experimental.nlp_create_qkv_heads(qkv, num_heads=c.heads, num_kv_heads=c.heads,
|
| 318 |
+
transpose_k_heads=False, memory_config=MEM)
|
| 319 |
+
ttnn.deallocate(qkv)
|
| 320 |
+
roped = []
|
| 321 |
+
for t in (q, k):
|
| 322 |
+
roped.append(ttnn.experimental.rotary_embedding_llama(t, song.cos, song.sin, self.trans_mat,
|
| 323 |
+
is_decode_mode=False, compute_kernel_config=self.ck_rope))
|
| 324 |
+
ttnn.deallocate(t)
|
| 325 |
+
a = self._sdpa(roped[0], roped[1], v, song, S)
|
| 326 |
+
for t in (*roped, v):
|
| 327 |
+
ttnn.deallocate(t)
|
| 328 |
+
a2 = ttnn.transformer.concatenate_heads(a, memory_config=MEM)
|
| 329 |
+
ttnn.deallocate(a)
|
| 330 |
+
o = self._mm(a2, L.wo, S, c.dim, c.dim, dtype=self.res_dtype) # added into the residual stream as is
|
| 331 |
+
ttnn.deallocate(a2)
|
| 332 |
+
return o
|
| 333 |
+
|
| 334 |
+
# ------------------------------------------------------------------ public
|
| 335 |
+
def forward(self, x_t: torch.Tensor, drop_text: bool, style: torch.Tensor, c_emb: torch.Tensor, song: Song,
|
| 336 |
+
taps: Optional[list] = None) -> torch.Tensor:
|
| 337 |
+
"""[S, 64] noisy latents, the branch, style [512] and c = time + start + duration embeddings [512]
|
| 338 |
+
-> [S, 64] float32 prediction."""
|
| 339 |
+
c, geo = self.cfg, song.geo
|
| 340 |
+
S = geo.seq_pad
|
| 341 |
+
branch = song.branches[drop_text]
|
| 342 |
+
rows = self.ref.rows(style.float(), c_emb.float())
|
| 343 |
+
if getattr(self.prec, "rows_fp32", False):
|
| 344 |
+
row = lambda v: _dev(self.dev, v.float().reshape(1, 1, 1, -1), ttnn.float32)
|
| 345 |
+
else:
|
| 346 |
+
row = lambda v: _row(self.dev, v)
|
| 347 |
+
in_row, gamma, shift = row(rows.input_row), row(1 + rows.out_scale), row(rows.out_shift)
|
| 348 |
+
p = self.prec
|
| 349 |
+
xs = host.pad_rows(x_t.float(), S).reshape(1, 1, S, -1)
|
| 350 |
+
if getattr(p, "x_fp32", False):
|
| 351 |
+
x = _dev(self.dev, xs, ttnn.float32)
|
| 352 |
+
else:
|
| 353 |
+
x = _dev(self.dev, xs.to(torch.bfloat16))
|
| 354 |
+
xw = ttnn.linear(x, self.wx, compute_kernel_config=self.ck_mm, memory_config=MEM, dtype=self.res_dtype)
|
| 355 |
+
ttnn.deallocate(x)
|
| 356 |
+
h0 = ttnn.add(xw, branch["base"], memory_config=MEM, dtype=self.res_dtype)
|
| 357 |
+
ttnn.deallocate(xw)
|
| 358 |
+
h1 = ttnn.add(h0, in_row, memory_config=MEM, dtype=self.res_dtype)
|
| 359 |
+
ttnn.deallocate(h0)
|
| 360 |
+
h = ttnn.multiply(h1, song.row_mask, memory_config=MEM, dtype=self.res_dtype)
|
| 361 |
+
ttnn.deallocate(h1)
|
| 362 |
+
conv_in = ttnn.typecast(h, ttnn.bfloat16) if self.res_dtype != ttnn.bfloat16 else h
|
| 363 |
+
pos = self._conv_pos(conv_in, song, S)
|
| 364 |
+
if conv_in is not h:
|
| 365 |
+
ttnn.deallocate(conv_in)
|
| 366 |
+
h = self._residual(h, pos)
|
| 367 |
+
tap = (lambda v: taps.append(ttnn.to_torch(v)[0, 0].float())) if taps is not None else (lambda v: None)
|
| 368 |
+
for i, L in enumerate(self.layers):
|
| 369 |
+
a = self._rms(h, L.ln1)
|
| 370 |
+
o = self._attention(a, L, S, song)
|
| 371 |
+
ttnn.deallocate(a)
|
| 372 |
+
h = self._residual(h, o)
|
| 373 |
+
m = self._rms(h, L.ln2)
|
| 374 |
+
gu = self._mm(m, L.w_gateup, S, c.dim, 2 * c.intermediate, fuse_swiglu=True)
|
| 375 |
+
ttnn.deallocate(m)
|
| 376 |
+
d = self._mm(gu, L.w_down, S, c.intermediate, c.dim, dtype=self.res_dtype)
|
| 377 |
+
ttnn.deallocate(gu)
|
| 378 |
+
h = self._residual(h, d)
|
| 379 |
+
if i < c.fusion_layers:
|
| 380 |
+
h2 = ttnn.add(h, branch["fusion"][i], memory_config=MEM, dtype=self.res_dtype)
|
| 381 |
+
ttnn.deallocate(h)
|
| 382 |
+
h = h2
|
| 383 |
+
tap(h)
|
| 384 |
+
n = ttnn.layer_norm(h, epsilon=c.eps, compute_kernel_config=self.ck_norm, memory_config=MEM)
|
| 385 |
+
ttnn.deallocate(h)
|
| 386 |
+
g = ttnn.multiply(n, gamma, memory_config=MEM)
|
| 387 |
+
ttnn.deallocate(n)
|
| 388 |
+
mod = self._bf16(ttnn.add(g, shift, memory_config=MEM))
|
| 389 |
+
ttnn.deallocate(g)
|
| 390 |
+
if self.b_out32 is not None:
|
| 391 |
+
o = ttnn.linear(mod, self.w_out, compute_kernel_config=self.ck_mm, memory_config=MEM, dtype=ttnn.float32)
|
| 392 |
+
out = ttnn.add(o, self.b_out32, memory_config=MEM)
|
| 393 |
+
ttnn.deallocate(o)
|
| 394 |
+
else:
|
| 395 |
+
out = ttnn.linear(mod, self.w_out, bias=self.b_out, compute_kernel_config=self.ck_mm, memory_config=MEM,
|
| 396 |
+
dtype=ttnn.float32 if getattr(p, "out_fp32", False) else ttnn.bfloat16)
|
| 397 |
+
ttnn.deallocate(mod)
|
| 398 |
+
host_out = ttnn.to_torch(out)[0, 0, :geo.frames, : c.mel_dim].float()
|
| 399 |
+
for t in (out, in_row, gamma, shift):
|
| 400 |
+
ttnn.deallocate(t)
|
| 401 |
+
return host_out
|
code/tt_diffrhythm/diffrhythm_host.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Host (torch, CPU) side of the DiffRhythm 1.2 DiT port to one Tenstorrent P100a.
|
| 2 |
+
|
| 3 |
+
Upstream (DiffRhythm model/dit.py DiT.forward) as used by CFM.sample, per call:
|
| 4 |
+
c = time_embed(t) + start_time_embed(start) + duration_time_embed(duration) [512]
|
| 5 |
+
text_embed = TextEmbedding(text) (embedding + sinusoid + 4 ConvNeXtV2 blocks; zeros in for drop_text) [S, 512]
|
| 6 |
+
h = proj(cat(x, cond, text_embed, style, c)) -> [S, 2048]; h = ConvPos(h) + h (two k31 groups-16 convs + Mish)
|
| 7 |
+
16 x LlamaDecoderLayer (RMSNorm, 32 x 64 heads, RoPE theta 1e4, SwiGLU 8192; no mask, bidirectional);
|
| 8 |
+
after each of the first 8: h += silu(text_fusion_linears[i](text_embed))
|
| 9 |
+
out = proj_out(LayerNorm(h) * (1 + scale) + shift), scale, shift = linear(silu(c)).chunk(2)
|
| 10 |
+
The CFG branch (drop_audio_cond + drop_text) zeroes cond and the text tokens and uses the negative style.
|
| 11 |
+
|
| 12 |
+
Split: the timestep-invariant text embedding and the small time MLPs stay in the DiffRhythm process on the host
|
| 13 |
+
(upstream modules, exact); everything weight-heavy runs on the card. The input projection is split by input:
|
| 14 |
+
h = x @ Wx + [cond @ Wcond + text_embed @ Wtext] (per song and branch) + [style @ Ws + c @ Wc + b] (per call row)
|
| 15 |
+
RefDecoder runs this formulation in float32 so it can be checked against upstream without a device.
|
| 16 |
+
"""
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
from dataclasses import dataclass
|
| 20 |
+
from typing import Dict
|
| 21 |
+
|
| 22 |
+
import torch
|
| 23 |
+
|
| 24 |
+
from .common import (NEG, TILE, apply_rope_adjacent, interleave_pairs_permutation, linear_to_mm,
|
| 25 |
+
permute_heads_rows, rms, round_up, swiglu_interleave)
|
| 26 |
+
|
| 27 |
+
SEQ_BUCKET = 256 # frames padded to multiples of this: 96-285 s songs (2067-6136 frames) -> 16 shapes
|
| 28 |
+
LATENT_RATE = 44100 / 2048 # latent frames per second (get_lrc_token: int(seconds * 44100 / 2048))
|
| 29 |
+
MIN_FRAMES, MAX_FRAMES = int(96 * LATENT_RATE), int(285 * LATENT_RATE) # the 96-285 s the port was exercised over
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def seq_buckets():
|
| 33 |
+
"""Every padded length a DiffRhythm song can have (warm_cache.py compiles them ahead of time)."""
|
| 34 |
+
return sorted({round_up(n, SEQ_BUCKET) for n in range(MIN_FRAMES, MAX_FRAMES + 1)})
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
@dataclass(frozen=True)
|
| 38 |
+
class Config:
|
| 39 |
+
dim: int = 2048
|
| 40 |
+
depth: int = 16
|
| 41 |
+
heads: int = 32
|
| 42 |
+
head_dim: int = 64
|
| 43 |
+
intermediate: int = 8192
|
| 44 |
+
text_dim: int = 512
|
| 45 |
+
cond_dim: int = 512
|
| 46 |
+
mel_dim: int = 64
|
| 47 |
+
fusion_layers: int = 8
|
| 48 |
+
conv_kernel: int = 31
|
| 49 |
+
conv_groups: int = 16
|
| 50 |
+
rope_theta: float = 10000.0
|
| 51 |
+
eps: float = 1e-6
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class Checkpoint:
|
| 55 |
+
"""transformer.* of DiffRhythm's cfm_model.pt (fp16 on disk), read as float32."""
|
| 56 |
+
|
| 57 |
+
def __init__(self, path):
|
| 58 |
+
sd = torch.load(str(path), weights_only=True, map_location="cpu")["model_state_dict"]
|
| 59 |
+
self._sd = {k[len("transformer."):]: v for k, v in sd.items() if k.startswith("transformer.")}
|
| 60 |
+
self.cfg = Config()
|
| 61 |
+
|
| 62 |
+
def get(self, name: str) -> torch.Tensor:
|
| 63 |
+
return self._sd[name].float()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@dataclass
|
| 67 |
+
class LayerWeights:
|
| 68 |
+
wqkv: torch.Tensor # [2048, 3 * 2048], q/k rows permuted to adjacent pairs
|
| 69 |
+
wo: torch.Tensor
|
| 70 |
+
w_gateup: torch.Tensor # [2048, 2 * 8192] tile-interleaved
|
| 71 |
+
w_down: torch.Tensor
|
| 72 |
+
ln1: torch.Tensor
|
| 73 |
+
ln2: torch.Tensor
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def layer_weights(ckpt: Checkpoint, i: int) -> LayerWeights:
|
| 77 |
+
c = ckpt.cfg
|
| 78 |
+
g = lambda k: ckpt.get(f"transformer_blocks.{i}.{k}")
|
| 79 |
+
perm = interleave_pairs_permutation(c.head_dim)
|
| 80 |
+
wq = permute_heads_rows(g("self_attn.q_proj.weight"), c.heads, c.head_dim, perm)
|
| 81 |
+
wk = permute_heads_rows(g("self_attn.k_proj.weight"), c.heads, c.head_dim, perm)
|
| 82 |
+
return LayerWeights(
|
| 83 |
+
wqkv=torch.cat([linear_to_mm(wq), linear_to_mm(wk), linear_to_mm(g("self_attn.v_proj.weight"))], dim=1),
|
| 84 |
+
wo=linear_to_mm(g("self_attn.o_proj.weight")),
|
| 85 |
+
w_gateup=swiglu_interleave(g("mlp.gate_proj.weight"), g("mlp.up_proj.weight")),
|
| 86 |
+
w_down=linear_to_mm(g("mlp.down_proj.weight")),
|
| 87 |
+
ln1=g("input_layernorm.weight"), ln2=g("post_attention_layernorm.weight"))
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@dataclass
|
| 91 |
+
class InputWeights:
|
| 92 |
+
"""input_embed.proj split by input (matmul layout [in, out])."""
|
| 93 |
+
|
| 94 |
+
wx: torch.Tensor # [64, 2048]
|
| 95 |
+
wcond: torch.Tensor # [64, 2048]
|
| 96 |
+
wtext: torch.Tensor # [512, 2048]
|
| 97 |
+
wstyle: torch.Tensor # [512, 2048]
|
| 98 |
+
wc: torch.Tensor # [512, 2048]
|
| 99 |
+
b: torch.Tensor
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def input_weights(ckpt: Checkpoint) -> InputWeights:
|
| 103 |
+
c = ckpt.cfg
|
| 104 |
+
w = linear_to_mm(ckpt.get("input_embed.proj.weight")) # [1664, 2048]
|
| 105 |
+
edges = [0, c.mel_dim, 2 * c.mel_dim, 2 * c.mel_dim + c.text_dim, 2 * c.mel_dim + c.text_dim + c.cond_dim]
|
| 106 |
+
parts = [w[a:b] for a, b in zip(edges, edges[1:] + [w.shape[0]])]
|
| 107 |
+
return InputWeights(*[p.contiguous() for p in parts], ckpt.get("input_embed.proj.bias"))
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
@dataclass(frozen=True)
|
| 111 |
+
class Geometry:
|
| 112 |
+
frames: int
|
| 113 |
+
|
| 114 |
+
@property
|
| 115 |
+
def seq_pad(self) -> int:
|
| 116 |
+
return round_up(self.frames, SEQ_BUCKET)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def key_mask(geo: Geometry) -> torch.Tensor:
|
| 120 |
+
"""Additive [S_pad, S_pad] mask excluding padded keys (upstream attends over exactly S frames)."""
|
| 121 |
+
valid = torch.arange(geo.seq_pad)[None, :] < geo.frames
|
| 122 |
+
return torch.where(valid, 0.0, NEG).expand(geo.seq_pad, -1).contiguous()
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def row_mask(geo: Geometry) -> torch.Tensor:
|
| 126 |
+
"""[S_pad, 1]: 1 on real frames, 0 on padding (the position convs must see zeros past the song, like upstream)."""
|
| 127 |
+
return (torch.arange(geo.seq_pad) < geo.frames).float()[:, None]
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def rope_tables(seq_pad: int, cfg: Config, dtype=torch.float32):
|
| 131 |
+
inv = 1.0 / (cfg.rope_theta ** (torch.arange(0, cfg.head_dim, 2, dtype=torch.float64) / cfg.head_dim))
|
| 132 |
+
ang = torch.outer(torch.arange(seq_pad, dtype=torch.float64), inv).float()
|
| 133 |
+
return torch.cos(ang).repeat_interleave(2, -1).to(dtype), torch.sin(ang).repeat_interleave(2, -1).to(dtype)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def pad_rows(t: torch.Tensor, rows: int) -> torch.Tensor:
|
| 137 |
+
out = torch.zeros(rows, t.shape[-1], dtype=t.dtype)
|
| 138 |
+
out[: t.shape[0]] = t
|
| 139 |
+
return out
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
@dataclass
|
| 143 |
+
class CallRows:
|
| 144 |
+
"""What changes per call: the style/time part of the input projection and the output modulation."""
|
| 145 |
+
|
| 146 |
+
input_row: torch.Tensor # [2048]
|
| 147 |
+
out_scale: torch.Tensor # [2048]
|
| 148 |
+
out_shift: torch.Tensor # [2048]
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def call_rows(ckpt_or_iw, norm_linear_w, norm_linear_b, style: torch.Tensor, c: torch.Tensor) -> CallRows:
|
| 152 |
+
"""style [512], c [512] (time + start + duration embeddings, from upstream on the host)."""
|
| 153 |
+
iw = ckpt_or_iw
|
| 154 |
+
input_row = style @ iw.wstyle + c @ iw.wc + iw.b
|
| 155 |
+
emb = torch.nn.functional.silu(c) @ norm_linear_w.t() + norm_linear_b
|
| 156 |
+
scale, shift = emb.chunk(2, dim=-1)
|
| 157 |
+
return CallRows(input_row, scale, shift)
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class RefDecoder:
|
| 161 |
+
"""float32 CPU DiT in the TT formulation."""
|
| 162 |
+
|
| 163 |
+
def __init__(self, ckpt: Checkpoint):
|
| 164 |
+
self.ckpt, self.cfg = ckpt, ckpt.cfg
|
| 165 |
+
c = self.cfg
|
| 166 |
+
self.layers = [layer_weights(ckpt, i) for i in range(c.depth)]
|
| 167 |
+
self.iw = input_weights(ckpt)
|
| 168 |
+
self.conv = [(ckpt.get(f"input_embed.conv_pos_embed.conv1d.{i}.weight"),
|
| 169 |
+
ckpt.get(f"input_embed.conv_pos_embed.conv1d.{i}.bias")) for i in (0, 2)]
|
| 170 |
+
self.fusion = [(linear_to_mm(ckpt.get(f"text_fusion_linears.{i}.0.weight")),
|
| 171 |
+
ckpt.get(f"text_fusion_linears.{i}.0.bias")) for i in range(c.fusion_layers)]
|
| 172 |
+
self.w_out, self.b_out = linear_to_mm(ckpt.get("proj_out.weight")), ckpt.get("proj_out.bias")
|
| 173 |
+
self.norm_w, self.norm_b = ckpt.get("norm_out.linear.weight"), ckpt.get("norm_out.linear.bias")
|
| 174 |
+
|
| 175 |
+
def rows(self, style: torch.Tensor, c: torch.Tensor) -> CallRows:
|
| 176 |
+
return call_rows(self.iw, self.norm_w, self.norm_b, style, c)
|
| 177 |
+
|
| 178 |
+
def branch(self, text_embed: torch.Tensor, cond, geo: Geometry) -> Dict[str, object]:
|
| 179 |
+
"""Per song and CFG branch: [cond @ Wcond + text @ Wtext] and the 8 text-fusion residuals, padded."""
|
| 180 |
+
t = pad_rows(text_embed.float(), geo.seq_pad)
|
| 181 |
+
base = t @ self.iw.wtext
|
| 182 |
+
if cond is not None:
|
| 183 |
+
base = base + pad_rows(cond.float(), geo.seq_pad) @ self.iw.wcond
|
| 184 |
+
fusion = [torch.nn.functional.silu(t @ w + b) for w, b in self.fusion]
|
| 185 |
+
return {"base": base, "fusion": fusion}
|
| 186 |
+
|
| 187 |
+
def conv_pos(self, h: torch.Tensor, mask: torch.Tensor = None) -> torch.Tensor:
|
| 188 |
+
"""Two k31 convs with Mish. Upstream's sequence ends at the song, so each conv sees zeros past it: the rows
|
| 189 |
+
past the song are zeroed before every conv (the first conv's Mish output is not zero there)."""
|
| 190 |
+
c = self.cfg
|
| 191 |
+
y = h
|
| 192 |
+
for w, b in self.conv:
|
| 193 |
+
if mask is not None:
|
| 194 |
+
y = y * mask
|
| 195 |
+
y = torch.nn.functional.mish(torch.nn.functional.conv1d(y.t()[None], w, b, padding=c.conv_kernel // 2,
|
| 196 |
+
groups=c.conv_groups))[0].t()
|
| 197 |
+
return y
|
| 198 |
+
|
| 199 |
+
def forward(self, x_t: torch.Tensor, branch: Dict[str, object], rows: CallRows, geo: Geometry, taps: list = None):
|
| 200 |
+
"""[S, 64] noisy latents -> [S, 64] prediction."""
|
| 201 |
+
c = self.cfg
|
| 202 |
+
mask = row_mask(geo)
|
| 203 |
+
h = (pad_rows(x_t.float(), geo.seq_pad) @ self.iw.wx + branch["base"] + rows.input_row) * mask
|
| 204 |
+
h = self.conv_pos(h, mask) + h
|
| 205 |
+
cos, sin = rope_tables(geo.seq_pad, c)
|
| 206 |
+
kmask = key_mask(geo)
|
| 207 |
+
for i, w in enumerate(self.layers):
|
| 208 |
+
a = rms(h, w.ln1, c.eps)
|
| 209 |
+
q, k, v = (a @ w.wqkv).split(c.dim, -1)
|
| 210 |
+
q = apply_rope_adjacent(q.view(-1, c.heads, c.head_dim).transpose(0, 1), cos, sin)
|
| 211 |
+
k = apply_rope_adjacent(k.view(-1, c.heads, c.head_dim).transpose(0, 1), cos, sin)
|
| 212 |
+
v = v.view(-1, c.heads, c.head_dim).transpose(0, 1)
|
| 213 |
+
scores = q @ k.transpose(-1, -2) * c.head_dim ** -0.5 + kmask
|
| 214 |
+
att = (torch.softmax(scores, -1) @ v).transpose(0, 1).reshape(geo.seq_pad, -1)
|
| 215 |
+
h = h + att @ w.wo
|
| 216 |
+
m = rms(h, w.ln2, c.eps)
|
| 217 |
+
gu = (m @ w.w_gateup).view(geo.seq_pad, -1, 2, TILE)
|
| 218 |
+
h = h + (torch.nn.functional.silu(gu[:, :, 0]) * gu[:, :, 1]).reshape(geo.seq_pad, -1) @ w.w_down
|
| 219 |
+
if i < c.fusion_layers:
|
| 220 |
+
h = h + branch["fusion"][i]
|
| 221 |
+
if taps is not None:
|
| 222 |
+
taps.append(h.clone())
|
| 223 |
+
h = torch.nn.functional.layer_norm(h, (c.dim,), eps=c.eps) * (1 + rows.out_scale) + rows.out_shift
|
| 224 |
+
return (h @ self.w_out + self.b_out)[: geo.frames]
|
code/tt_diffrhythm/local.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""The card in THIS process, behind the same `.call(msg)` surface `TTWorker` gives.
|
| 3 |
+
|
| 4 |
+
`RemoteDiffRhythmDiT` and `RemoteVae` reach the card through exactly one method -- `worker.call(msg)`
|
| 5 |
+
-- so swapping the transport is enough to run everything in one process: the shims, their window
|
| 6 |
+
arithmetic and their drop-in signatures are reused unchanged rather than reimplemented here.
|
| 7 |
+
|
| 8 |
+
Why there are two transports at all: DiffRhythm resolves `torch 2.10.0+cu128` and `numpy>=2`, while
|
| 9 |
+
ttnn's extension modules are built against tt-metal's own `torch 2.11.0+cpu` and `numpy 1.x`. Two
|
| 10 |
+
environments cannot be one process, so the deployment this port came from ran DiffRhythm on the host
|
| 11 |
+
and the card in a container, joined by a Unix socket (`remote.py` / `worker.py`). A container built
|
| 12 |
+
for this model alone has no such split -- one environment satisfies both -- and then the socket is
|
| 13 |
+
pure cost. Use `LocalWorker` there, `TTWorker` when the two environments must stay apart.
|
| 14 |
+
|
| 15 |
+
from tt_diffrhythm import LocalWorker, RemoteDiffRhythmDiT, RemoteVae
|
| 16 |
+
|
| 17 |
+
worker = LocalWorker(cfm_model_pt, vae=vae_model_pt).start()
|
| 18 |
+
cfm.transformer = RemoteDiffRhythmDiT(worker, cfm.transformer)
|
| 19 |
+
waveform = RemoteVae(worker, upsample=worker.upsample)(latent)
|
| 20 |
+
...
|
| 21 |
+
worker.close()
|
| 22 |
+
"""
|
| 23 |
+
from __future__ import annotations
|
| 24 |
+
|
| 25 |
+
import time
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
from typing import Optional
|
| 28 |
+
|
| 29 |
+
import torch
|
| 30 |
+
|
| 31 |
+
from . import diffrhythm_dit, oobleck_vae, wire
|
| 32 |
+
from .shapes import VAE_WINDOWS
|
| 33 |
+
from .worker import DiffRhythm
|
| 34 |
+
|
| 35 |
+
MODELS = {"diffrhythm": DiffRhythm}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class LocalWorker:
|
| 39 |
+
"""`TTWorker`'s surface, answered in-process. `checkpoint` is cfm_model.pt and `vae` is
|
| 40 |
+
vae_model.pt; both are the files DiffRhythm's own Hugging Face cache holds."""
|
| 41 |
+
|
| 42 |
+
def __init__(self, checkpoint, vae, model: str = "diffrhythm", warm: bool = True):
|
| 43 |
+
self.checkpoint = Path(checkpoint)
|
| 44 |
+
self.vae_path = Path(vae)
|
| 45 |
+
self.model_name = model
|
| 46 |
+
self.warm = warm
|
| 47 |
+
self.dev = None
|
| 48 |
+
self.model = self.vae = None
|
| 49 |
+
self.song = None
|
| 50 |
+
self.stats: dict = {}
|
| 51 |
+
self.failed = False
|
| 52 |
+
self.upsample: Optional[int] = None
|
| 53 |
+
|
| 54 |
+
# ------------------------------------------------------------------ lifecycle
|
| 55 |
+
def start(self) -> "LocalWorker":
|
| 56 |
+
started = time.monotonic()
|
| 57 |
+
self.dev = diffrhythm_dit.open_device()
|
| 58 |
+
try:
|
| 59 |
+
self.model = MODELS[self.model_name](self.dev, str(self.checkpoint))
|
| 60 |
+
self.vae = oobleck_vae.OobleckTT(self.dev, str(self.vae_path))
|
| 61 |
+
self.upsample = self.vae.upsample
|
| 62 |
+
if self.warm:
|
| 63 |
+
# Same reason worker.main() warms here: built now, the VAE's conv kernels compile
|
| 64 |
+
# in one device state and every later decode reuses the in-process programs. Built
|
| 65 |
+
# after a song's DiT steps instead, the state -- and so the kernels -- depends on
|
| 66 |
+
# the song length and recompiles for 20-40 s.
|
| 67 |
+
t0 = time.monotonic()
|
| 68 |
+
for window in VAE_WINDOWS:
|
| 69 |
+
self.vae.decode(torch.zeros(64, window))
|
| 70 |
+
self.stats["vae_warm_s"] = round(time.monotonic() - t0, 2)
|
| 71 |
+
self.stats["load_s"] = round(time.monotonic() - started, 2)
|
| 72 |
+
self.stats["dram"] = diffrhythm_dit.dram_stats(self.dev)
|
| 73 |
+
except BaseException:
|
| 74 |
+
self.close()
|
| 75 |
+
raise
|
| 76 |
+
return self
|
| 77 |
+
|
| 78 |
+
def wait_ready(self, timeout_s: float = 0.0) -> dict:
|
| 79 |
+
"""`TTWorker.wait_ready`'s shape. Loading already finished in `start`, so this only reports."""
|
| 80 |
+
if self.model is None:
|
| 81 |
+
raise RuntimeError("LocalWorker.start() has not run")
|
| 82 |
+
return {"op": "ready", "model": self.model_name, "upsample": self.upsample, **self.stats}
|
| 83 |
+
|
| 84 |
+
def died(self) -> bool:
|
| 85 |
+
"""There is no child process to lose; a failure here raised in the caller's own stack."""
|
| 86 |
+
return False
|
| 87 |
+
|
| 88 |
+
def close(self) -> None:
|
| 89 |
+
if self.song is not None and self.model is not None:
|
| 90 |
+
self.model.dit.release(self.song)
|
| 91 |
+
self.song = None
|
| 92 |
+
self.model = self.vae = None
|
| 93 |
+
if self.dev is not None:
|
| 94 |
+
diffrhythm_dit.close_device(self.dev)
|
| 95 |
+
self.dev = None
|
| 96 |
+
|
| 97 |
+
def __enter__(self):
|
| 98 |
+
return self.start()
|
| 99 |
+
|
| 100 |
+
def __exit__(self, *exc):
|
| 101 |
+
self.close()
|
| 102 |
+
|
| 103 |
+
# ------------------------------------------------------------------ calls
|
| 104 |
+
def call(self, msg: dict) -> dict:
|
| 105 |
+
"""`worker.py:serve`'s dispatch with the socket taken out and nothing else changed.
|
| 106 |
+
|
| 107 |
+
Messages still carry `wire`-encoded tensors even though both ends are now one process.
|
| 108 |
+
That is deliberate: it is what lets `RemoteDiffRhythmDiT` and `RemoteVae` run against this
|
| 109 |
+
worker unmodified, and the encoding is a float32 copy of a few MB against card work
|
| 110 |
+
measured in seconds."""
|
| 111 |
+
if self.model is None:
|
| 112 |
+
raise RuntimeError("LocalWorker.start() has not run")
|
| 113 |
+
op = msg.get("op")
|
| 114 |
+
started = time.monotonic()
|
| 115 |
+
try:
|
| 116 |
+
if op == "prepare":
|
| 117 |
+
if self.song is not None:
|
| 118 |
+
self.model.dit.release(self.song)
|
| 119 |
+
self.song = None
|
| 120 |
+
self.song, geometry = self.model.prepare(msg)
|
| 121 |
+
return {"op": "prepared", "seconds": time.monotonic() - started, "geometry": geometry}
|
| 122 |
+
if op == "forward":
|
| 123 |
+
if self.song is None:
|
| 124 |
+
raise RuntimeError("forward before prepare")
|
| 125 |
+
v = self.model.forward(msg, self.song)
|
| 126 |
+
return {"op": "velocity", "v": wire.encode(v), "seconds": time.monotonic() - started}
|
| 127 |
+
if op == "vae":
|
| 128 |
+
if self.song is not None:
|
| 129 |
+
# Sampling is over; freeing the song's tensors first gives the VAE the same
|
| 130 |
+
# device allocation state for every song length, so its compiled conv kernels
|
| 131 |
+
# are reused instead of recompiled.
|
| 132 |
+
self.model.dit.release(self.song)
|
| 133 |
+
self.song = None
|
| 134 |
+
audio = self.vae.decode(wire.decode(msg["z"]))
|
| 135 |
+
return {"op": "audio", "audio": wire.encode(audio), "seconds": time.monotonic() - started}
|
| 136 |
+
if op == "close":
|
| 137 |
+
self.close()
|
| 138 |
+
return {"op": "closed", "seconds": time.monotonic() - started}
|
| 139 |
+
except BaseException:
|
| 140 |
+
self.failed = True # the card may be unhealthy, same contract as TTWorker
|
| 141 |
+
raise
|
| 142 |
+
raise RuntimeError(f"unknown op {op!r}")
|
code/tt_diffrhythm/oobleck_vae.py
ADDED
|
@@ -0,0 +1,296 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""Oobleck audio VAE decoders on ONE Blackhole (P100a), TTNN: ACE-Step's (diffusers AutoencoderOobleck, 48 kHz
|
| 3 |
+
stereo, strides 10, 6, 4, 4, 2) and DiffRhythm's (stable-audio-tools, TorchScript, 44.1 kHz stereo, strides
|
| 4 |
+
8, 8, 4, 4, 2). Same architecture, different weight files:
|
| 5 |
+
|
| 6 |
+
conv1 (64 -> 2048, k7)
|
| 7 |
+
5 x block: Snake -> ConvTranspose1d (k = 2s, stride s) -> 3 x residual unit (dilation 1, 3, 9):
|
| 8 |
+
x + conv_k1(Snake(conv_k7_dilated(Snake(x))))
|
| 9 |
+
Snake -> conv2 (128 -> 2, k7, no bias)
|
| 10 |
+
Snake(x) = x + 1 / (exp(beta) + 1e-9) * sin(exp(alpha) * x)^2 per channel. Weight norm is folded on the host
|
| 11 |
+
(diffusers) or already folded (the TorchScript export).
|
| 12 |
+
|
| 13 |
+
Activations are [1, 1, L, C] (channels last), as ttnn.conv1d / conv_transpose2d (height 1) expect.
|
| 14 |
+
"""
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import json
|
| 18 |
+
import math
|
| 19 |
+
from dataclasses import dataclass
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
from typing import List, Optional
|
| 22 |
+
|
| 23 |
+
import torch
|
| 24 |
+
import ttnn
|
| 25 |
+
|
| 26 |
+
MEM = ttnn.DRAM_MEMORY_CONFIG
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def fold_weight_norm(g: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
"""torch weight_norm(dim=0): w = g * v / ||v|| over every dim but the first."""
|
| 31 |
+
norm = v.flatten(1).norm(dim=1).view(-1, *([1] * (v.dim() - 1)))
|
| 32 |
+
return g * v / norm
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class VaeWeights:
|
| 36 |
+
"""decoder.* of checkpoints/vae, float32, weight norm folded."""
|
| 37 |
+
|
| 38 |
+
def __init__(self, vae_dir):
|
| 39 |
+
from safetensors import safe_open
|
| 40 |
+
|
| 41 |
+
self.dir = Path(vae_dir)
|
| 42 |
+
self.config = json.loads((self.dir / "config.json").read_text())
|
| 43 |
+
self._f = safe_open(str(self.dir / "diffusion_pytorch_model.safetensors"), framework="pt")
|
| 44 |
+
self.keys = set(self._f.keys())
|
| 45 |
+
|
| 46 |
+
def get(self, name):
|
| 47 |
+
return self._f.get_tensor(f"decoder.{name}").float()
|
| 48 |
+
|
| 49 |
+
def conv(self, name):
|
| 50 |
+
w = fold_weight_norm(self.get(f"{name}.weight_g"), self.get(f"{name}.weight_v"))
|
| 51 |
+
b = self.get(f"{name}.bias") if f"decoder.{name}.bias" in self.keys else None
|
| 52 |
+
return w, b
|
| 53 |
+
|
| 54 |
+
def snake(self, name):
|
| 55 |
+
alpha = torch.exp(self.get(f"{name}.alpha")).flatten()
|
| 56 |
+
inv_beta = 1.0 / (torch.exp(self.get(f"{name}.beta")).flatten() + 1e-9)
|
| 57 |
+
return alpha, inv_beta
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class StableAudioVaeWeights:
|
| 61 |
+
"""decoder.* of DiffRhythm's vae_model.pt (a stable-audio-tools OobleckDecoder exported with TorchScript, weight
|
| 62 |
+
norm already folded), under VaeWeights' diffusers names."""
|
| 63 |
+
|
| 64 |
+
def __init__(self, path):
|
| 65 |
+
sd = torch.jit.load(str(path), map_location="cpu").state_dict()
|
| 66 |
+
self._sd = {k[len("decoder."):]: v for k, v in sd.items() if k.startswith("decoder.")}
|
| 67 |
+
strides = [self._sd[f"layers.{i + 1}.layers.1.weight"].shape[-1] // 2 for i in range(5)]
|
| 68 |
+
self.config = {"downsampling_ratios": strides}
|
| 69 |
+
|
| 70 |
+
@staticmethod
|
| 71 |
+
def _key(name):
|
| 72 |
+
parts = name.split(".")
|
| 73 |
+
if name == "conv1":
|
| 74 |
+
return "layers.0"
|
| 75 |
+
if name == "snake1":
|
| 76 |
+
return "layers.6"
|
| 77 |
+
if name == "conv2":
|
| 78 |
+
return "layers.7"
|
| 79 |
+
block = f"layers.{int(parts[1]) + 1}"
|
| 80 |
+
if parts[2] == "snake1":
|
| 81 |
+
return f"{block}.layers.0"
|
| 82 |
+
if parts[2] == "conv_t1":
|
| 83 |
+
return f"{block}.layers.1"
|
| 84 |
+
unit = f"{block}.layers.{int(parts[2][len('res_unit'):]) + 1}"
|
| 85 |
+
return f"{unit}.layers.{ {'snake1': 0, 'conv1': 1, 'snake2': 2, 'conv2': 3}[parts[3]] }"
|
| 86 |
+
|
| 87 |
+
def conv(self, name):
|
| 88 |
+
k = self._key(name)
|
| 89 |
+
return self._sd[f"{k}.weight"].float(), (self._sd[f"{k}.bias"].float() if f"{k}.bias" in self._sd else None)
|
| 90 |
+
|
| 91 |
+
def snake(self, name):
|
| 92 |
+
k = self._key(name)
|
| 93 |
+
alpha = torch.exp(self._sd[f"{k}.alpha"].float()).flatten()
|
| 94 |
+
inv_beta = 1.0 / (torch.exp(self._sd[f"{k}.beta"].float()).flatten() + 1e-9)
|
| 95 |
+
return alpha, inv_beta
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def vae_weights(path):
|
| 99 |
+
"""A diffusers VAE directory (ACE-Step) or a stable-audio TorchScript file (DiffRhythm)."""
|
| 100 |
+
path = Path(path)
|
| 101 |
+
if path.is_dir() and (path / "config.json").exists():
|
| 102 |
+
return VaeWeights(path)
|
| 103 |
+
return StableAudioVaeWeights(path / "vae_model.pt" if path.is_dir() else path)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
@dataclass
|
| 107 |
+
class Conv:
|
| 108 |
+
weight: object # ttnn host tensor, replaced by the prepared device tensor after the first call
|
| 109 |
+
bias: object
|
| 110 |
+
in_ch: int
|
| 111 |
+
out_ch: int
|
| 112 |
+
kernel: int
|
| 113 |
+
pad: int
|
| 114 |
+
dilation: int = 1
|
| 115 |
+
stride: int = 1
|
| 116 |
+
transpose: bool = False
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
@dataclass
|
| 120 |
+
class Snake:
|
| 121 |
+
alpha: ttnn.Tensor
|
| 122 |
+
inv_beta: ttnn.Tensor
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@dataclass
|
| 126 |
+
class ResUnit:
|
| 127 |
+
snake1: Snake
|
| 128 |
+
conv1: Conv
|
| 129 |
+
snake2: Snake
|
| 130 |
+
conv2: Conv
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@dataclass
|
| 134 |
+
class Block:
|
| 135 |
+
snake: Snake
|
| 136 |
+
up: Conv
|
| 137 |
+
units: List[ResUnit]
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@dataclass
|
| 141 |
+
class Precision:
|
| 142 |
+
fidelity: ttnn.MathFidelity = ttnn.MathFidelity.HiFi4 # HiFi2/LoFi are no faster here: the convs are not math-bound
|
| 143 |
+
act_dtype: ttnn.DataType = ttnn.bfloat16
|
| 144 |
+
# vae_conv_sweep.py: activation blocks of 64 rows make the 48 kHz k7 convs 4.9x and the output conv 6x faster
|
| 145 |
+
# than the default block height, same output; double buffering and bfp8 weights were slower. Applied to the
|
| 146 |
+
# layers with <= 256 channels (the long, late ones); the wide early layers keep the default (the override fails
|
| 147 |
+
# or is slower there).
|
| 148 |
+
act_block_h: int = 64
|
| 149 |
+
transpose_act_block_h: int = 64
|
| 150 |
+
act_block_h_max_channels: int = 256
|
| 151 |
+
config_tensors_in_dram: bool = True
|
| 152 |
+
fused_snake: bool = True
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
class OobleckTT:
|
| 156 |
+
def __init__(self, dev, vae_path, prec: Optional[Precision] = None):
|
| 157 |
+
self.dev = dev
|
| 158 |
+
self.prec = prec or Precision()
|
| 159 |
+
w = vae_weights(vae_path)
|
| 160 |
+
self.upsample = math.prod(w.config["downsampling_ratios"])
|
| 161 |
+
self.conv1 = self._conv(*w.conv("conv1"), pad=3)
|
| 162 |
+
self.blocks = []
|
| 163 |
+
for i in range(len(w.config["downsampling_ratios"])):
|
| 164 |
+
p = f"block.{i}"
|
| 165 |
+
wt, bt = w.conv(f"{p}.conv_t1") # ConvTranspose1d weight [in, out, 2s]
|
| 166 |
+
stride = wt.shape[-1] // 2
|
| 167 |
+
units = []
|
| 168 |
+
for j, dilation in enumerate((1, 3, 9)):
|
| 169 |
+
u = f"{p}.res_unit{j + 1}"
|
| 170 |
+
units.append(ResUnit(self._snake(*w.snake(f"{u}.snake1")),
|
| 171 |
+
self._conv(*w.conv(f"{u}.conv1"), pad=3 * dilation, dilation=dilation),
|
| 172 |
+
self._snake(*w.snake(f"{u}.snake2")), self._conv(*w.conv(f"{u}.conv2"), pad=0)))
|
| 173 |
+
self.blocks.append(Block(self._snake(*w.snake(f"{p}.snake1")),
|
| 174 |
+
self._conv(wt, bt, pad=math.ceil(stride / 2), stride=stride, transpose=True), units))
|
| 175 |
+
self.snake_out = self._snake(*w.snake("snake1"))
|
| 176 |
+
self.conv2 = self._conv(*w.conv("conv2"), pad=3)
|
| 177 |
+
self.ck = ttnn.init_device_compute_kernel_config(dev.arch(), math_fidelity=self.prec.fidelity,
|
| 178 |
+
math_approx_mode=False, fp32_dest_acc_en=True,
|
| 179 |
+
packer_l1_acc=False)
|
| 180 |
+
self.profile = None # a list to record (where, op, length, channels, seconds) per op, synchronizing each
|
| 181 |
+
self._where = ""
|
| 182 |
+
|
| 183 |
+
def _timed(self, op, length, channels, fn, *args):
|
| 184 |
+
if self.profile is None:
|
| 185 |
+
return fn(*args)
|
| 186 |
+
import time
|
| 187 |
+
|
| 188 |
+
ttnn.synchronize_device(self.dev)
|
| 189 |
+
started = time.perf_counter()
|
| 190 |
+
out = fn(*args)
|
| 191 |
+
ttnn.synchronize_device(self.dev)
|
| 192 |
+
self.profile.append((self._where, op, length, channels, time.perf_counter() - started))
|
| 193 |
+
return out
|
| 194 |
+
|
| 195 |
+
# ------------------------------------------------------------------ weights
|
| 196 |
+
def _conv(self, w, b, pad, dilation=1, stride=1, transpose=False):
|
| 197 |
+
if transpose: # [in, out, k] -> [in, out, 1, k]
|
| 198 |
+
in_ch, out_ch, k = w.shape
|
| 199 |
+
else: # [out, in, k] -> [out, in, 1, k]
|
| 200 |
+
out_ch, in_ch, k = w.shape
|
| 201 |
+
weight = ttnn.from_torch(w.unsqueeze(2).contiguous(), dtype=ttnn.float32)
|
| 202 |
+
bias = ttnn.from_torch(b.reshape(1, 1, 1, -1), dtype=ttnn.float32) if b is not None else None
|
| 203 |
+
return Conv(weight, bias, in_ch, out_ch, k, pad, dilation, stride, transpose)
|
| 204 |
+
|
| 205 |
+
def _snake(self, alpha, inv_beta):
|
| 206 |
+
row = lambda v: ttnn.from_torch(v.float().reshape(1, 1, 1, -1), dtype=self.prec.act_dtype,
|
| 207 |
+
layout=ttnn.TILE_LAYOUT, device=self.dev, memory_config=MEM)
|
| 208 |
+
return Snake(row(alpha), row(inv_beta))
|
| 209 |
+
|
| 210 |
+
# ------------------------------------------------------------------ ops
|
| 211 |
+
def _run_conv(self, x, c: Conv, length: int):
|
| 212 |
+
block_h = self.prec.transpose_act_block_h if c.transpose else self.prec.act_block_h
|
| 213 |
+
if max(c.in_ch, c.out_ch) > self.prec.act_block_h_max_channels:
|
| 214 |
+
block_h = 0
|
| 215 |
+
# smaller activation blocks need larger per-core config tensors; keep them in DRAM, not the 96 KiB L1_SMALL
|
| 216 |
+
cfg = ttnn.Conv2dConfig(weights_dtype=ttnn.bfloat16, config_tensors_in_dram=self.prec.config_tensors_in_dram)
|
| 217 |
+
if block_h:
|
| 218 |
+
cfg.act_block_h_override = block_h
|
| 219 |
+
common = dict(device=self.dev, in_channels=c.in_ch, out_channels=c.out_ch, batch_size=1, bias_tensor=c.bias,
|
| 220 |
+
dtype=self.prec.act_dtype, compute_config=self.ck, conv_config=cfg,
|
| 221 |
+
return_weights_and_bias=True)
|
| 222 |
+
if c.transpose:
|
| 223 |
+
out, out_len, (c.weight, c.bias) = ttnn.conv_transpose2d(
|
| 224 |
+
input_tensor=x, weight_tensor=c.weight, input_height=1, input_width=length, kernel_size=(1, c.kernel),
|
| 225 |
+
stride=(1, c.stride), padding=(0, c.pad), output_padding=(0, 0), dilation=(1, 1), groups=1,
|
| 226 |
+
return_output_dim=True, **common)
|
| 227 |
+
out_len = out_len[1] if isinstance(out_len, (tuple, list)) else out_len
|
| 228 |
+
else:
|
| 229 |
+
out, out_len, (c.weight, c.bias) = ttnn.conv1d(
|
| 230 |
+
input_tensor=x, weight_tensor=c.weight, input_length=length, kernel_size=c.kernel, stride=1,
|
| 231 |
+
padding=c.pad, dilation=c.dilation, groups=1, return_output_dim=True, **common)
|
| 232 |
+
if out.layout != ttnn.TILE_LAYOUT:
|
| 233 |
+
out = ttnn.to_layout(out, ttnn.TILE_LAYOUT)
|
| 234 |
+
return ttnn.reshape(out, [1, 1, out_len, c.out_ch]), out_len
|
| 235 |
+
|
| 236 |
+
def _snake_op(self, x, s: Snake):
|
| 237 |
+
if self.prec.fused_snake: # sin(alpha x)^2 in one multiply, then x + inv_beta * that in one addcmul: 2 passes
|
| 238 |
+
unary = lambda op: ttnn.UnaryWithParam(op)
|
| 239 |
+
sq = ttnn.multiply(x, s.alpha, activations=[unary(ttnn.UnaryOpType.SIN), unary(ttnn.UnaryOpType.SQUARE)],
|
| 240 |
+
memory_config=MEM)
|
| 241 |
+
out = ttnn.addcmul(x, sq, s.inv_beta, value=1.0, memory_config=MEM)
|
| 242 |
+
ttnn.deallocate(sq)
|
| 243 |
+
return out
|
| 244 |
+
ax = ttnn.multiply(x, s.alpha, memory_config=MEM)
|
| 245 |
+
sn = ttnn.sin(ax, memory_config=MEM)
|
| 246 |
+
ttnn.deallocate(ax)
|
| 247 |
+
sq = ttnn.multiply(sn, sn, memory_config=MEM)
|
| 248 |
+
ttnn.deallocate(sn)
|
| 249 |
+
t = ttnn.multiply(sq, s.inv_beta, memory_config=MEM)
|
| 250 |
+
ttnn.deallocate(sq)
|
| 251 |
+
out = ttnn.add(x, t, memory_config=MEM)
|
| 252 |
+
ttnn.deallocate(t)
|
| 253 |
+
return out
|
| 254 |
+
|
| 255 |
+
def _unit(self, x, u: ResUnit, length):
|
| 256 |
+
c = u.conv1.out_ch
|
| 257 |
+
h = self._timed("snake", length, c, self._snake_op, x, u.snake1)
|
| 258 |
+
y, _ = self._timed(f"conv_k7_d{u.conv1.dilation}", length, c, self._run_conv, h, u.conv1, length)
|
| 259 |
+
ttnn.deallocate(h)
|
| 260 |
+
h = self._timed("snake", length, c, self._snake_op, y, u.snake2)
|
| 261 |
+
ttnn.deallocate(y)
|
| 262 |
+
y, _ = self._timed("conv_k1", length, c, self._run_conv, h, u.conv2, length)
|
| 263 |
+
ttnn.deallocate(h)
|
| 264 |
+
out = self._timed("add", length, c, lambda a, b: ttnn.add(a, b, memory_config=MEM), x, y)
|
| 265 |
+
ttnn.deallocate(y)
|
| 266 |
+
ttnn.deallocate(x)
|
| 267 |
+
return out
|
| 268 |
+
|
| 269 |
+
# ------------------------------------------------------------------ public
|
| 270 |
+
def decode(self, latents: torch.Tensor, taps: Optional[list] = None) -> torch.Tensor:
|
| 271 |
+
"""[64, T] latents -> [2, T * upsample] float32 audio (1920 for ACE-Step, 2048 for DiffRhythm)."""
|
| 272 |
+
length = latents.shape[-1]
|
| 273 |
+
x = ttnn.from_torch(latents.t().contiguous().reshape(1, 1, length, -1).float(), dtype=self.prec.act_dtype,
|
| 274 |
+
layout=ttnn.TILE_LAYOUT, device=self.dev, memory_config=MEM)
|
| 275 |
+
tap = (lambda t, n: taps.append(ttnn.to_torch(t)[0, 0, :n].float())) if taps is not None else (lambda t, n: None)
|
| 276 |
+
self._where = "in"
|
| 277 |
+
h, length = self._timed("conv_in", length, self.conv1.out_ch, self._run_conv, x, self.conv1, length)
|
| 278 |
+
ttnn.deallocate(x)
|
| 279 |
+
tap(h, length)
|
| 280 |
+
for i, b in enumerate(self.blocks):
|
| 281 |
+
self._where = f"block{i}"
|
| 282 |
+
s = self._timed("snake", length, b.up.in_ch, self._snake_op, h, b.snake)
|
| 283 |
+
ttnn.deallocate(h)
|
| 284 |
+
h, length = self._timed(f"conv_t_s{b.up.stride}", length, b.up.out_ch, self._run_conv, s, b.up, length)
|
| 285 |
+
ttnn.deallocate(s)
|
| 286 |
+
for u in b.units:
|
| 287 |
+
h = self._unit(h, u, length)
|
| 288 |
+
tap(h, length)
|
| 289 |
+
self._where = "out"
|
| 290 |
+
s = self._timed("snake", length, self.snake_out.alpha.shape[-1], self._snake_op, h, self.snake_out)
|
| 291 |
+
ttnn.deallocate(h)
|
| 292 |
+
out, length = self._timed("conv_out", length, 2, self._run_conv, s, self.conv2, length)
|
| 293 |
+
ttnn.deallocate(s)
|
| 294 |
+
audio = ttnn.to_torch(out)[0, 0, :length, :2].float().t().contiguous()
|
| 295 |
+
ttnn.deallocate(out)
|
| 296 |
+
return audio
|
code/tt_diffrhythm/remote.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Host side of the P100a models: start the TT worker container on the card and stand in for the DiT and
|
| 2 |
+
the audio VAE decoder of the host pipeline.
|
| 3 |
+
|
| 4 |
+
The ACE-Step port is the sibling repo, tt-acestep: it shares this worker and transport.
|
| 5 |
+
|
| 6 |
+
Used from the DiffRhythm host pipeline: TTWorker(cfm_model.pt, model="diffrhythm", vae=vae_model.pt),
|
| 7 |
+
cfm.transformer = RemoteDiffRhythmDiT(worker, cfm.transformer), decoding with RemoteVae(worker, upsample=2048).
|
| 8 |
+
|
| 9 |
+
Runs in the model's own environment; needs only the standard library and torch.
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from multiprocessing.connection import Client
|
| 14 |
+
import os
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
import secrets
|
| 17 |
+
import shutil
|
| 18 |
+
import subprocess
|
| 19 |
+
import tempfile
|
| 20 |
+
import time
|
| 21 |
+
|
| 22 |
+
import torch
|
| 23 |
+
|
| 24 |
+
from . import wire
|
| 25 |
+
from .shapes import VAE_WINDOWS
|
| 26 |
+
|
| 27 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 28 |
+
# The tt-metal image the worker runs in. There is no portable default: set MUSIC_TT_IMAGE,
|
| 29 |
+
# or pass image= , to the tt-metal / ttnn image you built the port against.
|
| 30 |
+
DEFAULT_IMAGE = ""
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class WorkerError(RuntimeError):
|
| 34 |
+
"""The P100a worker failed (died, hung or reported an error)."""
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class TTWorker:
|
| 38 |
+
"""checkpoint is cfm_model.pt and vae is vae_model.pt (Hugging Face cache links are resolved)."""
|
| 39 |
+
|
| 40 |
+
def __init__(self, checkpoint: Path, image: str = None, log=None,
|
| 41 |
+
reply_timeout_s: float = 120.0, # the slowest call (an 8-minute song's DiT step or VAE window) is < 1 s
|
| 42 |
+
model: str = "diffrhythm", vae: Path = None):
|
| 43 |
+
self.checkpoint = Path(checkpoint)
|
| 44 |
+
self.model = model
|
| 45 |
+
self.vae = Path(vae) if vae is not None else None
|
| 46 |
+
self.image = image or os.environ.get("MUSIC_TT_IMAGE") or DEFAULT_IMAGE
|
| 47 |
+
if not self.image:
|
| 48 |
+
raise ValueError("set MUSIC_TT_IMAGE (or pass image=) to the tt-metal image the worker runs in")
|
| 49 |
+
self.log = log
|
| 50 |
+
self.reply_timeout_s = reply_timeout_s
|
| 51 |
+
self.proc = self.conn = self.ipc = None
|
| 52 |
+
self.name = f"music-tt-{os.getpid()}-{secrets.token_hex(3)}"
|
| 53 |
+
self.stats = {}
|
| 54 |
+
self.failed = False # set when the worker died, hung or reported an error: the card may be unhealthy
|
| 55 |
+
|
| 56 |
+
# ------------------------------------------------------------------ lifecycle
|
| 57 |
+
def start(self) -> "TTWorker":
|
| 58 |
+
try:
|
| 59 |
+
cache = ROOT / "data/tt-cache"
|
| 60 |
+
cache.mkdir(parents=True, exist_ok=True)
|
| 61 |
+
run_dir = ROOT / "data/tt-run"
|
| 62 |
+
run_dir.mkdir(parents=True, exist_ok=True)
|
| 63 |
+
self.ipc = Path(tempfile.mkdtemp(prefix="dit-", dir=run_dir))
|
| 64 |
+
self.authkey = secrets.token_hex(16)
|
| 65 |
+
mounts = [("/dev/hugepages-1G", "/dev/hugepages-1G", False), (ROOT, "/work", True),
|
| 66 |
+
*self._model_mounts(), (cache, "/cache", False), (self.ipc, "/ipc", False)]
|
| 67 |
+
cmd = ["docker", "run", "--rm", "--name", self.name, "--ipc", "host", "--device", "/dev/tenstorrent"]
|
| 68 |
+
for src, dst, ro in mounts:
|
| 69 |
+
cmd += ["--mount", f"type=bind,src={src},dst={dst}" + (",readonly" if ro else "")]
|
| 70 |
+
for k, v in {"TT_METAL_VISIBLE_DEVICES": "0", "MESH_DEVICE": "P100", "PYTHONUNBUFFERED": "1",
|
| 71 |
+
"TT_METAL_OPERATION_TIMEOUT_SECONDS": "90", "MUSIC_TT_AUTHKEY": self.authkey}.items():
|
| 72 |
+
cmd += ["-e", f"{k}={v}"]
|
| 73 |
+
cmd += [self.image, "python", "/work/tt_diffrhythm/worker.py", "--socket", "/ipc/worker.sock", "--model", self.model]
|
| 74 |
+
# own session: a supervisor may stop the host process by signalling its process group, and a signal reaching
|
| 75 |
+
# `docker run` directly would stop the container while this process still has to wait for it
|
| 76 |
+
self.proc = subprocess.Popen(cmd, stdout=self.log or subprocess.DEVNULL, stderr=subprocess.STDOUT,
|
| 77 |
+
start_new_session=True)
|
| 78 |
+
self._started = time.monotonic()
|
| 79 |
+
except BaseException:
|
| 80 |
+
self.close()
|
| 81 |
+
raise
|
| 82 |
+
return self
|
| 83 |
+
|
| 84 |
+
def _model_mounts(self):
|
| 85 |
+
if self.model == "diffrhythm": # files, mounted where worker.py expects them
|
| 86 |
+
return [(self.checkpoint.resolve(), "/checkpoint/cfm_model.pt", True),
|
| 87 |
+
(self.vae.resolve(), "/vae/vae_model.pt", True)]
|
| 88 |
+
raise ValueError(f"unknown model {self.model!r}")
|
| 89 |
+
|
| 90 |
+
def died(self) -> bool:
|
| 91 |
+
"""The worker container exited on its own (not through close())."""
|
| 92 |
+
return self.proc is not None and self.proc.poll() is not None
|
| 93 |
+
|
| 94 |
+
def _fail(self, message):
|
| 95 |
+
self.failed = True
|
| 96 |
+
raise WorkerError(message)
|
| 97 |
+
|
| 98 |
+
def wait_ready(self, timeout_s: float = 300.0):
|
| 99 |
+
sock = self.ipc / "worker.sock"
|
| 100 |
+
deadline = time.monotonic() + timeout_s
|
| 101 |
+
while not sock.exists():
|
| 102 |
+
self._check_alive()
|
| 103 |
+
if time.monotonic() > deadline:
|
| 104 |
+
self._fail("TT worker did not open its socket")
|
| 105 |
+
time.sleep(0.2)
|
| 106 |
+
try:
|
| 107 |
+
self.conn = Client(str(sock), family="AF_UNIX", authkey=self.authkey.encode())
|
| 108 |
+
except (OSError, EOFError) as exc: # the worker died while loading
|
| 109 |
+
self._fail(f"TT worker connection failed: {exc}")
|
| 110 |
+
try:
|
| 111 |
+
ready = self._recv(timeout_s)
|
| 112 |
+
except (OSError, EOFError) as exc:
|
| 113 |
+
self._fail(f"TT worker connection lost: {exc}")
|
| 114 |
+
self.stats.update(worker_load_s=round(ready["load_s"], 2), start_to_ready_s=round(time.monotonic() - self._started, 2))
|
| 115 |
+
return ready
|
| 116 |
+
|
| 117 |
+
def close(self):
|
| 118 |
+
"""Stop the worker and wait for the container to exit; only then give the card back."""
|
| 119 |
+
try:
|
| 120 |
+
if self.conn is not None:
|
| 121 |
+
try:
|
| 122 |
+
self.conn.send({"op": "close"})
|
| 123 |
+
except OSError:
|
| 124 |
+
pass
|
| 125 |
+
self.conn.close()
|
| 126 |
+
if self.proc is not None:
|
| 127 |
+
try:
|
| 128 |
+
self.proc.wait(60)
|
| 129 |
+
except subprocess.TimeoutExpired:
|
| 130 |
+
subprocess.run(["docker", "stop", "-t", "20", self.name], capture_output=True, timeout=60)
|
| 131 |
+
self.proc.wait(60)
|
| 132 |
+
finally:
|
| 133 |
+
self.conn = None
|
| 134 |
+
if self.ipc is not None:
|
| 135 |
+
shutil.rmtree(self.ipc, ignore_errors=True)
|
| 136 |
+
|
| 137 |
+
def __enter__(self):
|
| 138 |
+
return self.start()
|
| 139 |
+
|
| 140 |
+
def __exit__(self, *exc):
|
| 141 |
+
self.close()
|
| 142 |
+
|
| 143 |
+
# ------------------------------------------------------------------ calls
|
| 144 |
+
def _check_alive(self):
|
| 145 |
+
if self.proc is not None and self.proc.poll() is not None:
|
| 146 |
+
self._fail(f"TT worker exited ({self.proc.returncode}); see the worker log")
|
| 147 |
+
|
| 148 |
+
def _recv(self, timeout_s):
|
| 149 |
+
deadline = time.monotonic() + timeout_s
|
| 150 |
+
while not self.conn.poll(1.0):
|
| 151 |
+
self._check_alive()
|
| 152 |
+
if time.monotonic() > deadline:
|
| 153 |
+
self._fail("TT worker did not answer in time")
|
| 154 |
+
msg = self.conn.recv()
|
| 155 |
+
if msg.get("op") == "error":
|
| 156 |
+
self._fail(msg["error"])
|
| 157 |
+
return msg
|
| 158 |
+
|
| 159 |
+
def call(self, msg: dict) -> dict:
|
| 160 |
+
try:
|
| 161 |
+
self.conn.send(msg)
|
| 162 |
+
return self._recv(self.reply_timeout_s)
|
| 163 |
+
except (OSError, EOFError) as exc: # the socket broke: the worker is gone
|
| 164 |
+
self._fail(f"TT worker connection lost: {exc}")
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class RemoteDiffRhythmDiT(torch.nn.Module):
|
| 168 |
+
"""Drop-in for DiffRhythm's DiT (cfm.transformer) in CFM.sample (batch 1): same call, same output.
|
| 169 |
+
|
| 170 |
+
Keeps upstream's small modules on the host, exactly as upstream runs them: the text embedding (computed once per
|
| 171 |
+
song and CFG branch; upstream recomputes the same thing every call) and the time / start / duration embeddings.
|
| 172 |
+
Everything weight-heavy (input projection, position convs, 16 Llama layers, output) runs on the card."""
|
| 173 |
+
|
| 174 |
+
def __init__(self, worker: TTWorker, dit):
|
| 175 |
+
super().__init__()
|
| 176 |
+
self.worker = worker
|
| 177 |
+
self.text_embed, self.time_embed = dit.text_embed, dit.time_embed
|
| 178 |
+
self.start_time_embed, self.duration_time_embed = dit.start_time_embed, dit.duration_time_embed
|
| 179 |
+
self._song = None
|
| 180 |
+
self.seconds = []
|
| 181 |
+
|
| 182 |
+
def reset(self) -> None:
|
| 183 |
+
"""Forget which song is prepared on the card.
|
| 184 |
+
|
| 185 |
+
The worker releases the song's tensors when the VAE runs, so that every song length meets
|
| 186 |
+
the same device allocation state and the VAE's compiled kernels are reused. After that the
|
| 187 |
+
card holds no song, and this cache -- which exists to skip re-sending an unchanged text and
|
| 188 |
+
conditioning -- would otherwise send a `forward` for a song that is no longer there.
|
| 189 |
+
|
| 190 |
+
A caller that builds a shim per song never notices. One that keeps it across songs, as a
|
| 191 |
+
server does, has to call this between them.
|
| 192 |
+
"""
|
| 193 |
+
self._song = None
|
| 194 |
+
|
| 195 |
+
def forward(self, x, cond, text, time, drop_audio_cond, drop_text, drop_prompt=False, style_prompt=None,
|
| 196 |
+
start_time=None, duration=None):
|
| 197 |
+
if x.shape[0] != 1:
|
| 198 |
+
raise ValueError("The P100a DiT runs batch 1")
|
| 199 |
+
if bool(drop_audio_cond) != bool(drop_text):
|
| 200 |
+
raise ValueError("The P100a DiT runs CFM.sample's two branches (both conditions kept or both dropped)")
|
| 201 |
+
seq_len = x.shape[1]
|
| 202 |
+
if self._song is None or not (torch.equal(text, self._song[0]) and torch.equal(cond, self._song[1])):
|
| 203 |
+
embeds = {null: wire.encode(self.text_embed(text, seq_len, drop_text=null)[0]) for null in (False, True)}
|
| 204 |
+
self.worker.call({"op": "prepare", "text_embed": embeds, "cond": wire.encode(cond[0])})
|
| 205 |
+
self._song = (text.clone(), cond.clone())
|
| 206 |
+
if time.ndim == 0:
|
| 207 |
+
time = time.repeat(1)
|
| 208 |
+
dtype = next(self.time_embed.parameters()).dtype # upstream hands over fp16 style/start/duration tensors
|
| 209 |
+
c = (self.time_embed(time.to(dtype)) + self.start_time_embed(start_time.to(dtype))
|
| 210 |
+
+ self.duration_time_embed(duration.to(dtype)))
|
| 211 |
+
style = torch.zeros_like(style_prompt) if drop_prompt else style_prompt
|
| 212 |
+
reply = self.worker.call({"op": "forward", "x": wire.encode(x[0]), "drop_text": bool(drop_text),
|
| 213 |
+
"style": wire.encode(style[0]), "c": wire.encode(c[0])})
|
| 214 |
+
self.seconds.append(reply["seconds"])
|
| 215 |
+
return wire.decode(reply["v"]).to(x.dtype)[None]
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
class RemoteVae:
|
| 219 |
+
"""Stands in for handler.tiled_decode: overlap-discard tiling like upstream's, but every window of a song has the
|
| 220 |
+
same length (the last one is moved back to end at the song's end), so the P100a compiles only a few shapes.
|
| 221 |
+
|
| 222 |
+
The window is the largest of `chunks` that fits the song: 1024 frames (41 s) decode at 0.61 ms per frame with
|
| 223 |
+
12.5 % overlap, 512 frames at the same rate with 25 % overlap (vae_profile.py); longer windows get slower per
|
| 224 |
+
frame. Songs of 30-41 s use 512, so no song compiles a shape of its own.
|
| 225 |
+
|
| 226 |
+
latents [B, 64, T] -> audio [B, 2, T * upsample] float32 on the CPU (1920 for ACE-Step, 2048 for DiffRhythm)."""
|
| 227 |
+
|
| 228 |
+
def __init__(self, worker: TTWorker, chunks=VAE_WINDOWS, overlap: int = 64, upsample: int = 1920):
|
| 229 |
+
self.worker, self.chunks, self.overlap, self.upsample = worker, tuple(sorted(chunks, reverse=True)), overlap, upsample
|
| 230 |
+
self.seconds = []
|
| 231 |
+
|
| 232 |
+
def chunk_for(self, frames: int) -> int:
|
| 233 |
+
return next((c for c in self.chunks if c <= frames), self.chunks[-1])
|
| 234 |
+
|
| 235 |
+
@staticmethod
|
| 236 |
+
def windows(frames: int, chunk: int, overlap: int):
|
| 237 |
+
"""(window_start, core_start, core_end) covering [0, frames); windows are exactly `chunk` frames long
|
| 238 |
+
(or the whole song when it is shorter) and keep >= overlap frames of context around each core."""
|
| 239 |
+
if frames <= chunk:
|
| 240 |
+
return [(0, 0, frames)]
|
| 241 |
+
stride = chunk - 2 * overlap
|
| 242 |
+
out = []
|
| 243 |
+
for core_start in range(0, frames, stride):
|
| 244 |
+
core_end = min(core_start + stride, frames)
|
| 245 |
+
out.append((min(max(core_start - overlap, 0), frames - chunk), core_start, core_end))
|
| 246 |
+
return out
|
| 247 |
+
|
| 248 |
+
def _decode(self, z: torch.Tensor) -> torch.Tensor:
|
| 249 |
+
reply = self.worker.call({"op": "vae", "z": wire.encode(z)})
|
| 250 |
+
self.seconds.append(reply["seconds"])
|
| 251 |
+
return wire.decode(reply["audio"])
|
| 252 |
+
|
| 253 |
+
def __call__(self, latents, chunk_size=None, overlap=None, offload_wav_to_cpu=None):
|
| 254 |
+
out = []
|
| 255 |
+
for z in latents.detach().float().cpu():
|
| 256 |
+
frames = z.shape[-1]
|
| 257 |
+
chunk = self.chunk_for(frames)
|
| 258 |
+
pieces = []
|
| 259 |
+
for win_start, core_start, core_end in self.windows(frames, chunk, self.overlap):
|
| 260 |
+
win_end = min(win_start + chunk, frames)
|
| 261 |
+
audio = self._decode(z[:, win_start:win_end].contiguous())
|
| 262 |
+
pieces.append(audio[:, (core_start - win_start) * self.upsample:(core_end - win_start) * self.upsample])
|
| 263 |
+
out.append(torch.cat(pieces, dim=-1))
|
| 264 |
+
return torch.stack(out)
|
code/tt_diffrhythm/server.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
"""HTTP server for DiffRhythm 1.2 Full on one Blackhole p100a, everything in one process.
|
| 3 |
+
|
| 4 |
+
uvicorn tt_diffrhythm.server:app --host 0.0.0.0 --port 20000
|
| 5 |
+
|
| 6 |
+
DiffRhythm's own sampling loop runs here on the CPU and the CFM transformer and the stable-audio
|
| 7 |
+
VAE decoder run on the card, joined by `LocalWorker` instead of the Unix socket `remote.py` uses.
|
| 8 |
+
That split existed because DiffRhythm and tt-metal could not share an interpreter in the
|
| 9 |
+
deployment this port came from; an image built for this model alone resolves one environment that
|
| 10 |
+
satisfies both, so the socket is unnecessary there. See `local.py`.
|
| 11 |
+
|
| 12 |
+
Requires DiffRhythm itself (its `model`, `infer` and `g2p` trees) importable beside this one -- it
|
| 13 |
+
is the model, and this package only moves two of its stages onto the card.
|
| 14 |
+
"""
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import base64
|
| 18 |
+
import io
|
| 19 |
+
import os
|
| 20 |
+
import random
|
| 21 |
+
import re
|
| 22 |
+
import sys
|
| 23 |
+
import time
|
| 24 |
+
from contextlib import asynccontextmanager
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
from threading import Lock
|
| 27 |
+
from typing import Optional
|
| 28 |
+
|
| 29 |
+
import torch
|
| 30 |
+
from fastapi import FastAPI, HTTPException
|
| 31 |
+
from pydantic import BaseModel, ConfigDict, Field
|
| 32 |
+
|
| 33 |
+
from .local import LocalWorker
|
| 34 |
+
from .remote import RemoteDiffRhythmDiT, RemoteVae
|
| 35 |
+
|
| 36 |
+
LICENSE = "apache-2.0"
|
| 37 |
+
WEIGHTS_REPO = "ASLP-lab/DiffRhythm-1_2-full"
|
| 38 |
+
VAE_REPO = "ASLP-lab/DiffRhythm-vae"
|
| 39 |
+
STYLE_REPO = "OpenMuQ/MuQ-MuLan-large"
|
| 40 |
+
STEPS = 32 # the schedule this port's shapes are compiled for
|
| 41 |
+
CFG_STRENGTH = 4.0
|
| 42 |
+
MAX_FRAMES = 6144 # the "full" checkpoint: 285 seconds at 21.5 frames per second
|
| 43 |
+
MIN_DURATION, MAX_DURATION = 96, 285
|
| 44 |
+
SAMPLE_RATE = 44100
|
| 45 |
+
UPSAMPLE = 2048 # VAE frames -> audio samples
|
| 46 |
+
TURN_WAIT_S = float(os.environ.get("MUSIC_TURN_WAIT_S", "1800"))
|
| 47 |
+
|
| 48 |
+
# DiffRhythm reads `./config/diffrhythm-1b.json` and `./g2p/g2p/vocab.json` relative to the working
|
| 49 |
+
# directory, so the server runs from the checkout rather than rewriting upstream's path handling.
|
| 50 |
+
DIFFRHYTHM_ROOT = os.environ.get("DIFFRHYTHM_ROOT", "")
|
| 51 |
+
|
| 52 |
+
TIMESTAMP = re.compile(r"^\[(\d{1,2}):(\d{2}(?:\.\d{1,3})?)\]\s*(.*)$")
|
| 53 |
+
SECTION = re.compile(r"^\[[A-Za-z][^\]]*\]$")
|
| 54 |
+
|
| 55 |
+
STATE: dict = {"status": "loading", "error": None, "generating": False, "load_s": None, "dram": None}
|
| 56 |
+
WORKER: Optional[LocalWorker] = None
|
| 57 |
+
CFM = TOKENIZER = STYLE = None
|
| 58 |
+
LOCK = Lock()
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _root() -> Path:
|
| 62 |
+
"""The DiffRhythm checkout: DIFFRHYTHM_ROOT, or the one shipped beside this package."""
|
| 63 |
+
if DIFFRHYTHM_ROOT:
|
| 64 |
+
return Path(DIFFRHYTHM_ROOT).resolve()
|
| 65 |
+
here = Path(__file__).resolve().parent.parent
|
| 66 |
+
if (here / "infer" / "infer_utils.py").is_file():
|
| 67 |
+
return here
|
| 68 |
+
raise RuntimeError("set DIFFRHYTHM_ROOT to the DiffRhythm checkout (the directory holding infer/)")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def _point_phonemizer_at_a_bundled_espeak() -> None:
|
| 72 |
+
"""Let phonemizer find a libespeak-ng that came from a wheel instead of a system package.
|
| 73 |
+
|
| 74 |
+
`g2p.utils.g2p` builds its `EspeakBackend`s at import, so espeak has to be resolvable before
|
| 75 |
+
upstream is imported at all. Everywhere but a container that means `apt install espeak-ng`;
|
| 76 |
+
the `espeakng-loader` wheel carries the library and its data instead. Both have to be set --
|
| 77 |
+
the library alone leaves espeak looking for `phontab` under the path it was built at, which
|
| 78 |
+
does not exist here.
|
| 79 |
+
|
| 80 |
+
Does nothing when the wheel is absent, or when PHONEMIZER_ESPEAK_LIBRARY already names a
|
| 81 |
+
library: a system espeak-ng is the better answer where there is one, and an explicit choice
|
| 82 |
+
is not ours to override. (Asking `EspeakWrapper` whether one is set is not the way to decide
|
| 83 |
+
-- `library_path` is a property on the class, so reading it off the class hands back the
|
| 84 |
+
property object, which is never None.)
|
| 85 |
+
"""
|
| 86 |
+
if os.environ.get("PHONEMIZER_ESPEAK_LIBRARY"):
|
| 87 |
+
return
|
| 88 |
+
try:
|
| 89 |
+
import espeakng_loader
|
| 90 |
+
from phonemizer.backend.espeak.wrapper import EspeakWrapper
|
| 91 |
+
except ImportError:
|
| 92 |
+
return
|
| 93 |
+
EspeakWrapper.set_library(str(espeakng_loader.get_library_path()))
|
| 94 |
+
if hasattr(EspeakWrapper, "set_data_path"):
|
| 95 |
+
EspeakWrapper.set_data_path(str(espeakng_loader.get_data_path()))
|
| 96 |
+
else: # older phonemizer: espeak reads the data path from its own environment variable
|
| 97 |
+
os.environ.setdefault("ESPEAK_DATA_PATH", str(espeakng_loader.get_data_path()))
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _enter_checkout() -> Path:
|
| 101 |
+
"""Make upstream importable and its relative paths resolve, the way its own scripts do."""
|
| 102 |
+
root = _root()
|
| 103 |
+
_point_phonemizer_at_a_bundled_espeak()
|
| 104 |
+
for path in (root, root / "infer"):
|
| 105 |
+
if str(path) not in sys.path:
|
| 106 |
+
sys.path.insert(0, str(path))
|
| 107 |
+
os.chdir(root)
|
| 108 |
+
return root
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _load_cfm(root: Path, device: str = "cpu"):
|
| 112 |
+
"""`infer_utils.prepare_model` without the VAE (the card decodes) and without its
|
| 113 |
+
CWD-relative download cache, so the weights land in the ordinary HF cache."""
|
| 114 |
+
import json
|
| 115 |
+
|
| 116 |
+
from huggingface_hub import hf_hub_download
|
| 117 |
+
from infer_utils import CNENTokenizer, load_checkpoint
|
| 118 |
+
from model import CFM as CFMModel
|
| 119 |
+
from model import DiT
|
| 120 |
+
|
| 121 |
+
with open(root / "config" / "diffrhythm-1b.json") as f:
|
| 122 |
+
config = json.load(f)
|
| 123 |
+
cfm = CFMModel(transformer=DiT(**config["model"], max_frames=MAX_FRAMES),
|
| 124 |
+
num_channels=config["model"]["mel_dim"], max_frames=MAX_FRAMES).to(device)
|
| 125 |
+
cfm = load_checkpoint(cfm, hf_hub_download(WEIGHTS_REPO, "cfm_model.pt"), device=device, use_ema=False)
|
| 126 |
+
return cfm, CNENTokenizer()
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def _style_model(device: str = "cpu"):
|
| 130 |
+
from muq import MuQMuLan
|
| 131 |
+
|
| 132 |
+
return MuQMuLan.from_pretrained(STYLE_REPO).to(device).eval().float()
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
@asynccontextmanager
|
| 136 |
+
async def lifespan(_app: FastAPI):
|
| 137 |
+
global WORKER, CFM, TOKENIZER, STYLE
|
| 138 |
+
started = time.monotonic()
|
| 139 |
+
try:
|
| 140 |
+
import gc
|
| 141 |
+
|
| 142 |
+
from huggingface_hub import hf_hub_download
|
| 143 |
+
|
| 144 |
+
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 145 |
+
root = _enter_checkout()
|
| 146 |
+
WORKER = LocalWorker(hf_hub_download(WEIGHTS_REPO, "cfm_model.pt"),
|
| 147 |
+
vae=hf_hub_download(VAE_REPO, "vae_model.pt")).start()
|
| 148 |
+
cfm, TOKENIZER = _load_cfm(root)
|
| 149 |
+
# Replacing the transformer drops the host copy of the big weights; the card holds them.
|
| 150 |
+
cfm.transformer = RemoteDiffRhythmDiT(WORKER, cfm.transformer)
|
| 151 |
+
CFM = cfm.float().eval()
|
| 152 |
+
STYLE = _style_model()
|
| 153 |
+
gc.collect()
|
| 154 |
+
STATE.update(status="ok", load_s=round(time.monotonic() - started, 2),
|
| 155 |
+
dram=WORKER.stats.get("dram"), vae_warm_s=WORKER.stats.get("vae_warm_s"))
|
| 156 |
+
except Exception as exc:
|
| 157 |
+
STATE.update(status="error", error=f"{type(exc).__name__}: {exc}")
|
| 158 |
+
try:
|
| 159 |
+
yield
|
| 160 |
+
finally:
|
| 161 |
+
CFM = TOKENIZER = STYLE = None
|
| 162 |
+
if WORKER is not None:
|
| 163 |
+
WORKER.close()
|
| 164 |
+
WORKER = None
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
app = FastAPI(title="DiffRhythm 1.2 Full on p100a", lifespan=lifespan, docs_url=None, redoc_url=None)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
class Request(BaseModel):
|
| 171 |
+
model_config = ConfigDict(extra="ignore")
|
| 172 |
+
prompt: str = Field(min_length=1, max_length=4000, description="the style description")
|
| 173 |
+
lyrics: str = Field(default="", max_length=20000, description="LRC, or plain lines to be timed")
|
| 174 |
+
instrumental: bool = False
|
| 175 |
+
duration: int = 180
|
| 176 |
+
seed: Optional[int] = Field(default=None, ge=0, le=4294967295)
|
| 177 |
+
inference_steps: Optional[int] = None
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def _stamp(seconds: float) -> str:
|
| 181 |
+
centiseconds = round(seconds * 100)
|
| 182 |
+
minutes, remainder = divmod(centiseconds, 6000)
|
| 183 |
+
return f"[{minutes:02d}:{remainder / 100:05.2f}]"
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def _lrc(lyrics: str, duration: int, instrumental: bool) -> str:
|
| 187 |
+
"""Real LRC as given, or approximate starts derived for untimed lines.
|
| 188 |
+
|
| 189 |
+
The model needs a timestamp on every line. Deriving them by word count is a convenience,
|
| 190 |
+
not learned lyric alignment -- pass LRC when the timing matters."""
|
| 191 |
+
if instrumental:
|
| 192 |
+
return ""
|
| 193 |
+
lines = [line.strip() for line in lyrics.splitlines() if line.strip()]
|
| 194 |
+
lines = [line for line in lines if not SECTION.fullmatch(line)]
|
| 195 |
+
if not lines:
|
| 196 |
+
raise ValueError("vocals need lyrics; set instrumental=true for music without them")
|
| 197 |
+
if len(lines) > 400:
|
| 198 |
+
raise ValueError("at most 400 lyric lines")
|
| 199 |
+
matches = [TIMESTAMP.match(line) for line in lines]
|
| 200 |
+
if any(matches):
|
| 201 |
+
if not all(matches):
|
| 202 |
+
raise ValueError("use either plain lyrics or a timestamp on every line (LRC)")
|
| 203 |
+
out, previous = [], -1.0
|
| 204 |
+
for match in matches:
|
| 205 |
+
minutes, seconds, line = match.groups()
|
| 206 |
+
start = int(minutes) * 60 + float(seconds)
|
| 207 |
+
if float(seconds) >= 60 or not previous <= start < duration:
|
| 208 |
+
raise ValueError("LRC timestamps must be ordered and shorter than the song")
|
| 209 |
+
previous = start
|
| 210 |
+
if line.strip(): # an empty timed line marks a break; the tokenizer cannot encode it
|
| 211 |
+
out.append(f"{_stamp(start)}{line.strip()}")
|
| 212 |
+
if not out:
|
| 213 |
+
raise ValueError("the LRC contains no singable lyrics")
|
| 214 |
+
return "\n".join(out)
|
| 215 |
+
if any(re.match(r"^\[\d", line) for line in lines):
|
| 216 |
+
raise ValueError("invalid LRC timestamp; use [mm:ss.xx] before each line")
|
| 217 |
+
intro, outro = min(12.0, duration * 0.06), min(10.0, duration * 0.05)
|
| 218 |
+
weights = [max(3, len(line.split())) for line in lines]
|
| 219 |
+
total, elapsed, out = sum(weights), 0, []
|
| 220 |
+
for line, weight in zip(lines, weights):
|
| 221 |
+
out.append(f"{_stamp(intro + (duration - intro - outro) * elapsed / total)}{line}")
|
| 222 |
+
elapsed += weight
|
| 223 |
+
return "\n".join(out)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
def _wav(waveform: torch.Tensor) -> bytes:
|
| 227 |
+
"""[channels, samples] float -> peak-normalized 16-bit 44.1 kHz WAV."""
|
| 228 |
+
import soundfile as sf
|
| 229 |
+
|
| 230 |
+
waveform = waveform.float().cpu()
|
| 231 |
+
if not torch.isfinite(waveform).all():
|
| 232 |
+
raise RuntimeError("non-finite audio; retry with a different seed")
|
| 233 |
+
peak = waveform.abs().max().item()
|
| 234 |
+
if peak < 1e-7:
|
| 235 |
+
raise RuntimeError("silent audio; retry with a different seed")
|
| 236 |
+
buf = io.BytesIO()
|
| 237 |
+
sf.write(buf, (waveform / peak).clamp(-1, 1).T.numpy(), SAMPLE_RATE, subtype="PCM_16", format="WAV")
|
| 238 |
+
return buf.getvalue()
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
@app.get("/health")
|
| 242 |
+
def health():
|
| 243 |
+
return dict(STATE)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
@app.get("/info")
|
| 247 |
+
def info():
|
| 248 |
+
return dict(STATE) | {
|
| 249 |
+
"model": WEIGHTS_REPO,
|
| 250 |
+
"license": LICENSE,
|
| 251 |
+
"device": "Tenstorrent Blackhole p100a",
|
| 252 |
+
"on_card": ["CFM transformer", "stable-audio VAE decoder"],
|
| 253 |
+
"on_cpu": ["MuQ-MuLan style encoder", "lyric tokenizer", "sampler", "post-processing"],
|
| 254 |
+
"inference_steps": STEPS,
|
| 255 |
+
"cfg_strength": CFG_STRENGTH,
|
| 256 |
+
"task_modes": ["text-to-music"],
|
| 257 |
+
"duration_limits": {"min": MIN_DURATION, "max": MAX_DURATION},
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
@app.post("/predict")
|
| 262 |
+
def predict(request: Request):
|
| 263 |
+
if not request.prompt.strip():
|
| 264 |
+
raise HTTPException(400, "Empty prompt")
|
| 265 |
+
try:
|
| 266 |
+
if request.inference_steps is not None and request.inference_steps != STEPS:
|
| 267 |
+
raise ValueError(f"this port runs {STEPS} steps (the schedule its shapes are compiled for)")
|
| 268 |
+
if not MIN_DURATION <= request.duration <= MAX_DURATION:
|
| 269 |
+
raise ValueError(f"duration must be between {MIN_DURATION} and {MAX_DURATION} seconds "
|
| 270 |
+
"(use ACE-Step for longer songs)")
|
| 271 |
+
lrc = _lrc(request.lyrics, request.duration, request.instrumental)
|
| 272 |
+
except ValueError as exc:
|
| 273 |
+
raise HTTPException(422, str(exc)) from exc
|
| 274 |
+
if STATE["status"] != "ok" or CFM is None:
|
| 275 |
+
raise HTTPException(503, STATE["error"] or "Model is not ready")
|
| 276 |
+
if not LOCK.acquire(timeout=TURN_WAIT_S):
|
| 277 |
+
raise HTTPException(409, "Another song is in progress")
|
| 278 |
+
try:
|
| 279 |
+
import numpy as np
|
| 280 |
+
from infer_utils import (get_lrc_token, get_negative_style_prompt, get_reference_latent,
|
| 281 |
+
get_style_prompt)
|
| 282 |
+
|
| 283 |
+
STATE["generating"] = True
|
| 284 |
+
t0 = time.perf_counter()
|
| 285 |
+
seed = request.seed if request.seed is not None else random.SystemRandom().randrange(2**32)
|
| 286 |
+
random.seed(seed)
|
| 287 |
+
np.random.seed(seed)
|
| 288 |
+
torch.manual_seed(seed)
|
| 289 |
+
with torch.inference_mode():
|
| 290 |
+
style = get_style_prompt(STYLE, prompt=request.prompt).float()
|
| 291 |
+
text, start_time, end_frame, song_duration = get_lrc_token(
|
| 292 |
+
MAX_FRAMES, lrc, TOKENIZER, request.duration, "cpu")
|
| 293 |
+
negative = get_negative_style_prompt("cpu").float()
|
| 294 |
+
cond, segments = get_reference_latent("cpu", MAX_FRAMES, False, None, None, None)
|
| 295 |
+
latents, _ = CFM.sample(
|
| 296 |
+
cond=cond, text=text, duration=end_frame, max_duration=end_frame,
|
| 297 |
+
song_duration=song_duration, style_prompt=style, negative_style_prompt=negative,
|
| 298 |
+
steps=STEPS, cfg_strength=CFG_STRENGTH, start_time=start_time,
|
| 299 |
+
latent_pred_segments=segments, batch_infer_num=1, seed=seed)
|
| 300 |
+
latent = latents[0].float().transpose(1, 2).contiguous()
|
| 301 |
+
vae = RemoteVae(WORKER, upsample=UPSAMPLE)
|
| 302 |
+
waveform = vae(latent)[0]
|
| 303 |
+
wav = _wav(waveform)
|
| 304 |
+
timing = {
|
| 305 |
+
"dit_s": round(sum(CFM.transformer.seconds), 2),
|
| 306 |
+
"dit_calls": len(CFM.transformer.seconds),
|
| 307 |
+
"vae_s": round(sum(vae.seconds), 2),
|
| 308 |
+
"total_s": round(time.perf_counter() - t0, 2),
|
| 309 |
+
}
|
| 310 |
+
CFM.transformer.seconds.clear()
|
| 311 |
+
# The card holds no song once the VAE has run, so the shim's "already prepared" cache has
|
| 312 |
+
# to go with it -- the next request's text and conditioning differ and would otherwise be
|
| 313 |
+
# skipped. A per-song shim would not need this; a server keeps one across songs.
|
| 314 |
+
CFM.transformer.reset()
|
| 315 |
+
return {
|
| 316 |
+
"audio": base64.b64encode(wav).decode(),
|
| 317 |
+
"format": "wav",
|
| 318 |
+
"model": WEIGHTS_REPO,
|
| 319 |
+
"license": LICENSE,
|
| 320 |
+
"seed": seed,
|
| 321 |
+
"duration": request.duration,
|
| 322 |
+
"lrc": lrc,
|
| 323 |
+
"inference_steps": STEPS,
|
| 324 |
+
"timing_s": timing,
|
| 325 |
+
}
|
| 326 |
+
except Exception as exc:
|
| 327 |
+
if CFM is not None:
|
| 328 |
+
CFM.transformer.reset()
|
| 329 |
+
if WORKER is not None and WORKER.failed:
|
| 330 |
+
STATE.update(status="error", error=str(exc))
|
| 331 |
+
raise HTTPException(503, str(exc)) from exc
|
| 332 |
+
finally:
|
| 333 |
+
STATE["generating"] = False
|
| 334 |
+
LOCK.release()
|
code/tt_diffrhythm/shapes.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shapes shared by the host pipeline and the TT worker (no torch or ttnn imports)."""
|
| 2 |
+
|
| 3 |
+
# VAE windows in latent frames (remote.py RemoteVae picks the largest that fits the song); the worker builds
|
| 4 |
+
# these programs at start and warm_cache.py compiles them ahead of time
|
| 5 |
+
VAE_WINDOWS = (1024, 512)
|
code/tt_diffrhythm/warm_cache.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Compile every P100a kernel shape this port can hit, ahead of the first songs.
|
| 3 |
+
|
| 4 |
+
The card compiles kernels per shape and keeps them in TT_METAL_CACHE (data/tt-cache). Without this, the first song
|
| 5 |
+
of each new length bucket pays that compilation (tens of seconds). Shapes, per model (--model):
|
| 6 |
+
ENC_BUCKET), and both VAE windows (remote.py RemoteVae)
|
| 7 |
+
diffrhythm: every DiT sequence bucket of a 96-285 s song (diffrhythm_host.seq_buckets), both CFG branches, and
|
| 8 |
+
both VAE windows
|
| 9 |
+
Random inputs: only the shapes matter. Run it again after changing this package or the TT image.
|
| 10 |
+
"""
|
| 11 |
+
import argparse
|
| 12 |
+
import json
|
| 13 |
+
import time
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
from . import diffrhythm_dit, diffrhythm_host as dhost, oobleck_vae
|
| 18 |
+
from .shapes import VAE_WINDOWS
|
| 19 |
+
|
| 20 |
+
def warm_vae(vae, report):
|
| 21 |
+
for window in VAE_WINDOWS: # exactly as the worker does right after loading (worker.py)
|
| 22 |
+
t0 = time.monotonic()
|
| 23 |
+
audio = vae.decode(torch.zeros(64, window))
|
| 24 |
+
rec = {"vae_window": window, "s": round(time.monotonic() - t0, 2), "finite": bool(torch.isfinite(audio).all())}
|
| 25 |
+
report["vae"].append(rec)
|
| 26 |
+
print(json.dumps(rec), flush=True)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def warm_diffrhythm(args):
|
| 30 |
+
g = torch.Generator().manual_seed(0)
|
| 31 |
+
started = time.monotonic()
|
| 32 |
+
dev = diffrhythm_dit.open_device()
|
| 33 |
+
report = {"seq_buckets": dhost.seq_buckets(), "dit": [], "vae": []}
|
| 34 |
+
try:
|
| 35 |
+
dit = diffrhythm_dit.DiffRhythmDiT(dev, dhost.Checkpoint(args.checkpoint or "/checkpoint/cfm_model.pt"))
|
| 36 |
+
vae = oobleck_vae.OobleckTT(dev, args.vae or "/vae/vae_model.pt")
|
| 37 |
+
report["load_s"] = round(time.monotonic() - started, 1)
|
| 38 |
+
warm_vae(vae, report)
|
| 39 |
+
for seq_pad in report["seq_buckets"]:
|
| 40 |
+
frames = min(seq_pad, dhost.MAX_FRAMES) # any length in the bucket gives the same shapes
|
| 41 |
+
t0 = time.monotonic()
|
| 42 |
+
song = dit.prepare({null: torch.randn(frames, 512, generator=g) for null in (False, True)},
|
| 43 |
+
torch.randn(frames, 64, generator=g))
|
| 44 |
+
assert song.geo.seq_pad == seq_pad
|
| 45 |
+
finite = True
|
| 46 |
+
for null in (False, True):
|
| 47 |
+
v = dit.forward(torch.randn(frames, 64, generator=g), null, torch.randn(512, generator=g),
|
| 48 |
+
torch.randn(512, generator=g), song)
|
| 49 |
+
finite &= bool(torch.isfinite(v).all())
|
| 50 |
+
dit.release(song)
|
| 51 |
+
rec = {"seq_pad": seq_pad, "s": round(time.monotonic() - t0, 2), "finite": finite}
|
| 52 |
+
report["dit"].append(rec)
|
| 53 |
+
print(json.dumps(rec), flush=True)
|
| 54 |
+
finally:
|
| 55 |
+
diffrhythm_dit.close_device(dev)
|
| 56 |
+
report["total_s"] = round(time.monotonic() - started, 1)
|
| 57 |
+
print(json.dumps({k: report[k] for k in ("seq_buckets", "load_s", "total_s")}), flush=True)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def main():
|
| 61 |
+
parser = argparse.ArgumentParser()
|
| 62 |
+
parser.add_argument("--checkpoint")
|
| 63 |
+
parser.add_argument("--vae")
|
| 64 |
+
args = parser.parse_args()
|
| 65 |
+
return warm_diffrhythm(args)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
if __name__ == "__main__":
|
| 69 |
+
main()
|
code/tt_diffrhythm/wire.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tensors between the host ACE-Step process and the TT DiT worker.
|
| 2 |
+
|
| 3 |
+
The two sides run different numpy/torch builds (ACE-Step venv vs the TT image), so tensors cross the socket as raw
|
| 4 |
+
little-endian bytes with dtype and shape, inside plain dicts that pickle identically on both sides."""
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
_DTYPES = {"float32": torch.float32}
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def encode(t: torch.Tensor) -> dict:
|
| 13 |
+
t = t.detach().to("cpu", torch.float32).contiguous()
|
| 14 |
+
return {"dtype": "float32", "shape": list(t.shape), "data": t.numpy().tobytes()}
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def decode(d: dict) -> torch.Tensor:
|
| 18 |
+
return torch.frombuffer(bytearray(d["data"]), dtype=_DTYPES[d["dtype"]]).reshape(d["shape"])
|
code/tt_diffrhythm/worker.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""TT worker: loads a music model's DiT and its audio VAE decoder onto the P100a and answers one host pipeline.
|
| 3 |
+
|
| 4 |
+
Runs inside the TT image; the host (remote.py) owns the container for its whole life and talks to
|
| 5 |
+
this process over a Unix socket in a directory bind-mounted into both:
|
| 6 |
+
|
| 7 |
+
python /work/tt_diffrhythm/worker.py --socket /ipc/worker.sock [--model diffrhythm] --checkpoint ... --vae ...
|
| 8 |
+
|
| 9 |
+
Messages are plain dicts (tensors via wire.py):
|
| 10 |
+
-> {"op": "ready", ...} once the weights are on the device
|
| 11 |
+
<- {"op": "prepare", ...} -> {"op": "prepared", ...}
|
| 12 |
+
DiffRhythm: "text_embed": {drop_text: [T, 512]}, "cond": [T, 64]
|
| 13 |
+
<- {"op": "forward", ...} -> {"op": "velocity", "v": [T, 64], ...}
|
| 14 |
+
DiffRhythm: "x": [T, 64], "drop_text": bool, "style": [512], "c": [512]
|
| 15 |
+
<- {"op": "vae", "z": [64, L]} -> {"op": "audio", "audio": [2, L * upsample], ...}
|
| 16 |
+
<- {"op": "close"}
|
| 17 |
+
Any failure is sent back as {"op": "error", "error": "..."} and ends the worker.
|
| 18 |
+
"""
|
| 19 |
+
import argparse
|
| 20 |
+
from multiprocessing.connection import Listener
|
| 21 |
+
import os
|
| 22 |
+
import signal
|
| 23 |
+
import sys
|
| 24 |
+
import time
|
| 25 |
+
import traceback
|
| 26 |
+
|
| 27 |
+
import torch
|
| 28 |
+
|
| 29 |
+
from . import diffrhythm_dit, oobleck_vae, wire
|
| 30 |
+
from .shapes import VAE_WINDOWS
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class DiffRhythm:
|
| 34 |
+
def __init__(self, dev, checkpoint):
|
| 35 |
+
from . import diffrhythm_host
|
| 36 |
+
|
| 37 |
+
self.dit = diffrhythm_dit.DiffRhythmDiT(dev, diffrhythm_host.Checkpoint(checkpoint))
|
| 38 |
+
|
| 39 |
+
def prepare(self, msg):
|
| 40 |
+
text_embed = {bool(k): wire.decode(v) for k, v in msg["text_embed"].items()}
|
| 41 |
+
song = self.dit.prepare(text_embed, wire.decode(msg["cond"]))
|
| 42 |
+
return song, {"frames": song.geo.frames, "seq_pad": song.geo.seq_pad}
|
| 43 |
+
|
| 44 |
+
def forward(self, msg, song):
|
| 45 |
+
return self.dit.forward(wire.decode(msg["x"]), bool(msg["drop_text"]), wire.decode(msg["style"]),
|
| 46 |
+
wire.decode(msg["c"]), song)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
MODELS = {"diffrhythm": (DiffRhythm, "/checkpoint/cfm_model.pt", "/vae/vae_model.pt")}
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def serve(conn, model, vae):
|
| 53 |
+
dit = model.dit
|
| 54 |
+
song = None
|
| 55 |
+
while True:
|
| 56 |
+
msg = conn.recv()
|
| 57 |
+
op = msg.get("op")
|
| 58 |
+
started = time.monotonic()
|
| 59 |
+
if op == "prepare":
|
| 60 |
+
if song is not None:
|
| 61 |
+
dit.release(song)
|
| 62 |
+
song = None
|
| 63 |
+
song, geometry = model.prepare(msg)
|
| 64 |
+
conn.send({"op": "prepared", "seconds": time.monotonic() - started, "geometry": geometry})
|
| 65 |
+
elif op == "forward":
|
| 66 |
+
if song is None:
|
| 67 |
+
raise RuntimeError("forward before prepare")
|
| 68 |
+
v = model.forward(msg, song)
|
| 69 |
+
conn.send({"op": "velocity", "v": wire.encode(v), "seconds": time.monotonic() - started})
|
| 70 |
+
elif op == "vae":
|
| 71 |
+
if song is not None: # sampling is over; freeing the song's tensors first gives the VAE the same device
|
| 72 |
+
dit.release(song) # allocation state for every song length, so its compiled conv kernels are reused
|
| 73 |
+
song = None
|
| 74 |
+
audio = vae.decode(wire.decode(msg["z"]))
|
| 75 |
+
conn.send({"op": "audio", "audio": wire.encode(audio), "seconds": time.monotonic() - started})
|
| 76 |
+
elif op == "close":
|
| 77 |
+
if song is not None:
|
| 78 |
+
dit.release(song)
|
| 79 |
+
return
|
| 80 |
+
else:
|
| 81 |
+
raise RuntimeError(f"unknown op {op!r}")
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def main():
|
| 85 |
+
# docker stop / a signal: leave through the finally below so the device is closed, not dropped
|
| 86 |
+
signal.signal(signal.SIGTERM, lambda *_: sys.exit(143))
|
| 87 |
+
parser = argparse.ArgumentParser()
|
| 88 |
+
parser.add_argument("--socket", required=True)
|
| 89 |
+
parser.add_argument("--model", choices=sorted(MODELS), default="diffrhythm")
|
| 90 |
+
parser.add_argument("--checkpoint")
|
| 91 |
+
parser.add_argument("--vae")
|
| 92 |
+
args = parser.parse_args()
|
| 93 |
+
model_class, checkpoint, vae_path = MODELS[args.model]
|
| 94 |
+
authkey = os.environ.get("MUSIC_TT_AUTHKEY", "").encode() or None
|
| 95 |
+
listener = Listener(args.socket, family="AF_UNIX", authkey=authkey)
|
| 96 |
+
started = time.monotonic()
|
| 97 |
+
dev = diffrhythm_dit.open_device()
|
| 98 |
+
conn = None
|
| 99 |
+
try:
|
| 100 |
+
model = model_class(dev, args.checkpoint or checkpoint)
|
| 101 |
+
vae = oobleck_vae.OobleckTT(dev, args.vae or vae_path)
|
| 102 |
+
# Build the VAE programs now, in the same device state for every song: their conv kernels then compile
|
| 103 |
+
# once (TT_METAL_CACHE) and later decodes reuse the in-process programs. Built after a song's DiT steps
|
| 104 |
+
# instead, the device state (and so the kernels) depends on the song length and recompiles for 20-40 s.
|
| 105 |
+
warm = time.monotonic()
|
| 106 |
+
for window in VAE_WINDOWS:
|
| 107 |
+
vae.decode(torch.zeros(64, window))
|
| 108 |
+
ready = {"op": "ready", "model": args.model, "load_s": time.monotonic() - started,
|
| 109 |
+
"vae_warm_s": time.monotonic() - warm, "upsample": vae.upsample, "dram": diffrhythm_dit.dram_stats(dev)}
|
| 110 |
+
conn = listener.accept() # one pipeline per container
|
| 111 |
+
conn.send(ready)
|
| 112 |
+
serve(conn, model, vae)
|
| 113 |
+
except BaseException as exc:
|
| 114 |
+
traceback.print_exc()
|
| 115 |
+
if conn is not None:
|
| 116 |
+
try:
|
| 117 |
+
conn.send({"op": "error", "error": f"{type(exc).__name__}: {exc}"})
|
| 118 |
+
except OSError:
|
| 119 |
+
pass
|
| 120 |
+
raise
|
| 121 |
+
finally:
|
| 122 |
+
diffrhythm_dit.close_device(dev)
|
| 123 |
+
listener.close()
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
main()
|
image/blobs/sha256/0cc125e439ef6882a01bae57bece67f14ec2ad123aec1fb12e86bc50175a4b44
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schemaVersion": 2,
|
| 3 |
+
"mediaType": "application/vnd.oci.image.index.v1+json",
|
| 4 |
+
"manifests": [
|
| 5 |
+
{
|
| 6 |
+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
| 7 |
+
"digest": "sha256:81aae6dd123609902a2f56719e50ef0bcf3aa00aff71fc66b02648b3f4aea2f2",
|
| 8 |
+
"size": 4499,
|
| 9 |
+
"platform": {
|
| 10 |
+
"architecture": "amd64",
|
| 11 |
+
"os": "linux"
|
| 12 |
+
}
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
| 16 |
+
"digest": "sha256:8f94a918ad7d7da27e2b025adda011abaa34d892741d6b28221015c75a4ac5ba",
|
| 17 |
+
"size": 837,
|
| 18 |
+
"annotations": {
|
| 19 |
+
"vnd.docker.reference.digest": "sha256:81aae6dd123609902a2f56719e50ef0bcf3aa00aff71fc66b02648b3f4aea2f2",
|
| 20 |
+
"vnd.docker.reference.type": "attestation-manifest"
|
| 21 |
+
},
|
| 22 |
+
"platform": {
|
| 23 |
+
"architecture": "unknown",
|
| 24 |
+
"os": "unknown"
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
]
|
| 28 |
+
}
|
image/blobs/sha256/340cf5e2c047e0fc940f2973810428f58250242238231682812dec6f6d54f3ac
ADDED
|
Binary file (4.4 kB). View file
|
|
|
image/blobs/sha256/44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{}
|
image/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1
ADDED
|
Binary file (32 Bytes). View file
|
|
|
image/blobs/sha256/6acee8cd93aa0889cc3a8b09f1b2564234ee82aa7c31e87ab61591552d15e894
ADDED
|
Binary file (5.61 kB). View file
|
|
|
image/blobs/sha256/81aae6dd123609902a2f56719e50ef0bcf3aa00aff71fc66b02648b3f4aea2f2
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schemaVersion": 2,
|
| 3 |
+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
| 4 |
+
"config": {
|
| 5 |
+
"mediaType": "application/vnd.oci.image.config.v1+json",
|
| 6 |
+
"digest": "sha256:eae56eb3c9610901618ab9c30e2ba8cdcf6981cdd3af7ed5755aba2cc46db6e1",
|
| 7 |
+
"size": 14328
|
| 8 |
+
},
|
| 9 |
+
"layers": [
|
| 10 |
+
{
|
| 11 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 12 |
+
"digest": "sha256:98c4455a98982b35380ec62f90d5cf0edff02eeb4b3bb18595d871fdeb0c0c67",
|
| 13 |
+
"size": 29751627
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 17 |
+
"digest": "sha256:3375e6602aa7264d6d67ae69ccb99690c8f40af7ab48d0af060dac1a2e9c24c9",
|
| 18 |
+
"size": 69483284
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 22 |
+
"digest": "sha256:340cf5e2c047e0fc940f2973810428f58250242238231682812dec6f6d54f3ac",
|
| 23 |
+
"size": 4399
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 27 |
+
"digest": "sha256:1f47921d53a3b8de72172e2fd5f0a0d27f1e8f4b77016c6b8a1a4b591bb83ae5",
|
| 28 |
+
"size": 9565639
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 32 |
+
"digest": "sha256:4ec2b03c373212fbe7fda97dbf9eaadb1bd42c2b0afc8b8a49806fe1dbc507f7",
|
| 33 |
+
"size": 138968721
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 37 |
+
"digest": "sha256:b7c2e1ec67bfd9d4578a81ca6987f095b74869db5f518bf2fc9d3ffeb30821e5",
|
| 38 |
+
"size": 66025096
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 42 |
+
"digest": "sha256:428f99cfe94d9b1e42af2f3d8104598f8ca6e6105925c7687595a18c78d21011",
|
| 43 |
+
"size": 503666016
|
| 44 |
+
},
|
| 45 |
+
{
|
| 46 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 47 |
+
"digest": "sha256:ee5adbd6eba4dfd9ba7e59692c46379b3d60a88f8445ec6a045d4aeb336f5bb9",
|
| 48 |
+
"size": 116
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 52 |
+
"digest": "sha256:672d584c094e51e29aa5e46a6dd22cbfb993f4f839609e954d2c299912ea5d77",
|
| 53 |
+
"size": 139080178
|
| 54 |
+
},
|
| 55 |
+
{
|
| 56 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 57 |
+
"digest": "sha256:2d16f4104fc6e34c556d42d6a35adedd5ffa396e71d59ef6dd7d5e88c01225e0",
|
| 58 |
+
"size": 39231465
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 62 |
+
"digest": "sha256:556ec3c4c0e12c97f4c9b8dd0cd65ae321238c55438fbf498055f0ad7afc05bb",
|
| 63 |
+
"size": 39231323
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 67 |
+
"digest": "sha256:b9ddf00cad9d9a3bc91d09c8ee0f4fef42056861bb5ca1209fcc204b68950abf",
|
| 68 |
+
"size": 66525731
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 72 |
+
"digest": "sha256:0b4ec6da503185d389dcfeefc2c5d03ab13e0d76620b5fb6ce022f79bb64140f",
|
| 73 |
+
"size": 12761466
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 77 |
+
"digest": "sha256:49353ead98e6a241dd015dda937bf8dcf335e4eed27fe732df8163dd955d042f",
|
| 78 |
+
"size": 1898379
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 82 |
+
"digest": "sha256:c2a425aad5e9144add8f2a619c6e3d452d30ddbb5d4f4d3e45caab4f7bb3d233",
|
| 83 |
+
"size": 6878
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 87 |
+
"digest": "sha256:e211ac567cceb6b5be62db5e135a1a5f3e77bcacebdac6a8cf9e0cdc24158c09",
|
| 88 |
+
"size": 108850224
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 92 |
+
"digest": "sha256:c6bd350dd2ed57d0ea34248e5b27d985a6786330d2d3ec29f5563e03ca9f00da",
|
| 93 |
+
"size": 1080
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 97 |
+
"digest": "sha256:8f09ce5e10995b3fa6362dae9945d9402a5ccabce3d5bf20df0a53c75d9f94ab",
|
| 98 |
+
"size": 982
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 102 |
+
"digest": "sha256:4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1",
|
| 103 |
+
"size": 32
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 107 |
+
"digest": "sha256:e600ba821186d2d540ea813a478dda1e629c539eef283f062f31309cb8c3af47",
|
| 108 |
+
"size": 674
|
| 109 |
+
},
|
| 110 |
+
{
|
| 111 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 112 |
+
"digest": "sha256:3c7495db974af283bccc2dee306a75da6491b2ca664d55597da926c4fd104dd5",
|
| 113 |
+
"size": 216662
|
| 114 |
+
},
|
| 115 |
+
{
|
| 116 |
+
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
|
| 117 |
+
"digest": "sha256:6acee8cd93aa0889cc3a8b09f1b2564234ee82aa7c31e87ab61591552d15e894",
|
| 118 |
+
"size": 5608
|
| 119 |
+
}
|
| 120 |
+
]
|
| 121 |
+
}
|
image/blobs/sha256/8f09ce5e10995b3fa6362dae9945d9402a5ccabce3d5bf20df0a53c75d9f94ab
ADDED
|
Binary file (982 Bytes). View file
|
|
|
image/blobs/sha256/8f94a918ad7d7da27e2b025adda011abaa34d892741d6b28221015c75a4ac5ba
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schemaVersion": 2,
|
| 3 |
+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
| 4 |
+
"artifactType": "application/vnd.docker.attestation.manifest.v1+json",
|
| 5 |
+
"config": {
|
| 6 |
+
"mediaType": "application/vnd.oci.empty.v1+json",
|
| 7 |
+
"digest": "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
|
| 8 |
+
"size": 2,
|
| 9 |
+
"data": "e30="
|
| 10 |
+
},
|
| 11 |
+
"layers": [
|
| 12 |
+
{
|
| 13 |
+
"mediaType": "application/vnd.in-toto+json",
|
| 14 |
+
"digest": "sha256:9b3433e39d3d40a2f64d1f1e8cb155387651684c5f5c9473bd733c72056dee58",
|
| 15 |
+
"size": 1923,
|
| 16 |
+
"annotations": {
|
| 17 |
+
"in-toto.io/predicate-type": "https://slsa.dev/provenance/v1"
|
| 18 |
+
}
|
| 19 |
+
}
|
| 20 |
+
],
|
| 21 |
+
"subject": {
|
| 22 |
+
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
| 23 |
+
"digest": "sha256:81aae6dd123609902a2f56719e50ef0bcf3aa00aff71fc66b02648b3f4aea2f2",
|
| 24 |
+
"size": 4499
|
| 25 |
+
}
|
| 26 |
+
}
|
image/blobs/sha256/9b3433e39d3d40a2f64d1f1e8cb155387651684c5f5c9473bd733c72056dee58
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"_type":"https://in-toto.io/Statement/v1","predicateType":"https://slsa.dev/provenance/v1","subject":[{"name":"pkg:docker/tt-model/diffrhythm-1.2-p100a@build-58648d444?platform=linux%2Famd64","digest":{"sha256":"81aae6dd123609902a2f56719e50ef0bcf3aa00aff71fc66b02648b3f4aea2f2"}}],"predicate":{"buildDefinition":{"buildType":"https://github.com/moby/buildkit/blob/master/docs/attestations/slsa-definitions.md","resolvedDependencies":[{"uri":"pkg:docker/docker/dockerfile@1.7-labs","digest":{"sha256":"b99fecfe00268a8b556fad7d9c37ee25d716ae08a5d7320e6d51c4dd83246894"}},{"uri":"pkg:docker/ubuntu@22.04?platform=linux%2Famd64","digest":{"sha256":"5ec03bb3441e8b0bf3b4f9cd4629a1ae763010dc3035bb8da3ae6cf026486401"}},{"uri":"pkg:docker/ghcr.io/tenstorrent/tt-metal/tt-metalium/ubuntu-22.04-dev-amd64@latest?platform=linux%2Famd64","digest":{"sha256":"df9d279c7f85c17c6fad982d196802682d669cca1b7ced9cbaad8181339cd5fc"}}],"externalParameters":{"configSource":{"path":"Dockerfile"},"request":{"frontend":"gateway.v0","args":{"cmdline":"docker/dockerfile:1.7-labs","context:metalsrc":"local:metalsrc","frontend.caps":"moby.buildkit.frontend.contexts+forward","sharedkey:localdir:metalsrc":"empty-metalsrc:f5e176f12fb9c045","source":"docker/dockerfile:1.7-labs"},"locals":[{"name":"context"},{"name":"dockerfile"},{"name":"metalsrc"}],"root":{"configSource":{"path":"Dockerfile"},"request":{"args":{"context:metalsrc":"local:metalsrc","frontend.caps":"moby.buildkit.frontend.contexts+forward","sharedkey:localdir:metalsrc":"empty-metalsrc:f5e176f12fb9c045"}}},"compatibilityVersion":30}},"internalParameters":{"builderPlatform":"linux/amd64"}},"runDetails":{"builder":{"id":""},"metadata":{"invocationId":"lt0mpnhkaz55xrvi2wqvfduge","startedOn":"2026-10-09T23:02:08.374050954-04:00","finishedOn":"2026-10-09T23:07:18.826107934-04:00","buildkit_metadata":{},"buildkit_completeness":{"request":false,"resolvedDependencies":false}}}}}
|
image/blobs/sha256/c2a425aad5e9144add8f2a619c6e3d452d30ddbb5d4f4d3e45caab4f7bb3d233
ADDED
|
Binary file (6.88 kB). View file
|
|
|
image/blobs/sha256/c6bd350dd2ed57d0ea34248e5b27d985a6786330d2d3ec29f5563e03ca9f00da
ADDED
|
Binary file (1.08 kB). View file
|
|
|
image/blobs/sha256/e600ba821186d2d540ea813a478dda1e629c539eef283f062f31309cb8c3af47
ADDED
|
Binary file (674 Bytes). View file
|
|
|
image/blobs/sha256/eae56eb3c9610901618ab9c30e2ba8cdcf6981cdd3af7ed5755aba2cc46db6e1
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"architecture":"amd64","config":{"User":"tt","Env":["PATH=/opt/tt-venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","VENV=/opt/tt-venv","VIRTUAL_ENV=/opt/tt-venv","TT_METAL_RUNTIME_ROOT=/opt/tt-metal","TT_METAL_HOME=/opt/tt-metal","PYTHONPATH=/opt/tt-metal","LD_LIBRARY_PATH=/opt/tt-metal/build/lib:/opt/openmpi-v5.0.7-ulfm/lib","EXTRA_MODELS_DIR=","TT_VLLM_BUILTIN_MODELS=","TT_MODEL_KIND=tt-dit-server","HF_HOME=/hf","TT_METAL_CACHE=/cache","HOME=/home/tt","USER=tt","LOGNAME=tt"],"Entrypoint":["/usr/local/bin/entrypoint.sh"],"Cmd":["/usr/local/bin/serve-default.sh"],"WorkingDir":"/home/tt/work","Labels":{"org.opencontainers.image.revision":"58648d44443223041148125843ca7c7d4780463e","org.opencontainers.image.version":"22.04","org.tenstorrent.tt-model":"diffrhythm-1.2-p100a","org.tenstorrent.tt-model.arch":"blackhole","org.tenstorrent.tt-model.kind":"tt-dit-server","org.tenstorrent.tt-model.plugin":"","org.tenstorrent.tt-model.profiles":"default","org.tenstorrent.tt-model.repo":"sunandbean/diffrhythm-1.2-p100a","org.tenstorrent.tt-model.tt-metal":"","org.tenstorrent.tt-model.weights":"ASLP-lab/DiffRhythm-1_2-full"},"ArgsEscaped":true},"created":"2026-10-09T23:06:45.672498279-04:00","history":[{"created":"2026-09-24T22:24:04.688908979Z","created_by":"/bin/sh -c #(nop) ARG RELEASE","empty_layer":true},{"created":"2026-09-24T22:24:04.723527376Z","created_by":"/bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH","empty_layer":true},{"created":"2026-09-24T22:24:04.751455651Z","created_by":"/bin/sh -c #(nop) LABEL org.opencontainers.image.version=22.04","empty_layer":true},{"created":"2026-09-24T22:24:06.837737736Z","created_by":"/bin/sh -c #(nop) ADD file:b0bf3f64519bf10a51e00d4f8ab9c8693620659a0c4a4a5090a9765e754e1d1c in / "},{"created":"2026-09-24T22:24:07.239071539Z","created_by":"/bin/sh -c #(nop) CMD [\"/bin/bash\"]","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG OMPI_DIR=/opt/openmpi-v5.0.7-ulfm","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG EXTRA_MODELS_DIR=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG TT_MODEL_KIND","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_NAME","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_REPO","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_WEIGHTS","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_ARCH","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_PROFILES","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_TT_METAL_SHA","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_TT_METAL_DESCRIBE","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"ARG MODEL_PLUGIN_SHA","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T09:22:08.388097186-04:00","created_by":"RUN |11 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=diffrhythm-1.2-p100a MODEL_REPO=sunandbean/diffrhythm-1.2-p100a MODEL_WEIGHTS=ASLP-lab/DiffRhythm-1_2-full MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=58648d44443223041148125843ca7c7d4780463e MODEL_TT_METAL_DESCRIBE= MODEL_PLUGIN_SHA= /bin/sh -c apt-get update \u0026\u0026 apt-get install -y --no-install-recommends libhwloc15 libnuma1 libatomic1 libudev1 libcap2 zlib1g libmpc3 libmpfr6 libgmp10 libzstd1 libevent-core-2.1-7 libevent-pthreads-2.1-7 libgl1 libsndfile1 ca-certificates \u0026\u0026 apt-get clean \u0026\u0026 rm -rf /var/lib/apt/lists/* # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T09:22:08.570265459-04:00","created_by":"RUN |11 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=diffrhythm-1.2-p100a MODEL_REPO=sunandbean/diffrhythm-1.2-p100a MODEL_WEIGHTS=ASLP-lab/DiffRhythm-1_2-full MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=58648d44443223041148125843ca7c7d4780463e MODEL_TT_METAL_DESCRIBE= MODEL_PLUGIN_SHA= /bin/sh -c existing=\"$(getent passwd 1000 | cut -d: -f1)\" \u0026\u0026 if [ -n \"$existing\" ]; then userdel -r \"$existing\" 2\u003e/dev/null || userdel \"$existing\"; fi \u0026\u0026 useradd --uid 1000 --create-home --home-dir /home/tt --shell /bin/bash tt \u0026\u0026 mkdir -p /home/tt/work/logs /cache /opt/tt-metal \u0026\u0026 chown -R tt:tt /home/tt /cache /opt/tt-metal \u0026\u0026 chmod 1777 /home/tt /home/tt/work /home/tt/work/logs /cache # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T09:26:50.393285226-04:00","created_by":"COPY /opt/openmpi-v5.0.7-ulfm /opt/openmpi-v5.0.7-ulfm # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T09:26:50.967022517-04:00","created_by":"COPY /opt/tenstorrent /opt/tenstorrent # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:49.28099242-04:00","created_by":"COPY /usr/local/share/uv /usr/local/share/uv # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:52.554077344-04:00","created_by":"COPY /opt/tt-venv /opt/tt-venv # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:52.741256841-04:00","created_by":"COPY /opt/vllm /opt/vllm # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:53.245850738-04:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/runtime /opt/tt-metal/runtime # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:53.42384338-04:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/build_Release /opt/tt-metal/build_Release # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:53.584846845-04:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/build /opt/tt-metal/build # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:54.25191112-04:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/tt_metal /opt/tt-metal/tt_metal # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:54.825250753-04:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/ttnn /opt/tt-metal/ttnn # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:54.915803026-04:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/tools /opt/tt-metal/tools # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:54.941505166-04:00","created_by":"COPY --chown=tt:tt /opt/tt-metal/setup.py /opt/tt-metal/pyproject.toml /opt/tt-metal/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:55.213574197-04:00","created_by":"COPY --chown=tt:tt code/ /opt/tt-metal/ # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:55.238685903-04:00","created_by":"COPY entrypoint.sh /usr/local/bin/entrypoint.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"COPY --chmod=0755 serve-default.sh /usr/local/bin/serve-default.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV VENV=/opt/tt-venv","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV VIRTUAL_ENV=/opt/tt-venv","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV PATH=/opt/tt-venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV TT_METAL_RUNTIME_ROOT=/opt/tt-metal","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV TT_METAL_HOME=/opt/tt-metal","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV PYTHONPATH=/opt/tt-metal","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV LD_LIBRARY_PATH=/opt/tt-metal/build/lib:/opt/openmpi-v5.0.7-ulfm/lib","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV EXTRA_MODELS_DIR=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ARG TT_VLLM_BUILTIN_MODELS=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV TT_VLLM_BUILTIN_MODELS=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV TT_MODEL_KIND=tt-dit-server","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV HF_HOME=/hf","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV TT_METAL_CACHE=/cache","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV HOME=/home/tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV USER=tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"ENV LOGNAME=tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.256601142-04:00","created_by":"USER tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:05:55.273892093-04:00","created_by":"WORKDIR /home/tt/work","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:05:55.28789741-04:00","created_by":"COPY verify.sh /ctx/verify.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:06:45.494946817-04:00","created_by":"RUN |12 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=diffrhythm-1.2-p100a MODEL_REPO=sunandbean/diffrhythm-1.2-p100a MODEL_WEIGHTS=ASLP-lab/DiffRhythm-1_2-full MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=58648d44443223041148125843ca7c7d4780463e MODEL_TT_METAL_DESCRIBE= MODEL_PLUGIN_SHA= TT_VLLM_BUILTIN_MODELS= /bin/sh -c bash /ctx/verify.sh # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:06:45.494946817-04:00","created_by":"USER root","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:06:45.672498279-04:00","created_by":"RUN |12 OMPI_DIR=/opt/openmpi-v5.0.7-ulfm EXTRA_MODELS_DIR= TT_MODEL_KIND=tt-dit-server MODEL_NAME=diffrhythm-1.2-p100a MODEL_REPO=sunandbean/diffrhythm-1.2-p100a MODEL_WEIGHTS=ASLP-lab/DiffRhythm-1_2-full MODEL_ARCH=blackhole MODEL_PROFILES=default MODEL_TT_METAL_SHA=58648d44443223041148125843ca7c7d4780463e MODEL_TT_METAL_DESCRIBE= MODEL_PLUGIN_SHA= TT_VLLM_BUILTIN_MODELS= /bin/sh -c chmod -R a+rwX /home/tt # buildkit","comment":"buildkit.dockerfile.v0"},{"created":"2026-10-09T23:06:45.672498279-04:00","created_by":"USER tt","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:06:45.672498279-04:00","created_by":"LABEL org.tenstorrent.tt-model=diffrhythm-1.2-p100a org.tenstorrent.tt-model.repo=sunandbean/diffrhythm-1.2-p100a org.tenstorrent.tt-model.weights=ASLP-lab/DiffRhythm-1_2-full org.tenstorrent.tt-model.arch=blackhole org.tenstorrent.tt-model.kind=tt-dit-server org.tenstorrent.tt-model.profiles=default org.opencontainers.image.revision=58648d44443223041148125843ca7c7d4780463e org.tenstorrent.tt-model.tt-metal= org.tenstorrent.tt-model.plugin=","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:06:45.672498279-04:00","created_by":"ENTRYPOINT [\"/usr/local/bin/entrypoint.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true},{"created":"2026-10-09T23:06:45.672498279-04:00","created_by":"CMD [\"/usr/local/bin/serve-default.sh\"]","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:cbaf391e670933f026c05c7dedec3faf8f194b6c2d83b0d5694ad68323c8077c","sha256:09daac6f134b2dba27b8d4a0ef3121a37537cad3f15daa032b9431f80ff87448","sha256:5287d0203a2b8dcd23409b5e9013a5c575f805d95599f294f57401c395c68522","sha256:542109b0dfa66e7d5c45e42504d6acaf67a10027553a1855ed50d97cf40b5fd4","sha256:85747f2ebe464ef004163a156c985fd7654ef8463f0e1f673d5fc3ebb8ca5036","sha256:0a843c5fa2e20d2fb2d102bd7c52914df44527da4a07f6a90d1b7cbdc99d4139","sha256:3c8124f890f49acc6c287be3d6967d2c7aea4b0613442eecb2d12649e346dd19","sha256:84150c24aea3517574d06f1ccb0fd8cbb705ef7fe1360b737bd73f41fe5fe802","sha256:7c7537d0f65122e0a4fb6e585e744529be140d9f30775d4ac3e7d71678841766","sha256:c55f647fbe1619ed1658092a93e0de5543b510df376925b0077fbf87c2b6a606","sha256:0f111d51bc656a83c00484d1ecaf7c053336f5d87914e3e169fdfb1b8730c0b1","sha256:b2b6c01d259d2ba562d3ee67e8481bb3c12c484fa4817411822773177d83122e","sha256:558e4e46be0c2d5366bf9e13cb1076dffcfa9b386b7a702a2be672df0abbfe26","sha256:2aa75a7513dd7fd2e2df7fb28da3f3de2bddfe115eb51b562b3a9cf7faea0719","sha256:ac5907e681ddb595c56a03bfe14231bfdd907438015951cbeaa6313e4b5072af","sha256:5fc6fd4ae4f4966d156d66670a5f1c1ec3087a656ecb06b8af21f27be8e94870","sha256:89c2380aa2116794479b618d5e7a373ba519c24b3a5e67756a1c09e6ca089fa5","sha256:568be329db8740036ac4055096cd89a8b909b0375fefe0f2885a287c7ffb9855","sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef","sha256:7b7966ac1b44afb1004b16928394cc98583c8854c533405e3e68d01de8600c5b","sha256:228b2f895919d9b9f799eb573f98d3e01f7e6e45b01a406366b69b1aa333fa91","sha256:369854526b98a8f50c544e3aca82a2cc696396fbe0f96ca020dca91a87502fd5"]}}
|
image/blobs/sha256/ee5adbd6eba4dfd9ba7e59692c46379b3d60a88f8445ec6a045d4aeb336f5bb9
ADDED
|
Binary file (116 Bytes). View file
|
|
|
image/index.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"schemaVersion":2,"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"mediaType":"application/vnd.oci.image.index.v1+json","digest":"sha256:0cc125e439ef6882a01bae57bece67f14ec2ad123aec1fb12e86bc50175a4b44","size":856,"annotations":{"io.containerd.image.name":"docker.io/tt-model/diffrhythm-1.2-p100a:0cc125e439ef","org.opencontainers.image.ref.name":"0cc125e439ef"}}]}
|
image/manifest.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"Config":"blobs/sha256/eae56eb3c9610901618ab9c30e2ba8cdcf6981cdd3af7ed5755aba2cc46db6e1","RepoTags":["tt-model/diffrhythm-1.2-p100a:0cc125e439ef"],"Layers":["blobs/sha256/98c4455a98982b35380ec62f90d5cf0edff02eeb4b3bb18595d871fdeb0c0c67","blobs/sha256/3375e6602aa7264d6d67ae69ccb99690c8f40af7ab48d0af060dac1a2e9c24c9","blobs/sha256/340cf5e2c047e0fc940f2973810428f58250242238231682812dec6f6d54f3ac","blobs/sha256/1f47921d53a3b8de72172e2fd5f0a0d27f1e8f4b77016c6b8a1a4b591bb83ae5","blobs/sha256/4ec2b03c373212fbe7fda97dbf9eaadb1bd42c2b0afc8b8a49806fe1dbc507f7","blobs/sha256/b7c2e1ec67bfd9d4578a81ca6987f095b74869db5f518bf2fc9d3ffeb30821e5","blobs/sha256/428f99cfe94d9b1e42af2f3d8104598f8ca6e6105925c7687595a18c78d21011","blobs/sha256/ee5adbd6eba4dfd9ba7e59692c46379b3d60a88f8445ec6a045d4aeb336f5bb9","blobs/sha256/672d584c094e51e29aa5e46a6dd22cbfb993f4f839609e954d2c299912ea5d77","blobs/sha256/2d16f4104fc6e34c556d42d6a35adedd5ffa396e71d59ef6dd7d5e88c01225e0","blobs/sha256/556ec3c4c0e12c97f4c9b8dd0cd65ae321238c55438fbf498055f0ad7afc05bb","blobs/sha256/b9ddf00cad9d9a3bc91d09c8ee0f4fef42056861bb5ca1209fcc204b68950abf","blobs/sha256/0b4ec6da503185d389dcfeefc2c5d03ab13e0d76620b5fb6ce022f79bb64140f","blobs/sha256/49353ead98e6a241dd015dda937bf8dcf335e4eed27fe732df8163dd955d042f","blobs/sha256/c2a425aad5e9144add8f2a619c6e3d452d30ddbb5d4f4d3e45caab4f7bb3d233","blobs/sha256/e211ac567cceb6b5be62db5e135a1a5f3e77bcacebdac6a8cf9e0cdc24158c09","blobs/sha256/c6bd350dd2ed57d0ea34248e5b27d985a6786330d2d3ec29f5563e03ca9f00da","blobs/sha256/8f09ce5e10995b3fa6362dae9945d9402a5ccabce3d5bf20df0a53c75d9f94ab","blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1","blobs/sha256/e600ba821186d2d540ea813a478dda1e629c539eef283f062f31309cb8c3af47","blobs/sha256/3c7495db974af283bccc2dee306a75da6491b2ca664d55597da926c4fd104dd5","blobs/sha256/6acee8cd93aa0889cc3a8b09f1b2564234ee82aa7c31e87ab61591552d15e894"]}]
|
image/oci-layout
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"imageLayoutVersion":"1.0.0"}
|
requirements.lock
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
accelerate==1.15.0
|
| 2 |
+
annotated-doc==0.0.5
|
| 3 |
+
annotated-types==0.8.0
|
| 4 |
+
anyio==4.15.1
|
| 5 |
+
attrs==26.1.0
|
| 6 |
+
beartype==0.22.9
|
| 7 |
+
certifi==2026.7.22
|
| 8 |
+
cffi==2.1.1
|
| 9 |
+
cfgv==3.5.0
|
| 10 |
+
charset-normalizer==3.5.2
|
| 11 |
+
click==8.5.0
|
| 12 |
+
cloudpickle==3.1.2
|
| 13 |
+
cn2an==0.5.24
|
| 14 |
+
contourpy==1.3.3
|
| 15 |
+
cycler==0.12.1
|
| 16 |
+
decorator==5.3.1
|
| 17 |
+
Deprecated==3.0.0
|
| 18 |
+
distlib==0.4.3
|
| 19 |
+
distro==1.9.0
|
| 20 |
+
dlinfo==2.0.0
|
| 21 |
+
easydict==1.13
|
| 22 |
+
einops==0.8.2
|
| 23 |
+
einx==0.4.3
|
| 24 |
+
elastic-transport==9.4.2
|
| 25 |
+
elasticsearch==9.5.1
|
| 26 |
+
ema-pytorch==0.8.3
|
| 27 |
+
espeakng-loader==0.2.4
|
| 28 |
+
fastapi==0.143.0
|
| 29 |
+
filelock==4.1.0
|
| 30 |
+
flatbuffers==25.12.19
|
| 31 |
+
fonttools==4.66.1
|
| 32 |
+
frozendict==2.4.7
|
| 33 |
+
fsspec==2026.9.0
|
| 34 |
+
ftfy==6.3.1
|
| 35 |
+
googleapis-common-protos==1.75.5
|
| 36 |
+
graphviz==0.21
|
| 37 |
+
h11==0.16.0
|
| 38 |
+
hf-xet==1.7.0
|
| 39 |
+
httpcore2==2.13.1
|
| 40 |
+
httpx2==2.13.1
|
| 41 |
+
huggingface_hub==2.2.0
|
| 42 |
+
identify==2.6.20
|
| 43 |
+
idna==3.20
|
| 44 |
+
inflect==7.5.0
|
| 45 |
+
jaconv==0.5.0
|
| 46 |
+
jieba==0.42.1
|
| 47 |
+
Jinja2==3.1.6
|
| 48 |
+
joblib==1.6.0
|
| 49 |
+
kiwisolver==1.5.1
|
| 50 |
+
lazy-loader==0.6
|
| 51 |
+
librosa==1.0.0
|
| 52 |
+
linkify-it-py==2.2.0
|
| 53 |
+
llvmlite==0.50.0
|
| 54 |
+
loguru==0.7.3
|
| 55 |
+
markdown-it-py==4.2.0
|
| 56 |
+
MarkupSafe==3.0.4
|
| 57 |
+
matplotlib==3.11.2
|
| 58 |
+
mdit-py-plugins==0.6.1
|
| 59 |
+
mdurl==0.1.2
|
| 60 |
+
ml_dtypes==0.5.4
|
| 61 |
+
more-itertools==11.1.0
|
| 62 |
+
mpmath==1.3.0
|
| 63 |
+
msgpack==1.2.3
|
| 64 |
+
muq==0.1.0
|
| 65 |
+
mutagen==1.48.1
|
| 66 |
+
narwhals==2.26.0
|
| 67 |
+
networkx==3.7
|
| 68 |
+
nnaudio==0.3.4
|
| 69 |
+
nodeenv==1.11.0
|
| 70 |
+
numba==0.68.0
|
| 71 |
+
numpy==2.5.3
|
| 72 |
+
onnxruntime==1.31.0
|
| 73 |
+
opentelemetry-api==1.45.1
|
| 74 |
+
opentelemetry-exporter-http-transport==0.66b1
|
| 75 |
+
opentelemetry-exporter-otlp-common==0.66b1
|
| 76 |
+
opentelemetry-exporter-otlp-proto-common==1.45.1
|
| 77 |
+
opentelemetry-exporter-otlp-proto-http==1.45.1
|
| 78 |
+
opentelemetry-proto==1.45.1
|
| 79 |
+
opentelemetry-sdk==1.45.1
|
| 80 |
+
opentelemetry-semantic-conventions==0.66b1
|
| 81 |
+
packaging==26.3
|
| 82 |
+
pandas==3.0.6
|
| 83 |
+
phonemizer==3.4.0
|
| 84 |
+
pillow==12.3.0
|
| 85 |
+
platformdirs==4.12.4
|
| 86 |
+
pooch==1.9.0
|
| 87 |
+
pre_commit==4.6.2
|
| 88 |
+
proces==0.1.7
|
| 89 |
+
protobuf==7.36.2
|
| 90 |
+
psutil==7.2.2
|
| 91 |
+
py3langid==0.2.2
|
| 92 |
+
pycparser==3.11
|
| 93 |
+
pydantic==2.14.0
|
| 94 |
+
pydantic_core==2.50.0
|
| 95 |
+
Pygments==2.21.0
|
| 96 |
+
pykakasi==2.3.0
|
| 97 |
+
pyluwen==0.10.0
|
| 98 |
+
pyparsing==3.3.3
|
| 99 |
+
pypinyin==0.55.0
|
| 100 |
+
python-dateutil==2.9.0.post0
|
| 101 |
+
python-discovery==1.6.2
|
| 102 |
+
PyYAML==6.0.3
|
| 103 |
+
regex==2026.9.29
|
| 104 |
+
requests==2.34.2
|
| 105 |
+
rich==15.0.0
|
| 106 |
+
safetensors==0.8.0
|
| 107 |
+
scikit-learn==1.9.1
|
| 108 |
+
scipy==1.18.1
|
| 109 |
+
seaborn==0.13.2
|
| 110 |
+
setuptools==80.10.2
|
| 111 |
+
setuptools-scm==8.1.0
|
| 112 |
+
shellingham==1.5.4
|
| 113 |
+
six==1.17.0
|
| 114 |
+
sniffio==1.3.1
|
| 115 |
+
soundfile==0.14.0
|
| 116 |
+
soxr==1.1.0
|
| 117 |
+
starlette==1.7.0
|
| 118 |
+
sympy==1.14.0
|
| 119 |
+
textual==8.2.8
|
| 120 |
+
threadpoolctl==3.7.0
|
| 121 |
+
tokenizers==0.23.3
|
| 122 |
+
tomli==2.5.0
|
| 123 |
+
torch==2.14.1+cpu
|
| 124 |
+
torch-einops-utils==0.1.36
|
| 125 |
+
torchaudio==2.11.0+cpu
|
| 126 |
+
torchdiffeq==0.2.5
|
| 127 |
+
torchvision==0.29.1+cpu
|
| 128 |
+
tqdm==4.70.1
|
| 129 |
+
transformers==5.19.0
|
| 130 |
+
truststore==0.10.4
|
| 131 |
+
tt-smi==6.7.0
|
| 132 |
+
tt-tools-common==1.6.0
|
| 133 |
+
tt-umd==0.9.12
|
| 134 |
+
typeguard==4.6.0
|
| 135 |
+
typer==0.27.3
|
| 136 |
+
typing-inspection==0.4.4
|
| 137 |
+
typing_extensions==4.16.0
|
| 138 |
+
Unidecode==1.4.0
|
| 139 |
+
urllib3==2.8.0
|
| 140 |
+
uvicorn==0.54.0
|
| 141 |
+
virtualenv==21.14.6
|
| 142 |
+
wandb==0.30.0
|
| 143 |
+
wcwidth==0.9.2
|
| 144 |
+
wheel==0.48.0
|
| 145 |
+
wrapt==2.5.0
|
| 146 |
+
x-clip==0.14.4
|
| 147 |
+
x-transformers==2.31.7
|
| 148 |
+
xxhash==4.0.1
|
tt_kernel_manifest.json
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": "5.1",
|
| 3 |
+
"name": "diffrhythm-1.2-p100a",
|
| 4 |
+
"tt_metal_version": "0.0.0.dev0",
|
| 5 |
+
"arch": "blackhole",
|
| 6 |
+
"device_count": 1,
|
| 7 |
+
"producer": {
|
| 8 |
+
"tt_kernel_version": "0.1.0",
|
| 9 |
+
"created_at": "2026-10-10T03:07:37.410607+00:00",
|
| 10 |
+
"hostname": ""
|
| 11 |
+
},
|
| 12 |
+
"weights": {
|
| 13 |
+
"repo_id": "ASLP-lab/DiffRhythm-1_2-full",
|
| 14 |
+
"revision": null,
|
| 15 |
+
"allow_patterns": null,
|
| 16 |
+
"ignore_patterns": null,
|
| 17 |
+
"repo_type": "model"
|
| 18 |
+
},
|
| 19 |
+
"mesh": null,
|
| 20 |
+
"entrypoint": null,
|
| 21 |
+
"resources": null,
|
| 22 |
+
"capabilities": null,
|
| 23 |
+
"env": {},
|
| 24 |
+
"bundled": null,
|
| 25 |
+
"deps": null,
|
| 26 |
+
"container": {
|
| 27 |
+
"image": {
|
| 28 |
+
"registry": "hf",
|
| 29 |
+
"repository": "diffrhythm-1.2-p100a",
|
| 30 |
+
"tag": "tt-model/diffrhythm-1.2-p100a:0cc125e439ef",
|
| 31 |
+
"digest": "sha256:0cc125e439ef6882a01bae57bece67f14ec2ad123aec1fb12e86bc50175a4b44"
|
| 32 |
+
},
|
| 33 |
+
"kind": "tt-dit-server",
|
| 34 |
+
"runtime": {
|
| 35 |
+
"app": "tt_diffrhythm.server:app",
|
| 36 |
+
"mesh_shape_env": "DIFFRHYTHM_MESH_SHAPE",
|
| 37 |
+
"packages": [
|
| 38 |
+
"torchaudio==2.11.0+cpu",
|
| 39 |
+
"torchdiffeq",
|
| 40 |
+
"x-transformers",
|
| 41 |
+
"transformers",
|
| 42 |
+
"librosa",
|
| 43 |
+
"soundfile",
|
| 44 |
+
"muq",
|
| 45 |
+
"mutagen",
|
| 46 |
+
"ema-pytorch",
|
| 47 |
+
"accelerate",
|
| 48 |
+
"einops",
|
| 49 |
+
"tqdm",
|
| 50 |
+
"wandb",
|
| 51 |
+
"scipy",
|
| 52 |
+
"numpy",
|
| 53 |
+
"huggingface_hub",
|
| 54 |
+
"phonemizer",
|
| 55 |
+
"espeakng-loader",
|
| 56 |
+
"pykakasi",
|
| 57 |
+
"jieba",
|
| 58 |
+
"onnxruntime",
|
| 59 |
+
"cn2an",
|
| 60 |
+
"pypinyin",
|
| 61 |
+
"Unidecode",
|
| 62 |
+
"inflect",
|
| 63 |
+
"py3langid==0.2.2"
|
| 64 |
+
],
|
| 65 |
+
"lock": "requirements.lock"
|
| 66 |
+
},
|
| 67 |
+
"serve": {
|
| 68 |
+
"hardware": "p100a",
|
| 69 |
+
"mesh_device": "P100",
|
| 70 |
+
"port": 8000,
|
| 71 |
+
"max_model_len": null,
|
| 72 |
+
"max_num_seqs": null,
|
| 73 |
+
"block_size": null,
|
| 74 |
+
"server_timeout": null,
|
| 75 |
+
"capabilities": null,
|
| 76 |
+
"additional_config": {},
|
| 77 |
+
"args": [],
|
| 78 |
+
"env": {
|
| 79 |
+
"ARCH_NAME": "blackhole",
|
| 80 |
+
"TT_METAL_OPERATION_TIMEOUT_SECONDS": "600.0"
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
"serve_profiles": [
|
| 84 |
+
{
|
| 85 |
+
"hardware": null,
|
| 86 |
+
"mesh_device": null,
|
| 87 |
+
"port": null,
|
| 88 |
+
"max_model_len": null,
|
| 89 |
+
"max_num_seqs": null,
|
| 90 |
+
"block_size": null,
|
| 91 |
+
"server_timeout": null,
|
| 92 |
+
"capabilities": null,
|
| 93 |
+
"additional_config": {},
|
| 94 |
+
"args": [],
|
| 95 |
+
"env": {},
|
| 96 |
+
"name": "default",
|
| 97 |
+
"description": null
|
| 98 |
+
}
|
| 99 |
+
],
|
| 100 |
+
"default_profile": null,
|
| 101 |
+
"code_dir": "code",
|
| 102 |
+
"verify": [
|
| 103 |
+
"import tt_diffrhythm.server as s; assert s.app",
|
| 104 |
+
"from tt_diffrhythm.local import LocalWorker; assert LocalWorker",
|
| 105 |
+
"import tt_diffrhythm.server as s; s._enter_checkout(); import infer_utils, model; assert model.CFM and infer_utils.load_checkpoint",
|
| 106 |
+
"from tt_diffrhythm.server import _enter_checkout; _enter_checkout(); import os; assert os.path.isfile('infer/example/vocal.npy'), 'the negative style prompt did not ship'",
|
| 107 |
+
"from tt_diffrhythm.server import _enter_checkout; _enter_checkout(); import json; assert json.load(open('config/diffrhythm-1b.json'))['model']",
|
| 108 |
+
"from tt_diffrhythm.server import _enter_checkout; _enter_checkout(); from infer_utils import CNENTokenizer; assert CNENTokenizer().encode('a quiet morning')",
|
| 109 |
+
"from tt_diffrhythm.server import _point_phonemizer_at_a_bundled_espeak as fix; fix(); from phonemizer.backend import EspeakBackend; assert EspeakBackend('en-us').phonemize(['hello'])[0].strip()"
|
| 110 |
+
],
|
| 111 |
+
"built": {
|
| 112 |
+
"image": "tt-model/diffrhythm-1.2-p100a:0cc125e439ef",
|
| 113 |
+
"repo": "sunandbean/diffrhythm-1.2-p100a",
|
| 114 |
+
"tt_model_version": "0.1.0",
|
| 115 |
+
"created_at": "2026-10-10T03:02:08+00:00",
|
| 116 |
+
"tt_metal": {
|
| 117 |
+
"sha": "58648d44443223041148125843ca7c7d4780463e",
|
| 118 |
+
"describe": null,
|
| 119 |
+
"dirty": false,
|
| 120 |
+
"scm_version": "0.0.0.dev0",
|
| 121 |
+
"mode": "git",
|
| 122 |
+
"remote": "https://github.com/tenstorrent/tt-metal",
|
| 123 |
+
"branch": null,
|
| 124 |
+
"pushed": null
|
| 125 |
+
},
|
| 126 |
+
"code_sha256": "322cdea4755e9a8117adc934aed2edfd1e3a7631fbbe9949bdbdc46c077cf184",
|
| 127 |
+
"image_digest": "sha256:0cc125e439ef6882a01bae57bece67f14ec2ad123aec1fb12e86bc50175a4b44"
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
}
|