Skip to content

Commit 9fb9b06

Browse files
Add Whisper-tiny.en CPU quality regression test
Signed-off-by: Roberto Laudani <laudani@roofline.ai>
1 parent 5fc52b6 commit 9fb9b06

9 files changed

Lines changed: 1829 additions & 0 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Whisper-tiny.en regression test
2+
3+
End-to-end regression for
4+
[`openai/whisper-tiny.en`](https://huggingface.co/openai/whisper-tiny.en). IREE
5+
compiles the committed MLIR, runs a single static-shape, teacher-forced forward
6+
on CPU, and compares the output logits against
7+
committed reference values.
8+
9+
## Files
10+
11+
| File | Description |
12+
| --- | --- |
13+
| `whisper-tiny-en_quality_cpu.json` | Quality test definition |
14+
| `modules/whisper-tiny-en_cpu.json` | Module definition and compiler flags |
15+
| `model.mlir` | Exported torch-dialect program |
16+
| `inference_input.0a.bin` | Log-mel features (`1x80x3000xf32`) |
17+
| `inference_input.0b.bin` | Decoder input ids (`1x33xi64`) |
18+
| `inference_output.0.bin` | Expected output logits (`1x33x51864xf32`) |
19+
20+
Model parameters are hosted on the Hugging Face Hub at
21+
[`roofline/iree-regression-models`](https://huggingface.co/roofline/iree-regression-models)
22+
as `whisper-tiny-en/real_weights.irpa`, pinned by revision in
23+
`whisper-tiny-en_quality_cpu.json` and fetched at test time.
24+
25+
26+
## Reproducing artifacts
27+
28+
```bash
29+
pip install -r whisper-tiny-en/requirements.txt
30+
python3 whisper-tiny-en/generate.py
31+
```
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#!/usr/bin/env python3
2+
"""Regenerates the Whisper-tiny.en IREE quality-test artifacts.
3+
4+
Produces in --out-dir:
5+
model.mlir exported torch-dialect MLIR
6+
real_weights.irpa externalized parameters
7+
inference_input.0a.bin f32 log-mel features, shape [1, 80, 3000]
8+
inference_input.0b.bin int64 decoder_input_ids, shape [1, dec_len]
9+
inference_output.0.bin f32 logits, shape [1, dec_len, 51864]
10+
"""
11+
12+
import argparse
13+
from pathlib import Path
14+
15+
import torch
16+
17+
from datasets import Audio, load_dataset
18+
from transformers import WhisperForConditionalGeneration, WhisperProcessor
19+
import iree.turbine.aot as aot
20+
21+
MODEL_ID = "openai/whisper-tiny.en"
22+
MODEL_REVISION = "87c7102498dcde7456f24cfd30239ca606ed9063"
23+
24+
DATASET_ID = "hf-internal-testing/librispeech_asr_dummy"
25+
DATASET_REVISION = "5be91486e11a2d616f4ec5db8d3fd248585ac07a"
26+
DATASET_CONFIG = "clean"
27+
DATASET_SPLIT = "validation"
28+
SAMPLING_RATE = 16000
29+
SAMPLE_INDEX = 0
30+
31+
32+
def create_causal_mask_4d(seq_len, dtype=torch.float32):
33+
"""Precomputed 4D additive causal mask, shape [1, 1, seq_len, seq_len]."""
34+
mask_fill = torch.finfo(dtype).min
35+
mask = torch.full((seq_len, seq_len), mask_fill, dtype=dtype)
36+
return torch.triu(mask, diagonal=1)[None, None]
37+
38+
39+
def load_reference_audio():
40+
"""Load the pinned LibriSpeech dummy sample as a 16 kHz mono waveform."""
41+
import io
42+
43+
import soundfile as sf
44+
45+
ds = load_dataset(
46+
DATASET_ID, DATASET_CONFIG, split=DATASET_SPLIT, revision=DATASET_REVISION
47+
)
48+
row = ds.cast_column("audio", Audio(decode=False))[SAMPLE_INDEX]
49+
audio_field = row["audio"]
50+
src = (
51+
io.BytesIO(audio_field["bytes"])
52+
if audio_field.get("bytes")
53+
else audio_field["path"]
54+
)
55+
audio, sampling_rate = sf.read(src, dtype="float32", always_2d=False)
56+
assert (
57+
sampling_rate == SAMPLING_RATE
58+
), f"expected {SAMPLING_RATE} Hz, got {sampling_rate}"
59+
return audio, row["text"]
60+
61+
62+
class WhisperLogits(torch.nn.Module):
63+
"""Single teacher-forced forward producing logits."""
64+
65+
def __init__(self, model):
66+
super().__init__()
67+
self.model = model
68+
69+
def forward(self, input_features, decoder_input_ids):
70+
encoder_hidden_states = self.model.model.encoder(
71+
input_features
72+
).last_hidden_state
73+
mask = create_causal_mask_4d(
74+
decoder_input_ids.shape[1], dtype=encoder_hidden_states.dtype
75+
)
76+
sequence_output = self.model.model.decoder(
77+
input_ids=decoder_input_ids,
78+
attention_mask=mask,
79+
encoder_hidden_states=encoder_hidden_states,
80+
use_cache=False,
81+
).last_hidden_state
82+
return self.model.proj_out(sequence_output)
83+
84+
85+
def main():
86+
parser = argparse.ArgumentParser()
87+
parser.add_argument(
88+
"--out-dir",
89+
type=Path,
90+
default=Path(__file__).parent / "artifacts",
91+
)
92+
args = parser.parse_args()
93+
args.out_dir.mkdir(parents=True, exist_ok=True)
94+
95+
print(f"Loading model {MODEL_ID}@{MODEL_REVISION}")
96+
model = WhisperForConditionalGeneration.from_pretrained(
97+
MODEL_ID,
98+
revision=MODEL_REVISION,
99+
dtype=torch.float32,
100+
attn_implementation="eager",
101+
)
102+
model.eval()
103+
processor = WhisperProcessor.from_pretrained(MODEL_ID, revision=MODEL_REVISION)
104+
105+
print(f"Loading reference audio {DATASET_ID}@{DATASET_REVISION}[{SAMPLE_INDEX}]")
106+
audio, transcript = load_reference_audio()
107+
input_features = processor(
108+
audio, sampling_rate=SAMPLING_RATE, return_tensors="pt"
109+
).input_features.to(torch.float32)
110+
print(f" transcript {transcript!r}")
111+
print(f" input features {tuple(input_features.shape)} {input_features.dtype}")
112+
113+
# Teacher-forced decoder input: forced prefix and ground-truth transcript.
114+
tokenizer = processor.tokenizer
115+
prefix = [
116+
model.config.decoder_start_token_id,
117+
tokenizer.convert_tokens_to_ids("<|notimestamps|>"),
118+
]
119+
transcript_ids = tokenizer(transcript, add_special_tokens=False).input_ids
120+
decoder_input_ids = torch.tensor([prefix + transcript_ids], dtype=torch.int64)
121+
print(
122+
f" decoder ids {tuple(decoder_input_ids.shape)} {decoder_input_ids.dtype}"
123+
)
124+
125+
print("Running eager reference forward")
126+
wrapper = WhisperLogits(model)
127+
with torch.no_grad():
128+
ref_logits = wrapper(input_features, decoder_input_ids)
129+
ref_logits = ref_logits.to(torch.float32).contiguous()
130+
print(f" output logits {tuple(ref_logits.shape)} {ref_logits.dtype}")
131+
132+
# Dump raw reference input/output data.
133+
print("Writing reference input/output .bin files")
134+
(args.out_dir / "inference_input.0a.bin").write_bytes(
135+
input_features.numpy().astype("<f4").tobytes()
136+
)
137+
(args.out_dir / "inference_input.0b.bin").write_bytes(
138+
decoder_input_ids.numpy().astype("<i8").tobytes()
139+
)
140+
(args.out_dir / "inference_output.0.bin").write_bytes(
141+
ref_logits.numpy().astype("<f4").tobytes()
142+
)
143+
144+
# Export to MLIR with weights externalized into a separate .irpa.
145+
print("Exporting to MLIR")
146+
aot.externalize_module_parameters(wrapper)
147+
export_output = aot.export(wrapper, args=(input_features, decoder_input_ids))
148+
export_output.save_mlir(str(args.out_dir / "model.mlir"))
149+
aot.save_module_parameters(str(args.out_dir / "real_weights.irpa"), wrapper)
150+
151+
print(f"Artifacts successfully written to {args.out_dir}")
152+
153+
154+
if __name__ == "__main__":
155+
main()
938 KB
Binary file not shown.
264 Bytes
Binary file not shown.
6.53 MB
Binary file not shown.

0 commit comments

Comments
 (0)