File size: 12,939 Bytes
cfbcb75
 
 
4d0e814
 
cfbcb75
 
 
 
4d0e814
cfbcb75
4d0e814
cfbcb75
4d0e814
 
cfbcb75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b253d0a
cfbcb75
42cf480
cfbcb75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b253d0a
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
---
tags:
- blackhole
- diffrhythm
- music-generation
- p100a
- tenstorrent
- text-to-audio
- tt-dit-server
- tt-metal
- tt-model-cache
- tt-model-catalog
- tt-model-container
- tt-nn
- ttnn
pipeline_tag: text-to-audio
license: apache-2.0
license_link: https://huggingface.co/ASLP-lab/DiffRhythm-1_2-full
base_model:
- ASLP-lab/DiffRhythm-1_2-full
---

# diffrhythm-1.2-p100a

DiffRhythm 1.2 Full, ported to one Tenstorrent Blackhole p100a.
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)

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.

**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.
**What it builds on** β€” DiffRhythm's own pipeline (Apache-2.0), which runs unchanged on the CPU; only those two stages move.

**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.

## Listen

Exactly what the container returned on run 1 of the measurement below β€” `POST /predict` with
`{"prompt": "warm lo-fi hip hop, mellow rhodes piano, soft vinyl crackle, relaxed drums",
"instrumental": true, "duration": 96, "seed": 42}`, generated in **10.05 s** on the card. The file is
the response's WAV bytes, unmodified: 96.0 s stereo 44.1 kHz 16-bit, sha256 `7e89a573e6d94e4f`.

<audio controls src="https://huggingface.co/sunandbean/diffrhythm-1.2-p100a/resolve/main/media/diffrhythm_96s_instrumental.wav"></audio>

[`media/diffrhythm_96s_instrumental.wav`](media/diffrhythm_96s_instrumental.wav) (16 MB)

## How it is wired

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.

Both shims reach the card through exactly one method, `worker.call(msg)`, and there are two things that answer it.

**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`:

```
  DiffRhythm venv (CPU)                    TT image (p100a)
  ─────────────────────                    ────────────────
  style encoder                ──socket──► worker.py
  CFM sampler loop                         DiffRhythmDiT  (on the card)
  post-processing                          OobleckTT      (on the card)
```

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.

**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.

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.

## Quickstart

Prerequisite: a tt-metal / ttnn environment for the worker, and DiffRhythm installed in its own environment for the host side.

```bash
hf download sunandbean/diffrhythm-1.2-p100a --include "code/*" --local-dir diffrhythm-1.2-p100a && cd diffrhythm-1.2-p100a
pip install -e code/
```

Host side, inside the DiffRhythm environment:

```python
from tt_diffrhythm import TTWorker, RemoteDiffRhythmDiT, RemoteVae

worker = TTWorker(cfm_model_pt, model="diffrhythm", vae=vae_model_pt).start()
worker.wait_ready()
cfm.transformer = RemoteDiffRhythmDiT(worker, cfm.transformer)
...                                          # upstream CFM.sample runs unchanged
audio = RemoteVae(worker, upsample=2048)(latents)
worker.close()
```

Worker side, inside the tt-metal image:

```bash
python -m tt_diffrhythm.worker --socket /ipc/worker.sock \
  --checkpoint /checkpoint/cfm_model.pt --vae /vae/vae_model.pt
python -m tt_diffrhythm.warm_cache     # compile every sequence bucket ahead of time
```

| | |
|---|---|
| **Duration** | 96–285 seconds |
| **Lyrics** | Plain lyrics with estimated line timing, or a timestamped LRC |
| **Guidance** | CFG: `pred + 4 (pred βˆ’ null)` β€” every step runs two DiT calls |
| **Shapes** | Song lengths fall into sequence buckets (`diffrhythm_host.seq_buckets`), both CFG branches, plus the VAE windows. `warm_cache` compiles all of them. |

## Performance

Measured on an idle p100a. A 96-second song is 2067 latent frames, padded to a sequence of 2560.

| | |
|---|---:|
| **DiT, whole 96-second song** | **8.9 s** (7.7 s at HiFi2) |
| One DiT call at sequence 2560 | 121 ms |
| Per call during the device check | 0.143–0.186 s |

Where a 121 ms call goes (`dit_profile.py`, 156.8 ms profiled):

| Op | Time | Calls |
|---|---:|---:|
| matmul | 66.6 ms | 66 |
| SDPA | 21.9 ms | 16 |
| add | 12.0 ms | 47 |
| RMS norm | 7.7 ms | 32 |
| slice | 7.3 ms | 62 |
| rotary embedding | 5.2 ms | 32 |

The audio VAE decoder, against a CPU decode of the same latents:

| Latent frames | CPU | p100a (first / warm) |
|---:|---:|---:|
| 512 | 10.67 s | 33.94 / **0.33** s |
| 1024 | β€” | 0.81 s |

The first decode at a shape pays the kernel compilation; afterwards it is 30Γ— faster than the CPU.

### From the published container

The same image anyone can `tt-model pull`, reached over its own HTTP server on an idle card
(2026-10-09). Three 96-second instrumentals, seed 42, from a cold start:

| | run 1 | run 2 | run 3 |
|---|---:|---:|---:|
| **Generation** | **10.05 s** | 10.06 s | 10.04 s |
| CFM transformer, 62 calls | 7.68 s | 7.68 s | 7.69 s |
| VAE decode | 2.03 s | 2.05 s | 2.01 s |

All three returned the same 96.0 s stereo 44.1 kHz WAV. Load to ready was 50 s, of which the VAE
warm-up is 3.1 s; device DRAM after load is 2.9 GB of 29.9 β€” the card is nowhere near full, and
the port holds that headroom deliberately so the sequence buckets can grow to 285 seconds.

The whole request takes about 30 s of wall time, because the response carries the song: 17 MB of
WAV, base64-encoded. Generation is the 10 s above; the rest is serialising and shipping it.

### Against an RTX 5070 Ti

DiffRhythm's own GPU path with the deployment's stage-by-stage placement (one model in VRAM at a
time, which is what keeps it inside 16 GB), same prompt and duration, a fresh seed per run:

| | p100a (container) | RTX 5070 Ti |
|---|---:|---:|
| 96-second instrumental | 10.04–10.06 s | **9.73–10.05 s** |
| First run | 10.05 s | 12.35 s |
| Peak device memory | 2.9 GB of 29.9 | 2.5 GB of 15.5 |

The same, within noise. The card reaches it with the CFM transformer and the audio VAE decoder in
TTNN and everything else on the CPU; the GPU reaches it by moving three models in and out of VRAM.

### Accuracy

**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.

**The card against this port's reference**: minimum PCC **0.99994** across the whole 96-second song.

**Final latents against the GPU fp16 run**, which is what actually matters after CFG amplification:

| | Final latent PCC | Relative RMS |
|---|---:|---:|
| CPU float32 | 0.99997 | 0.008 |
| **p100a (HiFi4, the default)** | **0.9954** | 0.096 |
| p100a (HiFi2) | 0.9931 | 0.118 |
| CPU bf16 | 0.99129 | 0.132 |

**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.

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.

## Caveats

- **Single card only.** Mesh 1Γ—1.
- **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.
- **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.
- **CFG doubles the work.** Every sampler step is two DiT calls, one conditional and one null.
- 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.
- A render holds the card for the length of the song. On a shared card that blocks everything else.

## Licensing

- Weights: [ASLP-lab/DiffRhythm-1_2-full](https://huggingface.co/ASLP-lab/DiffRhythm-1_2-full), Apache-2.0. Not redistributed here.
- Port code (`code/tt_diffrhythm/`): Apache-2.0, from [SunandBean/tt-diffrhythm](https://github.com/SunandBean/tt-diffrhythm).
- `code/{model,infer,g2p,config,dataset}/` are **DiffRhythm itself** (Apache-2.0), at [`28ad63c`](https://github.com/ASLP-lab/DiffRhythm/commit/28ad63c0f096fe2ee258bcabbcf081d5d9366afd), unmodified, with the upstream Apache-2.0 licence at [`code/LICENSE-DiffRhythm.md`](code/LICENSE-DiffRhythm.md) (its `g2p/` carries Amphion's MIT headers in the files themselves). 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.
- `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.
- `code/THIRD-PARTY.md` lists every directory under `code/` that came from somewhere else, with its upstream commit and where its licence text sits in this repo.

## Run it as a container

Everything below is generated by `tt-model package` from the manifest this image was built from.

```bash
tt-model pull  sunandbean/diffrhythm-1.2-p100a --with-weights
tt-model serve sunandbean/diffrhythm-1.2-p100a
```

`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`.

### Request shape

`POST /predict` takes `{"prompt": str, "lyrics": str, "instrumental": bool,
"duration": int, "seed": int}` and returns a base64 WAV with `timing_s` and the `lrc` the
song was actually sung to.

`prompt` is the style description. Vocals need `lyrics`: real LRC is used as given, and
plain lines get approximate starts derived from their word counts -- a convenience, not
learned alignment. `duration` is 96-285 seconds; use the ACE-Step port for longer songs.
The schedule is fixed at **32 steps**; any other `inference_steps` is a 422.

### Provenance

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):

| component | built from |
| --- | --- |
| tt-metal | [`58648d44443223041148125843ca7c7d4780463e`](https://github.com/tenstorrent/tt-metal/commit/58648d44443223041148125843ca7c7d4780463e) |
| `code/` digest | `322cdea4755e9a81` (sha256, first 16 hex digits) |
| built | 2026-10-10T03:02:08+00:00 by tt-model 0.1.0 |