【云驻共创】华为云之玩转云端文生视频

举报
愚公搬代码 发表于 2024/08/14 17:29:38 2024/08/14
【摘要】 在当今数字化时代,视频内容已成为信息传播和创意表达的主要媒介之一。随着人工智能技术的飞速发展,文生视频(Text-to-Video)这一前沿技术也逐渐走入大众视野。通过简单的文字描述生成高质量的视频,这样的技术不仅能够极大地提升内容创作效率,还能让更多人享受到创作的乐趣。

🏆 作者简介,愚公搬代码
🏆《头衔》:华为云特约编辑,华为云云享专家,华为开发者专家,华为产品云测专家,CSDN博客专家,CSDN商业化专家,阿里云专家博主,阿里云签约作者,腾讯云优秀博主,腾讯云内容共创官,掘金优秀博主,亚马逊技领云博主,51CTO博客专家等。
🏆《近期荣誉》:2022年度博客之星TOP2,2023年度博客之星TOP2,2022年华为云十佳博主,2023年华为云十佳博主等。
🏆《博客内容》:.NET、Java、Python、Go、Node、前端、IOS、Android、鸿蒙、Linux、物联网、网络安全、大数据、人工智能、U3D游戏、小程序等相关领域知识。
🏆🎉欢迎 👍点赞✍评论⭐收藏

🚀前言

在当今数字化时代,视频内容已成为信息传播和创意表达的主要媒介之一。随着人工智能技术的飞速发展,文生视频(Text-to-Video)这一前沿技术也逐渐走入大众视野。通过简单的文字描述生成高质量的视频,这样的技术不仅能够极大地提升内容创作效率,还能让更多人享受到创作的乐趣。

本文将带你深入了解云端文生视频的实现原理和应用场景。无论你是AI技术爱好者、开发者,还是希望提升内容创作能力的从业者,本篇文章都将为你提供宝贵的见解和实用的技巧。

🚀一、华为云之玩转云端文生视频

🔎1.相关概念

🦋1.1 华为云 ModelArts

☀️1.1.1 ModelArts 功能介绍

华为云 ModelArts 是一个一站式的 AI 开发平台,旨在简化和加速人工智能模型的开发与部署。它提供了从数据准备、算法开发、模型训练到 AI 应用打包部署的全流程支持,使开发者能够轻松地构建、训练和部署 AI 模型。ModelArts 集成了强大的数据处理能力和先进的算法库,适用于各类 AI 场景,包括机器学习、深度学习、以及自动化的 AI 服务。此外,ModelArts 还支持大规模并行训练和模型自动调优,帮助开发者更快地将 AI 模型应用于实际生产环境。
在这里插入图片描述

🦋1.2 AIGC文生视频大模型 Open-Sora

☀️1.2.1 Open-Sora 相关概念

Open-Sora 是由 Colossal-AI 团队推出的开源解决方案,专注于高效的视频生成。该方案迅速走红,凭借其强大的视频生成效果在众多文本转视频模型中脱颖而出。Open-Sora 的训练过程分为三个主要阶段:

  1. 大规模图像预训练:借助成熟的文生图模型,通过大量图像的预训练,有效降低了视频预训练的成本。

  2. 大规模视频预训练:通过对大规模视频数据的预训练,增强了模型的泛化能力,并有效与视频的时间序列相关联。

  3. 高质量视频数据微调:对高质量的视频数据进行微调,显著提高了生成视频的质量。

Open-Sora 的 1.0 版本已于 2024 年 3 月发布,包含了整个训练过程的详细内容,包括数据处理、训练细节和模型检查点。随后在 2024 年 4 月发布的 Open-Sora 1.1 版本,进一步扩展了功能,支持生成 2 秒至 15 秒、144p 至 720p 分辨率的视频,且支持文本到图像、文本到视频和图像到视频的生成。

☀️1.2.2 Open-Sora 的技术原理

🌈1.2.2.1 模型的架构设计

Open-Sora 模型的整体架构设计由以下主要组件构成:

  1. 预训练模型 VAE (Variational Autoencoder)

    • 作为模型的基础,VAE 负责将输入数据(如图像或视频帧)编码为一个低维的潜在表示空间。通过这种方式,VAE 能够有效地捕捉数据的基本特征,并在生成过程中为扩散模型提供初始条件。
  2. 文本编码器

    • 文本编码器的作用是将输入的文本描述编码为高维语义向量,这些向量包含了与文本相关的丰富语义信息。这些编码后的语义向量将作为生成视频的指导信息,与 VAE 生成的潜在表示一起,传递给扩散模型进行进一步处理。
  3. STDiT (Spatial Temporal Diffusion Transformer) 模型

    • STDiT 是 Open-Sora 的核心生成模型,它融合了扩散模型和时空自注意力机制。这个模型不仅能够处理空间维度上的图像生成,还能够捕捉视频的时间维度动态变化。
    • 扩散模型:负责从随机噪声中逐步生成视频帧,通过多次迭代去噪,最终生成高质量的视频。扩散模型的逐步生成过程确保了视频的每一帧都具有细腻的细节。
    • 时空自注意力机制:该机制增强了模型对视频时间序列特征的建模能力,使得生成的视频在时间维度上更加连贯和自然。同时,自注意力机制也提升了模型在处理复杂场景和动态变化时的表现。

通过这三大组件的紧密协作,Open-Sora 能够高效地将文本描述或图像输入转化为高质量、连贯的短视频内容。VAE 提供了强大的表示能力,文本编码器确保了语义信息的准确传递,而 STDiT 模型则在视频生成过程中发挥了关键作用,确保生成的视频既具有视觉美感又在时间上合理连贯。
在这里插入图片描述

