さくらインターネットのGPUサービスDOKでVALL-E X（音声読み上げAI）を試す

[DOK](https://www.sakura.ad.jp/koukaryoku-dok/)はコンテナー型のGPUサービスで、NVIDIA V100とかH100を実行時間課金で利用できるサービスです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/98a7fac2-c363-088f-8072-0a6b65395fe2.png)


[コンテナー型GPUクラウドサービス 高火力 DOK（ドック） | さくらインターネット](https://www.sakura.ad.jp/koukaryoku-dok/)

今回はこのDOKで、[VALL-E X（音声読み上げAI）](https://github.com/Plachtaa/VALL-E-X) を試してみました。

## 実行までのステップ

1. さくらのクラウドのアカウントの用意（必須）
2. Dockerイメージのビルド環境（Linuxなど）を用意
3. Dockerイメージのビルド
4. DOKにイメージをアップロード
5. DOKでコンテナーを起動・実行

## 1. さくらのクラウドのアカウントの用意

DOKはさくらのクラウドに紐付いたサービスなので、さくらのクラウドのアカウント必須です。

[クラウドサーバーはIaaS型のさくらのクラウド](https://cloud.sakura.ad.jp/)

### コンテナレジストリの作成

さくらのクラウドにログインしたら `さくらのクラウド` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e247a30c-bbc7-fb7e-136d-71ee2a795533.png)


左側のメニューの `LAB` の中にある `コンテナレジストリ` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/25d041f2-9039-fec3-77c6-88edf5afa936.png)


`追加` を押して、コンテナレジストリを作成します。最低限、以下の入力が必要です。

| 項目                 | 設定                                                                              |
| -------------------- | --------------------------------------------------------------------------------- |
| 名前                 | 分かりやすい、任意の名前を入力してください                                        |
| コンテナレジストリ名 | ドメイン名に使われます。 `EXAMPLE.sakuracr.jp` というコンテナレジストリになります |
| 公開設定             | 非公開で良いかと思います                                                          |

#### ユーザーの作成

コンテナレジストリにアクセスできるユーザーを作成します。作成したコンテナレジストリをダブルクリックして、詳細を表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/b9f5ef9d-ebef-c24e-de89-f63c5ce5d443.png)


`ユーザー` タブを選択して、`追加` を押します。

IDとパスワードを設定して、権限は `All` にして作成してください。

## 2. Dockerイメージのビルド環境（Linuxなど）を用意

WindowsのWSL2でできるかは分かりませんが、少なくともmacOS（M1など）ではDockerイメージをビルドできなかったので、別途用意が必要です。これは適当なVPSサーバーや自宅サーバ、さくらのクラウドでインスタンスを立ち上げるでもOKです。

以下はスペックの例です。

- コア  
2
- メモリ  
4GB
- OS  
Ubuntu Server 22.04.4 LTS 64bit
- ディスク  
100GB

## 3. Dockerイメージのビルド

作業フォルダを作成します。

```bash
mkdir VALL-E-X
cd VALL-E-X
```

後はこの `VALL-E-X` ディレクトリの中で処理を書いていきます。

### Dockerfileの作成

Dockerfileを作成します。内容は以下の通りです。

```dockerfile
FROM nvidia/cuda:12.5.1-runtime-ubuntu22.04
ENV DEBIAN_FRONTEND=noninteractive

# 必要なパッケージのインストール
# /appはアプリのディレクトリ、/opt/artifactはアウトプット先のディレクトリ
RUN apt-get update && \
    apt-get install -y \
        git \
        git-lfs \
        python3 \
        python3-pip \
      && \
    mkdir /app /opt/artifact && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# VALL-E Xのリポジトリをクローン
RUN git clone https://github.com/Plachtaa/VALL-E-X.git /app
WORKDIR /app
# git-lfsのインストール
RUN git lfs install --skip-smudge 
# 依存ライブラリのインストール
RUN pip install -r requirements.txt
# 出力データをオブジェクトストレージにアップロードするためのライブラリ
RUN pip install boto3
RUN pip cache purge

# 実行スクリプト（後で作成）
COPY runner.py /app/
# Dockerコンテナー起動時に実行するスクリプト（後で作成）
COPY docker-entrypoint.sh /
# 実行権限を付与
RUN chmod +x /docker-entrypoint.sh /

WORKDIR /
# Dockerコンテナー起動時に実行するスクリプトを指定して実行
CMD ["/bin/bash", "/docker-entrypoint.sh"]
```

### docker-entrypoint.shの作成

docker-entrypoint.shは、Dockerコンテナー起動時に実行するスクリプトです。内容は以下の通りです。

```bash
#!/bin/bash
 
set -ue
shopt -s nullglob

export TZ=${TZ:-Asia/Tokyo}

# アウトプット先ディレクトリ（自動付与） /opt/artifact固定です
if [ -z "${SAKURA_ARTIFACT_DIR:-}" ]; then
  echo "Environment variable SAKURA_ARTIFACT_DIR is not set" >&2
  exit 1
fi

# DOKのタスクID（自動付与）
if [ -z "${SAKURA_TASK_ID:-}" ]; then
  echo "Environment variable SAKURA_TASK_ID is not set" >&2
  exit 1
fi

# 読み上げるテキスト（環境変数で指定）
if [ -z "${TEXT:-}" ]; then
  echo "Environment variable TEXT is not set" >&2
  exit 1
fi

# S3_はすべてboto3用の環境変数です
pushd /app
  python3 runner.py \
	  --id="${SAKURA_TASK_ID}" \
	  --output="${SAKURA_ARTIFACT_DIR}" \
	  --text="${TEXT}" \
	  --s3-bucket="${S3_BUCKET:-}" \
	  --s3-endpoint="${S3_ENDPOINT:-}" \
	  --s3-secret="${S3_SECRET:-}" \
	  --s3-token="${S3_TOKEN:-}"
popd
```

### runner.pyの作成

runner.pyは実際に実行するスクリプトです。内容は以下の通りです。

#### ライブラリの読み込み

最初にライブラリを読み込みます。

```python
from utils.generation import SAMPLE_RATE, generate_audio, preload_models
from scipy.io.wavfile import write as write_wav
import argparse
```

#### 引数のパース

実行時に渡される引数をパースします。

```python
arg_parser = argparse.ArgumentParser()

arg_parser.add_argument(
    '--output',
    default='/opt/artifact',
    help='出力先ディレクトリを指定します。',
)
arg_parser.add_argument(
    '--text',
    default='Hello from Dok',
    help='読み上げるテキストを指定します。',
)
arg_parser.add_argument(
    '--id',
    default='',
    help='タスクIDを指定します。',
)
arg_parser.add_argument('--s3-bucket', help='S3のバケットを指定します。')
arg_parser.add_argument('--s3-endpoint', help='S3互換エンドポイントのURLを指定します。')
arg_parser.add_argument('--s3-secret', help='S3のシークレットアクセスキーを指定します。')
arg_parser.add_argument('--s3-token', help='S3のアクセスキーIDを指定します。')

args = arg_parser.parse_args()
```

#### モデルの読み込み

必要なモデルをダウンロードして読み込みます。

```python
preload_models()
```

#### テキストから音声の生成

テキストから音声データを生成します。

```python
audio_array = generate_audio(args.text)
```

#### 音声データの保存

音声データを保存します。 `/opt/artifact` ディレクトリに `output-{args.id}.wav` という名前で保存します。

```python
file = f'{args.output}/output-{args.id}.wav'
write_wav(file, SAMPLE_RATE, audio_array)
```

#### オブジェクトストレージへのアップロード

もし、S3_ではじまる環境変数が設定されていれば、boto3を使って出力内容をアップロードします。

```python
if args.s3_token and args.s3_secret and args.s3_bucket:
		# S3クライアントの作成
    s3 = boto3.client(
        's3',
        endpoint_url=args.s3_endpoint if args.s3_endpoint else None,
        aws_access_key_id=args.s3_token,
        aws_secret_access_key=args.s3_secret,
    )
		# ファイルアップロード
		s3.upload_file(
			Filename=file,
			Bucket=args.s3_bucket,
			Key=os.path.basename(file),
		)
```

#### 全体のコード

`runner.py` の全体のコードは以下の通りです。

```python
from utils.generation import SAMPLE_RATE, generate_audio, preload_models
from scipy.io.wavfile import write as write_wav
import argparse

arg_parser = argparse.ArgumentParser()

arg_parser.add_argument(
    '--output',
    default='/opt/artifact',
    help='出力先ディレクトリを指定します。',
)
arg_parser.add_argument(
    '--text',
    default='Hello from Dok',
    help='読み上げるテキストを指定します。',
)
arg_parser.add_argument(
    '--id',
    default='',
    help='タスクIDを指定します。',
)
arg_parser.add_argument('--s3-bucket', help='S3のバケットを指定します。')
arg_parser.add_argument('--s3-endpoint', help='S3互換エンドポイントのURLを指定します。')
arg_parser.add_argument('--s3-secret', help='S3のシークレットアクセスキーを指定します。')
arg_parser.add_argument('--s3-token', help='S3のアクセスキーIDを指定します。')

args = arg_parser.parse_args()

preload_models()

audio_array = generate_audio(args.text)

file = f'{args.output}/output-{args.id}.wav'
write_wav(file, SAMPLE_RATE, audio_array)

if args.s3_token and args.s3_secret and args.s3_bucket:
		# S3クライアントの作成
    s3 = boto3.client(
        's3',
        endpoint_url=args.s3_endpoint if args.s3_endpoint else None,
        aws_access_key_id=args.s3_token,
        aws_secret_access_key=args.s3_secret,
    )
		# ファイルアップロード
		s3.upload_file(
			Filename=file,
			Bucket=args.s3_bucket,
			Key=os.path.basename(file),
		)
```

### Dockerイメージのビルド

Dockerイメージをビルドします。この時、コンテナレジストリのドメイン名を使ってイメージ名を作成してください。

`EXAMPLE.sakuracr.jp` の場合、 `EXAMPLE.sakuracr.jp/VALL-E-X` といった名前になります。 `VALL-E-X` は適当に分かりやすいものを付けます。

```bash
$ sudo docker build -t EXAMPLE.sakuracr.jp/VALL-E-X .
```

実際の動作を確認する際には、`docker run` でコンテナーを起動して動作確認をしてください。出力ファイルを確認する際には、適当なディレクトリを `/opt/artifact` にマウントしてください。

```bash
$ sudo docker run -e TEXT=hello \
    -e SAKURA_TASK_ID=001 \
    -e SAKURA_ARTIFACT_DIR=/opt/artifact \
    -v /path/to/data/:/opt/artifact \
    -t EXAMPLE.sakuracr.jp/VALL-E-X
```

これで、`/path/to/data/` に `output-001.wav` というファイルが作成されているはずです。

## 4. DOKにイメージをアップロード

作成したDockerイメージをDOKにアップロードします。まずコンテナレジストリにログインします。コンテナレジストリのドメイン、ユーザー名、パスワードを入力してください。

```bash
$ sudo docker login EXAMPLE.sakuracr.jp
Username:
Password:
```

ログインしたら、イメージをプッシュします。

```bash
$ sudo docker image push EXAMPLE.sakuracr.jp/VALL-E-X
```

これでプッシュが終われば、準備は完了です。

## 5. DOKでコンテナーを起動・実行

### レジストリーの登録

DOKのダッシュボードで、 `レジストリー` を選択して、 `新規登録` を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/17a5ae43-a01f-f840-b3dc-6d4e821b85c8.png)


