From 2fc951c62b591f9b3427d0ecbae6d38811a31623 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 28 Mar 2023 23:04:35 -0500 Subject: [PATCH 1/4] Fix logo --- etc/logo.png | Bin 8537 -> 8461 bytes etc/logo.svg | 16 ++++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/etc/logo.png b/etc/logo.png index fbb8705ad7639a289c8b042904adedb605dfd904..a3ea7d25d64ed775d8a37b9d07d2a435649205ce 100644 GIT binary patch literal 8461 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 literal 8537 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 CLVfc9 diff --git a/etc/logo.svg b/etc/logo.svg index 1943a5e..99c73b5 100644 --- a/etc/logo.svg +++ b/etc/logo.svg @@ -26,15 +26,15 @@ borderopacity="1.0" inkscape:pageopacity="1" inkscape:pageshadow="2" - inkscape:zoom="1.8469919" - inkscape:cx="164.97755" - inkscape:cy="48.418276" + inkscape:zoom="1.421213" + inkscape:cx="-23.774381" + inkscape:cy="33.944028" inkscape:document-units="mm" inkscape:current-layer="layer1" inkscape:document-rotation="0" showgrid="false" - inkscape:window-width="1920" - inkscape:window-height="1012" + inkscape:window-width="1280" + inkscape:window-height="653" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" @@ -50,7 +50,7 @@ image/svg+xml - + @@ -144,8 +144,8 @@ + d="m 19.523765,185.51136 11.807216,-7.07896 0.647873,1.12215 c -3.765389,1.73827 -8.398223,4.7151 -11.807217,7.07894 l -0.647874,-1.12213 z" + sodipodi:nodetypes="sccccs" /> From 41d129a1f7b9b4d18810ac41aabd14aab4f5bb14 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 28 Mar 2023 23:08:21 -0500 Subject: [PATCH 2/4] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63e7ef5..e440399 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Piper logo](etc/logo.png) -A fast, local neural text to speech system that is meant to sound good and run reasonably fast on the Raspberry Pi 4. +A fast, local neural text to speech system that sound great and is optimized for the Raspberry Pi 4. ``` sh echo 'Welcome to the world of speech synthesis!' | \ From 8ac6cc3691e80ee021f67b066882cfd053abda36 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 28 Mar 2023 23:42:34 -0500 Subject: [PATCH 3/4] Typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e440399..ab03d2b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![Piper logo](etc/logo.png) -A fast, local neural text to speech system that sound great and is optimized for the Raspberry Pi 4. +A fast, local neural text to speech system that sounds great and is optimized for the Raspberry Pi 4. ``` sh echo 'Welcome to the world of speech synthesis!' | \ From 1eed98ecd9d2e450020c40ed469432483790d265 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Sun, 9 Apr 2023 20:15:05 -0500 Subject: [PATCH 4/4] Add benchmark --- src/benchmark/benchmark_generator.py | 78 ++++++++++++++++++++++++ src/benchmark/benchmark_onnx.py | 88 ++++++++++++++++++++++++++++ src/benchmark/requirements.txt | 2 + 3 files changed, 168 insertions(+) create mode 100644 src/benchmark/benchmark_generator.py create mode 100644 src/benchmark/benchmark_onnx.py create mode 100644 src/benchmark/requirements.txt diff --git a/src/benchmark/benchmark_generator.py b/src/benchmark/benchmark_generator.py new file mode 100644 index 0000000..bfb70be --- /dev/null +++ b/src/benchmark/benchmark_generator.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +import argparse +import json +import time +import sys + +import torch + +_SPEAKER_ID = 0 + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("-m", "--model", required=True, help="Path to Onnx model file") + parser.add_argument("-c", "--config", help="Path to model config file") + args = parser.parse_args() + + if not args.config: + args.config = f"{args.model}.json" + + with open(args.config, "r", encoding="utf-8") as config_file: + config = json.load(config_file) + + sample_rate = config["audio"]["sample_rate"] + utterances = [json.loads(line) for line in sys.stdin] + + start_time = time.monotonic_ns() + model = torch.load(args.model) + end_time = time.monotonic_ns() + + model.eval() + + load_sec = (end_time - start_time) / 1e9 + synthesize_rtf = [] + for utterance in utterances: + phoneme_ids = utterance["phoneme_ids"] + speaker_id = utterance.get("speaker_id") + synthesize_rtf.append( + synthesize( + model, + phoneme_ids, + speaker_id, + sample_rate, + ) + ) + + json.dump( + {"load_sec": load_sec, "synthesize_rtf": synthesize_rtf}, + sys.stdout, + ) + + +def synthesize(model, phoneme_ids, speaker_id, sample_rate) -> float: + text = torch.LongTensor(phoneme_ids).unsqueeze(0) + text_lengths = torch.LongTensor([len(phoneme_ids)]) + sid = torch.LongTensor([speaker_id]) if speaker_id is not None else None + + start_time = time.monotonic_ns() + audio = ( + model( + text, + text_lengths, + sid, + )[0] + .detach() + .numpy() + .squeeze() + ) + end_time = time.monotonic_ns() + + audio_sec = (len(audio) / 2) / sample_rate + infer_sec = (end_time - start_time) / 1e9 + + return infer_sec / audio_sec + + +if __name__ == "__main__": + main() diff --git a/src/benchmark/benchmark_onnx.py b/src/benchmark/benchmark_onnx.py new file mode 100644 index 0000000..22426cd --- /dev/null +++ b/src/benchmark/benchmark_onnx.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +import argparse +import json +import time +import sys + +import onnxruntime +import numpy as np + +_NOISE_SCALE = 0.667 +_LENGTH_SCALE = 1.0 +_NOISE_W = 0.8 +_SPEAKER_ID = 0 + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("-m", "--model", required=True, help="Path to Onnx model file") + parser.add_argument("-c", "--config", help="Path to model config file") + args = parser.parse_args() + + if not args.config: + args.config = f"{args.model}.json" + + with open(args.config, "r", encoding="utf-8") as config_file: + config = json.load(config_file) + + sample_rate = config["audio"]["sample_rate"] + utterances = [json.loads(line) for line in sys.stdin] + + start_time = time.monotonic_ns() + session = onnxruntime.InferenceSession(args.model) + end_time = time.monotonic_ns() + + load_sec = (end_time - start_time) / 1e9 + synthesize_rtf = [] + for utterance in utterances: + phoneme_ids = utterance["phoneme_ids"] + speaker_id = utterance.get("speaker_id") + synthesize_rtf.append( + synthesize( + session, + phoneme_ids, + speaker_id, + sample_rate, + ) + ) + + json.dump( + {"load_sec": load_sec, "synthesize_rtf": synthesize_rtf}, + sys.stdout, + ) + + +def synthesize(session, phoneme_ids, speaker_id, sample_rate) -> float: + phoneme_ids_array = np.expand_dims(np.array(phoneme_ids, dtype=np.int64), 0) + phoneme_ids_lengths = np.array([phoneme_ids_array.shape[1]], dtype=np.int64) + scales = np.array( + [_NOISE_SCALE, _LENGTH_SCALE, _NOISE_W], + dtype=np.float32, + ) + + sid = None + + if speaker_id is not None: + sid = np.array([speaker_id], dtype=np.int64) + + # Synthesize through Onnx + start_time = time.monotonic_ns() + audio = session.run( + None, + { + "input": phoneme_ids_array, + "input_lengths": phoneme_ids_lengths, + "scales": scales, + "sid": sid, + }, + )[0].squeeze() + end_time = time.monotonic_ns() + + audio_sec = (len(audio) / 2) / sample_rate + infer_sec = (end_time - start_time) / 1e9 + + return infer_sec / audio_sec + + +if __name__ == "__main__": + main() diff --git a/src/benchmark/requirements.txt b/src/benchmark/requirements.txt new file mode 100644 index 0000000..26f8d83 --- /dev/null +++ b/src/benchmark/requirements.txt @@ -0,0 +1,2 @@ +onnxruntime~=1.11.0 +torch~=1.11.0