🌈1.2.2.2 模型的实现流程

在这里插入图片描述
整个模型的训练和推理流程如上图所示。Open-Sora 模型的实现流程可以分为训练阶段和推理阶段,具体如下:

训练阶段

  1. 数据预处理与编码

    • 首先,使用预训练模型 VAE 的编码器对输入的视频数据进行压缩,将高维的视频帧数据转换为低维的潜在表示空间中的特征向量。
    • 同时,将文本描述通过文本编码器转化为文本向量。
  2. 潜在空间嵌入与联合训练

    • 将编码后的视频特征向量与文本向量一起嵌入到潜在空间中,这些嵌入向量包含了视频的空间和时间信息以及文本的语义信息。
    • 通过联合训练 STDiT(Spatial Temporal Diffusion Transformer)模型,模型学习如何在时空维度上生成符合文本描述的高质量视频。STDiT 利用扩散模型和时空自注意力机制,逐步生成和调整视频的特征表示。

推理阶段

  1. 高斯噪声采样

    • 在推理阶段,从 VAE 的潜在空间中随机采样高斯噪声,这个噪声表示模型生成视频的初始条件。
  2. 提示词嵌入与去噪处理

    • 将采样得到的高斯噪声与提示词(即文本描述)嵌入一起输入到 STDiT 模型中。STDiT 模型利用扩散过程中的去噪步骤,逐步将噪声转化为更加清晰的特征表示,这些特征表示包含了视频帧的时空信息。
  3. 解码生成视频

    • 最后,将去噪后的特征向量输入到 VAE 的解码器中,解码器将这些特征向量转换回视频帧,生成最终的输出视频。

Open-Sora 模型能够有效地将文本或图像输入转化为高质量的视频内容,并确保生成的视频在视觉上具有一致性和连贯性。

🌈1.2.2.3 模型的训练方法

open-Sora采用了一种多阶段的训练方法,每个阶段都会基于前一个阶段的权重继续训练。相较于单一阶段训练,这种多阶段训练通过分步骤引入数据,更高效地实现了高质量视频生成的目标。

Open-Sora 的训练参考了 Stable Video Diffusion 的工作,总共包含三个阶段:

  1. 第一阶段:大规模图像预训练。借助成熟的文生图模型,通过大规模图像预训练,有效降低视频预训练的成本。

  2. 第二阶段:大规模视频预训练。通过大规模视频预训练,提高模型的泛化能力,有效与视频的时间序列相关联。

  3. 第三阶段:高质量视频数据微调。通过对高质量视频数据进行微调,显著提高生成视频的质量。

训练数据的数量和质量对生成视频效果有很大的影响,有时甚至比模型架构和训练策略更加重要。
在这里插入图片描述

🦋1.3 Al Gallery

AI Gallery 是一个平台,专门为人工智能和机器学习模型提供展示、分享和应用的空间。通常,它允许开发者上传、分享他们开发的 AI 模型,并使其他用户能够浏览、测试、下载和使用这些模型。AI Gallery 的主要功能包括:

  1. 模型展示与分享:开发者可以将自己训练的 AI 模型上传到 AI Gallery,与社区分享。其他用户可以浏览这些模型,查看详细的模型信息和使用说明。

  2. 模型应用:用户可以直接在平台上应用这些模型,测试其效果,或将模型集成到自己的项目中。

  3. 模型版本控制:AI Gallery 通常支持模型的版本控制,使开发者可以更新和维护他们的模型版本,并让用户能够选择最适合的版本进行使用。

  4. 社区互动:AI Gallery 平台往往还支持社区互动,用户可以对模型进行评论、评分,提出建议或问题,促进开发者和用户之间的交流。

  5. 模型资源库:AI Gallery 作为一个资源库,汇集了来自全球各地开发者的各种模型,涵盖了不同的应用场景,如计算机视觉、自然语言处理、推荐系统等。

通过 AI Gallery,开发者能够更容易地分享他们的工作成果,而用户则可以轻松获取和应用最新的 AI 技术。

☀️1.3.1 定位:精品昇腾云AI资产与AI解决方案交易市场

AI Gallery 的定位是作为一个精品昇腾云 AI 资产与 AI 解决方案交易市场。具体来说,它致力于提供高质量的 AI 模型、算法、工具和完整解决方案,帮助企业和开发者更便捷地获取和部署 AI 技术。其核心目标包括:

核心目标 详细内容
优质 AI 资产 汇集高质量的昇腾云 AI 模型和算法,满足企业 AI 开发需求
AI 解决方案市场 提供完整的 AI 解决方案,涵盖从数据处理到应用部署的全流程
交易平台 促进 AI 资产和解决方案的交易,推动 AI 生态系统的发展

通过这种定位,AI Gallery 成为一个连接 AI 技术供需双方的重要平台,为企业数字化转型和智能化升级提供强有力的支持。
在这里插入图片描述

☀️1.3.2 开发体验:流畅模型开发体验,微调及AI应用,提升大模型开发效率

开发体验 详细内容
流畅模型开发体验 提供一站式平台,简化模型开发流程,使开发者能够快速构建和测试模型
微调及 AI 应用 支持模型微调功能,方便开发者根据具体需求调整模型,并快速应用于实际场景
提升大模型开发效率 提供优化工具和资源,提升大规模模型开发的效率,缩短开发周期

在这里插入图片描述

🔎2.案例体验

案例地址:https://developer.huaweicloud.cn/develop/aigallery/notebook/detail?id=e0e9ffd1-8888-4220-8144-bdf2c19f3fe5&ticket=ST-8969109-EllFfK0cly1fyqltdiCBFWJL-sso&locale=zh-cn
在这里插入图片描述

