Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6894a93d98 | ||
|
|
027c7dcec3 |
-264
@@ -1,264 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
# Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
|
||||
# Docker
|
||||
docker/
|
||||
|
||||
# Hugging Face artifacts
|
||||
*.safetensors
|
||||
*.vae
|
||||
*.pt
|
||||
|
||||
# Git (critical for Docker - not in .gitignore)
|
||||
.git/
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
LICENSE
|
||||
CHANGELOG.md
|
||||
docs/
|
||||
|
||||
# Tests (commit to git, but exclude from image)
|
||||
tests/
|
||||
*_test.py
|
||||
|
||||
# IDEs and editors
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# CI/CD
|
||||
.github/
|
||||
|
||||
# Docker
|
||||
docker/
|
||||
Dockerfile
|
||||
Dockerfile.*
|
||||
compose.yaml
|
||||
.dockerignore
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Development tools config
|
||||
.pre-commit-config.yaml
|
||||
.commitizenrc
|
||||
@@ -1,5 +0,0 @@
|
||||
# .env.default
|
||||
|
||||
# Default cache directory for ComfyUI-Diffusers
|
||||
# Path is relative to ComfyUI root directory
|
||||
COMFYUI_DIFFUSERS_CACHE_DIR=./cache
|
||||
@@ -1 +0,0 @@
|
||||
COMFYUI_DIFFUSERS_CACHE_DIR=./tmp/cache
|
||||
+17
-79
@@ -1,6 +1,6 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[codz]
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
@@ -27,8 +27,8 @@ share/python-wheels/
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
@@ -46,7 +46,7 @@ htmlcov/
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
@@ -92,37 +92,22 @@ ipython_config.py
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
# Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# uv.lock
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
# poetry.lock
|
||||
# poetry.toml
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
||||
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
||||
# pdm.lock
|
||||
# pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# pixi
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
||||
# pixi.lock
|
||||
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
||||
# in the .venv directory. It is recommended not to include this directory in version control.
|
||||
.pixi
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
@@ -131,25 +116,11 @@ __pypackages__/
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# Redis
|
||||
*.rdb
|
||||
*.aof
|
||||
*.pid
|
||||
|
||||
# RabbitMQ
|
||||
mnesia/
|
||||
rabbitmq/
|
||||
rabbitmq-data/
|
||||
|
||||
# ActiveMQ
|
||||
activemq-data/
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.envrc
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
@@ -182,43 +153,10 @@ dmypy.json
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
# .idea/
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Abstra
|
||||
# Abstra is an AI-powered process automation framework.
|
||||
# Ignore directories containing user credentials, local state, and settings.
|
||||
# Learn more at https://abstra.io/docs
|
||||
.abstra/
|
||||
|
||||
# Visual Studio Code
|
||||
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
||||
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
||||
# you could uncomment the following to ignore the entire vscode folder
|
||||
# .vscode/
|
||||
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
# Marimo
|
||||
marimo/_static/
|
||||
marimo/_lsp/
|
||||
__marimo__/
|
||||
|
||||
# Streamlit
|
||||
.streamlit/secrets.toml
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
|
||||
# Hugging Face artifacts
|
||||
*.safetensors
|
||||
*.vae
|
||||
*.pt
|
||||
StreamDiffusion/*
|
||||
Vendored
-236
@@ -1,236 +0,0 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"abfs",
|
||||
"abstra",
|
||||
"Abstra",
|
||||
"abstractmethod",
|
||||
"addopts",
|
||||
"adlfs",
|
||||
"aiobotocore",
|
||||
"aiohttp",
|
||||
"analyser",
|
||||
"antlr",
|
||||
"ANTLR",
|
||||
"argcomplete",
|
||||
"argparse",
|
||||
"armv",
|
||||
"asyncio",
|
||||
"authlib",
|
||||
"autoencoder",
|
||||
"beautifulsoup",
|
||||
"bitsandbytes",
|
||||
"cairocffi",
|
||||
"celerybeat",
|
||||
"certifi",
|
||||
"cfgv",
|
||||
"Chardet",
|
||||
"checkdocs",
|
||||
"cloudpickle",
|
||||
"codz",
|
||||
"colorama",
|
||||
"comfyui",
|
||||
"commitizen",
|
||||
"commonmark",
|
||||
"Connor",
|
||||
"contextily",
|
||||
"copybutton",
|
||||
"cublas",
|
||||
"CUBLAS",
|
||||
"cuda",
|
||||
"CUDA",
|
||||
"cudnn",
|
||||
"cufft",
|
||||
"CUFFT",
|
||||
"cufile",
|
||||
"cupti",
|
||||
"curand",
|
||||
"CURAND",
|
||||
"cusolver",
|
||||
"cusparse",
|
||||
"CUSPARSE",
|
||||
"cusparselt",
|
||||
"cython",
|
||||
"Cython",
|
||||
"dask",
|
||||
"dataframe",
|
||||
"decli",
|
||||
"distlib",
|
||||
"dmypy",
|
||||
"docstrings",
|
||||
"dotenv",
|
||||
"downsample",
|
||||
"downsamplers",
|
||||
"dropboxdrivefs",
|
||||
"dvclive",
|
||||
"envrc",
|
||||
"exceptiongroup",
|
||||
"execnet",
|
||||
"fastai",
|
||||
"fastapi",
|
||||
"fastcore",
|
||||
"fastparquet",
|
||||
"filelock",
|
||||
"filterwarnings",
|
||||
"flufl",
|
||||
"fsspec",
|
||||
"furo",
|
||||
"fusepy",
|
||||
"gcsfs",
|
||||
"gguf",
|
||||
"gmpy",
|
||||
"gprof",
|
||||
"graphviz",
|
||||
"grimp",
|
||||
"hdfs",
|
||||
"htmlcov",
|
||||
"httpx",
|
||||
"huggingface",
|
||||
"idna",
|
||||
"IDNA",
|
||||
"igraph",
|
||||
"importlinter",
|
||||
"iniconfig",
|
||||
"intersphinx",
|
||||
"ipykernel",
|
||||
"ipynb",
|
||||
"ipython",
|
||||
"isort",
|
||||
"itsdangerous",
|
||||
"jaraco",
|
||||
"jaxlib",
|
||||
"jschema",
|
||||
"keras",
|
||||
"kerchunk",
|
||||
"libcst",
|
||||
"librosa",
|
||||
"Limitex",
|
||||
"linkify",
|
||||
"loras",
|
||||
"lxml",
|
||||
"manylinux",
|
||||
"Marimo",
|
||||
"markupsafe",
|
||||
"matplotlib",
|
||||
"mdit",
|
||||
"mdurl",
|
||||
"mkdocs",
|
||||
"mnesia",
|
||||
"momepy",
|
||||
"moto",
|
||||
"mpmath",
|
||||
"musllinux",
|
||||
"mypy",
|
||||
"mypyc",
|
||||
"nccl",
|
||||
"NCCL",
|
||||
"networkx",
|
||||
"nodeenv",
|
||||
"nosetests",
|
||||
"numpy",
|
||||
"numpydoc",
|
||||
"nvjitlink",
|
||||
"nvrtc",
|
||||
"NVRTC",
|
||||
"nvshmem",
|
||||
"NVSHMEM",
|
||||
"nvtx",
|
||||
"ocifs",
|
||||
"omegaconf",
|
||||
"orjson",
|
||||
"osmnx",
|
||||
"panflute",
|
||||
"paramiko",
|
||||
"pathspec",
|
||||
"peft",
|
||||
"pexpect",
|
||||
"phonemizer",
|
||||
"pipenv",
|
||||
"Pipfile",
|
||||
"psutil",
|
||||
"pyarrow",
|
||||
"pybuilder",
|
||||
"pycache",
|
||||
"pycodestyle",
|
||||
"pydantic",
|
||||
"pydata",
|
||||
"pydocstyle",
|
||||
"pydot",
|
||||
"pyenv",
|
||||
"pyfakefs",
|
||||
"pyflakes",
|
||||
"pyflow",
|
||||
"pyftpdlib",
|
||||
"pygit",
|
||||
"pygraphviz",
|
||||
"pylint",
|
||||
"pypa",
|
||||
"pypackages",
|
||||
"pypirc",
|
||||
"pyproject",
|
||||
"pypy",
|
||||
"pyrs",
|
||||
"pytest",
|
||||
"pytype",
|
||||
"pyupgrade",
|
||||
"pyyaml",
|
||||
"quanto",
|
||||
"rabbitmq",
|
||||
"repr",
|
||||
"rerunfailures",
|
||||
"resnets",
|
||||
"riscv",
|
||||
"ropeproject",
|
||||
"rtoml",
|
||||
"safetensors",
|
||||
"sarif",
|
||||
"scikit",
|
||||
"scipy",
|
||||
"scrapy",
|
||||
"Scrapy",
|
||||
"sdist",
|
||||
"seaborn",
|
||||
"sentencepiece",
|
||||
"setuptools",
|
||||
"simplejson",
|
||||
"smbprotocol",
|
||||
"soundfile",
|
||||
"Spyder",
|
||||
"spyderproject",
|
||||
"spyproject",
|
||||
"stestr",
|
||||
"streamlit",
|
||||
"subtests",
|
||||
"swanlab",
|
||||
"sympy",
|
||||
"tensorboard",
|
||||
"termcolor",
|
||||
"testpaths",
|
||||
"testscenarios",
|
||||
"testtools",
|
||||
"tiktoken",
|
||||
"timm",
|
||||
"tomli",
|
||||
"tomlkit",
|
||||
"torchao",
|
||||
"torchaudio",
|
||||
"torchdata",
|
||||
"torchpippy",
|
||||
"torchvision",
|
||||
"tqdm",
|
||||
"trackio",
|
||||
"typer",
|
||||
"ukkonen",
|
||||
"upsamplers",
|
||||
"urllib",
|
||||
"usecase",
|
||||
"venv",
|
||||
"wandb",
|
||||
"webassets",
|
||||
"xarray",
|
||||
"xdist",
|
||||
"yarl",
|
||||
"zarr",
|
||||
"zipp",
|
||||
"zstandard"
|
||||
]
|
||||
}
|
||||
@@ -1 +1,70 @@
|
||||
# ComfyUI-Diffusers
|
||||
|
||||
This repository is a custom node in ComfyUI.
|
||||
|
||||
## Overview
|
||||
|
||||
### Workflow 1
|
||||
|
||||
This is a program that allows you to use Huggingface Diffusers module with ComfyUI. Additionally, Stream Diffusion is also available.
|
||||
|
||||

|
||||
|
||||
### Workflow 2
|
||||
|
||||
In addition, real-time generation is possible by doing the following.
|
||||
When running, please enable Auto Queue in Extra options.
|
||||
|
||||

|
||||
|
||||
### Workflow 3
|
||||
|
||||
In combination with [VideoHelperSuite](https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite.git), you can also run vid2vid.
|
||||
|
||||

|
||||
|
||||
The execution looks like this:
|
||||
|
||||

|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Run the following command inside ComfyUI/custom_nodes.
|
||||
|
||||
```cmd
|
||||
git clone https://github.com/Limitex/ComfyUI-Diffusers.git
|
||||
cd ComfyUI-Diffusers
|
||||
pip install -r requirements.txt
|
||||
git clone https://github.com/cumulo-autumn/StreamDiffusion.git
|
||||
python -m streamdiffusion.tools.install-tensorrt
|
||||
```
|
||||
## Recommended Custom Nodes
|
||||
|
||||
https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite.git
|
||||
|
||||
## Node
|
||||
|
||||
### Diffusers Pipeline Loader (DiffusersPipelineLoader)
|
||||
|
||||
### Diffusers Vae Loader (DiffusersVaeLoader)
|
||||
|
||||
### Diffusers Scheduler Loader (DiffusersSchedulerLoader)
|
||||
|
||||
### Diffusers Model Makeup (DiffusersModelMakeup)
|
||||
|
||||
### Diffusers Clip Text Encode (DiffusersClipTextEncode)
|
||||
|
||||
### Diffusers Sampler (DiffusersSampler)
|
||||
|
||||
### Create Int List (CreateIntListNode)
|
||||
|
||||
### LcmLoraLoader (LcmLoraLoader)
|
||||
|
||||
### StreamDiffusion Create Stream (StreamDiffusionCreateStream)
|
||||
|
||||
### StreamDiffusion Sampler (StreamDiffusionSampler)
|
||||
|
||||
### StreamDiffusion Warmup (StreamDiffusionWarmup)
|
||||
|
||||
### StreamDiffusion Fast Sampler (StreamDiffusionFastSampler)
|
||||
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
PYTHON_SRC: "src"
|
||||
PYTHON_TESTS: "tests"
|
||||
LINT_TARGETS: "src tests"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: "List all available tasks"
|
||||
cmds:
|
||||
- task --list
|
||||
|
||||
install:
|
||||
desc: "Install all dependencies (including dev) based on poetry.lock"
|
||||
cmds:
|
||||
- poetry install
|
||||
- task: export
|
||||
|
||||
install:prod:
|
||||
desc: "Install only production dependencies"
|
||||
cmds:
|
||||
- poetry install --without dev
|
||||
- task: export
|
||||
|
||||
update:
|
||||
desc: "Update dependencies based on pyproject.toml and regenerate poetry.lock"
|
||||
cmds:
|
||||
- poetry update
|
||||
- task: export
|
||||
|
||||
add:
|
||||
desc: "Add a new production dependency (usage: task add -- requests)"
|
||||
cmds:
|
||||
- poetry add {{.CLI_ARGS}}
|
||||
- task: export
|
||||
preconditions:
|
||||
- sh: "[ -n '{{.CLI_ARGS}}' ]"
|
||||
msg: "CLI_ARGS must be set (e.g., task add -- requests)"
|
||||
|
||||
add:dev:
|
||||
desc: "Add a new development dependency (usage: task add:dev -- pytest)"
|
||||
cmds:
|
||||
- poetry add {{.CLI_ARGS}} --group dev
|
||||
- task: export
|
||||
preconditions:
|
||||
- sh: "[ -n '{{.CLI_ARGS}}' ]"
|
||||
msg: "CLI_ARGS must be set (e.g., task add:dev -- pytest)"
|
||||
|
||||
remove:
|
||||
desc: "Remove a dependency (usage: task remove -- requests)"
|
||||
cmds:
|
||||
- poetry remove {{.CLI_ARGS}}
|
||||
- task: export
|
||||
preconditions:
|
||||
- sh: "[ -n '{{.CLI_ARGS}}' ]"
|
||||
msg: "CLI_ARGS must be set (e.g., task remove -- requests)"
|
||||
|
||||
shell:
|
||||
desc: "Activate the virtual environment shell managed by Poetry"
|
||||
cmds:
|
||||
- poetry shell
|
||||
|
||||
run:
|
||||
desc: "Run a command within the virtual environment (usage: task run -- python main.py)"
|
||||
cmds:
|
||||
- poetry run {{.CLI_ARGS}}
|
||||
preconditions:
|
||||
- sh: "[ -n '{{.CLI_ARGS}}' ]"
|
||||
msg: "CLI_ARGS must be set (e.g., task run -- python main.py)"
|
||||
|
||||
format:
|
||||
desc: "Automatically format code using Ruff"
|
||||
cmds:
|
||||
- poetry run ruff format {{.LINT_TARGETS}}
|
||||
|
||||
format:check:
|
||||
desc: "Check code formatting without making changes"
|
||||
cmds:
|
||||
- poetry run ruff format {{.LINT_TARGETS}} --check
|
||||
|
||||
lint:
|
||||
desc: "Run linting with ruff"
|
||||
cmds:
|
||||
- poetry run ruff check {{.LINT_TARGETS}}
|
||||
|
||||
lint:fix:
|
||||
desc: "Run linting with auto-fix"
|
||||
cmds:
|
||||
- poetry run ruff check {{.LINT_TARGETS}} --fix
|
||||
|
||||
type:
|
||||
desc: "Run static type checking using mypy"
|
||||
cmds:
|
||||
- poetry run mypy {{.PYTHON_SRC}}
|
||||
|
||||
fix:
|
||||
desc: "Auto-fix all formatting and linting issues"
|
||||
cmds:
|
||||
- task: format
|
||||
- task: lint:fix
|
||||
|
||||
check:
|
||||
desc: "Run all checks (format check, lint, type check, and tests)"
|
||||
cmds:
|
||||
- task: format:check
|
||||
- task: lint
|
||||
- task: type
|
||||
- task: test
|
||||
|
||||
test:
|
||||
desc: "Run tests using pytest"
|
||||
cmds:
|
||||
- poetry run pytest {{.PYTHON_TESTS}}
|
||||
|
||||
test:verbose:
|
||||
desc: "Run tests with verbose output"
|
||||
cmds:
|
||||
- poetry run pytest -v {{.PYTHON_TESTS}}
|
||||
|
||||
test:cov:
|
||||
desc: "Run tests with coverage report"
|
||||
cmds:
|
||||
- poetry run pytest --cov={{.PYTHON_SRC}} {{.PYTHON_TESTS}} --cov-report=html --cov-report=term
|
||||
|
||||
build:
|
||||
desc: "Build sdist and wheel"
|
||||
cmds:
|
||||
- poetry build
|
||||
|
||||
export:
|
||||
desc: "Export production dependencies to requirements.txt"
|
||||
cmds:
|
||||
- poetry export -f requirements.txt --output requirements.txt --without-hashes
|
||||
|
||||
export:dev:
|
||||
desc: "Export all dependencies (including dev) to requirements-dev.txt"
|
||||
cmds:
|
||||
- poetry export -f requirements.txt --output requirements-dev.txt --without-hashes --with dev
|
||||
|
||||
export:all:
|
||||
desc: "Export both prod and dev requirements"
|
||||
cmds:
|
||||
- task: export
|
||||
- task: export:dev
|
||||
|
||||
lookup:
|
||||
desc: "Check available versions of a package on PyPI (usage: task lookup -- requests)"
|
||||
cmds:
|
||||
- poetry run pip index versions {{.CLI_ARGS}}
|
||||
preconditions:
|
||||
- sh: "[ -n '{{.CLI_ARGS}}' ]"
|
||||
msg: "CLI_ARGS must be set (e.g., task lookup -- requests)"
|
||||
|
||||
tree:
|
||||
desc: "Show dependency tree of a package (usage: task tree -- requests)"
|
||||
cmds:
|
||||
- poetry show {{.CLI_ARGS}} --tree
|
||||
preconditions:
|
||||
- sh: "[ -n '{{.CLI_ARGS}}' ]"
|
||||
msg: "CLI_ARGS must be set (e.g., task tree -- requests)"
|
||||
|
||||
clean:
|
||||
desc: "Clean up cache and temporary files"
|
||||
cmds:
|
||||
- rm -rf .pytest_cache
|
||||
- rm -rf .mypy_cache
|
||||
- rm -rf .ruff_cache
|
||||
- rm -rf htmlcov
|
||||
- rm -rf .coverage
|
||||
- rm -rf dist
|
||||
- rm -rf build
|
||||
- find . -type d -name __pycache__ -exec rm -rf {} +
|
||||
- find . -type d -name "*.egg-info" -exec rm -rf {} +
|
||||
- find . -type f -name "*.pyc" -delete
|
||||
+2
-27
@@ -1,28 +1,3 @@
|
||||
from .src.config import load_envs
|
||||
from .src.nodes import (
|
||||
NODE_CLASS_MAPPINGS,
|
||||
NODE_DISPLAY_NAME_MAPPINGS,
|
||||
ComfyUIAutoencoderDTO,
|
||||
ComfyUIClipDTO,
|
||||
ComfyUIConditioningDTO,
|
||||
ComfyUILcmLoraDTO,
|
||||
ComfyUIPipelineDTO,
|
||||
ComfyUISchedulerDTO,
|
||||
ComfyUIStreamDTO,
|
||||
ComfyUIWarmupStreamDTO,
|
||||
)
|
||||
from .nodes import *
|
||||
|
||||
load_envs()
|
||||
|
||||
__all__ = [
|
||||
"NODE_CLASS_MAPPINGS",
|
||||
"NODE_DISPLAY_NAME_MAPPINGS",
|
||||
"ComfyUIPipelineDTO",
|
||||
"ComfyUIAutoencoderDTO",
|
||||
"ComfyUIClipDTO",
|
||||
"ComfyUIConditioningDTO",
|
||||
"ComfyUISchedulerDTO",
|
||||
"ComfyUILcmLoraDTO",
|
||||
"ComfyUIStreamDTO",
|
||||
"ComfyUIWarmupStreamDTO",
|
||||
]
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
services:
|
||||
comfyui:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/comfyui/Dockerfile
|
||||
container_name: comfyui
|
||||
runtime: nvidia
|
||||
environment:
|
||||
- CUDA_VISIBLE_DEVICES=0
|
||||
- NVIDIA_VISIBLE_DEVICES=all
|
||||
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
||||
- POETRY_VENV_IN_PROJECT=false
|
||||
- POETRY_NO_INTERACTION=1
|
||||
- POETRY_CACHE_DIR=/tmp/poetry_cache
|
||||
volumes:
|
||||
- ./tmp/models/checkpoints:/app/ComfyUI/models/checkpoints
|
||||
- ./tmp/models/loras:/app/ComfyUI/models/loras
|
||||
- ./tmp/models/vae:/app/ComfyUI/models/vae
|
||||
- ./tmp/output:/app/ComfyUI/output
|
||||
- ./tmp/workflows:/app/ComfyUI/user/default/workflows
|
||||
- ./tmp/tmp:/app/ComfyUI/temp
|
||||
- .:/app/ComfyUI/custom_nodes/ComfyUI-Diffusers
|
||||
ports:
|
||||
- "8188:8188"
|
||||
restart: unless-stopped
|
||||
stdin_open: true
|
||||
tty: true
|
||||
|
||||
dev:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/dev/Dockerfile
|
||||
volumes:
|
||||
- .:/app
|
||||
- venv_data:/cache/.venv
|
||||
|
||||
volumes:
|
||||
venv_data:
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM alpine/git:latest AS builder
|
||||
WORKDIR /app
|
||||
RUN git clone --depth 1 --branch v0.3.67 https://github.com/comfyanonymous/ComfyUI.git ComfyUI && \
|
||||
rm -rf ComfyUI/.git
|
||||
|
||||
FROM pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/ComfyUI /app/ComfyUI
|
||||
WORKDIR /app/ComfyUI
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
curl \
|
||||
build-essential \
|
||||
&& pip install --no-cache-dir -r requirements.txt \
|
||||
&& apt-get remove -y build-essential curl \
|
||||
&& apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
COPY ./requirements.txt ./tmp.txt
|
||||
RUN pip install --no-cache-dir -r tmp.txt && rm tmp.txt
|
||||
EXPOSE 8188
|
||||
CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188"]
|
||||
@@ -1,24 +0,0 @@
|
||||
FROM python:3.11-slim-bookworm
|
||||
ENV POETRY_HOME="/opt/poetry"
|
||||
ENV POETRY_VERSION="2.2.1"
|
||||
ENV POETRY_VIRTUALENVS_PATH="/cache/.venv"
|
||||
ENV POETRY_VIRTUALENVS_IN_PROJECT="false"
|
||||
ENV PATH="$POETRY_HOME/bin:$PATH"
|
||||
WORKDIR /app
|
||||
RUN mkdir -p /cache/.venv && chmod 777 /cache/.venv
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
curl \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
&& curl -sSL https://install.python-poetry.org | python - \
|
||||
&& curl -1sLf 'https://dl.cloudsmith.io/public/task/task/setup.deb.sh' | bash \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y task \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN poetry self add \
|
||||
poetry-plugin-export \
|
||||
poetry-plugin-shell
|
||||
RUN task --version && poetry --version
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 476 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 339 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.0 MiB |
@@ -1,86 +0,0 @@
|
||||
[mypy]
|
||||
python_version = 3.10
|
||||
namespace_packages = True
|
||||
explicit_package_bases = True
|
||||
strict = True
|
||||
warn_return_any = True
|
||||
warn_unused_configs = True
|
||||
disallow_untyped_defs = True
|
||||
disallow_incomplete_defs = True
|
||||
check_untyped_defs = True
|
||||
no_implicit_optional = True
|
||||
warn_redundant_casts = True
|
||||
warn_unused_ignores = True
|
||||
warn_no_return = True
|
||||
warn_unreachable = True
|
||||
strict_equality = True
|
||||
|
||||
# Main module
|
||||
[mypy-__main__.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-comfy.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# ComfyUI folder_paths module
|
||||
[mypy-folder_paths]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-folder_paths.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# Diffusers library
|
||||
[mypy-diffusers]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-diffusers.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# Dependency Injector
|
||||
[mypy-dependency_injector]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-dependency_injector.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# Transformers library
|
||||
[mypy-transformers]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-transformers.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# PyTorch
|
||||
[mypy-torch]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-torch.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# TorchVision
|
||||
[mypy-torchvision]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-torchvision.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# TorchAudio
|
||||
[mypy-torchaudio]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-torchaudio.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# OmegaConf
|
||||
[mypy-omegaconf]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-omegaconf.*]
|
||||
ignore_missing_imports = True
|
||||
|
||||
# SafeTensors
|
||||
[mypy-safetensors]
|
||||
ignore_missing_imports = True
|
||||
|
||||
[mypy-safetensors.*]
|
||||
ignore_missing_imports = True
|
||||
@@ -0,0 +1,612 @@
|
||||
import copy
|
||||
import os
|
||||
import gc
|
||||
import torch
|
||||
import traceback
|
||||
from safetensors.torch import load_file
|
||||
from .utils import SCHEDULERS, token_auto_concat_embeds, vae_pt_to_vae_diffuser, convert_images_to_tensors, convert_tensors_to_images, resize_images
|
||||
from comfy.model_management import get_torch_device
|
||||
import folder_paths
|
||||
from streamdiffusion import StreamDiffusion
|
||||
from streamdiffusion.image_utils import postprocess_image
|
||||
from diffusers import StableDiffusionPipeline, AutoencoderKL, AutoencoderTiny
|
||||
from pathlib import Path
|
||||
|
||||
class DiffusersPipelineLoader:
|
||||
def __init__(self):
|
||||
self.tmp_dir = folder_paths.get_temp_directory()
|
||||
self.dtype = torch.float32
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "ckpt_name": (folder_paths.get_filename_list("checkpoints"), ), }}
|
||||
|
||||
RETURN_TYPES = ("PIPELINE", "AUTOENCODER", "SCHEDULER",)
|
||||
|
||||
FUNCTION = "create_pipeline"
|
||||
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
def create_pipeline(self, ckpt_name):
|
||||
ckpt_cache_path = os.path.join(self.tmp_dir, ckpt_name)
|
||||
|
||||
StableDiffusionPipeline.from_single_file(
|
||||
pretrained_model_link_or_path=folder_paths.get_full_path("checkpoints", ckpt_name),
|
||||
torch_dtype=self.dtype,
|
||||
cache_dir=self.tmp_dir,
|
||||
).save_pretrained(ckpt_cache_path, safe_serialization=True)
|
||||
|
||||
pipe = StableDiffusionPipeline.from_pretrained(
|
||||
pretrained_model_name_or_path=ckpt_cache_path,
|
||||
torch_dtype=self.dtype,
|
||||
cache_dir=self.tmp_dir,
|
||||
)
|
||||
return ((pipe, ckpt_cache_path), pipe.vae, pipe.scheduler)
|
||||
|
||||
class DiffusersVaeLoader:
|
||||
def __init__(self):
|
||||
self.tmp_dir = folder_paths.get_temp_directory()
|
||||
self.dtype = torch.float32
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "vae_name": (folder_paths.get_filename_list("vae"), ), }}
|
||||
|
||||
RETURN_TYPES = ("AUTOENCODER",)
|
||||
|
||||
FUNCTION = "create_pipeline"
|
||||
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
def create_pipeline(self, vae_name):
|
||||
ckpt_cache_path = os.path.join(self.tmp_dir, vae_name)
|
||||
vae_pt_to_vae_diffuser(folder_paths.get_full_path("vae", vae_name), ckpt_cache_path)
|
||||
|
||||
vae = AutoencoderKL.from_pretrained(
|
||||
pretrained_model_name_or_path=ckpt_cache_path,
|
||||
torch_dtype=self.dtype,
|
||||
cache_dir=self.tmp_dir,
|
||||
)
|
||||
|
||||
return (vae,)
|
||||
|
||||
class DiffusersSchedulerLoader:
|
||||
def __init__(self):
|
||||
self.tmp_dir = folder_paths.get_temp_directory()
|
||||
self.dtype = torch.float32
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"pipeline": ("PIPELINE", ),
|
||||
"scheduler_name": (list(SCHEDULERS.keys()), ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SCHEDULER",)
|
||||
|
||||
FUNCTION = "load_scheduler"
|
||||
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
def load_scheduler(self, pipeline, scheduler_name):
|
||||
scheduler = SCHEDULERS[scheduler_name].from_pretrained(
|
||||
pretrained_model_name_or_path=pipeline[1],
|
||||
torch_dtype=self.dtype,
|
||||
cache_dir=self.tmp_dir,
|
||||
subfolder='scheduler'
|
||||
)
|
||||
return (scheduler,)
|
||||
|
||||
class DiffusersModelMakeup:
|
||||
def __init__(self):
|
||||
self.torch_device = get_torch_device()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"pipeline": ("PIPELINE", ),
|
||||
"scheduler": ("SCHEDULER", ),
|
||||
"autoencoder": ("AUTOENCODER", ),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MAKED_PIPELINE",)
|
||||
|
||||
FUNCTION = "makeup_pipeline"
|
||||
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
def makeup_pipeline(self, pipeline, scheduler, autoencoder):
|
||||
pipe = pipeline[0]
|
||||
ckpt_cache_path = pipeline[1]
|
||||
pipe.vae = autoencoder
|
||||
pipe.scheduler = scheduler
|
||||
pipe.safety_checker = None if pipe.safety_checker is None else lambda images, **kwargs: (images, [False])
|
||||
pipe.enable_attention_slicing()
|
||||
pipe = pipe.to(self.torch_device)
|
||||
return ((pipe, ckpt_cache_path),)
|
||||
|
||||
class DiffusersClipTextEncode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"maked_pipeline": ("MAKED_PIPELINE", ),
|
||||
"positive": ("STRING", {"multiline": True}),
|
||||
"negative": ("STRING", {"multiline": True}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("EMBEDS", "EMBEDS", "STRING", "STRING", )
|
||||
RETURN_NAMES = ("positive_embeds", "negative_embeds", "positive", "negative", )
|
||||
|
||||
FUNCTION = "concat_embeds"
|
||||
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
def concat_embeds(self, maked_pipeline, positive, negative):
|
||||
positive_embeds, negative_embeds = token_auto_concat_embeds(maked_pipeline[0], positive,negative)
|
||||
|
||||
return (positive_embeds, negative_embeds, positive, negative, )
|
||||
|
||||
class DiffusersSampler:
|
||||
def __init__(self):
|
||||
self.torch_device = get_torch_device()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"maked_pipeline": ("MAKED_PIPELINE", ),
|
||||
"positive_embeds": ("EMBEDS", ),
|
||||
"negative_embeds": ("EMBEDS", ),
|
||||
"width": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}),
|
||||
"height": ("INT", {"default": 512, "min": 1, "max": 8192, "step": 1}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "sample"
|
||||
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
def sample(self, maked_pipeline, positive_embeds, negative_embeds, height, width, steps, cfg, seed):
|
||||
images = maked_pipeline[0](
|
||||
prompt_embeds=positive_embeds,
|
||||
height=height,
|
||||
width=width,
|
||||
num_inference_steps=steps,
|
||||
guidance_scale=cfg,
|
||||
negative_prompt_embeds=negative_embeds,
|
||||
generator=torch.Generator(self.torch_device).manual_seed(seed)
|
||||
).images
|
||||
return (convert_images_to_tensors(images),)
|
||||
|
||||
# - Stream Diffusion -
|
||||
|
||||
class CreateIntListNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
max_element = 10
|
||||
return {
|
||||
"required": {
|
||||
"elements_count" : ("INT", {"default": 2, "min": 1, "max": max_element, "step": 1}),
|
||||
},
|
||||
"optional": {
|
||||
f"element_{i}": ("INT", {"default": 0}) for i in range(1, max_element)
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
FUNCTION = "create_list"
|
||||
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
def create_list(self, elements_count, **kwargs):
|
||||
return ([value for key, value in kwargs.items()][:elements_count], )
|
||||
|
||||
class LcmLoraLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "lora_name": (folder_paths.get_filename_list("loras"), ), }}
|
||||
|
||||
RETURN_TYPES = ("LCM_LORA",)
|
||||
FUNCTION = "load_lora"
|
||||
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
def load_lora(self, lora_name):
|
||||
return (load_file(folder_paths.get_full_path("loras", lora_name)), )
|
||||
|
||||
class StreamDiffusionCreateStream:
|
||||
def __init__(self):
|
||||
self.dtype = torch.float32
|
||||
self.torch_device = get_torch_device()
|
||||
self.tmp_dir = folder_paths.get_temp_directory()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"maked_pipeline": ("MAKED_PIPELINE", ),
|
||||
"t_index_list": ("LIST", ),
|
||||
"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"}),
|
||||
"acceleration": (["none", "xformers", "tensorrt"], {"default": "tensorrt"}),
|
||||
"lcm_lora" : ("LCM_LORA", ),
|
||||
"tiny_vae" : ("STRING", {"default": "madebyollin/taesd"}),
|
||||
"use_lcm_lora" : ("BOOLEAN", {"default": True}),
|
||||
"use_tiny_vae" : ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STREAM",)
|
||||
FUNCTION = "load_stream"
|
||||
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
def load_stream(self, maked_pipeline, t_index_list, width, height, do_add_noise, use_denoising_batch, frame_buffer_size, cfg_type, acceleration, lcm_lora, tiny_vae, use_lcm_lora, use_tiny_vae):
|
||||
model_id_or_path = maked_pipeline[1]
|
||||
maked_pipeline: StableDiffusionPipeline = copy.deepcopy(maked_pipeline[0])
|
||||
lcm_lora = copy.deepcopy(lcm_lora)
|
||||
stream = StreamDiffusion(
|
||||
pipe = maked_pipeline,
|
||||
t_index_list = t_index_list,
|
||||
torch_dtype = self.dtype,
|
||||
width = width,
|
||||
height = height,
|
||||
do_add_noise = do_add_noise,
|
||||
use_denoising_batch = use_denoising_batch,
|
||||
frame_buffer_size = frame_buffer_size,
|
||||
cfg_type = cfg_type,
|
||||
)
|
||||
stream.load_lcm_lora(lcm_lora)
|
||||
stream.fuse_lora()
|
||||
stream.vae = AutoencoderTiny.from_pretrained(
|
||||
pretrained_model_name_or_path=tiny_vae,
|
||||
torch_dtype=self.dtype,
|
||||
cache_dir=self.tmp_dir,
|
||||
).to(
|
||||
device=maked_pipeline.device,
|
||||
dtype=maked_pipeline.dtype
|
||||
)
|
||||
|
||||
batch_size = (
|
||||
len(t_index_list) * frame_buffer_size
|
||||
if use_denoising_batch
|
||||
else frame_buffer_size
|
||||
)
|
||||
|
||||
try:
|
||||
if acceleration == "xformers":
|
||||
stream.pipe.enable_xformers_memory_efficient_attention()
|
||||
if acceleration == "tensorrt":
|
||||
from polygraphy import cuda
|
||||
from streamdiffusion.acceleration.tensorrt import (
|
||||
TorchVAEEncoder,
|
||||
compile_unet,
|
||||
compile_vae_decoder,
|
||||
compile_vae_encoder,
|
||||
)
|
||||
from streamdiffusion.acceleration.tensorrt.engine import (
|
||||
AutoencoderKLEngine,
|
||||
UNet2DConditionModelEngine,
|
||||
)
|
||||
from streamdiffusion.acceleration.tensorrt.models import (
|
||||
VAE,
|
||||
UNet,
|
||||
VAEEncoder,
|
||||
)
|
||||
|
||||
def create_prefix(
|
||||
model_id_or_path: str,
|
||||
max_batch_size: int,
|
||||
min_batch_size: int,
|
||||
):
|
||||
maybe_path = Path(model_id_or_path)
|
||||
if maybe_path.exists():
|
||||
return f"{maybe_path.stem}--lcm_lora-{use_lcm_lora}--tiny_vae-{use_tiny_vae}--max_batch-{max_batch_size}--min_batch-{min_batch_size}--mode-{self.mode}"
|
||||
else:
|
||||
return f"{model_id_or_path}--lcm_lora-{use_lcm_lora}--tiny_vae-{use_tiny_vae}--max_batch-{max_batch_size}--min_batch-{min_batch_size}--mode-{self.mode}"
|
||||
|
||||
engine_dir = Path(engine_dir)
|
||||
unet_path = os.path.join(
|
||||
engine_dir,
|
||||
create_prefix(
|
||||
model_id_or_path=model_id_or_path,
|
||||
max_batch_size=stream.trt_unet_batch_size,
|
||||
min_batch_size=stream.trt_unet_batch_size,
|
||||
),
|
||||
"unet.engine",
|
||||
)
|
||||
vae_encoder_path = os.path.join(
|
||||
engine_dir,
|
||||
create_prefix(
|
||||
model_id_or_path=model_id_or_path,
|
||||
max_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
min_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
),
|
||||
"vae_encoder.engine",
|
||||
)
|
||||
vae_decoder_path = os.path.join(
|
||||
engine_dir,
|
||||
create_prefix(
|
||||
model_id_or_path=model_id_or_path,
|
||||
max_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
min_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
),
|
||||
"vae_decoder.engine",
|
||||
)
|
||||
|
||||
if not os.path.exists(unet_path):
|
||||
os.makedirs(os.path.dirname(unet_path), exist_ok=True)
|
||||
unet_model = UNet(
|
||||
fp16=True,
|
||||
device=stream.device,
|
||||
max_batch_size=stream.trt_unet_batch_size,
|
||||
min_batch_size=stream.trt_unet_batch_size,
|
||||
embedding_dim=stream.text_encoder.config.hidden_size,
|
||||
unet_dim=stream.unet.config.in_channels,
|
||||
)
|
||||
compile_unet(
|
||||
stream.unet,
|
||||
unet_model,
|
||||
unet_path + ".onnx",
|
||||
unet_path + ".opt.onnx",
|
||||
unet_path,
|
||||
opt_batch_size=stream.trt_unet_batch_size,
|
||||
)
|
||||
|
||||
if not os.path.exists(vae_decoder_path):
|
||||
os.makedirs(os.path.dirname(vae_decoder_path), exist_ok=True)
|
||||
stream.vae.forward = stream.vae.decode
|
||||
vae_decoder_model = VAE(
|
||||
device=stream.device,
|
||||
max_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
min_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
)
|
||||
compile_vae_decoder(
|
||||
stream.vae,
|
||||
vae_decoder_model,
|
||||
vae_decoder_path + ".onnx",
|
||||
vae_decoder_path + ".opt.onnx",
|
||||
vae_decoder_path,
|
||||
opt_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
)
|
||||
delattr(stream.vae, "forward")
|
||||
|
||||
if not os.path.exists(vae_encoder_path):
|
||||
os.makedirs(os.path.dirname(vae_encoder_path), exist_ok=True)
|
||||
vae_encoder = TorchVAEEncoder(stream.vae).to(torch.device("cuda"))
|
||||
vae_encoder_model = VAEEncoder(
|
||||
device=stream.device,
|
||||
max_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
min_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
)
|
||||
compile_vae_encoder(
|
||||
vae_encoder,
|
||||
vae_encoder_model,
|
||||
vae_encoder_path + ".onnx",
|
||||
vae_encoder_path + ".opt.onnx",
|
||||
vae_encoder_path,
|
||||
opt_batch_size=batch_size
|
||||
if self.mode == "txt2img"
|
||||
else stream.frame_bff_size,
|
||||
)
|
||||
|
||||
cuda_steram = cuda.Stream()
|
||||
|
||||
vae_config = stream.vae.config
|
||||
vae_dtype = stream.vae.dtype
|
||||
|
||||
stream.unet = UNet2DConditionModelEngine(
|
||||
unet_path, cuda_steram, use_cuda_graph=False
|
||||
)
|
||||
stream.vae = AutoencoderKLEngine(
|
||||
vae_encoder_path,
|
||||
vae_decoder_path,
|
||||
cuda_steram,
|
||||
stream.pipe.vae_scale_factor,
|
||||
use_cuda_graph=False,
|
||||
)
|
||||
setattr(stream.vae, "config", vae_config)
|
||||
setattr(stream.vae, "dtype", vae_dtype)
|
||||
|
||||
gc.collect()
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
print("TensorRT acceleration enabled.")
|
||||
if acceleration == "sfast":
|
||||
from streamdiffusion.acceleration.sfast import (
|
||||
accelerate_with_stable_fast,
|
||||
)
|
||||
|
||||
stream = accelerate_with_stable_fast(stream)
|
||||
print("StableFast acceleration enabled.")
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
print("Acceleration has failed. Falling back to normal mode.")
|
||||
return (stream, )
|
||||
|
||||
class StreamDiffusionSampler:
|
||||
def __init__(self):
|
||||
self.torch_device = get_torch_device()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"stream": ("STREAM", ),
|
||||
"positive": ("STRING", {"multiline": True}),
|
||||
"negative": ("STRING", {"multiline": True}),
|
||||
"steps": ("INT", {"default": 50, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 100.0}),
|
||||
"delta": ("FLOAT", {"default": 1, "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": 1, "min": 0, "max": 10000}),
|
||||
},
|
||||
"optional" : {
|
||||
"image" : ("IMAGE", )
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "sample"
|
||||
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
def sample(self, stream: StreamDiffusion, positive, negative, steps, cfg, delta, seed, num, warmup, image = None):
|
||||
stream.prepare(
|
||||
prompt = positive,
|
||||
negative_prompt = negative,
|
||||
num_inference_steps = steps,
|
||||
guidance_scale = cfg,
|
||||
delta = delta,
|
||||
seed = seed
|
||||
)
|
||||
|
||||
if image != None:
|
||||
image = convert_tensors_to_images(image)
|
||||
image = resize_images(image, (stream.width, stream.height))
|
||||
|
||||
for _ in range(warmup):
|
||||
stream()
|
||||
|
||||
result = []
|
||||
for _ in range(num):
|
||||
x_outputs = []
|
||||
if image is None:
|
||||
x_outputs.append(stream.txt2img())
|
||||
else:
|
||||
stream(image[0])
|
||||
for i in image[1:] + image[-1:]:
|
||||
x_outputs.append(stream(i))
|
||||
for x_output in x_outputs:
|
||||
result.append(postprocess_image(x_output, output_type="pil")[0])
|
||||
|
||||
return (convert_images_to_tensors(result),)
|
||||
|
||||
class StreamDiffusionWarmup:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"stream": ("STREAM", ),
|
||||
"negative": ("STRING", {"multiline": True}),
|
||||
"steps": ("INT", {"default": 50, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 100.0}),
|
||||
"delta": ("FLOAT", {"default": 1, "min": 0.0, "max": 1.0}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"warmup": ("INT", {"default": 1, "min": 0, "max": 10000}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("WARMUP_STREAM",)
|
||||
|
||||
FUNCTION = "stream_warmup"
|
||||
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
def stream_warmup(self, stream: StreamDiffusion, negative, steps, cfg, delta, seed, warmup):
|
||||
stream.prepare(
|
||||
prompt="",
|
||||
negative_prompt=negative,
|
||||
num_inference_steps = steps,
|
||||
guidance_scale = cfg,
|
||||
delta = delta,
|
||||
seed = seed
|
||||
)
|
||||
|
||||
for _ in range(warmup):
|
||||
stream()
|
||||
|
||||
return (stream, )
|
||||
|
||||
|
||||
class StreamDiffusionFastSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"warmup_stream": ("WARMUP_STREAM", ),
|
||||
"positive": ("STRING", {"multiline": True}),
|
||||
"num": ("INT", {"default": 1, "min": 1, "max": 10000}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "sample"
|
||||
|
||||
CATEGORY = "Diffusers/StreamDiffusion"
|
||||
|
||||
def sample(self, warmup_stream, positive, num):
|
||||
stream: StreamDiffusion = warmup_stream
|
||||
|
||||
stream.update_prompt(positive)
|
||||
|
||||
result = []
|
||||
for _ in range(num):
|
||||
x_output = stream.txt2img()
|
||||
result.append(postprocess_image(x_output, output_type="pil")[0])
|
||||
return (convert_images_to_tensors(result),)
|
||||
|
||||
# - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"DiffusersPipelineLoader": DiffusersPipelineLoader,
|
||||
"DiffusersVaeLoader": DiffusersVaeLoader,
|
||||
"DiffusersSchedulerLoader": DiffusersSchedulerLoader,
|
||||
"DiffusersModelMakeup": DiffusersModelMakeup,
|
||||
"DiffusersClipTextEncode": DiffusersClipTextEncode,
|
||||
"DiffusersSampler": DiffusersSampler,
|
||||
"CreateIntListNode": CreateIntListNode,
|
||||
"LcmLoraLoader": LcmLoraLoader,
|
||||
"StreamDiffusionCreateStream": StreamDiffusionCreateStream,
|
||||
"StreamDiffusionSampler": StreamDiffusionSampler,
|
||||
"StreamDiffusionWarmup": StreamDiffusionWarmup,
|
||||
"StreamDiffusionFastSampler": StreamDiffusionFastSampler,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"DiffusersPipelineLoader": "Diffusers Pipeline Loader",
|
||||
"DiffusersVaeLoader": "Diffusers Vae Loader",
|
||||
"DiffusersSchedulerLoader": "Diffusers Scheduler Loader",
|
||||
"DiffusersModelMakeup": "Diffusers Model Makeup",
|
||||
"DiffusersClipTextEncode": "Diffusers Clip Text Encode",
|
||||
"DiffusersSampler": "Diffusers Sampler",
|
||||
"CreateIntListNode": "Create Int List",
|
||||
"LcmLoraLoader": "LCM Lora Loader",
|
||||
"StreamDiffusionCreateStream": "StreamDiffusion Create Stream",
|
||||
"StreamDiffusionSampler": "StreamDiffusion Sampler",
|
||||
"StreamDiffusionWarmup": "StreamDiffusion Warmup",
|
||||
"StreamDiffusionFastSampler": "StreamDiffusion Fast Sampler",
|
||||
}
|
||||
Generated
-3258
File diff suppressed because it is too large
Load Diff
-222
@@ -1,222 +0,0 @@
|
||||
[project]
|
||||
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" },
|
||||
]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.15"
|
||||
dependencies = [
|
||||
"dependency-injector (>=4.48.2,<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" }]
|
||||
|
||||
[[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"]
|
||||
python_files = ["test_*.py", "*_test.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = [
|
||||
"--strict-markers",
|
||||
"--strict-config",
|
||||
"--cov=src",
|
||||
"--cov-branch",
|
||||
"--cov-report=term-missing:skip-covered",
|
||||
"--cov-report=html",
|
||||
"--cov-report=xml",
|
||||
"--cov-fail-under=80",
|
||||
"-v",
|
||||
"-ra",
|
||||
]
|
||||
markers = [
|
||||
"unit: Unit tests",
|
||||
"integration: Integration tests",
|
||||
"slow: Slow tests",
|
||||
]
|
||||
filterwarnings = ["error", "ignore::UserWarning", "ignore::DeprecationWarning"]
|
||||
|
||||
[tool.coverage.run]
|
||||
source = ["src"]
|
||||
omit = [
|
||||
"*/tests/*",
|
||||
"*/test_*.py",
|
||||
"*/__init__.py",
|
||||
"src/di/*", # DI container doesn't need test coverage
|
||||
]
|
||||
branch = true
|
||||
parallel = true
|
||||
|
||||
[tool.coverage.report]
|
||||
precision = 2
|
||||
show_missing = true
|
||||
skip_covered = false
|
||||
exclude_lines = [
|
||||
"pragma: no cover",
|
||||
"def __repr__",
|
||||
"def __str__",
|
||||
"raise AssertionError",
|
||||
"raise NotImplementedError",
|
||||
"if __name__ == .__main__.:",
|
||||
"if TYPE_CHECKING:",
|
||||
"if typing.TYPE_CHECKING:",
|
||||
"@abstractmethod",
|
||||
"@abc.abstractmethod",
|
||||
"@overload",
|
||||
"class .*\\bProtocol\\):",
|
||||
"pass",
|
||||
]
|
||||
|
||||
[tool.coverage.html]
|
||||
directory = "htmlcov"
|
||||
|
||||
[tool.coverage.xml]
|
||||
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",
|
||||
]
|
||||
skips = ["B101", "B601"] # Allow assert in tests & shell=True if needed
|
||||
|
||||
[tool.bandit.assert_used]
|
||||
skips = ["*/test_*.py", "*/*_test.py"]
|
||||
|
||||
[tool.importlinter]
|
||||
root_package = "src"
|
||||
|
||||
[[tool.importlinter.contracts]]
|
||||
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)
|
||||
]
|
||||
containers = ["src"]
|
||||
|
||||
[[tool.importlinter.contracts]]
|
||||
name = "Domain Layer Independence"
|
||||
type = "forbidden"
|
||||
source_modules = ["src.domain"]
|
||||
forbidden_modules = [
|
||||
"src.service",
|
||||
"src.ui",
|
||||
"src.nodes",
|
||||
"src.infra",
|
||||
"src.di",
|
||||
]
|
||||
|
||||
[[tool.importlinter.contracts]]
|
||||
name = "Application Layer Independence"
|
||||
type = "forbidden"
|
||||
source_modules = ["src.service"]
|
||||
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"]
|
||||
|
||||
[[tool.importlinter.contracts]]
|
||||
name = "Presentation Layer can use Application & Domain"
|
||||
type = "forbidden"
|
||||
source_modules = ["src.ui", "src.nodes"]
|
||||
forbidden_modules = ["src.infra"]
|
||||
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
version = "0.1.0"
|
||||
tag_format = "v$version"
|
||||
version_files = ["pyproject.toml:version"]
|
||||
update_changelog_on_bump = true
|
||||
changelog_file = "CHANGELOG.md"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ruff (>=0.14.2,<0.15.0)",
|
||||
"mypy (>=1.18.2,<2.0.0)",
|
||||
"types-requests (>=2.32.4.20250913,<3.0.0.0)",
|
||||
"pytest (>=8.4.2,<9.0.0)",
|
||||
"pytest-cov (>=7.0.0,<8.0.0)",
|
||||
"pytest-asyncio (>=1.2.0,<2.0.0)",
|
||||
"pytest-mock (>=3.15.1,<4.0.0)",
|
||||
"pytest-xdist (>=3.8.0,<4.0.0)",
|
||||
"bandit[toml] (>=1.8.6,<2.0.0)",
|
||||
"import-linter (>=2.5.2,<3.0.0)",
|
||||
"pre-commit (>=4.3.0,<5.0.0)",
|
||||
"commitizen (>=4.9.1,<5.0.0)",
|
||||
]
|
||||
+8
-76
@@ -1,76 +1,8 @@
|
||||
--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.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"
|
||||
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==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"
|
||||
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.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"
|
||||
diffusers[torch]
|
||||
accelerate
|
||||
transformers
|
||||
safetensors
|
||||
omegaconf
|
||||
pytorch_lightning
|
||||
xformers
|
||||
git+https://github.com/cumulo-autumn/StreamDiffusion.git@main#egg=streamdiffusion[tensorrt]
|
||||
@@ -1,56 +0,0 @@
|
||||
# Ruff configuration
|
||||
line-length = 100
|
||||
target-version = "py310"
|
||||
src = ["src"]
|
||||
|
||||
[lint]
|
||||
select = [
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"B", # flake8-bugbear
|
||||
"C4", # flake8-comprehensions
|
||||
"UP", # pyupgrade
|
||||
"D", # pydocstyle
|
||||
"N", # pep8-naming
|
||||
"S", # flake8-bandit
|
||||
"T20", # flake8-print
|
||||
"PT", # flake8-pytest-style
|
||||
"RET", # flake8-return
|
||||
"SIM", # flake8-simplify
|
||||
"ARG", # flake8-unused-arguments
|
||||
"PIE", # flake8-pie
|
||||
"PL", # pylint
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E501", # line too long (handled by formatter)
|
||||
"B008", # do not perform function calls in argument defaults
|
||||
"C901", # too complex
|
||||
"PLR0913", # too many arguments
|
||||
"PLR0912", # too many branches
|
||||
"D203", # one-blank-line-before-class (conflicts with D211)
|
||||
"D213", # multi-line-summary-second-line (conflicts with D212)
|
||||
]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
"__init__.py" = ["F401", "D104", "N999"] # Root __init__.py for ComfyUI-Diffusers
|
||||
"tests/__init__.py" = ["D104", "N999"] # tests/__init__.py
|
||||
"src/**/__init__.py" = ["F401", "D104"] # src package __init__.py files
|
||||
"tests/**/*.py" = ["S101", "D103", "PLR2004", "S106", "ARG001", "PLR0913"]
|
||||
"src/nodes/*.py" = ["N802", "D102"] # Allow INPUT_TYPES uppercase & missing docstrings
|
||||
"src/nodes/dto/*.py" = ["N815"] # Allow ClassVar uppercase (COMFY_TYPE)
|
||||
"src/infra/diffusers/_autoencoder_repository.py" = ["PLR0915"] # Allow large conversion function
|
||||
"src/di/*.py" = ["D102"] # DI container doesn't need method docstrings
|
||||
|
||||
[lint.isort]
|
||||
known-first-party = ["src"]
|
||||
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
|
||||
|
||||
[lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
@@ -1,5 +0,0 @@
|
||||
from ._cache import get_cache_dir
|
||||
from ._env import load_envs
|
||||
from ._path import get_project_root
|
||||
|
||||
__all__ = ["load_envs", "get_cache_dir", "get_project_root"]
|
||||
@@ -1,8 +0,0 @@
|
||||
from ._env import get_str
|
||||
|
||||
|
||||
def get_cache_dir() -> str:
|
||||
cache_dir = get_str("COMFYUI_DIFFUSERS_CACHE_DIR", "./cache")
|
||||
if cache_dir is None:
|
||||
raise ValueError("Cache directory environment variable is not set.")
|
||||
return cache_dir
|
||||
@@ -1,62 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from functools import lru_cache
|
||||
from typing import Final
|
||||
|
||||
from dotenv import find_dotenv, load_dotenv
|
||||
|
||||
from ._path import get_project_root
|
||||
|
||||
_TRUE_SET: Final[set[str]] = {"1", "true", "yes", "on"}
|
||||
_FALSE_SET: Final[set[str]] = {"0", "false", "no", "off"}
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _load_envs_once() -> None:
|
||||
root = get_project_root()
|
||||
dotenv_path = str(root / ".env")
|
||||
default_dotenv_path = str(root / ".env.default")
|
||||
load_dotenv(find_dotenv(dotenv_path, usecwd=True), override=False)
|
||||
load_dotenv(find_dotenv(default_dotenv_path, usecwd=True), override=False)
|
||||
|
||||
|
||||
def load_envs(force: bool = False) -> None:
|
||||
if force:
|
||||
_load_envs_once.cache_clear()
|
||||
_load_envs_once()
|
||||
|
||||
|
||||
def get_str(name: str, default: str | None = None) -> str | None:
|
||||
return os.getenv(name, default)
|
||||
|
||||
|
||||
def get_bool(name: str, default: bool = False) -> bool:
|
||||
raw = os.getenv(name)
|
||||
if raw is None:
|
||||
return default
|
||||
value = raw.strip().lower()
|
||||
if value in _TRUE_SET:
|
||||
return True
|
||||
if value in _FALSE_SET:
|
||||
return False
|
||||
raise ValueError(f"Environment variable '{name}' has invalid boolean value '{raw}'")
|
||||
|
||||
|
||||
def get_int(name: str, default: int | None = None) -> int | None:
|
||||
raw = os.getenv(name)
|
||||
if raw is None:
|
||||
return default
|
||||
try:
|
||||
return int(raw.strip())
|
||||
except (TypeError, ValueError) as err:
|
||||
raise ValueError(
|
||||
f"Environment variable '{name}' has invalid integer value '{raw}'"
|
||||
) from err
|
||||
|
||||
|
||||
def require(name: str) -> str:
|
||||
value = os.getenv(name)
|
||||
if value is None:
|
||||
raise KeyError(f"Required environment variable not set: '{name}'")
|
||||
return value
|
||||
@@ -1,5 +0,0 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_project_root() -> Path:
|
||||
return Path(__file__).parents[2].resolve()
|
||||
@@ -1,3 +0,0 @@
|
||||
from .di import Container
|
||||
|
||||
__all__ = ["Container"]
|
||||
@@ -1,80 +0,0 @@
|
||||
"""Dependency injection container configuration."""
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
class Container(containers.DeclarativeContainer):
|
||||
"""Dependency injection container for the application."""
|
||||
|
||||
# 1. Repositories
|
||||
pipeline_repository = providers.Factory(DiffusersPipelineRepository)
|
||||
autoencoder_repository = providers.Factory(DiffusersAutoencoderRepository)
|
||||
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(
|
||||
PipelineUsecase,
|
||||
pipeline_repo=pipeline_repository,
|
||||
)
|
||||
autoencoder_usecase = providers.Factory(
|
||||
AutoencoderUsecase,
|
||||
autoencoder_repo=autoencoder_repository,
|
||||
)
|
||||
clip_text_encode_usecase = providers.Factory(
|
||||
ClipTextEncodeUsecase,
|
||||
text_encoder_repo=text_encoder_repository,
|
||||
)
|
||||
sampler_usecase = providers.Factory(
|
||||
SamplerUsecase,
|
||||
sampler_repo=sampler_repository,
|
||||
)
|
||||
scheduler_usecase = providers.Factory(
|
||||
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,40 +0,0 @@
|
||||
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",
|
||||
"Autoencoder",
|
||||
"Clip",
|
||||
"Conditioning",
|
||||
"Image",
|
||||
"Scheduler",
|
||||
"ImageSize",
|
||||
"Steps",
|
||||
"CFGScale",
|
||||
"Seed",
|
||||
"LcmLora",
|
||||
"TIndexList",
|
||||
"FrameBufferSize",
|
||||
"CFGType",
|
||||
"CFGTypeEnum",
|
||||
"Delta",
|
||||
"WarmupCount",
|
||||
"NumSamples",
|
||||
"StreamDiffusionStream",
|
||||
]
|
||||
@@ -1,19 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from diffusers import AutoencoderKL
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Autoencoder:
|
||||
autoencoder: AutoencoderKL
|
||||
path: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.autoencoder, AutoencoderKL):
|
||||
raise ValueError(
|
||||
f"Autoencoder must be an instance of AutoencoderKL, got {type(self.autoencoder).__name__}"
|
||||
)
|
||||
if not isinstance(self.path, str):
|
||||
raise ValueError(f"Path must be a string, got {type(self.path).__name__}")
|
||||
if not self.path.strip():
|
||||
raise ValueError("Path cannot be empty or whitespace only")
|
||||
@@ -1,12 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CFGScale:
|
||||
value: float
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.value, bool) or not isinstance(self.value, float):
|
||||
raise ValueError(f"CFG scale must be a float, got {type(self.value).__name__}")
|
||||
if self.value < 0.0:
|
||||
raise ValueError(f"CFG scale must be non-negative, got {self.value}")
|
||||
@@ -1,22 +0,0 @@
|
||||
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__}")
|
||||
@@ -1,24 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Clip:
|
||||
tokenizer: CLIPTokenizer
|
||||
text_encoder: CLIPTextModel
|
||||
path: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.tokenizer, CLIPTokenizer):
|
||||
raise ValueError(
|
||||
f"Tokenizer must be an instance of CLIPTokenizer, got {type(self.tokenizer).__name__}"
|
||||
)
|
||||
if not isinstance(self.text_encoder, CLIPTextModel):
|
||||
raise ValueError(
|
||||
f"Text encoder must be an instance of CLIPTextModel, got {type(self.text_encoder).__name__}"
|
||||
)
|
||||
if not isinstance(self.path, str):
|
||||
raise ValueError(f"Path must be a string, got {type(self.path).__name__}")
|
||||
if not self.path.strip():
|
||||
raise ValueError("Path cannot be empty or whitespace only")
|
||||
@@ -1,28 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Conditioning:
|
||||
conditioning: torch.Tensor
|
||||
path: str
|
||||
prompt: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.conditioning, torch.Tensor):
|
||||
raise ValueError(
|
||||
f"Conditioning must be a torch.Tensor, got {type(self.conditioning).__name__}"
|
||||
)
|
||||
if not isinstance(self.path, str):
|
||||
raise ValueError(f"Path must be a string, got {type(self.path).__name__}")
|
||||
if not isinstance(self.prompt, str):
|
||||
raise ValueError(f"Prompt must be a string, got {type(self.prompt).__name__}")
|
||||
if torch.isnan(self.conditioning).any():
|
||||
raise ValueError("Conditioning tensor contains NaN values")
|
||||
if torch.isinf(self.conditioning).any():
|
||||
raise ValueError("Conditioning tensor contains Inf values")
|
||||
if not self.path.strip():
|
||||
raise ValueError("Path cannot be empty or whitespace only")
|
||||
if not self.prompt.strip():
|
||||
raise ValueError("Prompt cannot be empty or whitespace only")
|
||||
@@ -1,14 +0,0 @@
|
||||
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}")
|
||||
@@ -1,14 +0,0 @@
|
||||
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}")
|
||||
@@ -1,12 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from PIL import Image as PilImage
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Image:
|
||||
image: PilImage.Image
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.image, PilImage.Image):
|
||||
raise ValueError(f"Image must be a PIL.Image.Image, got {type(self.image).__name__}")
|
||||
@@ -1,21 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ImageSize:
|
||||
width: int
|
||||
height: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.width, bool) or not isinstance(self.width, int):
|
||||
raise ValueError(f"Width must be an int, got {type(self.width).__name__}")
|
||||
if isinstance(self.height, bool) or not isinstance(self.height, int):
|
||||
raise ValueError(f"Height must be an int, got {type(self.height).__name__}")
|
||||
if self.width <= 0:
|
||||
raise ValueError(f"Width must be positive, got {self.width}")
|
||||
if self.height <= 0:
|
||||
raise ValueError(f"Height must be positive, got {self.height}")
|
||||
if self.width % 8 != 0:
|
||||
raise ValueError(f"Width must be divisible by 8, got {self.width}")
|
||||
if self.height % 8 != 0:
|
||||
raise ValueError(f"Height must be divisible by 8, got {self.height}")
|
||||
@@ -1,16 +0,0 @@
|
||||
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")
|
||||
@@ -1,14 +0,0 @@
|
||||
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}")
|
||||
@@ -1,19 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from diffusers import StableDiffusionPipeline
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Pipeline:
|
||||
pipeline: StableDiffusionPipeline
|
||||
path: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.pipeline, StableDiffusionPipeline):
|
||||
raise ValueError(
|
||||
f"Pipeline must be an instance of StableDiffusionPipeline, got {type(self.pipeline).__name__}"
|
||||
)
|
||||
if not isinstance(self.path, str):
|
||||
raise ValueError(f"Path must be a string, got {type(self.path).__name__}")
|
||||
if not self.path.strip():
|
||||
raise ValueError("Path cannot be empty or whitespace only")
|
||||
@@ -1,46 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
from diffusers import (
|
||||
DDIMScheduler,
|
||||
DDPMScheduler,
|
||||
DEISMultistepScheduler,
|
||||
DPMSolverMultistepScheduler,
|
||||
DPMSolverSinglestepScheduler,
|
||||
EulerAncestralDiscreteScheduler,
|
||||
EulerDiscreteScheduler,
|
||||
HeunDiscreteScheduler,
|
||||
KDPM2AncestralDiscreteScheduler,
|
||||
KDPM2DiscreteScheduler,
|
||||
UniPCMultistepScheduler,
|
||||
)
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Scheduler:
|
||||
class Type(Enum):
|
||||
DDIM = DDIMScheduler
|
||||
DDPM = DDPMScheduler
|
||||
DEISMultistep = DEISMultistepScheduler
|
||||
DPMSolverMultistep = DPMSolverMultistepScheduler
|
||||
DPMSolverSinglestep = DPMSolverSinglestepScheduler
|
||||
EulerAncestralDiscrete = EulerAncestralDiscreteScheduler
|
||||
EulerDiscrete = EulerDiscreteScheduler
|
||||
HeunDiscrete = HeunDiscreteScheduler
|
||||
KDPM2AncestralDiscrete = KDPM2AncestralDiscreteScheduler
|
||||
KDPM2Discrete = KDPM2DiscreteScheduler
|
||||
UniPCMultistep = UniPCMultistepScheduler
|
||||
|
||||
scheduler: SchedulerMixin
|
||||
path: str
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.scheduler, SchedulerMixin):
|
||||
raise ValueError(
|
||||
f"Scheduler must inherit from SchedulerMixin, got {type(self.scheduler).__name__}"
|
||||
)
|
||||
if not isinstance(self.path, str):
|
||||
raise ValueError(f"Path must be a string, got {type(self.path).__name__}")
|
||||
if not self.path.strip():
|
||||
raise ValueError("Path cannot be empty or whitespace only")
|
||||
@@ -1,10 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Seed:
|
||||
value: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.value, bool) or not isinstance(self.value, int):
|
||||
raise ValueError(f"Seed must be an int, got {type(self.value).__name__}")
|
||||
@@ -1,12 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Steps:
|
||||
value: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if isinstance(self.value, bool) or not isinstance(self.value, int):
|
||||
raise ValueError(f"Steps must be an int, got {type(self.value).__name__}")
|
||||
if self.value <= 0:
|
||||
raise ValueError(f"Steps must be positive, got {self.value}")
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
@@ -1,35 +0,0 @@
|
||||
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)
|
||||
@@ -1,14 +0,0 @@
|
||||
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,17 +0,0 @@
|
||||
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__ = [
|
||||
"PipelineRepository",
|
||||
"AutoencoderRepository",
|
||||
"TextEncoderRepository",
|
||||
"SamplerRepository",
|
||||
"SchedulerRepository",
|
||||
"LcmLoraRepository",
|
||||
"StreamDiffusionRepository",
|
||||
]
|
||||
@@ -1,14 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
from diffusers import AutoencoderKL
|
||||
|
||||
|
||||
class AutoencoderRepository(ABC):
|
||||
@abstractmethod
|
||||
def convert_and_save_from_single_file(self, checkpoint_path: str, dtype: torch.dtype) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def load_autoencoder_from_path(self, model_path: str, dtype: torch.dtype) -> AutoencoderKL:
|
||||
pass
|
||||
@@ -1,16 +0,0 @@
|
||||
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
|
||||
"""
|
||||
@@ -1,16 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
from diffusers import StableDiffusionPipeline
|
||||
|
||||
|
||||
class PipelineRepository(ABC):
|
||||
@abstractmethod
|
||||
def convert_and_save_from_single_file(self, checkpoint_path: str, dtype: torch.dtype) -> str:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def load_pipeline_from_path(
|
||||
self, model_path: str, dtype: torch.dtype
|
||||
) -> StableDiffusionPipeline:
|
||||
pass
|
||||
@@ -1,25 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
from diffusers import AutoencoderKL, StableDiffusionPipeline
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
from PIL import Image
|
||||
|
||||
from ..model import CFGScale, ImageSize, Seed, Steps
|
||||
|
||||
|
||||
class SamplerRepository(ABC):
|
||||
@abstractmethod
|
||||
def sample(
|
||||
self,
|
||||
pipeline: StableDiffusionPipeline,
|
||||
vae: AutoencoderKL,
|
||||
scheduler: SchedulerMixin,
|
||||
positive_embeds: torch.Tensor,
|
||||
negative_embeds: torch.Tensor,
|
||||
image_size: ImageSize,
|
||||
steps: Steps,
|
||||
cfg: CFGScale,
|
||||
seed: Seed,
|
||||
) -> list[Image.Image]:
|
||||
pass
|
||||
@@ -1,14 +0,0 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
|
||||
from ...domain.model import Scheduler
|
||||
|
||||
|
||||
class SchedulerRepository(ABC):
|
||||
@abstractmethod
|
||||
def create_scheduler(
|
||||
self, model_path: str, dtype: torch.dtype, scheduler_type: Scheduler.Type
|
||||
) -> SchedulerMixin:
|
||||
pass
|
||||
@@ -1,137 +0,0 @@
|
||||
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,14 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
import torch
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
|
||||
|
||||
class TextEncoderRepository(ABC):
|
||||
@abstractmethod
|
||||
def encode(
|
||||
self, tokenizer: CLIPTokenizer, text_encoder: CLIPTextModel, text: str
|
||||
) -> torch.Tensor:
|
||||
pass
|
||||
@@ -1,17 +0,0 @@
|
||||
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__ = [
|
||||
"DiffusersPipelineRepository",
|
||||
"DiffusersAutoencoderRepository",
|
||||
"DiffusersTextEncoderRepository",
|
||||
"DiffusersSamplerRepository",
|
||||
"DiffusersSchedulerRepository",
|
||||
"DiffusersLcmLoraRepository",
|
||||
"DiffusersStreamDiffusionRepository",
|
||||
]
|
||||
@@ -1,41 +0,0 @@
|
||||
import os
|
||||
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device # pyright: ignore[reportMissingImports]
|
||||
from diffusers import AutoencoderKL
|
||||
|
||||
from ...domain.repositories import AutoencoderRepository
|
||||
from ._cache import get_cache_path, is_vae_cached
|
||||
|
||||
|
||||
class DiffusersAutoencoderRepository(AutoencoderRepository):
|
||||
def __init__(self) -> None:
|
||||
self.cache_dir = get_cache_path()
|
||||
self.device = get_torch_device()
|
||||
|
||||
def convert_and_save_from_single_file(self, checkpoint_path: str, dtype: torch.dtype) -> str:
|
||||
checkpoint_name = os.path.basename(checkpoint_path)
|
||||
ckpt_cache_path = os.path.join(self.cache_dir, checkpoint_name)
|
||||
|
||||
# Check if already cached
|
||||
if is_vae_cached(ckpt_cache_path):
|
||||
return ckpt_cache_path
|
||||
|
||||
AutoencoderKL.from_single_file(
|
||||
pretrained_model_link_or_path=checkpoint_path,
|
||||
torch_dtype=dtype,
|
||||
cache_dir=self.cache_dir,
|
||||
).save_pretrained(ckpt_cache_path, safe_serialization=True)
|
||||
return ckpt_cache_path
|
||||
|
||||
def load_autoencoder_from_path(self, model_path: str, dtype: torch.dtype) -> AutoencoderKL:
|
||||
if not os.path.exists(model_path):
|
||||
raise FileNotFoundError(f"Model path not found: {model_path}")
|
||||
vae: AutoencoderKL = AutoencoderKL.from_pretrained( # type: ignore[no-untyped-call]
|
||||
pretrained_model_name_or_path=model_path,
|
||||
torch_dtype=dtype,
|
||||
cache_dir=self.cache_dir,
|
||||
).to(self.device)
|
||||
if vae is None:
|
||||
raise RuntimeError(f"Failed to load AutoencoderKL from path: {model_path}")
|
||||
return vae
|
||||
@@ -1,58 +0,0 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from ...config import get_cache_dir, get_project_root
|
||||
|
||||
# Centralized cache helpers for diffusers-related repositories
|
||||
|
||||
_PIPELINE_INDEX_FILE: Final[str] = "model_index.json"
|
||||
_VAE_CONFIG_FILE: Final[str] = "config.json"
|
||||
_VAE_WEIGHT_CANDIDATES: Final[tuple[str, ...]] = (
|
||||
"diffusion_pytorch_model.safetensors",
|
||||
"diffusion_pytorch_model.bin",
|
||||
"pytorch_model.bin",
|
||||
"model.safetensors",
|
||||
)
|
||||
|
||||
|
||||
def setup_cache_path() -> Path:
|
||||
env_cache_str = get_cache_dir()
|
||||
env_cache_path = Path(env_cache_str)
|
||||
if env_cache_path.is_absolute():
|
||||
cache_dir = env_cache_path
|
||||
else:
|
||||
project_root = get_project_root()
|
||||
cache_dir = (project_root / env_cache_path).resolve()
|
||||
if not cache_dir.exists():
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return cache_dir
|
||||
|
||||
|
||||
def get_cache_path(filename: str | None = None) -> Path:
|
||||
cache_dir = setup_cache_path()
|
||||
if filename is None:
|
||||
return cache_dir
|
||||
return cache_dir / filename
|
||||
|
||||
|
||||
def is_pipeline_cached(cache_path: str) -> bool:
|
||||
"""Returns True if the Stable Diffusion pipeline has already been converted.
|
||||
|
||||
Criteria: directory exists and contains a `model_index.json`.
|
||||
"""
|
||||
return os.path.isdir(cache_path) and os.path.isfile(
|
||||
os.path.join(cache_path, _PIPELINE_INDEX_FILE)
|
||||
)
|
||||
|
||||
|
||||
def is_vae_cached(cache_path: str) -> bool:
|
||||
"""Returns True if the VAE has already been converted.
|
||||
|
||||
Criteria: directory exists, contains `config.json`, and at least one known weight file.
|
||||
"""
|
||||
if not (
|
||||
os.path.isdir(cache_path) and os.path.isfile(os.path.join(cache_path, _VAE_CONFIG_FILE))
|
||||
):
|
||||
return False
|
||||
return any(os.path.isfile(os.path.join(cache_path, fname)) for fname in _VAE_WEIGHT_CANDIDATES)
|
||||
@@ -1,17 +0,0 @@
|
||||
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)
|
||||
@@ -1,45 +0,0 @@
|
||||
import os
|
||||
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device # pyright: ignore[reportMissingImports]
|
||||
from diffusers import StableDiffusionPipeline
|
||||
|
||||
from ...domain.repositories import PipelineRepository
|
||||
from ._cache import get_cache_path, is_pipeline_cached
|
||||
|
||||
|
||||
class DiffusersPipelineRepository(PipelineRepository):
|
||||
def __init__(self) -> None:
|
||||
self.cache_dir = get_cache_path()
|
||||
self.device = get_torch_device()
|
||||
|
||||
def convert_and_save_from_single_file(self, checkpoint_path: str, dtype: torch.dtype) -> str:
|
||||
checkpoint_name = os.path.basename(checkpoint_path)
|
||||
ckpt_cache_path = os.path.join(self.cache_dir, checkpoint_name)
|
||||
|
||||
# Check if already cached
|
||||
if is_pipeline_cached(ckpt_cache_path):
|
||||
return ckpt_cache_path
|
||||
|
||||
StableDiffusionPipeline.from_single_file(
|
||||
pretrained_model_link_or_path=checkpoint_path,
|
||||
torch_dtype=dtype,
|
||||
cache_dir=self.cache_dir,
|
||||
).save_pretrained(ckpt_cache_path, safe_serialization=True)
|
||||
return ckpt_cache_path
|
||||
|
||||
def load_pipeline_from_path(
|
||||
self, model_path: str, dtype: torch.dtype
|
||||
) -> StableDiffusionPipeline:
|
||||
pipe: StableDiffusionPipeline = StableDiffusionPipeline.from_pretrained( # type: ignore[no-untyped-call]
|
||||
pretrained_model_name_or_path=model_path,
|
||||
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
|
||||
@@ -1,39 +0,0 @@
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device # pyright: ignore[reportMissingImports]
|
||||
from diffusers import AutoencoderKL, StableDiffusionPipeline
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
from PIL import Image
|
||||
|
||||
from ...domain.model import CFGScale, ImageSize, Seed, Steps
|
||||
from ...domain.repositories import SamplerRepository
|
||||
|
||||
|
||||
class DiffusersSamplerRepository(SamplerRepository):
|
||||
def __init__(self) -> None:
|
||||
self.device = get_torch_device()
|
||||
|
||||
def sample(
|
||||
self,
|
||||
pipeline: StableDiffusionPipeline,
|
||||
vae: AutoencoderKL,
|
||||
scheduler: SchedulerMixin,
|
||||
positive_embeds: torch.Tensor,
|
||||
negative_embeds: torch.Tensor,
|
||||
image_size: ImageSize,
|
||||
steps: Steps,
|
||||
cfg: CFGScale,
|
||||
seed: Seed,
|
||||
) -> list[Image.Image]:
|
||||
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,
|
||||
height=image_size.height,
|
||||
width=image_size.width,
|
||||
num_inference_steps=steps.value,
|
||||
guidance_scale=cfg.value,
|
||||
negative_prompt_embeds=negative_embeds,
|
||||
generator=torch.Generator(self.device).manual_seed(seed.value),
|
||||
)
|
||||
images: list[Image.Image] = result.images
|
||||
return images
|
||||
@@ -1,22 +0,0 @@
|
||||
import torch
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
|
||||
from ...domain.model import Scheduler
|
||||
from ...domain.repositories import SchedulerRepository
|
||||
from ._cache import get_cache_path
|
||||
|
||||
|
||||
class DiffusersSchedulerRepository(SchedulerRepository):
|
||||
def __init__(self) -> None:
|
||||
self.cache_dir = get_cache_path()
|
||||
|
||||
def create_scheduler(
|
||||
self, model_path: str, dtype: torch.dtype, scheduler_type: Scheduler.Type
|
||||
) -> SchedulerMixin:
|
||||
scheduler: SchedulerMixin = scheduler_type.value.from_pretrained(
|
||||
pretrained_model_name_or_path=model_path,
|
||||
subfolder="scheduler",
|
||||
torch_dtype=dtype,
|
||||
cache_dir=self.cache_dir,
|
||||
)
|
||||
return scheduler
|
||||
@@ -1,173 +0,0 @@
|
||||
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,44 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import torch
|
||||
from comfy.model_management import get_torch_device # pyright: ignore[reportMissingImports]
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
|
||||
from ...domain.repositories._text_encoder_repository import TextEncoderRepository
|
||||
|
||||
|
||||
class DiffusersTextEncoderRepository(TextEncoderRepository):
|
||||
def __init__(self) -> None:
|
||||
self.device = get_torch_device()
|
||||
|
||||
def encode(
|
||||
self, tokenizer: CLIPTokenizer, text_encoder: CLIPTextModel, text: str
|
||||
) -> torch.Tensor:
|
||||
max_length = tokenizer.model_max_length
|
||||
input_ids = tokenizer(text, return_tensors="pt").input_ids
|
||||
text_length = input_ids.shape[-1]
|
||||
|
||||
if max_length < text_length:
|
||||
text_ids = input_ids.to(self.device)
|
||||
else:
|
||||
text_ids = tokenizer(
|
||||
text,
|
||||
truncation=False,
|
||||
padding="max_length",
|
||||
max_length=max_length,
|
||||
return_tensors="pt",
|
||||
).input_ids.to(self.device)
|
||||
|
||||
concat_embeds: list[torch.Tensor] = []
|
||||
with torch.no_grad():
|
||||
for i in range(0, text_ids.shape[-1], max_length):
|
||||
segment_ids = text_ids[:, i : i + max_length]
|
||||
outputs = text_encoder(segment_ids)
|
||||
if hasattr(outputs, "last_hidden_state"):
|
||||
embeds = outputs.last_hidden_state
|
||||
else:
|
||||
embeds = outputs[0]
|
||||
concat_embeds.append(embeds)
|
||||
|
||||
text_embeds: torch.Tensor = torch.cat(concat_embeds, dim=1)
|
||||
return text_embeds
|
||||
@@ -1,62 +0,0 @@
|
||||
from ..di import Container
|
||||
from ._diffusers_clip_text_encode import DiffusersClipTextEncode
|
||||
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()
|
||||
container.wire(modules=[__name__])
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
DiffusersPipelineLoader.__name__: DiffusersPipelineLoader,
|
||||
DiffusersVaeLoader.__name__: DiffusersVaeLoader,
|
||||
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",
|
||||
DiffusersVaeLoader.__name__: "Diffusers VAE Loader",
|
||||
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__ = [
|
||||
"NODE_CLASS_MAPPINGS",
|
||||
"NODE_DISPLAY_NAME_MAPPINGS",
|
||||
"ComfyUIPipelineDTO",
|
||||
"ComfyUIAutoencoderDTO",
|
||||
"ComfyUIClipDTO",
|
||||
"ComfyUIConditioningDTO",
|
||||
"ComfyUISchedulerDTO",
|
||||
"ComfyUILcmLoraDTO",
|
||||
"ComfyUIStreamDTO",
|
||||
"ComfyUIWarmupStreamDTO",
|
||||
]
|
||||
@@ -1,40 +0,0 @@
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..usecase import ClipTextEncodeUsecase
|
||||
from .dto import ComfyUIClipDTO, ComfyUIConditioningDTO
|
||||
|
||||
|
||||
class DiffusersClipTextEncode:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict[str, dict[str, tuple[str, ...] | tuple[str, dict[str, bool]]]]:
|
||||
return {
|
||||
"required": {
|
||||
"clip": (ComfyUIClipDTO.COMFY_TYPE,),
|
||||
"text": ("STRING", {"multiline": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUIConditioningDTO.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
clip: ComfyUIClipDTO,
|
||||
text: str,
|
||||
usecase: ClipTextEncodeUsecase = Provide[Container.clip_text_encode_usecase],
|
||||
) -> tuple[ComfyUIConditioningDTO]:
|
||||
clip_domain = ComfyUIClipDTO.to_domain(clip)
|
||||
conditioning_model = usecase.execute(
|
||||
clip_domain.tokenizer,
|
||||
clip_domain.text_encoder,
|
||||
clip_domain.path,
|
||||
text,
|
||||
)
|
||||
conditioning_dto = ComfyUIConditioningDTO.from_domain(conditioning_model)
|
||||
return (conditioning_dto,)
|
||||
@@ -1,41 +0,0 @@
|
||||
import folder_paths # pyright: ignore[reportMissingImports]
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..usecase import PipelineUsecase
|
||||
from .dto import ComfyUIClipDTO, ComfyUIPipelineDTO
|
||||
|
||||
|
||||
class DiffusersPipelineLoader:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict[str, dict[str, tuple[tuple[str, ...], ...]]]:
|
||||
return {
|
||||
"required": {
|
||||
"checkpoint_name": (folder_paths.get_filename_list("checkpoints"),),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (
|
||||
ComfyUIPipelineDTO.COMFY_TYPE,
|
||||
ComfyUIClipDTO.COMFY_TYPE,
|
||||
)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
checkpoint_name: str,
|
||||
usecase: PipelineUsecase = Provide[Container.pipeline_usecase],
|
||||
) -> tuple[ComfyUIPipelineDTO, ComfyUIClipDTO]:
|
||||
checkpoint_path = folder_paths.get_full_path("checkpoints", checkpoint_name)
|
||||
pipeline_model, clip_model = usecase.execute(checkpoint_path)
|
||||
pipeline_dto = ComfyUIPipelineDTO.from_domain(pipeline_model)
|
||||
clip_dto = ComfyUIClipDTO.from_domain(clip_model)
|
||||
return (
|
||||
pipeline_dto,
|
||||
clip_dto,
|
||||
)
|
||||
@@ -1,84 +0,0 @@
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..domain.model import CFGScale, ImageSize, Seed, Steps
|
||||
from ..usecase import SamplerUsecase
|
||||
from .dto import (
|
||||
ComfyUIAutoencoderDTO,
|
||||
ComfyUIConditioningDTO,
|
||||
ComfyUIImage,
|
||||
ComfyUIImageDTO,
|
||||
ComfyUIPipelineDTO,
|
||||
ComfyUISchedulerDTO,
|
||||
)
|
||||
|
||||
|
||||
class DiffusersSampler:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(
|
||||
cls,
|
||||
) -> dict[str, dict[str, tuple[str, ...] | tuple[str, dict[str, int | float]]]]:
|
||||
return {
|
||||
"required": {
|
||||
"pipeline": (ComfyUIPipelineDTO.COMFY_TYPE,),
|
||||
"vae": (ComfyUIAutoencoderDTO.COMFY_TYPE,),
|
||||
"scheduler": (ComfyUISchedulerDTO.COMFY_TYPE,),
|
||||
"positive_embeds": (ComfyUIConditioningDTO.COMFY_TYPE,),
|
||||
"negative_embeds": (ComfyUIConditioningDTO.COMFY_TYPE,),
|
||||
"width": ("INT", {"default": 512, "min": 8, "max": 8192, "step": 8.0}),
|
||||
"height": ("INT", {"default": 512, "min": 8, "max": 8192, "step": 8.0}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000, "step": 1.0}),
|
||||
"cfg": (
|
||||
"FLOAT",
|
||||
{"default": 8.0, "min": 0.0, "max": 100.0, "step": 0.1, "round": 0.01},
|
||||
),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xFFFFFFFFFFFFFFFF, "step": 1.0}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUIImage.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
pipeline: ComfyUIPipelineDTO,
|
||||
vae: ComfyUIAutoencoderDTO,
|
||||
scheduler: ComfyUISchedulerDTO,
|
||||
positive_embeds: ComfyUIConditioningDTO,
|
||||
negative_embeds: ComfyUIConditioningDTO,
|
||||
width: int,
|
||||
height: int,
|
||||
steps: int,
|
||||
cfg: float,
|
||||
seed: int,
|
||||
usecase: SamplerUsecase = Provide[Container.sampler_usecase],
|
||||
) -> tuple[ComfyUIImageDTO]:
|
||||
pipeline_domain = ComfyUIPipelineDTO.to_domain(pipeline)
|
||||
vae_domain = ComfyUIAutoencoderDTO.to_domain(vae)
|
||||
scheduler_domain = ComfyUISchedulerDTO.to_domain(scheduler)
|
||||
positive_embeds_domain = ComfyUIConditioningDTO.to_domain(positive_embeds)
|
||||
negative_embeds_domain = ComfyUIConditioningDTO.to_domain(negative_embeds)
|
||||
|
||||
image_size = ImageSize(width=width, height=height)
|
||||
steps_vo = Steps(value=steps)
|
||||
cfg_vo = CFGScale(value=cfg)
|
||||
seed_vo = Seed(value=seed)
|
||||
|
||||
images_model = usecase.execute(
|
||||
pipeline_domain,
|
||||
vae_domain,
|
||||
scheduler_domain,
|
||||
positive_embeds_domain,
|
||||
negative_embeds_domain,
|
||||
image_size,
|
||||
steps_vo,
|
||||
cfg_vo,
|
||||
seed_vo,
|
||||
)
|
||||
sampler_dto = ComfyUIImage.from_domains(images_model)
|
||||
return (sampler_dto,)
|
||||
@@ -1,37 +0,0 @@
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..usecase import SchedulerUsecase
|
||||
from .dto import ComfyUIPipelineDTO, ComfyUISchedulerDTO
|
||||
from .map import SchedulerMap
|
||||
|
||||
|
||||
class DiffusersSchedulerLoader:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict[str, dict[str, tuple[str] | tuple[list[str]]]]:
|
||||
return {
|
||||
"required": {
|
||||
"pipeline": (ComfyUIPipelineDTO.COMFY_TYPE,),
|
||||
"scheduler_name": (SchedulerMap.SCHEDULERS,),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUISchedulerDTO.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
pipeline: ComfyUIPipelineDTO,
|
||||
scheduler_name: str,
|
||||
usecase: SchedulerUsecase = Provide[Container.scheduler_usecase],
|
||||
) -> tuple[ComfyUISchedulerDTO]:
|
||||
domain = ComfyUIPipelineDTO.to_domain(pipeline)
|
||||
scheduler_type = SchedulerMap.to_domain(scheduler_name)
|
||||
scheduler_domain = usecase.execute(domain, scheduler_type)
|
||||
scheduler_dto = ComfyUISchedulerDTO.from_domain(scheduler_domain)
|
||||
return (scheduler_dto,)
|
||||
@@ -1,34 +0,0 @@
|
||||
import folder_paths # pyright: ignore[reportMissingImports]
|
||||
from dependency_injector.wiring import Provide, inject
|
||||
|
||||
from ..di import Container
|
||||
from ..usecase import AutoencoderUsecase
|
||||
from .dto import ComfyUIAutoencoderDTO
|
||||
|
||||
|
||||
class DiffusersVaeLoader:
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls) -> dict[str, dict[str, tuple[tuple[str, ...], ...]]]:
|
||||
return {
|
||||
"required": {
|
||||
"vae_name": (folder_paths.get_filename_list("vae"),),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (ComfyUIAutoencoderDTO.COMFY_TYPE,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "Diffusers"
|
||||
|
||||
@inject
|
||||
def execute(
|
||||
self,
|
||||
vae_name: str,
|
||||
usecase: AutoencoderUsecase = Provide[Container.autoencoder_usecase],
|
||||
) -> tuple[ComfyUIAutoencoderDTO]:
|
||||
vae_path = folder_paths.get_full_path("vae", vae_name)
|
||||
autoencoder_model = usecase.execute(vae_path)
|
||||
autoencoder_dto = ComfyUIAutoencoderDTO.from_domain(autoencoder_model)
|
||||
return (autoencoder_dto,)
|
||||
@@ -1,45 +0,0 @@
|
||||
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,)
|
||||
@@ -1,112 +0,0 @@
|
||||
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,)
|
||||
@@ -1,66 +0,0 @@
|
||||
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,)
|
||||
@@ -1,117 +0,0 @@
|
||||
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,)
|
||||
@@ -1,85 +0,0 @@
|
||||
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,)
|
||||
@@ -1,21 +0,0 @@
|
||||
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",
|
||||
"ComfyUIAutoencoderDTO",
|
||||
"ComfyUIClipDTO",
|
||||
"ComfyUIConditioningDTO",
|
||||
"ComfyUIImage",
|
||||
"ComfyUIImageDTO",
|
||||
"ComfyUISchedulerDTO",
|
||||
"ComfyUILcmLoraDTO",
|
||||
"ComfyUIStreamDTO",
|
||||
"ComfyUIWarmupStreamDTO",
|
||||
]
|
||||
@@ -1,69 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from diffusers import AutoencoderKL
|
||||
|
||||
from ...domain.model import Autoencoder
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIAutoencoderDTO:
|
||||
"""Data Transfer Object for Diffusers Autoencoder in ComfyUI.
|
||||
|
||||
This DTO is designed to be used across different custom nodes.
|
||||
Other node developers can import and use this type for autoencoder operations.
|
||||
|
||||
Attributes:
|
||||
autoencoder: AutoencoderKL instance from diffusers library
|
||||
path: Path to the loaded model directory
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
|
||||
Example:
|
||||
```python
|
||||
from your_node_package.nodes.dto import ComfyUIAutoencoderDTO
|
||||
|
||||
class YourCustomNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"autoencoder": (ComfyUIAutoencoderDTO.COMFY_TYPE,)}}
|
||||
|
||||
RETURN_TYPES = (ComfyUIAutoencoderDTO.COMFY_TYPE,)
|
||||
|
||||
def execute(self, autoencoder: ComfyUIAutoencoderDTO):
|
||||
# Use the autoencoder for encoding/decoding
|
||||
encoded = autoencoder.autoencoder.encode(image)
|
||||
return (encoded,)
|
||||
```
|
||||
"""
|
||||
|
||||
# ClassVar to avoid being treated as a dataclass field
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_VAE"
|
||||
|
||||
autoencoder: AutoencoderKL
|
||||
path: str
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, autoencoder: Autoencoder) -> "ComfyUIAutoencoderDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
autoencoder: Autoencoder from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUIAutoencoderDTO instance
|
||||
"""
|
||||
return cls(autoencoder=autoencoder.autoencoder, path=autoencoder.path)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUIAutoencoderDTO") -> "Autoencoder":
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUIAutoencoderDTO instance
|
||||
|
||||
Returns:
|
||||
Autoencoder domain model
|
||||
"""
|
||||
return Autoencoder(autoencoder=dto.autoencoder, path=dto.path)
|
||||
@@ -1,76 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
|
||||
from ...domain.model import Clip
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIClipDTO:
|
||||
"""Data Transfer Object for CLIP (Tokenizer and Text Encoder) in ComfyUI.
|
||||
|
||||
This DTO is designed to be used across different custom nodes.
|
||||
Other node developers can import and use this type for CLIP operations.
|
||||
|
||||
Attributes:
|
||||
tokenizer: CLIPTokenizer instance from transformers library
|
||||
text_encoder: CLIPTextModel instance from transformers library
|
||||
path: Path to the loaded model directory
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
|
||||
Example:
|
||||
```python
|
||||
from your_node_package.nodes.dto import ComfyUIClipDTO
|
||||
|
||||
class YourCustomNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"clip": (ComfyUIClipDTO.COMFY_TYPE,)}}
|
||||
|
||||
RETURN_TYPES = (ComfyUIClipDTO.COMFY_TYPE,)
|
||||
|
||||
def execute(self, clip: ComfyUIClipDTO):
|
||||
# Use the tokenizer and text encoder
|
||||
tokens = clip.tokenizer(prompt, return_tensors="pt")
|
||||
embeddings = clip.text_encoder(**tokens)
|
||||
return (embeddings,)
|
||||
```
|
||||
"""
|
||||
|
||||
# ClassVar to avoid being treated as a dataclass field
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_CLIP"
|
||||
|
||||
tokenizer: CLIPTokenizer
|
||||
text_encoder: CLIPTextModel
|
||||
path: str
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, clip: Clip) -> "ComfyUIClipDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
clip: Clip from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUIClipDTO instance
|
||||
"""
|
||||
return cls(
|
||||
tokenizer=clip.tokenizer,
|
||||
text_encoder=clip.text_encoder,
|
||||
path=clip.path,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUIClipDTO") -> "Clip":
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUIClipDTO instance
|
||||
|
||||
Returns:
|
||||
Clip domain model
|
||||
"""
|
||||
return Clip(tokenizer=dto.tokenizer, text_encoder=dto.text_encoder, path=dto.path)
|
||||
@@ -1,6 +0,0 @@
|
||||
from ._image_dto import ComfyUIImage, ComfyUIImageDTO
|
||||
|
||||
__all__ = [
|
||||
"ComfyUIImageDTO",
|
||||
"ComfyUIImage",
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import TypeAlias
|
||||
|
||||
import torch
|
||||
from torchvision.transforms import ToTensor
|
||||
|
||||
from ....domain.model import Image
|
||||
|
||||
ComfyUIImageDTO: TypeAlias = torch.Tensor
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIImage:
|
||||
COMFY_TYPE: str = "IMAGE"
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, image: Image) -> "ComfyUIImageDTO":
|
||||
tensor: torch.Tensor = ToTensor()(image.image)
|
||||
return tensor.permute(1, 2, 0)
|
||||
|
||||
@classmethod
|
||||
def from_domains(cls, images: list[Image]) -> "ComfyUIImageDTO":
|
||||
return torch.stack([cls.from_domain(image) for image in images])
|
||||
@@ -1,75 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
import torch
|
||||
|
||||
from ...domain.model import Conditioning
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIConditioningDTO:
|
||||
"""Data Transfer Object for Diffusers Conditioning in ComfyUI.
|
||||
|
||||
This DTO is designed to be used across different custom nodes.
|
||||
Other node developers can import and use this type for conditioning operations.
|
||||
|
||||
Attributes:
|
||||
conditioning: Tensor representing text embeddings/conditioning
|
||||
path: Path to the source model directory
|
||||
prompt: Prompt text that produced the conditioning
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
|
||||
Example:
|
||||
```python
|
||||
from your_node_package.nodes.dto import ComfyUIConditioningDTO
|
||||
|
||||
class YourCustomNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"conditioning": (ComfyUIConditioningDTO.COMFY_TYPE,)}}
|
||||
|
||||
RETURN_TYPES = (ComfyUIConditioningDTO.COMFY_TYPE,)
|
||||
|
||||
def execute(self, conditioning: ComfyUIConditioningDTO):
|
||||
# Use the conditioning tensor for generation
|
||||
result = process_with_conditioning(conditioning.conditioning)
|
||||
return (result,)
|
||||
```
|
||||
"""
|
||||
|
||||
# ClassVar to avoid being treated as a dataclass field
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_CONDITIONING"
|
||||
|
||||
conditioning: torch.Tensor
|
||||
path: str
|
||||
prompt: str
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, conditioning: Conditioning) -> "ComfyUIConditioningDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
conditioning: Conditioning from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUIConditioningDTO instance
|
||||
"""
|
||||
return cls(
|
||||
conditioning=conditioning.conditioning,
|
||||
path=conditioning.path,
|
||||
prompt=conditioning.prompt,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUIConditioningDTO") -> Conditioning:
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUIConditioningDTO instance
|
||||
|
||||
Returns:
|
||||
Conditioning domain model instance
|
||||
"""
|
||||
return Conditioning(conditioning=dto.conditioning, path=dto.path, prompt=dto.prompt)
|
||||
@@ -1,46 +0,0 @@
|
||||
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)
|
||||
@@ -1,68 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from diffusers import StableDiffusionPipeline
|
||||
|
||||
from ...domain.model import Pipeline
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUIPipelineDTO:
|
||||
"""Data Transfer Object for Diffusers Pipeline in ComfyUI.
|
||||
|
||||
This DTO is designed to be used across different custom nodes.
|
||||
Other node developers can import and use this type for pipeline operations.
|
||||
|
||||
Attributes:
|
||||
pipeline: StableDiffusionPipeline instance from diffusers library
|
||||
path: Path to the loaded model directory
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
|
||||
Example:
|
||||
```python
|
||||
from your_node_package.nodes.dto import ComfyUIPipelineDTO
|
||||
|
||||
class YourCustomNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"pipeline": (ComfyUIPipelineDTO.COMFY_TYPE,)}}
|
||||
|
||||
RETURN_TYPES = (ComfyUIPipelineDTO.COMFY_TYPE,)
|
||||
|
||||
def execute(self, pipeline: ComfyUIPipelineDTO):
|
||||
result = pipeline.pipeline(prompt="...")
|
||||
return (result,)
|
||||
```
|
||||
"""
|
||||
|
||||
# ClassVar to avoid being treated as a dataclass field
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_PIPELINE"
|
||||
|
||||
pipeline: StableDiffusionPipeline
|
||||
path: str
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, pipeline: Pipeline) -> "ComfyUIPipelineDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
pipeline: Pipeline from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUIPipelineDTO instance
|
||||
"""
|
||||
return cls(pipeline=pipeline.pipeline, path=pipeline.path)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUIPipelineDTO") -> Pipeline:
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUIPipelineDTO instance
|
||||
|
||||
Returns:
|
||||
Pipeline domain model instance
|
||||
"""
|
||||
return Pipeline(pipeline=dto.pipeline, path=dto.path)
|
||||
@@ -1,66 +0,0 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import ClassVar
|
||||
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
|
||||
from ...domain.model import Scheduler
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComfyUISchedulerDTO:
|
||||
"""Data Transfer Object for Diffusers Scheduler in ComfyUI.
|
||||
|
||||
This DTO is designed to be used across different custom nodes.
|
||||
Other node developers can import and use this type for scheduler operations.
|
||||
|
||||
Attributes:
|
||||
scheduler: SchedulerMixin instance from diffusers library
|
||||
path: Path to the source model directory
|
||||
|
||||
Class Attributes:
|
||||
COMFY_TYPE: Type name used in ComfyUI's RETURN_TYPES and INPUT_TYPES
|
||||
|
||||
Example:
|
||||
```python
|
||||
from your_node_package.nodes.dto import ComfyUISchedulerDTO
|
||||
|
||||
class YourCustomNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {"required": {"scheduler": (ComfyUISchedulerDTO.COMFY_TYPE,)}}
|
||||
|
||||
def execute(self, scheduler: ComfyUISchedulerDTO):
|
||||
# Use the scheduler for sampler operations
|
||||
pass
|
||||
```
|
||||
"""
|
||||
|
||||
# ClassVar to avoid being treated as a dataclass field
|
||||
COMFY_TYPE: ClassVar[str] = "DIFFUSERS_SCHEDULER"
|
||||
|
||||
scheduler: SchedulerMixin
|
||||
path: str
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, scheduler: Scheduler) -> "ComfyUISchedulerDTO":
|
||||
"""Create DTO from domain model.
|
||||
|
||||
Args:
|
||||
scheduler: Scheduler from domain layer
|
||||
|
||||
Returns:
|
||||
ComfyUISchedulerDTO instance
|
||||
"""
|
||||
return cls(scheduler=scheduler.scheduler, path=scheduler.path)
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, dto: "ComfyUISchedulerDTO") -> Scheduler:
|
||||
"""Convert DTO back to domain model.
|
||||
|
||||
Args:
|
||||
dto: ComfyUISchedulerDTO instance
|
||||
|
||||
Returns:
|
||||
Scheduler domain model
|
||||
"""
|
||||
return Scheduler(scheduler=dto.scheduler, path=dto.path)
|
||||
@@ -1,88 +0,0 @@
|
||||
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,5 +0,0 @@
|
||||
from ._scheduler_map import SchedulerMap
|
||||
|
||||
__all__ = [
|
||||
"SchedulerMap",
|
||||
]
|
||||
@@ -1,76 +0,0 @@
|
||||
from typing import Final
|
||||
|
||||
from ...domain.model import Scheduler
|
||||
|
||||
|
||||
class SchedulerMap:
|
||||
"""Maps between domain Scheduler.Type enum and ComfyUI string representation.
|
||||
|
||||
This map provides bidirectional conversion between the domain layer's
|
||||
type-safe enum and ComfyUI's string-based interface requirements.
|
||||
"""
|
||||
|
||||
SCHEDULERS: Final[list[str]] = [
|
||||
"DDIM",
|
||||
"DDPM",
|
||||
"DEISMultistep",
|
||||
"DPMSolverMultistep",
|
||||
"DPMSolverSinglestep",
|
||||
"EulerAncestralDiscrete",
|
||||
"EulerDiscrete",
|
||||
"HeunDiscrete",
|
||||
"KDPM2AncestralDiscrete",
|
||||
"KDPM2Discrete",
|
||||
"UniPCMultistep",
|
||||
]
|
||||
|
||||
_DOMAIN_TO_STRING: dict[Scheduler.Type, str] = {
|
||||
Scheduler.Type.DDIM: "DDIM",
|
||||
Scheduler.Type.DDPM: "DDPM",
|
||||
Scheduler.Type.DEISMultistep: "DEISMultistep",
|
||||
Scheduler.Type.DPMSolverMultistep: "DPMSolverMultistep",
|
||||
Scheduler.Type.DPMSolverSinglestep: "DPMSolverSinglestep",
|
||||
Scheduler.Type.EulerAncestralDiscrete: "EulerAncestralDiscrete",
|
||||
Scheduler.Type.EulerDiscrete: "EulerDiscrete",
|
||||
Scheduler.Type.HeunDiscrete: "HeunDiscrete",
|
||||
Scheduler.Type.KDPM2AncestralDiscrete: "KDPM2AncestralDiscrete",
|
||||
Scheduler.Type.KDPM2Discrete: "KDPM2Discrete",
|
||||
Scheduler.Type.UniPCMultistep: "UniPCMultistep",
|
||||
}
|
||||
|
||||
_STRING_TO_DOMAIN: dict[str, Scheduler.Type] = {v: k for k, v in _DOMAIN_TO_STRING.items()}
|
||||
|
||||
@classmethod
|
||||
def from_domain(cls, scheduler_type: Scheduler.Type) -> str:
|
||||
"""Convert domain Scheduler.Type enum to ComfyUI string representation.
|
||||
|
||||
Args:
|
||||
scheduler_type: Scheduler.Type enum value from domain layer
|
||||
|
||||
Returns:
|
||||
String representation for ComfyUI interface
|
||||
|
||||
Raises:
|
||||
KeyError: If the scheduler_type is not in the mapping
|
||||
"""
|
||||
return cls._DOMAIN_TO_STRING[scheduler_type]
|
||||
|
||||
@classmethod
|
||||
def to_domain(cls, scheduler_name: str) -> Scheduler.Type:
|
||||
"""Convert ComfyUI string to domain Scheduler.Type enum.
|
||||
|
||||
Args:
|
||||
scheduler_name: String representation from ComfyUI interface
|
||||
|
||||
Returns:
|
||||
Corresponding Scheduler.Type enum value from domain layer
|
||||
|
||||
Raises:
|
||||
ValueError: If the scheduler_name is not recognized
|
||||
"""
|
||||
if scheduler_name not in cls._STRING_TO_DOMAIN:
|
||||
raise ValueError(
|
||||
f"Unknown scheduler type: '{scheduler_name}'. "
|
||||
f"Available types: {', '.join(cls.SCHEDULERS)}"
|
||||
)
|
||||
return cls._STRING_TO_DOMAIN[scheduler_name]
|
||||
@@ -1,23 +0,0 @@
|
||||
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",
|
||||
"AutoencoderUsecase",
|
||||
"ClipTextEncodeUsecase",
|
||||
"SamplerUsecase",
|
||||
"SchedulerUsecase",
|
||||
"LcmLoraUsecase",
|
||||
"StreamDiffusionCreateStreamUsecase",
|
||||
"StreamDiffusionWarmupUsecase",
|
||||
"StreamDiffusionSampleUsecase",
|
||||
"StreamDiffusionFastSampleUsecase",
|
||||
]
|
||||
@@ -1,26 +0,0 @@
|
||||
import os
|
||||
|
||||
import torch
|
||||
|
||||
from ..domain.model import Autoencoder
|
||||
from ..domain.repositories import AutoencoderRepository
|
||||
|
||||
|
||||
class AutoencoderUsecase:
|
||||
def __init__(self, autoencoder_repo: AutoencoderRepository) -> None:
|
||||
self.autoencoder_repo = autoencoder_repo
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(self, vae_path: str) -> Autoencoder:
|
||||
if not os.path.exists(vae_path):
|
||||
raise FileNotFoundError(f"VAE file not found: {vae_path}")
|
||||
|
||||
path = self.autoencoder_repo.convert_and_save_from_single_file(vae_path, self.dtype)
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError(f"Failed to convert VAE checkpoint: {vae_path}")
|
||||
|
||||
vae = self.autoencoder_repo.load_autoencoder_from_path(path, self.dtype)
|
||||
if vae is None:
|
||||
raise RuntimeError(f"Failed to create autoencoder from checkpoint: {vae_path}")
|
||||
|
||||
return Autoencoder(autoencoder=vae, path=path)
|
||||
@@ -1,27 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from transformers import CLIPTextModel, CLIPTokenizer
|
||||
|
||||
from ..domain.model import Conditioning
|
||||
from ..domain.repositories._text_encoder_repository import TextEncoderRepository
|
||||
|
||||
|
||||
class ClipTextEncodeUsecase:
|
||||
def __init__(self, text_encoder_repo: TextEncoderRepository) -> None:
|
||||
self.text_encoder_repo = text_encoder_repo
|
||||
|
||||
def execute(
|
||||
self,
|
||||
tokenizer: CLIPTokenizer,
|
||||
text_encoder: CLIPTextModel,
|
||||
path: str,
|
||||
text: str,
|
||||
) -> Conditioning:
|
||||
if not text.strip():
|
||||
raise ValueError("Text input is empty or whitespace only.")
|
||||
|
||||
embeddings = self.text_encoder_repo.encode(tokenizer, text_encoder, text)
|
||||
if embeddings is None:
|
||||
raise RuntimeError("Failed to generate embeddings.")
|
||||
|
||||
return Conditioning(conditioning=embeddings, path=path, prompt=text)
|
||||
@@ -1,26 +0,0 @@
|
||||
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,33 +0,0 @@
|
||||
import os
|
||||
|
||||
import torch
|
||||
|
||||
from ..domain.model import Clip, Pipeline
|
||||
from ..domain.repositories import PipelineRepository
|
||||
|
||||
|
||||
class PipelineUsecase:
|
||||
def __init__(self, pipeline_repo: PipelineRepository) -> None:
|
||||
self.pipeline_repo = pipeline_repo
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(self, checkpoint_path: str) -> tuple[Pipeline, Clip]:
|
||||
if not os.path.exists(checkpoint_path):
|
||||
raise FileNotFoundError(f"Checkpoint file not found: {checkpoint_path}")
|
||||
|
||||
path = self.pipeline_repo.convert_and_save_from_single_file(checkpoint_path, self.dtype)
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError(f"Failed to convert pipeline checkpoint: {checkpoint_path}")
|
||||
|
||||
pipe = self.pipeline_repo.load_pipeline_from_path(path, self.dtype)
|
||||
if pipe is None:
|
||||
raise RuntimeError(f"Failed to create pipeline from checkpoint: {checkpoint_path}")
|
||||
|
||||
pipeline = Pipeline(pipeline=pipe, path=path)
|
||||
clip = Clip(
|
||||
tokenizer=pipe.tokenizer, # type: ignore[attr-defined]
|
||||
text_encoder=pipe.text_encoder, # type: ignore[attr-defined]
|
||||
path=checkpoint_path,
|
||||
)
|
||||
|
||||
return pipeline, clip
|
||||
@@ -1,51 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import torch
|
||||
|
||||
from ..domain.model import (
|
||||
Autoencoder,
|
||||
CFGScale,
|
||||
Conditioning,
|
||||
Image,
|
||||
ImageSize,
|
||||
Pipeline,
|
||||
Scheduler,
|
||||
Seed,
|
||||
Steps,
|
||||
)
|
||||
from ..domain.repositories import SamplerRepository
|
||||
|
||||
|
||||
class SamplerUsecase:
|
||||
def __init__(self, sampler_repo: SamplerRepository) -> None:
|
||||
self.sampler_repo = sampler_repo
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(
|
||||
self,
|
||||
pipeline: Pipeline,
|
||||
vae: Autoencoder,
|
||||
scheduler: Scheduler,
|
||||
positive_embeds: Conditioning,
|
||||
negative_embeds: Conditioning,
|
||||
image_size: ImageSize,
|
||||
steps: Steps,
|
||||
cfg: CFGScale,
|
||||
seed: Seed,
|
||||
) -> list[Image]:
|
||||
images = self.sampler_repo.sample(
|
||||
pipeline.pipeline,
|
||||
vae.autoencoder,
|
||||
scheduler.scheduler,
|
||||
positive_embeds.conditioning,
|
||||
negative_embeds.conditioning,
|
||||
image_size,
|
||||
steps,
|
||||
cfg,
|
||||
seed,
|
||||
)
|
||||
|
||||
if not images:
|
||||
raise RuntimeError("Sampler repository returned no images.")
|
||||
|
||||
return [Image(image=img) for img in images]
|
||||
@@ -1,20 +0,0 @@
|
||||
import torch
|
||||
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
||||
|
||||
from ..domain.model import Pipeline, Scheduler
|
||||
from ..domain.repositories import SchedulerRepository
|
||||
|
||||
|
||||
class SchedulerUsecase:
|
||||
def __init__(self, scheduler_repo: SchedulerRepository) -> None:
|
||||
self.scheduler_repo = scheduler_repo
|
||||
self.dtype = torch.float16
|
||||
|
||||
def execute(self, pipeline: Pipeline, scheduler_type: Scheduler.Type) -> Scheduler:
|
||||
scheduler_obj: SchedulerMixin = self.scheduler_repo.create_scheduler(
|
||||
pipeline.path,
|
||||
self.dtype,
|
||||
scheduler_type,
|
||||
)
|
||||
|
||||
return Scheduler(scheduler=scheduler_obj, path=pipeline.path)
|
||||
@@ -1,70 +0,0 @@
|
||||
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)
|
||||
@@ -1,40 +0,0 @@
|
||||
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]
|
||||
@@ -1,91 +0,0 @@
|
||||
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]
|
||||
@@ -1,51 +0,0 @@
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user