Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 0.1.0 Release #24

Draft
wants to merge 84 commits into
base: latest
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
667839a
feat: add AnimateDiff repo as Git submodule
threeal Jul 17, 2023
3cd9682
feat: add `animate.py` that currently does nothing
threeal Jul 17, 2023
1af1199
feat: add function that check if Stable Diffusion exists
threeal Jul 17, 2023
5629252
feat: replace `check_stable_diffusion` function with `init_stable_dif…
threeal Jul 17, 2023
b0fdec4
refactor: rename `init_stable_diffusion_dir` to `init_snapshot`
threeal Jul 18, 2023
0e8c039
feat: load tokenizer, text encoder, and vae from the stable diffusion
threeal Jul 18, 2023
000d570
feat: load U-Net from the Stable Diffusion
threeal Jul 18, 2023
ba2a73a
refactor: auto check Stable Diffusion snapshot when loading for pretr…
threeal Jul 18, 2023
27176a3
feat: add `create_animation_pipeline` function
threeal Jul 18, 2023
c428401
feat: add `motion_module.load_state_dict` function
threeal Jul 18, 2023
86b8efc
refactor: rename `modules/pipeline.py` to `modules/animation_pipeline…
threeal Jul 18, 2023
6560283
feat: add `diffusion_model.load_model` function
threeal Jul 18, 2023
9331f57
feat: run inference and save the result
threeal Jul 18, 2023
27a449f
chore: merge pull request #2 from `threeal/impl-animatediff`
threeal Jul 18, 2023
6bb774e
ci: add `test.yaml` workflow
threeal Jul 20, 2023
0c223a6
feat: cache models path using `ANIMATEDIFF_MODELS_CACHE` env
threeal Jul 20, 2023
c20bfcc
ci: add Dependabot config that checks for new version of GitHub actions
threeal Jul 20, 2023
8395e5b
chore: merge pull request #4 from `threeal/add-github-workflows`
threeal Jul 20, 2023
8c7a306
feat: only download the required Stable Diffusion files
threeal Jul 20, 2023
4871d0f
chore: merge pull request #5 from `threeal/include-required-stable-di…
threeal Jul 20, 2023
2f5a093
ci: exclude pull request event from `latest` branch in test workflow
threeal Jul 21, 2023
21f4a20
chore: merge pull request #7 from `threeal/workflows-on-pr-exclude`
threeal Jul 21, 2023
9d8c3f0
feat: add `app.py` as an entrypoint for running Gradio app
threeal Jul 21, 2023
20c80d5
feat(app): create animation pipeline before generating the image
threeal Jul 21, 2023
9b18a2f
feat(app): show progress when running the inference
threeal Jul 21, 2023
0315320
feat(app): add several inputs options
threeal Jul 21, 2023
66606ac
chore: merge pull request #10 from `threeal/gradio-ui`
threeal Jul 21, 2023
3cadc4a
chore: remove old location for AnimateDiff dependencies
threeal Jul 24, 2023
0936e87
feat: update AnimateDiff deps to the latest version that support grad…
threeal Jul 24, 2023
276a540
chore: merge pull request #12 from `threeal/gradio-demo-support`
threeal Jul 24, 2023
87354c4
feat: add step that checks for lint in the `test.yaml` workflow
threeal Jul 24, 2023
17a190e
fix: fix some pylint errors
threeal Jul 24, 2023
d3a60c8
fix: add `.pylintrc` to ignore some errors
threeal Jul 24, 2023
777c57e
chore: merge pull request #14 from `threeal/integrate-pylint`
threeal Jul 24, 2023
12c6ea7
test: modify `animate.py` script to `test_animate.py`
threeal Jul 25, 2023
6b6d6a5
ci: run pytest instead in the `test.yaml` workflow
threeal Jul 25, 2023
8158297
test: use pytest-dependency to run test in specific orders
threeal Jul 25, 2023
39520fe
test: check for saved animation hash
threeal Jul 25, 2023
46cbb30
ci: show verbose information and duration when running tests in the `…
threeal Jul 25, 2023
cd006ec
test: run pylint in the `test` directory and fix errors
threeal Jul 25, 2023
082d76c
test: run pylint dirrectly from pytest
threeal Jul 25, 2023
8d53e0f
chore: merge pull request #16 from `threeal/use-pytest`
threeal Jul 25, 2023
5895204
refactor: move modules to `minimal_animatediff` directory
threeal Jul 26, 2023
159dedf
test: enable `E0401` error
threeal Jul 26, 2023
6470f10
chore: merge pull request #18 from `threeal/organize-modules`
threeal Jul 26, 2023
1c4b28b
feat: add `stable_diffusion.Snapshot` class
threeal Jul 26, 2023
70d1be9
feat: use `warnings` module in the `stable_diffusion.py`
threeal Jul 26, 2023
660b0d2
refactor: rename `stable_diffusion.Snapshot` into `StableDiffusionSna…
threeal Jul 26, 2023
26419cc
chore: merge pull request #19 from `threeal/scoped-stable-diff`
threeal Jul 26, 2023
241d383
ci: check for code coverage in the `test.yaml` workflow
threeal Jul 26, 2023
e8815fa
test: shorter inference time to make animate test faster
threeal Jul 26, 2023
663477a
build: add `Makefile` to simplify test call
threeal Jul 26, 2023
1afb96e
test: report code coverage annotation
threeal Jul 26, 2023
68786db
chore: merge pull request #21 from `threeal/integrate-pytest-cov`
threeal Jul 26, 2023
8160657
test: use flake8 for linting
threeal Jul 26, 2023
ce5f1e4
test: use black to format source code
threeal Jul 26, 2023
5bbe0f8
style: fix source code formatting
threeal Jul 26, 2023
d373c4b
ci: check diff in the `test.yaml` workflow
threeal Jul 26, 2023
1fed98d
chore: merge pull request #22 from `threeal/flake8-linting`
threeal Jul 26, 2023
1ec0bc5
feat(app): output video instead of GIF image
threeal Jul 28, 2023
404c27f
test: add test to save video as mp4
threeal Jul 28, 2023
54730b9
test: save animation result in the dir relative to the test file
threeal Jul 28, 2023
9703ebd
chore: merge pull request #25 from `threeal/video-output`
threeal Jul 28, 2023
300f7a6
feat: replace `diffusion_model.load_model` with `DreamBoothModel` class
threeal Jul 28, 2023
3810497
test: test load DreamBooth model
threeal Jul 28, 2023
039ad90
feat: initialize Dream Booth snapshot from Hugging Face repository
threeal Jul 28, 2023
8bec0d2
feat: add `populate_snapshot` utility function
threeal Jul 31, 2023
08249ee
fix: correct the path of the DreamBooth snapshot directory
threeal Jul 31, 2023
0d6c4b5
refactor: rename `DreamBoothSnapshot` back to `DreamBoothModel`
threeal Jul 31, 2023
ac23862
refactor: add further test during DreamBooth model init
threeal Jul 31, 2023
d60a587
chore: merge pull request #26 from `threeal/dreamboth-model-class`
threeal Jul 31, 2023
1f4e296
feat: add `MotionModuleModel` class
threeal Jul 31, 2023
1c576bf
chore: merge pull request #27 from `threeal/motion-module-class`
threeal Jul 31, 2023
3777651
feat: load Stable Diffusion component during init
threeal Jul 31, 2023
55ecde6
test: add Stable Diffusion init testing
threeal Jul 31, 2023
7697be0
feat: assert if xFormers is not installed
threeal Jul 31, 2023
f2a18cc
chore: remove unused `get_model_path` utility function
threeal Jul 31, 2023
d6dd876
refactor: rename `StableDiffusionSnapshot` to just `StableDiffusion`
threeal Jul 31, 2023
6ce6a65
refactor: rename `MotionModuleModel` to just `MotionModule`
threeal Jul 31, 2023
9a0c77a
chore: merge pull request #28 from `threeal/stable-diffusion-better-s…
threeal Jul 31, 2023
a476e31
chore: bump actions/checkout from 3.5.3 to 3.6.0 (#29)
dependabot[bot] Sep 1, 2023
9d673c1
chore: bump actions/checkout from 3.6.0 to 4.0.0 (#30)
dependabot[bot] Sep 12, 2023
e386c59
chore: bump actions/checkout from 4.0.0 to 4.1.0 (#31)
dependabot[bot] Sep 30, 2023
06ffd6c
chore: bump actions/checkout from 4.1.0 to 4.1.2 (#33)
dependabot[bot] Apr 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: daily
commit-message:
prefix: chore
24 changes: 24 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: test
on:
workflow_dispatch:
pull_request:
branches: ['*', '!latest']
push:
branches: [latest, main]
jobs:
lib:
runs-on: [self-hosted, linux, x64]
steps:
- name: Checkout
uses: actions/[email protected]
with:
submodules: true

- name: Run tests
shell: fish {0}
run: |
conda activate minimal-animatediff
make test

- name: Check diff
run: git diff --exit-code HEAD
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
.*
!.git*
!.pylintrc

__pycache__/
coverage/
flagged/
models/
samples/
snapshots/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "deps/AnimateDiff"]
path = deps/AnimateDiff
url = https://github.com/guoyww/AnimateDiff
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
format:
python3 -m black minimal_animatediff scripts tests \
--line-length 120

lint: format
python3 -m flake8 minimal_animatediff scripts tests \
--max-line-length 120

test: lint
python3 -m pytest -v -s \
--durations=0 \
--cov=minimal_animatediff \
--cov-fail-under=85 \
--cov-report term \
--cov-report annotate:coverage

.PHONY: format lint test
1 change: 1 addition & 0 deletions deps/AnimateDiff
Submodule AnimateDiff added at 53c63a
26 changes: 26 additions & 0 deletions environment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: minimal-animatediff
channels:
- pytorch
- xformers
dependencies:
- cudatoolkit=11.3
- pip
- python=3.10
- pytorch==1.12.1
- torchvision==0.13.1
- xformers
- pip:
- black
- diffusers[torch]==0.11.1
- einops
- filehash
- flake8
- gdown
- huggingface_hub
- imageio[ffmpeg]
- pytest
- pytest-cov
- pytest-dependency
- safetensors
- transformers==4.25.1
- triton
Empty file added minimal_animatediff/__init__.py
Empty file.
54 changes: 54 additions & 0 deletions minimal_animatediff/animation_pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import sys

from diffusers import DDIMScheduler

from deps.AnimateDiff.animatediff.pipelines.pipeline_animation import AnimationPipeline
import deps.AnimateDiff.animatediff.utils.convert_from_ckpt as cvt

from .dream_booth import DreamBoothModel
from .motion_module import MotionModule
from .stable_diffusion import StableDiffusion


def create_animation_pipeline():
sd = StableDiffusion()

pipeline = AnimationPipeline(
text_encoder=sd.text_encoder,
tokenizer=sd.tokenizer,
vae=sd.vae,
unet=sd.unet,
scheduler=DDIMScheduler(
**{
"num_train_timesteps": 1000,
"beta_start": 0.00085,
"beta_end": 0.012,
"beta_schedule": "linear",
"steps_offset": 1,
"clip_sample": False,
}
),
)

pipeline.to("cuda")

print("Loading motion module to the animation pipeline...")
mm = MotionModule("mm_sd_v15.ckpt")
_, unexpected = pipeline.unet.load_state_dict(mm.states, strict=False)
if len(unexpected) > 0:
sys.exit("Failed to load motion module to the animation pipeline!")

print("Loading diffusion model to the animation pipeline...")
db_model = DreamBoothModel("toonyou_beta3.safetensors")

converted_vae_checkpoint = cvt.convert_ldm_vae_checkpoint(db_model.states, pipeline.vae.config)
pipeline.vae.load_state_dict(converted_vae_checkpoint)

converted_unet_checkpoint = cvt.convert_ldm_unet_checkpoint(db_model.states, pipeline.unet.config)
pipeline.unet.load_state_dict(converted_unet_checkpoint, strict=False)

pipeline.text_encoder = cvt.convert_ldm_clip_checkpoint(db_model.states)

pipeline.to("cuda")

return pipeline
13 changes: 13 additions & 0 deletions minimal_animatediff/dream_booth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from safetensors import safe_open
from .utils import populate_snapshot


class DreamBoothModel:
def __init__(self, name: str):
snapshot = populate_snapshot("dream_booth/" + name)

# Load the model states
self.states = {}
with safe_open(snapshot, framework="pt", device="cpu") as model:
for key in model.keys():
self.states[key] = model.get_tensor(key)
9 changes: 9 additions & 0 deletions minimal_animatediff/motion_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import torch

from .utils import populate_snapshot


class MotionModule:
def __init__(self, name: str):
snapshot = populate_snapshot("motion_modules/" + name)
self.states = torch.load(snapshot, map_location="cpu")
53 changes: 53 additions & 0 deletions minimal_animatediff/stable_diffusion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from diffusers import AutoencoderKL
from diffusers.utils.import_utils import is_xformers_available
from huggingface_hub import snapshot_download
from transformers import CLIPTextModel, CLIPTokenizer

from deps.AnimateDiff.animatediff.models.unet import UNet3DConditionModel


class StableDiffusion:
def __init__(self):
snapshot = "snapshots/stable_diffusion"
snapshot_download(
repo_id="runwayml/stable-diffusion-v1-5",
local_dir=snapshot,
allow_patterns=[
"text_encoder/*.json",
"text_encoder/*model.bin",
"tokenizer/*",
"unet/*.json",
"unet/*model.bin",
"vae/*.json",
"vae/*model.bin",
],
)

self.text_encoder = CLIPTextModel.from_pretrained(snapshot, subfolder="text_encoder")
self.tokenizer = CLIPTokenizer.from_pretrained(snapshot, subfolder="tokenizer")
self.vae = AutoencoderKL.from_pretrained(snapshot, subfolder="vae")

self.unet = UNet3DConditionModel.from_pretrained_2d(
snapshot,
subfolder="unet",
unet_additional_kwargs={
"unet_use_cross_frame_attention": False,
"unet_use_temporal_attention": False,
"use_motion_module": True,
"motion_module_resolutions": [1, 2, 4, 8],
"motion_module_mid_block": False,
"motion_module_decoder_only": False,
"motion_module_type": "Vanilla",
"motion_module_kwargs": {
"num_attention_heads": 8,
"num_transformer_block": 1,
"attention_block_types": ["Temporal_Self", "Temporal_Self"],
"temporal_position_encoding": True,
"temporal_position_encoding_max_len": 24,
"temporal_attention_dim_div": 1,
},
},
)

assert is_xformers_available()
self.unet.enable_xformers_memory_efficient_attention()
25 changes: 25 additions & 0 deletions minimal_animatediff/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os

from huggingface_hub import snapshot_download


def populate_snapshot(path: str) -> str:
"""
Populates the snapshot of a specified path from the 'threeal/AnimateDiffMirrors' repository.

Args:
path (str): The path to the file or directory in the repository to be populated.

Returns:
str: The local path of the populated snapshot.

Example:
>>> populate_snapshot("MotionModules/mm_sd_v15.ckpt")
'snapshots/MotionModules/mm_sd_v15.ckpt'
"""
snapshot_download(
repo_id="threeal/AnimateDiffMirrors",
local_dir="snapshots",
allow_patterns=[path],
)
return os.path.join("snapshots", path)
48 changes: 48 additions & 0 deletions scripts/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import gradio
import torch

from deps.AnimateDiff.animatediff.utils.util import save_videos_grid
from minimal_animatediff.animation_pipeline import create_animation_pipeline


if __name__ == "__main__":
pipeline = create_animation_pipeline()

def generate(prompt, neg_prompt, steps, width, height, frames, progress=gradio.Progress()):
torch.manual_seed(16372571278361863751)

def update(i, _, __):
progress(i / float(steps + 1), desc="Sampling")

progress(0, desc="Sampling")
sample = pipeline(
prompt,
negative_prompt=neg_prompt,
num_inference_steps=int(steps),
guidance_scale=7.5,
width=int(width),
height=int(height),
video_length=int(frames),
callback=update,
).videos

progress(steps / float(steps + 1), desc="Converting")
save_videos_grid(sample, "samples/sample.mp4")

progress(1)
return "samples/sample.mp4"

app = gradio.Interface(
fn=generate,
inputs=[
gradio.Textbox(label="Prompt"),
gradio.Textbox(label="Negative Prompt"),
gradio.Number(label="Steps", value=25, minimum=1),
gradio.Number(label="Width", value=512, minimum=1),
gradio.Number(label="Height", value=512, minimum=1),
gradio.Number(label="Frames", value=16, minimum=1),
],
outputs=gradio.Video(label="Generated Animation"),
)

app.launch(share=True, enable_queue=True)
Empty file added tests/__init__.py
Empty file.
52 changes: 52 additions & 0 deletions tests/test_animate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from filehash import FileHash
import pytest
import torch
import os

from deps.AnimateDiff.animatediff.utils.util import save_videos_grid
from minimal_animatediff.animation_pipeline import create_animation_pipeline

hasher = FileHash("md5")

dir_path = os.path.dirname(os.path.realpath(__file__))
PIPELINE = None
SAMPLE = None


@pytest.mark.dependency()
def test_create_animation_pipeline():
global PIPELINE
PIPELINE = create_animation_pipeline()


@pytest.mark.dependency(depends=["test_create_animation_pipeline"])
def test_run_animation_pipeline():
global SAMPLE
assert PIPELINE is not None

torch.manual_seed(16372571278361863751)
SAMPLE = PIPELINE(
"best quality, masterpiece, 1girl, cloudy sky, dandelion, alternate hairstyle,",
negative_prompt="",
num_inference_steps=10,
guidance_scale=7.5,
width=512,
height=512,
video_length=16,
).videos


@pytest.mark.dependency(depends=["test_run_animation_pipeline"])
def test_save_animation_gif():
assert SAMPLE is not None
gif_path = os.path.join(dir_path, "samples/sample.gif")
save_videos_grid(SAMPLE, gif_path)
assert hasher.hash_file(gif_path) == "30cc5fb2a6446f0849889b7a84ec1c42"


@pytest.mark.dependency(depends=["test_run_animation_pipeline"])
def test_save_animation_mp4():
assert SAMPLE is not None
mp4_path = os.path.join(dir_path, "samples/sample.mp4")
save_videos_grid(SAMPLE, mp4_path)
assert hasher.hash_file(mp4_path) == "76494406baff329aa68d9c22ad0436cc"
19 changes: 19 additions & 0 deletions tests/test_dream_booth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from minimal_animatediff.dream_booth import DreamBoothModel


def test_init_dream_booth_model():
model = DreamBoothModel("toonyou_beta3.safetensors")
assert len(model.states.keys()) == 1133


def test_init_other_dream_booth_model():
model = DreamBoothModel("lyriel_v16.safetensors")
assert len(model.states.keys()) == 1131


def test_init_non_existing_dream_booth_model():
try:
DreamBoothModel("invalid.safetensors")
assert False
except FileNotFoundError:
pass
19 changes: 19 additions & 0 deletions tests/test_motion_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from minimal_animatediff.motion_module import MotionModule


def test_init_motion_module():
model = MotionModule("mm_sd_v15.ckpt")
assert len(model.states.keys()) == 560


def test_init_other_motion_module():
model = MotionModule("mm_sd_v14.ckpt")
assert len(model.states.keys()) == 560


def test_init_non_existing_motion_module():
try:
MotionModule("invalid.ckpt")
assert False
except FileNotFoundError:
pass
Loading
Loading