以下のように入力してください。入力したら、 `登録` を押します。

| 項目                 | 設定                             |
| -------------------- | -------------------------------- |
| ホスト名             | 例）EXAMPLE.sakuracr.jp          |
| ユーザー名           | コンテナレジストリで登録したもの |
| パスワード           | コンテナレジストリで登録したもの |
| パスワード（確認用） | コンテナレジストリで登録したもの |

### タスクの作成

DOKのダッシュボードで、 `タスク` を選択して、 `新規作成` を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/8700cda0-552b-4572-8961-295c6f31f939.png)

以下のように入力してください。入力したら、 `作成` を押します。 `:latest` をつけて、レジストリの最新イメージを指定しています。

| 項目               | 設定                         |
| ------------------ | ---------------------------- |
| イメージ           | EXAMPLE.sakuracr.jp/VALL-E-X:latest |
| レジストリー       | 登録したレジストリー         |
| コマンド           | 何もなし                     |
| エントリーポイント | 何もなし                     |
| 環境変数           | 後述                         |
| プラン             | v100-32gbまたはh100-80gb     |

環境変数は、以下のように設定してください。

| 項目               | 設定                         |
| ------------------ | ---------------------------- |
| TEXT              | 読み上げるテキストを指定します |

TEXTは英語や日本語、中国語が指定できます（自動判別）。

![FireShot Capture 049 - 高火力 DOK - タスク › 新規作成 - secure.sakura.ad.jp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/495ffc3e-e8ba-d9af-6ac6-fe9e50f11a98.jpeg)

タスクを作成するとキューに入り、順番に処理されます。

### 結果の確認

結果はタスクの画面で確認できます。完了していれば、 `アーティファクト` にて、出力ファイルをダウンロードできます。なお、ファイルはtar.gz形式で圧縮されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/5d5ace4b-0427-f928-fb6a-072088120a73.png)

## おまけ（オブジェクトストレージを使う）

アーティファクトの保存期限は72時間なので、データを永続的に残しておきたい場合はオブジェクトストレージへの保存をお勧めします。

### オブジェクトストレージの作成

さくらのクラウドのダッシュボードで、 `オブジェクトストレージ` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/31c2bd38-b083-c446-7be5-7765e8601930.png)


バケットとアクセスキーを作成します。シークレットアクセスキーも生成されるので、保存しておいてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f368a79e-af79-ed87-e6ab-5b29ebfff193.png)


情報としては、以下が手に入ります（例）。

- S3エンドポイント  
s3.isk01.sakurastorage.jp
- バケット名  
自分で決めたもの
- アクセスキー  
自動生成されたもの
- シークレットアクセスキー  
自動生成されたもの

そして、これらの情報をDOKのタスク実行時の環境変数に設定してください。

| 項目        | 設定                     |
| ----------- | ------------------------ |
| S3_ENDPOINT | S3エンドポイント         |
| S3_BUCKET   | バケット名               |
| S3_TOKEN    | アクセスキー             |
| S3_SECRET   | シークレットアクセスキー |

これでDOKのタスクを実行すると、結果ファイルがオブジェクトストレージにアップロードされます。ファイルアップロードの場合は、圧縮されていない状態で取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/8242b162-1f3d-e2d5-2b59-f6cf22edfd2e.png)

## まとめ

今回は、DOKでVALL-E X（音声読み上げAI）を試してみました。Dockerイメージを作成する必要があるので、Dockerの知識とDockerイメージを作成できる環境を用意する必要があるので注意してください。

Dockerイメージさえできてしまえば、後は環境変数を指定して実行すれば良いだけなので簡単です。ぜひAI・機械学習などに活用してください。

[コンテナー型GPUクラウドサービス 高火力 DOK（ドック） | さくらインターネット](https://www.sakura.ad.jp/koukaryoku-dok/)