🦋2.1 下载代码和模型

此处运行大约需要1分钟,请耐心等待!

import os
import moxing as mox

if not os.path.exists('Open-Sora'):
    mox.file.copy_parallel('obs://modelbox-course/open-sora_1.1/Open-Sora', 'Open-Sora')
    
if not os.path.exists('/home/ma-user/.cache/huggingface'):
    mox.file.copy_parallel('obs://modelbox-course/huggingface', '/home/ma-user/.cache/huggingface')
    
if not os.path.exists('Open-Sora/opensora/models/sd-vae-ft-ema'):
    mox.file.copy_parallel('obs://modelbox-course/sd-vae-ft-ema', 'Open-Sora/opensora/models/sd-vae-ft-ema')

if not os.path.exists('Open-Sora/opensora/models/text_encoder/t5-v1_1-xxl'):
    mox.file.copy_parallel('obs://modelbox-course/t5-v1_1-xxl', 'Open-Sora/opensora/models/text_encoder/t5-v1_1-xxl')
    
if not os.path.exists('/home/ma-user/work/t5.py'):
    mox.file.copy_parallel('obs://modelbox-course/open-sora_1.1/t5.py', '/home/ma-user/work/t5.py')
    
if not os.path.exists('Open-Sora/opus-mt-zh-en'):
    mox.file.copy_parallel('obs://modelarts-labs-bj4-v2/course/ModelBox/opus-mt-zh-en', 'Open-Sora/opus-mt-zh-en')
    
if not os.path.exists('/home/ma-user/work/frpc_linux_amd64'):
    mox.file.copy_parallel('obs://modelarts-labs-bj4-v2/course/ModelBox/frpc_linux_amd64', '/home/ma-user/work/frpc_linux_amd64')

在这里插入图片描述

🦋2.2 配置运行环境

本案例依赖Python3.10.10及以上环境,因此我们首先创建虚拟环境:

!/home/ma-user/anaconda3/bin/conda clean -i
!/home/ma-user/anaconda3/bin/conda create -n python-3.10.10 python=3.10.10 -y --override-channels --channel https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
!/home/ma-user/anaconda3/envs/python-3.10.10/bin/pip install ipykernel

在这里插入图片描述

import json
import os

data = {
   "display_name": "python-3.10.10",
   "env": {
      "PATH": "/home/ma-user/anaconda3/envs/python-3.10.10/bin:/home/ma-user/anaconda3/envs/python-3.7.10/bin:/modelarts/authoring/notebook-conda/bin:/opt/conda/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/ma-user/modelarts/ma-cli/bin:/home/ma-user/modelarts/ma-cli/bin:/home/ma-user/anaconda3/envs/PyTorch-1.8/bin"
   },
   "language": "python",
   "argv": [
      "/home/ma-user/anaconda3/envs/python-3.10.10/bin/python",
      "-m",
      "ipykernel",
      "-f",
      "{connection_file}"
   ]
}

if not os.path.exists("/home/ma-user/anaconda3/share/jupyter/kernels/python-3.10.10/"):
    os.mkdir("/home/ma-user/anaconda3/share/jupyter/kernels/python-3.10.10/")

with open('/home/ma-user/anaconda3/share/jupyter/kernels/python-3.10.10/kernel.json', 'w') as f:
    json.dump(data, f, indent=4)
conda env list

在这里插入图片描述
创建完成后,稍等片刻,或刷新页面,点击右上角kernel选择python-3.10.10
在这里插入图片描述
查看Python版本和检查可用GPU,至少需要32GB显存

!python -V
!nvidia-smi

在这里插入图片描述
安装依赖包

!pip install --upgrade pip
!pip install torch==2.0.1 torchvision==0.15.2 torchaudio==2.0.2 xformers==0.0.22

在这里插入图片描述

%cd Open-Sora

在这里插入图片描述

!pip install colossalai==0.3.6 accelerate==0.29.2 diffusers==0.27.2 ftfy==6.2.0 gdown==5.1.0 mmengine==0.10.3 pre-commit==3.7.0 pyav==12.0.5 tensorboard==2.16.2 timm==0.9.16 transformers==4.39.3 wandb==0.16.6

在这里插入图片描述

!pip install .

在这里插入图片描述

!pip install spaces gradio MoviePy numpy==1.26.4 -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn
!cp /home/ma-user/work/frpc_linux_amd64 /home/ma-user/anaconda3/envs/python-3.10.10/lib/python3.10/site-packages/gradio/frpc_linux_amd64_v0.2
!chmod +x /home/ma-user/anaconda3/envs/python-3.10.10/lib/python3.10/site-packages/gradio/frpc_linux_amd64_v0.2

在这里插入图片描述

🦋2.3 生成视频

修改模型配置文件:

%%writefile configs/opensora-v1-1/inference/sample.py
num_frames = 16
frame_interval = 3
fps = 24
image_size = (240, 426)
multi_resolution = "STDiT2"

# Define model
model = dict(
    type="STDiT2-XL/2",
    from_pretrained="hpcai-tech/OpenSora-STDiT-v2-stage3",
    input_sq_size=512,  # 使用huggingface上下载好的模型权重
    qk_norm=True,
    enable_flash_attn=True,
    enable_layernorm_kernel=True,
)
vae = dict(
    type="VideoAutoencoderKL",
    from_pretrained="./opensora/models/sd-vae-ft-ema",
    cache_dir=None,  # 修改为从当前目录加载
    micro_batch_size=4,
)
text_encoder = dict(
    type="t5",
    from_pretrained="./opensora/models/text_encoder/t5-v1_1-xxl",
    cache_dir=None,  # 修改为从当前目录加载
    model_max_length=200,
)
scheduler = dict(
    type="iddpm",
    num_sampling_steps=100,
    cfg_scale=7.0,
    cfg_channel=3,  # or None
)
dtype = "fp16"

