DOKはコンテナー型のGPUサービスで、NVIDIA V100やH100を実行時間課金で利用できるサービスです。
コンテナー型GPUクラウドサービス 高火力 DOK(ドック) | さくらインターネット
今回はこのDOKを使って、CogVideoXにてテキストから動画を生成してみました。
結果
結果は以下のようになっています。ちょっと猫の指先が怪しい時もありますが、全体として崩壊が少ないんじゃないかと思います。
一瞬、猿の手みたいになる…
画像 + プロンプトの場合。写真は写真素材:雪の降る街角で暖かい飲み物を飲む女性より。
参考
動画生成AI【CogVideoX】をローカルで実行するを参考にさせてもらっています。
とりあえず試す
学習済みのデータを使って実行してみたい方は、DOKにて新しいタスクを作成し、以下の情報を入力してください。
項目 | 設定 |
---|---|
イメージ | dok-handson.sakuracr.jp/cog-video-x |
環境変数 | PROMPT=A black-and-white tiger-striped cat is staring fixedly at a round fish tank. Inside the tank are two orange goldfish, one large and one small. The cat puts its hand into the tank and tries to catch the goldfish. The goldfish jumps out of the water and escapes the cat's hand. The goldfish escapes the cat's hand and swims around in the tank again. TYPE=t2v |
PROMPTで指定したテキストに対して動画を生成します。t2vはテキストからビデオを生成することを示しています。他、IMAGEで画像を指定しつつ、TYPEをi2vにして画像から動画の生成もできます。IMAGEで動画を指定して、TYPEをv2vにすると動画から動画もできるようですが、試していません。
なお、プランはh100-80gbを指定してください。デフォルトのv100-32gbではメモリが足りないため、エラーが発生します。
コンテナイメージの作成と登録
上記タスクで利用したDockerイメージを作成する手順は以下の通りです。ベースとして、動画生成AI【CogVideoX】をローカルで実行するを参考にしつつ、DOK向けに改造しています。
完成版はgoofmint/dok-cog-video-xにありますので、実装時の参考にしてください。
requirements.txt の作成
DOKで実行する際に必要なライブラリを追加します。boto3は今回使っていませんが、できあがった動画を任意のオブジェクトストレージにアップロードする場合、利用できます。
transformers
accelerate
diffusers
imageio-ffmpeg
torch
sentencepiece
opencv-python
imageio
imageio-ffmpeg
boto3
Dockerfile の作成
Dockerfileの内容は以下の通りです。必要なライブラリをインストールし、ファイルをコピーしています。また、 requirements.txt
を使ってライブラリをインストールしています。
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 runner.py .
# 依存ライブラリのインストール
RUN pip install -r requirements.txt
# 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
を呼び出します。以下の内容で作成します。
#!/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 "${TYPE:-}" ]; then
TYPE="t2v"
fi
# S3_はすべてboto3用の環境変数です
pushd /app
python3 runner.py \
--id="${SAKURA_TASK_ID}" \
--output="${SAKURA_ARTIFACT_DIR}" \
--prompt="${PROMPT}" \
--type="${TYPE}" \
--image="${IMAGE:-}" \
--s3-bucket="${S3_BUCKET:-}" \
--s3-endpoint="${S3_ENDPOINT:-}" \
--s3-secret="${S3_SECRET:-}" \
--s3-token="${S3_TOKEN:-}"
popd
runner.py の作成
runner.py は実際に処理を行うスクリプトです。まず必要なライブラリをインポートします。
from typing import Literal
import argparse
import torch
from diffusers import (
CogVideoXPipeline,
CogVideoXDDIMScheduler,
CogVideoXDPMScheduler,
CogVideoXImageToVideoPipeline,
CogVideoXVideoToVideoPipeline,
)
from diffusers.utils import export_to_video, load_image, load_video
パラメータの取得
docker-entrypoint.sh
から渡されたパラメータを取得します。
arg_parser = argparse.ArgumentParser()
arg_parser.add_argument(
'--output',
default='/opt/artifact',
help='出力先ディレクトリを指定します。',
)
arg_parser.add_argument(
'--prompt',
default='',
help='プロンプト',
)
arg_parser.add_argument(
'--type',
default='t2v',
help='実行する処理',
)
arg_parser.add_argument(
'--image',
default='',
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()
動画生成処理を行う関数の追加
動画生成は generate_video
という関数で行います。
def generate_video(
prompt: str,
model_path: str,
lora_path: str = None,
lora_rank: int = 128,
output_path: str = "./output.mp4",
image_or_video_path: str = "",
num_inference_steps: int = 50,
guidance_scale: float = 6.0,
num_videos_per_prompt: int = 1,
strength: float = 0.8,
dtype: torch.dtype = torch.bfloat16,
generate_type: str = Literal["t2v", "i2v", "v2v"], # i2v: image to video, v2v: video to video
seed: int = 42,
):
image = None
video = None
print(image_or_video_path)
if generate_type == "i2v":
pipe = CogVideoXImageToVideoPipeline.from_pretrained(model_path, torch_dtype=dtype)
image = load_image(image=image_or_video_path)
elif generate_type == "t2v":
pipe = CogVideoXPipeline.from_pretrained(model_path, torch_dtype=dtype)
else:
pipe = CogVideoXVideoToVideoPipeline.from_pretrained(model_path, torch_dtype=dtype)
video = load_video(image_or_video_path)
# If you're using with lora, add this code
if lora_path:
pipe.load_lora_weights(lora_path, weight_name="pytorch_lora_weights.safetensors", adapter_name="test_1")
pipe.fuse_lora(lora_scale=1 / lora_rank)
pipe.scheduler = CogVideoXDPMScheduler.from_config(pipe.scheduler.config, timestep_spacing="trailing")
pipe.enable_sequential_cpu_offload()
#pipe.enable_model_cpu_offload()
pipe.vae.enable_slicing()
pipe.vae.enable_tiling()
if generate_type == "i2v":
video_generate = pipe(
prompt=prompt,
image=image, # The path of the image to be used as the background of the video
num_videos_per_prompt=num_videos_per_prompt, # Number of videos to generate per prompt
num_inference_steps=num_inference_steps, # Number of inference steps
num_frames=49, # Number of frames to generate,changed to 49 for diffusers version `0.30.3` and after.
use_dynamic_cfg=True, # This id used for DPM Sechduler, for DDIM scheduler, it should be False
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(seed), # Set the seed for reproducibility
).frames[0]
elif generate_type == "t2v":
video_generate = pipe(
prompt=prompt,
num_videos_per_prompt=num_videos_per_prompt,
num_inference_steps=num_inference_steps,
num_frames=49,
use_dynamic_cfg=True,
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(seed),
).frames[0]
else:
video_generate = pipe(
prompt=prompt,
video=video, # The path of the video to be used as the background of the video
num_videos_per_prompt=num_videos_per_prompt,
num_inference_steps=num_inference_steps,
# num_frames=49,
use_dynamic_cfg=True,
strength=strength,
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(seed), # Set the seed for reproducibility
).frames[0]
# 5. Export the generated frames to a video file. fps must be 8 for original video.
export_to_video(video_generate, output_path, fps=8)
生成に必要な情報の準備
generate_video
関数に必要な情報を準備します。
dtype = torch.bfloat16
generate_type = args.type # t2v: text to video, i2v: image to video, v2v: video
if not args.image:
image_or_video_path = args.image
lora_path = None
strength = 0.8
prompt=args.prompt
# モデルの指定
if generate_type == "t2v":
model_path = "THUDM/CogVideoX-5b"
elif generate_type == "i2v":
model_path = "THUDM/CogVideoX-5b-I2V"
else:
model_path = "THUDM/CogVideoX-5b"
output_path = f'{args.output}/output-{args.id}.mp4' # 動画の出力先
動画の生成
generate_video
関数を呼び出して、動画を生成します。
generate_video(
prompt=prompt,
model_path=model_path,
lora_path=lora_path,
lora_rank=128,
output_path=output_path,
image_or_video_path=image_or_video_path,
num_inference_steps=50,
strength=strength,
guidance_scale=6.0,
num_videos_per_prompt=1,
dtype=dtype,
generate_type=generate_type,
seed=42,
)
全体の処理
runner.py
の全体の処理は以下の通りです。
from typing import Literal
import argparse
# PyTorchをインポート
import torch
# Diffusersライブラリから必要なモジュールをインポート
from diffusers import (
CogVideoXPipeline,
CogVideoXDDIMScheduler,
CogVideoXDPMScheduler,
CogVideoXImageToVideoPipeline,
CogVideoXVideoToVideoPipeline,
)
from diffusers.utils import export_to_video, load_image, load_video
# コマンドライン引数を処理するためのArgumentParserを作成
arg_parser = argparse.ArgumentParser()
# 出力先ディレクトリを指定する引数
arg_parser.add_argument(
'--output',
default='/opt/artifact',
help='出力先ディレクトリを指定します。',
)
# 動画生成に使うプロンプト(説明文や指示文)を指定する引数
arg_parser.add_argument(
'--prompt',
default='',
help='プロンプト',
)
# 処理タイプ(テキストから動画など)を指定する引数
arg_parser.add_argument(
'--type',
default='t2v',
help='実行する処理',
)
# ベースとなる画像または動画を指定する引数
arg_parser.add_argument(
'--image',
default='',
help='ベースの画像(または動画)',
)
# タスクIDを指定する引数
arg_parser.add_argument(
'--id',
default='',
help='タスクIDを指定します。',
)
# S3関連の設定をする引数(オプション)
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()
# 動画を生成する関数
def generate_video(
prompt: str, # 動画生成の指示文
model_path: str, # 使用するモデルのパス
lora_path: str = None, # LoRAモデルのパス(オプション)
lora_rank: int = 128, # LoRAモデルのランク(圧縮度)
output_path: str = "./output.mp4", # 出力する動画のパス
image_or_video_path: str = "", # 入力画像または動画のパス
num_inference_steps: int = 50, # 推論ステップ数
guidance_scale: float = 6.0, # ガイダンススケール(生成内容の制御)
num_videos_per_prompt: int = 1, # プロンプトごとに生成する動画の数
strength: float = 0.8, # 元画像/動画の強度(v2v用)
dtype: torch.dtype = torch.bfloat16, # データ型
generate_type: str = Literal["t2v", "i2v", "v2v"], # 処理タイプ(テキストから動画など)
seed: int = 42, # 乱数シード(再現性のため)
):
# 入力画像や動画の初期化
image = None
video = None
print(image_or_video_path) # 入力パスを表示
# 処理タイプに応じて異なるパイプラインを使用
if generate_type == "i2v":
pipe = CogVideoXImageToVideoPipeline.from_pretrained(model_path, torch_dtype=dtype)
image = load_image(image=image_or_video_path) # 画像をロード
elif generate_type == "t2v":
pipe = CogVideoXPipeline.from_pretrained(model_path, torch_dtype=dtype)
else:
pipe = CogVideoXVideoToVideoPipeline.from_pretrained(model_path, torch_dtype=dtype)
video = load_video(image_or_video_path) # 動画をロード
# LoRAモデルを使用する場合の設定
if lora_path:
pipe.load_lora_weights(lora_path, weight_name="pytorch_lora_weights.safetensors", adapter_name="test_1")
pipe.fuse_lora(lora_scale=1 / lora_rank)
# スケジューラーの設定
pipe.scheduler = CogVideoXDPMScheduler.from_config(pipe.scheduler.config, timestep_spacing="trailing")
# メモリを効率的に使用する設定
pipe.enable_sequential_cpu_offload()
pipe.vae.enable_slicing()
pipe.vae.enable_tiling()
# 動画を生成
if generate_type == "i2v":
video_generate = pipe(
prompt=prompt,
image=image,
num_videos_per_prompt=num_videos_per_prompt,
num_inference_steps=num_inference_steps,
num_frames=49, # フレーム数
use_dynamic_cfg=True,
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(seed),
).frames[0]
elif generate_type == "t2v":
video_generate = pipe(
prompt=prompt,
num_videos_per_prompt=num_videos_per_prompt,
num_inference_steps=num_inference_steps,
num_frames=49,
use_dynamic_cfg=True,
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(seed),
).frames[0]
else:
video_generate = pipe(
prompt=prompt,
video=video,
num_videos_per_prompt=num_videos_per_prompt,
num_inference_steps=num_inference_steps,
use_dynamic_cfg=True,
strength=strength,
guidance_scale=guidance_scale,
generator=torch.Generator().manual_seed(seed),
).frames[0]
# 生成されたフレームを動画ファイルにエクスポート
export_to_video(video_generate, output_path, fps=8)
# データ型と処理タイプの設定
dtype = torch.bfloat16
generate_type = args.type # 't2v': テキストから動画, 'i2v': 画像から動画, 'v2v': 動画から動画
# 入力画像または動画のパスを取得
if not args.image:
image_or_video_path = args.image
# モデルやプロンプトの設定
lora_path = None
strength = 0.8
prompt = args.prompt
# 処理タイプに応じて異なるモデルを使用
if generate_type == "t2v":
model_path = "THUDM/CogVideoX-5b"
elif generate_type == "i2v":
model_path = "THUDM/CogVideoX-5b-I2V"
else:
model_path = "THUDM/CogVideoX-5b"
# 出力ファイルパスを設定
output_path = f'{args.output}/output-{args.id}.mp4'
# 動画生成の開始
print(f"Generating video")
generate_video(
prompt=prompt,
model_path=model_path,
lora_path=lora_path,
lora_rank=128,
output_path=output_path,
image_or_video_path=image_or_video_path,
num_inference_steps=50,
strength=strength,
guidance_scale=6.0,
num_videos_per_prompt=1,
dtype=dtype,
generate_type=generate_type,
seed=42,
)
Dockerイメージのビルド
上記の内容で、Dockerイメージをビルドします。Linux環境などで行います。
コンテナレジストリの用意
Dockerイメージを登録するコンテナレジストリを作成します。さくらのクラウドではLAB機能で、コンテナレジストリを提供しています。さくらのクラウドにログインしたら さくらのクラウド
を選択します。
左側のメニューの LAB
の中にある コンテナレジストリ
を選択します。
追加
を押して、コンテナレジストリを作成します。最低限、以下の入力が必要です。
項目 | 設定 |
---|---|
名前 | 分かりやすい、任意の名前を入力してください |
コンテナレジストリ名 | ドメイン名に使われます。以下では、 EXAMPLE.sakuracr.jp として説明します |
公開設定 | Pullのみとします |
ユーザーの作成
コンテナレジストリを作成したら、作成したコンテナレジストリを一覧でダブルクリックします。
詳細表示にて、ユーザータブをクリックします。
追加ボタンを押し、ユーザーを作成します。 YOUR_USER_NAME
と PASSWORD
は任意のものを指定してください。
項目 | 設定 |
---|---|
ユーザー名 | YOUR_USER_NAME |
パスワード | YOUR_PASSWORD |
ユーザ権限設定 | All |
Dockerイメージのビルド
DockerイメージはLinuxで行います。今回はUbuntu 24.04を使っています。Dockerが使える環境であれば、Windows + WSL2でも問題ありません。macOSの場合、アーキテクチャが異なるので動かせないかも知れません(未検証です)。
EXAMPLE.sakuracr.jp
の部分は、作成したコンテナレジストリのドメイン名に置き換えてください。また、 cog-video-x
は任意の名前で大丈夫です(以下はその名称で読み替えてください)。
sudo docker build -t EXAMPLE.sakuracr.jp/cog-video-x:latest .
コンテナレジストリへのログイン
作成したコンテナレジストリにログインします。ログインIDとパスワードが求められるので、作成したものを入力してください。
sudo docker login EXAMPLE.sakuracr.jp
イメージのプッシュ
作成したイメージをコンテナレジストリにプッシュします。イメージサイズが大きいので、数十分かかります。
sudo docker push EXAMPLE.sakuracr.jp/cog-video-x:latest
タスクを作成する
後は最初と同じようにDOKでタスクを作成、実行します。
項目 | 設定 |
---|---|
イメージ | EXAMPLE.sakuracr.jp/cog-video-x |
環境変数 | PROMPT=A black-and-white tiger-striped cat is staring fixedly at a round fish tank. Inside the tank are two orange goldfish, one large and one small. The cat puts its hand into the tank and tries to catch the goldfish. The goldfish jumps out of the water and escapes the cat's hand. The goldfish escapes the cat's hand and swims around in the tank again. TYPE=t2v |
まとめ
今回はCogVideoXを使って、DOK上で動画を作成する手順を解説しました。まずは実行できるのみ、次にDockerイメージの作成と段階的に進められるようにしています。ぜひ、試してみてください。
DOKはタスクを多数立ち上げて、後は結果を待つのみと言った使い方ができます。ぜひAI・機械学習に活用してください。