Skip to content

Commit

Permalink
change: Rust APIの脱Tokioと、voicevox_core::{tokiononblocking} (#831)
Browse files Browse the repository at this point in the history
Tokioに依存したプログラムは、async-stdやsmolで使うことはできない。一方、
現在のVOICEVOX CORE Rust APIのTokio依存部分は
`tokio::task::spawn_blocking`のみである。そのため
`tokio::task::spawn_blocking`を、同等の機能を持つblockingクレートのも
のに置き換えることでRust APIの"脱Tokio"を行う。

https://docs.rs/crate/blocking

またこの"脱Tokio"に伴い、Rust APIの`voicevox_core::tokio`を
`voicevox_core::nonblocking`にリネームする。

Python APIではpyo3-asyncioが現在Tokio版かasync-std版しかない状態なので、
Tokioに依存した状態のままにしてある。またtest_utilやdownloaderではこれま
で通りreqwestに依存する。

また将来`Synthesizer`や`OpenJtalk`なども`trait Async`をベースにした設計
にすることを考えているが、本PRではTODOコメントを残すのみにしてある。

#830 (comment)
  • Loading branch information
qryxip authored Sep 13, 2024
1 parent e07c795 commit b8118b9
Show file tree
Hide file tree
Showing 19 changed files with 257 additions and 141 deletions.
22 changes: 22 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ async-fs = "2.1.2"
async_zip = "=0.0.16"
bindgen = "0.69.4"
binstall-tar = "0.4.39"
blocking = "1.6.1"
bytes = "1.1.0"
camino = "1.1.6"
cargo_metadata = "0.18.1"
Expand Down Expand Up @@ -57,6 +58,7 @@ octocrab = { version = "0.19.0", default-features = false }
once_cell = "1.19.0"
ouroboros = "0.18.0"
parse-display = "0.8.2"
pollster = "0.3.0"
pretty_assertions = "1.3.0"
proc-macro2 = "1.0.69"
pyo3 = "0.20.3"
Expand Down
5 changes: 3 additions & 2 deletions crates/voicevox_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ link-onnxruntime = []
anyhow.workspace = true
async-fs.workspace = true
async_zip = { workspace = true, features = ["deflate"] }
blocking.workspace = true
camino.workspace = true
const_format.workspace = true
derive-getters.workspace = true
Expand All @@ -27,7 +28,7 @@ duplicate.workspace = true
easy-ext.workspace = true
educe.workspace = true
enum-map.workspace = true
fs-err = { workspace = true, features = ["tokio"] }
fs-err.workspace = true
futures-io.workspace = true
futures-lite.workspace = true
futures-util = { workspace = true, features = ["io"] }
Expand All @@ -46,14 +47,14 @@ smallvec.workspace = true
strum = { workspace = true, features = ["derive"] }
tempfile.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["rt"] } # FIXME: feature-gateする
tracing.workspace = true
uuid = { workspace = true, features = ["v4", "serde"] }
voicevox-ort = { workspace = true, features = ["download-binaries", "__init-for-voicevox"] }
voicevox_core_macros = { path = "../voicevox_core_macros" }

