Skip to content

Commit a8d23ee

Browse files
committed
feat: implement Whisper forward() for fine-tuning support (fixes #2678)
- Implemented cross-entropy loss computation in WhisperWarp.forward() - Added fine-tuning example script and documentation - Users can now fine-tune any Whisper model on custom data via FunASR training framework
1 parent c83e666 commit a8d23ee

3 files changed

Lines changed: 137 additions & 2 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Whisper Fine-tuning with FunASR
2+
3+
Fine-tune OpenAI Whisper models on your own data using FunASR's training framework.
4+
5+
## Supported Models
6+
7+
- whisper-tiny / whisper-tiny.en
8+
- whisper-base / whisper-base.en
9+
- whisper-small / whisper-small.en
10+
- whisper-medium / whisper-medium.en
11+
- whisper-large-v1 / whisper-large-v2 / whisper-large-v3 / whisper-large-v3-turbo
12+
13+
## Data Preparation
14+
15+
Prepare data in JSONL format:
16+
17+
```json
18+
{"key": "utt001", "source": "/path/to/audio1.wav", "target": "the transcription text"}
19+
{"key": "utt002", "source": "/path/to/audio2.wav", "target": "another transcription"}
20+
```
21+
22+
## Fine-tuning
23+
24+
```bash
25+
bash finetune.sh
26+
```
27+
28+
Or customize directly:
29+
30+
```python
31+
from funasr import AutoModel
32+
33+
model = AutoModel(model="Whisper-large-v3", model_conf={"hub": "openai"})
34+
35+
# Training uses the forward() method which computes cross-entropy loss
36+
# on (mel-spectrogram, token_ids) pairs
37+
```
38+
39+
## Key Parameters
40+
41+
| Parameter | Default | Description |
42+
|-----------|---------|-------------|
43+
| model | Whisper-large-v3 | Model size |
44+
| lr | 1e-5 | Learning rate (lower for larger models) |
45+
| max_epoch | 10 | Training epochs |
46+
| batch_size | 4 | Per-GPU batch size |
47+
| warmup_steps | 500 | LR warmup |
48+
49+
## Tips
50+
51+
- For Chinese fine-tuning, use `whisper-large-v3` (best multilingual base)
52+
- Freeze encoder for faster training: add `++train_conf.freeze_param="model.encoder"`
53+
- Use smaller learning rates (1e-5 ~ 5e-6) to avoid catastrophic forgetting
54+
- Recommended: 100+ hours of target-domain audio for meaningful improvement
55+
56+
## After Fine-tuning
57+
58+
```python
59+
from funasr import AutoModel
60+
61+
# Load fine-tuned model
62+
model = AutoModel(model="/path/to/exp/whisper_finetune")
63+
result = model.generate(input="test.wav")
64+
print(result[0]["text"])
65+
```
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/bin/bash
2+
# Whisper Fine-tuning with FunASR
3+
#
4+
# This script fine-tunes OpenAI Whisper models on custom data using FunASR's training framework.
5+
# Supports: whisper-tiny, whisper-base, whisper-small, whisper-medium, whisper-large-v3
6+
#
7+
# Data format: JSONL with "audio" and "text" fields
8+
# {"key": "utt1", "source": "/path/to/audio.wav", "target": "transcription text"}
9+
10+
export CUDA_VISIBLE_DEVICES=0,1
11+
12+
model_name="Whisper-large-v3"
13+
train_data="data/train.jsonl"
14+
val_data="data/val.jsonl"
15+
output_dir="exp/whisper_finetune"
16+
17+
python -m funasr.bin.train \
18+
++model="${model_name}" \
19+
++model_conf.hub="openai" \
20+
++train_data_set_list="${train_data}" \
21+
++valid_data_set_list="${val_data}" \
22+
++dataset_conf.batch_size=4 \
23+
++dataset_conf.num_workers=4 \
24+
++train_conf.output_dir="${output_dir}" \
25+
++train_conf.max_epoch=10 \
26+
++train_conf.lr=1e-5 \
27+
++train_conf.warmup_steps=500 \
28+
++optim="adam" \
29+
++optim_conf.lr=1e-5 \
30+
++scheduler="warmuplr" \
31+
++scheduler_conf.warmup_steps=500

funasr/models/whisper/model.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,48 @@ def __init__(self, *args, **kwargs):
6565

6666
def forward(
6767
self,
68+
speech: torch.Tensor = None,
69+
speech_lengths: torch.Tensor = None,
70+
text: torch.Tensor = None,
71+
text_lengths: torch.Tensor = None,
72+
**kwargs,
6873
):
69-
"""Forward pass for training."""
70-
pass
74+
"""Forward pass for training. Computes cross-entropy loss.
75+
76+
Args:
77+
speech: (B, T, D) mel-spectrogram features
78+
speech_lengths: (B,) lengths of each audio
79+
text: (B, U) token IDs (with SOT/EOT tokens)
80+
text_lengths: (B,) lengths of each text sequence
81+
Returns:
82+
dict with "loss" and optionally "stats"
83+
"""
84+
if speech is None or text is None:
85+
raise ValueError("forward() requires speech and text for training")
86+
87+
# Encoder
88+
audio_features = self.model.encoder(speech)
89+
90+
# Decoder: shift text right for teacher forcing
91+
# text format: [SOT, lang, task, ..., tokens, EOT]
92+
decoder_input = text[:, :-1]
93+
decoder_target = text[:, 1:]
94+
95+
# Decoder forward
96+
logits = self.model.decoder(decoder_input, audio_features)
97+
98+
# Cross-entropy loss (ignore padding, token_id = -1 or pad)
99+
loss = F.cross_entropy(
100+
logits.reshape(-1, logits.size(-1)),
101+
decoder_target.reshape(-1),
102+
ignore_index=-100,
103+
)
104+
105+
stats = {
106+
"loss": loss.detach().item(),
107+
"batch_size": speech.size(0),
108+
}
109+
return {"loss": loss, "stats": stats}
71110

72111
def inference(
73112
self,

0 commit comments

Comments
 (0)