# Condition
prompt_path = "./assets/texts/t2v_samples.txt"
prompt = None  # prompt has higher priority than prompt_path

# Others
batch_size = 1
seed = 42
save_dir = "./samples/samples/"

在这里插入图片描述

import os

os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
!cp /home/ma-user/work/t5.py /home/ma-user/anaconda3/envs/python-3.10.10/lib/python3.10/site-packages/opensora/models/text_encoder/t5.py
# text to video
!python scripts/inference.py configs/opensora-v1-1/inference/sample.py --prompt "A beautiful sunset over the city" --num-frames 32 --image-size 240 426

在这里插入图片描述
生成的视频保存在Open-Sora/samples文件夹中,随机查看:

import os
import random
from moviepy.editor import *
from IPython.display import Image

# 视频存放目录
video_root = 'samples/samples'
# 列出所有文件
videos = os.listdir(video_root)
# 随机抽取视频
video = random.sample(videos, 1)[0]
# 视频输入路径
video_path = os.path.join(video_root, video)
# 加载原始视频
clip = VideoFileClip(video_path)
# 保存为GIF文件
clip.write_gif("output_animation.gif", fps=10)
# 显示生成结果
Image(open('output_animation.gif','rb').read())

在这里插入图片描述
至此案例就结束了,下面是 Gradio 界面介绍。

🦋2.4 Gradio 界面

修改配置文件:

%%writefile configs/opensora-v1-1/inference/sample-ref.py
num_frames = 16
frame_interval = 3
fps = 24
image_size = (240, 426)
multi_resolution = "STDiT2"

# Condition
prompt_path = None
prompt = [
    "A car driving on the ocean.",
    "In an ornate, historical hall, a massive tidal wave peaks and begins to crash. Two surfers, seizing the moment, skillfully navigate the face of the wave.",
]

loop = 2
condition_frame_length = 4
# (
#   loop id, [the loop index of the condition image or video]
#   reference id, [the index of the condition image or video in the reference_path]
#   reference start, [the start frame of the condition image or video]
#   target start, [the location to insert]
#   length, [the number of frames to insert]
#   edit_ratio [the edit rate of the condition image or video]
# )
# See https://github.com/hpcaitech/Open-Sora/blob/main/docs/config.md#advanced-inference-config for more details
# See https://github.com/hpcaitech/Open-Sora/blob/main/docs/commands.md#inference-with-open-sora-11 for more examples
mask_strategy = [
    "0,0,0,0,8,0.3",
    None,
    "0",
]
reference_path = [
    "https://cdn.openai.com/tmp/s/interp/d0.mp4",
    None,
    "assets/images/condition/wave.png",
]

# Define model
model = dict(
    type="STDiT2-XL/2",
    from_pretrained="hpcai-tech/OpenSora-STDiT-v2-stage3",
    input_sq_size=512,  # 使用huggingface上下载好的模型权重
    qk_norm=True,
    enable_flash_attn=True,
    enable_layernorm_kernel=True,
)
vae = dict(
    type="VideoAutoencoderKL",
    from_pretrained="./opensora/models/sd-vae-ft-ema",
    cache_dir=None,  # 修改为从当前目录加载
    micro_batch_size=4,
)
text_encoder = dict(
    type="t5",
    from_pretrained="./opensora/models/text_encoder/t5-v1_1-xxl",
    cache_dir=None,  # 修改为从当前目录加载
    model_max_length=200,
)
scheduler = dict(
    type="iddpm",
    num_sampling_steps=100,
    cfg_scale=7.0,
    cfg_channel=3,  # or None
)
dtype = "fp16"

# Others
batch_size = 1
seed = 42
save_dir = "./samples/samples/"

修改Gradio应用:

%%writefile gradio/app-ref.py
import argparse
import importlib
import os
import subprocess
import sys
import re
import json
import math
import spaces
import torch
import gradio as gr
from tempfile import NamedTemporaryFile
import datetime
from transformers import pipeline

zh2en = pipeline("translation", model="./opus-mt-zh-en")

MODEL_TYPES = ["v1.1-stage2", "v1.1-stage3"]
CONFIG_MAP = {
    "v1.1-stage2": "configs/opensora-v1-1/inference/sample-ref.py",
    "v1.1-stage3": "configs/opensora-v1-1/inference/sample-ref.py",
}
HF_STDIT_MAP = {
    "v1.1-stage2": "hpcai-tech/OpenSora-STDiT-v2-stage2",
    "v1.1-stage3": "hpcai-tech/OpenSora-STDiT-v2-stage3",
}
RESOLUTION_MAP = {
    "144p": {
        "16:9": (256, 144), 
        "9:16": (144, 256),
        "4:3": (221, 165),
        "3:4": (165, 221),
        "1:1": (192, 192),
    },
    "240p": {
        "16:9": (426, 240), 
        "9:16": (240, 426),
        "4:3": (370, 278),
        "3:4": (278, 370),
        "1:1": (320, 320),
    },
    "360p": {
        "16:9": (640, 360), 
        "9:16": (360, 640),
        "4:3": (554, 416),
        "3:4": (416, 554),
        "1:1": (480, 480),
    },
    "480p": {
        "16:9": (854, 480), 
        "9:16": (480, 854),
        "4:3": (740, 555),
        "3:4": (555, 740),
        "1:1": (640, 640),
    },
    "720p": {
        "16:9": (1280, 720), 
        "9:16": (720, 1280),
        "4:3": (1108, 832),
        "3:4": (832, 1110),
        "1:1": (960, 960),
    },
}