[dev-dependencies]
heck.workspace = true
pollster = { workspace = true, features = ["macro"] }
pretty_assertions.workspace = true
rstest.workspace = true
rstest_reuse.workspace = true
Expand Down
12 changes: 6 additions & 6 deletions crates/voicevox_core/src/__internal/doctest_fixtures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ pub async fn synthesizer_with_sample_voice_model(
OsString,
>,
open_jtalk_dic_dir: impl AsRef<Utf8Path>,
) -> anyhow::Result<crate::tokio::Synthesizer<crate::tokio::OpenJtalk>> {
let syntesizer = crate::tokio::Synthesizer::new(
) -> anyhow::Result<crate::nonblocking::Synthesizer<crate::nonblocking::OpenJtalk>> {
let syntesizer = crate::nonblocking::Synthesizer::new(
#[cfg(feature = "load-onnxruntime")]
crate::tokio::Onnxruntime::load_once()
crate::nonblocking::Onnxruntime::load_once()
.filename(onnxruntime_dylib_path)
.exec()
.await?,
#[cfg(feature = "link-onnxruntime")]
crate::tokio::Onnxruntime::init_once().await?,
crate::tokio::OpenJtalk::new(open_jtalk_dic_dir).await?,
crate::nonblocking::Onnxruntime::init_once().await?,
crate::nonblocking::OpenJtalk::new(open_jtalk_dic_dir).await?,
&InitializeOptions {
acceleration_mode: AccelerationMode::Cpu,
..Default::default()
},
)?;

let model = &crate::tokio::VoiceModel::from_path(voice_model_path).await?;
let model = &crate::nonblocking::VoiceModel::from_path(voice_model_path).await?;
syntesizer.load_voice_model(model).await?;

Ok(syntesizer)
Expand Down
6 changes: 2 additions & 4 deletions crates/voicevox_core/src/asyncs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@
//! に[`SingleTasked`]を用意している。
//!
//! [ブロッキング版API]: crate::blocking
//! [非同期版API]: crate::tokio
//! [blocking]: https://docs.rs/crate/blocking
//! [非同期版API]: crate::nonblocking

use std::{
io::{self, Read as _, Seek as _, SeekFrom},
Expand Down Expand Up @@ -71,8 +70,7 @@ impl Async for SingleTasked {
///
/// [非同期版API]用。
///
/// [blocking]: https://docs.rs/crate/blocking
/// [非同期版API]: crate::tokio
/// [非同期版API]: crate::nonblocking
pub(crate) enum BlockingThreadPool {}

impl Async for BlockingThreadPool {
Expand Down
4 changes: 2 additions & 2 deletions crates/voicevox_core/src/devices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ fn test_gpu(
/// しても`cuda`や`dml`は`true`を示しうる。
///
/// ```
/// # #[tokio::main]
/// # #[pollster::main]
/// # async fn main() -> anyhow::Result<()> {
/// use voicevox_core::{tokio::Onnxruntime, SupportedDevices};
/// use voicevox_core::{nonblocking::Onnxruntime, SupportedDevices};
///
/// # voicevox_core::blocking::Onnxruntime::load_once()
/// # .filename(if cfg!(windows) {
Expand Down
4 changes: 2 additions & 2 deletions crates/voicevox_core/src/engine/full_context_label.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,7 @@ mod tests {
#[apply(label_cases)]
#[tokio::test]
async fn open_jtalk(text: &str, labels: &[&str], _accent_phrase: &[AccentPhrase]) {
let open_jtalk = crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
let open_jtalk = crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
.await
.unwrap();
assert_eq!(&open_jtalk.extract_fullcontext(text).unwrap(), labels);
Expand All @@ -447,7 +447,7 @@ mod tests {
#[apply(label_cases)]
#[tokio::test]
async fn extract_fullcontext(text: &str, _labels: &[&str], accent_phrase: &[AccentPhrase]) {
let open_jtalk = crate::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
let open_jtalk = crate::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
.await
.unwrap();
assert_eq!(
Expand Down
28 changes: 24 additions & 4 deletions crates/voicevox_core/src/engine/open_jtalk.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
// TODO: `VoiceModel`のように、次のような設計にする。
//
// ```
// pub(crate) mod blocking {
// pub struct OpenJtalk(Inner<SingleTasked>);
// // …
// }
// pub(crate) mod nonblocking {
// pub struct OpenJtalk(Inner<BlockingThreadPool>);
// // …
// }
// ```

use ::open_jtalk::Text2MecabError;

#[derive(thiserror::Error, Debug)]
Expand Down Expand Up @@ -183,12 +196,19 @@ pub(crate) mod blocking {
}
}

pub(crate) mod tokio {
pub(crate) mod nonblocking {
use camino::Utf8Path;

use super::FullcontextExtractor;

/// テキスト解析器としてのOpen JTalk。
///
/// # Performance
///
/// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。
///
/// [blocking]: https://docs.rs/crate/blocking
/// [`nonblocking`モジュールのドキュメント]: crate::nonblocking
#[derive(Clone)]
pub struct OpenJtalk(super::blocking::OpenJtalk);

Expand All @@ -206,7 +226,7 @@ pub(crate) mod tokio {
/// この関数を呼び出した後にユーザー辞書を変更した場合は、再度この関数を呼ぶ必要がある。
pub async fn use_user_dict(
&self,
user_dict: &crate::tokio::UserDict,
user_dict: &crate::nonblocking::UserDict,
) -> crate::result::Result<()> {
let inner = self.0 .0.clone();
let words = user_dict.to_mecab_format();
Expand Down Expand Up @@ -325,7 +345,7 @@ mod tests {
#[case] text: &str,
#[case] expected: anyhow::Result<Vec<String>>,
) {
let open_jtalk = super::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
let open_jtalk = super::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
.await
.unwrap();
let result = open_jtalk.extract_fullcontext(text);
Expand All @@ -339,7 +359,7 @@ mod tests {
#[case] text: &str,
#[case] expected: anyhow::Result<Vec<String>>,
) {
let open_jtalk = super::tokio::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
let open_jtalk = super::nonblocking::OpenJtalk::new(OPEN_JTALK_DIC_DIR)
.await
.unwrap();
for _ in 0..10 {
Expand Down
38 changes: 28 additions & 10 deletions crates/voicevox_core/src/infer/runtimes/onnxruntime.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
// TODO: `VoiceModel`のように、次のような設計にする。
//
// ```
// pub(crate) mod blocking {
// pub struct Onnxruntime(Inner<SingleTasked>);
// // …
// }
// pub(crate) mod nonblocking {
// pub struct Onnxruntime(Inner<BlockingThreadPool>);
// // …
// }
// ```

use std::{fmt::Debug, vec};

use anyhow::{anyhow, bail, ensure};
Expand All @@ -18,9 +31,6 @@ use super::super::{
OutputScalarKind, OutputTensor, ParamInfo, PushInputTensor,
};

// TODO: `trait AsyncRuntime`みたいなものを作って抽象化しながら同期版と非同期版に別個の役割を
// 持たせる
// (なぜそうしたいかの理由の一つとしては<https://github.com/VOICEVOX/voicevox_core/issues/687>)
impl InferenceRuntime for self::blocking::Onnxruntime {
type Session = ort::Session;
type RunContext<'a> = OnnxruntimeRunContext<'a>;
Expand Down Expand Up @@ -254,7 +264,7 @@ pub(crate) mod blocking {
/// # Rust APIにおけるインスタンスの共有
///
/// インスタンスは[voicevox-ort]側に作られる。Rustのクレートとしてこのライブラリを利用する場合、
/// Tokio版APIやvoicevox-ortを利用する他クレートともインスタンスが共有される。
/// 非同期版APIやvoicevox-ortを利用する他クレートともインスタンスが共有される。
///
#[cfg_attr(feature = "load-onnxruntime", doc = "```")]
#[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")]
Expand All @@ -268,7 +278,7 @@ pub(crate) mod blocking {
/// # .exec()?;
/// # }
/// let ort1 = voicevox_core::blocking::Onnxruntime::load_once().exec()?;
/// let ort2 = another_lib::tokio::Onnxruntime::get().expect("`ort1`と同一のはず");
/// let ort2 = another_lib::nonblocking::Onnxruntime::get().expect("`ort1`と同一のはず");
/// assert_eq!(ptr_addr(ort1), ptr_addr(ort2));
///
/// fn ptr_addr(obj: &impl Sized) -> usize {
Expand Down Expand Up @@ -430,7 +440,7 @@ pub(crate) mod blocking {
}
}

pub(crate) mod tokio {
pub(crate) mod nonblocking {
use ref_cast::{ref_cast_custom, RefCastCustom};

use crate::SupportedDevices;
Expand All @@ -448,15 +458,17 @@ pub(crate) mod tokio {
#[cfg_attr(not(feature = "load-onnxruntime"), doc = "```compile_fail")]
/// # use voicevox_core as another_lib;
/// #
/// # #[tokio::main]
/// # #[pollster::main]
/// # async fn main() -> anyhow::Result<()> {
/// # if cfg!(windows) {
/// # // Windows\System32\onnxruntime.dllを回避
/// # voicevox_core::blocking::Onnxruntime::load_once()
/// # .filename(test_util::ONNXRUNTIME_DYLIB_PATH)
/// # .exec()?;
/// # }
/// let ort1 = voicevox_core::tokio::Onnxruntime::load_once().exec().await?;
/// let ort1 = voicevox_core::nonblocking::Onnxruntime::load_once()
/// .exec()
/// .await?;
/// let ort2 = another_lib::blocking::Onnxruntime::get().expect("`ort1`と同一のはず");
/// assert_eq!(ptr_addr(ort1), ptr_addr(ort2));
///
Expand All @@ -467,7 +479,13 @@ pub(crate) mod tokio {
/// # }
/// ```
///
/// # Performance
///
/// [blocking]クレートにより動いている。詳しくは[`nonblocking`モジュールのドキュメント]を参照。
///
/// [voicevox-ort]: https://github.com/VOICEVOX/ort
/// [blocking]: https://docs.rs/crate/blocking
/// [`nonblocking`モジュールのドキュメント]: crate::nonblocking
#[derive(Debug, RefCastCustom)]
#[repr(transparent)]
pub struct Onnxruntime(pub(crate) super::blocking::Onnxruntime);
Expand Down Expand Up @@ -584,11 +602,11 @@ mod tests {

assert_eq!(
super::blocking::Onnxruntime::LIB_NAME,
super::tokio::Onnxruntime::LIB_NAME,
super::nonblocking::Onnxruntime::LIB_NAME,
);
assert_eq!(
super::blocking::Onnxruntime::LIB_VERSION,
super::tokio::Onnxruntime::LIB_VERSION,
super::nonblocking::Onnxruntime::LIB_VERSION,
);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/voicevox_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ mod voice_model;

pub mod __internal;
pub mod blocking;
pub mod tokio;
pub mod nonblocking;

#[cfg(test)]
mod test_util;
Expand Down
25 changes: 25 additions & 0 deletions crates/voicevox_core/src/nonblocking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! 非同期版API。
//!
//! # Performance
//!
//! これらは[blocking]クレートにより動いている。特定の非同期ランタイムを必要とせず、[pollster]など
//! でも動かすことができる。
//!
//! スレッドプールおよびエグゼキュータはblockingクレートに依存するすべてのプログラム間で共有される。
//! スレッドプールのサイズは、blockingクレートの説明にある通り`$BLOCKING_MAX_THREADS`で調整すること
//! ができる。
//!
//! [blocking]: https://docs.rs/crate/blocking
//! [pollster]: https://docs.rs/crate/pollster

pub use crate::{
engine::open_jtalk::nonblocking::OpenJtalk,
infer::runtimes::onnxruntime::nonblocking::Onnxruntime, synthesizer::nonblocking::Synthesizer,
user_dict::dict::nonblocking::UserDict, voice_model::nonblocking::VoiceModel,
};

pub mod onnxruntime {
#[cfg(feature = "load-onnxruntime")]
#[cfg_attr(docsrs, doc(cfg(feature = "load-onnxruntime")))]
pub use crate::infer::runtimes::onnxruntime::nonblocking::LoadOnce;
}
4 changes: 2 additions & 2 deletions crates/voicevox_core/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ mod tests {
talk: enum_map!(_ => InferenceSessionOptions::new(0, DeviceSpec::Cpu)),
},
);
let model = &crate::tokio::VoiceModel::sample().await.unwrap();
let model = &crate::nonblocking::VoiceModel::sample().await.unwrap();
let model_contents = &model.read_inference_models().await.unwrap();
let result = status.insert_model(model.header(), model_contents);
assert_debug_fmt_eq!(Ok(()), result);
Expand All @@ -424,7 +424,7 @@ mod tests {
talk: enum_map!(_ => InferenceSessionOptions::new(0, DeviceSpec::Cpu)),
},
);
let vvm = &crate::tokio::VoiceModel::sample().await.unwrap();
let vvm = &crate::nonblocking::VoiceModel::sample().await.unwrap();
let model_header = vvm.header();
let model_contents = &vvm.read_inference_models().await.unwrap();
assert!(
Expand Down
Loading

0 comments on commit b8118b9

Please sign in to comment.