Benchmark on FluidInference/fleurs-full (650 Japanese samples):
CER: 10.29% (within expected 10-13% range)
RTFx: 136.85x (far exceeds real-time)
Avg Latency: 91.34ms per sample on M-series chips
Expected CER by Dataset (from NeMo paper):
Dataset
CER
JSUT basic5000
6.5%
Mozilla Common Voice 8.0 test
7.2%
Mozilla Common Voice 16.1 dev
10.2%
Mozilla Common Voice 16.1 test
13.3%
TEDxJP-10k
9.1%
Critical Implementation Note: Raw Logits Output
IMPORTANT: The CTC decoder outputs raw logits (not log-probabilities). You must apply log_softmax before CTC decoding.
Why?
During CoreML conversion, we discovered that log_softmax failed to convert correctly, producing extreme values (-45440 instead of -67). The solution was to output raw logits and apply log_softmax in post-processing.
Usage Example
python
1import coremltools as ct
2import numpy as np
3import torch
45# Load the three CoreML models6preprocessor = ct.models.MLModel('Preprocessor.mlpackage')7encoder = ct.models.MLModel('Encoder.mlpackage')8ctc_decoder = ct.models.MLModel('CtcDecoder.mlpackage')910# Prepare audio (16kHz, mono, max 15 seconds)11audio = np.array(audio_samples, dtype=np.float32).reshape(1,-1)12audio_length = np.array([audio.shape[1]], dtype=np.int32)1314# Pad or truncate to 240,000 samples (15 seconds)15if audio.shape[1]<240000:16 audio = np.pad(audio,((0,0),(0,240000- audio.shape[1])))17else:18 audio = audio[:,:240000]1920# Step 1: Preprocessor (audio → mel)21prep_out = preprocessor.predict({22'audio_signal': audio,23'length': audio_length
24})2526# Step 2: Encoder (mel → features)27enc_out = encoder.predict({28'mel_features': prep_out['mel_features'],29'mel_length': prep_out['mel_length']30})3132# Step 3: CTC Decoder (features → raw logits)33ctc_out = ctc_decoder.predict({34'encoder_output': enc_out['encoder_output']35})36raw_logits = ctc_out['ctc_logits']# [1, 188, 3073]3738# Apply log_softmax (CRITICAL!)39logits_tensor = torch.from_numpy(raw_logits)40log_probs = torch.nn.functional.log_softmax(logits_tensor, dim=-1)4142# Now use log_probs for CTC decoding43# Greedy decoding example:44labels = torch.argmax(log_probs, dim=-1)[0].numpy()# [188]4546# Collapse repeats and remove blanks47blank_id =307248decoded =[]49prev =None50for label in labels:51if label != blank_id and label != prev:52 decoded.append(label)53 prev = label
5455# Convert to text using vocabulary56import json
57withopen('vocab.json','r')as f:58 vocab = json.load(f)59tokens =[vocab[i]for i in decoded if i <len(vocab)]60text =''.join(tokens).replace('▁',' ').strip()61print(text)