# ============================
# Utils
# ============================
def collect_references_batch(reference_paths, vae, image_size):
    from opensora.datasets.utils import read_from_path

    refs_x = []
    for reference_path in reference_paths:
        if reference_path is None:
            refs_x.append([])
            continue
        ref_path = reference_path.split(";")
        ref = []
        for r_path in ref_path:
            r = read_from_path(r_path, image_size, transform_name="resize_crop")
            r_x = vae.encode(r.unsqueeze(0).to(vae.device, vae.dtype))
            r_x = r_x.squeeze(0)
            ref.append(r_x)
        refs_x.append(ref)
    # refs_x: [batch, ref_num, C, T, H, W]
    return refs_x


def process_mask_strategy(mask_strategy):
    mask_batch = []
    mask_strategy = mask_strategy.split(";")
    for mask in mask_strategy:
        mask_group = mask.split(",")
        assert len(mask_group) >= 1 and len(mask_group) <= 6, f"Invalid mask strategy: {mask}"
        if len(mask_group) == 1:
            mask_group.extend(["0", "0", "0", "1", "0"])
        elif len(mask_group) == 2:
            mask_group.extend(["0", "0", "1", "0"])
        elif len(mask_group) == 3:
            mask_group.extend(["0", "1", "0"])
        elif len(mask_group) == 4:
            mask_group.extend(["1", "0"])
        elif len(mask_group) == 5:
            mask_group.append("0")
        mask_batch.append(mask_group)
    return mask_batch


def apply_mask_strategy(z, refs_x, mask_strategys, loop_i):
    masks = []
    for i, mask_strategy in enumerate(mask_strategys):
        mask = torch.ones(z.shape[2], dtype=torch.float, device=z.device)
        if mask_strategy is None:
            masks.append(mask)
            continue
        mask_strategy = process_mask_strategy(mask_strategy)
        for mst in mask_strategy:
            loop_id, m_id, m_ref_start, m_target_start, m_length, edit_ratio = mst
            loop_id = int(loop_id)
            if loop_id != loop_i:
                continue
            m_id = int(m_id)
            m_ref_start = int(m_ref_start)
            m_length = int(m_length)
            m_target_start = int(m_target_start)
            edit_ratio = float(edit_ratio)
            ref = refs_x[i][m_id]  # [C, T, H, W]
            if m_ref_start < 0:
                m_ref_start = ref.shape[1] + m_ref_start
            if m_target_start < 0:
                # z: [B, C, T, H, W]
                m_target_start = z.shape[2] + m_target_start
            z[i, :, m_target_start : m_target_start + m_length] = ref[:, m_ref_start : m_ref_start + m_length]
            mask[m_target_start : m_target_start + m_length] = edit_ratio
        masks.append(mask)
    masks = torch.stack(masks)
    return masks


def process_prompts(prompts, num_loop):
    from opensora.models.text_encoder.t5 import text_preprocessing

    ret_prompts = []
    for prompt in prompts:
        if prompt.startswith("|0|"):
            prompt_list = prompt.split("|")[1:]
            text_list = []
            for i in range(0, len(prompt_list), 2):
                start_loop = int(prompt_list[i])
                text = prompt_list[i + 1]
                text = text_preprocessing(text)
                end_loop = int(prompt_list[i + 2]) if i + 2 < len(prompt_list) else num_loop
                text_list.extend([text] * (end_loop - start_loop))
            assert len(text_list) == num_loop, f"Prompt loop mismatch: {len(text_list)} != {num_loop}"
            ret_prompts.append(text_list)
        else:
            prompt = text_preprocessing(prompt)
            ret_prompts.append([prompt] * num_loop)
    return ret_prompts


def extract_json_from_prompts(prompts):
    additional_infos = []
    ret_prompts = []
    for prompt in prompts:
        parts = re.split(r"(?=[{\[])", prompt)
        assert len(parts) <= 2, f"Invalid prompt: {prompt}"
        ret_prompts.append(parts[0])
        if len(parts) == 1:
            additional_infos.append({})
        else:
            additional_infos.append(json.loads(parts[1]))
    return ret_prompts, additional_infos


# ============================
# Model-related
# ============================
def read_config(config_path):
    """
    Read the configuration file.
    """
    from mmengine.config import Config

    return Config.fromfile(config_path)


def build_models(model_type, config, enable_optimization=False):
    """
    Build the models for the given model type and configuration.
    """
    # build vae
    from opensora.registry import MODELS, build_module

    vae = build_module(config.vae, MODELS).cuda()

    # build text encoder
    text_encoder = build_module(config.text_encoder, MODELS)  # T5 must be fp32
    text_encoder.t5.model = text_encoder.t5.model.cuda()

    # build stdit
    # we load model from HuggingFace directly so that we don't need to
    # handle model download logic in HuggingFace Space
    from opensora.models.stdit.stdit2 import STDiT2

    stdit = STDiT2.from_pretrained(
        HF_STDIT_MAP[model_type],
        enable_flash_attn=enable_optimization,
        trust_remote_code=True,
    ).cuda()

    # build scheduler
    from opensora.registry import SCHEDULERS

    scheduler = build_module(config.scheduler, SCHEDULERS)

    # hack for classifier-free guidance
    text_encoder.y_embedder = stdit.y_embedder

    # move modelst to device
    vae = vae.to(torch.float16).eval()
    text_encoder.t5.model = text_encoder.t5.model.eval()  # t5 must be in fp32
    stdit = stdit.to(torch.float16).eval()

    # clear cuda
    torch.cuda.empty_cache()
    return vae, text_encoder, stdit, scheduler


