buddy-pronunciation-onnx
int8 ONNX exports of two phoneme-recognition models, for on-device pronunciation assessment in Buddy.
These are format conversions only β no retraining, no fine-tuning. See NOTICE for the exact export and quantization steps, as Apache-2.0 requires.
Contents
| Path | Language | Size | Vocabulary |
|---|---|---|---|
en/v1/model.int8.onnx |
English | 357 MB | 125 ARPAbet tokens, with stress digits |
zh/v1/model.int8.onnx |
Mandarin | 358 MB | 392 IPA tokens, with tone markers |
Each sits beside its vocab.json, copied verbatim from the source repository.
Paths are versioned and immutable. A future export goes to v2/; files
already published are never replaced, so a client that downloaded v1 keeps
exactly what it verified.
Why two models
They were chosen by measurement, not by reputation. Both were run against the same clips β clean speech, a planted /v/β/w/ substitution, and accented speech β and scored against CMUdict:
| Phone error rate | Notes | |
|---|---|---|
en/v1 |
0.0% | Emits ARPAbet, so it lines up with CMUdict without a translation table |
zh/v1 |
4.3% | The only one of the candidates with Mandarin tone tokens |
The English model was preferred over an L2-trained alternative that scored worse, and over a smaller 123 MB model that carries no stress information.
The Mandarin model mis-hears English think as s Ιͺ Ε k β exactly the
substitution a learner makes β so it is not used for English, where a false
accusation would be worse than a missed error.
Usage
Both are CTC models. Feed 16 kHz mono float32 audio, normalized to zero mean and unit variance (what wav2vec2 was trained on β skipping this does not error, it quietly degrades every output):
import numpy as np, soundfile as sf, onnxruntime as ort, json
audio, sr = sf.read("speech.wav") # 16 kHz mono
audio = audio.astype(np.float32)
audio = (audio - audio.mean()) / (audio.std() + 1e-7)
sess = ort.InferenceSession("en/v1/model.int8.onnx")
logits = sess.run(None, {"input_values": audio[None, :]})[0] # [1, frames, vocab]
vocab = {v: k for k, v in json.load(open("en/v1/vocab.json")).items()}
ids = logits.argmax(-1)[0]
out, prev = [], None
for i in ids: # collapse CTC repeats and blanks
if i != prev and i != 0:
out.append(vocab[int(i)])
prev = i
print(" ".join(out))
The output is logits, not probabilities β apply log-softmax per frame before using them as posteriors.
Measured at ~77 ms for a 2.3 s clip on CPU, roughly 30Γ faster than real time.
Licence
Apache-2.0, inherited from both originals. Cite the original authors rather than this repository; it exists only to host a runnable format.
Model tree for asingingbird/buddy-pronunciation-onnx
Base model
facebook/wav2vec2-xlsr-53-espeak-cv-ft