Belarusian FastConformer RNN-T - Core ML (fp16)

fp16 Core ML (mlprogram) export of the RNN-T (Transducer) path of nvidia/stt_be_fastconformer_hybrid_large_pc

  • a 115M-parameter FastConformer Transducer-CTC model for Belarusian speech recognition that emits punctuated, capitalized text. Runs fully on-device on Apple Silicon (CPU / GPU / Neural Engine), no Python and no NeMo at inference.

The model is split into three packages: the encoder runs once per 30 s audio window, the decoder and joint run in a host-side greedy loop. The log-mel front-end stays outside Core ML and is reproduced from the checkpoint's own window and filterbank buffers (mel_buffers.npz).

Files

file size role
BeFastConformerEncoder.mlpackage 212 MB FastConformer encoder, fixed 30 s window, 8x downsampling
BeFastConformerDecoderStep.mlpackage 7.5 MB RNN-T prediction net, one LSTM step with explicit state I/O
BeFastConformerJointStep.mlpackage 2.7 MB joint network, raw logits (log_softmax stripped - argmax-invariant)
mel_buffers.npz 84 KB Hann window + 80-bin mel filterbank, taken verbatim from the checkpoint
tokenizer.model 251 KB SentencePiece model (1024 unigram pieces, byte fallback)
tokens.json 14 KB the 1024 vocabulary pieces ( = word boundary)
model_config.yaml - full upstream NeMo config extracted from the checkpoint
model_info.json - dims, blank id, mel parameters
convert_info.json - window length in mel frames, conversion settings
example_infer.py - runnable, self-contained reference inference

The repo is self-contained: everything needed for on-device inference is here.

I/O contracts

Encoder: features float32 [1, 80, 2999] (log-mel, 30 s window, zero-padded right), length int32 [1] (true mel frames, so the padded tail is masked) -> encoded [1, 512, 375]. encoded valid length is computed host-side as L = (L-1)//2 + 1 applied three times to the mel length (8x downsampling); fp16 cannot represent the length exactly, so it is not returned by the graph.

Decoder step: token int32 [1, 1], h_in/c_in float32 [1, 1, 640] -> dec_out [1, 640], h_out, c_out. Start state: token = 1024 (blank), zero h/c - the blank embedding row is zeros (blank_as_pad), reproducing the reference fresh start exactly.

Joint step: enc_t [1, 512] (one encoder frame), dec_t [1, 640] -> logits [1, 1025]. blank_id = 1024; the 1024 non-blank ids index tokens.json.

Mel front-end (must match exactly): 16 kHz mono, preemphasis 0.97, n_fft = 512, win_length = 400, hop_length = 160, Hann window, center = True (reflect pad), 80 mel bins, power spectrum, log(x + 2^-24), then per-feature mean/variance normalization over the valid frames. The window and filterbank are embedded in mel_buffers.npz; the full upstream config is in model_config.yaml.

Usage

pip install coremltools torch numpy sentencepiece   # + ffmpeg on PATH
python example_infer.py audio.wav

example_infer.py loads the three packages, computes the mel from the embedded buffers, runs the encoder once per <=30 s window and a greedy RNN-T loop over the decoder + joint, and detokenizes with SentencePiece. No NeMo is required.

Longer audio: the example splits into <=30 s windows at low-energy boundaries and resets h/c/last per window. The emission frame index t maps to seconds as t * 8 * 160 / 16000 = t * 0.08 (8x downsampling, 10 ms hop) plus the window offset.

Conversion fidelity

Verified against the NeMo PyTorch reference (fp32 CPU) on three Belarusian FLEURS clips, decoding the same windows. The NeMo-free standalone mel is itself token-exact against NeMo's AudioToMelSpectrogramPreprocessor.

compute units encoder mean abs diff transcript
CPU_AND_GPU 0.0004 token-exact (3/3 clips)

The ANE runs the encoder in higher-noise fp16 accumulation; as with other Conformer exports, a borderline low-confidence emission can flip to blank on the ANE. For transcripts that must match the reference exactly, use CPU_AND_GPU.

Latency

On Apple Silicon (CPU_AND_GPU, single clip) the full pipeline runs at RTF ~0.006-0.01 (roughly 100-160x faster than real time); the greedy loop is not the bottleneck.

Base model quality

From the base model card: WER 2.72% without punctuation/capitalization and 3.87% with, on Common Voice 12 Belarusian test (Transducer decoder, greedy). These are clean read-speech numbers; expect higher error on noisy or conversational audio.

This model is Belarusian-monolingual (Belarusian-only tokenizer, trained on Common Voice Belarusian). It will degrade on Russian insertions and on mixed Belarusian-Russian speech; it is not a multilingual model.

Conversion notes

torch 2.10.0 -> torch.jit.trace(strict=False) -> coremltools 9.0 (mlprogram, FLOAT16, macOS15). Three things needed care: the encoder is warmed up with one forward before tracing so the relative positional-encoding cache is built; coremltools 9.0 crashes on the aten::Int pattern under numpy 2 (fixed with a scoped register_torch_op override on int); and dither must be 0 at inference or the random spectrogram perturbation flips borderline emissions.

License and attribution

Licensed under CC-BY-4.0, inherited from the base model nvidia/stt_be_fastconformer_hybrid_large_pc (© NVIDIA, CC-BY-4.0). Changes made: export of the RNN-T path to fp16 Core ML (mlprogram), split into encoder / decoder-step / joint-step packages, with the log-mel front-end reproduced from the checkpoint buffers.

Architecture: FastConformer. Toolkit: NVIDIA NeMo.

Core ML conversion © 2026 Sergey Makarov (smkrv).

Downloads last month
13
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for smkrv/stt-be-fastconformer-rnnt-coreml

Quantized
(2)
this model

Paper for smkrv/stt-be-fastconformer-rnnt-coreml