Skip to content

Streaming ASR interfaces - #2377

Merged
mravanelli merged 63 commits into
speechbrain:developfrom
asumagic:streaming-interfaces
Feb 24, 2024
Merged

mravanelli merged 63 commits into
speechbrain:developfrom
asumagic:streaming-interfaces

Conversation

@asumagic

@asumagic asumagic commented Jan 31, 2024

Copy link
Copy Markdown
Collaborator

What does this PR do?

The goal of this PR is to provide high-level interfaces for streaming inference using the model introduced by #2140, and to provide some shared infrastructure for future streaming models.

I have hosted a LibriSpeech streaming ASR model at https://huggingface.co/sdelangen/speechbrain-asr-conformer-test/tree/main. There is currently no model card, but it will be possible to migrate this model under the speechbrain org once this is merged.

Introduced interfaces

The StreamingASR inference interface was introduced.

Features:

  • Streaming decode of long audio files (if ffmpeg is installed), see demos
  • Streaming decode of live streams (if ffmpeg is installed), see demos
  • Chunkwise decoding support (with an exposed lower-level transcribe_chunk/decode_chunk API)
  • Batching support for the lower-level interfaces which makes it suitable as a basis for transcription servers
  • Relatively model agnostic

Feature extraction

In the current conformer-transducer model, we have some feature extraction logic that stacks filter banks and 2 layers of down-sampling convolutions.
Ignoring normalization, we can consider feature extraction as a "filter" with a specific window size and stride.

Dynamic chunk training does not change how feature extraction is performed: it is applied over the entire batch at once, and is unaware of chunking.
This is mostly for simplicity, flexibility and training performance reasons.

Thus, when streaming, we have to be careful about how we chunk up our signal: for any given chunk, we must provide some past and future frames so that the transition across chunks remains as close to training as possible.

Streaming feature extraction abstraction

This logic is abstracted by speechbrain.lobes.features.StreamingFeatureWrapper as introduced by this PR. When provided the "filter properties" of our feature extractor (window size and stride), it is able to process a stream of audio chunk by chunk while abstracting padding and left context saving details¹.

Automatically determining filter properties

Those filter properties remain to be provided by the model developer. For this, we introduce speechbrain.utils.filter_analysis. It allows modelling simple filter properties, and provides utilities to "stack" them, i.e. model what the properties of filter2(filter1(x)) are.

In our case, we are essentially trying to model conv_layer_2(conv_layer_1(fbanks(waveform)))².

This allows us to determine the filter properties of our feature extraction easily and requires little to no adjustment when changing feature extraction hyperparameters.
This PR introduces a get_filter_propreties method for some modules, so all that remains is providing StreamingFeatureWrapper our feature extractor module list.

¹ This comes with a latency cost which depends on the window size and stride. However, that overhead is generally insignificant in practice as long as the window size and stride are kept reasonable.

² ignoring normalization. Not considering normalization specifically in our chunking process does not appear to have a significant effect on WER.


Example programs

Commandline tool to transcribe a file or a live stream

Decoding from a live stream using ffmpeg (BBC Radio 4): python3 asr.py http://as-hls-ww-live.akamaized.net/pool_904/live/ww/bbc_radio_fourfm/bbc_radio_fourfm.isml/bbc_radio_fourfm-audio%3d96000.norewind.m3u8 --model-source=sdelangen/speechbrain-asr-conformer-test --device=cpu -v

Decoding from a file: python3 asr.py some-english-speech.wav --model-source=sdelangen/speechbrain-asr-conformer-test --device=cpu -v

from argparse import ArgumentParser
import logging

parser = ArgumentParser()
parser.add_argument("audio_path")
parser.add_argument("--model-source", required=True)
parser.add_argument("--device", default="cpu")
parser.add_argument("--ip", default="127.0.0.1")
parser.add_argument("--port", default=9431)
parser.add_argument("--chunk-size", default=24, type=int)
parser.add_argument("--left-context-chunks", default=4, type=int)
parser.add_argument("--num-threads", default=None, type=int)
parser.add_argument("--verbose", "-v", default=False, action="store_true")
args = parser.parse_args()

if args.verbose:
    logging.getLogger().setLevel(logging.INFO)

logging.info("Loading libraries")

from speechbrain.inference.ASR import StreamingASR
from speechbrain.utils.dynamic_chunk_training import DynChunkTrainConfig
import torch

