onnx-asr

repository·main·Indexed 43 days ago

https://github.com/istupakov/onnx-asr

A lightweight, fast, and pure Python package for Automatic Speech Recognition (ASR) using ONNX models. It supports architectures including NVIDIA NeMo (Conformer, FastConformer, Parakeet, Canary), GigaChat (GigaAM), Kaldi Icefall (Zipformer), T-Tech (T-one), HuggingFace (Wav2Vec2), and OpenAI Whisper. Designed to run on devices from edge/IoT to servers without requiring PyTorch or FFmpeg.

Tokens
18.3K
Snippets
62
Records
83
Agent score
56%

What's inside onnx-asr

  1. Supported Model Architectures in onnx-asr

    main

    onnx-asr provides the necessary preprocessing (e.g., Log-mel spectrograms) and decoding (e.g., Greedy search) implementations for the following architectures when provided in ONNX format:

    • NVIDIA NeMo: Conformer, FastConformer, Parakeet, and Canary (supporting CTC, RNN-T, TDT, and Transformer decoders).
    • GigaChat: GigaAM v2, v3, and Multilingual (supporting CTC and RNN-T decoders, including E2E versions).
    • Kaldi Icefall: Zipformer (with stateless RNN-T decoder), including Alpha Cephei Vosk 0.52+.
    • T-Tech: T-one (CTC decoder; streaming not yet supported).
    • HuggingFace: Wav2Vec2 (CTC decoder).
    • OpenAI: Whisper.
  2. Understand the Inverse Real-Time Factor (RTFx) metric

    main

    The project uses Inverse Real-Time Factor (RTFx) to measure ASR processing speed relative to audio duration.

    $$\text{RTFx} = \frac{\text{Audio Duration}}{\text{Processing Time}}$$

    • RTFx > 1: Faster than real-time (e.g., 10 means 10 seconds of audio are processed in 1 second).
    • RTFx = 1: Real-time processing.
    • RTFx < 1: Slower than real-time.
  3. Load and use an ONNX ASR model

    main

    Use onnx_asr.load_model() to load a model from Hugging Face or a local directory. Once loaded, use the .recognize() method to transcribe audio files.

    Supported WAV formats: PCM_U8, PCM_16, PCM_24, and PCM_32. For other formats, convert them first or use a library like soundfile to load them into a NumPy array.

    import onnx_asr
    
    # Load from Hugging Face
    model = onnx_asr.load_model("nemo-parakeet-tdt-0.6b-v3")
    print(model.recognize("test.wav"))
  4. Install onnx-asr

    main

    Install the onnx-asr package using pip. You can include optional extras like cpu for CPU-only environments or hub to enable loading models directly from the Hugging Face Hub.

    pip install onnx-asr[cpu,hub]
    pip install onnx-asr[cpu,hub]
  5. Quick Start: Load a model and recognize speech

    main

    To perform Automatic Speech Recognition (ASR), use onnx_asr.load_model() to fetch a model (from Hugging Face or a local path) and the .recognize() method on the returned model object to process an audio file.

    Note: Most models have a maximum audio length of 20–30 seconds. For longer audio files, you should use Voice Activity Detection (VAD).

    import onnx_asr
    
    # Load the Parakeet TDT v3 model from Hugging Face
    model = onnx_asr.load_model("nemo-parakeet-tdt-0.6b-v3")
    
    # Recognize speech and print the result
    result = model.recognize("test.wav")
    print(result)
    import onnx_asr
    
    # Load the Parakeet TDT v3 model from Hugging Face (may take a few minutes)
    model = onnx_asr.load_model("nemo-parakeet-tdt-0.6b-v3")
    
    # Recognize speech and print the result
    result = model.recognize("test.wav")
    print(result)
  6. Run models with TensorRT and FP16

    main

    To use the TensorRT execution provider with FP16 precision, pass a providers list to load_model(). This requires tensorrt-cu12-libs (or equivalent) to be installed.

    import onnx_asr
    import tensorrt_libs
    
    providers = [
        (
            "TensorrtExecutionProvider",
            {
                "trt_max_workspace_size": 6 * 1024**3,
                "trt_fp16_enable": True,
            },
        )
    ]
    model = onnx_asr.load_model("nemo-parakeet-tdt-0.6b-v3", providers=providers)
    print(model.recognize("test.wav"))
  7. Try the ONNX ASR demo on Hugging Face Spaces

    main

    You can interact with the ONNX ASR model through a web interface hosted on Hugging Face Spaces using Gradio. This allows you to test speech recognition capabilities without local installation.

    https://huggingface.co/spaces/istupakov/onnx-asr
  8. Use Voice Activity Detection (VAD)

    main

    You can improve transcription by using a VAD model to segment audio. First, load a VAD model with load_vad(), then attach it to your ASR model using .with_vad(vad).

    import onnx_asr
    
    vad = onnx_asr.load_vad("silero")
    model = onnx_asr.load_model("nemo-parakeet-tdt-0.6b-v3").with_vad(vad)
    for res in model.recognize("test.wav"):
        print(res)
  9. Convert HuggingFace Wav2Vec2 CTC to ONNX

    main

    When converting Wav2Vec2 CTC models, it is recommended to bake feature normalization (do_normalize) directly into the ONNX graph. This allows the model to accept raw waveforms without requiring a separate feature-extractor asset.

    Key Conversion Details:

    • Vocabulary Mapping: The pad token must be mapped to <blk> (the CTC blank token). The word-delimiter token (|) should be mapped to a literal space ( ).
    • Subsampling Factor: This must be included in config.json. It is calculated as the product of the strides in the feature-encoder convolutional layers (e.g., 320 for standard XLS-R stacks).
    • Input/Output Names: The graph should use input_values and input_lengths as inputs, and logprobs as the output.
    import json
    from pathlib import Path
    import torch
    from transformers import Wav2Vec2ForCTC, Wav2Vec2Processor
    
    hf_model_id = "proxectonos/Nos_ASR-wav2vec2-xls-r-300m-gl"
    onnx_dir = Path("wav2vec2-onnx")
    onnx_dir.mkdir(exist_ok=True)
    
    model = Wav2Vec2ForCTC.from_pretrained(hf_model_id).eval()
    processor = Wav2Vec2Processor.from_pretrained(hf_model_id)
    do_normalize = bool(processor.feature_extractor.do_normalize)
    
    class NormalizedWav2Vec2(torch.nn.Module):
        def forward(self, input_values, input_lengths):
            if do_normalize:
                mask = torch.arange(input_values.shape[1])[None, :] < input_lengths[:, None]
                count = input_lengths.to(input_values.dtype).clamp(min=1)[:, None]
                mean = (input_values * mask).sum(dim=1, keepdim=True) / count
                var = (((input_values - mean) * mask) ** 2).sum(dim=1, keepdim=True) / count
                input_values = torch.where(mask, (input_values - mean) / torch.sqrt(var + 1e-5), input_values)
            logits = model(input_values).logits
            return torch.nn.functional.log_softmax(logits, dim=-1)
    
    torch.onnx.export(
        NormalizedWav2Vec2(),
        (torch.randn(1, 16000), torch.tensor([16000], dtype=torch.int64)),
        str(onnx_dir / "model.onnx"),
        input_names=["input_values", "input_lengths"],
        output_names=["logprobs"],
        dynamic_axes={
            "input_values": {0: "batch", 1: "time"},
            "input_lengths": {0: "batch"},
            "logprobs": {0: "batch", 1: "frames"},
        },
        opset_version=18,
    )
    
    vocab = processor.tokenizer.get_vocab()
    pad_token = processor.tokenizer.pad_token
    word_delimiter = processor.tokenizer.word_delimiter_token
    
    with (onnx_dir / "vocab.txt").open("wt") as f:
        for token, idx in sorted(vocab.items(), key=lambda kv: kv[1]):
            token = "<blk>" if token == pad_token else " " if token == word_delimiter else token
            f.write(f"{token} {idx}\n")
    
    subsampling_factor = 1
    for layer in model.wav2vec2.feature_extractor.conv_layers:
        stride = layer.conv.stride
        subsampling_factor *= stride[0] if isinstance(stride, tuple) else stride
    
    with (onnx_dir / "config.json").open("wt") as f:
        json.dump({"model_type": "wav2vec2-ctc", "subsampling_factor": subsampling_factor}, f, indent=2)