def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--model-type",
        default="v1.1-stage3",
        choices=MODEL_TYPES,
        help=f"The type of model to run for the Gradio App, can only be {MODEL_TYPES}",
    )
    parser.add_argument("--output", default="./outputs", type=str, help="The path to the output folder")
    parser.add_argument("--port", default=None, type=int, help="The port to run the Gradio App on.")
    parser.add_argument("--host", default=None, type=str, help="The host to run the Gradio App on.")
    parser.add_argument("--share", action="store_true", help="Whether to share this gradio demo.")
    parser.add_argument(
        "--enable-optimization",
        action="store_true",
        help="Whether to enable optimization such as flash attention and fused layernorm",
    )
    return parser.parse_args()


# ============================
# Main Gradio Script
# ============================
# as `run_inference` needs to be wrapped by `spaces.GPU` and the input can only be the prompt text
# so we can't pass the models to `run_inference` as arguments.
# instead, we need to define them globally so that we can access these models inside `run_inference`

# read config
args = parse_args()
config = read_config(CONFIG_MAP[args.model_type])

# make outputs dir
os.makedirs(args.output, exist_ok=True)

# disable torch jit as it can cause failure in gradio SDK
# gradio sdk uses torch with cuda 11.3
torch.jit._state.disable()

# import after installation
from opensora.datasets import IMG_FPS, save_sample
from opensora.utils.misc import to_torch_dtype

# some global variables
dtype = to_torch_dtype(config.dtype)
device = torch.device("cuda")

# build model
vae, text_encoder, stdit, scheduler = build_models(args.model_type, config, enable_optimization=args.enable_optimization)