device = args.device

if args.num_threads is not None:
    torch.set_num_threads(args.num_threads)

logging.info(f"Loading model from \"{args.model_source}\" onto device {device}")

asr = StreamingASR.from_hparams(args.model_source, run_opts={"device": device})
config = DynChunkTrainConfig(args.chunk_size, args.left_context_chunks)

logging.info(f"Starting stream from URI \"{args.audio_path}\"")

for text_chunk in asr.transcribe_file_streaming(args.audio_path, config):
    print(text_chunk, flush=True, end="")

TODOs

Completed
  • Will need uploading the model and inference hparams to huggingface. (I probably should be made part of the SB team on HF? done)
  • Currently, some stuff around streaming state initialization is essentially hardcoded inside of the interfaces code, which should not need to care about MHA left context etc., which should be the responsibility of the hparams in some way.
  • Really need to double/triple-check the StreamingASR interface for flexibility as we might want to have several architectures be able to use it. -- I tried to make it so, but it's probably still biased towards the architecture I made it for. For what it's worth, it can be modified down the line and extended by any external users.
  • The sentencepiece space hack is very annoying and should probably be moved somewhere else, and the code should be made to support more than just sentencepiece -- the code was moved but it still only supports sentencepiece; but adding support for other tokenizers should otherwise not be too painful.
  • Audio loading should be done in a streaming fashion -- might wait for Load audio with PyAV #2354 probably will use torchaudio ffmpeg streaming stuff?
  • Figure out why the doc generation even fails... Probably a type annotation somewhere.
  • Final pass of checking all docstrings (and how they generate too)

Not sure how to handle things like rescoring that could change entire sentences, but let's consider it out-of-scope for StreamingASR.


Before submitting
  • Did you read the contributor guideline?
  • Did you make sure your PR does only one thing, instead of bundling different changes together?
  • Did you make sure to update the documentation with your changes? (if necessary)
  • Did you write any new necessary tests? (not for typos and docs)
  • Did you verify new and existing tests pass locally with your changes?
  • Did you list all the breaking changes introduced by this pull request?
  • Does your code adhere to project-specific code style and conventions?

PR review

Reviewer checklist
  • Is this pull request ready for review? (if not, please submit in draft mode)
  • Check that all items from Before submitting are resolved
  • Make sure the title is self-explanatory and the description concisely explains the PR
  • Add labels and milestones (and optionally projects) to the PR so it can be classified
  • Confirm that the changes adhere to compatibility requirements (e.g., Python version, platform)
  • Review the self-review checklist to ensure the code is ready for review

@asumagic asumagic added the enhancement New feature or request label Jan 31, 2024
@asumagic
asumagic marked this pull request as draft January 31, 2024 14:49
@mravanelli
mravanelli requested a review from TParcollet February 4, 2024 22:39
@mravanelli mravanelli added the work in progress Not ready for merge label Feb 4, 2024

@asumagic asumagic left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet done with replying/fixing according to all the review

Comment thread docs/conf.py
Comment thread speechbrain/lobes/models/convolution.py Outdated
Comment thread speechbrain/inference/ASR.py
Comment thread speechbrain/tokenizers/SentencePiece.py
Comment thread speechbrain/utils/filter_analysis.py Outdated
Comment thread speechbrain/lobes/models/convolution.py
Comment thread speechbrain/lobes/models/transformer/Conformer.py
Comment thread speechbrain/inference/ASR.py Outdated
Comment thread speechbrain/inference/ASR.py
@asumagic
asumagic requested a review from TParcollet February 22, 2024 11:06

@Adel-Moumen Adel-Moumen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @asumagic! I left some comments.

I'd like to better understand about the usability of this new streaming ASR interface. For instance, If I'd like to use a CTC based model. How can I run streaming inference with it ? What needs to be changed in order to make everything works ?

I also think that we will really need a google colab on that (or a dedicated page on our documentation) to explain how to use it and define your own streaming interface.

Comment thread speechbrain/inference/ASR.py Outdated
Comment thread speechbrain/inference/ASR.py Outdated
Comment thread speechbrain/inference/ASR.py
Comment thread speechbrain/inference/ASR.py
Comment thread speechbrain/inference/ASR.py Outdated
Comment thread speechbrain/lobes/features.py
Comment thread speechbrain/tokenizers/SentencePiece.py
Comment thread speechbrain/tokenizers/SentencePiece.py

