Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ad327119b | ||
|
|
de792d510f | ||
|
|
1b678e3e4d | ||
|
|
c66afa595f | ||
|
|
a3e6c284db | ||
|
|
97c54553db | ||
|
|
ef1f3036bb | ||
|
|
b529601613 | ||
|
|
c6d5511b46 | ||
|
|
8ffba3c09c | ||
|
|
008f3310a5 | ||
|
|
02dc9bbcbd | ||
|
|
06c9f21ffb | ||
|
|
2aecbc841e | ||
|
|
e17bdfc621 | ||
|
|
3dbb83d209 | ||
|
|
0297f99c10 | ||
|
|
ceafe418b5 | ||
|
|
aeacd3269a | ||
|
|
ccc5c00c8e | ||
|
|
7e0e6abc05 | ||
|
|
baec648773 | ||
|
|
4b0c19d34a | ||
|
|
563d5cf881 | ||
|
|
85674301fb | ||
|
|
0d1de5af4c | ||
|
|
e3b444c799 | ||
|
|
1d6aba3261 | ||
|
|
c3ca25d897 | ||
|
|
376766163e | ||
|
|
d417b2a63f |
Vendored
+1
@@ -222,6 +222,7 @@
|
||||
"ukkonen",
|
||||
"upsamplers",
|
||||
"urllib",
|
||||
"usecase",
|
||||
"venv",
|
||||
"wandb",
|
||||
"webassets",
|
||||
|
||||
@@ -5,8 +5,11 @@ from .src.nodes import (
|
||||
ComfyUIAutoencoderDTO,
|
||||
ComfyUIClipDTO,
|
||||
ComfyUIConditioningDTO,
|
||||
ComfyUILcmLoraDTO,
|
||||
ComfyUIPipelineDTO,
|
||||
ComfyUISchedulerDTO,
|
||||
ComfyUIStreamDTO,
|
||||
ComfyUIWarmupStreamDTO,
|
||||
)
|
||||
|
||||
load_envs()
|
||||
@@ -19,4 +22,7 @@ __all__ = [
|
||||
"ComfyUIClipDTO",
|
||||
"ComfyUIConditioningDTO",
|
||||
"ComfyUISchedulerDTO",
|
||||
"ComfyUILcmLoraDTO",
|
||||
"ComfyUIStreamDTO",
|
||||
"ComfyUIWarmupStreamDTO",
|
||||
]
|
||||
|
||||
Generated
+688
-380
File diff suppressed because it is too large
Load Diff
+74
-41
@@ -3,27 +3,56 @@ name = "ComfyUI-Diffusers"
|
||||
version = "0.1.0"
|
||||
description = "This repository is a custom node in ComfyUI. This is a program that allows you to use Huggingface Diffusers module with ComfyUI. Additionally, Stream Diffusion is also available."
|
||||
authors = [
|
||||
{name = "Limitex", email = "76650151+Limitex@users.noreply.github.com"}
|
||||
{ name = "Limitex", email = "76650151+Limitex@users.noreply.github.com" },
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.15"
|
||||
dependencies = [
|
||||
"dependency-injector (>=4.48.2,<5.0.0)",
|
||||
"diffusers[torch] (>=0.35.2,<0.36.0)",
|
||||
"torch (>=2.9.0,<3.0.0)",
|
||||
"torchaudio (>=2.9.0,<3.0.0)",
|
||||
"torchvision (>=0.24.0,<0.25.0)",
|
||||
"transformers (>=4.57.1,<5.0.0)",
|
||||
"diffusers[torch] (>=0.24.0,<0.36.0)",
|
||||
"torch (>=2.6.0,<3.0.0)",
|
||||
"torchaudio (>=2.6.0,<3.0.0)",
|
||||
"torchvision (>=0.21.0,<0.25.0)",
|
||||
"transformers (>=4.48.3,<4.49.0)",
|
||||
"omegaconf (>=2.3.0,<3.0.0)",
|
||||
"safetensors (>=0.4.0,<0.5.0)",
|
||||
"requests (>=2.32.0,<3.0.0)",
|
||||
"python-dotenv (>=1.2.1,<2.0.0)",
|
||||
"xformers (>=0.0.29.post3,<0.0.30)",
|
||||
"streamdiffusion[tensorrt] @ git+https://github.com/cumulo-autumn/StreamDiffusion.git@b623251dc055e1fd858d53509aa43e09dfc5cdc0",
|
||||
"tensorrt (>=10.12.0,<10.13.0)",
|
||||
"polygraphy (>=0.47.1,<0.48.0)",
|
||||
"onnx-graphsurgeon (>=0.3.27,<0.4.0)",
|
||||
"numpy (>=1.26.4,<2.0.0)",
|
||||
"huggingface-hub (>=0.25.2,<0.26.0)",
|
||||
]
|
||||
|
||||
[tool.poetry]
|
||||
packages = [
|
||||
{include = "src"}
|
||||
]
|
||||
packages = [{ include = "src" }]
|
||||
|
||||
[[tool.poetry.source]]
|
||||
name = "pytorch-cu124"
|
||||
url = "https://download.pytorch.org/whl/cu124"
|
||||
priority = "explicit"
|
||||
|
||||
[[tool.poetry.source]]
|
||||
name = "nvidia"
|
||||
url = "https://pypi.nvidia.com"
|
||||
priority = "supplemental"
|
||||
|
||||
[[tool.poetry.source]]
|
||||
name = "ngc"
|
||||
url = "https://pypi.ngc.nvidia.com"
|
||||
priority = "supplemental"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
torch = { version = ">=2.6.0,<3.0.0", source = "pytorch-cu124" }
|
||||
torchaudio = { version = ">=2.6.0,<3.0.0", source = "pytorch-cu124" }
|
||||
torchvision = { version = ">=0.21.0,<0.25.0", source = "pytorch-cu124" }
|
||||
xformers = { version = ">=0.0.29.post3,<0.0.30", source = "pytorch-cu124" }
|
||||
tensorrt = { version = ">=10.12.0,<10.13.0", source = "nvidia" }
|
||||
polygraphy = { version = ">=0.47.1,<0.48.0", source = "ngc" }
|
||||
onnx-graphsurgeon = { version = ">=0.3.27,<0.4.0", source = "ngc" }
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
@@ -47,11 +76,7 @@ markers = [
|
||||
"integration: Integration tests",
|
||||
"slow: Slow tests",
|
||||
]
|
||||
filterwarnings = [
|
||||
"error",
|
||||
"ignore::UserWarning",
|
||||
"ignore::DeprecationWarning",
|
||||
]
|
||||
filterwarnings = ["error", "ignore::UserWarning", "ignore::DeprecationWarning"]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["src"]
|
||||
@@ -59,7 +84,7 @@ omit = [
|
||||
"*/tests/*",
|
||||
"*/test_*.py",
|
||||
"*/__init__.py",
|
||||
"src/di/*", # DI container doesn't need test coverage
|
||||
"src/di/*", # DI container doesn't need test coverage
|
||||
]
|
||||
branch = true
|
||||
parallel = true
|
||||
@@ -93,11 +118,33 @@ output = "coverage.xml"
|
||||
[tool.bandit]
|
||||
exclude_dirs = ["tests", "venv", ".venv", ".tox", "build", "dist"]
|
||||
tests = [
|
||||
"B201", "B301", "B302", "B303", "B304", "B305", "B306", "B307",
|
||||
"B308", "B309", "B310", "B311", "B312", "B313", "B314", "B315",
|
||||
"B316", "B317", "B318", "B319", "B320", "B321", "B323", "B324", "B325"
|
||||
"B201",
|
||||
"B301",
|
||||
"B302",
|
||||
"B303",
|
||||
"B304",
|
||||
"B305",
|
||||
"B306",
|
||||
"B307",
|
||||
"B308",
|
||||
"B309",
|
||||
"B310",
|
||||
"B311",
|
||||
"B312",
|
||||
"B313",
|
||||
"B314",
|
||||
"B315",
|
||||
"B316",
|
||||
"B317",
|
||||
"B318",
|
||||
"B319",
|
||||
"B320",
|
||||
"B321",
|
||||
"B323",
|
||||
"B324",
|
||||
"B325",
|
||||
]
|
||||
skips = ["B101", "B601"] # Allow assert in tests & shell=True if needed
|
||||
skips = ["B101", "B601"] # Allow assert in tests & shell=True if needed
|
||||
|
||||
[tool.bandit.assert_used]
|
||||
skips = ["*/test_*.py", "*/*_test.py"]
|
||||
@@ -109,10 +156,10 @@ root_package = "src"
|
||||
name = "Clean Architecture Layers"
|
||||
type = "layers"
|
||||
layers = [
|
||||
"src.nodes", # Presentation Layer (ComfyUI Nodes)
|
||||
"src.ui", # Presentation Layer (Handlers)
|
||||
"src.service", # Application Layer (Use Cases)
|
||||
"src.domain", # Domain Layer (Entities & Interfaces)
|
||||
"src.nodes", # Presentation Layer (ComfyUI Nodes)
|
||||
"src.ui", # Presentation Layer (Handlers)
|
||||
"src.service", # Application Layer (Use Cases)
|
||||
"src.domain", # Domain Layer (Entities & Interfaces)
|
||||
]
|
||||
containers = ["src"]
|
||||
|
||||
@@ -132,39 +179,25 @@ forbidden_modules = [
|
||||
name = "Application Layer Independence"
|
||||
type = "forbidden"
|
||||
source_modules = ["src.service"]
|
||||
forbidden_modules = [
|
||||
"src.ui",
|
||||
"src.nodes",
|
||||
"src.infra",
|
||||
"src.di",
|
||||
]
|
||||
forbidden_modules = ["src.ui", "src.nodes", "src.infra", "src.di"]
|
||||
|
||||
[[tool.importlinter.contracts]]
|
||||
name = "Infrastructure depends only on Domain"
|
||||
type = "forbidden"
|
||||
source_modules = ["src.infra"]
|
||||
forbidden_modules = [
|
||||
"src.service",
|
||||
"src.ui",
|
||||
"src.nodes",
|
||||
"src.di",
|
||||
]
|
||||
forbidden_modules = ["src.service", "src.ui", "src.nodes", "src.di"]
|
||||
|
||||
[[tool.importlinter.contracts]]
|
||||
name = "Presentation Layer can use Application & Domain"
|
||||
type = "forbidden"
|
||||
source_modules = ["src.ui", "src.nodes"]
|
||||
forbidden_modules = [
|
||||
"src.infra",
|
||||
]
|
||||
forbidden_modules = ["src.infra"]
|
||||
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
version = "0.1.0"
|
||||
tag_format = "v$version"
|
||||
version_files = [
|
||||
"pyproject.toml:version"
|
||||
]
|
||||
version_files = ["pyproject.toml:version"]
|
||||
update_changelog_on_bump = true
|
||||
changelog_file = "CHANGELOG.md"
|
||||
|
||||
|
||||
+48
-26
@@ -1,54 +1,76 @@
|
||||
--extra-index-url https://pypi.nvidia.com
|
||||
--extra-index-url https://pypi.ngc.nvidia.com
|
||||
--extra-index-url https://download.pytorch.org/whl/cu124
|
||||
|
||||
accelerate==1.11.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
antlr4-python3-runtime==4.9.3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
certifi==2025.10.5 ; python_version >= "3.10" and python_version < "3.15"
|
||||
charset-normalizer==3.4.4 ; python_version >= "3.10" and python_version < "3.15"
|
||||
colorama==0.4.6 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Windows"
|
||||
colored==2.3.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
coloredlogs==15.0.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
cuda-bindings==13.0.3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
cuda-pathfinder==1.3.2 ; python_version >= "3.10" and python_version < "3.15"
|
||||
cuda-python==13.0.3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
dependency-injector==4.48.2 ; python_version >= "3.10" and python_version < "3.15"
|
||||
diffusers==0.35.2 ; python_version >= "3.10" and python_version < "3.15"
|
||||
diffusers==0.24.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
filelock==3.20.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
fire==0.7.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
flatbuffers==25.9.23 ; python_version >= "3.10" and python_version < "3.15"
|
||||
fsspec==2025.10.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
hf-xet==1.2.0 ; python_version >= "3.10" and python_version < "3.15" and (platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "arm64" or platform_machine == "aarch64")
|
||||
huggingface-hub==0.36.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
huggingface-hub==0.25.2 ; python_version >= "3.10" and python_version < "3.15"
|
||||
humanfriendly==10.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
idna==3.11 ; python_version >= "3.10" and python_version < "3.15"
|
||||
importlib-metadata==8.7.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
jinja2==3.1.6 ; python_version >= "3.10" and python_version < "3.15"
|
||||
markupsafe==3.0.3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
mpmath==1.3.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
networkx==3.4.2 ; python_version >= "3.10" and python_version < "3.15"
|
||||
numpy==2.2.6 ; python_version >= "3.10" and python_version < "3.15"
|
||||
nvidia-cublas-cu12==12.8.4.1 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cuda-cupti-cu12==12.8.90 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cuda-nvrtc-cu12==12.8.93 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cuda-runtime-cu12==12.8.90 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cudnn-cu12==9.10.2.21 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cufft-cu12==11.3.3.83 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cufile-cu12==1.13.1.3 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-curand-cu12==10.3.9.90 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cusolver-cu12==11.7.3.90 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cusparse-cu12==12.5.8.93 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cusparselt-cu12==0.7.1 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-nccl-cu12==2.27.5 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-nvjitlink-cu12==12.8.93 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-nvshmem-cu12==3.3.20 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-nvtx-cu12==12.8.90 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
numpy==1.26.4 ; python_version >= "3.10" and python_version < "3.15"
|
||||
nvidia-cublas-cu12==12.4.5.8 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cuda-cupti-cu12==12.4.127 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cuda-nvrtc-cu12==12.4.127 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cuda-runtime-cu12==12.4.127 ; python_version >= "3.10" and python_version < "3.15"
|
||||
nvidia-cudnn-cu12==9.1.0.70 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cufft-cu12==11.2.1.3 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-curand-cu12==10.3.5.147 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cusolver-cu12==11.6.1.9 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cusparse-cu12==12.3.1.170 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-cusparselt-cu12==0.6.2 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-nccl-cu12==2.21.5 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-nvjitlink-cu12==12.4.127 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
nvidia-nvtx-cu12==12.4.127 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
omegaconf==2.3.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
onnx-graphsurgeon==0.3.27 ; python_version >= "3.10" and python_version < "3.15"
|
||||
onnx==1.15.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
onnxruntime==1.16.3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
packaging==25.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
pillow==12.0.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
polygraphy==0.47.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
protobuf==3.20.2 ; python_version >= "3.10" and python_version < "3.15"
|
||||
psutil==7.1.2 ; python_version >= "3.10" and python_version < "3.15"
|
||||
pyreadline3==3.5.4 ; python_version >= "3.10" and python_version < "3.15" and sys_platform == "win32"
|
||||
python-dotenv==1.2.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
pyyaml==6.0.3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
regex==2025.10.23 ; python_version >= "3.10" and python_version < "3.15"
|
||||
requests==2.32.5 ; python_version >= "3.10" and python_version < "3.15"
|
||||
safetensors==0.4.5 ; python_version >= "3.10" and python_version < "3.15"
|
||||
setuptools==80.9.0 ; python_version >= "3.12" and python_version < "3.15"
|
||||
sympy==1.14.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
tokenizers==0.22.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
torch==2.9.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
torchaudio==2.9.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
torchvision==0.24.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
streamdiffusion @ git+https://github.com/cumulo-autumn/StreamDiffusion.git@b623251dc055e1fd858d53509aa43e09dfc5cdc0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
sympy==1.13.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
tensorrt-cu12-bindings==10.12.0.36 ; python_version >= "3.10" and python_version < "3.15"
|
||||
tensorrt-cu12-libs==10.12.0.36 ; python_version >= "3.10" and python_version < "3.15"
|
||||
tensorrt-cu12==10.12.0.36 ; python_version >= "3.10" and python_version < "3.15"
|
||||
tensorrt==10.12.0.36 ; python_version >= "3.10" and python_version < "3.15"
|
||||
termcolor==3.2.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
tokenizers==0.21.4 ; python_version >= "3.10" and python_version < "3.15"
|
||||
torch==2.6.0+cu124 ; python_version >= "3.10" and python_version < "3.15"
|
||||
torchaudio==2.6.0+cu124 ; python_version >= "3.10" and python_version < "3.15"
|
||||
torchvision==0.21.0+cu124 ; python_version >= "3.10" and python_version < "3.15"
|
||||
tqdm==4.67.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
transformers==4.57.1 ; python_version >= "3.10" and python_version < "3.15"
|
||||
triton==3.5.0 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
transformers==4.48.3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
triton==3.2.0 ; python_version >= "3.10" and python_version < "3.15" and platform_system == "Linux" and platform_machine == "x86_64"
|
||||
typing-extensions==4.15.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
urllib3==2.5.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
xformers==0.0.29.post3 ; python_version >= "3.10" and python_version < "3.15"
|
||||
zipp==3.23.0 ; python_version >= "3.10" and python_version < "3.15"
|
||||
|
||||
@@ -4,17 +4,24 @@ from dependency_injector import containers, providers
|
||||
|
||||
from ..infra.diffusers import (
|
||||
DiffusersAutoencoderRepository,
|
||||
DiffusersLcmLoraRepository,
|
||||
DiffusersPipelineRepository,
|
||||
DiffusersSamplerRepository,
|
||||
DiffusersSchedulerRepository,
|
||||
DiffusersStreamDiffusionRepository,
|
||||
DiffusersTextEncoderRepository,
|
||||
)
|
||||
from ..usecase import (
|
||||
AutoencoderUsecase,
|
||||
ClipTextEncodeUsecase,
|
||||
LcmLoraUsecase,
|
||||
PipelineUsecase,
|
||||
SamplerUsecase,
|
||||
SchedulerUsecase,
|
||||
StreamDiffusionCreateStreamUsecase,
|
||||
StreamDiffusionFastSampleUsecase,
|
||||
StreamDiffusionSampleUsecase,
|
||||
StreamDiffusionWarmupUsecase,
|
||||
)
|
||||
|
||||
|
||||
@@ -27,6 +34,8 @@ class Container(containers.DeclarativeContainer):
|
||||
text_encoder_repository = providers.Factory(DiffusersTextEncoderRepository)
|
||||
sampler_repository = providers.Factory(DiffusersSamplerRepository)
|
||||
scheduler_repository = providers.Factory(DiffusersSchedulerRepository)
|
||||
lcm_lora_repository = providers.Factory(DiffusersLcmLoraRepository)
|
||||
stream_diffusion_repository = providers.Factory(DiffusersStreamDiffusionRepository)
|
||||
|
||||
# 2. Usecases
|
||||
pipeline_usecase = providers.Factory(
|
||||
@@ -49,3 +58,23 @@ class Container(containers.DeclarativeContainer):
|
||||
SchedulerUsecase,
|
||||
scheduler_repo=scheduler_repository,
|
||||
)
|
||||
lcm_lora_usecase = providers.Factory(
|
||||
LcmLoraUsecase,
|
||||
lcm_lora_repo=lcm_lora_repository,
|
||||
)
|
||||
stream_diffusion_create_stream_usecase = providers.Factory(
|
||||
StreamDiffusionCreateStreamUsecase,
|
||||
stream_diffusion_repo=stream_diffusion_repository,
|
||||
)
|
||||
stream_diffusion_warmup_usecase = providers.Factory(
|
||||
StreamDiffusionWarmupUsecase,
|
||||
stream_diffusion_repo=stream_diffusion_repository,
|
||||
)
|
||||
stream_diffusion_sample_usecase = providers.Factory(
|
||||
StreamDiffusionSampleUsecase,
|
||||
stream_diffusion_repo=stream_diffusion_repository,
|
||||
)
|
||||
stream_diffusion_fast_sample_usecase = providers.Factory(
|
||||
StreamDiffusionFastSampleUsecase,
|
||||
stream_diffusion_repo=stream_diffusion_repository,
|
||||
)
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
from ._autoencoder import Autoencoder
|
||||
from ._cfg_scale import CFGScale
|
||||
from ._cfg_type import CFGType, CFGTypeEnum
|
||||
from ._clip import Clip
|
||||
from ._conditioning import Conditioning
|
||||
from ._delta import Delta
|
||||
from ._frame_buffer_size import FrameBufferSize
|
||||
from ._image import Image
|
||||
from ._image_size import ImageSize
|
||||
from ._lcm_lora import LcmLora
|
||||
from ._num_samples import NumSamples
|
||||
from ._pipeline import Pipeline
|
||||
from ._scheduler import Scheduler
|
||||
from ._seed import Seed
|
||||
from ._steps import Steps
|
||||
from ._stream_diffusion_stream import StreamDiffusionStream
|
||||
from ._t_index_list import TIndexList
|
||||
from ._warmup_count import WarmupCount
|
||||
|
||||
__all__ = [
|
||||
"Pipeline",
|
||||
@@ -20,4 +28,13 @@ __all__ = [
|
||||
"Steps",
|
||||
"CFGScale",
|
||||
"Seed",
|
||||
"LcmLora",
|
||||
"TIndexList",
|
||||
"FrameBufferSize",
|
||||
"CFGType",
|
||||
"CFGTypeEnum",
|
||||
"Delta",
|
||||
"WarmupCount",
|
||||
"NumSamples",
|
||||
"StreamDiffusionStream",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class CFGTypeEnum(str, Enum):
|
||||
"""CFG type options for Stream Diffusion."""
|
||||
|
||||
NONE = "none"
|
||||
FULL = "full"
|
||||
SELF = "self"
|
||||
INITIALIZE = "initialize"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CFGType:
|
||||
"""CFG type configuration for Stream Diffusion."""
|
||||
|
||||
value: CFGTypeEnum
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.value, CFGTypeEnum):
|
||||
raise ValueError(f"CFG type must be a CFGTypeEnum, got {type(self.value).__name__}")
|
||||
@@ -0,0 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Delta:
|
||||
"""Delta value for Stream Diffusion."""
|
||||
|
||||
value: float
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.value, bool) or not isinstance(self.value, float):
|
||||
raise ValueError(f"Delta must be a float, got {type(self.value).__name__}")
|
||||
if not (0.0 <= self.value <= 1.0):
|
||||
raise ValueError(f"Delta must be between 0.0 and 1.0, got {self.value}")
|
||||
@@ -0,0 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class FrameBufferSize:
|
||||
"""Frame buffer size for Stream Diffusion."""
|
||||
|
||||
value: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.value, bool) or not isinstance(self.value, int):
|
||||
raise ValueError(f"Frame buffer size must be an int, got {type(self.value).__name__}")
|
||||
if self.value <= 0:
|
||||
raise ValueError(f"Frame buffer size must be positive, got {self.value}")
|
||||
@@ -0,0 +1,16 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LcmLora:
|
||||
"""LCM LoRA weights for Stream Diffusion."""
|
||||
|
||||
weights: dict[str, torch.Tensor]
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.weights, dict):
|
||||
raise ValueError(f"LCM LoRA weights must be a dict, got {type(self.weights).__name__}")
|
||||
if not self.weights:
|
||||
raise ValueError("LCM LoRA weights cannot be empty")
|
||||
@@ -0,0 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NumSamples:
|
||||
"""Number of samples to generate."""
|
||||
|
||||
value: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.value, bool) or not isinstance(self.value, int):
|
||||
raise ValueError(f"Number of samples must be an int, got {type(self.value).__name__}")
|
||||
if self.value <= 0:
|
||||
raise ValueError(f"Number of samples must be positive, got {self.value}")
|
||||
@@ -0,0 +1,16 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from streamdiffusion import StreamDiffusion # type: ignore[import-untyped]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StreamDiffusionStream:
|
||||
"""Stream Diffusion stream instance."""
|
||||
|
||||
stream: StreamDiffusion
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.stream is None:
|
||||
raise ValueError("Stream cannot be None")
|
||||
# We can't check for exact type here as it would create circular dependency
|
||||
# The actual type checking will be done at runtime in the repository layer
|
||||
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TIndexList:
|
||||
"""Value object that validates and stores the t-index sequence."""
|
||||
|
||||
raw_value: str
|
||||
value: tuple[int, ...] = field(init=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.raw_value, str):
|
||||
raise ValueError("t_index_list must be provided as a string.")
|
||||
|
||||
try:
|
||||
parsed = json.loads(self.raw_value)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ValueError("t_index_list must be a JSON array formatted string.") from exc
|
||||
|
||||
if not isinstance(parsed, list) or not parsed:
|
||||
raise ValueError("t_index_list must be a non-empty list of integers.")
|
||||
|
||||
validated: list[int] = []
|
||||
for idx, item in enumerate(parsed):
|
||||
if isinstance(item, bool) or not isinstance(item, int):
|
||||
raise ValueError(f"t_index_list[{idx}] must be an int, got {type(item).__name__}.")
|
||||
validated.append(item)
|
||||
|
||||
object.__setattr__(self, "value", tuple(validated))
|
||||
|
||||
def as_list(self) -> list[int]:
|
||||
return list(self.value)
|
||||
@@ -0,0 +1,14 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class WarmupCount:
|
||||
"""Number of warmup iterations for Stream Diffusion."""
|
||||
|
||||
value: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.value, bool) or not isinstance(self.value, int):
|
||||
raise ValueError(f"Warmup count must be an int, got {type(self.value).__name__}")
|
||||
if self.value < 0:
|
||||
raise ValueError(f"Warmup count must be non-negative, got {self.value}")
|
||||
@@ -1,7 +1,9 @@
|
||||
from ._autoencoder_repository import AutoencoderRepository
|
||||
from ._lcm_lora_repository import LcmLoraRepository
|
||||
from ._pipeline_repository import PipelineRepository
|
||||
from ._sampler_repository import SamplerRepository
|
||||
from ._scheduler_repository import SchedulerRepository
|
||||
from ._stream_diffusion_repository import StreamDiffusionRepository
|
||||
from ._text_encoder_repository import TextEncoderRepository
|
||||
|
||||
__all__ = [
|
||||
@@ -10,4 +12,6 @@ __all__ = [
|
||||
"TextEncoderRepository",
|
||||
"SamplerRepository",
|
||||
"SchedulerRepository",
|
||||
"LcmLoraRepository",
|
||||
"StreamDiffusionRepository",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
class LcmLoraRepository(ABC):
|
||||
@abstractmethod
|
||||
def load_lcm_lora(self, lora_path: str) -> dict[str, torch.Tensor]:
|
||||
"""Load LCM LoRA weights from file.
|
||||
|
||||
Args:
|
||||
lora_path: Path to the LCM LoRA file
|
||||
|
||||
Returns:
|
||||
Dictionary containing the loaded weights
|
||||
"""
|
||||
@@ -0,0 +1,137 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
from diffusers import StableDiffusionPipeline
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
from PIL import Image
|
||||
from streamdiffusion import StreamDiffusion # type: ignore[import-untyped]
|
||||
|
||||
from ..model import (
|
||||
CFGScale,
|
||||
CFGType,
|
||||
Delta,
|
||||
FrameBufferSize,
|
||||
ImageSize,
|
||||
NumSamples,
|
||||
Seed,
|
||||
Steps,
|
||||
TIndexList,
|
||||
WarmupCount,
|
||||
)
|
||||
|
||||
|
||||
class StreamDiffusionRepository(ABC):
|
||||
@abstractmethod
|
||||
def create_stream(
|
||||
self,
|
||||
pipeline: StableDiffusionPipeline,
|
||||
scheduler: SchedulerMixin,
|
||||
t_index_list: TIndexList,
|
||||
image_size: ImageSize,
|
||||
do_add_noise: bool,
|
||||
use_denoising_batch: bool,
|
||||
frame_buffer_size: FrameBufferSize,
|
||||
cfg_type: CFGType,
|
||||
lcm_lora_weights: dict[str, torch.Tensor],
|
||||
tiny_vae_name: str,
|
||||
enable_xformers: bool,
|
||||
) -> StreamDiffusion:
|
||||
"""Create a Stream Diffusion stream instance.
|
||||
|
||||
Args:
|
||||
pipeline: StableDiffusionPipeline instance
|
||||
scheduler: Scheduler to attach to the pipeline
|
||||
t_index_list: List of timestep indices
|
||||
image_size: Image dimensions
|
||||
do_add_noise: Whether to add noise
|
||||
use_denoising_batch: Whether to use denoising batch
|
||||
frame_buffer_size: Size of frame buffer
|
||||
cfg_type: CFG type configuration
|
||||
lcm_lora_weights: LCM LoRA weights
|
||||
tiny_vae_name: Name of tiny VAE model
|
||||
enable_xformers: Whether to enable xformers memory efficient attention
|
||||
|
||||
Returns:
|
||||
StreamDiffusion instance
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def warmup_stream(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
warmup_count: WarmupCount,
|
||||
input_image: Image.Image | None = None,
|
||||
) -> None:
|
||||
"""Warm up the stream with given parameters.
|
||||
|
||||
Args:
|
||||
stream: StreamDiffusion instance
|
||||
warmup_count: Number of warmup iterations
|
||||
input_image: Optional input image for img2img warmup
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def prepare_stream(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
prompt: str,
|
||||
negative_prompt: str,
|
||||
steps: Steps,
|
||||
cfg: CFGScale,
|
||||
delta: Delta,
|
||||
seed: Seed,
|
||||
) -> None:
|
||||
"""Prepare the stream with given parameters.
|
||||
|
||||
Args:
|
||||
stream: StreamDiffusion instance
|
||||
prompt: Prompt text
|
||||
negative_prompt: Negative prompt text
|
||||
steps: Number of inference steps
|
||||
cfg: CFG scale value
|
||||
delta: Delta value
|
||||
seed: Random seed
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update_prompt(self, stream: StreamDiffusion, prompt: str) -> None:
|
||||
"""Update the prompt for the stream.
|
||||
|
||||
Args:
|
||||
stream: StreamDiffusion instance
|
||||
prompt: New prompt text
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def sample_txt2img(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
num_samples: NumSamples,
|
||||
) -> list[Image.Image]:
|
||||
"""Generate images using txt2img.
|
||||
|
||||
Args:
|
||||
stream: StreamDiffusion instance
|
||||
num_samples: Number of images to generate
|
||||
|
||||
Returns:
|
||||
List of generated PIL images
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def sample_with_images(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
num_samples: NumSamples,
|
||||
input_images: list[Image.Image] | None,
|
||||
) -> list[Image.Image]:
|
||||
"""Generate images with optional input images.
|
||||
|
||||
Args:
|
||||
stream: StreamDiffusion instance
|
||||
num_samples: Number of images to generate
|
||||
input_images: Optional list of input images
|
||||
|
||||
Returns:
|
||||
List of generated PIL images
|
||||
"""
|
||||
@@ -1,7 +1,9 @@
|
||||
from ._autoencoder_repository import DiffusersAutoencoderRepository
|
||||
from ._lcm_lora_repository import DiffusersLcmLoraRepository
|
||||
from ._pipeline_repository import DiffusersPipelineRepository
|
||||
from ._sampler_repository import DiffusersSamplerRepository
|
||||
from ._scheduler_repository import DiffusersSchedulerRepository
|
||||
from ._stream_diffusion_repository import DiffusersStreamDiffusionRepository
|
||||
from ._text_encoder_repository import DiffusersTextEncoderRepository
|
||||
|
||||
__all__ = [
|
||||
@@ -10,4 +12,6 @@ __all__ = [
|
||||
"DiffusersTextEncoderRepository",
|
||||
"DiffusersSamplerRepository",
|
||||
"DiffusersSchedulerRepository",
|
||||
"DiffusersLcmLoraRepository",
|
||||
"DiffusersStreamDiffusionRepository",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import torch
|
||||
from safetensors.torch import load_file
|
||||
|
||||
from ...domain.repositories import LcmLoraRepository
|
||||
|
||||
|
||||
class DiffusersLcmLoraRepository(LcmLoraRepository):
|
||||
def load_lcm_lora(self, lora_path: str) -> dict[str, torch.Tensor]:
|
||||
"""Load LCM LoRA weights from safetensors file.
|
||||
|
||||
Args:
|
||||
lora_path: Path to the LCM LoRA safetensors file
|
||||
|
||||
Returns:
|
||||
Dictionary containing the loaded weights
|
||||
"""
|
||||
return load_file(lora_path)
|
||||
@@ -36,4 +36,10 @@ class DiffusersPipelineRepository(PipelineRepository):
|
||||
torch_dtype=dtype,
|
||||
cache_dir=self.cache_dir,
|
||||
).to(self.device)
|
||||
pipe.safety_checker = ( # type: ignore[attr-defined]
|
||||
None
|
||||
if pipe.safety_checker is None # type: ignore[attr-defined]
|
||||
else lambda images, **_kwargs: (images, [False])
|
||||
)
|
||||
pipe.enable_attention_slicing() # type: ignore[attr-defined]
|
||||
return pipe
|
||||
|
||||
@@ -24,10 +24,10 @@ class DiffusersSamplerRepository(SamplerRepository):
|
||||
cfg: CFGScale,
|
||||
seed: Seed,
|
||||
) -> list[Image.Image]:
|
||||
result = pipeline( # type: ignore[operator]
|
||||
pipeline.vae = vae # type: ignore[attr-defined]
|
||||
pipeline.scheduler = scheduler # type: ignore[attr-defined]
|
||||
result = pipeline.to(self.device)( # type: ignore[attr-defined]
|
||||
prompt_embeds=positive_embeds,
|
||||
vae=vae,
|
||||
scheduler=scheduler,
|
||||
height=image_size.height,
|
||||
width=image_size.width,
|
||||
num_inference_steps=steps.value,
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import copy
|
||||
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device # pyright: ignore[reportMissingImports]
|
||||
from diffusers import AutoencoderTiny, StableDiffusionPipeline
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
from PIL import Image
|
||||
from streamdiffusion import StreamDiffusion # type: ignore[import-untyped]
|
||||
from streamdiffusion.image_utils import postprocess_image # type: ignore[import-untyped]
|
||||
|
||||
from ...domain.model import (
|
||||
CFGScale,
|
||||
CFGType,
|
||||
Delta,
|
||||
FrameBufferSize,
|
||||
ImageSize,
|
||||
NumSamples,
|
||||
Seed,
|
||||
Steps,
|
||||
TIndexList,
|
||||
WarmupCount,
|
||||
)
|
||||
from ...domain.repositories import StreamDiffusionRepository
|
||||
from ._cache import get_cache_path
|
||||
|
||||
|
||||
class DiffusersStreamDiffusionRepository(StreamDiffusionRepository):
|
||||
def __init__(self) -> None:
|
||||
self.device = get_torch_device()
|
||||
self.cache_dir = get_cache_path()
|
||||
self.dtype = torch.float16
|
||||
|
||||
def create_stream(
|
||||
self,
|
||||
pipeline: StableDiffusionPipeline,
|
||||
scheduler: SchedulerMixin,
|
||||
t_index_list: TIndexList,
|
||||
image_size: ImageSize,
|
||||
do_add_noise: bool,
|
||||
use_denoising_batch: bool,
|
||||
frame_buffer_size: FrameBufferSize,
|
||||
cfg_type: CFGType,
|
||||
lcm_lora_weights: dict[str, torch.Tensor],
|
||||
tiny_vae_name: str,
|
||||
enable_xformers: bool,
|
||||
) -> StreamDiffusion:
|
||||
"""Create a Stream Diffusion stream instance."""
|
||||
# Deep copy to avoid modifying the original pipeline
|
||||
# Note: load_lcm_lora() and fuse_lora() modify the pipeline,
|
||||
# so we need to copy it to allow reusing the original pipeline in ComfyUI
|
||||
pipeline_copy = copy.deepcopy(pipeline)
|
||||
pipeline_copy.scheduler = scheduler # type: ignore[attr-defined]
|
||||
lora_weights_copy = copy.deepcopy(lcm_lora_weights)
|
||||
|
||||
# Create stream
|
||||
stream = StreamDiffusion(
|
||||
pipe=pipeline_copy,
|
||||
t_index_list=t_index_list.as_list(),
|
||||
torch_dtype=self.dtype,
|
||||
width=image_size.width,
|
||||
height=image_size.height,
|
||||
do_add_noise=do_add_noise,
|
||||
use_denoising_batch=use_denoising_batch,
|
||||
frame_buffer_size=frame_buffer_size.value,
|
||||
cfg_type=cfg_type.value.value, # Get the string value from enum
|
||||
)
|
||||
|
||||
# Load and fuse LCM LoRA
|
||||
# Pass ignore_mismatched_sizes to tolerate conv weight shape differences (e.g., meta tensors)
|
||||
stream.load_lcm_lora(
|
||||
pretrained_model_name_or_path_or_dict=lora_weights_copy,
|
||||
low_cpu_mem_usage=False,
|
||||
ignore_mismatched_sizes=True,
|
||||
)
|
||||
stream.fuse_lora()
|
||||
|
||||
# Load tiny VAE
|
||||
stream.vae = AutoencoderTiny.from_pretrained( # type: ignore[no-untyped-call]
|
||||
pretrained_model_name_or_path=tiny_vae_name,
|
||||
torch_dtype=self.dtype,
|
||||
cache_dir=self.cache_dir,
|
||||
).to(device=pipeline_copy.device, dtype=pipeline_copy.dtype) # type: ignore[attr-defined]
|
||||
|
||||
# Enable xformers if requested
|
||||
if enable_xformers:
|
||||
pipeline_copy.enable_xformers_memory_efficient_attention() # type: ignore[attr-defined]
|
||||
|
||||
return stream
|
||||
|
||||
def warmup_stream(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
warmup_count: WarmupCount,
|
||||
input_image: Image.Image | None = None,
|
||||
) -> None:
|
||||
"""Warm up the stream with given parameters."""
|
||||
if input_image is not None:
|
||||
# Resize input image to match stream dimensions
|
||||
resized_image = input_image.resize((stream.width, stream.height))
|
||||
for _ in range(warmup_count.value):
|
||||
stream(resized_image)
|
||||
else:
|
||||
for _ in range(warmup_count.value):
|
||||
stream()
|
||||
|
||||
def prepare_stream(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
prompt: str,
|
||||
negative_prompt: str,
|
||||
steps: Steps,
|
||||
cfg: CFGScale,
|
||||
delta: Delta,
|
||||
seed: Seed,
|
||||
) -> None:
|
||||
"""Prepare the stream with given parameters."""
|
||||
stream.prepare(
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
num_inference_steps=steps.value,
|
||||
guidance_scale=cfg.value,
|
||||
delta=delta.value,
|
||||
generator=torch.Generator().manual_seed(seed.value),
|
||||
seed=seed.value,
|
||||
)
|
||||
|
||||
def update_prompt(self, stream: StreamDiffusion, prompt: str) -> None:
|
||||
"""Update the prompt for the stream."""
|
||||
stream.update_prompt(prompt)
|
||||
|
||||
def sample_txt2img(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
num_samples: NumSamples,
|
||||
) -> list[Image.Image]:
|
||||
"""Generate images using txt2img."""
|
||||
result: list[Image.Image] = []
|
||||
for _ in range(num_samples.value):
|
||||
x_output = stream.txt2img()
|
||||
image = postprocess_image(x_output, output_type="pil")[0]
|
||||
result.append(image)
|
||||
return result
|
||||
|
||||
def sample_with_images(
|
||||
self,
|
||||
stream: StreamDiffusion,
|
||||
num_samples: NumSamples,
|
||||
input_images: list[Image.Image] | None,
|
||||
) -> list[Image.Image]:
|
||||
"""Generate images with optional input images."""
|
||||
# Resize input images if provided
|
||||
if input_images is not None:
|
||||
resized_images = [img.resize((stream.width, stream.height)) for img in input_images]
|
||||
else:
|
||||
resized_images = None
|
||||
|
||||
# Generate images
|
||||
result: list[Image.Image] = []
|
||||
if resized_images is None:
|
||||
# Text-to-image: Generate num_samples images
|
||||
for _ in range(num_samples.value):
|
||||
x_output = stream.txt2img()
|
||||
image = postprocess_image(x_output, output_type="pil")[0]
|
||||
result.append(image)
|
||||
else:
|
||||
# Image-to-image: Process each input image num_samples times
|
||||
for _ in range(num_samples.value):
|
||||
for img in resized_images:
|
||||
x_output = stream(img)
|
||||
image = postprocess_image(x_output, output_type="pil")[0]
|
||||
result.append(image)
|
||||
|
||||
return result
|
||||
@@ -1,7 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device # pyright: ignore[reportMissingImports]
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
@@ -35,7 +33,7 @@ class DiffusersTextEncoderRepository(TextEncoderRepository):
|
||||
with torch.no_grad():
|
||||
for i in range(0, text_ids.shape[-1], max_length):
|
||||
segment_ids = text_ids[:, i : i + max_length]
|
||||
outputs: Any = text_encoder(segment_ids)
|
||||
outputs = text_encoder(segment_ids)
|
||||
if hasattr(outputs, "last_hidden_state"):
|
||||
embeds = outputs.last_hidden_state
|
||||
else:
|
||||
|
||||
@@ -4,12 +4,20 @@ from ._diffusers_pipeline_loader import DiffusersPipelineLoader
|
||||
from ._diffusers_sampler import DiffusersSampler
|
||||
from ._diffusers_scheduler_loader import DiffusersSchedulerLoader
|
||||
from ._diffusers_vae_loader import DiffusersVaeLoader
|
||||
from ._lcm_lora_loader import LcmLoraLoader
|
||||
from ._stream_diffusion_create_stream import StreamDiffusionCreateStream
|
||||
from ._stream_diffusion_fast_sampler import StreamDiffusionFastSampler
|
||||
from ._stream_diffusion_sampler import StreamDiffusionSampler
|
||||
from ._stream_diffusion_warmup import StreamDiffusionWarmup
|
||||
from .dto import (
|
||||
ComfyUIAutoencoderDTO,
|
||||
ComfyUIClipDTO,
|
||||
ComfyUIConditioningDTO,
|
||||
ComfyUILcmLoraDTO,
|
||||
ComfyUIPipelineDTO,
|
||||
ComfyUISchedulerDTO,
|
||||
ComfyUIStreamDTO,
|
||||
ComfyUIWarmupStreamDTO,
|
||||
)
|
||||
|
||||
container = Container()
|
||||
@@ -21,6 +29,11 @@ NODE_CLASS_MAPPINGS = {
|
||||
DiffusersClipTextEncode.__name__: DiffusersClipTextEncode,
|
||||
DiffusersSampler.__name__: DiffusersSampler,
|
||||
DiffusersSchedulerLoader.__name__: DiffusersSchedulerLoader,
|
||||
LcmLoraLoader.__name__: LcmLoraLoader,
|
||||
StreamDiffusionCreateStream.__name__: StreamDiffusionCreateStream,
|
||||
StreamDiffusionWarmup.__name__: StreamDiffusionWarmup,
|
||||
StreamDiffusionSampler.__name__: StreamDiffusionSampler,
|
||||
StreamDiffusionFastSampler.__name__: StreamDiffusionFastSampler,
|
||||
}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
DiffusersPipelineLoader.__name__: "Diffusers Pipeline Loader",
|
||||
@@ -28,6 +41,11 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
DiffusersClipTextEncode.__name__: "Diffusers CLIP Text Encode",
|
||||
DiffusersSampler.__name__: "Diffusers Sampler",
|
||||
DiffusersSchedulerLoader.__name__: "Diffusers Scheduler Loader",
|
||||
LcmLoraLoader.__name__: "LCM LoRA Loader",
|
||||
StreamDiffusionCreateStream.__name__: "StreamDiffusion Create Stream",
|
||||
StreamDiffusionWarmup.__name__: "StreamDiffusion Warmup",
|
||||
StreamDiffusionSampler.__name__: "StreamDiffusion Sampler",
|
||||
StreamDiffusionFastSampler.__name__: "StreamDiffusion Fast Sampler",
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
@@ -38,4 +56,7 @@ __all__ = [
|
||||
"ComfyUIClipDTO",
|
||||
"ComfyUIConditioningDTO",
|
||||
"ComfyUISchedulerDTO",
|
||||
"ComfyUILcmLoraDTO",
|
||||
"ComfyUIStreamDTO",
|
||||
"ComfyUIWarmupStreamDTO",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import folder_paths # pyright: ignore[reportMissingImports]
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..usecase import LcmLoraUsecase
|
||||
from .dto import ComfyUILcmLoraDTO
|
||||
|
||||
|
||||
class LcmLoraLoader:
|
||||
"""Node to load LCM LoRA weights for Stream Diffusion."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict[str, dict[str, tuple[tuple[str, ...], ...]]]:
|
||||
return {
|
||||
"required": {
|
||||
"lora_name": (folder_paths.get_filename_list("loras"),),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUILcmLoraDTO.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
lora_name: str,
|
||||
usecase: LcmLoraUsecase = Provide[Container.lcm_lora_usecase],
|
||||
) -> tuple[ComfyUILcmLoraDTO]:
|
||||
"""Load LCM LoRA weights.
|
||||
|
||||
Args:
|
||||
lora_name: Name of the LoRA file
|
||||
usecase: Injected LcmLoraUsecase
|
||||
|
||||
Returns:
|
||||
Tuple containing ComfyUILcmLoraDTO
|
||||
"""
|
||||
lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||
lcm_lora = usecase.execute(lora_path)
|
||||
dto = ComfyUILcmLoraDTO.from_domain(lcm_lora)
|
||||
return (dto,)
|
||||
@@ -0,0 +1,112 @@
|
||||
from typing import Any
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..domain.model import CFGType, CFGTypeEnum, FrameBufferSize, ImageSize, TIndexList
|
||||
from ..usecase import StreamDiffusionCreateStreamUsecase
|
||||
from .dto import (
|
||||
ComfyUILcmLoraDTO,
|
||||
ComfyUIPipelineDTO,
|
||||
ComfyUISchedulerDTO,
|
||||
ComfyUIStreamDTO,
|
||||
)
|
||||
|
||||
NodeInputMap = dict[str, dict[str, tuple[str, ...] | tuple[str | list[str], dict[str, Any]]]]
|
||||
|
||||
|
||||
class StreamDiffusionCreateStream:
|
||||
"""Node to create a Stream Diffusion stream."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(
|
||||
cls,
|
||||
) -> NodeInputMap:
|
||||
return {
|
||||
"required": {
|
||||
"pipeline": (ComfyUIPipelineDTO.COMFY_TYPE,),
|
||||
"scheduler": (ComfyUISchedulerDTO.COMFY_TYPE,),
|
||||
"t_index_list": ("STRING", {"default": "[0, 16, 32, 45]"}),
|
||||
"width": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}),
|
||||
"height": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}),
|
||||
"do_add_noise": ("BOOLEAN", {"default": True}),
|
||||
"use_denoising_batch": ("BOOLEAN", {"default": True}),
|
||||
"frame_buffer_size": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"cfg_type": (["none", "full", "self", "initialize"], {"default": "none"}),
|
||||
"lcm_lora": (ComfyUILcmLoraDTO.COMFY_TYPE,),
|
||||
"tiny_vae": ("STRING", {"default": "madebyollin/taesd"}),
|
||||
"enable_xformers": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUIStreamDTO.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
pipeline: ComfyUIPipelineDTO,
|
||||
scheduler: ComfyUISchedulerDTO,
|
||||
t_index_list: str,
|
||||
width: int,
|
||||
height: int,
|
||||
do_add_noise: bool,
|
||||
use_denoising_batch: bool,
|
||||
frame_buffer_size: int,
|
||||
cfg_type: str,
|
||||
lcm_lora: ComfyUILcmLoraDTO,
|
||||
tiny_vae: str,
|
||||
enable_xformers: bool,
|
||||
usecase: StreamDiffusionCreateStreamUsecase = Provide[
|
||||
Container.stream_diffusion_create_stream_usecase
|
||||
],
|
||||
) -> tuple[ComfyUIStreamDTO]:
|
||||
"""Create a Stream Diffusion stream.
|
||||
|
||||
Args:
|
||||
pipeline: Pipeline DTO
|
||||
scheduler: Scheduler DTO
|
||||
t_index_list: JSON array string of timestep indices
|
||||
width: Image width
|
||||
height: Image height
|
||||
do_add_noise: Whether to add noise
|
||||
use_denoising_batch: Whether to use denoising batch
|
||||
frame_buffer_size: Size of frame buffer
|
||||
cfg_type: CFG type string
|
||||
lcm_lora: LCM LoRA DTO
|
||||
tiny_vae: Name of tiny VAE model
|
||||
enable_xformers: Whether to enable xformers
|
||||
usecase: Injected StreamDiffusionCreateStreamUsecase
|
||||
|
||||
Returns:
|
||||
Tuple containing ComfyUIStreamDTO
|
||||
"""
|
||||
pipeline_domain = ComfyUIPipelineDTO.to_domain(pipeline)
|
||||
scheduler_domain = ComfyUISchedulerDTO.to_domain(scheduler)
|
||||
lcm_lora_domain = ComfyUILcmLoraDTO.to_domain(lcm_lora)
|
||||
|
||||
t_index_list_vo = TIndexList(raw_value=t_index_list)
|
||||
image_size = ImageSize(width=width, height=height)
|
||||
frame_buffer_size_vo = FrameBufferSize(value=frame_buffer_size)
|
||||
cfg_type_vo = CFGType(value=CFGTypeEnum(cfg_type))
|
||||
|
||||
stream = usecase.execute(
|
||||
pipeline=pipeline_domain,
|
||||
scheduler=scheduler_domain,
|
||||
t_index_list=t_index_list_vo,
|
||||
image_size=image_size,
|
||||
do_add_noise=do_add_noise,
|
||||
use_denoising_batch=use_denoising_batch,
|
||||
frame_buffer_size=frame_buffer_size_vo,
|
||||
cfg_type=cfg_type_vo,
|
||||
lcm_lora=lcm_lora_domain,
|
||||
tiny_vae_name=tiny_vae,
|
||||
enable_xformers=enable_xformers,
|
||||
)
|
||||
|
||||
dto = ComfyUIStreamDTO.from_domain(stream)
|
||||
return (dto,)
|
||||
@@ -0,0 +1,66 @@
|
||||
from typing import Any
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..domain.model import NumSamples
|
||||
from ..usecase import StreamDiffusionFastSampleUsecase
|
||||
from .dto import ComfyUIImage, ComfyUIImageDTO, ComfyUIWarmupStreamDTO
|
||||
|
||||
NodeInputMap = dict[str, dict[str, tuple[str, ...] | tuple[str | list[str], dict[str, Any]]]]
|
||||
|
||||
|
||||
class StreamDiffusionFastSampler:
|
||||
"""Node for fast sampling from a warmed-up Stream Diffusion stream."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(
|
||||
cls,
|
||||
) -> NodeInputMap:
|
||||
return {
|
||||
"required": {
|
||||
"warmup_stream": (ComfyUIWarmupStreamDTO.COMFY_TYPE,),
|
||||
"positive_prompt": ("STRING", {"multiline": True}),
|
||||
"num": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUIImage.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
warmup_stream: ComfyUIWarmupStreamDTO,
|
||||
positive_prompt: str,
|
||||
num: int,
|
||||
usecase: StreamDiffusionFastSampleUsecase = Provide[
|
||||
Container.stream_diffusion_fast_sample_usecase
|
||||
],
|
||||
) -> tuple[ComfyUIImageDTO]:
|
||||
"""Fast sample images from a warmed-up stream.
|
||||
|
||||
Args:
|
||||
warmup_stream: Warmed-up stream DTO
|
||||
positive_prompt: Prompt text
|
||||
num: Number of images to generate
|
||||
usecase: Injected StreamDiffusionFastSampleUsecase
|
||||
|
||||
Returns:
|
||||
Tuple containing image tensor
|
||||
"""
|
||||
stream_domain = ComfyUIWarmupStreamDTO.to_domain(warmup_stream)
|
||||
num_vo = NumSamples(value=num)
|
||||
|
||||
images = usecase.execute(
|
||||
stream=stream_domain,
|
||||
prompt=positive_prompt,
|
||||
num_samples=num_vo,
|
||||
)
|
||||
|
||||
result_dto = ComfyUIImage.from_domains(images)
|
||||
return (result_dto,)
|
||||
@@ -0,0 +1,117 @@
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
from PIL import Image as PilImage
|
||||
|
||||
from ..di import Container
|
||||
from ..domain.model import CFGScale, Delta, Image, NumSamples, Seed, Steps, WarmupCount
|
||||
from ..usecase import StreamDiffusionSampleUsecase
|
||||
from .dto import ComfyUIConditioningDTO, ComfyUIImage, ComfyUIImageDTO, ComfyUIStreamDTO
|
||||
|
||||
NodeInputMap = dict[str, dict[str, tuple[str, ...] | tuple[str | list[str], dict[str, Any]]]]
|
||||
|
||||
|
||||
class StreamDiffusionSampler:
|
||||
"""Node to sample images from a Stream Diffusion stream."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(
|
||||
cls,
|
||||
) -> NodeInputMap:
|
||||
return {
|
||||
"required": {
|
||||
"stream": (ComfyUIStreamDTO.COMFY_TYPE,),
|
||||
"positive_conditioning": (ComfyUIConditioningDTO.COMFY_TYPE,),
|
||||
"negative_conditioning": (ComfyUIConditioningDTO.COMFY_TYPE,),
|
||||
"steps": ("INT", {"default": 50, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 100.0}),
|
||||
"delta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
|
||||
"num": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
"warmup": ("INT", {"default": 10, "min": 0, "max": 10000}),
|
||||
},
|
||||
"optional": {
|
||||
"image": (ComfyUIImage.COMFY_TYPE,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUIImage.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
stream: ComfyUIStreamDTO,
|
||||
positive_conditioning: ComfyUIConditioningDTO,
|
||||
negative_conditioning: ComfyUIConditioningDTO,
|
||||
steps: int,
|
||||
cfg: float,
|
||||
delta: float,
|
||||
seed: int,
|
||||
num: int,
|
||||
warmup: int,
|
||||
image: ComfyUIImageDTO | None = None,
|
||||
usecase: StreamDiffusionSampleUsecase = Provide[Container.stream_diffusion_sample_usecase],
|
||||
) -> tuple[ComfyUIImageDTO]:
|
||||
"""Sample images from a Stream Diffusion stream.
|
||||
|
||||
Args:
|
||||
stream: Stream DTO
|
||||
positive_conditioning: Positive conditioning from CLIP Text Encode
|
||||
negative_conditioning: Negative conditioning from CLIP Text Encode
|
||||
steps: Number of inference steps
|
||||
cfg: CFG scale value
|
||||
delta: Delta value
|
||||
seed: Random seed
|
||||
num: Number of images to generate
|
||||
warmup: Number of warmup iterations
|
||||
image: Optional input image tensor
|
||||
usecase: Injected StreamDiffusionSampleUsecase
|
||||
|
||||
Returns:
|
||||
Tuple containing image tensor
|
||||
"""
|
||||
stream_domain = ComfyUIStreamDTO.to_domain(stream)
|
||||
|
||||
positive_prompt = positive_conditioning.prompt
|
||||
negative_prompt = negative_conditioning.prompt
|
||||
|
||||
steps_vo = Steps(value=steps)
|
||||
cfg_vo = CFGScale(value=cfg)
|
||||
delta_vo = Delta(value=delta)
|
||||
seed_vo = Seed(value=seed)
|
||||
num_vo = NumSamples(value=num)
|
||||
warmup_vo = WarmupCount(value=warmup)
|
||||
|
||||
# Convert input images if provided
|
||||
input_images_domain: list[Image] | None = None
|
||||
if image is not None:
|
||||
# ComfyUI images are in format [B, H, W, C] with values in [0, 1]
|
||||
images_np = image.cpu().numpy()
|
||||
input_images_domain = []
|
||||
for img_np in images_np:
|
||||
# Convert to uint8 and create PIL image
|
||||
img_uint8 = (np.clip(img_np, 0, 1) * 255).astype(np.uint8)
|
||||
pil_img = PilImage.fromarray(img_uint8)
|
||||
input_images_domain.append(Image(image=pil_img))
|
||||
|
||||
images = usecase.execute(
|
||||
stream=stream_domain,
|
||||
prompt=positive_prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
steps=steps_vo,
|
||||
cfg=cfg_vo,
|
||||
delta=delta_vo,
|
||||
seed=seed_vo,
|
||||
num_samples=num_vo,
|
||||
warmup_count=warmup_vo,
|
||||
input_images=input_images_domain,
|
||||
)
|
||||
|
||||
result_dto = ComfyUIImage.from_domains(images)
|
||||
return (result_dto,)
|
||||
@@ -0,0 +1,85 @@
|
||||
from typing import Any
|
||||
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..domain.model import CFGScale, Delta, Seed, Steps, WarmupCount
|
||||
from ..usecase import StreamDiffusionWarmupUsecase
|
||||
from .dto import ComfyUIStreamDTO, ComfyUIWarmupStreamDTO
|
||||
|
||||
NodeInputMap = dict[str, dict[str, tuple[str, ...] | tuple[str | list[str], dict[str, Any]]]]
|
||||
|
||||
|
||||
class StreamDiffusionWarmup:
|
||||
"""Node to warm up a Stream Diffusion stream."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(
|
||||
cls,
|
||||
) -> NodeInputMap:
|
||||
return {
|
||||
"required": {
|
||||
"stream": (ComfyUIStreamDTO.COMFY_TYPE,),
|
||||
"negative_prompt": ("STRING", {"multiline": True, "default": ""}),
|
||||
"steps": ("INT", {"default": 50, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 100.0}),
|
||||
"delta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF}),
|
||||
"warmup": ("INT", {"default": 10, "min": 0, "max": 10000}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUIWarmupStreamDTO.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
stream: ComfyUIStreamDTO,
|
||||
negative_prompt: str,
|
||||
steps: int,
|
||||
cfg: float,
|
||||
delta: float,
|
||||
seed: int,
|
||||
warmup: int,
|
||||
usecase: StreamDiffusionWarmupUsecase = Provide[Container.stream_diffusion_warmup_usecase],
|
||||
) -> tuple[ComfyUIWarmupStreamDTO]:
|
||||
"""Warm up a Stream Diffusion stream.
|
||||
|
||||
Args:
|
||||
stream: Stream DTO
|
||||
negative_prompt: Negative prompt text
|
||||
steps: Number of inference steps
|
||||
cfg: CFG scale value
|
||||
delta: Delta value
|
||||
seed: Random seed
|
||||
warmup: Number of warmup iterations
|
||||
usecase: Injected StreamDiffusionWarmupUsecase
|
||||
|
||||
Returns:
|
||||
Tuple containing ComfyUIWarmupStreamDTO
|
||||
"""
|
||||
stream_domain = ComfyUIStreamDTO.to_domain(stream)
|
||||
|
||||
steps_vo = Steps(value=steps)
|
||||
cfg_vo = CFGScale(value=cfg)
|
||||
delta_vo = Delta(value=delta)
|
||||
seed_vo = Seed(value=seed)
|
||||
warmup_vo = WarmupCount(value=warmup)
|
||||
|
||||
warmed_stream = usecase.execute(
|
||||
stream=stream_domain,
|
||||
negative_prompt=negative_prompt,
|
||||
steps=steps_vo,
|
||||
cfg=cfg_vo,
|
||||
delta=delta_vo,
|
||||
seed=seed_vo,
|
||||
warmup_count=warmup_vo,
|
||||
)
|
||||
|
||||
dto = ComfyUIWarmupStreamDTO.from_domain(warmed_stream)
|
||||
return (dto,)
|
||||
@@ -2,8 +2,10 @@ from ._autoencoder_dto import ComfyUIAutoencoderDTO
|
||||
from ._clip import ComfyUIClipDTO
|
||||
from ._comfyui import ComfyUIImage, ComfyUIImageDTO
|
||||
from ._conditioning_dto import ComfyUIConditioningDTO
|
||||
from ._lcm_lora_dto import ComfyUILcmLoraDTO
|
||||
from ._pipeline_dto import ComfyUIPipelineDTO
|
||||
from ._scheduler_dto import ComfyUISchedulerDTO
|
||||
from ._stream_diffusion_stream_dto import ComfyUIStreamDTO, ComfyUIWarmupStreamDTO
|
||||
|
||||
__all__ = [
|
||||
"ComfyUIPipelineDTO",
|
||||
@@ -13,4 +15,7 @@ __all__ = [
|
||||
"ComfyUIImage",
|
||||
"ComfyUIImageDTO",
|
||||
"ComfyUISchedulerDTO",
|
||||
"ComfyUILcmLoraDTO",
|
||||
"ComfyUIStreamDTO",
|
||||
"ComfyUIWarmupStreamDTO",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
import torch
|
||||
|
||||
from ...domain.model import LcmLora
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUILcmLoraDTO:
|
||||
"""Data Transfer Object for LCM LoRA in ComfyUI.
|
||||
|
||||
Attributes:
|
||||
weights: Dictionary containing the LCM LoRA weights
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
"""
|
||||
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_LCM_LORA"
|
||||
|
||||
weights: dict[str, torch.Tensor]
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, lcm_lora: LcmLora) -> "ComfyUILcmLoraDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
lcm_lora: LcmLora from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUILcmLoraDTO instance
|
||||
"""
|
||||
return cls(weights=lcm_lora.weights)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUILcmLoraDTO") -> LcmLora:
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUILcmLoraDTO instance
|
||||
|
||||
Returns:
|
||||
LcmLora domain model instance
|
||||
"""
|
||||
return LcmLora(weights=dto.weights)
|
||||
@@ -0,0 +1,88 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from streamdiffusion import StreamDiffusion # type: ignore[import-untyped]
|
||||
|
||||
from ...domain.model import StreamDiffusionStream
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIStreamDTO:
|
||||
"""Data Transfer Object for Stream Diffusion Stream in ComfyUI.
|
||||
|
||||
Attributes:
|
||||
stream: StreamDiffusion instance from streamdiffusion library
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
"""
|
||||
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_STREAM"
|
||||
|
||||
stream: StreamDiffusion
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, stream: StreamDiffusionStream) -> "ComfyUIStreamDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
stream: StreamDiffusionStream from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUIStreamDTO instance
|
||||
"""
|
||||
return cls(stream=stream.stream)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUIStreamDTO") -> StreamDiffusionStream:
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUIStreamDTO instance
|
||||
|
||||
Returns:
|
||||
StreamDiffusionStream domain model instance
|
||||
"""
|
||||
return StreamDiffusionStream(stream=dto.stream)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIWarmupStreamDTO:
|
||||
"""Data Transfer Object for Warmed-up Stream Diffusion Stream in ComfyUI.
|
||||
|
||||
This is a separate type to distinguish warmed-up streams from fresh streams.
|
||||
|
||||
Attributes:
|
||||
stream: StreamDiffusion instance from streamdiffusion library
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
"""
|
||||
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_WARMUP_STREAM"
|
||||
|
||||
stream: StreamDiffusion
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, stream: StreamDiffusionStream) -> "ComfyUIWarmupStreamDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
stream: StreamDiffusionStream from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUIWarmupStreamDTO instance
|
||||
"""
|
||||
return cls(stream=stream.stream)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUIWarmupStreamDTO") -> StreamDiffusionStream:
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUIWarmupStreamDTO instance
|
||||
|
||||
Returns:
|
||||
StreamDiffusionStream domain model instance
|
||||
"""
|
||||
return StreamDiffusionStream(stream=dto.stream)
|
||||
@@ -1,8 +1,13 @@
|
||||
from ._autoencoder_usecase import AutoencoderUsecase
|
||||
from ._clip_text_encode_usecase import ClipTextEncodeUsecase
|
||||
from ._lcm_lora_usecase import LcmLoraUsecase
|
||||
from ._pipeline_usecase import PipelineUsecase
|
||||
from ._sampler_usecase import SamplerUsecase
|
||||
from ._scheduler_usecase import SchedulerUsecase
|
||||
from ._stream_diffusion_create_stream_usecase import StreamDiffusionCreateStreamUsecase
|
||||
from ._stream_diffusion_fast_sample_usecase import StreamDiffusionFastSampleUsecase
|
||||
from ._stream_diffusion_sample_usecase import StreamDiffusionSampleUsecase
|
||||
from ._stream_diffusion_warmup_usecase import StreamDiffusionWarmupUsecase
|
||||
|
||||
__all__ = [
|
||||
"PipelineUsecase",
|
||||
@@ -10,4 +15,9 @@ __all__ = [
|
||||
"ClipTextEncodeUsecase",
|
||||
"SamplerUsecase",
|
||||
"SchedulerUsecase",
|
||||
"LcmLoraUsecase",
|
||||
"StreamDiffusionCreateStreamUsecase",
|
||||
"StreamDiffusionWarmupUsecase",
|
||||
"StreamDiffusionSampleUsecase",
|
||||
"StreamDiffusionFastSampleUsecase",
|
||||
]
|
||||
|
||||
@@ -9,7 +9,7 @@ from ..domain.repositories import AutoencoderRepository
|
||||
class AutoencoderUsecase:
|
||||
def __init__(self, autoencoder_repo: AutoencoderRepository) -> None:
|
||||
self.autoencoder_repo = autoencoder_repo
|
||||
self.dtype = torch.float32
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(self, vae_path: str) -> Autoencoder:
|
||||
if not os.path.exists(vae_path):
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from ..domain.model import LcmLora
|
||||
from ..domain.repositories import LcmLoraRepository
|
||||
|
||||
|
||||
class LcmLoraUsecase:
|
||||
def __init__(self, lcm_lora_repo: LcmLoraRepository) -> None:
|
||||
self.lcm_lora_repo = lcm_lora_repo
|
||||
|
||||
def execute(self, lora_path: str) -> LcmLora:
|
||||
"""Load LCM LoRA weights from file.
|
||||
|
||||
Args:
|
||||
lora_path: Path to the LCM LoRA file
|
||||
|
||||
Returns:
|
||||
LcmLora domain model
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: If the lora file doesn't exist
|
||||
RuntimeError: If loading fails
|
||||
"""
|
||||
weights = self.lcm_lora_repo.load_lcm_lora(lora_path)
|
||||
if not weights:
|
||||
raise RuntimeError(f"Failed to load LCM LoRA weights from: {lora_path}")
|
||||
|
||||
return LcmLora(weights=weights)
|
||||
@@ -9,7 +9,7 @@ from ..domain.repositories import PipelineRepository
|
||||
class PipelineUsecase:
|
||||
def __init__(self, pipeline_repo: PipelineRepository) -> None:
|
||||
self.pipeline_repo = pipeline_repo
|
||||
self.dtype = torch.float32
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(self, checkpoint_path: str) -> tuple[Pipeline, Clip]:
|
||||
if not os.path.exists(checkpoint_path):
|
||||
|
||||
@@ -19,7 +19,7 @@ from ..domain.repositories import SamplerRepository
|
||||
class SamplerUsecase:
|
||||
def __init__(self, sampler_repo: SamplerRepository) -> None:
|
||||
self.sampler_repo = sampler_repo
|
||||
self.dtype = torch.float32
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(
|
||||
self,
|
||||
|
||||
@@ -8,7 +8,7 @@ from ..domain.repositories import SchedulerRepository
|
||||
class SchedulerUsecase:
|
||||
def __init__(self, scheduler_repo: SchedulerRepository) -> None:
|
||||
self.scheduler_repo = scheduler_repo
|
||||
self.dtype = torch.float32
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(self, pipeline: Pipeline, scheduler_type: Scheduler.Type) -> Scheduler:
|
||||
scheduler_obj: SchedulerMixin = self.scheduler_repo.create_scheduler(
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
from ..domain.model import (
|
||||
CFGType,
|
||||
FrameBufferSize,
|
||||
ImageSize,
|
||||
LcmLora,
|
||||
Pipeline,
|
||||
Scheduler,
|
||||
StreamDiffusionStream,
|
||||
TIndexList,
|
||||
)
|
||||
from ..domain.repositories import StreamDiffusionRepository
|
||||
|
||||
|
||||
class StreamDiffusionCreateStreamUsecase:
|
||||
def __init__(self, stream_diffusion_repo: StreamDiffusionRepository) -> None:
|
||||
self.stream_diffusion_repo = stream_diffusion_repo
|
||||
|
||||
def execute(
|
||||
self,
|
||||
pipeline: Pipeline,
|
||||
scheduler: Scheduler,
|
||||
t_index_list: TIndexList,
|
||||
image_size: ImageSize,
|
||||
do_add_noise: bool,
|
||||
use_denoising_batch: bool,
|
||||
frame_buffer_size: FrameBufferSize,
|
||||
cfg_type: CFGType,
|
||||
lcm_lora: LcmLora,
|
||||
tiny_vae_name: str,
|
||||
enable_xformers: bool,
|
||||
) -> StreamDiffusionStream:
|
||||
"""Create a Stream Diffusion stream.
|
||||
|
||||
Args:
|
||||
pipeline: Pipeline model
|
||||
scheduler: Scheduler model
|
||||
t_index_list: List of timestep indices
|
||||
image_size: Image dimensions
|
||||
do_add_noise: Whether to add noise
|
||||
use_denoising_batch: Whether to use denoising batch
|
||||
frame_buffer_size: Size of frame buffer
|
||||
cfg_type: CFG type configuration
|
||||
lcm_lora: LCM LoRA model
|
||||
tiny_vae_name: Name of tiny VAE model
|
||||
enable_xformers: Whether to enable xformers
|
||||
|
||||
Returns:
|
||||
StreamDiffusionStream domain model
|
||||
|
||||
Raises:
|
||||
RuntimeError: If stream creation fails
|
||||
"""
|
||||
stream = self.stream_diffusion_repo.create_stream(
|
||||
pipeline=pipeline.pipeline,
|
||||
scheduler=scheduler.scheduler,
|
||||
t_index_list=t_index_list,
|
||||
image_size=image_size,
|
||||
do_add_noise=do_add_noise,
|
||||
use_denoising_batch=use_denoising_batch,
|
||||
frame_buffer_size=frame_buffer_size,
|
||||
cfg_type=cfg_type,
|
||||
lcm_lora_weights=lcm_lora.weights,
|
||||
tiny_vae_name=tiny_vae_name,
|
||||
enable_xformers=enable_xformers,
|
||||
)
|
||||
|
||||
if stream is None:
|
||||
raise RuntimeError("Failed to create Stream Diffusion stream")
|
||||
|
||||
return StreamDiffusionStream(stream=stream)
|
||||
@@ -0,0 +1,40 @@
|
||||
from ..domain.model import Image, NumSamples, StreamDiffusionStream
|
||||
from ..domain.repositories import StreamDiffusionRepository
|
||||
|
||||
|
||||
class StreamDiffusionFastSampleUsecase:
|
||||
def __init__(self, stream_diffusion_repo: StreamDiffusionRepository) -> None:
|
||||
self.stream_diffusion_repo = stream_diffusion_repo
|
||||
|
||||
def execute(
|
||||
self,
|
||||
stream: StreamDiffusionStream,
|
||||
prompt: str,
|
||||
num_samples: NumSamples,
|
||||
) -> list[Image]:
|
||||
"""Fast sample images from a warmed-up Stream Diffusion stream.
|
||||
|
||||
Args:
|
||||
stream: Warmed-up stream to sample from
|
||||
prompt: Prompt text
|
||||
num_samples: Number of images to generate
|
||||
|
||||
Returns:
|
||||
List of generated images as domain models
|
||||
|
||||
Raises:
|
||||
RuntimeError: If sampling fails
|
||||
"""
|
||||
# Update prompt
|
||||
self.stream_diffusion_repo.update_prompt(stream=stream.stream, prompt=prompt)
|
||||
|
||||
# Sample images
|
||||
images = self.stream_diffusion_repo.sample_txt2img(
|
||||
stream=stream.stream,
|
||||
num_samples=num_samples,
|
||||
)
|
||||
|
||||
if not images:
|
||||
raise RuntimeError("Stream Diffusion repository returned no images")
|
||||
|
||||
return [Image(image=img) for img in images]
|
||||
@@ -0,0 +1,91 @@
|
||||
from ..domain.model import (
|
||||
CFGScale,
|
||||
Delta,
|
||||
Image,
|
||||
NumSamples,
|
||||
Seed,
|
||||
Steps,
|
||||
StreamDiffusionStream,
|
||||
WarmupCount,
|
||||
)
|
||||
from ..domain.repositories import StreamDiffusionRepository
|
||||
|
||||
|
||||
class StreamDiffusionSampleUsecase:
|
||||
def __init__(self, stream_diffusion_repo: StreamDiffusionRepository) -> None:
|
||||
self.stream_diffusion_repo = stream_diffusion_repo
|
||||
|
||||
def execute(
|
||||
self,
|
||||
stream: StreamDiffusionStream,
|
||||
prompt: str,
|
||||
negative_prompt: str,
|
||||
steps: Steps,
|
||||
cfg: CFGScale,
|
||||
delta: Delta,
|
||||
seed: Seed,
|
||||
num_samples: NumSamples,
|
||||
warmup_count: WarmupCount,
|
||||
input_images: list[Image] | None,
|
||||
) -> list[Image]:
|
||||
"""Sample images from a Stream Diffusion stream.
|
||||
|
||||
Args:
|
||||
stream: Stream to sample from
|
||||
prompt: Prompt text
|
||||
negative_prompt: Negative prompt text
|
||||
steps: Number of inference steps
|
||||
cfg: CFG scale value
|
||||
delta: Delta value
|
||||
seed: Random seed
|
||||
num_samples: Number of images to generate
|
||||
warmup_count: Number of warmup iterations
|
||||
input_images: Optional list of input images
|
||||
|
||||
Returns:
|
||||
List of generated images as domain models
|
||||
|
||||
Raises:
|
||||
RuntimeError: If sampling fails
|
||||
"""
|
||||
# Prepare stream with parameters
|
||||
self.stream_diffusion_repo.prepare_stream(
|
||||
stream=stream.stream,
|
||||
prompt=prompt,
|
||||
negative_prompt=negative_prompt,
|
||||
steps=steps,
|
||||
cfg=cfg,
|
||||
delta=delta,
|
||||
seed=seed,
|
||||
)
|
||||
|
||||
# Convert domain images to PIL images if provided
|
||||
pil_images = None
|
||||
warmup_image = None
|
||||
if input_images is not None:
|
||||
pil_images = [img.image for img in input_images]
|
||||
# Use first image for warmup (img2img mode)
|
||||
warmup_image = pil_images[0] if pil_images else None
|
||||
|
||||
self.stream_diffusion_repo.warmup_stream(
|
||||
stream=stream.stream,
|
||||
warmup_count=warmup_count,
|
||||
input_image=warmup_image,
|
||||
)
|
||||
|
||||
if pil_images:
|
||||
images = self.stream_diffusion_repo.sample_with_images(
|
||||
stream=stream.stream,
|
||||
num_samples=num_samples,
|
||||
input_images=pil_images,
|
||||
)
|
||||
else:
|
||||
images = self.stream_diffusion_repo.sample_txt2img(
|
||||
stream=stream.stream,
|
||||
num_samples=num_samples,
|
||||
)
|
||||
|
||||
if not images:
|
||||
raise RuntimeError("Stream Diffusion repository returned no images")
|
||||
|
||||
return [Image(image=img) for img in images]
|
||||
@@ -0,0 +1,51 @@
|
||||
from ..domain.model import CFGScale, Delta, Seed, Steps, StreamDiffusionStream, WarmupCount
|
||||
from ..domain.repositories import StreamDiffusionRepository
|
||||
|
||||
|
||||
class StreamDiffusionWarmupUsecase:
|
||||
def __init__(self, stream_diffusion_repo: StreamDiffusionRepository) -> None:
|
||||
self.stream_diffusion_repo = stream_diffusion_repo
|
||||
|
||||
def execute(
|
||||
self,
|
||||
stream: StreamDiffusionStream,
|
||||
negative_prompt: str,
|
||||
steps: Steps,
|
||||
cfg: CFGScale,
|
||||
delta: Delta,
|
||||
seed: Seed,
|
||||
warmup_count: WarmupCount,
|
||||
) -> StreamDiffusionStream:
|
||||
"""Warm up a Stream Diffusion stream.
|
||||
|
||||
Args:
|
||||
stream: Stream to warm up
|
||||
negative_prompt: Negative prompt text
|
||||
steps: Number of inference steps
|
||||
cfg: CFG scale value
|
||||
delta: Delta value
|
||||
seed: Random seed
|
||||
warmup_count: Number of warmup iterations
|
||||
|
||||
Returns:
|
||||
Warmed up StreamDiffusionStream (same instance)
|
||||
"""
|
||||
# Prepare stream with parameters
|
||||
self.stream_diffusion_repo.prepare_stream(
|
||||
stream=stream.stream,
|
||||
prompt="",
|
||||
negative_prompt=negative_prompt,
|
||||
steps=steps,
|
||||
cfg=cfg,
|
||||
delta=delta,
|
||||
seed=seed,
|
||||
)
|
||||
|
||||
# Warmup (txt2img mode - no input image)
|
||||
self.stream_diffusion_repo.warmup_stream(
|
||||
stream=stream.stream,
|
||||
warmup_count=warmup_count,
|
||||
input_image=None,
|
||||
)
|
||||
|
||||
return stream
|
||||
Reference in New Issue
Block a user