GPUコンテナのDOKでAnimagine XL 4.0（アニメ風イラスト生成）を試す

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

今回はこのDOKを使って、[Animagine XL 4.0](https://huggingface.co/cagliostrolab/animagine-xl-4.0)を試してみました。

## 注意

この画像生成で作られる画像は、アニメのキャラクターを学習利用しており、固有名詞を使って絵柄が指定できます。そういった画像は著作権上、問題になる可能性が高いので利用時には注意してください。

## 参考

[cagliostrolab/animagine\-xl\-4\.0 · Hugging Face](https://huggingface.co/cagliostrolab/animagine-xl-4.0)にある利用例をベースに進めます。

## とりあえず試す

コンテナレジストリにプッシュ済みのイメージを使って実行してみたい方は、DOKにて新しいタスクを作成し、以下の情報を入力してください。

| 項目     | 設定                                                          |
| -------- | ------------------------------------------------------------- |
| イメージ | dok-handson.sakuracr.jp/animagine                            |
| 環境変数 | PROMPT = 1girl, yellow long hair, dancing, cosplay, looking at viewer, smile, outdoors, night, v, masterpiece, high score, great score, absurdres<br />NEGATIVE = lowres, bad anatomy, bad hands, text, error, missing finger, extra digits, fewer digits, cropped, worst quality, low quality, low score, bad score, average score, signature, watermark, username, blurry<br />WIDTH = 1024<br />HEIGHT = 1024 |

PROMPTは生成する画像の指示内容です。NEGATIVEは、生成対象外にする情報、WIDTHとHEIGHTは画像のサイズです。

## コンテナイメージの作成と登録

上記タスクで利用したDockerイメージを作成する手順は以下の通りです。完成版は[goofmint/dok\-animagine](https://github.com/goofmint/dok-animagine)にありますので、実装時の参考にしてください。

### Dockerfile の作成

[cagliostrolab/animagine\-xl\-4\.0 · Hugging Face](https://huggingface.co/cagliostrolab/animagine-xl-4.0)の内容に沿って、Dockerfileを作成します。

#### ベースイメージ

ベースは `FROM nvidia/cuda:12.5.1-runtime-ubuntu22.04` です。

```Dockerfile
FROM nvidia/cuda:12.5.1-runtime-ubuntu22.04
```

#### ライブラリのインストール

Pythonと、必要なライブラリをインストールします。

```Dockerfile
RUN apt-get update && \
    apt-get install -y \
        git \
        python3 \
        python3-pip \
      && \
    mkdir /app /opt/artifact && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
# 依存ライブラリのインストール
RUN pip install -r requirements.txt
```

`requirements.txt` の内容は以下の通りです。公式サイトにあるものと、CLIのパラメーターのパーサー、そしてオブジェクトストレージ用ライブラリを追加しています。

```txt
diffusers
transformers
accelerate
safetensors
boto3
argparse
```

後は 後述する `runner.py` と、 `docker-entrypoint.sh` をコピーします。

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

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

### 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 \
        python3 \
        python3-pip \
      && \
    mkdir /app /opt/artifact && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

WORKDIR /app
COPY requirements.txt .
# 依存ライブラリのインストール
RUN pip install -r requirements.txt

COPY runner.py .
# 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コンテナー起動時に実行するスクリプトです。ここでは環境変数をチェックして、 `runner.py` を呼び出します。以下の内容で作成します。

```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 "${PROMPT:-}" ]; then
  echo "Environment variable PROMPT is not set" >&2
  exit 1
fi

# 画像生成用プロンプト（ネガティブ）
if [ -z "${NEGATIVE:-}" ]; then
  echo "Environment variable NEGATIVE is not set" >&2
  exit 1
fi

# 画像の幅
if [ -z "${WIDTH:-}" ]; then
  WIDTH=800
  exit 1
fi

# 画像の高さ
if [ -z "${HEIGHT:-}" ]; then
  HEIGHT=600
  exit 1
fi

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

### runner.py の作成

runner.py は実際に処理を行うスクリプトです。まず必要なライブラリをインポートします。

```python
import torch
from diffusers import StableDiffusionXLPipeline
import argparse
import boto3
```

#### パラメータの取得

`docker-entrypoint.sh` から渡されたパラメータを取得します。

```python
arg_parser = argparse.ArgumentParser()

arg_parser.add_argument(
    '--output',
    default='/opt/artifact',
    help='出力先ディレクトリを指定します。',
)
arg_parser.add_argument(
    '--prompt',
    default='',
    help='プロンプト',
)
arg_parser.add_argument(
    '--negative',
    default='',
    help='プロンプト（ネガティブ）',
)
arg_parser.add_argument(
    '--width',
    default=800,
    help='画像幅',
)
arg_parser.add_argument(
    '--height',
    default=600,
    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()
```

### オブジェクトストレージ用のオブジェクトを準備

S3_ではじまる環境変数があれば、それを使ってS3オブジェクトを作成します。

```python
s3 = None
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)
```

### Animagine XL 4.0の実行

公式サイトのドキュメントに沿って、実行部分を記述します。生成された画像を `/opt/artifact` （環境変数で `--output` にて受け取ったパス） に保存します。こうすると、実行後にDOKの管理画面でダウンロードできます。

```python
pipe = StableDiffusionXLPipeline.from_pretrained(
    "cagliostrolab/animagine-xl-4.0",
    torch_dtype=torch.float16,
    use_safetensors=True,
    custom_pipeline="lpw_stable_diffusion_xl",
    add_watermarker=False
)
pipe.to('cuda')
print(args)
image = pipe(
    args.prompt,
    negative_prompt=args.negative,
    width=int(args.width),
    height=int(args.height),
    guidance_scale=6,
    num_inference_steps=25
).images[0]

save_path = f'{args.output}/{args.id}.png'
image.save(save_path)
```

### オブジェクトストレージに保存

もし `s3` があれば、生成した画像をオブジェクトストレージに保存します。

```python
if s3 is not None:
    s3.upload_file(
        Filename=save_path,
        Bucket=args.s3_bucket,
        Key=os.path.basename(save_path))
```

### 全体の処理

`runner.py` の全体の処理は以下の通りです。

```python
import torch
from diffusers import StableDiffusionXLPipeline
import argparse
import boto3

arg_parser = argparse.ArgumentParser()

arg_parser.add_argument(
    '--output',
    default='/opt/artifact',
    help='出力先ディレクトリを指定します。',
)
arg_parser.add_argument(
    '--prompt',
    default='',
    help='プロンプト',
)
arg_parser.add_argument(
    '--negative',
    default='',
    help='プロンプト（ネガティブ）',
)
arg_parser.add_argument(
    '--width',
    default=800,
    help='画像幅',
)
arg_parser.add_argument(
    '--height',
    default=600,
    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()

s3 = None
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)

pipe = StableDiffusionXLPipeline.from_pretrained(
    "cagliostrolab/animagine-xl-4.0",
    torch_dtype=torch.float16,
    use_safetensors=True,
    custom_pipeline="lpw_stable_diffusion_xl",
    add_watermarker=False
)
pipe.to('cuda')
print(args)
image = pipe(
    args.prompt,
    negative_prompt=args.negative,
    width=int(args.width),
    height=int(args.height),
    guidance_scale=6,
    num_inference_steps=25
).images[0]

save_path = f'{args.output}/{args.id}.png'
image.save(save_path)

if s3 is not None:
    s3.upload_file(
        Filename=save_path,
        Bucket=args.s3_bucket,
        Key=os.path.basename(save_path))
```

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

上記の内容で、Dockerイメージをビルドします。Linux環境などで行います。

#### コンテナレジストリの用意

Dockerイメージを登録するコンテナレジストリを作成します。さくらのクラウドではLAB機能で、コンテナレジストリを提供しています。さくらのクラウドにログインしたら `さくらのクラウド` を選択します。

![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` として説明します |
| 公開設定             | Pullのみとします                                                          |

#### ユーザーの作成

コンテナレジストリを作成したら、作成したコンテナレジストリを一覧でダブルクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/6425f493-0ce1-db62-f322-64a51867ce58.png)

詳細表示にて、ユーザータブをクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/79bdc51c-ea66-8d8f-c801-8f54346cf960.png)

追加ボタンを押し、ユーザーを作成します。 `YOUR_USER_NAME` と `PASSWORD` は任意のものを指定してください。

| 項目           | 設定           |
| -------------- | -------------- |
| ユーザー名     | YOUR_USER_NAME |
| パスワード     | YOUR_PASSWORD  |
| ユーザ権限設定 | All            |

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

DockerイメージはLinuxで行います。今回はUbuntu 24.04を使っています。Dockerが使える環境であれば、Windows + WSL2でも問題ありません。macOSの場合、アーキテクチャが異なるので動かせないかも知れません（未検証です）。

`EXAMPLE.sakuracr.jp` の部分は、作成したコンテナレジストリのドメイン名に置き換えてください。また、 `animagine` は任意の名前で大丈夫です（以下はその名称で読み替えてください）。

```bash
sudo docker build -t EXAMPLE.sakuracr.jp/animagine:latest .
```

#### コンテナレジストリへのログイン

作成したコンテナレジストリにログインします。ログインIDとパスワードが求められるので、作成したものを入力してください。

```bash
sudo docker login EXAMPLE.sakuracr.jp
```

#### イメージのプッシュ

作成したイメージをコンテナレジストリにプッシュします。イメージサイズが大きいので、数十分かかります。

```bash
sudo docker push EXAMPLE.sakuracr.jp/animagine:latest
```

### タスクを作成する

後は最初と同じようにDOKでタスクを作成、実行します。

| 項目     | 設定                                                          |
| -------- | ------------------------------------------------------------- |
| イメージ | dok-handson.sakuracr.jp/animagine                            |
| 環境変数 | PROMPT = 1girl, yellow long hair, dancing, cosplay, looking at viewer, smile, outdoors, night, v, masterpiece, high score, great score, absurdres<br />NEGATIVE = lowres, bad anatomy, bad hands, text, error, missing finger, extra digits, fewer digits, cropped, worst quality, low quality, low score, bad score, average score, signature, watermark, username, blurry<br />WIDTH = 1024<br />HEIGHT = 1024 |

![FireShot Capture 393 - 高火力 DOK - タスク › 新規作成 - secure.sakura.ad.jp.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/bd0adada-1eee-3349-8a36-b9b3f11a1c69.jpeg)

実行が完了し、画像が生成できていれば成功です。

## まとめ

今回はAnimagine XL 4.0を使って、DOK上で画像生成を行いました。まずは実行できるのみ、次にDockerイメージの作成と段階的に進められるようにしています。画像生成のように、処理に時間がかかるものを利用する際にDOKは便利です。

DOKはタスクを多数立ち上げて、後は結果を待つのみと言った使い方ができます。ぜひAI・機械学習に活用してください。

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