@TParcollet TParcollet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asumagic can you run a WER validation as well to verify that this does not alter the expected WER?

Comment thread speechbrain/lobes/models/convolution.py Outdated
Comment thread speechbrain/lobes/models/transformer/Conformer.py
Comment thread speechbrain/tokenizers/SentencePiece.py
Comment thread speechbrain/inference/ASR.py Outdated

HPARAMS_NEEDED = [
"fea_streaming_extractor",
"Greedysearcher",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw, in a future we will have to refactor this. For instance, if we want to use a CTC-based model, which could be the case since we will have a competitive CTC Branch/Conformer recipe on LibriSpeech, then one could use a beam search or greedy searcher in an online fashion. Maybe changing the naming to decoding_function or smth like that would be better.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a decoding_function hparams and wrote some code for it, see the latest commit. Does that implementation seem reasonable?

Comment thread speechbrain/inference/ASR.py Outdated
@asumagic

asumagic commented Feb 23, 2024

Copy link
Copy Markdown
Collaborator Author

I tested WER on test-clean with custom code using the StreamingASR transcribe_file code path and ended up with the same score as advertised (when streaming with a chunk size of 24, left context of 8).

Jank test script for reference
device = "cuda"
model_path = "./librispeech-conformer"
test_csv = "/users/sdelangen/sb/recipes/LibriSpeech/ASR/transducer/results/conformer_transducer_large/3407/test-clean.csv"

import pandas as pd

test = pd.read_csv(test_csv)

import torch
torch.set_num_threads(1)

from concurrent.futures import ThreadPoolExecutor
from speechbrain.inference.ASR import StreamingASR
from speechbrain.utils.dynamic_chunk_training import DynChunkTrainConfig
from speechbrain.utils.metric_stats import ErrorRateStats
from speechbrain.utils.parallel import parallel_map

asr = StreamingASR.from_hparams(model_path, run_opts={"device": device})
config = DynChunkTrainConfig(24, 8)

wer_compute = ErrorRateStats()

def process_line(row):
    i, line = row

    path = line["wav"]

    truth = line["wrd"]
    pred = asr.transcribe_file(path, config, use_torchaudio_streaming=True)

    return pred, truth
    

for i, (pred, truth) in enumerate(parallel_map(process_line, test.iterrows(), executor=ThreadPoolExecutor(20), chunk_size=1)):
    wer_compute.append([i], [pred.split(" ")], [truth.split(" ")])
    print(wer_compute.summarize()["WER"])

# for text_chunk in asr.transcribe_file_streaming(args.audio_path, config):
#     print(text_chunk, flush=True, end="")

Still fixing some stuff as suggested then I will re-request a review once done.

@Adel-Moumen Adel-Moumen left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that I have nothing much to say now. You answered all of my concerns. Could you please add the HF interface in private on the speechbrain HF hub ?

My only remark will be on the documentation part, I think we really need in the future (incoming weeks) to write good and high quality tutorials/etc to explain exactly what is needed to be changed if one wants to reuse the streamingASR interface.

Other than that, let's wait from @TParcollet POV on that :) (bug congrats this is a big thing for SB, a lot of people (industrials/researchers) were asking for this feature!).

@asumagic

Copy link
Copy Markdown
Collaborator Author

I would say that I have nothing much to say now. You answered all of my concerns. Could you please add the HF interface in private on the speechbrain HF hub ?

I just moved the model to speechbrain/asr-streaming-conformer-librispeech and made it private. Still need to add a model card.

@asumagic

Copy link
Copy Markdown
Collaborator Author

As for the docs, I've started writing some stuff but it will take some time. I already had some model details explained from the original PR that I will reuse.

I am also starting to write a simple gradio demo, which I believe is supposed to allow streaming ASR. Theoretically, it would be possible to use this as an HF space.

@mravanelli

Copy link
Copy Markdown
Collaborator

Thank you @asumagic, this is a great contribution. I ran recipe tests and everything seems fine. Thank you @Adel-Moumen and @TParcollet for the review.

@mravanelli
mravanelli self-requested a review February 24, 2024 17:17
@mravanelli
mravanelli merged commit f9f21c6 into speechbrain:develop Feb 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request ready to review Waiting on reviewer to provide feedback

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants