File size: 8,646 Bytes
15bcb35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48f6e72
 
 
15bcb35
48f6e72
15bcb35
48f6e72
 
 
 
15bcb35
48f6e72
15bcb35
48f6e72
 
 
15bcb35
48f6e72
 
 
7f0d463
48f6e72
 
15bcb35
 
 
48f6e72
7f0d463
ffd7f3e
 
 
 
 
 
 
15bcb35
ab6821a
 
 
 
 
15bcb35
48f6e72
15bcb35
48f6e72
15bcb35
48f6e72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15bcb35
 
 
48f6e72
15bcb35
48f6e72
 
15bcb35
48f6e72
 
 
15bcb35
48f6e72
 
15bcb35
48f6e72
15bcb35
48f6e72
15bcb35
 
 
 
48f6e72
7f0d463
48f6e72
 
 
7f0d463
48f6e72
15bcb35
 
48f6e72
 
15bcb35
 
48f6e72
 
 
15bcb35
48f6e72
 
 
15bcb35
48f6e72
 
7f0d463
48f6e72
 
 
15bcb35
 
 
48f6e72
 
15bcb35
48f6e72
15bcb35
48f6e72
 
 
7f0d463
48f6e72
15bcb35
48f6e72
15bcb35
48f6e72
15bcb35
48f6e72
7f0d463
ffd7f3e
48f6e72
ffd7f3e
48f6e72
15bcb35
ffd7f3e
 
 
 
 
ab6821a
 
 
15bcb35
 
 
48f6e72
 
15bcb35
 
 
 
 
 
 
48f6e72
15bcb35
 
 
48f6e72
15bcb35
 
 
 
48f6e72
 
 
15bcb35
48f6e72
 
15bcb35
48f6e72
15bcb35
48f6e72
 
15bcb35
 
 
48f6e72
 
 
 
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
---
language: en
license: other
base_model:
  - JetLM/SDAR-1.7B-Chat
  - Qwen/Qwen3-ASR-0.6B
datasets:
  - librispeech_asr
tags:
  - automatic-speech-recognition
  - speech
  - diffusion
  - lora
  - block-diffusion
pipeline_tag: automatic-speech-recognition
---

# Diffusion ASR

Speech recognition with a **block-diffusion language model as the decoder**. A frozen audio
encoder and a frozen text diffusion LM are joined by a small trained adapter plus LoRA β€” about
1 % of the stack is trained.

**4.60 % WER on LibriSpeech test-clean** (all 2,620 clips).

