Author SHA1 Message Date
Limitex 3ad327119b refactor: update input parameters to use conditioning DTOs in StreamDiffusionSampler 2025-11-29 01:50:28 +09:00
Limitex de792d510f refactor: t index list 2025-11-24 19:41:40 +09:00
Limitex 1b678e3e4d refactor: any 2025-11-24 02:28:43 +09:00
Limitex c66afa595f fix: warmup input image 2025-11-24 02:09:17 +09:00
Limitex a3e6c284db refactor: warmup 2025-11-24 01:09:50 +09:00
Limitex 97c54553db remove: reshape lora weights 2025-11-24 00:38:29 +09:00
Limitex ef1f3036bb fix: enable_xformers defaults 2025-11-24 00:38:29 +09:00
Limitex b529601613 feat: use scheduler to stream diffusion 2025-11-24 00:38:29 +09:00
Limitex c6d5511b46 fix: default warmup steps 2025-11-24 00:38:29 +09:00
Limitex 8ffba3c09c feat: add LoRA compatibility filtering for mixed SDXL/SD1.5 models 2025-11-24 00:38:29 +09:00
Limitex 008f3310a5 refactor: simplify image generation loop in StreamDiffusion 2025-11-24 00:37:17 +09:00
Limitex 02dc9bbcbd fix: lcm lora type 2025-11-24 00:11:41 +09:00
Limitex 06c9f21ffb feat: stream diffusion impl 2025-11-23 22:59:05 +09:00
Limitex 2aecbc841e fix: dtype 2025-11-23 22:57:53 +09:00
Limitex e17bdfc621 refactor: diffusers impl 2025-11-23 10:02:37 +09:00
Limitex 3dbb83d209 vscode: add cspell 2025-11-22 06:21:36 +09:00
Limitex 0297f99c10 fix(deps): resolving mismatch between batch size and timestep tensor size in StreamDiffusion 2025-11-12 23:40:51 +09:00
Limitex ceafe418b5 chore(deps): downgrade huggingface_hub dependencies 2025-11-12 23:36:24 +09:00
Limitex aeacd3269a chore(deps): downgrade transformers dependencies 2025-11-12 23:35:25 +09:00
Limitex ccc5c00c8e chore(deps): downgrade numpy dependencies 2025-11-12 23:33:42 +09:00
Limitex 7e0e6abc05 add: onnx-graphsurgeon package 2025-11-12 23:29:51 +09:00
Limitex baec648773 add: polygraphy package 2025-11-12 23:28:41 +09:00
Limitex 4b0c19d34a chore: add ngc nvidia source 2025-11-12 23:27:21 +09:00
Limitex 563d5cf881 add: tensorrt package 2025-11-12 23:26:17 +09:00
Limitex 85674301fb chore: add nvidia source 2025-11-12 23:21:46 +09:00
Limitex 0d1de5af4c add: streamdiffusion package 2025-11-12 23:12:57 +09:00
Limitex e3b444c799 chore(deps): downgrade diffusers dependencies 2025-11-12 23:11:37 +09:00
Limitex 1d6aba3261 chore: add xformers 2025-11-12 23:09:32 +09:00
Limitex c3ca25d897 chore: pytoch dependencies 2025-11-12 23:08:27 +09:00
Limitex 376766163e chore: add pytorch source 2025-11-12 22:58:43 +09:00
Limitex d417b2a63f chore: format pyproject 2025-11-12 22:58:09 +09:00
43 changed files with 2246 additions and 457 deletions
+1
View File
@@ -222,6 +222,7 @@
"ukkonen",
"upsamplers",
"urllib",
"usecase",
"venv",
"wandb",
"webassets",
+6
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+74 -41
View File
@@ -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
View File
@@ -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"
+29
View File
@@ -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,
)
+17
View File
@@ -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",
]
+22
View File
@@ -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__}")
+14
View File
@@ -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}")
+14
View File
@@ -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}")
+16
View File
@@ -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")
+14
View File
@@ -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
+35
View File
@@ -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)
+14
View File
@@ -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}")
+4
View File
@@ -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
"""
+4
View File
@@ -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
+3 -3
View File
@@ -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:
+21
View File
@@ -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",
]
+45
View File
@@ -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,)
+117
View File
@@ -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,)
+85
View File
@@ -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,)
+5
View File
@@ -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",
]
+46
View File
@@ -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)
+10
View File
@@ -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",
]
+1 -1
View File
@@ -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):
+26
View File
@@ -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)
+1 -1
View File
@@ -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):
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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