CoolFace
Modelpublic

danielbodart/nemotron-speech-600m-coreml

sourceHugging Facecc-by-4.0updated 6mo agoView on Hugging Face
1likes6downloads
Model Card

Nemotron Speech 600M — CoreML (Streaming)

Native CoreML conversion of nvidia/nemotron-speech-streaming-en-0.6b, a 600M-parameter streaming ASR model using FastConformer encoder + RNNT decoder. Optimized for Apple Neural Engine (ANE) on Apple Silicon.

Converted directly from the original NeMo checkpoint via coremltools, with 3-level numerical validation against the PyTorch reference.

Sibling project: [danielbodart/nemotron-speech-600m-onnx](https://huggingface.co/danielbodart/nemotron-speech-600m-onnx) — ONNX Runtime version for Linux (CUDA + CPU).

Performance

MetricValue
Encoder ANE utilization93% (1486/1597 ops on Neural Engine)
Inference speed~15.5x realtime on Apple Silicon
Encoder CPU ops7% (softmax, attention masking — see config.json for breakdown)
Decoder100% CPU (CPU_ONLY compute units)

No manual ANE optimizations applied — coremltools compiler routes ops automatically.

Available Precisions

VariantEncoderDecoderTotalCompute UnitsNotes
fp16/1.1 GB17 MB~1.1 GBEncoder: CPU+ANE, Decoder: CPURecommended

Future: INT8 quantization via coremltools (can halve encoder size on ANE).

Model Architecture

Two CoreML models (decoder and joint network are fused into one):

ModelInputOutputCompute
Encodermel [1, 128, 65] + caches (FP32 in, FP16 out)encoded [1, 1024, 7] + cachesCPUANDNE
Fused Decoder+Jointenc_frame [1, 1024, 1] + token [1, 1] + LSTM h,c (FP32 in, FP16 out)logits [1, 1025] + LSTM h,cCPU_ONLY

Mel spectrogram preprocessing runs on the host (not in CoreML).

Important: ANE stride padding

CoreML output MLMultiArrays may have non-contiguous strides due to ANE alignment padding. For example, the encoder output [1, 1024, 7] may have physical strides [32768, 32, 1] instead of C-contiguous [7168, 7, 1]. Callers must use stride-aware copy, not flat memcpy.

Runtime Configuration

All parameters needed to run the model are documented in `config.json`, including I/O specs, cache shapes, the streaming protocol, and ANE profiling results.

Audio Preprocessing

ParameterValue
Sample rate16000 Hz
Sample formatS16_LE (16-bit signed little-endian)
Pre-emphasis0.97
FFT size512
Hop length160 samples (10ms)
Window length400 samples (25ms)
Window typeHann
Mel bands128
Mel normSlaney
Mel layoutBand-major [n_mels, n_frames] (not frame-major)

Encoder Streaming

ParameterValue
Chunk size56 mel frames (560ms audio)
Pre-encode cache9 mel frames (prepended from previous chunk)
Total input frames65 per chunk (56 + 9)
Layers24
Dimension1024
cache_last_channel shape[1, 24, 70, 1024] FP32 in, FP16 out (init zeros)
cache_last_time shape[1, 24, 1024, 8] FP32 in, FP16 out (init zeros)
cache_last_channel_len[1] int32 (init zero)

Feed cache outputs back as next chunk's cache inputs. Convert FP16 outputs to FP32 before feeding back (model expects FP32 inputs).

RNNT Decoder

ParameterValue
Blank token ID1024
Vocab size1025 (1024 tokens + blank)
Max symbols per frame10
Prediction layers2 (LSTM)
Prediction hidden640
input_states_1/2 shape[2, 1, 640] FP32 in, FP16 out (init zeros)

For each encoder output frame: feed single frame [1, 1024, 1] to decoder, argmax logits over 1025 vocab, if not blank emit token and loop (up to 10), if blank move to next frame. Feed decoder states back for next symbol/frame.

Files

config.json                       # Machine-readable runtime parameters + ANE profile

fp16/
├── encoder.mlmodelc/             # Pre-compiled encoder (load directly with MLModel)
│   ├── model.mil
│   ├── coremldata.bin
│   └── weights/weight.bin
├── encoder.mlpackage/            # Source encoder (for runtime compilation fallback)
│   └── Data/com.apple.CoreML/
│       ├── model.mlmodel
│       └── weights/weight.bin
├── decoder.mlmodelc/             # Pre-compiled fused decoder+joint
├── decoder.mlpackage/            # Source fused decoder+joint
└── metadata.json                 # Cache shapes, vocab size, model parameters

Usage

Download:

bash
# Download compiled models (recommended)
hf download danielbodart/nemotron-speech-600m-coreml fp16/ config.json --local-dir ./model

# Download only .mlmodelc (skip .mlpackage to save space)
hf download danielbodart/nemotron-speech-600m-coreml fp16/encoder.mlmodelc/ fp16/decoder.mlmodelc/ fp16/metadata.json config.json --local-dir ./model

Load with CoreML (Swift):

swift
import CoreML

let config = MLModelConfiguration()
config.computeUnits = .cpuAndNeuralEngine

let encoder = try MLModel(contentsOf: URL(fileURLWithPath: "model/fp16/encoder.mlmodelc"),
                          configuration: config)

let decConfig = MLModelConfiguration()
decConfig.computeUnits = .cpuOnly
let decoder = try MLModel(contentsOf: URL(fileURLWithPath: "model/fp16/decoder.mlmodelc"),
                          configuration: decConfig)

Load with CoreML (Python/coremltools):

python
import coremltools as ct

encoder = ct.models.MLModel("model/fp16/encoder.mlpackage")
decoder = ct.models.MLModel("model/fp16/decoder.mlpackage")

Conversion Reproducibility

All conversion and validation scripts are in the companion GitHub repo: [danielbodart/nemotron-speech-600m-coreml](https://github.com/danielbodart/nemotron-speech-600m-coreml)

  • convert.py — NeMo → CoreML conversion (wrap, trace, convert, compile)
  • validate.py — 3-level validation (wrapper equiv, CoreML vs PyTorch, end-to-end transcript)
  • wrappers.py — PyTorch wrappers (EncoderWrapper, FusedDecoderJointWrapper)

Requires macOS with Apple Silicon, Python 3.10, coremltools 9.0b1.

Related

  • [danielbodart/nemotron-speech-600m-onnx](https://huggingface.co/danielbodart/nemotron-speech-600m-onnx) — ONNX Runtime version for Linux (CUDA + CPU, FP16/INT8)
  • [nvidia/nemotron-speech-streaming-en-0.6b](https://huggingface.co/nvidia/nemotron-speech-streaming-en-0.6b) — Original NeMo model

License

The original model is licensed under CC-BY-4.0 by NVIDIA.