def run_inference(mode, prompt_text, resolution, aspect_ratio, length, reference_image, seed, sampling_steps, cfg_scale):
    torch.manual_seed(seed)
    with torch.inference_mode():
        # ======================
        # 1. Preparation
        # ======================
        # parse the inputs
        resolution = RESOLUTION_MAP[resolution][aspect_ratio]

        # gather args from config
        num_frames = config.num_frames
        frame_interval = config.frame_interval
        fps = config.fps
        condition_frame_length = config.condition_frame_length

        # compute number of loops
        if mode == "Text2Image":
            num_frames = 1
            num_loop = 1
        else:
            num_seconds = int(length.rstrip('s'))
            if num_seconds <= 16:
                num_frames = num_seconds * fps // frame_interval
                num_loop = 1
            else:
                config.num_frames = 16
                total_number_of_frames = num_seconds * fps / frame_interval
                num_loop = math.ceil((total_number_of_frames - condition_frame_length) / (num_frames - condition_frame_length))

        # prepare model args
        if config.num_frames == 1:
            fps = IMG_FPS

        model_args = dict()
        height_tensor = torch.tensor([resolution[0]], device=device, dtype=dtype)
        width_tensor = torch.tensor([resolution[1]], device=device, dtype=dtype)
        num_frames_tensor = torch.tensor([num_frames], device=device, dtype=dtype)
        ar_tensor = torch.tensor([resolution[0] / resolution[1]], device=device, dtype=dtype)
        fps_tensor = torch.tensor([fps], device=device, dtype=dtype)
        model_args["height"] = height_tensor
        model_args["width"] = width_tensor
        model_args["num_frames"] = num_frames_tensor
        model_args["ar"] = ar_tensor
        model_args["fps"] = fps_tensor

        # compute latent size
        input_size = (num_frames, *resolution)
        latent_size = vae.get_latent_size(input_size)

        # process prompt
        prompt = zh2en(prompt_text)[0].get("translation_text")
        prompt_raw = [prompt]
        print(prompt_raw)
        prompt_raw, _ = extract_json_from_prompts(prompt_raw)
        prompt_loops = process_prompts(prompt_raw, num_loop)
        video_clips = []

        # prepare mask strategy
        if mode == "Text2Image":
            mask_strategy = [None]
        elif mode == "Text2Video":
            if reference_image is not None:
                mask_strategy = ['0']
            else:
                mask_strategy = [None]
        else:
            raise ValueError(f"Invalid mode: {mode}")

        # =========================
        # 2. Load reference images
        # =========================
        if mode == "Text2Image":
            refs_x = collect_references_batch([None], vae, resolution)
        elif mode == "Text2Video":
            if reference_image is not None:
                # save image to disk
                from PIL import Image
                im = Image.fromarray(reference_image)

                with NamedTemporaryFile(suffix=".jpg") as temp_file:
                    im.save(temp_file.name)
                    refs_x = collect_references_batch([temp_file.name], vae, resolution)
            else:
                refs_x = collect_references_batch([None], vae, resolution)
        else:
            raise ValueError(f"Invalid mode: {mode}")

        # 4.3. long video generation
        for loop_i in range(num_loop):
            # 4.4 sample in hidden space
            batch_prompts = [prompt[loop_i] for prompt in prompt_loops]
            z = torch.randn(len(batch_prompts), vae.out_channels, *latent_size, device=device, dtype=dtype)

            # 4.5. apply mask strategy
            masks = None

            # if cfg.reference_path is not None:
            if loop_i > 0:
                ref_x = vae.encode(video_clips[-1])
                for j, refs in enumerate(refs_x):
                    if refs is None:
                        refs_x[j] = [ref_x[j]]
                    else:
                        refs.append(ref_x[j])
                    if mask_strategy[j] is None:
                        mask_strategy[j] = ""
                    else:
                        mask_strategy[j] += ";"
                    mask_strategy[
                        j
                    ] += f"{loop_i},{len(refs)-1},-{condition_frame_length},0,{condition_frame_length}"

            masks = apply_mask_strategy(z, refs_x, mask_strategy, loop_i)

            # 4.6. diffusion sampling
            # hack to update num_sampling_steps and cfg_scale
            scheduler_kwargs = config.scheduler.copy()
            scheduler_kwargs.pop('type')
            scheduler_kwargs['num_sampling_steps'] = sampling_steps
            scheduler_kwargs['cfg_scale'] = cfg_scale

            scheduler.__init__(
                **scheduler_kwargs
            )
            samples = scheduler.sample(
                stdit,
                text_encoder,
                z=z,
                prompts=batch_prompts,
                device=device,
                additional_args=model_args,
                mask=masks,  # scheduler must support mask
            )
            samples = vae.decode(samples.to(dtype))
            video_clips.append(samples)

            # 4.7. save video
            if loop_i == num_loop - 1:
                video_clips_list = [
                    video_clips[0][0]] + [video_clips[i][0][:, config.condition_frame_length :] 
                    for i in range(1, num_loop)
                ]
                video = torch.cat(video_clips_list, dim=1)
                current_datetime = datetime.datetime.now()
                timestamp = current_datetime.timestamp()
                save_path = os.path.join(args.output, f"output_{timestamp}")
                saved_path = save_sample(video, save_path=save_path, fps=config.fps // config.frame_interval)
                return saved_path

@spaces.GPU(duration=200)
def run_image_inference(prompt_text, resolution, aspect_ratio, length, reference_image, seed, sampling_steps, cfg_scale):
    return run_inference("Text2Image", prompt_text, resolution, aspect_ratio, length, reference_image, seed, sampling_steps, cfg_scale)

@spaces.GPU(duration=200)
def run_video_inference(prompt_text, resolution, aspect_ratio, length, reference_image, seed, sampling_steps, cfg_scale):
    return run_inference("Text2Video", prompt_text, resolution, aspect_ratio, length, reference_image, seed, sampling_steps, cfg_scale)


def main():
    # create demo
    with gr.Blocks() as demo:
        with gr.Row():
            with gr.Column():
                gr.HTML("""<h1 align="center">Open-Sora 1.1</h1>""")

        with gr.Row():
            with gr.Column():
                prompt_text = gr.Textbox(
                    label="Prompt",
                    placeholder="请输入中文提示词",
                    lines=4,
                )
                resolution = gr.Radio(
                     choices=["144p", "240p", "360p", "480p", "720p"],
                     value="240p",
                    label="Resolution", 
                )
                aspect_ratio = gr.Radio(
                     choices=["9:16", "16:9", "3:4", "4:3", "1:1"],
                     value="9:16",
                    label="Aspect Ratio (H:W)", 
                )
                length = gr.Radio(
                    choices=["2s", "4s", "8s", "16s"], 
                    value="2s",
                    label="Video Length (only effective for video generation)", 
                    info="8s may fail as Hugging Face ZeroGPU has the limitation of max 200 seconds inference time."
                )

                with gr.Row():
                    seed = gr.Slider(
                        value=1024,
                        minimum=1,
                        maximum=2048,
                        step=1,
                        label="Seed"
                    )

                    sampling_steps = gr.Slider(
                        value=100,
                        minimum=1,
                        maximum=200,
                        step=1,
                        label="Sampling steps"
                    )
                    cfg_scale = gr.Slider(
                        value=7.0,
                        minimum=0.0,
                        maximum=10.0,
                        step=0.1,
                        label="CFG Scale"
                    )
                
                reference_image = gr.Image(
                    label="Reference Image (Optional)",
                )
            
            with gr.Column():
                output_video = gr.Video(
                    label="Output Video",
                    height="100%"
                )

        with gr.Row():
             image_gen_button = gr.Button("Generate image")
             video_gen_button = gr.Button("Generate video")
        

        image_gen_button.click(
             fn=run_image_inference, 
             inputs=[prompt_text, resolution, aspect_ratio, length, reference_image, seed, sampling_steps, cfg_scale], 
             outputs=reference_image
             )
        video_gen_button.click(
             fn=run_video_inference, 
             inputs=[prompt_text, resolution, aspect_ratio, length, reference_image, seed, sampling_steps, cfg_scale], 
             outputs=output_video
             )

    # launch
    demo.launch(share=True, inbrowser=True)
    

if __name__ == "__main__":
    main()

运行Gradio应用,运行成功后点击 Running on public URL 后的网页链接即可体验!

!python gradio/app-ref.py
/home/ma-user/anaconda3/envs/python-3.10.10/lib/python3.10/site-packages/torch/_utils.py:776: UserWarning: TypedStorage is deprecated. It will be removed in the future and UntypedStorage will be the only storage class. This should only matter to you if you are using storages directly.  To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()

  return self.fget.__get__(instance, owner)()

/home/ma-user/anaconda3/envs/python-3.10.10/lib/python3.10/site-packages/transformers/models/marian/tokenization_marian.py:197: UserWarning: Recommended: pip install sacremoses.

  warnings.warn("Recommended: pip install sacremoses.")

/home/ma-user/anaconda3/envs/python-3.10.10/lib/python3.10/site-packages/colossalai/shardformer/layer/normalization.py:45: UserWarning: Please install apex from source (https://github.com/NVIDIA/apex) to use the fused layernorm kernel

  warnings.warn("Please install apex from source (https://github.com/NVIDIA/apex) to use the fused layernorm kernel")

Loading checkpoint shards: 100%|██████████████████| 2/2 [00:36<00:00, 18.47s/it]

/home/ma-user/anaconda3/envs/python-3.10.10/lib/python3.10/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.

  warnings.warn(

Running on local URL:  http://127.0.0.1:7860

Running on public URL: https://716216a33a762bafbc.gradio.live



This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run `gradio deploy` from Terminal to deploy to Spaces (https://huggingface.co/spaces)

在这里插入图片描述

提示词参考:

一只穿着紫色长袍的胖兔子穿过奇幻的风景
海浪冲击着孤零零的灯塔,不祥的灯光
一个神秘的森林展示了旅行者的冒险经历
一个蓝头发的法师在唱歌
一个超现实的景观,漂浮的岛屿和空中的瀑布
一只蓝鸟站在水里
一个年轻人独自走在海边
粉红色的玫瑰在玻璃表面滴,特写
驱车远眺,一列地铁正从隧道中驶出
太空中所有的行星都是绿色和粉色的,背景是明亮的白色恒星
一座漂浮在星体空间的城市,有星星和星云
高楼顶上的日出
粉色和青色粉末爆炸
树林里的鹿在阳光下凝视着相机
一道闪电,一个巫师从稀薄的空气中出现了,他的长袍在风中翻腾
夜晚的未来赛博朋克城市景观,高耸的霓虹灯照亮的摩天大楼
在这里,树木、花朵和动物聚集在一起,谱写出一曲大自然的交响乐
一艘幽灵般的船在云层中航行,在月光下的天空中航行
日落和美丽的海滩
一个年轻人独自走在森林里

生成好的视频也可以使用MusicGen进行配乐,使用AI进行短视频创作。

🦋2.5 视频效果展示

提示词:一个极端的特写一个头发花白的胡子的男人在他的60年代,他在思想深处思考宇宙的历史,他坐在一家咖啡馆在巴黎,他的眼睛关注人私生活方面大多像他坐在他们走不动,他穿着一件羊毛外套西装外套和一件衬衫,他穿着一件棕色的贝雷帽,眼镜和有一个非常专业的外表,和结束他提供了一个微妙的封闭式的笑容好像找到了答案的神秘生活,灯光非常电影化,金色的灯光和巴黎的街道和城市作为背景,景深,电影化的35mm胶片。
在这里插入图片描述
提示词:无人机拍摄的海浪冲击着大苏尔加雷角海滩上崎岖的悬崖。蓝色的海水拍打着白色的波浪,夕阳的金色光芒照亮了岩石海岸。远处有一座小岛,岛上有一座灯塔,悬崖边上长满了绿色的灌木丛。从公路到海滩的陡峭落差是一个戏剧性的壮举,悬崖的边缘突出在海面上。这是一幅捕捉到海岸原始美景和太平洋海岸公路崎岖景观的景色。
在这里插入图片描述
提示词:一段高耸的无人机镜头捕捉到了海岸悬崖的雄伟之美,它的红色和黄色分层岩石表面色彩丰富,映衬着充满活力的绿松石般的大海。可以看到海鸟在悬崖峭壁上飞翔。当无人机从不同角度缓慢移动时,变化的阳光投射出移动的阴影,突出了悬崖的崎岖纹理和周围平静的大海。水轻轻地拍打着岩石基座和附着在悬崖顶部的绿色植物,这一场景给人一种宁静的感觉,在海洋的边缘孤立。这段视频捕捉了未受人类建筑影响的原始自然美的本质。
在这里插入图片描述
提示词:雄伟美丽的瀑布从悬崖上倾泻而下,进入宁静的湖泊。瀑布,以其强大的流量,是视频的中心焦点。周围的景色郁郁葱葱,树木和树叶增添了自然美景。相机角度提供了瀑布的鸟瞰图,让观众欣赏瀑布的全部高度和壮观。这段视频令人惊叹地展现了大自然的力量和美。
在这里插入图片描述
提示词:夜晚熙熙攘攘的城市街道,充满了汽车前灯的光辉和街灯的环境光。场景是一个模糊的运动,汽车飞驰而过,行人在人行横道上穿行。城市景观是高耸的建筑和照明标志的混合,创造了一个充满活力和动态的氛围。视频的视角是高角度的,提供了街道及其周围环境的鸟瞰图。整个视频的风格充满活力和活力,捕捉到了夜晚城市生活的精髓。
在这里插入图片描述
提示词:森林地区宁静的夜景。第一个画面是一个宁静的湖泊,倒映着繁星满天的夜空。第二帧展示了美丽的日落,在风景上投下温暖的光芒。第三帧展示了夜空,充满了星星和充满活力的银河系。这段视频是延时拍摄的,捕捉了从白天到夜晚的过渡,湖泊和森林作为恒定的背景。视频的风格是自然主义的,强调夜空的美丽和森林的宁静。
在这里插入图片描述

🚀总结

本文介绍了华为云在云端文生视频领域的最新进展,特别是通过 Open-Sora 模型实现高效的视频生成。Open-Sora 模型凭借其流畅的开发体验和强大的功能,成为云端视频生成领域的佼佼者。文章详细阐述了模型的架构设计、实现流程以及其带来的流畅开发体验,展示了华为云如何通过技术创新大幅提升大模型的开发效率,并为开发者提供了便捷的模型微调与应用能力。整体而言,华为云通过 Open-Sora 模型为开发者提供了高效、智能的解决方案,进一步推动了云端 AI 技术的发展。


本文整理自华为云社区【内容共创】活动第27期。

查看活动详情:https://bbs.huaweicloud.cn/blogs/430812

相关任务详情:任务3.如何玩转云端文生视频?华为云AI优秀开发者来揭秘!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。