Spaces:
Build error
Build error
import os | |
import platform | |
from ctypes import CDLL, POINTER, c_bool, c_char_p, c_float, c_int, c_long | |
from ctypes.util import find_library | |
from dataclasses import dataclass | |
from enum import Enum, auto | |
from pathlib import Path | |
from typing import List, Optional | |
import numpy as np | |
class OldCoreError(Exception): | |
"""古いコアが使用されている場合に発生するエラー""" | |
class CoreError(Exception): | |
"""コア呼び出しで発生したエラー""" | |
def load_runtime_lib(runtime_dirs: List[Path]): | |
if platform.system() == "Windows": | |
# DirectML.dllはonnxruntimeと互換性のないWindows標準搭載のものを優先して読み込むことがあるため、明示的に読み込む | |
# 参考 1. https://github.com/microsoft/onnxruntime/issues/3360 | |
# 参考 2. https://tadaoyamaoka.hatenablog.com/entry/2020/06/07/113616 | |
lib_file_names = [ | |
"torch_cpu.dll", | |
"torch_cuda.dll", | |
"DirectML.dll", | |
"onnxruntime.dll", | |
] | |
lib_names = ["torch_cpu", "torch_cuda", "onnxruntime"] | |
elif platform.system() == "Linux": | |
lib_file_names = ["libtorch.so", "libonnxruntime.so"] | |
lib_names = ["torch", "onnxruntime"] | |
elif platform.system() == "Darwin": | |
lib_file_names = ["libonnxruntime.dylib"] | |
lib_names = ["onnxruntime"] | |
else: | |
raise RuntimeError("不明なOSです") | |
for lib_path in runtime_dirs: | |
for file_name in lib_file_names: | |
try: | |
CDLL(str((lib_path / file_name).resolve(strict=True))) | |
except OSError: | |
pass | |
for lib_name in lib_names: | |
try: | |
CDLL(find_library(lib_name)) | |
except (OSError, TypeError): | |
pass | |
class GPUType(Enum): | |
# NONEはCPUしか対応していないことを示す | |
NONE = auto() | |
CUDA = auto() | |
DIRECT_ML = auto() | |
class CoreInfo: | |
name: str | |
platform: str | |
arch: str | |
core_type: str | |
gpu_type: GPUType | |
# version 0.12 より前のコアの情報 | |
CORE_INFOS = [ | |
# Windows | |
CoreInfo( | |
name="core.dll", | |
platform="Windows", | |
arch="x64", | |
core_type="libtorch", | |
gpu_type=GPUType.CUDA, | |
), | |
CoreInfo( | |
name="core_cpu.dll", | |
platform="Windows", | |
arch="x64", | |
core_type="libtorch", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="core_gpu_x64_nvidia.dll", | |
platform="Windows", | |
arch="x64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.CUDA, | |
), | |
CoreInfo( | |
name="core_gpu_x64_directml.dll", | |
platform="Windows", | |
arch="x64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.DIRECT_ML, | |
), | |
CoreInfo( | |
name="core_cpu_x64.dll", | |
platform="Windows", | |
arch="x64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="core_cpu_x86.dll", | |
platform="Windows", | |
arch="x86", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="core_gpu_x86_directml.dll", | |
platform="Windows", | |
arch="x86", | |
core_type="onnxruntime", | |
gpu_type=GPUType.DIRECT_ML, | |
), | |
CoreInfo( | |
name="core_cpu_arm.dll", | |
platform="Windows", | |
arch="armv7l", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="core_gpu_arm_directml.dll", | |
platform="Windows", | |
arch="armv7l", | |
core_type="onnxruntime", | |
gpu_type=GPUType.DIRECT_ML, | |
), | |
CoreInfo( | |
name="core_cpu_arm64.dll", | |
platform="Windows", | |
arch="aarch64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="core_gpu_arm64_directml.dll", | |
platform="Windows", | |
arch="aarch64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.DIRECT_ML, | |
), | |
# Linux | |
CoreInfo( | |
name="libcore.so", | |
platform="Linux", | |
arch="x64", | |
core_type="libtorch", | |
gpu_type=GPUType.CUDA, | |
), | |
CoreInfo( | |
name="libcore_cpu.so", | |
platform="Linux", | |
arch="x64", | |
core_type="libtorch", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="libcore_gpu_x64_nvidia.so", | |
platform="Linux", | |
arch="x64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.CUDA, | |
), | |
CoreInfo( | |
name="libcore_cpu_x64.so", | |
platform="Linux", | |
arch="x64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="libcore_cpu_armhf.so", | |
platform="Linux", | |
arch="armv7l", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
CoreInfo( | |
name="libcore_cpu_arm64.so", | |
platform="Linux", | |
arch="aarch64", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
# macOS | |
CoreInfo( | |
name="libcore_cpu_universal2.dylib", | |
platform="Darwin", | |
arch="universal", | |
core_type="onnxruntime", | |
gpu_type=GPUType.NONE, | |
), | |
] | |
# version 0.12 以降のコアの名前の辞書 | |
# - version 0.12, 0.13 のコアの名前: core | |
# - version 0.14 からのコアの名前: voicevox_core | |
CORENAME_DICT = { | |
"Windows": ("voicevox_core.dll", "core.dll"), | |
"Linux": ("libvoicevox_core.so", "libcore.so"), | |
"Darwin": ("libvoicevox_core.dylib", "libcore.dylib"), | |
} | |
def find_version_0_12_core_or_later(core_dir: Path) -> Optional[str]: | |
""" | |
core_dir で指定したディレクトリにあるコアライブラリが Version 0.12 以降である場合、 | |
見つかった共有ライブラリの名前を返す。 | |
Version 0.12 以降と判定する条件は、 | |
- core_dir に metas.json が存在しない | |
- コアライブラリの名前が CORENAME_DICT の定義に従っている | |
の両方が真のときである。 | |
cf. https://github.com/VOICEVOX/voicevox_engine/issues/385 | |
""" | |
if (core_dir / "metas.json").exists(): | |
return None | |
for core_name in CORENAME_DICT[platform.system()]: | |
if (core_dir / core_name).is_file(): | |
return core_name | |
return None | |
def get_arch_name() -> Optional[str]: | |
""" | |
platform.machine() が特定のアーキテクチャ上で複数パターンの文字列を返し得るので、 | |
一意な文字列に変換する | |
サポート外のアーキテクチャである場合、None を返す | |
""" | |
machine = platform.machine() | |
if machine == "x86_64" or machine == "x64" or machine == "AMD64": | |
return "x64" | |
elif machine == "i386" or machine == "x86": | |
return "x86" | |
elif machine == "arm64": | |
return "aarch64" | |
elif machine in ["armv7l", "aarch64"]: | |
return machine | |
else: | |
return None | |
def get_core_name( | |
arch_name: str, | |
platform_name: str, | |
model_type: str, | |
gpu_type: GPUType, | |
) -> Optional[str]: | |
if platform_name == "Darwin": | |
if gpu_type == GPUType.NONE and (arch_name == "x64" or arch_name == "aarch64"): | |
arch_name = "universal" | |
else: | |
return None | |
for core_info in CORE_INFOS: | |
if ( | |
core_info.platform == platform_name | |
and core_info.arch == arch_name | |
and core_info.core_type == model_type | |
and core_info.gpu_type == gpu_type | |
): | |
return core_info.name | |
return None | |
def get_suitable_core_name( | |
model_type: str, | |
gpu_type: GPUType, | |
) -> Optional[str]: | |
arch_name = get_arch_name() | |
if arch_name is None: | |
return None | |
platform_name = platform.system() | |
return get_core_name(arch_name, platform_name, model_type, gpu_type) | |
def check_core_type(core_dir: Path) -> Optional[str]: | |
# libtorch版はDirectML未対応なので、ここでは`gpu_type=GPUType.DIRECT_ML`は入れない | |
libtorch_core_names = [ | |
get_suitable_core_name("libtorch", gpu_type=GPUType.CUDA), | |
get_suitable_core_name("libtorch", gpu_type=GPUType.NONE), | |
] | |
onnxruntime_core_names = [ | |
get_suitable_core_name("onnxruntime", gpu_type=GPUType.CUDA), | |
get_suitable_core_name("onnxruntime", gpu_type=GPUType.DIRECT_ML), | |
get_suitable_core_name("onnxruntime", gpu_type=GPUType.NONE), | |
] | |
if any([(core_dir / name).is_file() for name in libtorch_core_names if name]): | |
return "libtorch" | |
elif any([(core_dir / name).is_file() for name in onnxruntime_core_names if name]): | |
return "onnxruntime" | |
else: | |
return None | |
def load_core(core_dir: Path, use_gpu: bool) -> CDLL: | |
core_name = find_version_0_12_core_or_later(core_dir) | |
if core_name: | |
try: | |
# NOTE: CDLL クラスのコンストラクタの引数 name には文字列を渡す必要がある。 | |
# Windows 環境では PathLike オブジェクトを引数として渡すと初期化に失敗する。 | |
return CDLL(str((core_dir / core_name).resolve(strict=True))) | |
except OSError as err: | |
raise RuntimeError(f"コアの読み込みに失敗しました:{err}") | |
model_type = check_core_type(core_dir) | |
if model_type is None: | |
raise RuntimeError("コアが見つかりません") | |
if use_gpu or model_type == "onnxruntime": | |
core_name = get_suitable_core_name(model_type, gpu_type=GPUType.CUDA) | |
if core_name: | |
try: | |
return CDLL(str((core_dir / core_name).resolve(strict=True))) | |
except OSError: | |
pass | |
core_name = get_suitable_core_name(model_type, gpu_type=GPUType.DIRECT_ML) | |
if core_name: | |
try: | |
return CDLL(str((core_dir / core_name).resolve(strict=True))) | |
except OSError: | |
pass | |
core_name = get_suitable_core_name(model_type, gpu_type=GPUType.NONE) | |
if core_name: | |
try: | |
return CDLL(str((core_dir / core_name).resolve(strict=True))) | |
except OSError as err: | |
if model_type == "libtorch": | |
core_name = get_suitable_core_name(model_type, gpu_type=GPUType.CUDA) | |
if core_name: | |
try: | |
return CDLL(str((core_dir / core_name).resolve(strict=True))) | |
except OSError as err_: | |
err = err_ | |
raise RuntimeError(f"コアの読み込みに失敗しました:{err}") | |
else: | |
raise RuntimeError(f"このコンピュータのアーキテクチャ {platform.machine()} で利用可能なコアがありません") | |
class CoreWrapper: | |
def __init__( | |
self, | |
use_gpu: bool, | |
core_dir: Path, | |
cpu_num_threads: int = 0, | |
load_all_models: bool = False, | |
) -> None: | |
self.core = load_core(core_dir, use_gpu) | |
self.core.initialize.restype = c_bool | |
self.core.metas.restype = c_char_p | |
self.core.yukarin_s_forward.restype = c_bool | |
self.core.yukarin_sa_forward.restype = c_bool | |
self.core.decode_forward.restype = c_bool | |
self.core.last_error_message.restype = c_char_p | |
self.exist_supported_devices = False | |
self.exist_finalize = False | |
exist_cpu_num_threads = False | |
self.exist_load_model = False | |
self.exist_is_model_loaded = False | |
is_version_0_12_core_or_later = ( | |
find_version_0_12_core_or_later(core_dir) is not None | |
) | |
if is_version_0_12_core_or_later: | |
model_type = "onnxruntime" | |
self.exist_load_model = True | |
self.exist_is_model_loaded = True | |
self.core.load_model.argtypes = (c_long,) | |
self.core.load_model.restype = c_bool | |
self.core.is_model_loaded.argtypes = (c_long,) | |
self.core.is_model_loaded.restype = c_bool | |
else: | |
model_type = check_core_type(core_dir) | |
assert model_type is not None | |
if model_type == "onnxruntime": | |
self.core.supported_devices.restype = c_char_p | |
self.core.finalize.restype = None | |
self.exist_supported_devices = True | |
self.exist_finalize = True | |
exist_cpu_num_threads = True | |
self.core.yukarin_s_forward.argtypes = ( | |
c_int, | |
POINTER(c_long), | |
POINTER(c_long), | |
POINTER(c_float), | |
) | |
self.core.yukarin_sa_forward.argtypes = ( | |
c_int, | |
POINTER(c_long), | |
POINTER(c_long), | |
POINTER(c_long), | |
POINTER(c_long), | |
POINTER(c_long), | |
POINTER(c_long), | |
POINTER(c_long), | |
POINTER(c_float), | |
) | |
self.core.decode_forward.argtypes = ( | |
c_int, | |
c_int, | |
POINTER(c_float), | |
POINTER(c_float), | |
POINTER(c_long), | |
POINTER(c_float), | |
) | |
cwd = os.getcwd() | |
os.chdir(core_dir) | |
try: | |
if is_version_0_12_core_or_later: | |
self.assert_core_success( | |
self.core.initialize(use_gpu, cpu_num_threads, load_all_models) | |
) | |
elif exist_cpu_num_threads: | |
self.assert_core_success( | |
self.core.initialize(".", use_gpu, cpu_num_threads) | |
) | |
else: | |
self.assert_core_success(self.core.initialize(".", use_gpu)) | |
finally: | |
os.chdir(cwd) | |
def metas(self) -> str: | |
return self.core.metas().decode("utf-8") | |
def yukarin_s_forward( | |
self, | |
length: int, | |
phoneme_list: np.ndarray, | |
speaker_id: np.ndarray, | |
) -> np.ndarray: | |
output = np.zeros((length,), dtype=np.float32) | |
self.assert_core_success( | |
self.core.yukarin_s_forward( | |
c_int(length), | |
phoneme_list.ctypes.data_as(POINTER(c_long)), | |
speaker_id.ctypes.data_as(POINTER(c_long)), | |
output.ctypes.data_as(POINTER(c_float)), | |
) | |
) | |
return output | |
def yukarin_sa_forward( | |
self, | |
length: int, | |
vowel_phoneme_list: np.ndarray, | |
consonant_phoneme_list: np.ndarray, | |
start_accent_list: np.ndarray, | |
end_accent_list: np.ndarray, | |
start_accent_phrase_list: np.ndarray, | |
end_accent_phrase_list: np.ndarray, | |
speaker_id: np.ndarray, | |
) -> np.ndarray: | |
output = np.empty( | |
( | |
len(speaker_id), | |
length, | |
), | |
dtype=np.float32, | |
) | |
self.assert_core_success( | |
self.core.yukarin_sa_forward( | |
c_int(length), | |
vowel_phoneme_list.ctypes.data_as(POINTER(c_long)), | |
consonant_phoneme_list.ctypes.data_as(POINTER(c_long)), | |
start_accent_list.ctypes.data_as(POINTER(c_long)), | |
end_accent_list.ctypes.data_as(POINTER(c_long)), | |
start_accent_phrase_list.ctypes.data_as(POINTER(c_long)), | |
end_accent_phrase_list.ctypes.data_as(POINTER(c_long)), | |
speaker_id.ctypes.data_as(POINTER(c_long)), | |
output.ctypes.data_as(POINTER(c_float)), | |
) | |
) | |
return output | |
def decode_forward( | |
self, | |
length: int, | |
phoneme_size: int, | |
f0: np.ndarray, | |
phoneme: np.ndarray, | |
speaker_id: np.ndarray, | |
) -> np.ndarray: | |
output = np.empty((length * 256,), dtype=np.float32) | |
self.assert_core_success( | |
self.core.decode_forward( | |
c_int(length), | |
c_int(phoneme_size), | |
f0.ctypes.data_as(POINTER(c_float)), | |
phoneme.ctypes.data_as(POINTER(c_float)), | |
speaker_id.ctypes.data_as(POINTER(c_long)), | |
output.ctypes.data_as(POINTER(c_float)), | |
) | |
) | |
return output | |
def supported_devices(self) -> str: | |
if self.exist_supported_devices: | |
return self.core.supported_devices().decode("utf-8") | |
raise OldCoreError | |
def finalize(self) -> None: | |
if self.exist_finalize: | |
self.core.finalize() | |
return | |
raise OldCoreError | |
def load_model(self, speaker_id: int) -> None: | |
if self.exist_load_model: | |
self.assert_core_success(self.core.load_model(c_long(speaker_id))) | |
raise OldCoreError | |
def is_model_loaded(self, speaker_id: int) -> bool: | |
if self.exist_is_model_loaded: | |
return self.core.is_model_loaded(c_long(speaker_id)) | |
raise OldCoreError | |
def assert_core_success(self, result: bool) -> None: | |
if not result: | |
raise CoreError( | |
self.core.last_error_message().decode("utf-8", "backslashreplace") | |
) | |