Set fix input frame size to simplify code, added benchmark script to estimate inference efficiency

This commit is contained in:
dscripka 2022-06-21 08:32:32 -04:00
parent 8d89349179
commit a8949f3b48
4 changed files with 101 additions and 15 deletions

50
benchmark/benchmark.py Normal file
View file

@ -0,0 +1,50 @@
# Copyright 2022 David Scripka. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Imports
import openwakeword
import numpy as np
from pathlib import Path
from collections import defaultdict
# Define benchmark to assess inference speed of models at different audio chunk sizes
# Smaller chunk sizes may increase model performance, at the cost of inference efficiency
def run_benchmark():
# Load models
model_paths = [str(i) for i in Path("openwakeword/resources/models").glob("*.onnx") \
if "embedding" not in str(i) and "melspectrogram" not in str(i)]
M = openwakeword.Model(
wakeword_model_paths=model_paths,
input_sizes=[16]
)
# Create random data to use for benchmarking
clip = np.random.random(16000*10).astype(np.float32)
# Run the benchmark
step_size = 1280
preprocessing_times = []
model_times = defaultdict(list)
for i in range(0, clip.shape[0]-step_size, step_size):
pred, timing_dict = M.predict(clip[i:i+step_size], timing=True)
preprocessing_times.append(timing_dict["preprocessor"])
for mdl_name in M.models.keys():
model_times[mdl_name].append(timing_dict["models"][mdl_name])
print(f"Average of {np.mean(preprocessing_times)} for audio preprocessing with a frame size of {step_size/16000} seconds")
for mdl_name in M.models.keys():
print(f"Average of {np.mean(model_times[mdl_name])} for model \"{mdl_name}\"", "\n\n")
if __name__ == "__main__":
run_benchmark()

View file

@ -18,6 +18,7 @@ import numpy as np
import os
from openwakeword.utils import AudioFeatures
from typing import List
import time
class Model():
def __init__(self, wakeword_model_paths: List[str], input_sizes: List[int], **kwargs):
@ -36,16 +37,40 @@ class Model():
# Create AudioFeatures object
self.preprocessor = AudioFeatures(**kwargs)
def predict(self, x):
def predict(self, x, timing=False):
"""Predict with all of the wakeword models on the input audio frames"""
# Get audio features
if timing:
timing_dict = {}
timing_dict["models"] = {}
feature_start = time.time()
self.preprocessor(x)
if timing:
feature_end = time.time()
timing_dict["preprocessor"] = feature_end - feature_start
# Get predictions from model(s)
predictions = {}
for mdl in self.models.keys():
input_name = self.models[mdl].get_inputs()[0].name
if timing:
model_start = time.time()
predictions[mdl] = self.models[mdl].run(
None,
{input_name: self.preprocessor.get_features(self.model_inputs[mdl])}
)[0][0][0]
return predictions
if timing:
model_end = time.time()
timing_dict["models"][mdl] = model_end - model_start
if timing:
return predictions, timing_dict
else:
return predictions

View file

@ -12,17 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# Imports
import os
import onnxruntime as ort
import numpy as np
import pathlib
from collections import deque
# Base class for computing audio features using Google's speech_embedding model (https://tfhub.dev/google/speech_embedding/1)
class AudioFeatures():
def __init__(self,
melspec_onnx_model_path=os.path.join("openwakeword", "resources", "models", "melspectrogram.onnx"),
embedding_onnx_model_path=os.path.join("openwakeword", "resources", "models", "embedding_model.onnx"),
melspec_onnx_model_path=os.path.join(pathlib.Path(__file__).parent.resolve(), "resources", "models", "melspectrogram.onnx"),
embedding_onnx_model_path=os.path.join(pathlib.Path(__file__).parent.resolve(), "resources", "models", "embedding_model.onnx"),
sr=16000,
ncpu=1
):
@ -80,22 +82,17 @@ class AudioFeatures():
self.melspectrogram_buffer = self.melspectrogram_buffer[-self.melspectrogram_max_len:, :]
def _streaming_features(self, x):
assert len(x) == 1280*3
if len(x) != 1280:
raise ValueError(f"You must provide input samples in frames of 1280 samples @ 1600khz. Received a frame of {len(x)} samples.")
self._streaming_melspectrogram(x)
if self.melspectrogram_buffer.shape[0] < 76:
pass
for i in range(-3,0,1):
start = -76+8*i
end = start + 76
x = self.melspectrogram_buffer[start:end].astype(np.float32)[None,:,:,None]
if x.shape[1] != 76:
continue
x = self.melspectrogram_buffer[-76:].astype(np.float32)[None,:,:,None]
if x.shape[1] == 76:
self.feature_buffer = np.vstack((self.feature_buffer, self.embedding_model.run(None, {'input_1': x})[0].squeeze()))
if self.feature_buffer.shape[0] > self.feature_buffer_max_len:
self.feature_buffer = self.feature_buffer[-self.feature_buffer_max_len:, :]
def get_features(self, x, n_feature_frames=16):
def get_features(self, n_feature_frames=16):
return self.feature_buffer[-n_feature_frames:, :][None,].astype(np.float32)
def __call__(self, x):

View file

@ -11,6 +11,20 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Copyright 2022 David Scripka. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Imports
import openwakeword
@ -30,7 +44,7 @@ class TestModels:
input_sizes=[16]
)
step_size = 1280*3
step_size = 1280
predictions = []
for i in range(0, hey_jane_clip.shape[0]-step_size, step_size):
predictions.append(model.predict(hey_jane_clip[i:i+step_size])["hey_jane"])