> **These are trained deltas, not a standalone model.** This repo holds the 5.25 M-parameter
> adapter and the 17.4 M-parameter LoRA. Running them also needs the two frozen base models
> below and the inference code from
> [ipritamdash/diffusion-asr-research](https://github.com/ipritamdash/diffusion-asr-research).

## Why this exists

The audio encoder came from a working ASR system. That system's own decoder is thrown away here
and a text-only diffusion model put in its place, connected through roughly 5 M trained
parameters.

The question is whether a language model that has never seen audio can be conditioned on
acoustic evidence through a thin learned interface β€” and whether writing several tokens per
forward pass buys anything.

Compare it against other frozen-encoder + frozen-LM + adapter systems. Do not compare it against
end-to-end ASR trained on thousands of hours; it saw 30.

## Results

Full LibriSpeech test-clean, 2,620 clips, corpus WER after Whisper English normalisation.

| Decoder | Config | WER | vs real time |
|---|---|---:|---:|
| **Reference loop** | block 2 | **4.60** | β€” |
| Reference loop | block 4 | 5.23 | β€” |
| **LMDeploy engine, swept config** | **block 4 Β· 4 steps Β· `sequential`** | **4.78** | 7.0Γ— |
| LMDeploy engine, old default | block 2 Β· dynamic | 5.00 | 9.1Γ— |
| LMDeploy engine, old default | block 4 Β· dynamic | 5.63 | 13.5Γ— |

Speed is single-stream real-time multiple on an A100; batched serving measured far higher
(~800Γ— on one L40S in an earlier benchmark). Under the engine's original confidence-ordered
commit rule it read a constant 0.40 worse than the reference loop; the swept `sequential`
commit order closes half of that (engine best 4.78 vs reference 4.60). The remaining +0.18 is
unexplained and stated rather than left out.

### Error analysis

Computed over every clip of the 4.60 decode.

**Output health** β€” zero empty transcripts, zero repeat loops, zero length blow-ups, and no clip
hit the token budget.

**Composition** β€” of 53,029 reference words: 3.75 % substituted, 0.40 % deleted, 0.45 %
inserted. Four errors in five are a *wrong* word rather than a missing one; the model commits to
a plausible word from unclear evidence instead of failing to produce one.

**Substitutions** β€” 40 % are within a small character edit of the reference (*jailer* β†’
*jailor*, *holmes* β†’ *homes*), which reads as blurred acoustic detail rather than a language
failure.

**Multi-piece words** β€” words the tokeniser splits into several subword pieces are 7.5 % of
words but carry 52 % of errors, at 13Γ— the failure rate of single-piece words. This is the
dominant failure mode.

**Length and position** β€” the shortest quarter of clips is hardest (5.85 % against 4.37 % on the
longest), and quality is flat across an utterance. A fixed token budget and a block decoder could
have degraded on long input; neither did.

## Files

| File | Contents | Params |
|---|---|---:|
| `adapter.pt` | audio adapter + `gain`, CTC logit scale, CTC blank projection | 5,250,049 (+2,050) |
| `lora.pt` | LoRA on the decoder, PEFT state dict, 392 tensors | 17,432,576 |

`adapter.pt` keys: `adapter`, `log_s`, `blank_w`, `blank_b`, `epoch`, `adapter_kind` (`conv`),
`adapter_stride` (`1`), `attn_mode` (`stair`), plus dev metrics.
`lora.pt` keys: `lora`, `epoch`, `dev_ce`, `objective`.

`log_s`, `blank_w` and `blank_b` belong to the CTC alignment loss used during training. They are
not used at decode time, and are kept so training can be resumed or audited.

## Base models β€” frozen, download separately

| Role | Repo | Pinned revision |
|---|---|---|
| Decoder | `JetLM/SDAR-1.7B-Chat` | `97cf0dc9a7e85433f3f71a6bbac6fec9d381294f` |
| Encoder | `Qwen/Qwen3-ASR-0.6B` | `5eb144179a02acc5e5ba31e748d22b0cf3e303b0` |

Use these exact revisions β€” the weights here were trained against them.

> The decoder must be loaded through the project repo's patched loader
> (`qwen_sdar/sdar.py::load_patched_sdar`), which swaps CUDA-only flash-attn for SDPA and adds
> the embeddings-input path. Plain `from_pretrained` will not reproduce these numbers.

## Architecture

```
audio 16 kHz β†’ Qwen3-ASR-0.6B encoder β†’ adapter β†’ SDAR-1.7B decoder + LoRA β†’ text
               frozen, 1024-d @ 12.5 Hz  trained   frozen                     trained
```

**Adapter** β€” `Conv1d(1024β†’1024, k=3, stride=1)` β†’ GELU β†’ `Linear(1024β†’2048)` β†’
`LayerNorm(2048)` β†’ Γ—`gain`. Stride 1 keeps the encoder's native 12.5 Hz; pooling to 6.25 Hz
measured worse across three seeds per arm, at roughly half the training cost.

`gain` initialises to the RMS of the decoder's token embeddings. A random projection emits
vectors 10–50Γ— the size of real word vectors, and a decoder fed a prefix that loud learns to
ignore it; matching the embedding scale is what makes the audio legible at all.

**LoRA** β€” `r=16`, `alpha=32`, dropout 0, on `q,k,v,o,gate,up,down` in all 28 layers. LoRA scales
updates by `alpha/r`, so the effective learning rate on those weights is 2Γ— nominal.

**Attention is block-causal at every stage**, training and inference alike. Each block sees the
audio, all earlier blocks, and itself. Training and serving therefore use the same convention,
and the model runs on a standard diffusion inference engine without retraining.

## Training

30 hours of LibriSpeech `train-clean-100`, speaker-disjoint from the held-out split. Three
stages, never joint:

| Stage | Trains | Loss |
|---|---|---|
| Grounding | adapter | CTC only, decoder detached |
| SFT | adapter | CTC + diffusion cross-entropy |
| LoRA | LoRA | diffusion cross-entropy only, adapter frozen |

Roughly 32 GPU-hours in total.

## Decoding

Block-causal, blocks written left to right, earlier blocks frozen once committed.

| | |
|---|---|
| Block width | 4 (engine, swept) Β· 2 (reference loop) |
| Denoising steps per block | up to 4 |
| Commit rule | reference loop: confidence β‰₯ 0.95 with forced fallback; engine: **`sequential` unmasking** (position order), found best in a 50-arm sweep |
| Token budget | 160 |

A 50-arm sweep of every engine dial (widths 1–64, steps, thresholds, all three unmasking
strategies), confirmed on the full dev set, chose the engine config above; decoding wider than
the trained block-4 width collapses (width 8 already mass-produces empty output), so the
frontier honestly ends at width 4.

With the original confidence-ordered commits, width 2 (below the paper's swept range) measured
best; with `sequential` commits the trained width 4 wins β€” commit order, not width alone, was
carrying that result.

## Usage

The encoder and decoder have conflicting dependencies, so inference runs in two steps in two
environments. Both need a CUDA GPU.

```bash
git clone https://github.com/ipritamdash/diffusion-asr-research
cd diffusion-asr-research

huggingface-cli download Tachyeon/diffusion-asr adapter.pt lora.pt --local-dir .

# 1. encoder environment: audio -> features
pip install -r requirements-encoder.txt
python featurize.py --audio clip.wav --out features.pt

# 2. decoder environment: features -> transcript
pip install -r requirements-decode.txt
python decode.py --features features.pt --adapter adapter.pt --lora lora.pt --block_length 2
```

`decode.py` reads `adapter_stride` from the checkpoint and refuses to run if it is not recorded.
A stride-1 and a stride-2 adapter have identical tensor shapes, so guessing wrong loads cleanly
and silently emits a prefix of the wrong length.

For serving, merge the LoRA and run on LMDeploy's PyTorch diffusion engine; see `engine/` in the
project repo.

## Scope

A research model for **English read speech**. LibriSpeech is clean, read audiobook material and
these numbers describe that domain.

## Licence

The adapter and LoRA here are trained deltas. Any use depends on the two frozen base models,
which carry **their own licences** β€” check `JetLM/SDAR-1.7B-Chat` and `Qwen/Qwen3-ASR-0.6B`
before redistributing anything, and do not redistribute a merged checkpoint (which embeds the
base weights) without confirming their terms.